From 125dcf4271b9ff24ec4e3cc1e1ea5b6ac9cc4c10 Mon Sep 17 00:00:00 2001 From: interacsean Date: Mon, 5 Oct 2026 12:12:54 +1100 Subject: [PATCH 1/4] feat(date-field): add regional dateFormat and showWeekday (#1883) Add `dateFormat` ("numeric" | "regional") and `showWeekday` to DateField, DatePicker and DateRangePicker, plus an app-wide `dateFormat` default on AppShell. "regional" uses the locale's written form where it keeps the month numeric (ja/zh/ko) and stays numeric elsewhere; the default stays "numeric". The weekday is read-only and waits while a segment is mid-entry. Refs tailor-inc/platform-planning#1883 Co-Authored-By: Claude Opus 5.5 --- .changeset/regional-dates-show-weekday.md | 14 +++ docs-manifest.json | 24 ++-- docs-src/components/app-shell.docs.outline.md | 12 ++ .../components/date-picker.docs.outline.md | 64 ++++++---- docs/components/app-shell.md | 12 ++ docs/components/date-picker.md | 64 ++++++---- .../src/pages/showcase/date-picker/page.tsx | 30 +++++ .../core/src/components/appshell/appshell.tsx | 13 ++ .../components/date-field/date-field.test.tsx | 114 ++++++++++++++++++ .../src/components/date-field/date-field.tsx | 27 ++++- .../date-field/date-input-group.tsx | 27 ++++- .../date-field/date-range-input-group.tsx | 2 + .../date-field/date-range-picker.test.tsx | 24 ++++ .../date-field/date-range-picker.tsx | 19 ++- .../date-field/use-date-field-state.ts | 107 +++++++++++++--- .../core/src/contexts/appshell-context.tsx | 24 ++++ packages/core/src/index.ts | 1 + packages/core/tests/test-utils.tsx | 9 +- 18 files changed, 508 insertions(+), 79 deletions(-) create mode 100644 .changeset/regional-dates-show-weekday.md diff --git a/.changeset/regional-dates-show-weekday.md b/.changeset/regional-dates-show-weekday.md new file mode 100644 index 000000000..61f622dfc --- /dev/null +++ b/.changeset/regional-dates-show-weekday.md @@ -0,0 +1,14 @@ +--- +"@tailor-platform/app-shell": minor +--- + +Add `dateFormat` and `showWeekday` to `DateField`, `DatePicker`, and `DateRangePicker`, plus an app-wide `dateFormat` default on `AppShell`. + +- `dateFormat="regional"` lays the segments out in the locale's written business form wherever that form keeps the month numeric — `2025年12月19日` for ja / zh, `2025년 12월 19일` for ko. Locales whose written form spells the month as a word (en, de, fr, …) stay numeric, so a single app-wide setting is safe for multi-locale apps. The default remains `"numeric"`, so existing screens are unchanged. +- `showWeekday` appends the locale's short weekday (`2025年12月19日(金)`, `Fri, 12/19/2025`). It is read-only, derived from the entered date, and waits while a segment is mid-entry so typing `25` never flashes the 2nd's weekday. + +```tsx + + + +``` diff --git a/docs-manifest.json b/docs-manifest.json index 63477342d..61643b772 100644 --- a/docs-manifest.json +++ b/docs-manifest.json @@ -97,10 +97,10 @@ "claims": [], "symbols": ["AppShell", "AppShellProps"], "hashes": { - "typeSurface": "51313066e142afc2", - "outline": "740b41d779b579ea", + "typeSurface": "90728bd25426ec7f", + "outline": "6d3f27d6895dcbb8", "snapshot": null, - "outputMd": "c4a7bf5560678bdf", + "outputMd": "497b4430353b8087", "examples": null } }, @@ -467,10 +467,10 @@ "DateRangePickerProps" ], "hashes": { - "typeSurface": "6b07d4169fa991df", - "outline": "85b4b50ff4dbc265", + "typeSurface": "c8f6d3af0cd59502", + "outline": "5eb60f9470368234", "snapshot": null, - "outputMd": "33902be121ff69db", + "outputMd": "f025441ebab053bc", "examples": null } }, @@ -605,7 +605,7 @@ "hashes": { "typeSurface": "b1614970f950ceb6", "outline": "bda40376d6e3e008", - "snapshot": "d4fa33f2da19d5ee", + "snapshot": "b845d4c54453b0f0", "outputMd": "ec6a0ac098d1d87a", "examples": "934c52d4e8761404" } @@ -1013,7 +1013,7 @@ "hashes": { "typeSurface": "943616def28cafb0", "outline": "8f06df28129676c8", - "snapshot": "02dfd02003105939", + "snapshot": "6233cd9de511210f", "outputMd": "ceb4a05822538d20", "examples": "cff0508bb5520ae6" } @@ -1166,7 +1166,7 @@ "hashes": { "typeSurface": "429710973dfa912b", "outline": "721e8995f53f0e8f", - "snapshot": "f6fcbd73e127f2f4", + "snapshot": "0ceac9786fb4ce3c", "outputMd": "ae287e2bb5abc1c6", "examples": "1ce89cec30045466" } @@ -1183,7 +1183,7 @@ "hashes": { "typeSurface": "e760949efcbe4c38", "outline": "464d91acc48f7c9d", - "snapshot": "12c20fc6f44d1b4c", + "snapshot": "40d15c3f1003fdb0", "outputMd": "1f81ebd562c50371", "examples": "b0eebb8c5e2219dc" } @@ -1661,7 +1661,7 @@ "packages/core/skills/app-shell-patterns/references/components/activity-card.md": "4f386e49380fb0b0", "packages/core/skills/app-shell-patterns/references/components/ai-chat.md": "824d1aa36bb7f194", "packages/core/skills/app-shell-patterns/references/components/alert.md": "db2c4388ca783a75", - "packages/core/skills/app-shell-patterns/references/components/app-shell.md": "c4a7bf5560678bdf", + "packages/core/skills/app-shell-patterns/references/components/app-shell.md": "497b4430353b8087", "packages/core/skills/app-shell-patterns/references/components/appearance-switcher.md": "b55f1d538651d467", "packages/core/skills/app-shell-patterns/references/components/attachment.md": "35bebce540d6ac28", "packages/core/skills/app-shell-patterns/references/components/autocomplete.md": "bd6ae01bf39d1ad6", @@ -1674,7 +1674,7 @@ "packages/core/skills/app-shell-patterns/references/components/command-palette.md": "7e5bc675a593791a", "packages/core/skills/app-shell-patterns/references/components/csv-importer.md": "53d3c795ca21b9dc", "packages/core/skills/app-shell-patterns/references/components/data-table.md": "dd2da884d7550daf", - "packages/core/skills/app-shell-patterns/references/components/date-picker.md": "33902be121ff69db", + "packages/core/skills/app-shell-patterns/references/components/date-picker.md": "f025441ebab053bc", "packages/core/skills/app-shell-patterns/references/components/default-header.md": "375e55f4b12fa2be", "packages/core/skills/app-shell-patterns/references/components/default-sidebar.md": "3422388a0cc78571", "packages/core/skills/app-shell-patterns/references/components/description-card.md": "a347fd1f4ccd433e", diff --git a/docs-src/components/app-shell.docs.outline.md b/docs-src/components/app-shell.docs.outline.md index d6143bac9..bfc32ea2d 100644 --- a/docs-src/components/app-shell.docs.outline.md +++ b/docs-src/components/app-shell.docs.outline.md @@ -226,6 +226,18 @@ Supported locales: `en`, `ja` ``` +### dateFormat + +- **Type:** `"numeric" | "regional"` (optional) +- **Default:** `"numeric"` +- **Description:** Default segment layout for `DateField`, `DatePicker`, and `DateRangePicker`. `"numeric"` uses the locale's numeric form (`2025/12/19`). `"regional"` uses the locale's written business form where it keeps the month numeric (`2025年12月19日` for ja / zh, `2025년 12월 19일` for ko) and stays numeric for every other locale. Each component's own `dateFormat` prop overrides it. + +```tsx + + {/* ... */} + +``` + Access the configured timezone in components using [`useTimeZone`](../api/use-time-zone.md). ### errorBoundary diff --git a/docs-src/components/date-picker.docs.outline.md b/docs-src/components/date-picker.docs.outline.md index a1c219eda..e13eb595c 100644 --- a/docs-src/components/date-picker.docs.outline.md +++ b/docs-src/components/date-picker.docs.outline.md @@ -183,6 +183,26 @@ Locale and timezone come from AppShell automatically. Override per field with `l ``` +### Date format + +By default the segments follow the locale's **numeric** form (`2025/12/19` for ja-JP, `12/19/2025` for en-US). `dateFormat="regional"` switches to the locale's written business form wherever that form keeps the month numeric — `2025年12月19日` for ja / zh, `2025년 12월 19일` for ko. Locales whose written form spells the month as a word (en, de, fr, …) stay numeric, so one setting is safe for a multi-locale app. + +Set it once for the whole app on `AppShell`, or per field (the prop wins): + +```tsx + + + +``` + +### Weekday + +`showWeekday` adds the locale's short weekday where the locale places it — `2025年12月19日(金)` for ja-JP, `Fri, 12/19/2025` for en-US. It is read-only and derived from the entered date: it shows a placeholder until the date is complete, and while a segment is mid-entry (so typing `25` into the day never flashes the 2nd's weekday). + +```tsx + +``` + ## Keyboard - **Segments:** `↑`/`↓` increment/decrement, digits type-to-fill (auto-advance), `←`/`→` move between segments, `Backspace` clears, `/` commits the current segment and advances. @@ -193,26 +213,28 @@ Locale and timezone come from AppShell automatically. Override per field with `l ### DateFieldProps -| Prop | Type | Description | -| -------------------------------- | ------------------------------------------------------------- | ---------------------------------------------------------- | -| `value` / `defaultValue` | `DateValue \| null` | Controlled / uncontrolled value | -| `onChange` | `(v: DateValue \| null) => void` | Fires when the value changes | -| `onBlur` | `() => void` | Fires when focus leaves the whole segmented control | -| `minValue` / `maxValue` | `DateValue` | Inclusive date range bounds | -| `isDateUnavailable` | `(date: DateValue) => boolean` | Marks specific dates unavailable | -| `isDisabled` | `boolean` | Disables interaction and form submission | -| `isReadOnly` | `boolean` | Allows focus/navigation without editing | -| `isRequired` | `boolean` | Marks the control required | -| `isInvalid` | `boolean` | Adds invalid styling / `aria-invalid` to the segmented UI | -| `placeholderValue` | `DateValue` | Seeds unset segments | -| `autoFocus` | `boolean` | Focus the first segment on mount | -| `locale` | `string` | BCP-47 locale override | -| `name` | `string` | Emits a form value through the proxy input | -| `id` | `string` | Proxy input id (use with external ` ``` +### dateFormat + +- **Type:** `"numeric" | "regional"` (optional) +- **Default:** `"numeric"` +- **Description:** Default segment layout for `DateField`, `DatePicker`, and `DateRangePicker`. `"numeric"` uses the locale's numeric form (`2025/12/19`). `"regional"` uses the locale's written business form where it keeps the month numeric (`2025年12月19日` for ja / zh, `2025년 12월 19일` for ko) and stays numeric for every other locale. Each component's own `dateFormat` prop overrides it. + +```tsx + + {/* ... */} + +``` + Access the configured timezone in components using [`useTimeZone`](../api/use-time-zone.md). ### errorBoundary diff --git a/docs/components/date-picker.md b/docs/components/date-picker.md index d06b2d842..d1a7e76a9 100644 --- a/docs/components/date-picker.md +++ b/docs/components/date-picker.md @@ -181,6 +181,26 @@ Locale and timezone come from AppShell automatically. Override per field with `l ``` +### Date format + +By default the segments follow the locale's **numeric** form (`2025/12/19` for ja-JP, `12/19/2025` for en-US). `dateFormat="regional"` switches to the locale's written business form wherever that form keeps the month numeric — `2025年12月19日` for ja / zh, `2025년 12월 19일` for ko. Locales whose written form spells the month as a word (en, de, fr, …) stay numeric, so one setting is safe for a multi-locale app. + +Set it once for the whole app on `AppShell`, or per field (the prop wins): + +```tsx + + + +``` + +### Weekday + +`showWeekday` adds the locale's short weekday where the locale places it — `2025年12月19日(金)` for ja-JP, `Fri, 12/19/2025` for en-US. It is read-only and derived from the entered date: it shows a placeholder until the date is complete, and while a segment is mid-entry (so typing `25` into the day never flashes the 2nd's weekday). + +```tsx + +``` + ## Keyboard - **Segments:** `↑`/`↓` increment/decrement, digits type-to-fill (auto-advance), `←`/`→` move between segments, `Backspace` clears, `/` commits the current segment and advances. @@ -191,26 +211,28 @@ Locale and timezone come from AppShell automatically. Override per field with `l ### DateFieldProps -| Prop | Type | Description | -| -------------------------------- | ------------------------------------------------------------- | ---------------------------------------------------------- | -| `value` / `defaultValue` | `DateValue \| null` | Controlled / uncontrolled value | -| `onChange` | `(v: DateValue \| null) => void` | Fires when the value changes | -| `onBlur` | `() => void` | Fires when focus leaves the whole segmented control | -| `minValue` / `maxValue` | `DateValue` | Inclusive date range bounds | -| `isDateUnavailable` | `(date: DateValue) => boolean` | Marks specific dates unavailable | -| `isDisabled` | `boolean` | Disables interaction and form submission | -| `isReadOnly` | `boolean` | Allows focus/navigation without editing | -| `isRequired` | `boolean` | Marks the control required | -| `isInvalid` | `boolean` | Adds invalid styling / `aria-invalid` to the segmented UI | -| `placeholderValue` | `DateValue` | Seeds unset segments | -| `autoFocus` | `boolean` | Focus the first segment on mount | -| `locale` | `string` | BCP-47 locale override | -| `name` | `string` | Emits a form value through the proxy input | -| `id` | `string` | Proxy input id (use with external `