From 5363e4064ffa401636296afb67426d93b5243108 Mon Sep 17 00:00:00 2001 From: Jeanne Mas Date: Tue, 4 Jun 2024 11:05:07 -0400 Subject: [PATCH] component: Calendar --- .../components/calendar/CalendarCell.svelte | 42 ++++ .../components/calendar/CalendarDay.svelte | 43 +++- .../components/calendar/CalendarGrid.svelte | 41 ++++ .../calendar/CalendarGridBody.svelte | 41 ++++ .../calendar/CalendarGridHead.svelte | 41 ++++ .../calendar/CalendarGridRow.svelte | 44 ++++ .../calendar/CalendarHeadCell.svelte | 34 +++ .../components/calendar/CalendarHeader.svelte | 45 +++- .../calendar/CalendarHeading.svelte | 35 +++ .../components/calendar/CalendarMonths.svelte | 33 ++- .../calendar/CalendarNextButton.svelte | 38 ++- .../calendar/CalendarPreviousButton.svelte | 38 ++- .../components/calendar/CalendarRoot.svelte | 231 ++++++++---------- src/lib/components/calendar/context.ts | 18 +- src/lib/components/calendar/index.ts | 2 +- .../components/form/FormFieldErrors.svelte | 2 +- src/routes/accordion/+page.svelte | 8 +- src/routes/calendar/+page.svelte | 42 +++- src/routes/calendar/+page.ts | 8 +- src/routes/calendar/props.schema.ts | 8 +- src/routes/combobox/+page.svelte | 6 +- src/routes/command/+page.svelte | 8 +- 22 files changed, 643 insertions(+), 165 deletions(-) diff --git a/src/lib/components/calendar/CalendarCell.svelte b/src/lib/components/calendar/CalendarCell.svelte index d7fb426..819d402 100644 --- a/src/lib/components/calendar/CalendarCell.svelte +++ b/src/lib/components/calendar/CalendarCell.svelte @@ -1,10 +1,13 @@ + + ; /** @@ -26,7 +28,9 @@ */ export const dayStyles = tv({ base: [ - Button.rootStyles({ variant: 'ghost' }), + Button.rootStyles({ + variant: 'ghost', + }), 'size-8 p-0 font-normal ', '[&[data-today]:not([data-selected])]:bg-accent [&[data-today]:not([data-selected])]:text-accent-foreground', @@ -56,11 +60,48 @@ export let month: Props['month']; $: attributes = $$restProps as Attributes; + + const cellCtx = cellContext.get(); + + if (!cellCtx) { + throw new Error('Calendar.Day must be used within a Calendar.Cell component.'); + } + + import { Calendar as CalendarPrimitive } from 'bits-ui'; import type { SvelteHTMLElements } from 'svelte/elements'; + import { writable } from 'svelte/store'; import { tv } from 'tailwind-variants'; import type { ComponentInfo } from '$lib/utils/types.js'; + import { gridContext, monthsContext } from './context.js'; + type Primitive = ComponentInfo; /** @@ -37,11 +40,49 @@ export let el: Props['el'] = undefined; $: attributes = $$restProps as Attributes; + + const monthsCtx = monthsContext.get(); + + if (!monthsCtx) { + throw new Error('Calendar.Grid must be used within a Calendar.Months component.'); + } + + const gridCtx = gridContext.set(writable()); + + $: gridCtx.update(($ctx) => ({ + ...$ctx, + })); + + import { Calendar as CalendarPrimitive } from 'bits-ui'; import type { SvelteHTMLElements } from 'svelte/elements'; + import { writable } from 'svelte/store'; import { tv } from 'tailwind-variants'; import type { ComponentInfo } from '$lib/utils/types.js'; + import { gridBodyContext, gridContext } from './context.js'; + type Primitive = ComponentInfo; /** @@ -37,11 +40,49 @@ export let el: Props['el'] = undefined; $: attributes = $$restProps as Attributes; + + const gridCtx = gridContext.get(); + + if (!gridCtx) { + throw new Error('Calendar.GridBody must be used within a Calendar.Grid component.'); + } + + const gridBodyCtx = gridBodyContext.set(writable()); + + $: gridBodyCtx.update(($ctx) => ({ + ...$ctx, + })); + + import { Calendar as CalendarPrimitive } from 'bits-ui'; import type { SvelteHTMLElements } from 'svelte/elements'; + import { writable } from 'svelte/store'; import { tv } from 'tailwind-variants'; import type { ComponentInfo } from '$lib/utils/types.js'; + import { gridContext, gridHeadContext } from './context.js'; + type Primitive = ComponentInfo; /** @@ -37,11 +40,49 @@ export let el: Props['el'] = undefined; $: attributes = $$restProps as Attributes; + + const gridCtx = gridContext.get(); + + if (!gridCtx) { + throw new Error('Calendar.GridHead must be used within a Calendar.Grid component.'); + } + + const gridHeadCtx = gridHeadContext.set(writable()); + + $: gridHeadCtx.update(($ctx) => ({ + ...$ctx, + })); + + import { Calendar as CalendarPrimitive } from 'bits-ui'; import type { SvelteHTMLElements } from 'svelte/elements'; + import { writable } from 'svelte/store'; import { tv } from 'tailwind-variants'; import type { ComponentInfo } from '$lib/utils/types.js'; + import { gridBodyContext, gridHeadContext, gridRowContext } from './context.js'; + type Primitive = ComponentInfo; /** @@ -37,11 +40,52 @@ export let el: Props['el'] = undefined; $: attributes = $$restProps as Attributes; + + const gridHeadCtx = gridHeadContext.get(); + const gridBodyCtx = gridBodyContext.get(); + + if (!gridHeadCtx && !gridBodyCtx) { + throw new Error( + 'Calendar.GridRow must be used within a Calendar.GridHead or Calendar.GridBody component.', + ); + } + + const gridRowCtx = gridRowContext.set(writable()); + + $: gridRowCtx.update(($ctx) => ({ + ...$ctx, + })); + + ; /** @@ -37,11 +39,43 @@ export let el: Props['el'] = undefined; $: attributes = $$restProps as Attributes; + + const gridRowCtx = gridRowContext.get(); + + if (!gridRowCtx) { + throw new Error('Calendar.HeadCell must be used within a Calendar.GridRow component.'); + } + + import { Calendar as CalendarPrimitive } from 'bits-ui'; import type { SvelteHTMLElements } from 'svelte/elements'; + import { writable } from 'svelte/store'; import { tv } from 'tailwind-variants'; import type { ComponentInfo } from '$lib/utils/types.js'; + import { headerContext, rootContext } from './context.js'; + type Primitive = ComponentInfo; /** * The attributes of the header. */ - export type Attributes = SvelteHTMLElements['div']; + export type Attributes = SvelteHTMLElements['header']; /** * The props of the header. */ @@ -24,7 +27,7 @@ * The styles of the header. */ export const headerStyles = tv({ - base: ['relative flex w-full flex-row items-center justify-between'], + base: ['relative flex flex-row items-center justify-between'], }); @@ -37,11 +40,49 @@ export let el: Props['el'] = undefined; $: attributes = $$restProps as Attributes; + + const rootCtx = rootContext.get(); + + if (!rootCtx) { + throw new Error('Calendar.Header must be used within a Calendar.Root component.'); + } + + const headerCtx = headerContext.set(writable()); + + headerCtx.update(($ctx) => ({ + ...$ctx, + })); + + ; /** @@ -37,11 +39,44 @@ export let el: Props['el'] = undefined; $: attributes = $$restProps as Attributes; + + const headerCtx = headerContext.get(); + + if (!headerCtx) { + throw new Error('Calendar.Heading must be used within a Calendar.Header component.'); + } + + import type { SvelteHTMLElements } from 'svelte/elements'; + import { writable } from 'svelte/store'; import { tv } from 'tailwind-variants'; import type { EmptyObject } from '$lib/utils/types.js'; - import { rootContext } from './context.js'; + import { monthsContext, rootContext } from './context.js'; /** * The attributes of the months. @@ -50,11 +51,41 @@ } $: ({ breakpoint } = $rootCtx!); + + const monthsCtx = monthsContext.set(writable()); + + $: monthsCtx.update(($ctx) => ({ + ...$ctx, + })); + +
--> + + ; /** @@ -27,7 +29,9 @@ */ export const previousButtonStyles = tv({ base: [ - Button.rootStyles({ variant: 'outline' }), + Button.rootStyles({ + variant: 'outline', + }), 'size-8 bg-transparent p-0 opacity-50', 'hover:opacity-100', @@ -44,11 +48,43 @@ export let el: Props['el'] = undefined; $: attributes = $$restProps as Attributes; + + const headerCtx = headerContext.get(); + + if (!headerCtx) { + throw new Error('Calendar.PreviousButton must be used within a Calendar.Header component.'); + } + + - import type { DateValue } from '@internationalized/date'; - import { Calendar as CalendarPrimitive, type Month } from 'bits-ui'; + import { Calendar as CalendarPrimitive } from 'bits-ui'; import type { SvelteHTMLElements } from 'svelte/elements'; import { writable } from 'svelte/store'; import { tv, type VariantProps } from 'tailwind-variants'; - import { cn } from '$lib/utils/cn.js'; import type { ComponentInfo } from '$lib/utils/types.js'; - import CalendarCell, { - type Attributes as CalendarCellAttributes, - type Props as CalendarCellProps, - } from './CalendarCell.svelte'; - import CalendarDay, { - type Attributes as CalendarDayAttributes, - type Props as CalendarDayProps, - } from './CalendarDay.svelte'; - import CalendarGrid, { - type Attributes as CalendarGridAttributes, - type Props as CalendarGridProps, - } from './CalendarGrid.svelte'; - import CalendarGridBody, { - type Attributes as CalendarGridBodyAttributes, - type Props as CalendarGridBodyProps, - } from './CalendarGridBody.svelte'; - import CalendarGridHead, { - type Attributes as CalendarGridHeadAttributes, - type Props as CalendarGridHeadProps, - } from './CalendarGridHead.svelte'; - import CalendarGridRow, { - type Attributes as CalendarGridRowAttributes, - type Props as CalendarGridRowProps, - } from './CalendarGridRow.svelte'; - import CalendarHeadCell, { - type Attributes as CalendarHeadCellAttributes, - type Props as CalendarHeadCellProps, - } from './CalendarHeadCell.svelte'; - import CalendarHeader, { - type Attributes as CalendarHeaderAttributes, - type Props as CalendarHeaderProps, - } from './CalendarHeader.svelte'; - import CalendarHeading, { - type Attributes as CalendarHeadingAttributes, - type Props as CalendarHeadingProps, - } from './CalendarHeading.svelte'; - import CalendarMonths, { - type Attributes as CalendarMonthsAttributes, - type Props as CalendarMonthsProps, - } from './CalendarMonths.svelte'; - import CalendarNextButton, { - type Attributes as CalendarNextButtonAttributes, - type Props as CalendarNextButtonProps, - } from './CalendarNextButton.svelte'; - import CalendarPreviousButton, { - type Attributes as CalendarPreviousButtonAttributes, - type Props as CalendarPreviousButtonProps, - } from './CalendarPreviousButton.svelte'; import { rootContext } from './context.js'; type Primitive = ComponentInfo< @@ -83,26 +33,6 @@ * @default 'sm' */ breakpoint?: Breakpoint; - /** - * Callback to be called when a day is clicked. - * - * @param day The clicked day. - */ - onDayClick?: (day: { date: DateValue; month: Month }) => unknown; - } & { - cellAttributesAndProps?: CalendarCellAttributes & CalendarCellProps; - dayAttributesAndProps?: CalendarDayAttributes & CalendarDayProps; - gridAttributesAndProps?: CalendarGridAttributes & CalendarGridProps; - gridBodyAttributesAndProps?: CalendarGridBodyAttributes & CalendarGridBodyProps; - gridHeadAttributesAndProps?: CalendarGridHeadAttributes & CalendarGridHeadProps; - gridRowAttributesAndProps?: CalendarGridRowAttributes & CalendarGridRowProps; - headCellAttributesAndProps?: CalendarHeadCellAttributes & CalendarHeadCellProps; - headerAttributesAndProps?: CalendarHeaderAttributes & CalendarHeaderProps; - headingAttributesAndProps?: CalendarHeadingAttributes & CalendarHeadingProps; - monthsAttributesAndProps?: CalendarMonthsAttributes & CalendarMonthsProps; - nextButtonAttributesAndProps?: CalendarNextButtonAttributes & CalendarNextButtonProps; - previousButtonAttributesAndProps?: CalendarPreviousButtonAttributes & - CalendarPreviousButtonProps; }; /** * The slots of the calendar. @@ -136,36 +66,22 @@ export let asChild: TypedProps['asChild'] = undefined; export let breakpoint: TypedProps['breakpoint'] = rootStyles.defaultVariants.breakpoint; export let calendarLabel: TypedProps['calendarLabel'] = undefined; - export let cellAttributesAndProps: TypedProps['cellAttributesAndProps'] = undefined; - export let dayAttributesAndProps: TypedProps['dayAttributesAndProps'] = undefined; export let disabled: TypedProps['disabled'] = undefined; export let el: TypedProps['el'] = undefined; export let fixedWeeks: TypedProps['fixedWeeks'] = undefined; - export let gridAttributesAndProps: TypedProps['gridAttributesAndProps'] = undefined; - export let gridBodyAttributesAndProps: TypedProps['gridBodyAttributesAndProps'] = undefined; - export let gridHeadAttributesAndProps: TypedProps['gridHeadAttributesAndProps'] = undefined; - export let gridRowAttributesAndProps: TypedProps['gridRowAttributesAndProps'] = undefined; - export let headCellAttributesAndProps: TypedProps['headCellAttributesAndProps'] = undefined; - export let headerAttributesAndProps: TypedProps['headerAttributesAndProps'] = undefined; - export let headingAttributesAndProps: TypedProps['headingAttributesAndProps'] = undefined; export let initialFocus: TypedProps['initialFocus'] = undefined; export let isDateDisabled: TypedProps['isDateDisabled'] = undefined; export let isDateUnavailable: TypedProps['isDateUnavailable'] = undefined; export let locale: TypedProps['locale'] = undefined; export let maxValue: TypedProps['maxValue'] = undefined; export let minValue: TypedProps['minValue'] = undefined; - export let monthsAttributesAndProps: TypedProps['monthsAttributesAndProps'] = undefined; export let multiple: TypedProps['multiple'] = undefined; - export let nextButtonAttributesAndProps: TypedProps['nextButtonAttributesAndProps'] = undefined; export let numberOfMonths: TypedProps['numberOfMonths'] = undefined; - export let onDayClick: TypedProps['onDayClick'] = undefined; export let onPlaceholderChange: TypedProps['onPlaceholderChange'] = undefined; export let onValueChange: TypedProps['onValueChange'] = undefined; export let pagedNavigation: TypedProps['pagedNavigation'] = undefined; export let placeholder: TypedProps['placeholder'] = undefined; export let preventDeselect: TypedProps['preventDeselect'] = undefined; - export let previousButtonAttributesAndProps: TypedProps['previousButtonAttributesAndProps'] = - undefined; export let readonly: TypedProps['readonly'] = undefined; export let weekStartsOn: TypedProps['weekStartsOn'] = undefined; export let weekdayFormat: TypedProps['weekdayFormat'] = 'short'; @@ -184,6 +100,101 @@ + + - - - - - - - - - - - {#each months as month} - - - - {#each weekdays as weekday} - - {weekday.slice(0, 2)} - - {/each} - - - - - {#each month.weeks as weekDates} - - {#each weekDates as date} - - - - {/each} - - {/each} - - - {/each} - - + diff --git a/src/lib/components/calendar/context.ts b/src/lib/components/calendar/context.ts index 523e086..65cf4f7 100644 --- a/src/lib/components/calendar/context.ts +++ b/src/lib/components/calendar/context.ts @@ -2,6 +2,22 @@ import type { Writable } from 'svelte/store'; import { Context } from '$lib/utils/context.js'; -import type { RootProps } from './index.js'; +import type { + CellProps, + GridBodyProps, + GridHeadProps, + GridProps, + GridRowProps, + HeaderProps, + MonthsProps, + RootProps, +} from './index.js'; +export const cellContext = new Context>>(); +export const gridBodyContext = new Context>>(); +export const gridHeadContext = new Context>>(); +export const gridContext = new Context>>(); +export const gridRowContext = new Context>>(); +export const headerContext = new Context>>(); +export const monthsContext = new Context>>(); export const rootContext = new Context>>(); diff --git a/src/lib/components/calendar/index.ts b/src/lib/components/calendar/index.ts index f15835f..2eeda84 100644 --- a/src/lib/components/calendar/index.ts +++ b/src/lib/components/calendar/index.ts @@ -85,8 +85,8 @@ export { export { default as Root, rootStyles, + type Breakpoint, type Attributes as RootAttributes, - type Breakpoint as RootBreakpoint, type Props as RootProps, type Slots as RootSlots, } from './CalendarRoot.svelte'; diff --git a/src/lib/components/form/FormFieldErrors.svelte b/src/lib/components/form/FormFieldErrors.svelte index fa4a388..9dc8561 100644 --- a/src/lib/components/form/FormFieldErrors.svelte +++ b/src/lib/components/form/FormFieldErrors.svelte @@ -60,7 +60,7 @@ let:fieldErrorsAttrs > - {#each errors as error} + {#each errors as error, index (index)}
{error}
diff --git a/src/routes/accordion/+page.svelte b/src/routes/accordion/+page.svelte index 3b96a8d..87f51d9 100644 --- a/src/routes/accordion/+page.svelte +++ b/src/routes/accordion/+page.svelte @@ -43,16 +43,16 @@ - {#each data.posts as post (post.id)} - + {#each data.posts as { body, id, title } (id)} + - {post.title} + {title} - {post.body} + {body} {/each} diff --git a/src/routes/calendar/+page.svelte b/src/routes/calendar/+page.svelte index 62b759d..401b0a6 100644 --- a/src/routes/calendar/+page.svelte +++ b/src/routes/calendar/+page.svelte @@ -26,9 +26,9 @@ $: selectedBreakpoint = { label: $props.breakpoint, value: $props.breakpoint, - } satisfies Selected; + } satisfies Selected; - function handleBreakpointhange(selected?: Selected) { + function handleBreakpointhange(selected?: Selected) { $props.breakpoint = selected!.value; } @@ -151,6 +151,42 @@ - + + + + + + + + + + + {#each months as { value, weeks }, index (index)} + + + + {#each weekdays as weekday, index (index)} + + {weekday.slice(0, 2)} + + {/each} + + + + + {#each weeks as weekDates, index (index)} + + {#each weekDates as date, index (index)} + + + + {/each} + + {/each} + + + {/each} + + diff --git a/src/routes/calendar/+page.ts b/src/routes/calendar/+page.ts index d6bb5a8..5c06878 100644 --- a/src/routes/calendar/+page.ts +++ b/src/routes/calendar/+page.ts @@ -2,7 +2,7 @@ import type { Selected } from 'bits-ui'; import { superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; -import { rootStyles, type RootBreakpoint } from '$lib/components/calendar/index.js'; +import * as Calendar from '$lib/components/calendar/index.js'; import type { PageLoad } from './$types.js'; import { schema } from './props.schema.js'; @@ -10,7 +10,7 @@ import { schema } from './props.schema.js'; export const load = (async () => { const form = await superValidate(zod(schema), { defaults: { - breakpoint: rootStyles.defaultVariants.breakpoint!, + breakpoint: Calendar.rootStyles.defaultVariants.breakpoint!, disabled: false, fixedWeeks: false, numberOfMonths: 1, @@ -19,10 +19,10 @@ export const load = (async () => { readonly: false, }, }); - const breakpoints = Object.keys(rootStyles.variants.breakpoint).map((breakpoint) => ({ + const breakpoints = Object.keys(Calendar.rootStyles.variants.breakpoint).map((breakpoint) => ({ label: breakpoint, value: breakpoint, - })) as Selected[]; + })) as Selected[]; return { breakpoints, diff --git a/src/routes/calendar/props.schema.ts b/src/routes/calendar/props.schema.ts index 9126bec..bcb2ace 100644 --- a/src/routes/calendar/props.schema.ts +++ b/src/routes/calendar/props.schema.ts @@ -1,10 +1,10 @@ import z from 'zod'; -import { rootStyles, type RootBreakpoint } from '$lib/components/calendar/index.js'; +import * as Calendar from '$lib/components/calendar/index.js'; -const breakpoints = Object.keys(rootStyles.variants.breakpoint) as [ - RootBreakpoint, - ...RootBreakpoint[], +const breakpoints = Object.keys(Calendar.rootStyles.variants.breakpoint) as [ + Calendar.Breakpoint, + ...Calendar.Breakpoint[], ]; export const schema = z.object({ diff --git a/src/routes/combobox/+page.svelte b/src/routes/combobox/+page.svelte index 99a67c5..21ec3b8 100644 --- a/src/routes/combobox/+page.svelte +++ b/src/routes/combobox/+page.svelte @@ -59,11 +59,11 @@ - {#each filteredUsers as user (user.value)} - + {#each filteredUsers as { label, value } (value)} + - {user.label} + {label} {:else} No users found diff --git a/src/routes/command/+page.svelte b/src/routes/command/+page.svelte index b61d0db..3c93ec0 100644 --- a/src/routes/command/+page.svelte +++ b/src/routes/command/+page.svelte @@ -79,9 +79,9 @@ No results found. - {#each activeTodos as todo (todo.id)} + {#each activeTodos as { id, title } (id)} - {todo.title} + {title} {/each} @@ -89,9 +89,9 @@ - {#each completedTodos as todo (todo.id)} + {#each completedTodos as { id, title } (id)} - {todo.title} + {title} {/each}