Skip to content

feat(DateInput): configurable format reusing Timestamp's format vocabulary (#4160)#4199

Draft
cixzhang wants to merge 2 commits into
mainfrom
feat/dateinput-format
Draft

feat(DateInput): configurable format reusing Timestamp's format vocabulary (#4160)#4199
cixzhang wants to merge 2 commits into
mainfrom
feat/dateinput-format

Conversation

@cixzhang

@cixzhang cixzhang commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

What

Adds a format prop to DateInput that controls how the committed date value is displayed, reusing Timestamp's format value vocabulary verbatim so the same string literal renders the same date shape in both components. Also extends Timestamp with two new shared members, date_long and date_weekday, giving the two components full value parity on the date-only formats.

export type DateInputFormat = Extract<
  TimestampFormat,
  'date' | 'date_long' | 'date_weekday' | 'system_date'
>;

format?: DateInputFormat | ((value: ISODateString) => string); // default 'date_long'

Selectable named values on DateInput:

value rendered (en-US, 2026-03-21) shared with
date_long (default) March 21, 2026 <Timestamp format="date_long" /> (new)
date Mar 21, 2026 <Timestamp format="date" />
date_weekday Wed, Mar 21, 2026 <Timestamp format="date_weekday" /> (new)
system_date 2026-03-21 <Timestamp format="system_date" />
(value) => string custom

Why

DateInput previously hard-coded a single long-month display. The reviewer ruling is that API/value reuse across related components trumps style conventions, so instead of forking a parallel enum, DateInput narrows Timestamp's own vocabulary with Extract<TimestampFormat, …>. The two types stay in compile-time lockstep — renaming or removing one of these members from TimestampFormat breaks DateInputFormat at build time. snake_case is kept because value reuse is the point.

Full parity via a named date_long default (non-breaking)

DateInput's historical default is long-month (March 21, 2026). Rather than leave "long" as an unnamed default, this PR adds a real date_long member to Timestamp's vocabulary (mapping to {year:'numeric', month:'long', day:'numeric'} = DATE_FORMAT_LONG) and makes it DateInput's default (format = 'date_long'). So DateInput's default is now a real named value that matches a real Timestamp value — complete long-format parity — and its rendered output is byte-identical to the old hardcoded plainDateFormat(…, DATE_FORMAT_LONG) (verified). Existing usage is unchanged (zero codemod).

Shared mapping (no drift)

The date-only members map through a single source of truth in utils/plainDate.ts (SHARED_DATE_FORMAT_OPTIONS + formatSharedDate). Timestamp's formatTimestamp switch and DateInput both consume it for the members they share (date, date_long, date_weekday; system_date is a fixed ISO string), so the two components can never diverge. date_long and date_weekday are added to TimestampFormat and its switch, so Timestamp consumers gain them too.

Function escape hatch + caveat

typeof format === 'function' lets a power user return any string. Caveat: a custom string that parseDateInput cannot read back can't be re-committed after an inline edit; external value changes always recompute the display from the ISO value. Formatting applies only to the committed value — never to in-progress typed input, preserving today's typing UX. No change to parseDateInput, value (still ISODateString), onChange, or ARIA.

Testing

  • DateInput: default = date_long long-month; explicit format="date_long" identical to the default; format="date" short shape identical to Timestamp's date; format="system_date" ISO; format="date_weekday" weekday shape; function hatch; format does not apply to pending typed input; controlled value change recomputes. Existing tests remain green.
  • Timestamp: new date_long (full month) and date_weekday (weekday prefix) members render correctly; existing format tests unchanged.
  • plainDate: direct tests for formatSharedDate across all four shared members.
  • Green locally: the DateInput + Timestamp + plainDate test files (191 tests), typecheck, ESLint (changed files), @astryxdesign/core build, check:sync, sync:exports --check, check:changesets.

Closes #4160

@vercel

vercel Bot commented Jul 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
astryx Ready Ready Preview, Comment Jul 22, 2026 12:57pm

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Jul 22, 2026
@github-actions github-actions Bot added the needs:design-review Affects visuals — Design should review label Jul 22, 2026
@cixzhang

Copy link
Copy Markdown
Contributor Author

