From 609f116b5db0ae49148662dbf8abe71445d4208b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kr=C3=BCger?= Date: Fri, 6 Mar 2026 17:03:35 +0100 Subject: [PATCH] feat: replace native date inputs with shadcn date picker Add calendar + popover components and a custom DateTimePicker wrapper. Video forms use date-only; article forms include a time picker. Also add video player preview to the video edit form. Co-Authored-By: Claude Sonnet 4.6 --- packages/frontend/package.json | 2 +- .../ui/calendar/calendar-caption.svelte | 76 ++++++++++++ .../ui/calendar/calendar-cell.svelte | 19 +++ .../ui/calendar/calendar-day.svelte | 35 ++++++ .../ui/calendar/calendar-grid-body.svelte | 12 ++ .../ui/calendar/calendar-grid-head.svelte | 12 ++ .../ui/calendar/calendar-grid-row.svelte | 12 ++ .../ui/calendar/calendar-grid.svelte | 16 +++ .../ui/calendar/calendar-head-cell.svelte | 19 +++ .../ui/calendar/calendar-header.svelte | 19 +++ .../ui/calendar/calendar-heading.svelte | 16 +++ .../ui/calendar/calendar-month-select.svelte | 48 ++++++++ .../ui/calendar/calendar-month.svelte | 15 +++ .../ui/calendar/calendar-months.svelte | 19 +++ .../ui/calendar/calendar-nav.svelte | 19 +++ .../ui/calendar/calendar-next-button.svelte | 31 +++++ .../ui/calendar/calendar-prev-button.svelte | 31 +++++ .../ui/calendar/calendar-year-select.svelte | 47 +++++++ .../components/ui/calendar/calendar.svelte | 115 ++++++++++++++++++ .../src/lib/components/ui/calendar/index.ts | 40 ++++++ .../ui/date-picker/date-picker.svelte | 85 +++++++++++++ .../lib/components/ui/date-picker/index.ts | 1 + .../src/lib/components/ui/popover/index.ts | 19 +++ .../ui/popover/popover-close.svelte | 7 ++ .../ui/popover/popover-content.svelte | 31 +++++ .../ui/popover/popover-portal.svelte | 7 ++ .../ui/popover/popover-trigger.svelte | 17 +++ .../lib/components/ui/popover/popover.svelte | 7 ++ .../routes/admin/articles/[id]/+page.svelte | 5 +- .../routes/admin/articles/new/+page.svelte | 5 +- .../src/routes/admin/videos/[id]/+page.svelte | 12 +- .../src/routes/admin/videos/new/+page.svelte | 5 +- pnpm-lock.yaml | 10 +- 33 files changed, 799 insertions(+), 15 deletions(-) create mode 100644 packages/frontend/src/lib/components/ui/calendar/calendar-caption.svelte create mode 100644 packages/frontend/src/lib/components/ui/calendar/calendar-cell.svelte create mode 100644 packages/frontend/src/lib/components/ui/calendar/calendar-day.svelte create mode 100644 packages/frontend/src/lib/components/ui/calendar/calendar-grid-body.svelte create mode 100644 packages/frontend/src/lib/components/ui/calendar/calendar-grid-head.svelte create mode 100644 packages/frontend/src/lib/components/ui/calendar/calendar-grid-row.svelte create mode 100644 packages/frontend/src/lib/components/ui/calendar/calendar-grid.svelte create mode 100644 packages/frontend/src/lib/components/ui/calendar/calendar-head-cell.svelte create mode 100644 packages/frontend/src/lib/components/ui/calendar/calendar-header.svelte create mode 100644 packages/frontend/src/lib/components/ui/calendar/calendar-heading.svelte create mode 100644 packages/frontend/src/lib/components/ui/calendar/calendar-month-select.svelte create mode 100644 packages/frontend/src/lib/components/ui/calendar/calendar-month.svelte create mode 100644 packages/frontend/src/lib/components/ui/calendar/calendar-months.svelte create mode 100644 packages/frontend/src/lib/components/ui/calendar/calendar-nav.svelte create mode 100644 packages/frontend/src/lib/components/ui/calendar/calendar-next-button.svelte create mode 100644 packages/frontend/src/lib/components/ui/calendar/calendar-prev-button.svelte create mode 100644 packages/frontend/src/lib/components/ui/calendar/calendar-year-select.svelte create mode 100644 packages/frontend/src/lib/components/ui/calendar/calendar.svelte create mode 100644 packages/frontend/src/lib/components/ui/calendar/index.ts create mode 100644 packages/frontend/src/lib/components/ui/date-picker/date-picker.svelte create mode 100644 packages/frontend/src/lib/components/ui/date-picker/index.ts create mode 100644 packages/frontend/src/lib/components/ui/popover/index.ts create mode 100644 packages/frontend/src/lib/components/ui/popover/popover-close.svelte create mode 100644 packages/frontend/src/lib/components/ui/popover/popover-content.svelte create mode 100644 packages/frontend/src/lib/components/ui/popover/popover-portal.svelte create mode 100644 packages/frontend/src/lib/components/ui/popover/popover-trigger.svelte create mode 100644 packages/frontend/src/lib/components/ui/popover/popover.svelte diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 39add90..bbd2945 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -15,7 +15,7 @@ "@iconify-json/ri": "^1.2.10", "@iconify/tailwind4": "^1.2.1", "@internationalized/date": "^3.11.0", - "@lucide/svelte": "^0.577.0", + "@lucide/svelte": "^0.561.0", "@sveltejs/adapter-node": "^5.5.4", "@sveltejs/adapter-static": "^3.0.10", "@sveltejs/kit": "^2.53.4", diff --git a/packages/frontend/src/lib/components/ui/calendar/calendar-caption.svelte b/packages/frontend/src/lib/components/ui/calendar/calendar-caption.svelte new file mode 100644 index 0000000..5c93037 --- /dev/null +++ b/packages/frontend/src/lib/components/ui/calendar/calendar-caption.svelte @@ -0,0 +1,76 @@ + + +{#snippet MonthSelect()} + { + if (!placeholder) return; + const v = Number.parseInt(e.currentTarget.value); + const newPlaceholder = placeholder.set({ month: v }); + placeholder = newPlaceholder.subtract({ months: monthIndex }); + }} + /> +{/snippet} + +{#snippet YearSelect()} + +{/snippet} + +{#if captionLayout === "dropdown"} + {@render MonthSelect()} + {@render YearSelect()} +{:else if captionLayout === "dropdown-months"} + {@render MonthSelect()} + {#if placeholder} + {formatYear(placeholder)} + {/if} +{:else if captionLayout === "dropdown-years"} + {#if placeholder} + {formatMonth(placeholder)} + {/if} + {@render YearSelect()} +{:else} + {formatMonth(month)} {formatYear(month)} +{/if} diff --git a/packages/frontend/src/lib/components/ui/calendar/calendar-cell.svelte b/packages/frontend/src/lib/components/ui/calendar/calendar-cell.svelte new file mode 100644 index 0000000..4cdb548 --- /dev/null +++ b/packages/frontend/src/lib/components/ui/calendar/calendar-cell.svelte @@ -0,0 +1,19 @@ + + + diff --git a/packages/frontend/src/lib/components/ui/calendar/calendar-day.svelte b/packages/frontend/src/lib/components/ui/calendar/calendar-day.svelte new file mode 100644 index 0000000..19d7bde --- /dev/null +++ b/packages/frontend/src/lib/components/ui/calendar/calendar-day.svelte @@ -0,0 +1,35 @@ + + +span]:text-xs [&>span]:opacity-70", + className + )} + {...restProps} +/> diff --git a/packages/frontend/src/lib/components/ui/calendar/calendar-grid-body.svelte b/packages/frontend/src/lib/components/ui/calendar/calendar-grid-body.svelte new file mode 100644 index 0000000..8cd86de --- /dev/null +++ b/packages/frontend/src/lib/components/ui/calendar/calendar-grid-body.svelte @@ -0,0 +1,12 @@ + + + diff --git a/packages/frontend/src/lib/components/ui/calendar/calendar-grid-head.svelte b/packages/frontend/src/lib/components/ui/calendar/calendar-grid-head.svelte new file mode 100644 index 0000000..333edc4 --- /dev/null +++ b/packages/frontend/src/lib/components/ui/calendar/calendar-grid-head.svelte @@ -0,0 +1,12 @@ + + + diff --git a/packages/frontend/src/lib/components/ui/calendar/calendar-grid-row.svelte b/packages/frontend/src/lib/components/ui/calendar/calendar-grid-row.svelte new file mode 100644 index 0000000..9032236 --- /dev/null +++ b/packages/frontend/src/lib/components/ui/calendar/calendar-grid-row.svelte @@ -0,0 +1,12 @@ + + + diff --git a/packages/frontend/src/lib/components/ui/calendar/calendar-grid.svelte b/packages/frontend/src/lib/components/ui/calendar/calendar-grid.svelte new file mode 100644 index 0000000..e0c8627 --- /dev/null +++ b/packages/frontend/src/lib/components/ui/calendar/calendar-grid.svelte @@ -0,0 +1,16 @@ + + + diff --git a/packages/frontend/src/lib/components/ui/calendar/calendar-head-cell.svelte b/packages/frontend/src/lib/components/ui/calendar/calendar-head-cell.svelte new file mode 100644 index 0000000..131807e --- /dev/null +++ b/packages/frontend/src/lib/components/ui/calendar/calendar-head-cell.svelte @@ -0,0 +1,19 @@ + + + diff --git a/packages/frontend/src/lib/components/ui/calendar/calendar-header.svelte b/packages/frontend/src/lib/components/ui/calendar/calendar-header.svelte new file mode 100644 index 0000000..c39e955 --- /dev/null +++ b/packages/frontend/src/lib/components/ui/calendar/calendar-header.svelte @@ -0,0 +1,19 @@ + + + diff --git a/packages/frontend/src/lib/components/ui/calendar/calendar-heading.svelte b/packages/frontend/src/lib/components/ui/calendar/calendar-heading.svelte new file mode 100644 index 0000000..a9b9810 --- /dev/null +++ b/packages/frontend/src/lib/components/ui/calendar/calendar-heading.svelte @@ -0,0 +1,16 @@ + + + diff --git a/packages/frontend/src/lib/components/ui/calendar/calendar-month-select.svelte b/packages/frontend/src/lib/components/ui/calendar/calendar-month-select.svelte new file mode 100644 index 0000000..664afab --- /dev/null +++ b/packages/frontend/src/lib/components/ui/calendar/calendar-month-select.svelte @@ -0,0 +1,48 @@ + + + + + {#snippet child({ props, monthItems, selectedMonthItem })} + + + {/snippet} + + diff --git a/packages/frontend/src/lib/components/ui/calendar/calendar-month.svelte b/packages/frontend/src/lib/components/ui/calendar/calendar-month.svelte new file mode 100644 index 0000000..e747fae --- /dev/null +++ b/packages/frontend/src/lib/components/ui/calendar/calendar-month.svelte @@ -0,0 +1,15 @@ + + +
+ {@render children?.()} +
diff --git a/packages/frontend/src/lib/components/ui/calendar/calendar-months.svelte b/packages/frontend/src/lib/components/ui/calendar/calendar-months.svelte new file mode 100644 index 0000000..f717a9d --- /dev/null +++ b/packages/frontend/src/lib/components/ui/calendar/calendar-months.svelte @@ -0,0 +1,19 @@ + + +
+ {@render children?.()} +
diff --git a/packages/frontend/src/lib/components/ui/calendar/calendar-nav.svelte b/packages/frontend/src/lib/components/ui/calendar/calendar-nav.svelte new file mode 100644 index 0000000..27f33d7 --- /dev/null +++ b/packages/frontend/src/lib/components/ui/calendar/calendar-nav.svelte @@ -0,0 +1,19 @@ + + + diff --git a/packages/frontend/src/lib/components/ui/calendar/calendar-next-button.svelte b/packages/frontend/src/lib/components/ui/calendar/calendar-next-button.svelte new file mode 100644 index 0000000..5c5a78d --- /dev/null +++ b/packages/frontend/src/lib/components/ui/calendar/calendar-next-button.svelte @@ -0,0 +1,31 @@ + + +{#snippet Fallback()} + +{/snippet} + + diff --git a/packages/frontend/src/lib/components/ui/calendar/calendar-prev-button.svelte b/packages/frontend/src/lib/components/ui/calendar/calendar-prev-button.svelte new file mode 100644 index 0000000..33cfd63 --- /dev/null +++ b/packages/frontend/src/lib/components/ui/calendar/calendar-prev-button.svelte @@ -0,0 +1,31 @@ + + +{#snippet Fallback()} + +{/snippet} + + diff --git a/packages/frontend/src/lib/components/ui/calendar/calendar-year-select.svelte b/packages/frontend/src/lib/components/ui/calendar/calendar-year-select.svelte new file mode 100644 index 0000000..33cc961 --- /dev/null +++ b/packages/frontend/src/lib/components/ui/calendar/calendar-year-select.svelte @@ -0,0 +1,47 @@ + + + + + {#snippet child({ props, yearItems, selectedYearItem })} + + + {/snippet} + + diff --git a/packages/frontend/src/lib/components/ui/calendar/calendar.svelte b/packages/frontend/src/lib/components/ui/calendar/calendar.svelte new file mode 100644 index 0000000..29b6fff --- /dev/null +++ b/packages/frontend/src/lib/components/ui/calendar/calendar.svelte @@ -0,0 +1,115 @@ + + + + + {#snippet children({ months, weekdays })} + + + + + + {#each months as month, monthIndex (month)} + + + + + + + + {#each weekdays as weekday (weekday)} + + {weekday.slice(0, 2)} + + {/each} + + + + {#each month.weeks as weekDates (weekDates)} + + {#each weekDates as date (date)} + + {#if day} + {@render day({ + day: date, + outsideMonth: !isEqualMonth(date, month.value), + })} + {:else} + + {/if} + + {/each} + + {/each} + + + + {/each} + + {/snippet} + diff --git a/packages/frontend/src/lib/components/ui/calendar/index.ts b/packages/frontend/src/lib/components/ui/calendar/index.ts new file mode 100644 index 0000000..f3a16d2 --- /dev/null +++ b/packages/frontend/src/lib/components/ui/calendar/index.ts @@ -0,0 +1,40 @@ +import Root from "./calendar.svelte"; +import Cell from "./calendar-cell.svelte"; +import Day from "./calendar-day.svelte"; +import Grid from "./calendar-grid.svelte"; +import Header from "./calendar-header.svelte"; +import Months from "./calendar-months.svelte"; +import GridRow from "./calendar-grid-row.svelte"; +import Heading from "./calendar-heading.svelte"; +import GridBody from "./calendar-grid-body.svelte"; +import GridHead from "./calendar-grid-head.svelte"; +import HeadCell from "./calendar-head-cell.svelte"; +import NextButton from "./calendar-next-button.svelte"; +import PrevButton from "./calendar-prev-button.svelte"; +import MonthSelect from "./calendar-month-select.svelte"; +import YearSelect from "./calendar-year-select.svelte"; +import Month from "./calendar-month.svelte"; +import Nav from "./calendar-nav.svelte"; +import Caption from "./calendar-caption.svelte"; + +export { + Day, + Cell, + Grid, + Header, + Months, + GridRow, + Heading, + GridBody, + GridHead, + HeadCell, + NextButton, + PrevButton, + Nav, + Month, + YearSelect, + MonthSelect, + Caption, + // + Root as Calendar, +}; diff --git a/packages/frontend/src/lib/components/ui/date-picker/date-picker.svelte b/packages/frontend/src/lib/components/ui/date-picker/date-picker.svelte new file mode 100644 index 0000000..91a2a96 --- /dev/null +++ b/packages/frontend/src/lib/components/ui/date-picker/date-picker.svelte @@ -0,0 +1,85 @@ + + + + + {#snippet child({ props })} + + {/snippet} + + + + {#if showTime} +
+ { + timeStr = (e.target as HTMLInputElement).value; + }} + class="w-full rounded-md border border-input bg-background px-3 py-1.5 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" + /> +
+ {/if} +
+
diff --git a/packages/frontend/src/lib/components/ui/date-picker/index.ts b/packages/frontend/src/lib/components/ui/date-picker/index.ts new file mode 100644 index 0000000..8275128 --- /dev/null +++ b/packages/frontend/src/lib/components/ui/date-picker/index.ts @@ -0,0 +1 @@ +export { default as DatePicker } from "./date-picker.svelte"; diff --git a/packages/frontend/src/lib/components/ui/popover/index.ts b/packages/frontend/src/lib/components/ui/popover/index.ts new file mode 100644 index 0000000..b79d12e --- /dev/null +++ b/packages/frontend/src/lib/components/ui/popover/index.ts @@ -0,0 +1,19 @@ +import Root from "./popover.svelte"; +import Close from "./popover-close.svelte"; +import Content from "./popover-content.svelte"; +import Trigger from "./popover-trigger.svelte"; +import Portal from "./popover-portal.svelte"; + +export { + Root, + Content, + Trigger, + Close, + Portal, + // + Root as Popover, + Content as PopoverContent, + Trigger as PopoverTrigger, + Close as PopoverClose, + Portal as PopoverPortal, +}; diff --git a/packages/frontend/src/lib/components/ui/popover/popover-close.svelte b/packages/frontend/src/lib/components/ui/popover/popover-close.svelte new file mode 100644 index 0000000..c360925 --- /dev/null +++ b/packages/frontend/src/lib/components/ui/popover/popover-close.svelte @@ -0,0 +1,7 @@ + + + diff --git a/packages/frontend/src/lib/components/ui/popover/popover-content.svelte b/packages/frontend/src/lib/components/ui/popover/popover-content.svelte new file mode 100644 index 0000000..3d79f3c --- /dev/null +++ b/packages/frontend/src/lib/components/ui/popover/popover-content.svelte @@ -0,0 +1,31 @@ + + + + + diff --git a/packages/frontend/src/lib/components/ui/popover/popover-portal.svelte b/packages/frontend/src/lib/components/ui/popover/popover-portal.svelte new file mode 100644 index 0000000..dd8265f --- /dev/null +++ b/packages/frontend/src/lib/components/ui/popover/popover-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/packages/frontend/src/lib/components/ui/popover/popover-trigger.svelte b/packages/frontend/src/lib/components/ui/popover/popover-trigger.svelte new file mode 100644 index 0000000..586323c --- /dev/null +++ b/packages/frontend/src/lib/components/ui/popover/popover-trigger.svelte @@ -0,0 +1,17 @@ + + + diff --git a/packages/frontend/src/lib/components/ui/popover/popover.svelte b/packages/frontend/src/lib/components/ui/popover/popover.svelte new file mode 100644 index 0000000..6b1aa5f --- /dev/null +++ b/packages/frontend/src/lib/components/ui/popover/popover.svelte @@ -0,0 +1,7 @@ + + + diff --git a/packages/frontend/src/routes/admin/articles/[id]/+page.svelte b/packages/frontend/src/routes/admin/articles/[id]/+page.svelte index c78205e..5ab5d5d 100644 --- a/packages/frontend/src/routes/admin/articles/[id]/+page.svelte +++ b/packages/frontend/src/routes/admin/articles/[id]/+page.svelte @@ -12,6 +12,7 @@ import { FileDropZone, MEGABYTE } from "$lib/components/ui/file-drop-zone"; import { getAssetUrl } from "$lib/api"; import { Select, SelectContent, SelectItem, SelectTrigger } from "$lib/components/ui/select"; + import { DatePicker } from "$lib/components/ui/date-picker"; const { data } = $props(); @@ -178,8 +179,8 @@
- - + +
diff --git a/packages/frontend/src/routes/admin/articles/new/+page.svelte b/packages/frontend/src/routes/admin/articles/new/+page.svelte index 41953a3..485309e 100644 --- a/packages/frontend/src/routes/admin/articles/new/+page.svelte +++ b/packages/frontend/src/routes/admin/articles/new/+page.svelte @@ -9,6 +9,7 @@ import { Label } from "$lib/components/ui/label"; import { Textarea } from "$lib/components/ui/textarea"; import { TagsInput } from "$lib/components/ui/tags-input"; + import { DatePicker } from "$lib/components/ui/date-picker"; import { FileDropZone, MEGABYTE } from "$lib/components/ui/file-drop-zone"; let title = $state(""); @@ -154,8 +155,8 @@
- - + +
diff --git a/packages/frontend/src/routes/admin/videos/[id]/+page.svelte b/packages/frontend/src/routes/admin/videos/[id]/+page.svelte index 22d4ad5..eecf2f2 100644 --- a/packages/frontend/src/routes/admin/videos/[id]/+page.svelte +++ b/packages/frontend/src/routes/admin/videos/[id]/+page.svelte @@ -11,6 +11,7 @@ import { FileDropZone, MEGABYTE } from "$lib/components/ui/file-drop-zone"; import { getAssetUrl } from "$lib/api"; import { Select, SelectContent, SelectItem, SelectTrigger } from "$lib/components/ui/select"; + import { DatePicker } from "$lib/components/ui/date-picker"; const { data } = $props(); @@ -124,7 +125,12 @@
{#if movieId} -

{$_("admin.video_form.current_file", { values: { id: movieId } })}

+ {/if}
@@ -135,8 +141,8 @@
- - + +
diff --git a/packages/frontend/src/routes/admin/videos/new/+page.svelte b/packages/frontend/src/routes/admin/videos/new/+page.svelte index 2f34635..90a29da 100644 --- a/packages/frontend/src/routes/admin/videos/new/+page.svelte +++ b/packages/frontend/src/routes/admin/videos/new/+page.svelte @@ -8,6 +8,7 @@ import { Label } from "$lib/components/ui/label"; import { Textarea } from "$lib/components/ui/textarea"; import { TagsInput } from "$lib/components/ui/tags-input"; + import { DatePicker } from "$lib/components/ui/date-picker"; import { FileDropZone, MEGABYTE } from "$lib/components/ui/file-drop-zone"; const { data } = $props(); @@ -151,8 +152,8 @@
- - + +
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d2b6800..87f624f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -186,8 +186,8 @@ importers: specifier: ^3.11.0 version: 3.11.0 '@lucide/svelte': - specifier: ^0.577.0 - version: 0.577.0(svelte@5.53.7) + specifier: ^0.561.0 + version: 0.561.0(svelte@5.53.7) '@sveltejs/adapter-node': specifier: ^5.5.4 version: 5.5.4(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)))(svelte@5.53.7)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0))) @@ -1202,8 +1202,8 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@lucide/svelte@0.577.0': - resolution: {integrity: sha512-0P6mkySd2MapIEgq08tADPmcN4DHndC/02PWwaLkOerXlx5Sv9aT4BxyXLIY+eccr0g/nEyCYiJesqS61YdBZQ==} + '@lucide/svelte@0.561.0': + resolution: {integrity: sha512-vofKV2UFVrKE6I4ewKJ3dfCXSV6iP6nWVmiM83MLjsU91EeJcEg7LoWUABLp/aOTxj1HQNbJD1f3g3L0JQgH9A==} peerDependencies: svelte: ^5 @@ -4138,7 +4138,7 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@lucide/svelte@0.577.0(svelte@5.53.7)': + '@lucide/svelte@0.561.0(svelte@5.53.7)': dependencies: svelte: 5.53.7