Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
ae29720
fix(DateInput, DateTimeInput): under a finger the month/year wheels m…
vjeux Nov 5, 2026
202173c
test(DateInput): the month/year wheels' proof waits on the sheet's ow…
vjeux Nov 5, 2026
a98a089
refactor(DateInput, DateTimeInput): the wheels' comments keep the reason
vjeux Nov 5, 2026
6c0e438
fix(DateInput, DateTimeInput): the sheet's calendar pages like UIDate…
vjeux Nov 5, 2026
02efa8b
refactor(DateInput): the month pager's sheet split is a mode of useOw…
vjeux Nov 5, 2026
7b9acb3
refactor(DateInput): MonthPager's Effects name what they sync, and it…
vjeux Nov 5, 2026
d1767b9
fix(DateInput): the month pager follows a trackpad through the shared…
vjeux Nov 5, 2026
fbeb9fc
refactor(DateInput): the scroller and the pager share one month pane
vjeux Nov 5, 2026
72c72fd
refactor(DateInput): the scroller and the pager share one keyboard-fo…
vjeux Nov 5, 2026
7eb81b0
test(DateInput): the sheet split's proof samples in the page
vjeux Nov 5, 2026
58d5558
fix(DateInput): the arrows' step names its FR45 gap, and the title re…
vjeux Nov 5, 2026
7a027c2
test(DateInput): four MonthPager proofs no longer hang on a loaded ru…
vjeux Nov 5, 2026
00d9d68
test(DateInput): month paging follows the active calendar's months
vjeux Nov 5, 2026
3704441
test(DateInput): the trackpad paging cases run on fake timers
vjeux Nov 5, 2026
9797905
test(DateInput): the toggle's re-render proof spies on the pane's hook
vjeux Nov 5, 2026
0667de5
fix(DateInput, DateTimeInput): the calendar's toggle and selection fo…
vjeux Nov 5, 2026
50ede6c
fix(DateInput, DateTimeInput): the toggle's handler decides the focus…
vjeux Nov 5, 2026
b2fd7b2
fix(DateInput, DateTimeInput): Done hands focus to the shown month, a…
vjeux Nov 5, 2026
3450ca4
spec(DateInput, DateTimeInput): the title chevron and Done's focus ha…
vjeux Nov 5, 2026
2ba4218
test(DateInput): the toggle's storyboard holds the fade as it starts
vjeux Nov 5, 2026
a702756
fix(DateTimeInput): with a mouse, Done hands focus to the month in view
vjeux Nov 5, 2026
aa063df
fix(DateInput, DateTimeInput): the grid's tab stop records no focus, …
vjeux Nov 5, 2026
cbdc64d
fix(DateInput, DateTimeInput): the month/year toggle plays the theme'…
vjeux Nov 5, 2026
55aacf2
fix(DateInput, DateTimeInput): only a transition the toggle starts en…
vjeux Nov 5, 2026
7027623
test(DateInput): the toggle spec waits for the sheet and the wheels' …
vjeux Nov 5, 2026
e784961
fix(DateInput): a finger's tapped day selects at once; a mouse or pen…
vjeux Nov 5, 2026
664ed1a
docs(DateInput, DateTimeInput): state the toggle's shield and focus
vjeux Nov 5, 2026
cb69fb2
Merge main into the month pager
vjeux Nov 6, 2026
6c7cecb
Merge main into the month/year wheels
vjeux Nov 6, 2026
b1fb87b
Merge main into the calendar's toggle and day tap
vjeux Nov 6, 2026
a712d60
Merge the month pager's main merge into the calendar's toggle
vjeux Nov 6, 2026
c6b74ea
Merge the wheels' main merge into the month pager
vjeux Nov 6, 2026
d84ec6e
docs(changeset): the toggle's note says what the wheels' warm-up does
vjeux Nov 6, 2026
5911468
Merge main into the month/year wheels
vjeux Nov 7, 2026
1baf567
Merge the wheels' main merge into the month pager
vjeux Nov 7, 2026
feca481
test(DateInput): the month pager's tests follow main
vjeux Nov 7, 2026
74c78b7
Merge the month pager's main merge into the calendar's toggle
vjeux Nov 7, 2026
4886ae5
test(DateInput): the toggle's tests follow main
vjeux Nov 7, 2026
e4c6e2b
docs(changeset): the toggle's note says what the warm-up spares it
vjeux Nov 7, 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
16 changes: 16 additions & 0 deletions .changeset/calendar-pages-like-uidatepicker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
'@astryxdesign/core': patch
---

