Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
ccdac38
refactor(date-field): align date inputs with Field composition
IzumiSy Jul 16, 2026
b783da1
fix(date-field): restore semantic props and form validation
IzumiSy Jul 29, 2026
c1345e1
Merge branch 'main' into refactor/date-field-field-composition
IzumiSy Jul 29, 2026
d9bb279
Merge branch 'main' into refactor/date-field-field-composition
interacsean Jul 30, 2026
80dbc54
refactor(date-field): drop Base UI field integration
IzumiSy Jul 31, 2026
77e3677
refactor(date-field): drop proxy validity effects
IzumiSy Jul 31, 2026
c891a58
Merge branch 'main' into refactor/date-field-field-composition
IzumiSy Jul 31, 2026
eb230c6
refactor(date-field): narrow react imports
IzumiSy Jul 31, 2026
e53dcb5
fix(vite-app): tighten date picker demo typing
IzumiSy Jul 31, 2026
a2af364
test(date-field): restore standalone regression coverage
IzumiSy Jul 31, 2026
090599b
test(date-field): restore test comments
IzumiSy Jul 31, 2026
37e33bf
test: restore date-field tests from main baseline
IzumiSy Jul 31, 2026
0ad0835
feat: integrate date controls with Field wiring
IzumiSy Jul 31, 2026
bc8a659
docs(date-field): clarify proxy input bridge
IzumiSy Jul 31, 2026
7833711
Merge branch 'main' into refactor/date-field-field-composition
IzumiSy Jul 31, 2026
344f611
Fix DatePicker popup focus and Field label wiring
IzumiSy Jul 31, 2026
98fbc50
Refine date field docs and typing
IzumiSy Jul 31, 2026
86abe76
Merge branch 'main' into refactor/date-field-field-composition
IzumiSy Aug 3, 2026
62d7e75
Merge branch 'main' into refactor/date-field-field-composition
IzumiSy Aug 12, 2026
14e7a9c
fix(date-field): restore Form and Field wiring
IzumiSy Aug 13, 2026
10e93aa
refactor(date-field): split and document hook responsibilities
IzumiSy Aug 13, 2026
47bb969
docs(date-field): tighten hook comments
IzumiSy Aug 13, 2026
a8f25da
refactor(date-field): reduce ref churn in field bridge
IzumiSy Aug 13, 2026
a86f81a
Merge branch 'main' into refactor/date-field-field-composition
IzumiSy Aug 13, 2026
4eaeb10
docs(changeset): consolidate date-field notes
IzumiSy Aug 13, 2026
27ee89c
docs(changeset): clarify hideTimeZone removal
IzumiSy Aug 13, 2026
665b34c
Use the exact version of base-ui
IzumiSy Aug 13, 2026
bc235a3
Merge branch 'main' into refactor/date-field-field-composition
IzumiSy Aug 13, 2026
cd18196
fix(date-field): address review follow-ups
IzumiSy Aug 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .changeset/odd-maps-glow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
"@tailor-platform/app-shell": major
---

Refactor `DateField` / `DatePicker` to follow the same composition model as `Field`, `Select`, `Combobox`, and `Autocomplete`.

The date controls are now **control-first**: field chrome moved out of the control props and into `Field.Root` composition. The old `label`, `description`, `errorMessage`, `isInvalid`, `isRequired`, `isDisabled`, `isReadOnly`, `minValue`, `maxValue`, and `isDateUnavailable` props were removed or reshaped.

Before:

```tsx
<DatePicker
label="Delivery date"
description="When should we ship your order?"
minValue={today(getLocalTimeZone())}
errorMessage={error}
isInvalid={!!error}
/>
```

After:

```tsx
<Field.Root invalid={!!error}>
<Field.Label>Delivery date</Field.Label>
<DatePicker aria-label="Delivery date" constraints={{ min: today(getLocalTimeZone()) }} />
<Field.Description>When should we ship your order?</Field.Description>
<Field.Error match={!!error}>{error}</Field.Error>
</Field.Root>
```

Standalone usage still works with accessible naming:

```tsx
<DateField aria-label="Invoice date" />
```
180 changes: 81 additions & 99 deletions docs/components/date-picker.md

