Merge branch 'dev'

This commit is contained in:
vcoppe
2026-04-06 18:26:22 +02:00
164 changed files with 1157 additions and 726 deletions

View File

@@ -9,7 +9,6 @@
"version": "0.0.1", "version": "0.0.1",
"dependencies": { "dependencies": {
"@docsearch/js": "^3.9.0", "@docsearch/js": "^3.9.0",
"@internationalized/date": "^3.8.2",
"@mapbox/sphericalmercator": "^2.0.1", "@mapbox/sphericalmercator": "^2.0.1",
"@mapbox/tilebelt": "^2.0.2", "@mapbox/tilebelt": "^2.0.2",
"@maplibre/maplibre-gl-geocoder": "^1.9.4", "@maplibre/maplibre-gl-geocoder": "^1.9.4",
@@ -28,6 +27,7 @@
"tailwind-merge": "^3.3.0" "tailwind-merge": "^3.3.0"
}, },
"devDependencies": { "devDependencies": {
"@internationalized/date": "^3.12.0",
"@lucide/svelte": "^0.544.0", "@lucide/svelte": "^0.544.0",
"@sveltejs/adapter-static": "^3.0.8", "@sveltejs/adapter-static": "^3.0.8",
"@sveltejs/enhanced-img": "^0.6.0", "@sveltejs/enhanced-img": "^0.6.0",
@@ -44,7 +44,7 @@
"@types/sortablejs": "^1.15.8", "@types/sortablejs": "^1.15.8",
"@typescript-eslint/eslint-plugin": "^8.33.1", "@typescript-eslint/eslint-plugin": "^8.33.1",
"@typescript-eslint/parser": "^8.33.1", "@typescript-eslint/parser": "^8.33.1",
"bits-ui": "^2.14.4", "bits-ui": "^2.17.2",
"eslint": "^9.28.0", "eslint": "^9.28.0",
"eslint-config-prettier": "^10.1.5", "eslint-config-prettier": "^10.1.5",
"eslint-plugin-svelte": "^3.9.1", "eslint-plugin-svelte": "^3.9.1",
@@ -1884,16 +1884,6 @@
} }
} }
}, },
"node_modules/@emnapi/core": {
"version": "1.9.2",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.2.1",
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/runtime": { "node_modules/@emnapi/runtime": {
"version": "1.9.2", "version": "1.9.2",
"dev": true, "dev": true,
@@ -1903,15 +1893,6 @@
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
}, },
"node_modules/@emnapi/wasi-threads": {
"version": "1.2.1",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@esbuild/aix-ppc64": { "node_modules/@esbuild/aix-ppc64": {
"version": "0.27.7", "version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
@@ -3170,6 +3151,9 @@
}, },
"node_modules/@internationalized/date": { "node_modules/@internationalized/date": {
"version": "3.12.0", "version": "3.12.0",
"resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.0.tgz",
"integrity": "sha512-/PyIMzK29jtXaGU23qTvNZxvBXRtKbNnGDFD+PY6CZw/Y8Ex8pFUzkuCJCG9aOqmShjqhS9mPqP6Dk5onQY8rQ==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@swc/helpers": "^0.5.0" "@swc/helpers": "^0.5.0"
@@ -3366,23 +3350,6 @@
"pbf": "bin/pbf" "pbf": "bin/pbf"
} }
}, },
"node_modules/@napi-rs/wasm-runtime": {
"version": "1.1.2",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@tybys/wasm-util": "^0.10.1"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
},
"peerDependencies": {
"@emnapi/core": "^1.7.1",
"@emnapi/runtime": "^1.7.1"
}
},
"node_modules/@polka/url": { "node_modules/@polka/url": {
"version": "1.0.0-next.29", "version": "1.0.0-next.29",
"dev": true, "dev": true,
@@ -3922,6 +3889,7 @@
}, },
"node_modules/@swc/helpers": { "node_modules/@swc/helpers": {
"version": "0.5.21", "version": "0.5.21",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"tslib": "^2.8.0" "tslib": "^2.8.0"
@@ -4203,15 +4171,6 @@
"vite": "^5.2.0 || ^6 || ^7 || ^8" "vite": "^5.2.0 || ^6 || ^7 || ^8"
} }
}, },
"node_modules/@tybys/wasm-util": {
"version": "0.10.1",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@types/cookie": { "node_modules/@types/cookie": {
"version": "0.6.0", "version": "0.6.0",
"dev": true, "dev": true,

View File

@@ -14,6 +14,7 @@
"format": "prettier --write . --config ../.prettierrc --ignore-path ../.prettierignore --ignore-path ./.gitignore" "format": "prettier --write . --config ../.prettierrc --ignore-path ../.prettierignore --ignore-path ./.gitignore"
}, },
"devDependencies": { "devDependencies": {
"@internationalized/date": "^3.12.0",
"@lucide/svelte": "^0.544.0", "@lucide/svelte": "^0.544.0",
"@sveltejs/adapter-static": "^3.0.8", "@sveltejs/adapter-static": "^3.0.8",
"@sveltejs/enhanced-img": "^0.6.0", "@sveltejs/enhanced-img": "^0.6.0",
@@ -30,7 +31,7 @@
"@types/sortablejs": "^1.15.8", "@types/sortablejs": "^1.15.8",
"@typescript-eslint/eslint-plugin": "^8.33.1", "@typescript-eslint/eslint-plugin": "^8.33.1",
"@typescript-eslint/parser": "^8.33.1", "@typescript-eslint/parser": "^8.33.1",
"bits-ui": "^2.14.4", "bits-ui": "^2.17.2",
"eslint": "^9.28.0", "eslint": "^9.28.0",
"eslint-config-prettier": "^10.1.5", "eslint-config-prettier": "^10.1.5",
"eslint-plugin-svelte": "^3.9.1", "eslint-plugin-svelte": "^3.9.1",
@@ -59,7 +60,6 @@
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@docsearch/js": "^3.9.0", "@docsearch/js": "^3.9.0",
"@internationalized/date": "^3.8.2",
"@mapbox/sphericalmercator": "^2.0.1", "@mapbox/sphericalmercator": "^2.0.1",
"@mapbox/tilebelt": "^2.0.2", "@mapbox/tilebelt": "^2.0.2",
"@maplibre/maplibre-gl-geocoder": "^1.9.4", "@maplibre/maplibre-gl-geocoder": "^1.9.4",

View File

@@ -33,7 +33,7 @@
<Card.Root <Card.Root
class="h-full {orientation === 'vertical' class="h-full {orientation === 'vertical'
? 'min-w-40 sm:min-w-44' ? 'min-w-40 sm:min-w-44'
: 'w-full h-fit my-1'} border-none shadow-none p-0 text-sm sm:text-base bg-transparent" : 'w-full h-fit my-1'} ring-0 p-0 text-sm sm:text-base bg-transparent"
> >
<Card.Content class="h-full p-0"> <Card.Content class="h-full p-0">
<div <div

View File

@@ -389,7 +389,7 @@
<Menubar.Content class="border-none"> <Menubar.Content class="border-none">
<Menubar.Sub> <Menubar.Sub>
<Menubar.SubTrigger> <Menubar.SubTrigger>
<Ruler size="16" class="mr-2" />{i18n._('menu.distance_units')} <Ruler size="16" />{i18n._('menu.distance_units')}
</Menubar.SubTrigger> </Menubar.SubTrigger>
<Menubar.SubContent> <Menubar.SubContent>
<Menubar.RadioGroup bind:value={$distanceUnits}> <Menubar.RadioGroup bind:value={$distanceUnits}>
@@ -407,7 +407,7 @@
</Menubar.Sub> </Menubar.Sub>
<Menubar.Sub> <Menubar.Sub>
<Menubar.SubTrigger> <Menubar.SubTrigger>
<Zap size="16" class="mr-2" />{i18n._('menu.velocity_units')} <Zap size="16" />{i18n._('menu.velocity_units')}
</Menubar.SubTrigger> </Menubar.SubTrigger>
<Menubar.SubContent> <Menubar.SubContent>
<Menubar.RadioGroup bind:value={$velocityUnits}> <Menubar.RadioGroup bind:value={$velocityUnits}>
@@ -422,7 +422,7 @@
</Menubar.Sub> </Menubar.Sub>
<Menubar.Sub> <Menubar.Sub>
<Menubar.SubTrigger> <Menubar.SubTrigger>
<Thermometer size="16" class="mr-2" />{i18n._('menu.temperature_units')} <Thermometer size="16" />{i18n._('menu.temperature_units')}
</Menubar.SubTrigger> </Menubar.SubTrigger>
<Menubar.SubContent> <Menubar.SubContent>
<Menubar.RadioGroup bind:value={$temperatureUnits}> <Menubar.RadioGroup bind:value={$temperatureUnits}>
@@ -438,7 +438,7 @@
<Menubar.Separator /> <Menubar.Separator />
<Menubar.Sub> <Menubar.Sub>
<Menubar.SubTrigger> <Menubar.SubTrigger>
<Languages size="16" class="mr-2" /> <Languages size="16" />
{i18n._('menu.language')} {i18n._('menu.language')}
</Menubar.SubTrigger> </Menubar.SubTrigger>
<Menubar.SubContent> <Menubar.SubContent>
@@ -454,9 +454,9 @@
<Menubar.Sub> <Menubar.Sub>
<Menubar.SubTrigger> <Menubar.SubTrigger>
{#if mode.current === 'light' || !mode.current} {#if mode.current === 'light' || !mode.current}
<Sun size="16" class="mr-2" /> <Sun size="16" />
{:else} {:else}
<Moon size="16" class="mr-2" /> <Moon size="16" />
{/if} {/if}
{i18n._('menu.mode')} {i18n._('menu.mode')}
</Menubar.SubTrigger> </Menubar.SubTrigger>
@@ -479,7 +479,7 @@
<Menubar.Separator /> <Menubar.Separator />
<Menubar.Sub> <Menubar.Sub>
<Menubar.SubTrigger> <Menubar.SubTrigger>
<PersonStanding size="16" class="mr-2" /> <PersonStanding size="16" />
{i18n._('menu.street_view_source')} {i18n._('menu.street_view_source')}
</Menubar.SubTrigger> </Menubar.SubTrigger>
<Menubar.SubContent> <Menubar.SubContent>
@@ -500,12 +500,12 @@
</Menubar.Content> </Menubar.Content>
</Menubar.Menu> </Menubar.Menu>
</Menubar.Root> </Menubar.Root>
<div class="h-fit flex flex-row items-center ml-1 gap-1"> <div class="h-fit flex flex-row items-center">
<Button <Button
variant="ghost" variant="ghost"
href="./help" href="./help"
target="_blank" target="_blank"
class="cursor-default h-fit rounded-sm px-3 py-0.5" class="cursor-default h-fit rounded-md px-3 py-0.5"
aria-label={i18n._('menu.help')} aria-label={i18n._('menu.help')}
> >
<BookOpenText size="18" class="md:hidden" /> <BookOpenText size="18" class="md:hidden" />
@@ -517,7 +517,7 @@
variant="ghost" variant="ghost"
href="https://opencollective.com/gpxstudio" href="https://opencollective.com/gpxstudio"
target="_blank" target="_blank"
class="cursor-default h-fit rounded-sm font-bold text-support hover:text-support px-3 py-0.5" class="cursor-default h-fit rounded-md font-bold text-support hover:text-support px-3 py-0.5"
aria-label={i18n._('menu.donate')} aria-label={i18n._('menu.donate')}
> >
<HeartHandshake size="18" class="md:hidden" /> <HeartHandshake size="18" class="md:hidden" />

View File

@@ -35,7 +35,7 @@
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
class="w-full flex flex-row gap-1 {side === 'right' class="w-full flex flex-row gap-1 border-none {side === 'right'
? 'justify-between' ? 'justify-between'
: 'justify-start pl-1'} h-fit {nohover : 'justify-start pl-1'} h-fit {nohover
? 'hover:bg-background' ? 'hover:bg-background'
@@ -62,7 +62,7 @@
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
class="w-full flex flex-row gap-1 {side === 'right' class="w-full flex flex-row gap-1 border-none {side === 'right'
? 'justify-between' ? 'justify-between'
: 'justify-start pl-1'} h-fit {nohover ? 'hover:bg-background' : ''}" : 'justify-start pl-1'} h-fit {nohover ? 'hover:bg-background' : ''}"
> >

View File

@@ -81,20 +81,18 @@
</ButtonWithTooltip> </ButtonWithTooltip>
</Popover.Trigger> </Popover.Trigger>
<Popover.Content <Popover.Content
class="w-fit p-0 flex flex-col" class="w-fit p-0 flex flex-col gap-0 overflow-hidden"
side="top" side="top"
align="end" align="end"
sideOffset={-32} sideOffset={-32}
> >
<ToggleGroup.Root <ToggleGroup.Root
class="flex flex-col items-start gap-0 p-1 w-full border-none" class="flex flex-col w-full border-none"
type="single" type="single"
size="sm"
bind:value={$elevationFill} bind:value={$elevationFill}
> >
<ToggleGroup.Item <ToggleGroup.Item value="slope" class="w-full flex flex-row justify-start">
class="p-0 pr-1.5 h-6 w-full gap-1.5 rounded flex justify-start data-[state=on]:bg-background data-[state=on]:hover:bg-accent hover:bg-accent hover:text-foreground"
value="slope"
>
<div class="w-6 flex justify-center items-center"> <div class="w-6 flex justify-center items-center">
{#if $elevationFill === 'slope'} {#if $elevationFill === 'slope'}
<Circle class="size-1.5 fill-current text-current" /> <Circle class="size-1.5 fill-current text-current" />
@@ -104,9 +102,8 @@
{i18n._('quantities.slope')} {i18n._('quantities.slope')}
</ToggleGroup.Item> </ToggleGroup.Item>
<ToggleGroup.Item <ToggleGroup.Item
class="p-0 pr-1.5 h-6 w-full gap-1.5 rounded flex justify-start data-[state=on]:bg-background data-[state=on]:hover:bg-accent hover:bg-accent hover:text-foreground"
value="surface" value="surface"
variant="outline" class="w-full flex flex-row justify-start"
> >
<div class="w-6 flex justify-center items-center"> <div class="w-6 flex justify-center items-center">
{#if $elevationFill === 'surface'} {#if $elevationFill === 'surface'}
@@ -117,9 +114,8 @@
{i18n._('quantities.surface')} {i18n._('quantities.surface')}
</ToggleGroup.Item> </ToggleGroup.Item>
<ToggleGroup.Item <ToggleGroup.Item
class="p-0 pr-1.5 h-6 w-full gap-1.5 rounded flex justify-start data-[state=on]:bg-background data-[state=on]:hover:bg-accent hover:bg-accent hover:text-foreground"
value="highway" value="highway"
variant="outline" class="w-full flex flex-row justify-start"
> >
<div class="w-6 flex justify-center items-center"> <div class="w-6 flex justify-center items-center">
{#if $elevationFill === 'highway'} {#if $elevationFill === 'highway'}
@@ -132,14 +128,12 @@
</ToggleGroup.Root> </ToggleGroup.Root>
<Separator /> <Separator />
<ToggleGroup.Root <ToggleGroup.Root
class="flex flex-col items-start gap-0 p-1" class="flex flex-col gap-0"
type="multiple" type="multiple"
size="sm"
bind:value={$additionalDatasets} bind:value={$additionalDatasets}
> >
<ToggleGroup.Item <ToggleGroup.Item value="speed" class="w-full flex flex-row justify-start">
class="p-0 pr-1.5 h-6 w-full gap-1.5 rounded flex justify-start data-[state=on]:bg-background data-[state=on]:hover:bg-accent hover:bg-accent hover:text-foreground"
value="speed"
>
<div class="w-6 flex justify-center items-center"> <div class="w-6 flex justify-center items-center">
{#if $additionalDatasets.includes('speed')} {#if $additionalDatasets.includes('speed')}
<Check size="14" /> <Check size="14" />
@@ -150,10 +144,7 @@
? i18n._('quantities.speed') ? i18n._('quantities.speed')
: i18n._('quantities.pace')} : i18n._('quantities.pace')}
</ToggleGroup.Item> </ToggleGroup.Item>
<ToggleGroup.Item <ToggleGroup.Item value="hr" class="w-full flex flex-row justify-start">
class="p-0 pr-1.5 h-6 w-full gap-1.5 rounded flex justify-start data-[state=on]:bg-background data-[state=on]:hover:bg-accent hover:bg-accent hover:text-foreground"
value="hr"
>
<div class="w-6 flex justify-center items-center"> <div class="w-6 flex justify-center items-center">
{#if $additionalDatasets.includes('hr')} {#if $additionalDatasets.includes('hr')}
<Check size="14" /> <Check size="14" />
@@ -162,10 +153,7 @@
<HeartPulse size="15" /> <HeartPulse size="15" />
{i18n._('quantities.heartrate')} {i18n._('quantities.heartrate')}
</ToggleGroup.Item> </ToggleGroup.Item>
<ToggleGroup.Item <ToggleGroup.Item value="cad" class="w-full flex flex-row justify-start">
class="p-0 pr-1.5 h-6 w-full gap-1.5 rounded flex justify-start data-[state=on]:bg-background data-[state=on]:hover:bg-accent hover:bg-accent hover:text-foreground"
value="cad"
>
<div class="w-6 flex justify-center items-center"> <div class="w-6 flex justify-center items-center">
{#if $additionalDatasets.includes('cad')} {#if $additionalDatasets.includes('cad')}
<Check size="14" /> <Check size="14" />
@@ -174,10 +162,7 @@
<Orbit size="15" /> <Orbit size="15" />
{i18n._('quantities.cadence')} {i18n._('quantities.cadence')}
</ToggleGroup.Item> </ToggleGroup.Item>
<ToggleGroup.Item <ToggleGroup.Item value="atemp" class="w-full flex flex-row justify-start">
class="p-0 pr-1.5 h-6 w-full gap-1.5 rounded flex justify-start data-[state=on]:bg-background data-[state=on]:hover:bg-accent hover:bg-accent hover:text-foreground"
value="atemp"
>
<div class="w-6 flex justify-center items-center"> <div class="w-6 flex justify-center items-center">
{#if $additionalDatasets.includes('atemp')} {#if $additionalDatasets.includes('atemp')}
<Check size="14" /> <Check size="14" />
@@ -186,10 +171,7 @@
<Thermometer size="15" /> <Thermometer size="15" />
{i18n._('quantities.temperature')} {i18n._('quantities.temperature')}
</ToggleGroup.Item> </ToggleGroup.Item>
<ToggleGroup.Item <ToggleGroup.Item value="power" class="w-full flex flex-row justify-start">
class="p-0 pr-1.5 h-6 w-full gap-1.5 rounded flex justify-start data-[state=on]:bg-background data-[state=on]:hover:bg-accent hover:bg-accent hover:text-foreground"
value="power"
>
<div class="w-6 flex justify-center items-center"> <div class="w-6 flex justify-center items-center">
{#if $additionalDatasets.includes('power')} {#if $additionalDatasets.includes('power')}
<Check size="14" /> <Check size="14" />

View File

@@ -114,10 +114,10 @@
<ContextMenu.Trigger class="grow truncate"> <ContextMenu.Trigger class="grow truncate">
<Button <Button
variant="ghost" variant="ghost"
class="relative w-full p-0 overflow-hidden focus-visible:ring-0 focus-visible:ring-offset-0 {orientation === class="relative w-full p-0 overflow-hidden border-none focus-visible:ring-0 focus-visible:ring-offset-0 flex flex-row {orientation ===
'vertical' 'vertical'
? 'h-fit' ? 'h-7'
: 'h-9 px-1.5 shadow-md'} pointer-events-auto" : 'h-9 px-1.5'} pointer-events-auto"
> >
{#if item instanceof ListFileItem || item instanceof ListTrackItem} {#if item instanceof ListFileItem || item instanceof ListTrackItem}
<MetadataDialog bind:open={openEditMetadata} {node} {item} /> <MetadataDialog bind:open={openEditMetadata} {node} {item} />
@@ -126,7 +126,7 @@
{#if item.level === ListLevel.FILE || item.level === ListLevel.TRACK} {#if item.level === ListLevel.FILE || item.level === ListLevel.TRACK}
<div <div
class="absolute {orientation === 'vertical' class="absolute {orientation === 'vertical'
? 'top-0 bottom-0 right-1 w-1' ? 'top-0 bottom-0 right-0 w-1'
: 'top-0 h-1 left-0 right-0'}" : 'top-0 h-1 left-0 right-0'}"
style="background:linear-gradient(to {orientation === 'vertical' style="background:linear-gradient(to {orientation === 'vertical'
? 'bottom' ? 'bottom'
@@ -139,7 +139,7 @@
></div> ></div>
{/if} {/if}
<span <span
class="w-full text-left truncate py-1 flex flex-row items-center {hidden class="grow text-left truncate ml-1 flex flex-row items-center {hidden
? 'text-muted-foreground' ? 'text-muted-foreground'
: ''} {$cut && $copied?.some((i) => i.getFullId() === item.getFullId()) : ''} {$cut && $copied?.some((i) => i.getFullId() === item.getFullId())
? 'text-muted-foreground' ? 'text-muted-foreground'

View File

@@ -16,7 +16,6 @@
</script> </script>
<Button <Button
size="sm"
class="justify-start {className}" class="justify-start {className}"
variant="outline" variant="outline"
onclick={() => { onclick={() => {

View File

@@ -39,7 +39,6 @@
/> />
{#if trackpoint.fileId === undefined} {#if trackpoint.fileId === undefined}
<Button <Button
size="sm"
variant="outline" variant="outline"
class="justify-start" class="justify-start"
href={`https://www.openstreetmap.org/edit?#map=${(($map?.getZoom() ?? 17) + 1).toFixed(0)}/${trackpoint.item.getLatitude().toFixed(5)}/${trackpoint.item.getLongitude().toFixed(5)}`} href={`https://www.openstreetmap.org/edit?#map=${(($map?.getZoom() ?? 17) + 1).toFixed(0)}/${trackpoint.item.getLatitude().toFixed(5)}/${trackpoint.item.getLongitude().toFixed(5)}`}

View File

@@ -88,7 +88,6 @@
<CopyCoordinates coordinates={waypoint.item.attributes} /> <CopyCoordinates coordinates={waypoint.item.attributes} />
{#if $currentTool === Tool.WAYPOINT && selected} {#if $currentTool === Tool.WAYPOINT && selected}
<Button <Button
class="p-1 has-[>svg]:px-2 h-8"
variant="outline" variant="outline"
onclick={() => { onclick={() => {
if (waypoint.fileId) { if (waypoint.fileId) {

View File

@@ -232,16 +232,13 @@
<div class="flex flex-col"> <div class="flex flex-col">
{#if $customBasemapOrder.length > 0} {#if $customBasemapOrder.length > 0}
<div class="px-3 py-2">
<div class="flex flex-row items-center gap-1 font-semibold mb-2"> <div class="flex flex-row items-center gap-1 font-semibold mb-2">
<Map size="16" /> <Map size="16" />
{i18n._('layers.label.basemaps')} {i18n._('layers.label.basemaps')}
<div class="grow">
<Separator />
</div> </div>
</div>
{/if}
<div <div
class="ml-1.5 flex flex-col gap-1 {$customBasemapOrder.length > 0 ? 'mb-2' : ''}" class="ml-1.5 flex flex-col gap-1"
use:dndzone={{ use:dndzone={{
items: customBasemapItems, items: customBasemapItems,
type: 'basemap', type: 'basemap',
@@ -258,14 +255,17 @@
onfinalize={(e) => { onfinalize={(e) => {
customBasemapItems = e.detail.items; customBasemapItems = e.detail.items;
$customBasemapOrder = customBasemapItems.map((item) => item.id); $customBasemapOrder = customBasemapItems.map((item) => item.id);
$selectedBasemapTree.basemaps['custom'] = customBasemapItems.reduce((acc, item) => { $selectedBasemapTree.basemaps['custom'] = customBasemapItems.reduce(
(acc, item) => {
acc[item.id] = true; acc[item.id] = true;
return acc; return acc;
}, {}); },
{}
);
}} }}
> >
{#each customBasemapItems as item (item.id)} {#each customBasemapItems as item (item.id)}
<div class="flex flex-row items-center gap-2"> <div class="flex flex-row items-center gap-1">
<Move size="12" /> <Move size="12" />
<span class="grow">{item.name}</span> <span class="grow">{item.name}</span>
<Button <Button
@@ -287,17 +287,18 @@
</div> </div>
{/each} {/each}
</div> </div>
</div>
<Separator />
{/if}
{#if $customOverlayOrder.length > 0} {#if $customOverlayOrder.length > 0}
<div class="px-3 py-2">
<div class="flex flex-row items-center gap-1 font-semibold mb-2"> <div class="flex flex-row items-center gap-1 font-semibold mb-2">
<Layers2 size="16" /> <Layers2 size="16" />
{i18n._('layers.label.overlays')} {i18n._('layers.label.overlays')}
<div class="grow"> <div class="grow"></div>
<Separator />
</div> </div>
</div>
{/if}
<div <div
class="ml-1.5 flex flex-col gap-1 {$customOverlayOrder.length > 0 ? 'mb-2' : ''}" class="ml-1.5 flex flex-col gap-1"
use:dndzone={{ use:dndzone={{
items: customOverlayItems, items: customOverlayItems,
type: 'overlay', type: 'overlay',
@@ -314,14 +315,17 @@
onfinalize={(e) => { onfinalize={(e) => {
customOverlayItems = e.detail.items; customOverlayItems = e.detail.items;
$customOverlayOrder = customOverlayItems.map((item) => item.id); $customOverlayOrder = customOverlayItems.map((item) => item.id);
$selectedOverlayTree.overlays['custom'] = customOverlayItems.reduce((acc, item) => { $selectedOverlayTree.overlays['custom'] = customOverlayItems.reduce(
(acc, item) => {
acc[item.id] = true; acc[item.id] = true;
return acc; return acc;
}, {}); },
{}
);
}} }}
> >
{#each customOverlayItems as item (item.id)} {#each customOverlayItems as item (item.id)}
<div class="flex flex-row items-center gap-2"> <div class="flex flex-row items-center gap-1">
<Move size="12" /> <Move size="12" />
<span class="grow">{item.name}</span> <span class="grow">{item.name}</span>
<Button <Button
@@ -343,9 +347,12 @@
</div> </div>
{/each} {/each}
</div> </div>
<Card.Root class="py-0 gap-0 shadow-none"> </div>
<Separator />
{/if}
<Card.Root class="py-0 gap-0 shadow-none ring-0">
<Card.Header class="p-3"> <Card.Header class="p-3">
<Card.Title class="text-base"> <Card.Title class="text-sm font-semibold">
{#if selectedLayerId} {#if selectedLayerId}
{i18n._('layers.custom_layers.edit')} {i18n._('layers.custom_layers.edit')}
{:else} {:else}
@@ -353,7 +360,7 @@
{/if} {/if}
</Card.Title> </Card.Title>
</Card.Header> </Card.Header>
<Card.Content class="p-3 pt-0"> <Card.Content class="px-3 py-2">
<fieldset class="flex flex-col gap-2"> <fieldset class="flex flex-col gap-2">
<Label for="name">{i18n._('menu.metadata.name')}</Label> <Label for="name">{i18n._('menu.metadata.name')}</Label>
<Input bind:value={name} id="name" class="h-8" /> <Input bind:value={name} id="name" class="h-8" />
@@ -410,7 +417,7 @@
</div> </div>
</RadioGroup.Root> </RadioGroup.Root>
{#if selectedLayerId} {#if selectedLayerId}
<div class="mt-2 flex flex-row gap-2"> <div class="mt-2 flex flex-row gap-1">
<Button variant="outline" onclick={createLayer} class="grow"> <Button variant="outline" onclick={createLayer} class="grow">
<Save size="16" /> <Save size="16" />
{i18n._('layers.custom_layers.update')} {i18n._('layers.custom_layers.update')}

View File

@@ -121,7 +121,7 @@
<Accordion.Root class="flex flex-col" bind:value={accordionValue} type="single"> <Accordion.Root class="flex flex-col" bind:value={accordionValue} type="single">
<Accordion.Item value="layer-selection" class="flex flex-col"> <Accordion.Item value="layer-selection" class="flex flex-col">
<Accordion.Trigger>{i18n._('layers.selection')}</Accordion.Trigger> <Accordion.Trigger>{i18n._('layers.selection')}</Accordion.Trigger>
<Accordion.Content class="grow flex flex-col border rounded"> <Accordion.Content class="grow flex flex-col border rounded-md mb-1.5">
<div class="py-2 pl-3 pr-2"> <div class="py-2 pl-3 pr-2">
<LayerTree <LayerTree
layerTree={basemapTree} layerTree={basemapTree}
@@ -152,7 +152,9 @@
</Accordion.Item> </Accordion.Item>
<Accordion.Item value="overlay-opacity"> <Accordion.Item value="overlay-opacity">
<Accordion.Trigger>{i18n._('layers.opacity')}</Accordion.Trigger> <Accordion.Trigger>{i18n._('layers.opacity')}</Accordion.Trigger>
<Accordion.Content class="flex flex-col gap-3 overflow-visible"> <Accordion.Content
class="flex flex-col gap-3 overflow-visible border rounded-md px-3 py-2 mb-1.5"
>
<div class="flex flex-row gap-6 items-center"> <div class="flex flex-row gap-6 items-center">
<Label> <Label>
{i18n._('layers.custom_layers.overlay')} {i18n._('layers.custom_layers.overlay')}
@@ -231,10 +233,10 @@
<Accordion.Item value="custom-layers"> <Accordion.Item value="custom-layers">
<Accordion.Trigger>{i18n._('layers.custom_layers.title')}</Accordion.Trigger <Accordion.Trigger>{i18n._('layers.custom_layers.title')}</Accordion.Trigger
> >
<Accordion.Content> <Accordion.Content
<ScrollArea> class="flex flex-col overflow-visible border rounded-md p-0 mb-1.5"
>
<CustomLayers /> <CustomLayers />
</ScrollArea>
</Accordion.Content> </Accordion.Content>
</Accordion.Item> </Accordion.Item>
<Accordion.Item value="terrain-source"> <Accordion.Item value="terrain-source">

View File

@@ -53,7 +53,7 @@
<CustomControl class="w-[29px] h-[29px] shrink-0"> <CustomControl class="w-[29px] h-[29px] shrink-0">
<ButtonWithTooltip <ButtonWithTooltip
variant="ghost" variant="ghost"
class="w-full h-full" class="w-full h-full border-none rounded-sm"
side="left" side="left"
label={i18n._('menu.toggle_street_view')} label={i18n._('menu.toggle_street_view')}
onclick={() => { onclick={() => {

View File

@@ -17,7 +17,7 @@
<div class="flex flex-col gap-3 w-full max-w-80 {props.class ?? ''}"> <div class="flex flex-col gap-3 w-full max-w-80 {props.class ?? ''}">
<Button <Button
variant="outline" variant="outline"
class="whitespace-normal h-fit" class="whitespace-normal h-fit min-h-8 py-1"
disabled={!validSelection} disabled={!validSelection}
onclick={() => fileActions.addElevationToSelection()} onclick={() => fileActions.addElevationToSelection()}
> >

View File

@@ -76,7 +76,7 @@
{/if} {/if}
<Button <Button
variant="outline" variant="outline"
class="whitespace-normal h-fit" class="whitespace-normal h-fit min-h-8 py-1"
disabled={(mergeType === MergeType.TRACES && !canMergeTraces) || disabled={(mergeType === MergeType.TRACES && !canMergeTraces) ||
(mergeType === MergeType.CONTENTS && !canMergeContents)} (mergeType === MergeType.CONTENTS && !canMergeContents)}
onclick={() => { onclick={() => {

View File

@@ -185,8 +185,8 @@
<div class="flex flex-col gap-3 w-full max-w-80 {props.class ?? ''}"> <div class="flex flex-col gap-3 w-full max-w-80 {props.class ?? ''}">
<fieldset class="flex flex-col gap-2"> <fieldset class="flex flex-col gap-2">
<div class="flex flex-row gap-2 justify-center"> <div class="flex flex-row gap-1.5 justify-center">
<div class="flex flex-col gap-2 grow"> <div class="flex flex-col gap-1 grow">
<Label for="speed" class="flex flex-row"> <Label for="speed" class="flex flex-row">
<Zap size="16" /> <Zap size="16" />
{#if $velocityUnits === 'speed'} {#if $velocityUnits === 'speed'}
@@ -239,7 +239,7 @@
{/if} {/if}
</div> </div>
</div> </div>
<div class="flex flex-col gap-2 grow"> <div class="flex flex-col gap-1 grow">
<Label for="duration" class="flex flex-row"> <Label for="duration" class="flex flex-row">
<Timer size="16" /> <Timer size="16" />
{i18n._('toolbar.time.total_time')} {i18n._('toolbar.time.total_time')}
@@ -253,11 +253,12 @@
/> />
</div> </div>
</div> </div>
<div class="flex flex-col gap-1">
<Label class="flex flex-row"> <Label class="flex flex-row">
<CirclePlay size="16" /> <CirclePlay size="16" />
{i18n._('toolbar.time.start')} {i18n._('toolbar.time.start')}
</Label> </Label>
<div class="flex flex-row gap-2"> <div class="flex flex-row gap-1.5">
<DatePicker <DatePicker
bind:value={startDate} bind:value={startDate}
disabled={!canUpdate} disabled={!canUpdate}
@@ -279,11 +280,13 @@
}} }}
/> />
</div> </div>
</div>
<div class="flex flex-col gap-1">
<Label class="flex flex-row"> <Label class="flex flex-row">
<CircleStop size="16" /> <CircleStop size="16" />
{i18n._('toolbar.time.end')} {i18n._('toolbar.time.end')}
</Label> </Label>
<div class="flex flex-row gap-2"> <div class="flex flex-row gap-1.5">
<DatePicker <DatePicker
bind:value={endDate} bind:value={endDate}
disabled={!canUpdate} disabled={!canUpdate}
@@ -305,6 +308,7 @@
}} }}
/> />
</div> </div>
</div>
{#if $gpxStatistics.global.time.moving === 0 || $gpxStatistics.global.time.moving === undefined} {#if $gpxStatistics.global.time.moving === 0 || $gpxStatistics.global.time.moving === undefined}
<div class="mt-0.5 flex flex-row gap-1 items-center"> <div class="mt-0.5 flex flex-row gap-1 items-center">
<Checkbox id="artificial-time" bind:checked={artificial} disabled={!canUpdate} /> <Checkbox id="artificial-time" bind:checked={artificial} disabled={!canUpdate} />
@@ -314,11 +318,11 @@
</div> </div>
{/if} {/if}
</fieldset> </fieldset>
<div class="flex flex-row gap-2 items-center"> <div class="flex flex-row gap-1.5 items-center">
<Button <Button
variant="outline" variant="outline"
disabled={!canUpdate} disabled={!canUpdate}
class="grow whitespace-normal h-fit" class="grow shrink whitespace-normal h-fit min-h-8 py-1"
onclick={() => { onclick={() => {
let effectiveSpeed = getSpeed(); let effectiveSpeed = getSpeed();
if ( if (

View File

@@ -14,7 +14,7 @@
let props: { class?: string } = $props(); let props: { class?: string } = $props();
let sliderValue = $state([50]); let sliderValue = $state(50);
const maxTolerance = 10000; const maxTolerance = 10000;
let validSelection = $derived( let validSelection = $derived(
@@ -25,7 +25,7 @@
$effect(() => { $effect(() => {
tolerance.set( tolerance.set(
minTolerance * 2 ** (sliderValue[0] / (100 / Math.log2(maxTolerance / minTolerance))) minTolerance * 2 ** (sliderValue / (100 / Math.log2(maxTolerance / minTolerance)))
); );
}); });
@@ -36,7 +36,7 @@
<div class="flex flex-col gap-3 w-full max-w-80 {props.class ?? ''}"> <div class="flex flex-col gap-3 w-full max-w-80 {props.class ?? ''}">
<div class="p-2"> <div class="p-2">
<Slider bind:value={sliderValue} min={0} max={100} step={1} type="multiple" /> <Slider bind:value={sliderValue} min={0} max={100} step={1} type="single" />
</div> </div>
<Label class="flex flex-row justify-between"> <Label class="flex flex-row justify-between">
<span>{i18n._('toolbar.reduce.tolerance')}</span> <span>{i18n._('toolbar.reduce.tolerance')}</span>

View File

@@ -191,7 +191,7 @@
<ButtonWithTooltip <ButtonWithTooltip
label={i18n._('toolbar.routing.reverse.tooltip')} label={i18n._('toolbar.routing.reverse.tooltip')}
variant="outline" variant="outline"
class="gap-1 text-xs" class="gap-1 text-xs px-1.5 py-1.5 h-fit"
disabled={!validSelection} disabled={!validSelection}
onclick={fileActions.reverseSelection} onclick={fileActions.reverseSelection}
> >
@@ -200,7 +200,7 @@
<ButtonWithTooltip <ButtonWithTooltip
label={i18n._('toolbar.routing.route_back_to_start.tooltip')} label={i18n._('toolbar.routing.route_back_to_start.tooltip')}
variant="outline" variant="outline"
class="gap-1 text-xs" class="gap-1 text-xs px-1.5 py-1.5 h-fit"
disabled={!validSelection} disabled={!validSelection}
onclick={() => { onclick={() => {
const selected = selection.getOrderedSelection(); const selected = selection.getOrderedSelection();
@@ -236,14 +236,14 @@
<ButtonWithTooltip <ButtonWithTooltip
label={i18n._('toolbar.routing.round_trip.tooltip')} label={i18n._('toolbar.routing.round_trip.tooltip')}
variant="outline" variant="outline"
class="gap-1 text-xs" class="gap-1 text-xs px-1.5 py-1.5 h-fit"
disabled={!validSelection} disabled={!validSelection}
onclick={fileActions.createRoundTripForSelection} onclick={fileActions.createRoundTripForSelection}
> >
<Repeat class="size-3" />{i18n._('toolbar.routing.round_trip.button')} <Repeat class="size-3" />{i18n._('toolbar.routing.round_trip.button')}
</ButtonWithTooltip> </ButtonWithTooltip>
</div> </div>
<div class="w-full flex flex-row gap-2 items-end justify-between"> <div class="w-full flex flex-row gap-1 items-end justify-between">
<Help link={getURLForLanguage(i18n.lang, '/help/toolbar/routing')}> <Help link={getURLForLanguage(i18n.lang, '/help/toolbar/routing')}>
{#if !validSelection} {#if !validSelection}
{i18n._('toolbar.routing.help_no_file')} {i18n._('toolbar.routing.help_no_file')}

View File

@@ -161,14 +161,17 @@
</script> </script>
<div class="flex flex-col gap-3 w-full max-w-96 {props.class ?? ''}"> <div class="flex flex-col gap-3 w-full max-w-96 {props.class ?? ''}">
<fieldset class="flex flex-col gap-2"> <fieldset class="flex flex-col gap-1.5">
<div class="flex flex-col gap-1">
<Label for="name">{i18n._('menu.metadata.name')}</Label> <Label for="name">{i18n._('menu.metadata.name')}</Label>
<Input <Input
bind:value={name} bind:value={name}
id="name" id="name"
class="font-semibold h-8" class="font-semibold"
disabled={!canCreate && !$selectedWaypoint} disabled={!canCreate && !$selectedWaypoint}
/> />
</div>
<div class="flex flex-col gap-1">
<Label for="description">{i18n._('menu.metadata.description')}</Label> <Label for="description">{i18n._('menu.metadata.description')}</Label>
<Textarea <Textarea
bind:value={description} bind:value={description}
@@ -176,11 +179,12 @@
disabled={!canCreate && !$selectedWaypoint} disabled={!canCreate && !$selectedWaypoint}
class="min-h-8 h-8 py-1 px-3 text-sm" class="min-h-8 h-8 py-1 px-3 text-sm"
/> />
</div>
<div class="flex flex-col gap-1">
<Label for="symbol">{i18n._('toolbar.waypoint.icon')}</Label> <Label for="symbol">{i18n._('toolbar.waypoint.icon')}</Label>
<Select.Root bind:value={sym} type="single"> <Select.Root bind:value={sym} type="single">
<Select.Trigger <Select.Trigger
id="symbol" id="symbol"
size="sm"
class="w-full" class="w-full"
disabled={!canCreate && !$selectedWaypoint} disabled={!canCreate && !$selectedWaypoint}
> >
@@ -196,7 +200,7 @@
{/if} {/if}
</span> </span>
</Select.Trigger> </Select.Trigger>
<Select.Content class="max-h-60 overflow-y-scroll"> <Select.Content class="max-h-60">
{#each sortedSymbols as [key, symbol]} {#each sortedSymbols as [key, symbol]}
<Select.Item value={symbol.value}> <Select.Item value={symbol.value}>
<span> <span>
@@ -212,6 +216,8 @@
{/each} {/each}
</Select.Content> </Select.Content>
</Select.Root> </Select.Root>
</div>
<div class="flex flex-col gap-1">
<Label for="link">{i18n._('toolbar.waypoint.link')}</Label> <Label for="link">{i18n._('toolbar.waypoint.link')}</Label>
<Input <Input
bind:value={link} bind:value={link}
@@ -219,8 +225,9 @@
class="h-8" class="h-8"
disabled={!canCreate && !$selectedWaypoint} disabled={!canCreate && !$selectedWaypoint}
/> />
<div class="flex flex-row gap-2"> </div>
<div class="grow flex flex-col gap-2"> <div class="flex flex-row gap-1.5">
<div class="grow flex flex-col gap-1">
<Label for="latitude">{i18n._('toolbar.waypoint.latitude')}</Label> <Label for="latitude">{i18n._('toolbar.waypoint.latitude')}</Label>
<Input <Input
bind:value={latitude} bind:value={latitude}
@@ -233,7 +240,7 @@
disabled={!canCreate && !$selectedWaypoint} disabled={!canCreate && !$selectedWaypoint}
/> />
</div> </div>
<div class="grow flex flex-col gap-2"> <div class="grow flex flex-col gap-1">
<Label for="longitude">{i18n._('toolbar.waypoint.longitude')}</Label> <Label for="longitude">{i18n._('toolbar.waypoint.longitude')}</Label>
<Input <Input
bind:value={longitude} bind:value={longitude}
@@ -248,11 +255,11 @@
</div> </div>
</div> </div>
</fieldset> </fieldset>
<div class="flex flex-row gap-2 items-center"> <div class="flex flex-row gap-1.5 items-center">
<Button <Button
variant="outline" variant="outline"
disabled={!canCreate && !$selectedWaypoint} disabled={!canCreate && !$selectedWaypoint}
class="grow whitespace-normal h-fit" class="grow shrink h-fit min-h-8 whitespace-normal py-1"
onclick={createOrUpdateWaypoint} onclick={createOrUpdateWaypoint}
> >
{#if $selectedWaypoint} {#if $selectedWaypoint}

View File

@@ -13,10 +13,15 @@
<AccordionPrimitive.Content <AccordionPrimitive.Content
bind:ref bind:ref
data-slot="accordion-content" data-slot="accordion-content"
class="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm" class="data-open:animate-accordion-down data-closed:animate-accordion-up text-sm overflow-hidden"
{...restProps} {...restProps}
> >
<div class={cn("pb-4 pt-0", className)}> <div
class={cn(
"pt-0 pb-2.5 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3 [&_p:not(:last-child)]:mb-4",
className
)}
>
{@render children?.()} {@render children?.()}
</div> </div>
</AccordionPrimitive.Content> </AccordionPrimitive.Content>

View File

@@ -12,6 +12,6 @@
<AccordionPrimitive.Item <AccordionPrimitive.Item
bind:ref bind:ref
data-slot="accordion-item" data-slot="accordion-item"
class={cn("border-b last:border-b-0", className)} class={cn("not-last:border-b", className)}
{...restProps} {...restProps}
/> />

View File

@@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import { Accordion as AccordionPrimitive } from "bits-ui"; import { Accordion as AccordionPrimitive } from "bits-ui";
import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
import { cn, type WithoutChild } from "$lib/utils.js"; import { cn, type WithoutChild } from "$lib/utils.js";
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
import ChevronUpIcon from '@lucide/svelte/icons/chevron-up';
let { let {
ref = $bindable(null), ref = $bindable(null),
@@ -19,14 +20,13 @@
data-slot="accordion-trigger" data-slot="accordion-trigger"
bind:ref bind:ref
class={cn( class={cn(
"focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium outline-none transition-all hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180", "focus-visible:ring-ring/50 focus-visible:border-ring focus-visible:after:border-ring **:data-[slot=accordion-trigger-icon]:text-muted-foreground rounded-lg py-2.5 text-left text-sm font-medium hover:underline focus-visible:ring-3 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 group/accordion-trigger relative flex flex-1 items-start justify-between border border-transparent transition-all outline-none disabled:pointer-events-none disabled:opacity-50",
className className
)} )}
{...restProps} {...restProps}
> >
{@render children?.()} {@render children?.()}
<ChevronDownIcon <ChevronDownIcon data-slot="accordion-trigger-icon" class="cn-accordion-trigger-icon pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden" />
class="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" <ChevronUpIcon data-slot="accordion-trigger-icon" class="cn-accordion-trigger-icon pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline" />
/>
</AccordionPrimitive.Trigger> </AccordionPrimitive.Trigger>
</AccordionPrimitive.Header> </AccordionPrimitive.Header>

View File

@@ -1,9 +1,11 @@
<script lang="ts"> <script lang="ts">
import { Accordion as AccordionPrimitive } from "bits-ui"; import { Accordion as AccordionPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let { let {
ref = $bindable(null), ref = $bindable(null),
value = $bindable(), value = $bindable(),
class: className,
...restProps ...restProps
}: AccordionPrimitive.RootProps = $props(); }: AccordionPrimitive.RootProps = $props();
</script> </script>
@@ -12,5 +14,6 @@
bind:ref bind:ref
bind:value={value as never} bind:value={value as never}
data-slot="accordion" data-slot="accordion"
class={cn("cn-accordion flex w-full flex-col", className)}
{...restProps} {...restProps}
/> />

View File

@@ -1,18 +1,27 @@
<script lang="ts"> <script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from "bits-ui"; import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
import { buttonVariants } from "$lib/components/ui/button/index.js"; import {
buttonVariants,
type ButtonVariant,
type ButtonSize,
} from "$lib/components/ui/button/index.js";
import { cn } from "$lib/utils.js"; import { cn } from "$lib/utils.js";
let { let {
ref = $bindable(null), ref = $bindable(null),
class: className, class: className,
variant = "default",
size = "default",
...restProps ...restProps
}: AlertDialogPrimitive.ActionProps = $props(); }: AlertDialogPrimitive.ActionProps & {
variant?: ButtonVariant;
size?: ButtonSize;
} = $props();
</script> </script>
<AlertDialogPrimitive.Action <AlertDialogPrimitive.Action
bind:ref bind:ref
data-slot="alert-dialog-action" data-slot="alert-dialog-action"
class={cn(buttonVariants(), className)} class={cn(buttonVariants({ variant, size }), "cn-alert-dialog-action", className)}
{...restProps} {...restProps}
/> />

View File

@@ -1,18 +1,27 @@
<script lang="ts"> <script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from "bits-ui"; import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
import { buttonVariants } from "$lib/components/ui/button/index.js"; import {
buttonVariants,
type ButtonVariant,
type ButtonSize,
} from "$lib/components/ui/button/index.js";
import { cn } from "$lib/utils.js"; import { cn } from "$lib/utils.js";
let { let {
ref = $bindable(null), ref = $bindable(null),
class: className, class: className,
variant = "outline",
size = "default",
...restProps ...restProps
}: AlertDialogPrimitive.CancelProps = $props(); }: AlertDialogPrimitive.CancelProps & {
variant?: ButtonVariant;
size?: ButtonSize;
} = $props();
</script> </script>
<AlertDialogPrimitive.Cancel <AlertDialogPrimitive.Cancel
bind:ref bind:ref
data-slot="alert-dialog-cancel" data-slot="alert-dialog-cancel"
class={cn(buttonVariants({ variant: "outline" }), className)} class={cn(buttonVariants({ variant, size }), "cn-alert-dialog-cancel", className)}
{...restProps} {...restProps}
/> />

View File

@@ -1,27 +1,32 @@
<script lang="ts"> <script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from "bits-ui"; import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
import AlertDialogPortal from "./alert-dialog-portal.svelte";
import AlertDialogOverlay from "./alert-dialog-overlay.svelte"; import AlertDialogOverlay from "./alert-dialog-overlay.svelte";
import { cn, type WithoutChild, type WithoutChildrenOrChild } from "$lib/utils.js"; import { cn, type WithoutChild, type WithoutChildrenOrChild } from "$lib/utils.js";
import type { ComponentProps } from "svelte";
let { let {
ref = $bindable(null), ref = $bindable(null),
class: className, class: className,
size = "default",
portalProps, portalProps,
...restProps ...restProps
}: WithoutChild<AlertDialogPrimitive.ContentProps> & { }: WithoutChild<AlertDialogPrimitive.ContentProps> & {
portalProps?: WithoutChildrenOrChild<AlertDialogPrimitive.PortalProps>; size?: "default" | "sm";
portalProps?: WithoutChildrenOrChild<ComponentProps<typeof AlertDialogPortal>>;
} = $props(); } = $props();
</script> </script>
<AlertDialogPrimitive.Portal {...portalProps}> <AlertDialogPortal {...portalProps}>
<AlertDialogOverlay /> <AlertDialogOverlay />
<AlertDialogPrimitive.Content <AlertDialogPrimitive.Content
bind:ref bind:ref
data-slot="alert-dialog-content" data-slot="alert-dialog-content"
data-size={size}
class={cn( class={cn(
"bg-background 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 fixed left-[50%] top-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg", "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-popover text-popover-foreground ring-foreground/10 gap-4 rounded-xl p-4 ring-1 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none",
className className
)} )}
{...restProps} {...restProps}
/> />
</AlertDialogPrimitive.Portal> </AlertDialogPortal>

View File

@@ -12,6 +12,6 @@
<AlertDialogPrimitive.Description <AlertDialogPrimitive.Description
bind:ref bind:ref
data-slot="alert-dialog-description" data-slot="alert-dialog-description"
class={cn("text-muted-foreground text-sm", className)} class={cn("text-muted-foreground *:[a]:hover:text-foreground text-sm text-balance md:text-pretty *:[a]:underline *:[a]:underline-offset-3", className)}
{...restProps} {...restProps}
/> />

View File

@@ -13,7 +13,10 @@
<div <div
bind:this={ref} bind:this={ref}
data-slot="alert-dialog-footer" data-slot="alert-dialog-footer"
class={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)} class={cn(
"bg-muted/50 -mx-4 -mb-4 rounded-b-xl border-t p-4 flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
className
)}
{...restProps} {...restProps}
> >
{@render children?.()} {@render children?.()}

View File

@@ -13,7 +13,7 @@
<div <div
bind:this={ref} bind:this={ref}
data-slot="alert-dialog-header" data-slot="alert-dialog-header"
class={cn("flex flex-col gap-2 text-center sm:text-left", className)} class={cn("grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]", className)}
{...restProps} {...restProps}
> >
{@render children?.()} {@render children?.()}

View File

@@ -0,0 +1,20 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn, type WithElementRef } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="alert-dialog-media"
class={cn("bg-muted mb-2 inline-flex size-10 items-center justify-center rounded-md sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6", className)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -12,9 +12,6 @@
<AlertDialogPrimitive.Overlay <AlertDialogPrimitive.Overlay
bind:ref bind:ref
data-slot="alert-dialog-overlay" data-slot="alert-dialog-overlay"
class={cn( class={cn("data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 z-50", className)}
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
className
)}
{...restProps} {...restProps}
/> />

View File

@@ -1,9 +1,7 @@
<script lang="ts"> <script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from "bits-ui"; import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
type $$Props = AlertDialogPrimitive.PortalProps; let { ...restProps }: AlertDialogPrimitive.PortalProps = $props();
</script> </script>
<AlertDialogPrimitive.Portal {...$$restProps}> <AlertDialogPrimitive.Portal {...restProps} />
<slot />
</AlertDialogPrimitive.Portal>

View File

@@ -12,6 +12,6 @@
<AlertDialogPrimitive.Title <AlertDialogPrimitive.Title
bind:ref bind:ref
data-slot="alert-dialog-title" data-slot="alert-dialog-title"
class={cn("text-lg font-semibold", className)} class={cn("text-base font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2", className)}
{...restProps} {...restProps}
/> />

View File

@@ -0,0 +1,7 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
let { open = $bindable(false), ...restProps }: AlertDialogPrimitive.RootProps = $props();
</script>
<AlertDialogPrimitive.Root bind:open {...restProps} />

View File

@@ -1,4 +1,5 @@
import { AlertDialog as AlertDialogPrimitive } from "bits-ui"; import Root from "./alert-dialog.svelte";
import Portal from "./alert-dialog-portal.svelte";
import Trigger from "./alert-dialog-trigger.svelte"; import Trigger from "./alert-dialog-trigger.svelte";
import Title from "./alert-dialog-title.svelte"; import Title from "./alert-dialog-title.svelte";
import Action from "./alert-dialog-action.svelte"; import Action from "./alert-dialog-action.svelte";
@@ -8,9 +9,7 @@ import Header from "./alert-dialog-header.svelte";
import Overlay from "./alert-dialog-overlay.svelte"; import Overlay from "./alert-dialog-overlay.svelte";
import Content from "./alert-dialog-content.svelte"; import Content from "./alert-dialog-content.svelte";
import Description from "./alert-dialog-description.svelte"; import Description from "./alert-dialog-description.svelte";
import Media from "./alert-dialog-media.svelte";
const Root = AlertDialogPrimitive.Root;
const Portal = AlertDialogPrimitive.Portal;
export { export {
Root, Root,
@@ -24,6 +23,7 @@ export {
Overlay, Overlay,
Content, Content,
Description, Description,
Media,
// //
Root as AlertDialog, Root as AlertDialog,
Title as AlertDialogTitle, Title as AlertDialogTitle,
@@ -36,4 +36,5 @@ export {
Overlay as AlertDialogOverlay, Overlay as AlertDialogOverlay,
Content as AlertDialogContent, Content as AlertDialogContent,
Description as AlertDialogDescription, Description as AlertDialogDescription,
Media as AlertDialogMedia,
}; };

View File

@@ -0,0 +1,20 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn, type WithElementRef } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="alert-action"
class={cn("absolute top-2 right-2", className)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -14,7 +14,7 @@
bind:this={ref} bind:this={ref}
data-slot="alert-description" data-slot="alert-description"
class={cn( class={cn(
"text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed", "text-muted-foreground text-sm text-balance md:text-pretty [&_p:not(:last-child)]:mb-4 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3",
className className
)} )}
{...restProps} {...restProps}

View File

@@ -13,7 +13,10 @@
<div <div
bind:this={ref} bind:this={ref}
data-slot="alert-title" data-slot="alert-title"
class={cn("col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight", className)} class={cn(
"font-medium group-has-[>svg]/alert:col-start-2 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3",
className
)}
{...restProps} {...restProps}
> >
{@render children?.()} {@render children?.()}

View File

@@ -2,12 +2,11 @@
import { type VariantProps, tv } from "tailwind-variants"; import { type VariantProps, tv } from "tailwind-variants";
export const alertVariants = tv({ export const alertVariants = tv({
base: "relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg border px-4 py-3 text-sm has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", base: "grid gap-0.5 rounded-lg border px-2.5 py-2 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4 group/alert relative w-full",
variants: { variants: {
variant: { variant: {
default: "bg-card text-card-foreground", default: "bg-card text-card-foreground",
destructive: destructive: "text-destructive bg-card *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current",
"text-destructive bg-card *:data-[slot=alert-description]:text-destructive/90 [&>svg]:text-current",
}, },
}, },
defaultVariants: { defaultVariants: {
@@ -36,9 +35,9 @@
<div <div
bind:this={ref} bind:this={ref}
data-slot="alert" data-slot="alert"
role="alert"
class={cn(alertVariants({ variant }), className)} class={cn(alertVariants({ variant }), className)}
{...restProps} {...restProps}
role="alert"
> >
{@render children?.()} {@render children?.()}
</div> </div>

View File

@@ -1,14 +1,17 @@
import Root from "./alert.svelte"; import Root from "./alert.svelte";
import Description from "./alert-description.svelte"; import Description from "./alert-description.svelte";
import Title from "./alert-title.svelte"; import Title from "./alert-title.svelte";
import Action from "./alert-action.svelte";
export { alertVariants, type AlertVariant } from "./alert.svelte"; export { alertVariants, type AlertVariant } from "./alert.svelte";
export { export {
Root, Root,
Description, Description,
Title, Title,
Action,
// //
Root as Alert, Root as Alert,
Description as AlertDescription, Description as AlertDescription,
Title as AlertTitle, Title as AlertTitle,
Action as AlertAction,
}; };

View File

@@ -4,25 +4,25 @@
import { type VariantProps, tv } from "tailwind-variants"; import { type VariantProps, tv } from "tailwind-variants";
export const buttonVariants = tv({ 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", 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 dark:aria-invalid:border-destructive/50 rounded-lg border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-3 active:not-aria-[haspopup]:translate-y-px aria-invalid:ring-3 [&_svg:not([class*='size-'])]:size-4 group/button inline-flex shrink-0 items-center justify-center whitespace-nowrap transition-all outline-none select-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
variants: { variants: {
variant: { variant: {
default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
destructive: outline: "border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground",
"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", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
outline: ghost: "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
"bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border", destructive: "bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
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", link: "text-primary underline-offset-4 hover:underline",
}, },
size: { size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3", default: "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5", xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4", sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
icon: "size-9", lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
"icon-sm": "size-8", icon: "size-8",
"icon-lg": "size-10", "icon-xs": "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
"icon-sm": "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
"icon-lg": "size-9",
}, },
}, },
defaultVariants: { defaultVariants: {

View File

@@ -12,7 +12,7 @@
<CalendarPrimitive.Cell <CalendarPrimitive.Cell
bind:ref bind:ref
class={cn( class={cn(
"size-(--cell-size) relative p-0 text-center text-sm focus-within:z-20 [&:first-child[data-selected]_[data-bits-day]]:rounded-l-md [&:last-child[data-selected]_[data-bits-day]]:rounded-r-md", "relative size-(--cell-size) p-0 text-center text-sm focus-within:z-20 [&:first-child[data-selected]_[data-bits-day]]:rounded-s-(--cell-radius) [&:last-child[data-selected]_[data-bits-day]]:rounded-e-(--cell-radius)",
className className
)} )}
{...restProps} {...restProps}

View File

@@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import { buttonVariants } from "$lib/components/ui/button/index.js";
import { cn } from "$lib/utils.js"; import { cn } from "$lib/utils.js";
import { Calendar as CalendarPrimitive } from "bits-ui"; import { Calendar as CalendarPrimitive } from "bits-ui";
@@ -13,18 +12,17 @@
<CalendarPrimitive.Day <CalendarPrimitive.Day
bind:ref bind:ref
class={cn( class={cn(
buttonVariants({ variant: "ghost" }), "flex size-(--cell-size) flex-col items-center justify-center gap-1 rounded-(--cell-radius) p-0 leading-none font-normal whitespace-nowrap select-none",
"size-(--cell-size) flex select-none flex-col items-center justify-center gap-1 whitespace-nowrap p-0 font-normal leading-none", "[&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius)",
"not-data-selected:hover:bg-accent/50 not-data-selected:hover:text-accent-foreground",
"[&[data-today]:not([data-selected])]:bg-accent [&[data-today]:not([data-selected])]:text-accent-foreground [&[data-today][data-disabled]]:text-muted-foreground", "[&[data-today]:not([data-selected])]:bg-accent [&[data-today]:not([data-selected])]:text-accent-foreground [&[data-today][data-disabled]]:text-muted-foreground",
"data-[selected]:bg-primary dark:data-[selected]:hover:bg-accent/50 data-[selected]:text-primary-foreground", "data-[selected]:bg-primary data-[selected]:text-primary-foreground data-[selected]:hover:text-foreground",
// Outside months // Outside months
"[&[data-outside-month]:not([data-selected])]:text-muted-foreground [&[data-outside-month]:not([data-selected])]:hover:text-accent-foreground", "[&[data-outside-month]:not([data-selected])]:text-muted-foreground [&[data-outside-month]:not([data-selected])]:hover:text-accent-foreground",
// Disabled // Disabled
"data-[disabled]:text-muted-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "data-[disabled]:text-muted-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
// Unavailable // Unavailable
"data-[unavailable]:text-muted-foreground data-[unavailable]:line-through", "data-[unavailable]:text-muted-foreground data-[unavailable]:line-through",
// hover
"dark:hover:text-accent-foreground",
// focus // focus
"focus:border-ring focus:ring-ring/50 focus:relative", "focus:border-ring focus:ring-ring/50 focus:relative",
// inner spans // inner spans

View File

@@ -11,6 +11,6 @@
<CalendarPrimitive.Grid <CalendarPrimitive.Grid
bind:ref bind:ref
class={cn("mt-4 flex w-full border-collapse flex-col gap-1", className)} class={cn("flex w-full border-collapse flex-col", className)}
{...restProps} {...restProps}
/> />

View File

@@ -12,7 +12,7 @@
<CalendarPrimitive.Header <CalendarPrimitive.Header
bind:ref bind:ref
class={cn( class={cn(
"h-(--cell-size) flex w-full items-center justify-center gap-1.5 text-sm font-medium", "flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium",
className className
)} )}
{...restProps} {...restProps}

View File

@@ -14,11 +14,15 @@
<span <span
class={cn( class={cn(
"has-focus:border-ring border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] relative flex rounded-md border", "has-focus:border-ring border-input has-focus:ring-ring/50 relative flex rounded-md border shadow-xs has-focus:ring-[3px]",
className className
)} )}
> >
<CalendarPrimitive.MonthSelect bind:ref class="absolute inset-0 opacity-0" {...restProps}> <CalendarPrimitive.MonthSelect
bind:ref
class="bg-background dark:bg-popover dark:text-popover-foreground absolute inset-0 opacity-0"
{...restProps}
>
{#snippet child({ props, monthItems, selectedMonthItem })} {#snippet child({ props, monthItems, selectedMonthItem })}
<select {...props} {value} {onchange}> <select {...props} {value} {onchange}>
{#each monthItems as monthItem (monthItem.value)} {#each monthItems as monthItem (monthItem.value)}
@@ -33,7 +37,7 @@
{/each} {/each}
</select> </select>
<span <span
class="[&>svg]:text-muted-foreground flex h-8 select-none items-center gap-1 rounded-md pl-2 pr-1 text-sm font-medium [&>svg]:size-3.5" class="[&>svg]:text-muted-foreground flex h-(--cell-size) items-center gap-1 rounded-md ps-2 pe-1 text-sm font-medium select-none [&>svg]:size-3.5"
aria-hidden="true" aria-hidden="true"
> >
{monthItems.find((item) => item.value === value)?.label || selectedMonthItem.label} {monthItems.find((item) => item.value === value)?.label || selectedMonthItem.label}

View File

@@ -10,6 +10,6 @@
}: WithElementRef<HTMLAttributes<HTMLElement>> = $props(); }: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
</script> </script>
<div {...restProps} bind:this={ref} class={cn("flex flex-col", className)}> <div {...restProps} bind:this={ref} class={cn("flex w-full flex-col gap-4", className)}>
{@render children?.()} {@render children?.()}
</div> </div>

View File

@@ -23,9 +23,14 @@
bind:ref bind:ref
class={cn( class={cn(
buttonVariants({ variant }), buttonVariants({ variant }),
"size-(--cell-size) select-none bg-transparent p-0 disabled:opacity-50 rtl:rotate-180", "size-(--cell-size) bg-transparent p-0 select-none disabled:opacity-50 rtl:rotate-180",
className className
)} )}
children={children || Fallback}
{...restProps} {...restProps}
/> >
{#if children}
{@render children?.()}
{:else}
{@render Fallback()}
{/if}
</CalendarPrimitive.NextButton>

View File

@@ -23,9 +23,14 @@
bind:ref bind:ref
class={cn( class={cn(
buttonVariants({ variant }), buttonVariants({ variant }),
"size-(--cell-size) select-none bg-transparent p-0 disabled:opacity-50 rtl:rotate-180", "size-(--cell-size) bg-transparent p-0 select-none disabled:opacity-50 rtl:rotate-180",
className className
)} )}
children={children || Fallback}
{...restProps} {...restProps}
/> >
{#if children}
{@render children?.()}
{:else}
{@render Fallback()}
{/if}
</CalendarPrimitive.PrevButton>

View File

@@ -13,11 +13,15 @@
<span <span
class={cn( class={cn(
"has-focus:border-ring border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] relative flex rounded-md border", "has-focus:border-ring border-input has-focus:ring-ring/50 relative flex rounded-md border shadow-xs has-focus:ring-[3px]",
className className
)} )}
> >
<CalendarPrimitive.YearSelect bind:ref class="absolute inset-0 opacity-0" {...restProps}> <CalendarPrimitive.YearSelect
bind:ref
class="dark:bg-popover dark:text-popover-foreground absolute inset-0 opacity-0"
{...restProps}
>
{#snippet child({ props, yearItems, selectedYearItem })} {#snippet child({ props, yearItems, selectedYearItem })}
<select {...props} {value}> <select {...props} {value}>
{#each yearItems as yearItem (yearItem.value)} {#each yearItems as yearItem (yearItem.value)}
@@ -32,7 +36,7 @@
{/each} {/each}
</select> </select>
<span <span
class="[&>svg]:text-muted-foreground flex h-8 select-none items-center gap-1 rounded-md pl-2 pr-1 text-sm font-medium [&>svg]:size-3.5" class="[&>svg]:text-muted-foreground flex h-(--cell-size) items-center gap-1 rounded-md ps-2 pe-1 text-sm font-medium select-none [&>svg]:size-3.5"
aria-hidden="true" aria-hidden="true"
> >
{yearItems.find((item) => item.value === value)?.label || selectedYearItem.label} {yearItems.find((item) => item.value === value)?.label || selectedYearItem.label}

View File

@@ -50,7 +50,7 @@ get along, so we shut typescript up by casting `value` to `never`.
{weekdayFormat} {weekdayFormat}
{disableDaysOutsideMonth} {disableDaysOutsideMonth}
class={cn( class={cn(
"bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent", "p-2 [--cell-radius:var(--radius-md)] [--cell-size:--spacing(7)] bg-background group/calendar in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent",
className className
)} )}
{locale} {locale}

View File

@@ -13,7 +13,10 @@
<div <div
bind:this={ref} bind:this={ref}
data-slot="card-action" data-slot="card-action"
class={cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className)} class={cn(
"cn-card-action col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className
)}
{...restProps} {...restProps}
> >
{@render children?.()} {@render children?.()}

View File

@@ -10,6 +10,11 @@
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props(); }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script> </script>
<div bind:this={ref} data-slot="card-content" class={cn("px-6", className)} {...restProps}> <div
bind:this={ref}
data-slot="card-content"
class={cn("px-4 group-data-[size=sm]/card:px-3", className)}
{...restProps}
>
{@render children?.()} {@render children?.()}
</div> </div>

View File

@@ -13,7 +13,7 @@
<div <div
bind:this={ref} bind:this={ref}
data-slot="card-footer" data-slot="card-footer"
class={cn("[.border-t]:pt-6 flex items-center px-6", className)} class={cn("bg-muted/50 rounded-b-xl border-t p-4 group-data-[size=sm]/card:p-3 flex items-center", className)}
{...restProps} {...restProps}
> >
{@render children?.()} {@render children?.()}

View File

@@ -14,7 +14,7 @@
bind:this={ref} bind:this={ref}
data-slot="card-header" data-slot="card-header"
class={cn( class={cn(
"@container/card-header has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6 grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6", "gap-1 rounded-t-xl px-4 group-data-[size=sm]/card:px-3 [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]",
className className
)} )}
{...restProps} {...restProps}

View File

@@ -13,7 +13,7 @@
<div <div
bind:this={ref} bind:this={ref}
data-slot="card-title" data-slot="card-title"
class={cn("font-semibold leading-none", className)} class={cn("text-base leading-snug font-medium group-data-[size=sm]/card:text-sm", className)}
{...restProps} {...restProps}
> >
{@render children?.()} {@render children?.()}

View File

@@ -6,17 +6,16 @@
ref = $bindable(null), ref = $bindable(null),
class: className, class: className,
children, children,
size = "default",
...restProps ...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props(); }: WithElementRef<HTMLAttributes<HTMLDivElement>> & { size?: "default" | "sm" } = $props();
</script> </script>
<div <div
bind:this={ref} bind:this={ref}
data-slot="card" data-slot="card"
class={cn( data-size={size}
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm", class={cn("ring-foreground/10 bg-card text-card-foreground gap-4 overflow-hidden rounded-xl py-4 text-sm ring-1 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl group/card flex flex-col", className)}
className
)}
{...restProps} {...restProps}
> >
{@render children?.()} {@render children?.()}

View File

@@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
import { Checkbox as CheckboxPrimitive } from "bits-ui"; import { Checkbox as CheckboxPrimitive } from "bits-ui";
import CheckIcon from "@lucide/svelte/icons/check";
import MinusIcon from "@lucide/svelte/icons/minus";
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js"; import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
import CheckIcon from '@lucide/svelte/icons/check';
import MinusIcon from '@lucide/svelte/icons/minus';
let { let {
ref = $bindable(null), ref = $bindable(null),
@@ -17,7 +17,7 @@
bind:ref bind:ref
data-slot="checkbox" data-slot="checkbox"
class={cn( class={cn(
"border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive shadow-xs peer flex size-4 shrink-0 items-center justify-center rounded-[4px] border outline-none transition-shadow focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50", "border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 flex size-4 items-center justify-center rounded-[4px] border transition-colors group-has-disabled/field:opacity-50 focus-visible:ring-3 aria-invalid:ring-3 peer relative shrink-0 outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50",
className className
)} )}
bind:checked bind:checked
@@ -25,11 +25,14 @@
{...restProps} {...restProps}
> >
{#snippet children({ checked, indeterminate })} {#snippet children({ checked, indeterminate })}
<div data-slot="checkbox-indicator" class="text-current transition-none"> <div
data-slot="checkbox-indicator"
class="[&>svg]:size-3.5 grid place-content-center text-current transition-none"
>
{#if checked} {#if checked}
<CheckIcon class="size-3.5" /> <CheckIcon />
{:else if indeterminate} {:else if indeterminate}
<MinusIcon class="size-3.5" /> <MinusIcon />
{/if} {/if}
</div> </div>
{/snippet} {/snippet}

View File

@@ -1,17 +1,19 @@
<script lang="ts"> <script lang="ts">
import { ContextMenu as ContextMenuPrimitive } from "bits-ui"; import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
import CheckIcon from "@lucide/svelte/icons/check";
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js"; import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
import type { Snippet } from "svelte"; import type { Snippet } from "svelte";
import CheckIcon from '@lucide/svelte/icons/check';
let { let {
ref = $bindable(null), ref = $bindable(null),
checked = $bindable(false), checked = $bindable(false),
indeterminate = $bindable(false), indeterminate = $bindable(false),
class: className, class: className,
inset,
children: childrenProp, children: childrenProp,
...restProps ...restProps
}: WithoutChildrenOrChild<ContextMenuPrimitive.CheckboxItemProps> & { }: WithoutChildrenOrChild<ContextMenuPrimitive.CheckboxItemProps> & {
inset?: boolean;
children?: Snippet; children?: Snippet;
} = $props(); } = $props();
</script> </script>
@@ -21,16 +23,17 @@
bind:checked bind:checked
bind:indeterminate bind:indeterminate
data-slot="context-menu-checkbox-item" data-slot="context-menu-checkbox-item"
data-inset={inset}
class={cn( class={cn(
"data-highlighted:bg-accent data-highlighted:text-accent-foreground outline-hidden relative flex cursor-default select-none items-center gap-2 rounded-sm py-1.5 pl-8 pr-2 text-sm data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", "focus:bg-accent focus:text-accent-foreground gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
className className
)} )}
{...restProps} {...restProps}
> >
{#snippet children({ checked })} {#snippet children({ checked })}
<span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center"> <span class="absolute right-2 pointer-events-none">
{#if checked} {#if checked}
<CheckIcon class="size-4" /> <CheckIcon />
{/if} {/if}
</span> </span>
{@render childrenProp?.()} {@render childrenProp?.()}

View File

@@ -1,6 +1,9 @@
<script lang="ts"> <script lang="ts">
import { ContextMenu as ContextMenuPrimitive } from "bits-ui"; import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js"; import { cn } from "$lib/utils.js";
import ContextMenuPortal from "./context-menu-portal.svelte";
import type { ComponentProps } from "svelte";
import type { WithoutChildrenOrChild } from "$lib/utils.js";
let { let {
ref = $bindable(null), ref = $bindable(null),
@@ -8,18 +11,18 @@
class: className, class: className,
...restProps ...restProps
}: ContextMenuPrimitive.ContentProps & { }: ContextMenuPrimitive.ContentProps & {
portalProps?: ContextMenuPrimitive.PortalProps; portalProps?: WithoutChildrenOrChild<ComponentProps<typeof ContextMenuPortal>>;
} = $props(); } = $props();
</script> </script>
<ContextMenuPrimitive.Portal {...portalProps}> <ContextMenuPortal {...portalProps}>
<ContextMenuPrimitive.Content <ContextMenuPrimitive.Content
bind:ref bind:ref
data-slot="context-menu-content" data-slot="context-menu-content"
class={cn( 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-(--bits-context-menu-content-available-height) origin-(--bits-context-menu-content-transform-origin) z-50 min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border p-1 shadow-md", "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 ring-foreground/10 bg-popover text-popover-foreground min-w-36 rounded-lg p-1 shadow-md ring-1 duration-100 z-50 overflow-x-hidden overflow-y-auto outline-none",
className className
)} )}
{...restProps} {...restProps}
/> />
</ContextMenuPrimitive.Portal> </ContextMenuPortal>

View File

@@ -16,6 +16,6 @@
bind:ref bind:ref
data-slot="context-menu-group-heading" data-slot="context-menu-group-heading"
data-inset={inset} data-inset={inset}
class={cn("text-foreground px-2 py-1.5 text-sm font-medium data-[inset]:pl-8", className)} class={cn("text-foreground px-2 py-1.5 text-sm font-medium data-inset:ps-8", className)}
{...restProps} {...restProps}
/> />

View File

@@ -20,7 +20,7 @@
data-inset={inset} data-inset={inset}
data-variant={variant} data-variant={variant}
class={cn( class={cn(
"data-highlighted:bg-accent data-highlighted:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:data-highlighted:bg-destructive/10 dark:data-[variant=destructive]:data-highlighted:bg-destructive/20 data-[variant=destructive]:data-highlighted:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground outline-hidden relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm data-[disabled]:pointer-events-none data-[inset]:pl-8 data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive focus:*:[svg]:text-accent-foreground gap-1.5 rounded-md px-1.5 py-1 text-sm data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 group/context-menu-item relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
className className
)} )}
{...restProps} {...restProps}

View File

@@ -17,7 +17,7 @@
bind:this={ref} bind:this={ref}
data-slot="context-menu-label" data-slot="context-menu-label"
data-inset={inset} data-inset={inset}
class={cn("text-foreground px-2 py-1.5 text-sm font-medium data-[inset]:pl-8", className)} class={cn("text-muted-foreground px-1.5 py-1 text-xs font-medium data-inset:pl-7 data-inset:pl-8", className)}
{...restProps} {...restProps}
> >
{@render children?.()} {@render children?.()}

View File

@@ -0,0 +1,7 @@
<script lang="ts">
import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
let { ...restProps }: ContextMenuPrimitive.PortalProps = $props();
</script>
<ContextMenuPrimitive.Portal {...restProps} />

View File

@@ -1,29 +1,33 @@
<script lang="ts"> <script lang="ts">
import { ContextMenu as ContextMenuPrimitive } from "bits-ui"; import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
import CircleIcon from "@lucide/svelte/icons/circle";
import { cn, type WithoutChild } from "$lib/utils.js"; import { cn, type WithoutChild } from "$lib/utils.js";
import CheckIcon from '@lucide/svelte/icons/check';
let { let {
ref = $bindable(null), ref = $bindable(null),
class: className, class: className,
inset,
children: childrenProp, children: childrenProp,
...restProps ...restProps
}: WithoutChild<ContextMenuPrimitive.RadioItemProps> = $props(); }: WithoutChild<ContextMenuPrimitive.RadioItemProps> & {
inset?: boolean;
} = $props();
</script> </script>
<ContextMenuPrimitive.RadioItem <ContextMenuPrimitive.RadioItem
bind:ref bind:ref
data-slot="context-menu-radio-item" data-slot="context-menu-radio-item"
data-inset={inset}
class={cn( class={cn(
"data-highlighted:bg-accent data-highlighted:text-accent-foreground outline-hidden relative flex cursor-default select-none items-center gap-2 rounded-sm py-1.5 pl-8 pr-2 text-sm data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", "focus:bg-accent focus:text-accent-foreground gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
className className
)} )}
{...restProps} {...restProps}
> >
{#snippet children({ checked })} {#snippet children({ checked })}
<span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center"> <span class="absolute right-2 pointer-events-none">
{#if checked} {#if checked}
<CircleIcon class="size-2 fill-current" /> <CheckIcon />
{/if} {/if}
</span> </span>
{@render childrenProp?.({ checked })} {@render childrenProp?.({ checked })}

View File

@@ -13,7 +13,7 @@
<span <span
bind:this={ref} bind:this={ref}
data-slot="context-menu-shortcut" data-slot="context-menu-shortcut"
class={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)} class={cn("text-muted-foreground group-focus/context-menu-item:text-accent-foreground ml-auto text-xs tracking-widest", className)}
{...restProps} {...restProps}
> >
{@render children?.()} {@render children?.()}

View File

@@ -12,9 +12,6 @@
<ContextMenuPrimitive.SubContent <ContextMenuPrimitive.SubContent
bind:ref bind:ref
data-slot="context-menu-sub-content" data-slot="context-menu-sub-content"
class={cn( class={cn("data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 bg-popover text-popover-foreground min-w-32 rounded-lg border p-1 shadow-lg duration-100", className)}
"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-context-menu-content-transform-origin) z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-lg",
className
)}
{...restProps} {...restProps}
/> />

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { ContextMenu as ContextMenuPrimitive } from "bits-ui"; import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
import ChevronRightIcon from "@lucide/svelte/icons/chevron-right";
import { cn, type WithoutChild } from "$lib/utils.js"; import { cn, type WithoutChild } from "$lib/utils.js";
import ChevronRightIcon from '@lucide/svelte/icons/chevron-right';
let { let {
ref = $bindable(null), ref = $bindable(null),
@@ -19,7 +19,7 @@
data-slot="context-menu-sub-trigger" data-slot="context-menu-sub-trigger"
data-inset={inset} data-inset={inset}
class={cn( class={cn(
"data-highlighted:bg-accent data-highlighted:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground outline-hidden [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm data-[disabled]:pointer-events-none data-[inset]:pl-8 data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", "focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground gap-1.5 rounded-md px-1.5 py-1 text-sm data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 flex cursor-default items-center outline-hidden select-none data-inset:ps-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
className className
)} )}
{...restProps} {...restProps}

View File

@@ -0,0 +1,7 @@
<script lang="ts">
import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
let { open = $bindable(false), ...restProps }: ContextMenuPrimitive.SubProps = $props();
</script>
<ContextMenuPrimitive.Sub bind:open {...restProps} />

View File

@@ -1,7 +1,17 @@
<script lang="ts"> <script lang="ts">
import { ContextMenu as ContextMenuPrimitive } from "bits-ui"; import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let { ref = $bindable(null), ...restProps }: ContextMenuPrimitive.TriggerProps = $props(); let {
ref = $bindable(null),
class: className,
...restProps
}: ContextMenuPrimitive.TriggerProps = $props();
</script> </script>
<ContextMenuPrimitive.Trigger bind:ref data-slot="context-menu-trigger" {...restProps} /> <ContextMenuPrimitive.Trigger
bind:ref
data-slot="context-menu-trigger"
class={cn("cn-context-menu-trigger select-none", className)}
{...restProps}
/>

View File

@@ -0,0 +1,7 @@
<script lang="ts">
import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
let { open = $bindable(false), ...restProps }: ContextMenuPrimitive.RootProps = $props();
</script>
<ContextMenuPrimitive.Root bind:open {...restProps} />

View File

@@ -1,5 +1,6 @@
import { ContextMenu as ContextMenuPrimitive } from "bits-ui"; import Root from "./context-menu.svelte";
import Sub from "./context-menu-sub.svelte";
import Portal from "./context-menu-portal.svelte";
import Trigger from "./context-menu-trigger.svelte"; import Trigger from "./context-menu-trigger.svelte";
import Group from "./context-menu-group.svelte"; import Group from "./context-menu-group.svelte";
import RadioGroup from "./context-menu-radio-group.svelte"; import RadioGroup from "./context-menu-radio-group.svelte";
@@ -13,12 +14,11 @@ import SubContent from "./context-menu-sub-content.svelte";
import SubTrigger from "./context-menu-sub-trigger.svelte"; import SubTrigger from "./context-menu-sub-trigger.svelte";
import CheckboxItem from "./context-menu-checkbox-item.svelte"; import CheckboxItem from "./context-menu-checkbox-item.svelte";
import Label from "./context-menu-label.svelte"; import Label from "./context-menu-label.svelte";
const Sub = ContextMenuPrimitive.Sub;
const Root = ContextMenuPrimitive.Root;
export { export {
Sub,
Root, Root,
Sub,
Portal,
Item, Item,
GroupHeading, GroupHeading,
Label, Label,
@@ -35,6 +35,7 @@ export {
// //
Root as ContextMenu, Root as ContextMenu,
Sub as ContextMenuSub, Sub as ContextMenuSub,
Portal as ContextMenuPortal,
Item as ContextMenuItem, Item as ContextMenuItem,
GroupHeading as ContextMenuGroupHeading, GroupHeading as ContextMenuGroupHeading,
Group as ContextMenuGroup, Group as ContextMenuGroup,

View File

@@ -22,9 +22,9 @@
onchange?: (date: DateValue | undefined) => void; onchange?: (date: DateValue | undefined) => void;
} = $props(); } = $props();
const df = new DateFormatter(locale, { const df = $derived(new DateFormatter(locale, {
dateStyle: 'long', dateStyle: 'long',
}); }));
let contentRef = $state<HTMLElement | null>(null); let contentRef = $state<HTMLElement | null>(null);
</script> </script>

View File

@@ -1,7 +1,11 @@
<script lang="ts"> <script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui"; import { Dialog as DialogPrimitive } from "bits-ui";
let { ref = $bindable(null), ...restProps }: DialogPrimitive.CloseProps = $props(); let {
ref = $bindable(null),
type = "button",
...restProps
}: DialogPrimitive.CloseProps = $props();
</script> </script>
<DialogPrimitive.Close bind:ref data-slot="dialog-close" {...restProps} /> <DialogPrimitive.Close bind:ref data-slot="dialog-close" {type} {...restProps} />

View File

@@ -1,9 +1,12 @@
<script lang="ts"> <script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui"; import { Dialog as DialogPrimitive } from "bits-ui";
import XIcon from "@lucide/svelte/icons/x"; import DialogPortal from "./dialog-portal.svelte";
import type { Snippet } from "svelte"; import type { Snippet } from "svelte";
import * as Dialog from "./index.js"; import * as Dialog from "./index.js";
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js"; import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
import type { ComponentProps } from "svelte";
import { Button } from "$lib/components/ui/button/index.js";
import XIcon from '@lucide/svelte/icons/x';
let { let {
ref = $bindable(null), ref = $bindable(null),
@@ -13,31 +16,33 @@
showCloseButton = true, showCloseButton = true,
...restProps ...restProps
}: WithoutChildrenOrChild<DialogPrimitive.ContentProps> & { }: WithoutChildrenOrChild<DialogPrimitive.ContentProps> & {
portalProps?: DialogPrimitive.PortalProps; portalProps?: WithoutChildrenOrChild<ComponentProps<typeof DialogPortal>>;
children: Snippet; children: Snippet;
showCloseButton?: boolean; showCloseButton?: boolean;
} = $props(); } = $props();
</script> </script>
<Dialog.Portal {...portalProps}> <DialogPortal {...portalProps}>
<Dialog.Overlay /> <Dialog.Overlay />
<DialogPrimitive.Content <DialogPrimitive.Content
bind:ref bind:ref
data-slot="dialog-content" data-slot="dialog-content"
class={cn( class={cn(
"bg-background 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 fixed left-[50%] top-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg", "bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-foreground/10 grid max-w-[calc(100%-2rem)] gap-4 rounded-xl p-4 text-sm ring-1 duration-100 sm:max-w-sm fixed top-1/2 left-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2 outline-none",
className className
)} )}
{...restProps} {...restProps}
> >
{@render children?.()} {@render children?.()}
{#if showCloseButton} {#if showCloseButton}
<DialogPrimitive.Close <DialogPrimitive.Close data-slot="dialog-close">
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" {#snippet child({ props })}
> <Button variant="ghost" class="absolute top-2 right-2" size="icon-sm" {...props}>
<XIcon /> <XIcon />
<span class="sr-only">Close</span> <span class="sr-only">Close</span>
</Button>
{/snippet}
</DialogPrimitive.Close> </DialogPrimitive.Close>
{/if} {/if}
</DialogPrimitive.Content> </DialogPrimitive.Content>
</Dialog.Portal> </DialogPortal>

View File

@@ -12,6 +12,6 @@
<DialogPrimitive.Description <DialogPrimitive.Description
bind:ref bind:ref
data-slot="dialog-description" data-slot="dialog-description"
class={cn("text-muted-foreground text-sm", className)} class={cn("text-muted-foreground *:[a]:hover:text-foreground text-sm *:[a]:underline *:[a]:underline-offset-3", className)}
{...restProps} {...restProps}
/> />

View File

@@ -1,20 +1,32 @@
<script lang="ts"> <script lang="ts">
import { cn, type WithElementRef } from "$lib/utils.js"; import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements"; import type { HTMLAttributes } from "svelte/elements";
import { Dialog as DialogPrimitive } from "bits-ui";
import { Button } from "$lib/components/ui/button/index.js";
let { let {
ref = $bindable(null), ref = $bindable(null),
class: className, class: className,
children, children,
showCloseButton = false,
...restProps ...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props(); }: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
showCloseButton?: boolean;
} = $props();
</script> </script>
<div <div
bind:this={ref} bind:this={ref}
data-slot="dialog-footer" data-slot="dialog-footer"
class={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)} class={cn("bg-muted/50 -mx-4 -mb-4 rounded-b-xl border-t p-4 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
{...restProps} {...restProps}
> >
{@render children?.()} {@render children?.()}
{#if showCloseButton}
<DialogPrimitive.Close>
{#snippet child({ props })}
<Button variant="outline" {...props}>Close</Button>
{/snippet}
</DialogPrimitive.Close>
{/if}
</div> </div>

View File

@@ -13,7 +13,7 @@
<div <div
bind:this={ref} bind:this={ref}
data-slot="dialog-header" data-slot="dialog-header"
class={cn("flex flex-col gap-2 text-center sm:text-left", className)} class={cn("gap-2 flex flex-col", className)}
{...restProps} {...restProps}
> >
{@render children?.()} {@render children?.()}

View File

@@ -12,9 +12,6 @@
<DialogPrimitive.Overlay <DialogPrimitive.Overlay
bind:ref bind:ref
data-slot="dialog-overlay" data-slot="dialog-overlay"
class={cn( class={cn("data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 isolate z-50", className)}
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
className
)}
{...restProps} {...restProps}
/> />

View File

@@ -0,0 +1,7 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
let { ...restProps }: DialogPrimitive.PortalProps = $props();
</script>
<DialogPrimitive.Portal {...restProps} />

View File

@@ -12,6 +12,6 @@
<DialogPrimitive.Title <DialogPrimitive.Title
bind:ref bind:ref
data-slot="dialog-title" data-slot="dialog-title"
class={cn("text-lg font-semibold leading-none", className)} class={cn("text-base leading-none font-medium", className)}
{...restProps} {...restProps}
/> />

View File

@@ -1,7 +1,11 @@
<script lang="ts"> <script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui"; import { Dialog as DialogPrimitive } from "bits-ui";
let { ref = $bindable(null), ...restProps }: DialogPrimitive.TriggerProps = $props(); let {
ref = $bindable(null),
type = "button",
...restProps
}: DialogPrimitive.TriggerProps = $props();
</script> </script>
<DialogPrimitive.Trigger bind:ref data-slot="dialog-trigger" {...restProps} /> <DialogPrimitive.Trigger bind:ref data-slot="dialog-trigger" {type} {...restProps} />

View File

@@ -0,0 +1,7 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
let { open = $bindable(false), ...restProps }: DialogPrimitive.RootProps = $props();
</script>
<DialogPrimitive.Root bind:open {...restProps} />

View File

@@ -1,5 +1,5 @@
import { Dialog as DialogPrimitive } from "bits-ui"; import Root from "./dialog.svelte";
import Portal from "./dialog-portal.svelte";
import Title from "./dialog-title.svelte"; import Title from "./dialog-title.svelte";
import Footer from "./dialog-footer.svelte"; import Footer from "./dialog-footer.svelte";
import Header from "./dialog-header.svelte"; import Header from "./dialog-header.svelte";
@@ -9,9 +9,6 @@ import Description from "./dialog-description.svelte";
import Trigger from "./dialog-trigger.svelte"; import Trigger from "./dialog-trigger.svelte";
import Close from "./dialog-close.svelte"; import Close from "./dialog-close.svelte";
const Root = DialogPrimitive.Root;
const Portal = DialogPrimitive.Portal;
export { export {
Root, Root,
Title, Title,

View File

@@ -0,0 +1,16 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
let {
ref = $bindable(null),
value = $bindable([]),
...restProps
}: DropdownMenuPrimitive.CheckboxGroupProps = $props();
</script>
<DropdownMenuPrimitive.CheckboxGroup
bind:ref
bind:value
data-slot="dropdown-menu-checkbox-group"
{...restProps}
/>

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui"; import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import CheckIcon from "@lucide/svelte/icons/check"; import MinusIcon from '@lucide/svelte/icons/minus';
import MinusIcon from "@lucide/svelte/icons/minus"; import CheckIcon from '@lucide/svelte/icons/check';
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js"; import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
import type { Snippet } from "svelte"; import type { Snippet } from "svelte";
@@ -23,17 +23,20 @@
bind:indeterminate bind:indeterminate
data-slot="dropdown-menu-checkbox-item" data-slot="dropdown-menu-checkbox-item"
class={cn( class={cn(
"focus:bg-accent focus:text-accent-foreground outline-hidden relative flex cursor-default select-none items-center gap-2 rounded-sm py-1.5 pl-8 pr-2 text-sm data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", "focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
className className
)} )}
{...restProps} {...restProps}
> >
{#snippet children({ checked, indeterminate })} {#snippet children({ checked, indeterminate })}
<span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center"> <span
class="absolute right-2 flex items-center justify-center pointer-events-none"
data-slot="dropdown-menu-checkbox-item-indicator"
>
{#if indeterminate} {#if indeterminate}
<MinusIcon class="size-4" /> <MinusIcon />
{:else} {:else if checked}
<CheckIcon class={cn("size-4", !checked && "text-transparent")} /> <CheckIcon />
{/if} {/if}
</span> </span>
{@render childrenProp?.()} {@render childrenProp?.()}

View File

@@ -1,27 +1,31 @@
<script lang="ts"> <script lang="ts">
import { cn } from "$lib/utils.js"; import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
import DropdownMenuPortal from "./dropdown-menu-portal.svelte";
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui"; import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import type { ComponentProps } from "svelte";
let { let {
ref = $bindable(null), ref = $bindable(null),
sideOffset = 4, sideOffset = 4,
align = "start",
portalProps, portalProps,
class: className, class: className,
...restProps ...restProps
}: DropdownMenuPrimitive.ContentProps & { }: DropdownMenuPrimitive.ContentProps & {
portalProps?: DropdownMenuPrimitive.PortalProps; portalProps?: WithoutChildrenOrChild<ComponentProps<typeof DropdownMenuPortal>>;
} = $props(); } = $props();
</script> </script>
<DropdownMenuPrimitive.Portal {...portalProps}> <DropdownMenuPortal {...portalProps}>
<DropdownMenuPrimitive.Content <DropdownMenuPrimitive.Content
bind:ref bind:ref
data-slot="dropdown-menu-content" data-slot="dropdown-menu-content"
{sideOffset} {sideOffset}
{align}
class={cn( 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-(--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", "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 ring-foreground/10 bg-popover text-popover-foreground min-w-32 rounded-lg p-1 shadow-md ring-1 duration-100 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 z-50 w-(--bits-dropdown-menu-anchor-width) overflow-x-hidden overflow-y-auto outline-none data-closed:overflow-hidden",
className className
)} )}
{...restProps} {...restProps}
/> />
</DropdownMenuPrimitive.Portal> </DropdownMenuPortal>

View File

@@ -17,6 +17,6 @@
bind:ref bind:ref
data-slot="dropdown-menu-group-heading" data-slot="dropdown-menu-group-heading"
data-inset={inset} data-inset={inset}
class={cn("px-2 py-1.5 text-sm font-semibold data-[inset]:pl-8", className)} class={cn("px-2 py-1.5 text-sm font-semibold data-[inset]:ps-8", className)}
{...restProps} {...restProps}
/> />

View File

@@ -20,7 +20,7 @@
data-inset={inset} data-inset={inset}
data-variant={variant} data-variant={variant}
class={cn( class={cn(
"data-highlighted:bg-accent data-highlighted:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:data-highlighted:bg-destructive/10 dark:data-[variant=destructive]:data-highlighted:bg-destructive/20 data-[variant=destructive]:data-highlighted:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground outline-hidden relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm data-[disabled]:pointer-events-none data-[inset]:pl-8 data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground gap-1.5 rounded-md px-1.5 py-1 text-sm data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 group/dropdown-menu-item relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
className className
)} )}
{...restProps} {...restProps}

View File

@@ -17,7 +17,7 @@
bind:this={ref} bind:this={ref}
data-slot="dropdown-menu-label" data-slot="dropdown-menu-label"
data-inset={inset} data-inset={inset}
class={cn("px-2 py-1.5 text-sm font-semibold data-[inset]:pl-8", className)} class={cn("text-muted-foreground px-1.5 py-1 text-xs font-medium data-inset:pl-7 data-[inset]:pl-8", className)}
{...restProps} {...restProps}
> >
{@render children?.()} {@render children?.()}

View File

@@ -0,0 +1,7 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
let { ...restProps }: DropdownMenuPrimitive.PortalProps = $props();
</script>
<DropdownMenuPrimitive.Portal {...restProps} />

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui"; import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import CircleIcon from "@lucide/svelte/icons/circle"; import CheckIcon from '@lucide/svelte/icons/check';
import { cn, type WithoutChild } from "$lib/utils.js"; import { cn, type WithoutChild } from "$lib/utils.js";
let { let {
@@ -15,15 +15,18 @@
bind:ref bind:ref
data-slot="dropdown-menu-radio-item" data-slot="dropdown-menu-radio-item"
class={cn( class={cn(
"focus:bg-accent focus:text-accent-foreground outline-hidden relative flex cursor-default select-none items-center gap-2 rounded-sm py-1.5 pl-8 pr-2 text-sm data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", "focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
className className
)} )}
{...restProps} {...restProps}
> >
{#snippet children({ checked })} {#snippet children({ checked })}
<span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center"> <span
class="absolute right-2 flex items-center justify-center pointer-events-none"
data-slot="dropdown-menu-radio-item-indicator"
>
{#if checked} {#if checked}
<CircleIcon class="size-2 fill-current" /> <CheckIcon />
{/if} {/if}
</span> </span>
{@render childrenProp?.({ checked })} {@render childrenProp?.({ checked })}

View File

@@ -13,7 +13,7 @@
<span <span
bind:this={ref} bind:this={ref}
data-slot="dropdown-menu-shortcut" data-slot="dropdown-menu-shortcut"
class={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)} class={cn("text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground ml-auto text-xs tracking-widest", className)}
{...restProps} {...restProps}
> >
{@render children?.()} {@render children?.()}

View File

@@ -12,9 +12,6 @@
<DropdownMenuPrimitive.SubContent <DropdownMenuPrimitive.SubContent
bind:ref bind:ref
data-slot="dropdown-menu-sub-content" data-slot="dropdown-menu-sub-content"
class={cn( class={cn("data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 ring-foreground/10 bg-popover text-popover-foreground min-w-[96px] rounded-lg p-1 shadow-lg ring-1 duration-100 w-auto", className)}
"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} {...restProps}
/> />

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui"; import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import ChevronRightIcon from "@lucide/svelte/icons/chevron-right"; import ChevronRightIcon from '@lucide/svelte/icons/chevron-right';
import { cn } from "$lib/utils.js"; import { cn } from "$lib/utils.js";
let { let {
@@ -19,11 +19,11 @@
data-slot="dropdown-menu-sub-trigger" data-slot="dropdown-menu-sub-trigger"
data-inset={inset} data-inset={inset}
class={cn( class={cn(
"data-highlighted:bg-accent data-highlighted:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground outline-hidden [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm data-[disabled]:pointer-events-none data-[inset]:pl-8 data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", "focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-1.5 rounded-md px-1.5 py-1 text-sm data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 flex cursor-default items-center outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
className className
)} )}
{...restProps} {...restProps}
> >
{@render children?.()} {@render children?.()}
<ChevronRightIcon class="ml-auto size-4" /> <ChevronRightIcon class="ml-auto" />
</DropdownMenuPrimitive.SubTrigger> </DropdownMenuPrimitive.SubTrigger>

View File

@@ -0,0 +1,7 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
let { open = $bindable(false), ...restProps }: DropdownMenuPrimitive.SubProps = $props();
</script>
<DropdownMenuPrimitive.Sub bind:open {...restProps} />

View File

@@ -0,0 +1,7 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
let { open = $bindable(false), ...restProps }: DropdownMenuPrimitive.RootProps = $props();
</script>
<DropdownMenuPrimitive.Root bind:open {...restProps} />

View File

@@ -1,4 +1,6 @@
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui"; import Root from "./dropdown-menu.svelte";
import Sub from "./dropdown-menu-sub.svelte";
import CheckboxGroup from "./dropdown-menu-checkbox-group.svelte";
import CheckboxItem from "./dropdown-menu-checkbox-item.svelte"; import CheckboxItem from "./dropdown-menu-checkbox-item.svelte";
import Content from "./dropdown-menu-content.svelte"; import Content from "./dropdown-menu-content.svelte";
import Group from "./dropdown-menu-group.svelte"; import Group from "./dropdown-menu-group.svelte";
@@ -12,15 +14,18 @@ import Trigger from "./dropdown-menu-trigger.svelte";
import SubContent from "./dropdown-menu-sub-content.svelte"; import SubContent from "./dropdown-menu-sub-content.svelte";
import SubTrigger from "./dropdown-menu-sub-trigger.svelte"; import SubTrigger from "./dropdown-menu-sub-trigger.svelte";
import GroupHeading from "./dropdown-menu-group-heading.svelte"; import GroupHeading from "./dropdown-menu-group-heading.svelte";
const Sub = DropdownMenuPrimitive.Sub; import Portal from "./dropdown-menu-portal.svelte";
const Root = DropdownMenuPrimitive.Root;
export { export {
CheckboxGroup,
CheckboxItem, CheckboxItem,
Content, Content,
Portal,
Root as DropdownMenu, Root as DropdownMenu,
CheckboxGroup as DropdownMenuCheckboxGroup,
CheckboxItem as DropdownMenuCheckboxItem, CheckboxItem as DropdownMenuCheckboxItem,
Content as DropdownMenuContent, Content as DropdownMenuContent,
Portal as DropdownMenuPortal,
Group as DropdownMenuGroup, Group as DropdownMenuGroup,
Item as DropdownMenuItem, Item as DropdownMenuItem,
Label as DropdownMenuLabel, Label as DropdownMenuLabel,

View File

@@ -25,9 +25,7 @@
bind:this={ref} bind:this={ref}
data-slot={dataSlot} data-slot={dataSlot}
class={cn( 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", "dark:bg-input/30 border-input 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:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 h-8 rounded-lg border bg-transparent px-2.5 py-1 text-base transition-colors file:h-6 file:text-sm file:font-medium focus-visible:ring-3 aria-invalid:ring-3 md:text-sm file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none 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 className
)} )}
type="file" type="file"
@@ -40,9 +38,7 @@
bind:this={ref} bind:this={ref}
data-slot={dataSlot} data-slot={dataSlot}
class={cn( 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", "dark:bg-input/30 border-input 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:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 h-8 rounded-lg border bg-transparent px-2.5 py-1 text-base transition-colors file:h-6 file:text-sm file:font-medium focus-visible:ring-3 aria-invalid:ring-3 md:text-sm file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none 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 className
)} )}
{type} {type}

View File

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

Some files were not shown because too many files have changed in this diff Show More