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 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
<script lang="ts">
|
||||
import { parseDate, type DateValue } from "@internationalized/date";
|
||||
import { Calendar } from "$lib/components/ui/calendar";
|
||||
import * as Popover from "$lib/components/ui/popover";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
|
||||
let {
|
||||
value = $bindable(""),
|
||||
placeholder = "Pick a date",
|
||||
showTime = true,
|
||||
}: {
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
showTime?: boolean;
|
||||
} = $props();
|
||||
|
||||
function toCalendarDate(v: string): DateValue | undefined {
|
||||
if (!v) return undefined;
|
||||
try {
|
||||
return parseDate(v.slice(0, 10));
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function pad(n: number) {
|
||||
return String(n).padStart(2, "0");
|
||||
}
|
||||
|
||||
let calendarDate = $state<DateValue | undefined>(toCalendarDate(value));
|
||||
let timeStr = $state(value.length >= 16 ? value.slice(11, 16) : "00:00");
|
||||
let open = $state(false);
|
||||
|
||||
$effect(() => {
|
||||
if (calendarDate) {
|
||||
const d = calendarDate;
|
||||
const dateStr = `${d.year}-${pad(d.month)}-${pad(d.day)}`;
|
||||
value = showTime ? `${dateStr}T${timeStr}` : dateStr;
|
||||
} else {
|
||||
value = "";
|
||||
}
|
||||
});
|
||||
|
||||
let displayLabel = $derived.by(() => {
|
||||
if (!calendarDate) return placeholder;
|
||||
const d = calendarDate;
|
||||
const date = new Date(d.year, d.month - 1, d.day);
|
||||
const dateLabel = date.toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
});
|
||||
return showTime ? `${dateLabel} ${timeStr}` : dateLabel;
|
||||
});
|
||||
</script>
|
||||
|
||||
<Popover.Root bind:open>
|
||||
<Popover.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Button
|
||||
variant="outline"
|
||||
{...props}
|
||||
class="w-full justify-start font-normal {!calendarDate ? 'text-muted-foreground' : ''}"
|
||||
>
|
||||
<span class="icon-[ri--calendar-line] h-4 w-4 mr-2 shrink-0"></span>
|
||||
{displayLabel}
|
||||
</Button>
|
||||
{/snippet}
|
||||
</Popover.Trigger>
|
||||
<Popover.Content class="w-auto p-0" align="start">
|
||||
<Calendar bind:value={calendarDate} />
|
||||
{#if showTime}
|
||||
<div class="border-t border-border/40 p-3">
|
||||
<input
|
||||
type="time"
|
||||
value={timeStr}
|
||||
oninput={(e) => {
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
Reference in New Issue
Block a user