Large diffs are not rendered by default.

164 changes: 102 additions & 62 deletions examples/vite-app/src/pages/date-picker/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
DatePicker,
Calendar,
Form,
Field,
Button,
useTimeZone,
parseDate,
Expand All @@ -29,8 +30,8 @@ const DatePickerPage = () => {
const tomorrow = tz.today().add({ days: 1 });
const threeMonths = tz.today().add({ months: 3 });

// Validation runs on submit; the DatePicker surfaces the message through its
// own `errorMessage` / `isInvalid` props (it isn't a Base UI Field control).
// Validation runs on submit; the DatePicker composes with Field.Root for the
// label + error presentation, like the rest of the control set.
const handleDeliverySubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (!deliveryDate) {
Expand Down Expand Up @@ -72,19 +73,30 @@ const DatePickerPage = () => {
<h2 className="text-base font-semibold border-b pb-2">DateField</h2>

<div className="flex flex-wrap gap-6 items-start">
<DateField
label="Basic"
value={fieldValue}
onChange={(v) => setFieldValue(v as CalendarDate | null)}
/>
<DateField
label="With description"
description="Select a date within the next 3 months"
minValue={tomorrow}
maxValue={threeMonths}
/>
<DateField label="Disabled" isDisabled defaultValue={parseDate("2025-06-15")} />
<DateField label="Required" isRequired errorMessage="Date is required" />
<Field.Root>
<Field.Label>Basic</Field.Label>
<DateField
aria-label="Basic"
value={fieldValue}
onChange={(v) => setFieldValue(v as CalendarDate | null)}
/>
</Field.Root>
<Field.Root>
<Field.Label>With description</Field.Label>
<DateField
aria-label="With description"
constraints={{ min: tomorrow, max: threeMonths }}
/>
<Field.Description>Select a date within the next 3 months</Field.Description>
</Field.Root>
<Field.Root disabled>
<Field.Label>Disabled</Field.Label>
<DateField aria-label="Disabled" defaultValue={parseDate("2025-06-15")} />
</Field.Root>
<Field.Root>
<Field.Label>Required</Field.Label>
<DateField aria-label="Required" constraints={{ required: true }} />
</Field.Root>
</div>

{fieldValue && (
Expand All @@ -99,30 +111,40 @@ const DatePickerPage = () => {
<h2 className="text-base font-semibold border-b pb-2">DatePicker</h2>

<div className="flex flex-wrap gap-6 items-start">
<DatePicker
label="Basic"
value={pickerValue}
onChange={(v) => setPickerValue(v as CalendarDate | null)}
/>
<DatePicker
label="Future dates only"
description="Minimum: tomorrow"
minValue={tomorrow}
/>
<DatePicker
label="No weekends"
description="Weekday dates only"
isDateUnavailable={(d) => {
const day = d.toDate(tz.value).getDay();
return day === 0 || day === 6;
}}
/>
<DatePicker
label="With range"
minValue={tz.today()}
maxValue={threeMonths}
description={`Today → ${threeMonths.toString()}`}
/>
<Field.Root>
<Field.Label>Basic</Field.Label>
<DatePicker
aria-label="Basic"
value={pickerValue}
onChange={(v) => setPickerValue(v as CalendarDate | null)}
/>
</Field.Root>
<Field.Root>
<Field.Label>Future dates only</Field.Label>
<DatePicker aria-label="Future dates only" constraints={{ min: tomorrow }} />
<Field.Description>Minimum: tomorrow</Field.Description>
</Field.Root>
<Field.Root>
<Field.Label>No weekends</Field.Label>
<DatePicker
aria-label="No weekends"
constraints={{
unavailable: (d) => {
const day = d.toDate(tz.value).getDay();
return day === 0 || day === 6;
},
}}
/>
<Field.Description>Weekday dates only</Field.Description>
</Field.Root>
<Field.Root>
<Field.Label>With range</Field.Label>
<DatePicker
aria-label="With range"
constraints={{ min: tz.today(), max: threeMonths }}
/>
<Field.Description>{`Today → ${threeMonths.toString()}`}</Field.Description>
</Field.Root>
</div>

{pickerValue && (
Expand All @@ -137,28 +159,28 @@ const DatePickerPage = () => {
<h2 className="text-base font-semibold border-b pb-2">In a form (submit validation)</h2>
<p className="text-sm text-muted-foreground">
Standard <code className="bg-muted px-1 py-0.5 rounded">Form</code> +{" "}
<code className="bg-muted px-1 py-0.5 rounded">Button</code>. Submitting empty (or
with a past date) triggers validation — the error surfaces through the DatePicker's
own <code className="bg-muted px-1 py-0.5 rounded">errorMessage</code> /{" "}
<code className="bg-muted px-1 py-0.5 rounded">isInvalid</code> props, and clears as
soon as a valid date is picked.
<code className="bg-muted px-1 py-0.5 rounded">Field.Root</code>. Submitting empty (or
with a past date) marks the field invalid through the field shell, and the error
clears as soon as a valid date is picked.
</p>
<Form
onSubmit={handleDeliverySubmit}
className="flex flex-col items-start gap-4 max-w-sm"
>
<DatePicker
label="Delivery date"
description="When should we ship your order?"
isRequired
value={deliveryDate}
onChange={(v) => {
setDeliveryDate(v as CalendarDate | null);
if (v) setDeliveryError(undefined);
}}
errorMessage={deliveryError}
isInvalid={!!deliveryError}
/>
<Field.Root invalid={!!deliveryError}>
<Field.Label>Delivery date</Field.Label>
<DatePicker
aria-label="Delivery date"
constraints={{ required: true }}
value={deliveryDate}
onChange={(v) => {
setDeliveryDate(v as CalendarDate | null);
if (v) setDeliveryError(undefined);
}}
/>
<Field.Description>When should we ship your order?</Field.Description>
<Field.Error match={!!deliveryError}>{deliveryError}</Field.Error>
</Field.Root>
<Button type="submit">Schedule delivery</Button>
</Form>
{confirmedDate && (
Expand All @@ -177,9 +199,18 @@ const DatePickerPage = () => {
explicitly to force a specific start day regardless of locale.
</p>
<div className="flex flex-wrap gap-6 items-start">
<DatePicker label="Forced Sunday" firstDayOfWeek="sun" />
<DatePicker label="Forced Monday" firstDayOfWeek="mon" />
<DatePicker label="Locale default" />
<Field.Root>
<Field.Label>Forced Sunday</Field.Label>
<DatePicker aria-label="Forced Sunday" firstDayOfWeek="sun" />
</Field.Root>
<Field.Root>
<Field.Label>Forced Monday</Field.Label>
<DatePicker aria-label="Forced Monday" firstDayOfWeek="mon" />
</Field.Root>
<Field.Root>
<Field.Label>Locale default</Field.Label>
<DatePicker aria-label="Locale default" />
</Field.Root>
</div>
</section>

Expand All @@ -189,9 +220,18 @@ const DatePickerPage = () => {
Locale (segment order + names)
</h2>
<div className="flex flex-wrap gap-6 items-start">
<DatePicker label="en-US (MM/DD/YYYY)" locale="en-US" />
<DatePicker label="en-GB (DD/MM/YYYY, Mon-first)" locale="en-GB" />
<DatePicker label="ja-JP (YYYY/MM/DD)" locale="ja-JP" />
<Field.Root>
<Field.Label>en-US (MM/DD/YYYY)</Field.Label>
<DatePicker aria-label="en-US (MM/DD/YYYY)" locale="en-US" />
</Field.Root>
<Field.Root>
<Field.Label>en-GB (DD/MM/YYYY, Mon-first)</Field.Label>
<DatePicker aria-label="en-GB (DD/MM/YYYY, Mon-first)" locale="en-GB" />
</Field.Root>
<Field.Root>
<Field.Label>ja-JP (YYYY/MM/DD)</Field.Label>
<DatePicker aria-label="ja-JP (YYYY/MM/DD)" locale="ja-JP" />
</Field.Root>
</div>
</section>

Expand Down
Loading
Loading