Skip to content

feat(core): add DateTimeInput timeOptionInterval for a preset-time dropdown - #4837

Open
AKnassa wants to merge 2 commits into
facebook:mainfrom
AKnassa:ak-2727-time-dropdown
Open

feat(core): add DateTimeInput timeOptionInterval for a preset-time dropdown#4837
AKnassa wants to merge 2 commits into
facebook:mainfrom
AKnassa:ak-2727-time-dropdown

Conversation

@AKnassa

@AKnassa AKnassa commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

What this does

Adds an optional dropdown of preset times to the time half of DateTimeInput. Set timeOptionInterval to 5, 10, 15, 30, or 60 and the time field offers a clickable list of times at that spacing. 60 gives the 12 AM to 11 PM list the issue asks for.

Why

Today, setting a specific time means typing it or holding an arrow key. For "pick a meeting time" flows that is slow. Opening a list and clicking is the expected way to do this.

What changed

  • New optional timeOptionInterval prop on DateTimeInput. Leave it off and nothing about the component changes.
  • When it is set, the time field opens a list on click or Alt+Down. Arrow keys move through it, Enter picks, Escape closes.
  • Typing still works. The list follows along and highlights the closest match, so a time between two options can still be typed by hand.
  • min and max trim the list on the boundary date, so only valid times are offered.
  • Two new theming hooks for the popup: date-time-input-time-listbox and date-time-input-time-option.

Why it is opt-in rather than always on

The issue suggests the list appear on focus and follow timeIncrement. Two things in the current component make that a breaking change rather than an addition:

  • The time field's Up and Down arrows already step the value by timeIncrement, which is documented and tested. A list that is always open would have to take those keys. Instead the list opens on Alt+Down, and plain arrows keep stepping whenever the list is closed.
  • Turning the time field into a combobox adds a second one to the field. DateTimeInput.test.tsx alone has 33 assertions using the singular getByRole('combobox') to reach the date input, and those throw the moment a second one exists. The same breakage would hit consumers silently on upgrade.

timeIncrement also defaults to 1, which would mean a 1440 row list, and its type cannot express 60. So the list gets its own prop. Making it default-on later is a one line change if that is preferred.

How to see it

Storybook: Core / DateTimeInput / With Time Options and With Hourly Time Options.

Notes for review

Four things surfaced while building that are worth a look:

  • Both the calendar and the new list are popover="auto", so opening the list evicts the calendar and fires its onHide, which refocused the date input. That is now guarded, and dismissing the calendar normally still restores date focus.
  • The layer renders its children whether open or closed, so the options only mount while the list is open.
  • A value carrying seconds (14:00:00) with hasSeconds off never matched a 14:00 option, so nothing showed as selected. Normalized before comparing.
  • Added scroll-into-view for the active option. Verified in Chrome, since jsdom cannot measure it.

TimeInput deliberately does not get this. It has real consumers that render it inside a popover, and it renders a different tree inside an InputGroup. Happy to file that as a follow-up.

Closes #2727

Adds an opt-in `timeOptionInterval` prop that turns the time field into an
APG combobox over a listbox of preset times at a 5/10/15/30/60-minute
cadence. Click or Alt+ArrowDown opens it, arrows move the active option,
Enter commits, Escape closes, and typing moves the highlight to the closest
option without filtering the list, so free-form entry keeps working.
min/max trim the options on the boundary date.

The dropdown is opt-in rather than default because the field's ArrowUp and
ArrowDown already step the value by timeIncrement, and because a second
always-on combobox would break every getByRole('combobox') query that
resolves to the date input. With the prop omitted the time field is
byte-identical to today: no combobox role, no listbox, no new attributes.
With the list closed, arrows keep stepping.

Guards three failure modes the popover stack creates: the calendar's onHide
no longer steals focus when the time list evicts it, options mount only
while the list is open, and the selected marker normalises seconds so an
external value of 14:00:00 still matches a 14:00 option.

Closes facebook#2727
@vercel

vercel Bot commented Aug 8, 2026

Copy link
Copy Markdown

@AKnassa is attempting to deploy a commit to the Meta Open Source Team on Vercel.

A member of the Team first needs to authorize it.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Aug 8, 2026
@github-actions github-actions Bot added community Authored by a community contributor (not on the eng/design team) needs:code-review High-risk change (new package/component/API) — needs human code review before merge needs:design-review Affects visuals — Design should review labels Aug 8, 2026
@github-actions

github-actions Bot commented Aug 8, 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

DateTimeInput (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 1196 -
Complexity N/A Very High (234) -

Bundle Size Summary

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

Accessibility Audit

Status: No accessibility violations detected.


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

github-actions Bot added a commit that referenced this pull request Aug 8, 2026
Adds 26 edge-case tests and fixes the nine defects they found.

Real bugs:
- Clicking the option already marked selected fired a change, because the
  commit compared the raw value while the marker compared a normalised one.
- A min/max window narrower than the cadence opened an empty list, and one
  that emptied under an open list left it open, where the gated keyboard
  switch let the arrows silently step the value instead.
- Opening on a field with a date but no time left no active option, so
  ArrowUp and Enter did nothing at all.
- Enter discarded a typed off-cadence time and committed the option below
  it; it now prefers typed text only while the highlight still follows the
  typing, and honours the active option once the user arrows away.
- A stale highlight survived the list shrinking, leaving no visible active
  option and arrow keys that looked dead.
- Blur never closed the list, stranding it open in the top layer on a
  programmatic focus move. Now mirrors BaseTypeahead's relatedTarget guard.
- A pointer press that never became a click wedged the light-dismiss
  deferral flag and held later keyboard opens hostage.
- The listbox was named from `label` while its own input was named from
  `timeLabel`, so renaming the field left the list announcing the old name.
- Option rows ignored `size`, leaving an sm field with md-sized rows.

Part of facebook#2727
@AKnassa
AKnassa marked this pull request as ready for review August 8, 2026 23:13
github-actions Bot added a commit that referenced this pull request Aug 8, 2026
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. community Authored by a community contributor (not on the eng/design team) needs:code-review High-risk change (new package/component/API) — needs human code review before merge needs:design-review Affects visuals — Design should review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] [bug-bash] DateTimeInput: add time-picker dropdown (preset times, e.g. 12am–11pm)

1 participant