feat: date picker

This commit is contained in:
trev 2025-03-16 10:32:18 -04:00
parent f6d75964c1
commit 4adedd96ee
2 changed files with 154 additions and 2 deletions

View File

@ -0,0 +1,141 @@
<script lang="ts">
import { ArrowLeft, ArrowRight, Minus, Calendar } from '@lucide/svelte';
import { DateRangePicker, type DateRange } from 'bits-ui';
import { fade } from 'svelte/transition';
import { getLocalTimeZone } from '@internationalized/date';
let {
start = $bindable(),
end = $bindable(),
onchange
}: {
start?: Date;
end?: Date;
onchange?: (start: Date, end: Date) => void;
} = $props();
let daterange: DateRange | undefined = $state();
</script>
<DateRangePicker.Root
bind:value={daterange}
onValueChange={(v) => {
if (v.start && v.end) {
start = v.start.toDate(getLocalTimeZone());
end = v.end.toDate(getLocalTimeZone());
if (onchange) {
onchange(start, end);
}
}
}}
>
<div
class="bg-mantle border-surface-0 hover:border-surface-2 flex items-center justify-center gap-2 rounded border p-1 text-sm drop-shadow-md transition-all"
>
<DateRangePicker.Label />
{#each ['start', 'end'] as const as type}
<DateRangePicker.Input {type}>
{#snippet children({ segments })}
{#each segments as { part, value }}
<div class="inline-block select-none">
{#if part === 'literal'}
<DateRangePicker.Segment {part} class="text-overlay-0 p-1">
{value}
</DateRangePicker.Segment>
{:else}
<DateRangePicker.Segment
{part}
class="aria-[valuetext=Empty]:text-overlay-0 hover:bg-surface-0 focus:bg-surface-0 focus:outline-sky rounded p-0.5 transition-all focus:outline focus:outline-offset-1"
>
{value}
</DateRangePicker.Segment>
{/if}
</div>
{/each}
{/snippet}
</DateRangePicker.Input>
{#if type === 'start'}
<div aria-hidden="true">
<Minus size="10" />
</div>
{/if}
{/each}
<DateRangePicker.Trigger
class="text-overlay-2 hover:bg-surface-0 ml-1 cursor-pointer rounded p-1 transition-all"
>
<Calendar size="20" />
</DateRangePicker.Trigger>
</div>
<DateRangePicker.Content forceMount>
{#snippet child({ props, open })}
{#if open}
<div
{...props}
class="absolute z-50"
transition:fade={{
duration: 100
}}
>
<DateRangePicker.Calendar
class="border-surface-0 bg-mantle mt-1 rounded border p-3 drop-shadow-md"
>
{#snippet children({ months, weekdays })}
<DateRangePicker.Header class="flex items-center justify-between">
<DateRangePicker.PrevButton
class="hover:bg-surface-0 inline-flex size-10 cursor-pointer items-center justify-center rounded transition-all active:scale-[0.98]"
>
<ArrowLeft />
</DateRangePicker.PrevButton>
<DateRangePicker.Heading class="select-none font-medium" />
<DateRangePicker.NextButton
class="hover:bg-surface-0 inline-flex size-10 cursor-pointer items-center justify-center rounded transition-all active:scale-[0.98]"
>
<ArrowRight />
</DateRangePicker.NextButton>
</DateRangePicker.Header>
<div class="flex flex-col space-y-4 pt-4 sm:flex-row sm:space-x-4 sm:space-y-0">
{#each months as month}
<DateRangePicker.Grid class="w-full border-collapse select-none space-y-1">
<DateRangePicker.GridHead>
<DateRangePicker.GridRow class="mb-1 flex w-full justify-between">
{#each weekdays as day}
<DateRangePicker.HeadCell
class="text-overlay-0 font-normal! w-10 rounded text-xs"
>
{day.slice(0, 2)}
</DateRangePicker.HeadCell>
{/each}
</DateRangePicker.GridRow>
</DateRangePicker.GridHead>
<DateRangePicker.GridBody>
{#each month.weeks as weekDates}
<DateRangePicker.GridRow class="flex w-full">
{#each weekDates as date}
<DateRangePicker.Cell
{date}
month={month.value}
class="p-0! relative m-0 size-10 overflow-visible text-center text-sm focus-within:relative focus-within:z-20"
>
<DateRangePicker.Day
class={'hover:border-sky focus-visible:ring-foreground! data-selected:rounded-none data-selection-end:rounded-r data-selection-start:rounded-l data-highlighted:bg-surface-0 data-selected:bg-surface-1 data-selection-end:bg-surface-2 data-selection-start:bg-surface-2 data-disabled:text-text/30 data-unavailable:text-overlay-0 data-disabled:pointer-events-none data-outside-month:pointer-events-none data-highlighted:rounded-none data-unavailable:line-through group relative inline-flex size-10 items-center justify-center overflow-visible whitespace-nowrap rounded border border-transparent bg-transparent p-0 text-sm font-normal transition-all'}
>
<div
class="bg-sky group-data-selected:bg-background group-data-today:block absolute top-[5px] hidden size-1 rounded-full transition-all"
></div>
{date.day}
</DateRangePicker.Day>
</DateRangePicker.Cell>
{/each}
</DateRangePicker.GridRow>
{/each}
</DateRangePicker.GridBody>
</DateRangePicker.Grid>
{/each}
</div>
{/snippet}
</DateRangePicker.Calendar>
</div>
{/if}
{/snippet}
</DateRangePicker.Content>
</DateRangePicker.Root>

View File

@ -1,11 +1,12 @@
<script lang="ts">
import { ItemClient } from '$lib/transport';
import { Plus, Trash, Pencil } from '@lucide/svelte';
import { Plus, Trash, Pencil, Calendar, Minus, ArrowLeft, ArrowRight } from '@lucide/svelte';
import { timestampFromDate, timestampDate } from '@bufbuild/protobuf/wkt';
import { toast } from 'svelte-sonner';
import { ConnectError } from '@connectrpc/connect';
import Modal from '$lib/ui/Modal.svelte';
import Button from '$lib/ui/Button.svelte';
import DateRangePicker from '$lib/ui/DateRangePicker.svelte';
import { SvelteMap } from 'svelte/reactivity';
// Config
@ -44,8 +45,18 @@
}
</script>
<div class="mx-4 my-2 flex items-center justify-center gap-4">
<input
type="text"
placeholder="Filter..."
class="border-surface-0 hover:border-surface-2 w-70 bg-mantle rounded border p-2 text-sm drop-shadow-md transition-all"
bind:value={filter}
/>
<DateRangePicker bind:start bind:end />
</div>
<div
class="border-surface-0 bg-mantle mx-4 mt-2 overflow-x-auto rounded border-x border-t drop-shadow-md"
class="border-surface-0 bg-mantle mx-4 my-2 overflow-x-auto rounded border-x border-t drop-shadow-md"
>
<table class="w-full table-auto border-collapse text-left rtl:text-right">
<thead>