You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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.
Self-review vs Build Protocol — Quick Self-Review checklist
Icons from registry — n/a; no new icons. Existing Icon usage untouched.
themeProps present — n/a; no new interactive elements. themeProps('date-input', …) unchanged.
No wrapper divs — no new DOM. format only changes the computed display string passed to the existing <input value>.
No CSS shorthands — no style changes in this PR.
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.
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.
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.
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.
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 truth — date, 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
CLA SignedThis label is managed by the Meta Open Source bot.needs:design-reviewAffects visuals — Design should review
1 participant
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a
formatprop to DateInput that controls how the committed date value is displayed, reusing Timestamp'sformatvalue vocabulary verbatim so the same string literal renders the same date shape in both components. Also extends Timestamp with two new shared members,date_longanddate_weekday, giving the two components full value parity on the date-only formats.Selectable named values on DateInput:
date_long(default)March 21, 2026<Timestamp format="date_long" />(new)dateMar 21, 2026<Timestamp format="date" />date_weekdayWed, Mar 21, 2026<Timestamp format="date_weekday" />(new)system_date2026-03-21<Timestamp format="system_date" />(value) => stringWhy
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 fromTimestampFormatbreaksDateInputFormatat build time. snake_case is kept because value reuse is the point.Full parity via a named
date_longdefault (non-breaking)DateInput's historical default is long-month (
March 21, 2026). Rather than leave "long" as an unnamed default, this PR adds a realdate_longmember 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 hardcodedplainDateFormat(…, 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'sformatTimestampswitch and DateInput both consume it for the members they share (date,date_long,date_weekday;system_dateis a fixed ISO string), so the two components can never diverge.date_longanddate_weekdayare added toTimestampFormatand 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 thatparseDateInputcannot read back can't be re-committed after an inline edit; externalvaluechanges 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 toparseDateInput,value(stillISODateString),onChange, or ARIA.Testing
date_longlong-month; explicitformat="date_long"identical to the default;format="date"short shape identical to Timestamp'sdate;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.date_long(full month) anddate_weekday(weekday prefix) members render correctly; existing format tests unchanged.formatSharedDateacross all four shared members.@astryxdesign/corebuild,check:sync,sync:exports --check,check:changesets.Closes #4160