diff --git a/src/uix/soma/components/date-range-field/components/date-range-field-input.svelte b/src/uix/soma/components/date-range-field/components/date-range-field-input.svelte index 2380b43a8..5cbacf2ec 100644 --- a/src/uix/soma/components/date-range-field/components/date-range-field-input.svelte +++ b/src/uix/soma/components/date-range-field/components/date-range-field-input.svelte @@ -75,9 +75,11 @@ ? provider.opts.startReadonlySegments.current : provider.opts.endReadonlySegments.current ), - // Date-range-field has no `kind` notion yet — endpoints stay as - // full date segments. Range-picker propagation lands in commit D. - kind: readableActive(() => 'date' as const), + // Forward the range provider's `kind` (Chakra-style picker + // variant) so both endpoint DateFields filter their segments + // uniformly. There's no use case for kind='year' on start + + // kind='month' on end — the range picker enforces a single kind. + kind: provider.opts.kind, required: readableActive(() => provider.isRequired), granularity: provider.opts.granularity, hideTimeZone: provider.opts.hideTimeZone, diff --git a/src/uix/soma/components/date-range-field/components/date-range-field.svelte b/src/uix/soma/components/date-range-field/components/date-range-field.svelte index e3258dae3..1c4e7bc04 100644 --- a/src/uix/soma/components/date-range-field/components/date-range-field.svelte +++ b/src/uix/soma/components/date-range-field/components/date-range-field.svelte @@ -39,6 +39,7 @@ startReadonlySegments = [], endReadonlySegments = [], required = false, + kind = 'date', granularity, hideTimeZone = false, hourCycle, @@ -129,6 +130,7 @@ startReadonlySegments: readableActive(() => startReadonlySegments), endReadonlySegments: readableActive(() => endReadonlySegments), required: readableActive(() => required), + kind: readableActive(() => kind), granularity: readableActive(() => granularity), hideTimeZone: readableActive(() => hideTimeZone), hourCycle: readableActive(() => hourCycle), diff --git a/src/uix/soma/components/date-range-field/date-range-field-provider.svelte.test.ts b/src/uix/soma/components/date-range-field/date-range-field-provider.svelte.test.ts index ecfbfd198..3ba044984 100644 --- a/src/uix/soma/components/date-range-field/date-range-field-provider.svelte.test.ts +++ b/src/uix/soma/components/date-range-field/date-range-field-provider.svelte.test.ts @@ -73,6 +73,7 @@ function createDateRangeFieldOpts(root = document.createElement('div')) { startReadonlySegments: state([]), endReadonlySegments: state([]), required: state(false), + kind: state<'date' | 'month' | 'year'>('date'), granularity: state(undefined), hideTimeZone: state(false), hourCycle: state(undefined), diff --git a/src/uix/soma/components/date-range-field/date-range-field-provider.svelte.ts b/src/uix/soma/components/date-range-field/date-range-field-provider.svelte.ts index ba9a20c04..7b77bee7e 100644 --- a/src/uix/soma/components/date-range-field/date-range-field-provider.svelte.ts +++ b/src/uix/soma/components/date-range-field/date-range-field-provider.svelte.ts @@ -47,6 +47,16 @@ interface DateRangeFieldOpts startReadonlySegments: EditableSegmentPart[]; endReadonlySegments: EditableSegmentPart[]; required: boolean; + /** + * Chakra-style kind. Forwarded to BOTH endpoint DateField + * providers so the start and end inputs filter their segments + * uniformly (range pickers always show the same kind on both + * sides — there's no use case for kind='year' on start and + * kind='month' on end). + * + * @default 'date' + */ + kind: 'date' | 'month' | 'year'; granularity: Granularity | undefined; hideTimeZone: boolean; hourCycle: HourCycle | undefined; diff --git a/src/uix/soma/components/date-range-field/types.ts b/src/uix/soma/components/date-range-field/types.ts index 01857d5c4..72f253c6f 100644 --- a/src/uix/soma/components/date-range-field/types.ts +++ b/src/uix/soma/components/date-range-field/types.ts @@ -60,6 +60,17 @@ export type DateRangeFieldProviderProps = WithChild<{ endReadonlySegments?: EditableSegmentPart[]; /** Hidden form inputs are required. @default false */ required?: boolean; + /** + * Chakra-style picker kind. Forwarded to both endpoint DateField + * providers so the start/end inputs filter their segments uniformly. + * + * - `'date'` (default): day + month + year segments + * - `'month'`: month + year segments + * - `'year'`: year segment only + * + * @default 'date' + */ + kind?: 'date' | 'month' | 'year'; /** Visual granularity (auto from placeholder when omitted). */ granularity?: Granularity; /** Hide the timezone segment on ZonedDateTime. @default false */ diff --git a/src/uix/soma/components/date-range-picker/components/date-range-picker.svelte b/src/uix/soma/components/date-range-picker/components/date-range-picker.svelte index b842dcd84..7ec2fb06d 100644 --- a/src/uix/soma/components/date-range-picker/components/date-range-picker.svelte +++ b/src/uix/soma/components/date-range-picker/components/date-range-picker.svelte @@ -32,6 +32,7 @@ onOpenChangeComplete = () => {}, closeOnRangeSelect = true, mode = 'inline', + kind = 'date', validate, onInvalid, minValue, @@ -175,6 +176,7 @@ required: readableActive(() => required), closeOnRangeSelect: readableActive(() => closeOnRangeSelect), mode: readableActive(() => mode), + kind: readableActive(() => kind), granularity: readableActive(() => granularity), hideTimeZone: readableActive(() => hideTimeZone), hourCycle: readableActive(() => hourCycle), @@ -228,6 +230,7 @@ startReadonlySegments: readableActive(() => startReadonlySegments), endReadonlySegments: readableActive(() => endReadonlySegments), required: readableActive(() => required), + kind: readableActive(() => kind), granularity: readableActive(() => granularity), hideTimeZone: readableActive(() => hideTimeZone), hourCycle: readableActive(() => hourCycle), diff --git a/src/uix/soma/components/date-range-picker/date-range-picker-provider.svelte.test.ts b/src/uix/soma/components/date-range-picker/date-range-picker-provider.svelte.test.ts index 4281d47f3..459cd7c62 100644 --- a/src/uix/soma/components/date-range-picker/date-range-picker-provider.svelte.test.ts +++ b/src/uix/soma/components/date-range-picker/date-range-picker-provider.svelte.test.ts @@ -81,6 +81,7 @@ function createDateRangePickerOpts() { required: state(false), closeOnRangeSelect: state(true), mode: state<'inline' | 'modal'>('inline'), + kind: state<'date' | 'month' | 'year'>('date'), granularity: state(undefined), hideTimeZone: state(false), hourCycle: state(undefined), diff --git a/src/uix/soma/components/date-range-picker/date-range-picker-provider.svelte.ts b/src/uix/soma/components/date-range-picker/date-range-picker-provider.svelte.ts index bc349bcb7..b7869165d 100644 --- a/src/uix/soma/components/date-range-picker/date-range-picker-provider.svelte.ts +++ b/src/uix/soma/components/date-range-picker/date-range-picker-provider.svelte.ts @@ -15,6 +15,17 @@ import { getLocalTimeZone, isBefore } from '$libs/days'; * action in the footer; Cancel restores the value snapshot taken at open. */ export type DateRangePickerMode = 'inline' | 'modal'; + +/** + * Range picker granularity (Chakra-style). Drives both the visible + * segments in the start/end inputs AND the calendar view in the + * popover: + * + * - `'date'` (default): MM/DD/YYYY inputs + day calendar + * - `'month'`: MM/YYYY inputs + month grid + * - `'year'`: YYYY inputs + year grid (decade) + */ +export type DateRangePickerKind = 'date' | 'month' | 'year'; import type { DateValue, DateRange, @@ -60,6 +71,7 @@ interface DateRangePickerOpts required: boolean; closeOnRangeSelect: boolean; mode: DateRangePickerMode; + kind: DateRangePickerKind; granularity: Granularity | undefined; hideTimeZone: boolean; hourCycle: HourCycle | undefined; diff --git a/src/uix/soma/components/date-range-picker/exports.ts b/src/uix/soma/components/date-range-picker/exports.ts index 0451d4470..2ccb35594 100644 --- a/src/uix/soma/components/date-range-picker/exports.ts +++ b/src/uix/soma/components/date-range-picker/exports.ts @@ -6,7 +6,10 @@ export { default as Calendar } from './components/date-range-picker-calendar.sve // Provider class — required by consumers that wire footer actions // (Clear / Cancel / Save) inside the calendar tree via `.require()`. export { DateRangePickerProvider } from './date-range-picker-provider.svelte'; -export type { DateRangePickerMode } from './date-range-picker-provider.svelte'; +export type { + DateRangePickerKind, + DateRangePickerMode +} from './date-range-picker-provider.svelte'; // Popover surface export { Content, Arrow, Close, Overlay, Anchor } from '../popover/exports'; diff --git a/src/uix/soma/components/date-range-picker/types.ts b/src/uix/soma/components/date-range-picker/types.ts index e6ece1abc..cc677b880 100644 --- a/src/uix/soma/components/date-range-picker/types.ts +++ b/src/uix/soma/components/date-range-picker/types.ts @@ -58,6 +58,19 @@ type DateRangePickerBaseProps = { * @default 'inline' */ mode?: import('./date-range-picker-provider.svelte').DateRangePickerMode; + /** + * Picker granularity / Chakra-style date kind. Drives the visible + * segments in BOTH endpoint inputs AND the calendar view in the + * popover. There's no use case for kind='year' on start + + * kind='month' on end — both endpoints share the same kind. + * + * - `'date'` (default): MM/DD/YYYY inputs + day calendar + * - `'month'`: MM/YYYY inputs + month grid + * - `'year'`: YYYY inputs + year grid (decade) + * + * @default 'date' + */ + kind?: import('./date-range-picker-provider.svelte').DateRangePickerKind; // ── Validation ── /** Custom range validator. */ validate?: DateRangeValidator; diff --git a/web/routes/uix/components/date-range-picker/+page.svelte b/web/routes/uix/components/date-range-picker/+page.svelte index ace35b1cd..70876d3ea 100644 --- a/web/routes/uix/components/date-range-picker/+page.svelte +++ b/web/routes/uix/components/date-range-picker/+page.svelte @@ -60,6 +60,7 @@ let allowSingleDay = $state(false); let closeOnRangeSelect = $state(false); let mode = $state<'inline' | 'modal'>('inline'); + let kind = $state<'date' | 'month' | 'year'>('date'); let disableDaysOutsideMonth = $state(true); let fixedWeeks = $state(true); let pagedNavigation = $state(false); @@ -483,6 +484,7 @@ {allowSingleDay} {closeOnRangeSelect} {mode} + {kind} {disableDaysOutsideMonth} {fixedWeeks} {pagedNavigation} @@ -696,6 +698,22 @@ modal +