Self-review vs Build Protocol — Quick Self-Review checklist

  1. Icons from registry — n/a; no new icons. Existing Icon usage untouched.
  2. themeProps present — n/a; no new interactive elements. themeProps('date-input', …) unchanged.
  3. No wrapper divs — no new DOM. format only changes the computed display string passed to the existing <input value>.
  4. No CSS shorthands — no style changes in this PR.
  5. ARIA still correct — no ARIA/primitive changes. role="combobox", aria-*, and the invalid-input live region are untouched; formatting affects only the committed display string.
  6. Tests pass locally — DateInput + Timestamp + plainDate test files run green (188 tests), plus typecheck, ESLint (changed files), core build, check:sync, sync:exports --check, check:changesets.
  7. Sandbox/stories updated — added format argType + a Formats story to DateInput; added a date_weekday option/story and AllFormats row to Timestamp; refreshed the Timestamp CLI showcase block. No props removed, so no callsite cleanup needed.
  8. Popover background — n/a; no popover/layer changes.

Additional protocol notes:

  • Spec compliance / surface area — built to the reviewer-refined contract: prop name format; exactly one new Timestamp member (date_weekday); unset default = long-month (non-breaking, no date_long member); DateInputFormat = Extract<TimestampFormat, …> keeps the two in compile-time lockstep. Exports: DateInputFormat from DateInput/index.ts; shared formatSharedDate/SHARED_DATE_FORMAT_OPTIONS from utils/plainDate.
  • Share implementation — the shared date members map through one source of truth in plainDate.ts, consumed by both Timestamp's switch and DateInput, so the components can't drift.
  • Docs — DateInput .doc.mjs (props / zh / dense) and Timestamp .doc.mjs updated; SYNC headers already list the touched files and check:sync is green.

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

🧪 Sandbox Preview

View Sandbox for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

Modified Components

DateInput · View in Storybook
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 510 -
Complexity N/A Very High (81) -
Timestamp · View in Storybook
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 319 -
Complexity N/A Very High (57) -

Bundle Size Summary

Package Size (ESM) Size (CJS) Gzipped
@astryxdesign/core N/A 4.7KB 0B

Accessibility Audit

Status: 1 accessibility violation(s) found — 1 serious.

Timestamp - 1 issue(s)
  • 🟠 serious: Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds
    • Rule: color-contrast · Affects 1/15 stories · Learn more
    • WCAG: 1.4.3 (Level AA)

Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

@cixzhang

Copy link
Copy Markdown
Contributor Author

Self-review update — date_long added and is now the default

Following the reviewer request, date_long is added to both components for full long-format parity, and it is now DateInput's default. Updated status vs the Build Protocol Quick Self-Review:

  • Timestamp gains date_long AND date_weekday — both added to TimestampFormat and the formatTimestamp switch; date_long maps to {year:'numeric', month:'long', day:'numeric'} (= DATE_FORMAT_LONG).
  • DateInputFormat = Extract<TimestampFormat, 'date' | 'date_long' | 'date_weekday' | 'system_date'> — still a strict Extract slice (compile-time lockstep).
  • Default is now format = 'date_long' — a real named value that matches a real Timestamp value, replacing the previous undefined → long special case. Verified byte-identical to the old hardcoded plainDateFormat(…, DATE_FORMAT_LONG) output (e.g. January 25, 2026), so still non-breaking / zero codemod.
  • Single source of truthdate, date_long, and date_weekday all map through SHARED_DATE_FORMAT_OPTIONS / formatSharedDate in utils/plainDate.ts, consumed by both Timestamp's switch and DateInput; system_date is the fixed ISO string in both. No drift possible.
  • Contract intact — prop name format; snake_case reused verbatim; function hatch + round-trip caveat unchanged; formatting only on the committed value (never pendingInput); no change to parseDateInput / value / onChange / ARIA.
  • Stories / docs / tests updated for date_long: Timestamp .doc.mjs + DateLongFormat story + AllFormats row + test; DateInput .doc.mjs (props/zh/dense) + Formats story default label + tests (default = date_long, explicit date_long); plainDate formatSharedDate test; Timestamp CLI showcase block; changeset text.

Gates (re-run, all green): DateInput + Timestamp + plainDate test files — 191 passed; typecheck 0; ESLint (changed files) 0; @astryxdesign/core build 0; check:sync ✓; sync:exports --check ✓; check:changesets ✓.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot. needs:design-review Affects visuals — Design should review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(DateInput): configurable display format for the selected date value

1 participant