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
{{ message }}
Repository navigation
feat(apollo-wind): drilldown calendar, independent range months, DateTimePicker timezone - #1245
Replaces #1215, which was opened from a fork so the required checks couldn't run.
Brings the date picker work from the Flow prototype into apollo-wind. Calendar gets a drill-down caption for jumping to a month or year, fixed day states in the Future themes, and a week start taken from the locale. DatePicker and DateRangePicker get popover control, a display format, and a range picker whose two months page independently. DateTimePicker swaps the native time input for hour and minute selects and gains an optional timezone select.
What changed
Calendar
Drill-down caption: new captionLayout="drilldown". Clicking the month shows a 3×4 grid of months, and clicking the year shows 12 years per page. Picking a year goes to its months, and picking a month goes to its days. The popover keeps its width in every view.
Focus moves into each new grid, and arrow keys move between cells.
Months and years outside startMonth / endMonth are disabled.
Week start: taken from the browser locale through Intl.Locale#getWeekInfo. That's Monday for en-GB and Sunday for en-US. An explicit weekStartsOn or a date-fns locale overrides it. Where the API is missing (Firefox), it stays on Sunday.
Compact size:size="sm" gives 36px day cells instead of 44px. It's opt-in.
Day states:
Today is an outline instead of a fill, matching the MUI picker. It no longer looks like hover or the middle of a range.
Days from neighbouring months are dimmer.
In the Future themes, day numbers used the muted text colour, and the selected day's text could lose to it. The day button now chooses its variant from its state.
The focus ring only shows for keyboard focus. It used to appear on every clicked day.
Range styling:
The band runs unbroken from the centre of the start day to the centre of the end day, and the ends are circles like a single selection.
A range with only a start date looks like a single selection.
Half-bands that would lead off a row edge or into a hidden day are dropped.
A divider separates side-by-side months.
DatePicker
Uses the drill-down caption and opens on the selected month. calendarProps={{ captionLayout: 'label' }} restores the old caption.
Closes when a date is picked.
Opening moves focus to the selected day, or to today. Before, focus landed on the previous-month chevron.
New popoverProps (align, side, offsets, className, container), aligned to the trigger's start by default. It used to be centred.
New displayFormat (date-fns), with PPP as the default.
DateRangePicker
Two calendars, each with its own chevrons and drill-down caption. They share one selection, and the right month always stays after the left.
Opens with the start month on the left and the end month on the right.
Days from neighbouring months are hidden, because they repeated the other month's days and range band.
New popoverProps and displayFormat (default LLL dd, y).
DateTimePicker
Time selects: hour and minute Selects replace the native time input, whose popup can't be styled. AM/PM appears with use12Hour. New minuteStep (default 5), and a value that isn't on the step stays selectable.
Commit as you go: a day keeps the chosen time, and a time chosen first applies once a day is picked. The popover stays open after a day click. It also uses the drill-down caption and focuses the selected day on open.
Timezone:showTimeZone adds a searchable select of every IANA zone the runtime knows, grouped by region with the viewer's zone first. The date and time are read in that zone and committed as that instant, so 14:00 in Europe/Bucharest is 11:00Z in summer. Changing the zone keeps the time on screen and moves the instant.
The trigger names the offset (for example GMT+3) whenever a zone is given through showTimeZone, timeZone or defaultTimeZone.
New timeZone, defaultTimeZone, onTimeZoneChange, calendarProps, popoverProps and displayFormat.
Adds the @date-fns/tz dependency.
Behaviour and API changes for consumers
DateRangePicker's calendarProps.month, defaultMonth and onMonthChange are deprecated but still work. They now apply to the left (start) month, with the right month kept after it, and onMonthChange only reports changes to the left month.
DatePicker now closes on select and aligns its popover to the start instead of the centre.
Calendars start the week on the locale's first day, so non-US users see Monday first unless weekStartsOn is passed.
DateTimePicker no longer uses a native <input type="time">, and it stays open after a day is picked.
A custom components.Root or components.Footer keeps the earlier behaviour, so it stays inside DayPicker's context: DatePicker and DateTimePicker fall back to the label caption, and DateRangePicker falls back to the single two-month DayPicker (one set of chevrons, months paging together, month/onMonthChange meaning the first month). The independent range months and the drill-down need a picker without those two overrides. On a Calendar given captionLayout="drilldown" explicitly, those two components render outside DayPicker's context, because the month and year views have no DayPicker around them. The drill-down draws its own caption and arrows, so the caption and navigation components (Nav, PreviousMonthButton, NextMonthButton, MonthCaption, CaptionLabel, the dropdowns) don't apply there. Chevron and the nav, button_previous and button_next classNames and styles still style its arrows. Other component overrides are unaffected. DateTimePicker's new calendarProps leaves out timeZone; the picker's own timeZone sets the zone.
Screenshots
Before
After
DatePicker, Future Light
DatePicker, Future Dark
Range
Range, start picked only
Drill-down: months
Drill-down: years
Compact (size="sm")
Checks
tsc --noEmit: clean.
biome lint and biome format: no new warnings in the changed files.
vitest run (apollo-wind): all calendar and picker tests pass, including new ones for the drill-down views, week start, focus on open, independent range months, popoverProps, the DateTimePicker time selects and timezone conversion (including daylight saving changes and years 0 to 99). form-plugins.test.tsx fails locally on Node 25, whose built-in localStorage hides jsdom's. It fails the same way on main and is unrelated.
Storybook checks in Future Light and Future Dark: keyboard focus, drill-down navigation, and range edge cases.
Date Picker: Bounded, CustomFormat, CompactInPanel.
Date Time Picker: MinuteStep, WithTimeZone, Compact.
Dependency security fixes
Most of the dependency security batch this PR carried has since landed on main in 3d64da6 ("chore(workspace): resolve pnpm audit advisories"). That covers next 16.3.8, sharp 0.35.5, hono 4.13.9, postcss-selector-parser >=7.1.6, the seroval 1.6.7, proxy-addr 2.0.8, source-map-js 1.2.2, @modelcontextprotocol/sdk 1.31.0 and handlebars 4.7.10 refreshes, their quarantine exemptions, and the braces and katex auditConfig.ignoreGhsas entries. After rebasing onto it, this PR keeps main's versions and notes and only adds one change:
@hono/node-server is pinned to the vetted 2.0.10. The ^2.0.10 override had drifted to 2.0.11, a perf/test-only release that was never vetted.
The earlier katex ^0.18.2 bump in apollo-react and its katex@<0.18.2 override are dropped. Main chose to stay on katex 0.16 and ignore the low-severity GHSA-238p-pmpm-9mq7, because 0.18 renames KaTeX's CSS classes. katex 0.18.7 and commander@15 are therefore no longer installed. pnpm audit and pnpm audit --prod pass, reporting only the two ignored advisories. Each entry's vetting notes are in pnpm-workspace.yaml.
Per-package coverage and bundle size on this PR. New-line coverage = of the source lines this PR adds or changes, the % hit by tests.
Package
Coverage
New-line coverage
Packed (gzip)
Unpacked
vs main
@uipath/apollo-core
75.0%
—
42.02 MB
50.17 MB
±0
@uipath/apollo-react
51.7%
—
8.01 MB
31.60 MB
+489 B
@uipath/apollo-ui-icons
—
—
2.86 MB
6.97 MB
±0
@uipath/apollo-vertex
85.0%
—
335.7 KB
1.88 MB
±0
@uipath/apollo-wind
80.0%
97.4% (517/531)
818.2 KB
4.71 MB
+30.7 KB
@uipath/ap-chat
85.8%
—
40.37 MB
45.58 MB
±0
"Coverage" is each package's own coverage.include scope (e.g. apollo-core instruments only scripts/). "Packed"/"Unpacked" come from npm pack --dry-run and only cover built packages — "—" means not measured this run (package not affected / not built). "vs main" is the packed (gzipped) delta against the last successful main build (the package-sizes artifact from the Release workflow); "—" there means no main baseline was available this run. The baseline is main's latest build, not this PR's exact merge-base, so it includes any drift since the branch diverged. Packages with no vitest config are omitted.
Not reviewed:packages/apollo-wind/package.json, packages/apollo-wind/src/components/ui/calendar.stories.tsx, packages/apollo-wind/src/components/ui/calendar.test.tsx +18 more
Not read: 1 generated or lockfile change.
What this PR does
The pull request author describes the intended change as follows: feat(apollo-wind): drilldown calendar, independent range months, DateTimePicker timezone
…TimePicker timezone
Calendar
- Add captionLayout="drilldown": the month and year zoom the grid out
to a 3x4 month grid or a paged 12-year grid, bounded by startMonth and
endMonth, with arrow-key navigation and focus following the view.
- Default the week start to the browser locale's first day.
- Add size="sm" for 36px day cells.
- Mark today with an outline instead of a fill, dim neighbouring-month
days, and pick the day button variant from its state so future-theme
text colours no longer compete with the selected fill.
- Show the day focus ring for keyboard focus only.
- Run the range band unbroken from the start day's centre to the end
day's, and render a one-day range as a single selection.
- Divide multiple months with a rule.
- Drop a range end's half-band after a week number cell.
- Mirror the range-end half-bands, the drilldown chrome and its arrow
keys in right-to-left calendars, and keep the hidden DayPicker
caption hidden when classNames.month_caption is customised.
- Open the drilldown inside startMonth/endMonth when defaultMonth
falls outside them, and move it back inside when the bounds change.
- Name the drilldown controls through `labels`: DayPicker's
labelPrevious, labelNext, labelMonthDropdown and labelYearDropdown,
plus new labelPreviousYears, labelNextYears, labelMonthsGrid and
labelYearsGrid. English defaults are unchanged.
- Keep `footer` (and a custom components.Footer), the root attributes
(id, title, lang, role, aria-label, aria-labelledby) and the grid
width (week number column included) in the month and year views.
- Honour hideNavigation in the drilldown caption, and mark the current
month or year cell with aria-current, taking DayPicker's today prop.
- Apply the style prop to the drilldown root in every view, keep the
year page in step with a controlled month, and name month cells with
the locale's month-and-year format. Root entries of classNames and
styles, and a custom components.Root, also go on the drilldown root,
with classNames.root overriding the built-in root classes.
- Name the drilldown caption and cells through formatMonthDropdown and
formatYearDropdown, and keep the grid arrow keys from scrolling the
page at its edges.
- Keep years 0 to 99 as given when building local and UTC dates, in the
drilldown, the date-time picker and the timezone conversions.
- Make disableNavigation turn off the drilldown arrows and the month
and year selectors, and apply styles.footer to the drilldown footer.
- Report drilldown arrow navigation through onPrevClick and onNextClick,
and draw the arrows with a custom components.Chevron.
- Write drilldown years in the numbering system given by numerals.
- Style the drilldown arrows with the nav, button_previous and
button_next classNames and styles, and keep data-slot="calendar" on
the persistent root only.
- Read and build drilldown dates in DayPicker's timeZone when one is
given, so it opens on and marks the zone's month. Document that a
custom components.Root or Footer renders outside DayPicker's context
in the drilldown and the range pair.
DatePicker
- Use the drilldown caption, open on the selected month, and close on
select. Focus the selected day (or today) on open.
- Add popoverProps (aligned to start by default) and displayFormat.
DateRangePicker
- Render two independently navigable months that share the selection,
keeping the right month after the left. Open on the start and end
months, and hide neighbouring-month days.
- Add popoverProps and displayFormat.
- Deprecate calendarProps.month and onMonthChange: month now controls
the left (start) month, with the right month kept after it, and
onMonthChange reports left-month changes only. Deprecate
calendarProps.defaultMonth too: it opens the left month (uncontrolled),
with a controlled month taking precedence.
- Render root-level calendarProps (footer, id, title, role, aria-label,
aria-labelledby, className, style, classNames.root, styles.root,
components.Root, dir, lang) once for the pair instead of on both
calendars, so the shared footer follows them too.
- Keep a controlled month inside startMonth/endMonth, leaving room for
the right month before endMonth.
- Refit the months, and switch between one calendar and two, when the
bounds change while open.
- Lay the pair out in calendarProps.dir, with a logical divider.
DateTimePicker time selects
- Replace the native time input, whose popup is the platform's own and
cannot be styled, with hour and minute Selects, plus AM/PM with
use12Hour. Add minuteStep (default 5, also used for a non-finite
step); a value off the step stays selectable. The selects are grouped under a Time legend.
- Commit as you go: a day keeps the chosen time, a time chosen first is
applied once a day is picked, and the popover stays open after a day
click. Clear and Done are compact.
- Use the drilldown caption, open on the selected month, and focus the
selected day on open. Follow value changes from the parent.
- Add popoverProps (aligned to start) and displayFormat.
- Name the date, date range and date-time popovers so the dialog has an
accessible name.
DateTimePicker timezone
- showTimeZone adds a searchable select of every IANA zone the runtime
knows, grouped by region with the viewer's zone first. Search matches
city, id (either spelling of renamed zones), region and offset.
- The date and time are read in the zone and committed as that instant,
so 14:00 in Europe/Bucharest is 11:00Z in summer; the trigger names
the offset. Changing the zone keeps the wall clock and moves the
instant. Conversions resolve times near a DST change.
- timeZone, defaultTimeZone and onTimeZoneChange let the parent track
the zone. A controlled timeZone without onTimeZoneChange locks the
timezone select.
- Name the offset whenever a zone is given, defaultTimeZone included.
- Leave timeZone out of calendarProps: the picker's own timeZone sets
the zone the calendar's date is read in.
- Read the viewer's zone as the client renders rather than seeding state
with it, so a server-rendered picker doesn't keep the server's zone.
- Read offsets for years before the common era through the formatted
era, so conversions stay correct there too.
- The timezone trigger keeps Radix's aria-haspopup="dialog", since the
popover is a dialog holding a search combobox and its list.
- The timezone list mounts its rows in batches as it scrolls, and
formats offsets only for mounted rows or an offset search.
- popoverProps.container also holds the time selects' menus and the
timezone list.
- Convert wall clocks with each zone's exact offset, seconds included
(Paris was GMT+0:09:21 in 1900), and round only the offset label.
- Document that a custom components.Root or Footer keeps the earlier
single two-month DayPicker in DateRangePicker.
Rebased onto main after 3d64da6 ("chore(workspace): resolve pnpm audit
advisories"), which landed the dependency security batch this commit used
to carry. Takes main's versions and vetting notes for it: next 16.3.8,
sharp 0.35.5, hono 4.13.9 (was 4.13.7 here), postcss-selector-parser
>=7.1.6, seroval 1.6.7, proxy-addr 2.0.8, source-map-js 1.2.2,
@modelcontextprotocol/sdk 1.31.0, handlebars 4.7.10, and the braces and
katex audit ignores. Drops the katex ^0.18.2 bump in apollo-react and its
katex@<0.18.2 override (katex stays on 0.16.27/0.16.45, as main chose),
so katex 0.18.7 and commander@15 are no longer installed.
Pins @hono/node-server to the vetted 2.0.10. The ^2.0.10 override had
drifted to 2.0.11, an unvetted perf/test-only release.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolves conflicts with 3b0ddee (read ISO date strings in date
fields): DatePicker and DateTimePicker keep the zoned formatting from
this branch and take main's guard that treats a non-Date or Invalid
Date value as no value. DateTimePicker already keeps sub-minute time
on a day change through zonedSubMinuteMs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TZDate's wall-clock setters read an offset with seconds to the minute, so
a zoned drilldown in Paris in 1900 built June 1 on May 31 and opened, and
stepped, onto the wrong month. Build the instant with zonedWallClockToDate.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without a defaultMonth the drilldown opened on the browser's month and
ignored `today`, so a DateTimePicker in a zone already in the next month
(Kiritimati at 12:00Z on 30 June) opened on June.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With no value and no calendarProps.defaultMonth, fall back to the zone's
today, so the calendar doesn't open on the browser's month near midnight.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
calendarProps.reverseMonths is still accepted, but the independent pair always renders the start month first. Forwarding the flag to two one-month calendars cannot reverse their order, so existing consumers lose the requested layout. Preserve the two-month DayPicker fallback when reverseMonths is enabled, or implement reversed rendering and divider placement at the pair level.
The reason will be displayed to describe this comment to others. Learn more.
The bug is real, but the suggested fix doesn't cure it, so I'm leaving this open for @andreea-ene. I tried a UTC-backed frame (TZDate UTC dates, calendar timeZone="UTC"): the rest of the DateTimePicker suite still passed, but on a Pacific/Apia host react-day-picker 9.14 itself still renders December 2011 as …29, 31, 31 even with timeZone="UTC", because its grid generation still uses the host's local dates. A bare <DayPicker timeZone="UTC" /> does the same. A real fix would need a custom dateLib for DayPicker or an upstream fix. That seems too much for a day that only full date-line jumps skip (Apia on 30 Dec 2011, a few Pacific zones in the 1990s), and only when the host sits in one of those zones.
The month and year grids found the focused cell through
document.activeElement, which is the shadow host when the picker is
portalled into a ShadowRoot, so the arrow keys were swallowed. Use the
key's target.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With no selection and no month override, the independent range months
opened on the browser's month and ignored a given today.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…side it
A calendarProps.footer holding a component that calls useDayPicker worked
in DatePicker and DateRangePicker on main, but the drill-down and the range
pair render the footer outside DayPicker and opening threw. Treat a footer
with a component like a Footer override and keep the DayPicker layout.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Editing minutes can silently switch occurrences of a repeated daylight-saving hour. With timeZone="America/New_York" and value 2024-11-03T06:30Z (01:30 GMT-5), choosing minute 35 commits 05:35Z (01:35 GMT-4), moving the value backwards by 55 minutes. The conversion rebuilds the instant without considering the selected value's offset. Pass that offset or instant as a hint and preserve it when it still matches the requested wall clock, falling back to the normal conversion otherwise. Add a regression test for editing the later occurrence.
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
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.
Brings the date picker work from the Flow prototype into apollo-wind.
Calendargets a drill-down caption for jumping to a month or year, fixed day states in the Future themes, and a week start taken from the locale.DatePickerandDateRangePickerget popover control, a display format, and a range picker whose two months page independently.DateTimePickerswaps the native time input for hour and minute selects and gains an optional timezone select.What changed
Calendar
captionLayout="drilldown". Clicking the month shows a 3×4 grid of months, and clicking the year shows 12 years per page. Picking a year goes to its months, and picking a month goes to its days. The popover keeps its width in every view.startMonth/endMonthare disabled.Intl.Locale#getWeekInfo. That's Monday for en-GB and Sunday for en-US. An explicitweekStartsOnor a date-fnslocaleoverrides it. Where the API is missing (Firefox), it stays on Sunday.size="sm"gives 36px day cells instead of 44px. It's opt-in.DatePicker
calendarProps={{ captionLayout: 'label' }}restores the old caption.popoverProps(align, side, offsets, className, container), aligned to the trigger's start by default. It used to be centred.displayFormat(date-fns), withPPPas the default.DateRangePicker
popoverPropsanddisplayFormat(defaultLLL dd, y).DateTimePicker
Selects replace the native time input, whose popup can't be styled. AM/PM appears withuse12Hour. NewminuteStep(default 5), and a value that isn't on the step stays selectable.showTimeZoneadds a searchable select of every IANA zone the runtime knows, grouped by region with the viewer's zone first. The date and time are read in that zone and committed as that instant, so 14:00 in Europe/Bucharest is 11:00Z in summer. Changing the zone keeps the time on screen and moves the instant.GMT+3) whenever a zone is given throughshowTimeZone,timeZoneordefaultTimeZone.timeZone,defaultTimeZone,onTimeZoneChange,calendarProps,popoverPropsanddisplayFormat.@date-fns/tzdependency.Behaviour and API changes for consumers
DateRangePicker'scalendarProps.month,defaultMonthandonMonthChangeare deprecated but still work. They now apply to the left (start) month, with the right month kept after it, andonMonthChangeonly reports changes to the left month.DatePickernow closes on select and aligns its popover to the start instead of the centre.weekStartsOnis passed.DateTimePickerno longer uses a native<input type="time">, and it stays open after a day is picked.components.Rootorcomponents.Footerkeeps the earlier behaviour, so it stays inside DayPicker's context:DatePickerandDateTimePickerfall back to the label caption, andDateRangePickerfalls back to the single two-month DayPicker (one set of chevrons, months paging together,month/onMonthChangemeaning the first month). The independent range months and the drill-down need a picker without those two overrides. On aCalendargivencaptionLayout="drilldown"explicitly, those two components render outside DayPicker's context, because the month and year views have no DayPicker around them. The drill-down draws its own caption and arrows, so the caption and navigation components (Nav,PreviousMonthButton,NextMonthButton,MonthCaption,CaptionLabel, the dropdowns) don't apply there.Chevronand thenav,button_previousandbutton_nextclassNames and styles still style its arrows. Other component overrides are unaffected.DateTimePicker's newcalendarPropsleaves outtimeZone; the picker's owntimeZonesets the zone.Screenshots
size="sm")Checks
tsc --noEmit: clean.biome lintandbiome format: no new warnings in the changed files.vitest run(apollo-wind): all calendar and picker tests pass, including new ones for the drill-down views, week start, focus on open, independent range months,popoverProps, the DateTimePicker time selects and timezone conversion (including daylight saving changes and years 0 to 99).form-plugins.test.tsxfails locally on Node 25, whose built-inlocalStoragehides jsdom's. It fails the same way onmainand is unrelated.New stories:
Dependency security fixes
Most of the dependency security batch this PR carried has since landed on main in 3d64da6 ("chore(workspace): resolve pnpm audit advisories"). That covers next 16.3.8, sharp 0.35.5, hono 4.13.9, postcss-selector-parser >=7.1.6, the seroval 1.6.7, proxy-addr 2.0.8, source-map-js 1.2.2, @modelcontextprotocol/sdk 1.31.0 and handlebars 4.7.10 refreshes, their quarantine exemptions, and the braces and katex
auditConfig.ignoreGhsasentries. After rebasing onto it, this PR keeps main's versions and notes and only adds one change:@hono/node-serveris pinned to the vetted 2.0.10. The^2.0.10override had drifted to 2.0.11, a perf/test-only release that was never vetted.The earlier katex ^0.18.2 bump in apollo-react and its
katex@<0.18.2override are dropped. Main chose to stay on katex 0.16 and ignore the low-severity GHSA-238p-pmpm-9mq7, because 0.18 renames KaTeX's CSS classes. katex 0.18.7 and commander@15 are therefore no longer installed.pnpm auditandpnpm audit --prodpass, reporting only the two ignored advisories. Each entry's vetting notes are inpnpm-workspace.yaml.🤖 Generated with Claude Code