update shadcn components

This commit is contained in:
vcoppe
2025-10-18 18:51:11 +02:00
parent c59cd66141
commit e68da7354e
30 changed files with 405 additions and 310 deletions

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import { CircleHelp } from '@lucide/svelte';
import { CircleQuestionMark } from '@lucide/svelte';
import { i18n } from '$lib/i18n.svelte';
export let link: string | undefined = undefined;
@@ -8,7 +8,7 @@
<div
class="text-sm bg-secondary rounded border flex flex-row items-center p-2 {$$props.class || ''}"
>
<CircleHelp size="16" class="w-4 mr-2 shrink-0 grow-0" />
<CircleQuestionMark size="16" class="w-4 mr-2 shrink-0 grow-0" />
<div>
<slot />
{#if link}

View File

@@ -120,13 +120,13 @@
</Menubar.Trigger>
<Menubar.Content class="border-none">
<Menubar.Item onclick={createFile}>
<Plus size="16" class="mr-1" />
<Plus size="16" />
{i18n._('menu.new')}
<Shortcut key="+" ctrl={true} />
</Menubar.Item>
<Menubar.Separator />
<Menubar.Item onclick={triggerFileInput}>
<FolderOpen size="16" class="mr-1" />
<FolderOpen size="16" />
{i18n._('menu.open')}
<Shortcut key="O" ctrl={true} />
</Menubar.Item>
@@ -135,7 +135,7 @@
onclick={fileActions.duplicateSelection}
disabled={$selection.size == 0}
>
<Copy size="16" class="mr-1" />
<Copy size="16" />
{i18n._('menu.duplicate')}
<Shortcut key="D" ctrl={true} />
</Menubar.Item>
@@ -144,7 +144,7 @@
onclick={fileActions.deleteSelectedFiles}
disabled={$selection.size == 0}
>
<FileX size="16" class="mr-1" />
<FileX size="16" />
{i18n._('menu.close')}
<Shortcut key="⌫" ctrl={true} />
</Menubar.Item>
@@ -152,7 +152,7 @@
onclick={fileActions.deleteAllFiles}
disabled={fileStateCollection.size == 0}
>
<FileX size="16" class="mr-1" />
<FileX size="16" />
{i18n._('menu.close_all')}
<Shortcut key="⌫" ctrl={true} shift={true} />
</Menubar.Item>
@@ -161,7 +161,7 @@
onclick={() => (exportState.current = ExportState.SELECTION)}
disabled={$selection.size == 0}
>
<Download size="16" class="mr-1" />
<Download size="16" />
{i18n._('menu.export')}
<Shortcut key="S" ctrl={true} />
</Menubar.Item>
@@ -169,7 +169,7 @@
onclick={() => (exportState.current = ExportState.ALL)}
disabled={fileStateCollection.size == 0}
>
<Download size="16" class="mr-1" />
<Download size="16" />
{i18n._('menu.export_all')}
<Shortcut key="S" ctrl={true} shift={true} />
</Menubar.Item>
@@ -185,7 +185,7 @@
onclick={() => fileActionManager.undo()}
disabled={!fileActionManager.canUndo}
>
<Undo2 size="16" class="mr-1" />
<Undo2 size="16" />
{i18n._('menu.undo')}
<Shortcut key="Z" ctrl={true} />
</Menubar.Item>
@@ -193,7 +193,7 @@
onclick={() => fileActionManager.redo()}
disabled={!fileActionManager.canRedo}
>
<Redo2 size="16" class="mr-1" />
<Redo2 size="16" />
{i18n._('menu.redo')}
<Shortcut key="Z" ctrl={true} shift={true} />
</Menubar.Item>
@@ -209,7 +209,7 @@
)}
onclick={() => (editMetadata.current = true)}
>
<Info size="16" class="mr-1" />
<Info size="16" />
{i18n._('menu.metadata.button')}
<Shortcut key="I" ctrl={true} />
</Menubar.Item>
@@ -224,7 +224,7 @@
)}
onclick={() => (editStyle.current = true)}
>
<PaintBucket size="16" class="mr-1" />
<PaintBucket size="16" />
{i18n._('menu.style.button')}
</Menubar.Item>
<Menubar.Item
@@ -238,10 +238,10 @@
disabled={$selection.size == 0}
>
<!-- {#if $allHidden}
<Eye size="16" class="mr-1" />
<Eye size="16" />
{i18n._('menu.unhide')}
{:else}
<EyeOff size="16" class="mr-1" />
<EyeOff size="16" />
{i18n._('menu.hide')}
{/if} -->
<Shortcut key="H" ctrl={true} />
@@ -256,7 +256,7 @@
)}
disabled={$selection.size !== 1}
>
<Plus size="16" class="mr-1" />
<Plus size="16" />
{i18n._('menu.new_track')}
</Menubar.Item>
{:else if $selection
@@ -273,7 +273,7 @@
}}
disabled={$selection.size !== 1}
>
<Plus size="16" class="mr-1" />
<Plus size="16" />
{i18n._('menu.new_segment')}
</Menubar.Item>
{/if}
@@ -283,7 +283,7 @@
onclick={selection.selectAll}
disabled={fileStateCollection.size == 0}
>
<FileStack size="16" class="mr-1" />
<FileStack size="16" />
{i18n._('menu.select_all')}
<Shortcut key="A" ctrl={true} />
</Menubar.Item>
@@ -294,7 +294,7 @@
}
}}
>
<Maximize size="16" class="mr-1" />
<Maximize size="16" />
{i18n._('menu.center')}
<Shortcut key="⏎" ctrl={true} />
</Menubar.Item>
@@ -304,7 +304,7 @@
onclick={selection.copySelection}
disabled={$selection.size === 0}
>
<ClipboardCopy size="16" class="mr-1" />
<ClipboardCopy size="16" />
{i18n._('menu.copy')}
<Shortcut key="C" ctrl={true} />
</Menubar.Item>
@@ -312,7 +312,7 @@
onclick={selection.cutSelection}
disabled={$selection.size === 0}
>
<Scissors size="16" class="mr-1" />
<Scissors size="16" />
{i18n._('menu.cut')}
<Shortcut key="X" ctrl={true} />
</Menubar.Item>
@@ -325,7 +325,7 @@
))}
onclick={pasteSelection}
>
<ClipboardPaste size="16" class="mr-1" />
<ClipboardPaste size="16" />
{i18n._('menu.paste')}
<Shortcut key="V" ctrl={true} />
</Menubar.Item>
@@ -335,7 +335,7 @@
onclick={fileActions.deleteSelection}
disabled={$selection.size == 0}
>
<Trash2 size="16" class="mr-1" />
<Trash2 size="16" />
{i18n._('menu.delete')}
<Shortcut key="⌫" ctrl={true} />
</Menubar.Item>
@@ -348,42 +348,36 @@
</Menubar.Trigger>
<Menubar.Content class="border-none">
<Menubar.CheckboxItem bind:checked={$elevationProfile}>
<ChartArea size="16" class="mr-1" />
<ChartArea size="16" />
{i18n._('menu.elevation_profile')}
<Shortcut key="P" ctrl={true} />
</Menubar.CheckboxItem>
<Menubar.CheckboxItem bind:checked={$treeFileView}>
<ListTree size="16" class="mr-1" />
<ListTree size="16" />
{i18n._('menu.tree_file_view')}
<Shortcut key="L" ctrl={true} />
</Menubar.CheckboxItem>
<Menubar.Separator />
<Menubar.Item inset onclick={switchBasemaps}>
<Map size="16" class="mr-1" />{i18n._('menu.switch_basemap')}<Shortcut
key="F1"
/>
<Map size="16" />{i18n._('menu.switch_basemap')}<Shortcut key="F1" />
</Menubar.Item>
<Menubar.Item inset onclick={toggleOverlays}>
<Layers2 size="16" class="mr-1" />{i18n._('menu.toggle_overlays')}<Shortcut
key="F2"
/>
<Layers2 size="16" />{i18n._('menu.toggle_overlays')}<Shortcut key="F2" />
</Menubar.Item>
<Menubar.Separator />
<Menubar.CheckboxItem bind:checked={$distanceMarkers}>
<Coins size="16" class="mr-1" />{i18n._('menu.distance_markers')}<Shortcut
key="F3"
/>
<Coins size="16" />{i18n._('menu.distance_markers')}<Shortcut key="F3" />
</Menubar.CheckboxItem>
<Menubar.CheckboxItem bind:checked={$directionMarkers}>
<Milestone size="16" class="mr-1" />{i18n._(
'menu.direction_markers'
)}<Shortcut key="F4" />
<Milestone size="16" />{i18n._('menu.direction_markers')}<Shortcut
key="F4"
/>
</Menubar.CheckboxItem>
<Menubar.Separator />
<Menubar.Item inset onclick={map.toggle3D}>
<Box size="16" class="mr-1" />
<Box size="16" />
{i18n._('menu.toggle_3d')}
<Shortcut key="{i18n._('menu.ctrl')}+{i18n._('menu.drag')}" />
<Shortcut key="{i18n._('menu.ctrl')} {i18n._('menu.drag')}" />
</Menubar.Item>
</Menubar.Content>
</Menubar.Menu>
@@ -397,7 +391,7 @@
<Menubar.Content class="border-none">
<Menubar.Sub>
<Menubar.SubTrigger>
<Ruler size="16" class="mr-1" />{i18n._('menu.distance_units')}
<Ruler size="16" class="mr-2" />{i18n._('menu.distance_units')}
</Menubar.SubTrigger>
<Menubar.SubContent>
<Menubar.RadioGroup bind:value={$distanceUnits}>
@@ -415,7 +409,7 @@
</Menubar.Sub>
<Menubar.Sub>
<Menubar.SubTrigger>
<Zap size="16" class="mr-1" />{i18n._('menu.velocity_units')}
<Zap size="16" class="mr-2" />{i18n._('menu.velocity_units')}
</Menubar.SubTrigger>
<Menubar.SubContent>
<Menubar.RadioGroup bind:value={$velocityUnits}>
@@ -430,7 +424,7 @@
</Menubar.Sub>
<Menubar.Sub>
<Menubar.SubTrigger>
<Thermometer size="16" class="mr-1" />{i18n._('menu.temperature_units')}
<Thermometer size="16" class="mr-2" />{i18n._('menu.temperature_units')}
</Menubar.SubTrigger>
<Menubar.SubContent>
<Menubar.RadioGroup bind:value={$temperatureUnits}>
@@ -446,7 +440,7 @@
<Menubar.Separator />
<Menubar.Sub>
<Menubar.SubTrigger>
<Languages size="16" class="mr-1" />
<Languages size="16" class="mr-2" />
{i18n._('menu.language')}
</Menubar.SubTrigger>
<Menubar.SubContent>
@@ -462,9 +456,9 @@
<Menubar.Sub>
<Menubar.SubTrigger>
{#if mode.current === 'light' || !mode.current}
<Sun size="16" class="mr-1" />
<Sun size="16" class="mr-2" />
{:else}
<Moon size="16" class="mr-1" />
<Moon size="16" class="mr-2" />
{/if}
{i18n._('menu.mode')}
</Menubar.SubTrigger>
@@ -487,7 +481,7 @@
<Menubar.Separator />
<Menubar.Sub>
<Menubar.SubTrigger>
<PersonStanding size="16" class="mr-1" />
<PersonStanding size="16" class="mr-2" />
{i18n._('menu.street_view_source')}
</Menubar.SubTrigger>
<Menubar.SubContent>
@@ -502,7 +496,7 @@
</Menubar.SubContent>
</Menubar.Sub>
<Menubar.Item onclick={() => (layerSettingsOpen = true)}>
<Layers size="16" class="mr-1" />
<Layers size="16" />
{i18n._('menu.layers')}
</Menubar.Item>
</Menubar.Content>

View File

@@ -2,14 +2,24 @@
import { isMac, isSafari } from '$lib/utils';
import { onMount } from 'svelte';
import { i18n } from '$lib/i18n.svelte';
import * as Kbd from '$lib/components/ui/kbd/index.js';
export let key: string | undefined = undefined;
export let shift: boolean = false;
export let ctrl: boolean = false;
export let click: boolean = false;
let {
key = undefined,
shift = false,
ctrl = false,
click = false,
class: className = '',
}: {
key?: string;
shift?: boolean;
ctrl?: boolean;
click?: boolean;
class?: string;
} = $props();
let mac = false;
let safari = false;
let mac = $state(false);
let safari = $state(false);
onMount(() => {
mac = isMac();
@@ -17,20 +27,17 @@
});
</script>
<div
class="ml-auto pl-2 text-xs tracking-widest text-muted-foreground flex flex-row gap-0 items-baseline"
{...$$props}
>
<Kbd.Root class="ml-auto {className}">
{#if shift}
<span></span>
{/if}
{#if ctrl}
<span>{mac && !safari ? '⌘' : i18n._('menu.ctrl') + '+'}</span>
{mac && !safari ? '⌘' : i18n._('menu.ctrl')}
{/if}
{#if key}
<span class={key === '+' ? 'font-medium text-sm/4' : ''}>{key}</span>
{key}
{/if}
{#if click}
<span>{i18n._('menu.click')}</span>
{i18n._('menu.click')}
{/if}
</div>
</Kbd.Root>

View File

@@ -1,19 +1,31 @@
<script lang="ts">
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
import type { Snippet } from 'svelte';
export let label: string;
export let side: 'top' | 'right' | 'bottom' | 'left' = 'top';
let {
label,
side = 'top',
children,
extra,
class: className = '',
}: {
label: string;
side?: 'top' | 'right' | 'bottom' | 'left';
children: Snippet;
extra?: Snippet;
class?: string;
} = $props();
</script>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger {...$$restProps} aria-label={label}>
<slot />
<Tooltip.Trigger class={className} aria-label={label}>
{@render children()}
</Tooltip.Trigger>
<Tooltip.Content {side}>
<div class="flex flex-row items-center">
<div class="flex flex-row items-center gap-2">
<span>{label}</span>
<slot name="extra" />
{@render extra?.()}
</div>
</Tooltip.Content>
</Tooltip.Root>

View File

@@ -36,7 +36,7 @@
variant="ghost"
class="w-full flex flex-row {side === 'right'
? 'justify-between'
: 'justify-start'} py-0 px-1 h-fit {nohover
: 'justify-start'} p-0 has-[>svg]:px-0 h-fit {nohover
? 'hover:bg-background'
: ''} pointer-events-none"
>
@@ -62,7 +62,7 @@
variant="ghost"
class="w-full flex flex-row {side === 'right'
? 'justify-between'
: 'justify-start'} py-0 px-1 h-fit {nohover ? 'hover:bg-background' : ''}"
: 'justify-start'} p-0 has-[>svg]:px-0 h-fit {nohover ? 'hover:bg-background' : ''}"
>
{#if side === 'left'}
<Collapsible.Trigger>
@@ -86,7 +86,7 @@
</Button>
{/if}
<Collapsible.Content class="ml-2">
<Collapsible.Content>
{@render props.content()}
</Collapsible.Content>
</Collapsible.Root>

View File

@@ -312,10 +312,20 @@
<div class="flex flex-row items-center gap-2" data-id={id}>
<Move size="12" />
<span class="grow">{$customLayers[id].name}</span>
<Button variant="outline" onclick={() => (selectedLayerId = id)} class="p-1 h-7">
<Button
variant="outline"
size="icon-sm"
onclick={() => (selectedLayerId = id)}
class="p-1 h-7"
>
<Pencil size="16" />
</Button>
<Button variant="outline" onclick={() => deleteLayer(id)} class="p-1 h-7">
<Button
variant="outline"
size="icon-sm"
onclick={() => deleteLayer(id)}
class="p-1 h-7"
>
<Trash2 size="16" />
</Button>
</div>
@@ -338,17 +348,26 @@
<div class="flex flex-row items-center gap-2" data-id={id}>
<Move size="12" />
<span class="grow">{$customLayers[id].name}</span>
<Button variant="outline" onclick={() => (selectedLayerId = id)} class="p-1 h-7">
<Button
variant="outline"
size="icon-sm"
onclick={() => (selectedLayerId = id)}
class="p-1 h-7"
>
<Pencil size="16" />
</Button>
<Button variant="outline" onclick={() => deleteLayer(id)} class="p-1 h-7">
<Button
variant="outline"
size="icon-sm"
onclick={() => deleteLayer(id)}
class="p-1 h-7"
>
<Trash2 size="16" />
</Button>
</div>
{/each}
</div>
<Card.Root>
<Card.Root class="py-0 gap-0 shadow-none">
<Card.Header class="p-3">
<Card.Title class="text-base">
{#if selectedLayerId}

View File

@@ -179,9 +179,9 @@
? 'grid-rows-[1fr] grid-cols-[1fr]'
: ''} {cancelEvents ? 'pointer-events-none' : ''}"
>
<ScrollArea>
<ScrollArea class="overflow-hidden">
<div class="h-fit">
<div class="p-2">
<div class="p-2 ml-1">
<LayerTree
layerTree={$selectedBasemapTree}
name="basemaps"
@@ -193,7 +193,7 @@
/>
</div>
<Separator class="w-full" />
<div class="p-2">
<div class="p-2 ml-1">
{#if $currentOverlays}
<LayerTree
layerTree={$selectedOverlayTree}
@@ -204,7 +204,7 @@
{/if}
</div>
<Separator class="w-full" />
<div class="p-2">
<div class="p-2 ml-1">
{#if $currentOverpassQueries}
<LayerTree
layerTree={$selectedOverpassTree}

View File

@@ -90,7 +90,7 @@
<Accordion.Item value="layer-selection" class="flex flex-col">
<Accordion.Trigger>{i18n._('layers.selection')}</Accordion.Trigger>
<Accordion.Content class="grow flex flex-col border rounded">
<div class="py-2 pl-1 pr-2">
<div class="py-2 pl-3 pr-2">
<LayerTree
layerTree={basemapTree}
name="basemapSettings"
@@ -99,7 +99,7 @@
/>
</div>
<Separator />
<div class="py-2 pl-1 pr-2">
<div class="py-2 pl-3 pr-2">
<LayerTree
layerTree={overlayTree}
name="overlaySettings"
@@ -108,7 +108,7 @@
/>
</div>
<Separator />
<div class="py-2 pl-1 pr-2">
<div class="py-2 pl-3 pr-2">
<LayerTree
layerTree={overpassTree}
name="overpassSettings"
@@ -130,7 +130,7 @@
type="single"
onValueChange={setOpacityFromSelection}
>
<Select.Trigger class="h-8 mr-1">
<Select.Trigger class="h-8 mr-1 w-full">
{#if selectedOverlay}
{#if isSelected($selectedOverlayTree, selectedOverlay)}
{i18n._(`layers.label.${selectedOverlay}`)}

View File

@@ -10,6 +10,7 @@
import { settings } from '$lib/logic/settings';
import { i18n } from '$lib/i18n.svelte';
import { onMount } from 'svelte';
import ButtonWithTooltip from '$lib/components/ButtonWithTooltip.svelte';
const { streetViewSource } = settings;
@@ -47,15 +48,21 @@
</script>
<CustomControl class="w-[29px] h-[29px] shrink-0">
<Tooltip class="w-full h-full" side="left" label={i18n._('menu.toggle_street_view')}>
<Toggle
bind:pressed={$streetViewEnabled}
class="w-full h-full rounded p-0"
aria-label={i18n._('menu.toggle_street_view')}
>
<PersonStanding size="22" />
</Toggle>
</Tooltip>
<ButtonWithTooltip
variant="ghost"
class="w-full h-full"
side="left"
label={i18n._('menu.toggle_street_view')}
onclick={() => {
$streetViewEnabled = !$streetViewEnabled;
}}
>
<PersonStanding
size="22"
class="size-5.5"
color={$streetViewEnabled ? '#33b5e5' : 'currentColor'}
/>
</ButtonWithTooltip>
</CustomControl>
<div

View File

@@ -26,31 +26,31 @@
''}"
>
<ToolbarItem itemTool={Tool.ROUTING} label={i18n._('toolbar.routing.tooltip')}>
<Pencil size="18" />
<Pencil size="18" class="size-4.5" />
</ToolbarItem>
<ToolbarItem itemTool={Tool.WAYPOINT} label={i18n._('toolbar.waypoint.tooltip')}>
<MapPin size="18" />
<MapPin size="18" class="size-4.5" />
</ToolbarItem>
<ToolbarItem itemTool={Tool.SCISSORS} label={i18n._('toolbar.scissors.tooltip')}>
<Scissors size="18" />
<Scissors size="18" class="size-4.5" />
</ToolbarItem>
<ToolbarItem itemTool={Tool.TIME} label={i18n._('toolbar.time.tooltip')}>
<CalendarClock size="18" />
<CalendarClock size="18" class="size-4.5" />
</ToolbarItem>
<ToolbarItem itemTool={Tool.MERGE} label={i18n._('toolbar.merge.tooltip')}>
<Group size="18" />
<Group size="18" class="size-4.5" />
</ToolbarItem>
<ToolbarItem itemTool={Tool.EXTRACT} label={i18n._('toolbar.extract.tooltip')}>
<Ungroup size="18" />
<Ungroup size="18" class="size-4.5" />
</ToolbarItem>
<ToolbarItem itemTool={Tool.ELEVATION} label={i18n._('toolbar.elevation.button')}>
<MountainSnow size="18" />
<MountainSnow size="18" class="size-4.5" />
</ToolbarItem>
<ToolbarItem itemTool={Tool.REDUCE} label={i18n._('toolbar.reduce.tooltip')}>
<Funnel size="18" />
<Funnel size="18" class="size-4.5" />
</ToolbarItem>
<ToolbarItem itemTool={Tool.CLEAN} label={i18n._('toolbar.clean.tooltip')}>
<SquareDashedMousePointer size="18" />
<SquareDashedMousePointer size="18" class="size-4.5" />
</ToolbarItem>
</div>
<ToolbarItemMenu class={props.class ?? ''} />

View File

@@ -24,13 +24,13 @@
</script>
<Tooltip.Provider>
<Tooltip.Root delayDuration={300}>
<Tooltip.Root>
<Tooltip.Trigger>
{#snippet child({ props })}
<Button
{...props}
variant="ghost"
class="h-[26px] px-1 py-1.5 {$currentTool === itemTool ? 'bg-accent' : ''}"
class="size-[24px] {$currentTool === itemTool ? 'bg-accent' : ''}"
onclick={toggleTool}
aria-label={label}
>

View File

@@ -40,8 +40,8 @@
{#if $currentTool !== null}
<div class="translate-x-1 h-full animate-in animate-out {className}">
<div class="rounded-md shadow-md pointer-events-auto">
<Card.Root class="rounded-md border-none">
<Card.Content class="p-2.5">
<Card.Root class="rounded-md border-none py-2.5">
<Card.Content class="px-2.5">
{#if $currentTool === Tool.ROUTING}
<Routing {popup} {popupElement} bind:minimized={$minimizeRoutingMenu} />
{:else if $currentTool === Tool.SCISSORS}

View File

@@ -95,22 +95,14 @@
{:else if mergeType === MergeType.TRACES && !canMergeTraces}
{i18n._('toolbar.merge.help_cannot_merge_traces')}
{i18n._('toolbar.merge.selection_tip').split('{KEYBOARD_SHORTCUT}')[0]}
<Shortcut
ctrl={true}
click={true}
class="inline-flex text-muted-foreground text-xs border rounded p-0.5 gap-0"
/>
<Shortcut ctrl={true} click={true} class="border" />
{i18n._('toolbar.merge.selection_tip').split('{KEYBOARD_SHORTCUT}')[1]}
{:else if mergeType === MergeType.CONTENTS && canMergeContents}
{i18n._('toolbar.merge.help_merge_contents')}
{:else if mergeType === MergeType.CONTENTS && !canMergeContents}
{i18n._('toolbar.merge.help_cannot_merge_contents')}
{i18n._('toolbar.merge.selection_tip').split('{KEYBOARD_SHORTCUT}')[0]}
<Shortcut
ctrl={true}
click={true}
class="inline-flex text-muted-foreground text-xs border rounded p-0.5 gap-0"
/>
<Shortcut ctrl={true} click={true} class="border" />
{i18n._('toolbar.merge.selection_tip').split('{KEYBOARD_SHORTCUT}')[1]}
{/if}
</Help>

View File

@@ -110,7 +110,9 @@
onDestroy(() => {
if ($map) {
fileStateCollectionObserver.destroy();
if (fileStateCollectionObserver) {
fileStateCollectionObserver.destroy();
}
mapCursor.notify(MapCursorState.TOOL_WITH_CROSSHAIR, false);
$map.off('click', createFileWithPoint);
@@ -120,14 +122,14 @@
{#if minimizable && minimized}
<div class="-m-1.5 -mb-2">
<Button variant="ghost" class="px-1 h-[26px]" onclick={() => (minimized = false)}>
<SquareArrowOutDownRight size="18" />
<Button variant="ghost" size="icon-sm" class="size-6" onclick={() => (minimized = false)}>
<SquareArrowOutDownRight size="18" class="size-4.5" />
</Button>
</div>
{:else}
<div class="flex flex-col gap-3 w-full max-w-80 animate-in animate-out {className ?? ''}">
<div class="flex flex-col gap-3">
<Label class="flex flex-row justify-between items-center gap-2">
<Label class="justify-between">
<span class="flex flex-row items-center gap-1">
{#if $routing}
<Route size="16" />
@@ -137,13 +139,15 @@
{i18n._('toolbar.routing.use_routing')}
</span>
<Tooltip label={i18n._('toolbar.routing.use_routing_tooltip')}>
<Switch class="scale-90" bind:checked={$routing} />
<Shortcut slot="extra" key="F5" />
<Switch bind:checked={$routing} />
{#snippet extra()}
<Shortcut key="F5" />
{/snippet}
</Tooltip>
</Label>
{#if $routing}
<div class="flex flex-col gap-3" in:slide>
<Label class="flex flex-row justify-between items-center gap-2">
<Label class="justify-between">
<span class="shrink-0 flex flex-row items-center gap-1">
{#if $routingProfile.includes('bike') || $routingProfile.includes('motorcycle')}
<Bike size="16" />
@@ -171,12 +175,12 @@
</Select.Content>
</Select.Root>
</Label>
<Label class="flex flex-row justify-between items-center gap-2">
<Label class="justify-between">
<span class="flex flex-row gap-1">
<TriangleAlert size="16" />
{i18n._('toolbar.routing.allow_private')}
</span>
<Switch class="scale-90" bind:checked={$privateRoads} />
<Switch bind:checked={$privateRoads} />
</Label>
</div>
{/if}
@@ -185,7 +189,7 @@
<ButtonWithTooltip
label={i18n._('toolbar.routing.reverse.tooltip')}
variant="outline"
class="flex flex-row gap-1 text-xs px-2"
class="gap-1 text-xs"
disabled={!validSelection}
onclick={fileActions.reverseSelection}
>
@@ -194,7 +198,7 @@
<ButtonWithTooltip
label={i18n._('toolbar.routing.route_back_to_start.tooltip')}
variant="outline"
class="flex flex-row gap-1 text-xs px-2"
class="gap-1 text-xs"
disabled={!validSelection}
onclick={() => {
const selected = selection.getOrderedSelection();
@@ -230,7 +234,7 @@
<ButtonWithTooltip
label={i18n._('toolbar.routing.round_trip.tooltip')}
variant="outline"
class="flex flex-row gap-1 text-xs px-2"
class="gap-1 text-xs"
disabled={!validSelection}
onclick={fileActions.createRoundTripForSelection}
>
@@ -247,7 +251,8 @@
</Help>
<Button
variant="ghost"
class="px-1 h-6"
size="icon-sm"
class="size-6"
onclick={() => {
if (minimizable) {
minimized = true;

View File

@@ -1,80 +1,82 @@
<script lang="ts" module>
import { cn, type WithElementRef } from '$lib/utils.js';
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
import { type VariantProps, tv } from 'tailwind-variants';
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from "svelte/elements";
import { type VariantProps, tv } from "tailwind-variants";
export const buttonVariants = tv({
base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium outline-none transition-all focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
variants: {
variant: {
default: 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
destructive:
'bg-destructive shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white',
outline:
'bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border',
secondary: 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
sm: 'h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5',
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
icon: 'size-9',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
});
export const buttonVariants = tv({
base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium outline-none transition-all focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
variants: {
variant: {
default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
destructive:
"bg-destructive shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white",
outline:
"bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border",
secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
"icon-sm": "size-8",
"icon-lg": "size-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
});
export type ButtonVariant = VariantProps<typeof buttonVariants>['variant'];
export type ButtonSize = VariantProps<typeof buttonVariants>['size'];
export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"];
export type ButtonSize = VariantProps<typeof buttonVariants>["size"];
export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
WithElementRef<HTMLAnchorAttributes> & {
variant?: ButtonVariant;
size?: ButtonSize;
};
export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
WithElementRef<HTMLAnchorAttributes> & {
variant?: ButtonVariant;
size?: ButtonSize;
};
</script>
<script lang="ts">
let {
class: className,
variant = 'default',
size = 'default',
ref = $bindable(null),
href = undefined,
type = 'button',
disabled,
children,
...restProps
}: ButtonProps = $props();
let {
class: className,
variant = "default",
size = "default",
ref = $bindable(null),
href = undefined,
type = "button",
disabled,
children,
...restProps
}: ButtonProps = $props();
</script>
{#if href}
<a
bind:this={ref}
data-slot="button"
class={cn(buttonVariants({ variant, size }), className)}
href={disabled ? undefined : href}
aria-disabled={disabled}
role={disabled ? 'link' : undefined}
tabindex={disabled ? -1 : undefined}
{...restProps}
>
{@render children?.()}
</a>
<a
bind:this={ref}
data-slot="button"
class={cn(buttonVariants({ variant, size }), className)}
href={disabled ? undefined : href}
aria-disabled={disabled}
role={disabled ? "link" : undefined}
tabindex={disabled ? -1 : undefined}
{...restProps}
>
{@render children?.()}
</a>
{:else}
<button
bind:this={ref}
data-slot="button"
class={cn(buttonVariants({ variant, size }), className)}
{type}
{disabled}
{...restProps}
>
{@render children?.()}
</button>
<button
bind:this={ref}
data-slot="button"
class={cn(buttonVariants({ variant, size }), className)}
{type}
{disabled}
{...restProps}
>
{@render children?.()}
</button>
{/if}

View File

@@ -33,7 +33,7 @@
{@render children?.()}
{#if showCloseButton}
<DialogPrimitive.Close
class="ring-offset-background focus:ring-ring rounded-xs focus:outline-hidden absolute right-4 top-4 opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0"
class="ring-offset-background focus:ring-ring rounded-xs focus:outline-hidden absolute end-4 top-4 opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0"
>
<XIcon />
<span class="sr-only">Close</span>

View File

@@ -19,7 +19,7 @@
data-slot="dropdown-menu-content"
{sideOffset}
class={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 max-h-(--radix-dropdown-menu-content-available-height) origin-(--radix-dropdown-menu-content-transform-origin) z-50 min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border p-1 shadow-md",
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 max-h-(--bits-dropdown-menu-content-available-height) origin-(--bits-dropdown-menu-content-transform-origin) z-50 min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border p-1 shadow-md outline-none",
className
)}
{...restProps}

View File

@@ -13,7 +13,7 @@
bind:ref
data-slot="dropdown-menu-sub-content"
class={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-dropdown-menu-content-transform-origin) z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-lg",
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--bits-dropdown-menu-content-transform-origin) z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-lg",
className
)}
{...restProps}

View File

@@ -15,6 +15,7 @@
type,
files = $bindable(),
class: className,
"data-slot": dataSlot = "input",
...restProps
}: Props = $props();
</script>
@@ -22,9 +23,9 @@
{#if type === "file"}
<input
bind:this={ref}
data-slot="input"
data-slot={dataSlot}
class={cn(
"selection:bg-primary dark:bg-input/30 selection:text-primary-foreground border-input ring-offset-background placeholder:text-muted-foreground shadow-xs flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 pt-1.5 text-sm font-medium outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"selection:bg-primary dark:bg-input/30 selection:text-primary-foreground border-input ring-offset-background placeholder:text-muted-foreground shadow-xs flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 pt-1.5 text-sm font-medium outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
className
@@ -37,7 +38,7 @@
{:else}
<input
bind:this={ref}
data-slot="input"
data-slot={dataSlot}
class={cn(
"border-input bg-background selection:bg-primary dark:bg-input/30 selection:text-primary-foreground ring-offset-background placeholder:text-muted-foreground shadow-xs flex h-9 w-full min-w-0 rounded-md border px-3 py-1 text-base outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",

View File

@@ -0,0 +1,10 @@
import Root from "./kbd.svelte";
import Group from "./kbd-group.svelte";
export {
Root,
Group,
//
Root as Kbd,
Group as KbdGroup,
};

View File

@@ -0,0 +1,10 @@
<script lang="ts">
import { cn } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let { class: className, children, ...restProps }: HTMLAttributes<HTMLElement> = $props();
</script>
<kbd data-slot="kbd-group" class={cn("inline-flex items-center gap-1", className)} {...restProps}>
{@render children?.()}
</kbd>

View File

@@ -0,0 +1,19 @@
<script lang="ts">
import { cn } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let { class: className, children, ...restProps }: HTMLAttributes<HTMLElement> = $props();
</script>
<kbd
data-slot="kbd"
class={cn(
"bg-muted text-muted-foreground pointer-events-none inline-flex h-5 w-fit min-w-5 select-none items-center justify-center gap-1 rounded-sm px-1 font-sans text-xs font-medium",
"[&_svg:not([class*='size-'])]:size-3",
"[[data-slot=tooltip-content]_&]:bg-background/20 [[data-slot=tooltip-content]_&]:text-background dark:[[data-slot=tooltip-content]_&]:bg-background/10",
className
)}
{...restProps}
>
{@render children?.()}
</kbd>

View File

@@ -1,40 +1,40 @@
<script lang="ts">
import { ScrollArea as ScrollAreaPrimitive } from 'bits-ui';
import { Scrollbar } from './index.js';
import { cn, type WithoutChild } from '$lib/utils.js';
import { ScrollArea as ScrollAreaPrimitive } from "bits-ui";
import { Scrollbar } from "./index.js";
import { cn, type WithoutChild } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
orientation = 'vertical',
scrollbarXClasses = '',
scrollbarYClasses = '',
children,
...restProps
}: WithoutChild<ScrollAreaPrimitive.RootProps> & {
orientation?: 'vertical' | 'horizontal' | 'both' | undefined;
scrollbarXClasses?: string | undefined;
scrollbarYClasses?: string | undefined;
} = $props();
let {
ref = $bindable(null),
class: className,
orientation = "vertical",
scrollbarXClasses = "",
scrollbarYClasses = "",
children,
...restProps
}: WithoutChild<ScrollAreaPrimitive.RootProps> & {
orientation?: "vertical" | "horizontal" | "both" | undefined;
scrollbarXClasses?: string | undefined;
scrollbarYClasses?: string | undefined;
} = $props();
</script>
<ScrollAreaPrimitive.Root
bind:ref
data-slot="scroll-area"
class={cn('relative overflow-hidden', className)}
{...restProps}
bind:ref
data-slot="scroll-area"
class={cn("relative", className)}
{...restProps}
>
<ScrollAreaPrimitive.Viewport
data-slot="scroll-area-viewport"
class="ring-ring/10 dark:ring-ring/20 dark:outline-ring/40 outline-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] focus-visible:outline-1 focus-visible:ring-4"
>
{@render children?.()}
</ScrollAreaPrimitive.Viewport>
{#if orientation === 'vertical' || orientation === 'both'}
<Scrollbar orientation="vertical" class={scrollbarYClasses} />
{/if}
{#if orientation === 'horizontal' || orientation === 'both'}
<Scrollbar orientation="horizontal" class={scrollbarXClasses} />
{/if}
<ScrollAreaPrimitive.Corner />
<ScrollAreaPrimitive.Viewport
data-slot="scroll-area-viewport"
class="ring-ring/10 dark:ring-ring/20 dark:outline-ring/40 outline-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] focus-visible:outline-1 focus-visible:ring-4"
>
{@render children?.()}
</ScrollAreaPrimitive.Viewport>
{#if orientation === "vertical" || orientation === "both"}
<Scrollbar orientation="vertical" class={scrollbarYClasses} />
{/if}
{#if orientation === "horizontal" || orientation === "both"}
<Scrollbar orientation="horizontal" class={scrollbarXClasses} />
{/if}
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>

View File

@@ -5,13 +5,14 @@
let {
ref = $bindable(null),
class: className,
"data-slot": dataSlot = "separator",
...restProps
}: SeparatorPrimitive.RootProps = $props();
</script>
<SeparatorPrimitive.Root
bind:ref
data-slot="separator"
data-slot={dataSlot}
class={cn(
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=vertical]:h-full data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px",
className

View File

@@ -8,6 +8,6 @@
<Sonner
theme={mode.current}
class="toaster group"
style="--normal-bg: var(--popover); --normal-text: var(--popover-foreground); --normal-border: var(--border);"
style="--normal-bg: var(--color-popover); --normal-text: var(--color-popover-foreground); --normal-border: var(--color-border);"
{...restProps}
/>

View File

@@ -6,13 +6,14 @@
ref = $bindable(null),
value = $bindable(),
class: className,
"data-slot": dataSlot = "textarea",
...restProps
}: WithoutChildren<WithElementRef<HTMLTextareaAttributes>> = $props();
</script>
<textarea
bind:this={ref}
data-slot="textarea"
data-slot={dataSlot}
class={cn(
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 field-sizing-content shadow-xs flex min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base outline-none transition-[color,box-shadow] focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className

View File

@@ -34,9 +34,9 @@
class={cn(
"bg-primary z-50 size-2.5 rotate-45 rounded-[2px]",
"data-[side=top]:translate-x-1/2 data-[side=top]:translate-y-[calc(-50%_+_2px)]",
"data-[side=bottom]:-translate-y-[calc(-50%_+_1px)] data-[side=bottom]:translate-x-1/2",
"data-[side=bottom]:-translate-x-1/2 data-[side=bottom]:-translate-y-[calc(-50%_+_1px)]",
"data-[side=right]:translate-x-[calc(50%_+_2px)] data-[side=right]:translate-y-1/2",
"data-[side=left]:translate-y-[calc(50%_-_3px)]",
"data-[side=left]:-translate-y-[calc(50%_-_3px)]",
arrowClasses
)}
{...props}