[fix] Under a finger, the calendar in the DateInput and DateTimeInput bottom sheets now pages its months like the iOS date picker:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Miss: this tells consumers the touch calendar pages like the iOS date picker (one month per swipe, the 10 px sheet split, the trackpad pan), but the description shows no real-device iOS Safari evidence and no Android emulator run at this head. spec:AST-094 Platform support and DEC-27 ask for real-device evidence for every iOS Safari claim and, for Android Chrome, Chrome with Gboard in the Android emulator at the exact head, with animations at normal scale and removed, plus a slow-device profile.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Route: real-device iOS for this paging is card 35, carried (the specs comment on #8200), and Android is Code Quality at the final head. This changeset is #8200's: #8209 and #8200 merge first, then this pull request redeclares the three-PR stack, so until then its compare carries their files.


- A swipe moves at most one month, chosen at the lift by iOS's paging rule: a quick flick, or a drag past half a month. It settles on iOS's paging curve.
- The month title follows the drag and the settle. It changes when the next month is more than half in view.
- An arrow scrolls one month. A second tap mid-scroll goes on to the month after.
- In the sheet, a drag is decided once it has moved 10 px either way. It pages the calendar if it has moved 10 px sideways by then, and moves the sheet otherwise.
- A horizontal trackpad swipe moves the calendar with it and pages at the release, like a drag.
- With Reduce Motion on, the arrows' step and a swipe's settle cross-fade in 200 ms instead of travelling.

With a mouse, the calendar still scrolls natively. With any pointer, PageUp and PageDown now move the keyboard focus by a month, and by a year with Shift.

@vjeux
18 changes: 18 additions & 0 deletions .changeset/calendar-toggle-uidatepicker-timing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
'@astryxdesign/core': patch
---

[feat] Under a finger, the calendar in the DateInput and DateTimeInput bottom sheets now toggles to its month and year wheels like the iOS date picker:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Miss: these touch claims still have no Android emulator run at this head, and the description's iOS table (9–23 % of the fade lost before its first frame) contradicts the 54–82 % the newest commit measures here, with card 36's real-device result unstated. spec:AST-094 Platform support and DEC-27 ask for real-device iOS evidence and, for Android Chrome, Chrome with Gboard in the emulator at the exact head, with animations at normal scale and removed, plus a slow-device profile.


- The month/year toggle cross-fades over `--duration-medium-min` (225 ms in the neutral theme, the token nearest iOS's 0.25 s there), where DateInput used a quick linear fade and DateTimeInput an instant swap. The wheels arrive on the theme's entrance curve and leave on its exit curve. As in UIKit, the header's month arrows and Reset and the footer's buttons take no tap during the fade, and a second tap on the title turns it around.
- The hidden month/year wheels are laid out once the sheet settles, so a toggle after that does not lay them out.
- The title's chevron points along the reading direction and turns a quarter turn to point down while the wheels show, on the theme's move curve. In DateInput it was a down chevron that flipped up; in DateTimeInput it was a down chevron that stayed still.
- A finger's tapped day takes its selection at once, with no colour transition. A mouse or pen pick keeps the fade, in the touch sheet too.

With a mouse or a pen, the toggle stays as it is.

When the title or Done toggles with keyboard focus in the outgoing panel, focus moves into the incoming one, with any pointer. In DateTimeInput, Done lands on the shown month's day.

With Reduce Motion on, nothing travels: under a finger the toggle cross-fades in 200 ms, and the chevron dips instead of turning, its turn landing mid-dip. With a mouse it changes at once.

@vjeux
12 changes: 12 additions & 0 deletions .changeset/month-year-wheels-move-the-date.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@astryxdesign/core': patch
---

[fix] Under a finger, the month and year wheels behind the calendar title in the DateInput and DateTimeInput bottom sheets now behave like the iOS date picker's:

- Turning a wheel moves a chosen date to the new month and keeps its day, so October 15 becomes May 15. A day the month lacks becomes its last day, the result stays within `min` and `max`, and DateTimeInput keeps the time. A date that `dateConstraints` refuses is not chosen; the calendar still moves.
- With no date chosen, the wheels only move the calendar.

With a mouse, the wheels still move only the calendar.

@vjeux
19 changes: 19 additions & 0 deletions apps/storybook/stories/DateInput.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,25 @@ export const WithMinMax: Story = {
},
};

/**
* The bottom-sheet calendar pages its months like the iOS date picker. One
* month moves per swipe, and the title follows the drag.
*/
export const BottomSheetCalendar: Story = {
render: args => {
const [value, setValue] = useState<ISODateString | undefined>(
'2026-10-15' as ISODateString,
);
return <DateInput {...args} value={value} onChange={setValue} />;
},
args: {
label: 'Date',
presentation: 'bottom-sheet',
min: '2026-01-01' as ISODateString,
max: '2026-12-31' as ISODateString,
},
};

export const WithMaxDateInLayout: Story = {
render: args => {
const [value, setValue] = useState<ISODateString | undefined>(undefined);
Expand Down
17 changes: 17 additions & 0 deletions apps/storybook/stories/DateTimeInput.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,23 @@ export const WithTimeIncrement: Story = {
},
};

/**
* The bottom sheet: the calendar with its month/year toggle, and the time
* wheels.
*/
export const BottomSheetCalendar: Story = {
render: args => {
const [value, setValue] = useState<ISODateTimeString | undefined>(
'2026-03-15T09:00' as ISODateTimeString,
);
return <DateTimeInput {...args} value={value} onChange={setValue} />;
},
args: {
label: 'Time slot',
presentation: 'bottom-sheet',
},
};

/**
* `timeOptionInterval` turns the time field into a combobox over a list of
* preset times at that cadence. Click the time field or press Alt+ArrowDown to
Expand Down
72 changes: 72 additions & 0 deletions apps/storybook/stories/MonthPagerProofs.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.

/**
* @file MonthPagerProofs.stories.tsx
* @input Uses React and the internal MonthPager (packages/core/src/DateInput)
* @output The month pager alone, whatever the primary pointer: 2026 from
* March, its shown month in a read-only field
* @position A checked-in reproduction path for the trackpad proofs in
* packages/core/src/DateInput/__tests__/MonthPager.interaction.browser.spec.ts.
* WebKit's driver sends a trackpad's precise deltas only from a desktop
* context, where the sheets show the mouse calendar instead.
*/

import type {Meta, StoryObj} from '@storybook/react';
import {useState} from 'react';
import {MonthPager} from '../../../packages/core/src/DateInput/MonthPager';
import {
createMonthModel,
fromMonthIndex,
toMonthIndex,
} from '../../../packages/core/src/DateInput/monthGeometry';

const FIRST = toMonthIndex(2026, 1);
const LAST = toMonthIndex(2026, 12);
const MARCH = toMonthIndex(2026, 3);
const TODAY = {year: 2026, month: 3, day: 15};
const GREGORY = createMonthModel('gregory');

/**
* The readout takes the theme's text colour on the theme's surface, not the
* browser's white field.
*/
const READOUT_STYLE = {color: 'inherit', backgroundColor: 'transparent'};

const meta = {
title: 'Core/MonthPager',
tags: ['no-visual', '!autodocs'],
parameters: {layout: 'centered'},
} satisfies Meta;

export default meta;
type Story = StoryObj<typeof meta>;

/** The pager alone. Its field names the month it shows. */
export const Pager: Story = {
render: () => {
const [shown, setShown] = useState(MARCH);
const {year, month} = fromMonthIndex(shown);
return (
<div style={{display: 'grid', gap: 16, inlineSize: 360}}>
<input
aria-label="Shown month"
readOnly
style={READOUT_STYLE}
value={`${year}-${String(month).padStart(2, '0')}`}
/>
<MonthPager
monthModel={GREGORY}
minMonthIndex={FIRST}
maxMonthIndex={LAST}
initialMonthIndex={MARCH}
onVisibleMonthChange={setShown}
selectedDate={null}
today={TODAY}
isDateDisabled={() => false}
weekStartsOn={0}
onSelect={() => {}}
/>
</div>
);
},
};
Loading