-
Notifications
You must be signed in to change notification settings - Fork 0
feat(base-ui): date/time/range pickers with sectioned guided input #38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
joshuapare
wants to merge
41
commits into
main
Choose a base branch
from
feat/date-time-pickers
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
41 commits
Select commit
Hold shift + click to select a range
ec7c580
feat(base-ui): add date picker theming tokens
joshuapare 25b8e3e
feat(base-ui): pure date utilities for DatePicker
joshuapare 27143f0
feat(base-ui): Intl-based formatters for DatePicker
joshuapare 5c62deb
feat(base-ui): Calendar grid with WAI-ARIA keyboard nav
joshuapare 65e035b
feat(base-ui): DatePicker component with compound API
joshuapare 5228945
feat(base-ui): TimePicker with 12/24 hour support and minute step
joshuapare 9c60c83
chore(base-ui): remove unused useRef import from TimePicker
joshuapare b2b5aa3
feat(base-ui): DateTimePicker composing DatePicker and TimePicker
joshuapare e8b7630
docs(base-ui): Storybook stories for date/time pickers
joshuapare ae8c649
docs(base-ui): note date/time pickers in component status
joshuapare 039dd97
feat(showcase): add date/time pickers interactive demo
joshuapare 827839a
fix(base-ui): input-styled triggers + solid popover surface for pickers
joshuapare 0f31883
fix(base-ui): drop DatePicker.Root/Trigger/Popup aliases to fix dts emit
joshuapare 86d2e37
feat(base-ui): DatePicker trigger becomes text input with live parse
joshuapare cadd96c
chore(base-ui): fix strict-mode index access in DatePicker tests
joshuapare c02beb7
feat(base-ui): TimePicker column-selector popover alongside text entry
joshuapare 1f2db0f
feat(base-ui): Calendar supports range selection mode
joshuapare f916c7f
fix(base-ui): handle Calendar discriminated union in DatePicker
joshuapare 44b216d
feat(base-ui): DateRangePicker with range calendar and text entry
joshuapare 9da8c66
docs(base-ui): DateRangePicker stories and update picker status
joshuapare 7ed297e
fix(base-ui): resolve pre-existing TS errors in test files
joshuapare d839c07
feat(base-ui): DateField sectioned input primitive (MUI-style useField)
joshuapare 8f32e62
chore(base-ui): clean up date-field diagnostics
joshuapare f5cdded
feat(base-ui): DatePicker uses sectioned DateField for guided input
joshuapare 219e7be
revert(showcase): remove date-time-pickers demo (storybook is the tar…
joshuapare 40080d6
feat(base-ui): TimePicker uses sectioned DateField for guided time input
joshuapare b463ca3
feat(base-ui): DateTimePicker uses sectioned DateField for guided input
joshuapare 6ec67b7
feat(base-ui): DateRangePicker uses two sectioned DateFields
joshuapare a2492a8
chore(base-ui): drop deprecated placeholder/format props from picker …
joshuapare 3bbefd8
docs(base-ui): document DateField + update picker status for sectione…
joshuapare 2a202d7
fix(base-ui): bare DateField inside pickers; DateTimePicker popup use…
joshuapare 1143216
feat(base-ui): DateTimePicker popup renders time columns inline
joshuapare 5df7b2d
fix(base-ui): time columns fill popup height in DateTimePicker
joshuapare 0d9af06
feat(base-ui): picker popups gain Clear/Done footer; fix DateField no…
joshuapare 0fae1c3
fix(base-ui): suppress TimeColumns re-center on user click/keyboard s…
joshuapare db14f74
fix(base-ui): time columns stretch via flex instead of percentage height
joshuapare ac1b947
fix(base-ui): drop fill mechanism; use fixed column height for consis…
joshuapare 687d5f1
fix(base-ui): time columns stretch to Calendar height via overflow:hi…
joshuapare 42cf5ea
build(showcase): auto-build library deps before dev and build
joshuapare 6721caf
fix(base-ui): address CodeRabbit review findings
joshuapare c952e6f
fix(base-ui): second round of CodeRabbit review fixes
joshuapare File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| # Component Status — @omniviewdev/base-ui | ||
|
|
||
| Tracks the implementation status of components in the package. | ||
|
|
||
| | Component | Status | Description | | ||
| |---|---|---| | ||
| | Calendar | Beta[^1] | Single-date and range-mode calendar grid; supports `min`/`max`, custom `isDateDisabled`, `weekStartsOn`, `locale`, and `autoFocus` | | ||
| | DateField | Beta[^1] | Sectioned guided input primitive; tabbable sections, per-section validation, arrow-key increment, paste-to-fill; supports `mode` (`date`/`time`/`datetime`), `locale`, `hourCycle`, `showSeconds`, `min`/`max`, `disabled`, `readOnly` | | ||
| | DatePicker | Beta[^1] | DateField-powered date input + calendar popover; supports `min`/`max`, `locale`, `hourCycle`, `disabled`, `readOnly` | | ||
| | DateRangePicker | Beta[^1] | Dual DateField trigger + range-selecting calendar popover; supports `min`/`max`, `locale`, `rangeSeparator`, `disabled`, `readOnly` | | ||
| | DateTimePicker | Beta[^1] | DateField-powered combined date+time input with calendar+time popover; supports `min`/`max`, `showSeconds`, `hourCycle`, `minuteStep`, `disabled`, `readOnly` | | ||
| | TimePicker | Beta[^1] | DateField-powered time input + column-selector popover; supports `showSeconds`, `hourCycle`, `minuteStep`, `disabled`, `readOnly` | | ||
|
|
||
| [^1]: Date and time components are feature-complete and unit-tested, but final UX verification (cross-browser behavior, locale exotica, keyboard-only flows) is still pending — treat as Beta until that sign-off lands. |
107 changes: 107 additions & 0 deletions
107
packages/base-ui/src/components/date-field/DateField.module.css
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| /* ─── DateField: sectioned input primitive ──────────────────────────────── */ | ||
|
|
||
| .root { | ||
| --_ov-control-height: var(--ov-control-height-md); | ||
| --_ov-font-size: var(--ov-font-size-body); | ||
| --_ov-padding-inline: var(--ov-space-inline-control); | ||
| --_ov-bg: var(--ov-color-bg-surface-raised); | ||
| --_ov-fg: var(--ov-color-fg-default); | ||
| --_ov-border: var(--ov-color-border-default); | ||
| --_ov-focus: var(--ov-color-border-focus); | ||
| --_ov-placeholder: var(--ov-color-fg-subtle); | ||
|
|
||
| display: inline-flex; | ||
| align-items: center; | ||
| min-height: var(--_ov-control-height); | ||
| padding-inline: var(--_ov-padding-inline); | ||
| border: 1px solid var(--_ov-border); | ||
| border-radius: var(--ov-radius-control); | ||
| background: var(--_ov-bg); | ||
| color: var(--_ov-fg); | ||
| font-family: var(--ov-font-sans); | ||
| font-size: var(--_ov-font-size); | ||
| line-height: 1.2; | ||
| /* Tabular numerals so sections align cleanly as values change */ | ||
| font-variant-numeric: tabular-nums; | ||
| white-space: nowrap; | ||
| cursor: text; | ||
| outline: none; | ||
| transition: | ||
| background-color var(--ov-duration-interactive) var(--ov-ease-standard), | ||
| border-color var(--ov-duration-interactive) var(--ov-ease-standard), | ||
| box-shadow var(--ov-duration-interactive) var(--ov-ease-standard); | ||
| } | ||
|
|
||
| .root:hover:not([data-disabled]) { | ||
| background: color-mix(in srgb, var(--_ov-bg) 86%, var(--ov-color-state-hover) 14%); | ||
| } | ||
|
|
||
| .root:focus-within:not([data-disabled]) { | ||
| border-color: var(--_ov-focus); | ||
| box-shadow: 0 0 0 1px var(--ov-color-state-focus-ring); | ||
| } | ||
|
|
||
| .root[data-disabled] { | ||
| opacity: var(--ov-opacity-disabled, 0.45); | ||
| cursor: not-allowed; | ||
| } | ||
|
|
||
| /* ─── Bare mode — strip shell styling when embedded inside a picker shell ─── */ | ||
|
|
||
| .root[data-bare] { | ||
| border: none; | ||
| background: transparent; | ||
| padding-inline: 0; | ||
| min-height: unset; | ||
| } | ||
|
|
||
| .root[data-bare]:hover { | ||
| background: transparent; | ||
| } | ||
|
|
||
| .root[data-bare]:focus-within { | ||
| border: none; | ||
| box-shadow: none; | ||
| } | ||
|
|
||
| /* ─── Section span (editable) ────────────────────────────────────────────── */ | ||
|
|
||
| .section { | ||
| display: inline-block; | ||
| padding: 0 1px; | ||
| border-radius: 2px; | ||
| min-width: 1ch; | ||
| text-align: center; | ||
| white-space: nowrap; | ||
| outline: none; | ||
| caret-color: transparent; | ||
| user-select: none; | ||
| -webkit-user-select: none; | ||
| cursor: text; | ||
| } | ||
|
|
||
| .section[data-focused] { | ||
| background: var(--ov-color-brand-500); | ||
| color: var(--ov-color-fg-inverse, var(--ov-color-fg-default)); | ||
| } | ||
|
|
||
| .section[data-placeholder] { | ||
| color: var(--ov-color-fg-muted); | ||
| } | ||
|
|
||
| /* When focused and also placeholder, preserve readable contrast on brand bg */ | ||
| .section[data-focused][data-placeholder] { | ||
| color: var(--ov-color-fg-inverse, var(--ov-color-fg-default)); | ||
| opacity: 0.85; | ||
| } | ||
|
|
||
| /* ─── Literal separators ─────────────────────────────────────────────────── */ | ||
|
|
||
| .literal { | ||
| display: inline-block; | ||
| color: var(--ov-color-fg-muted); | ||
| user-select: none; | ||
| -webkit-user-select: none; | ||
| pointer-events: none; | ||
| white-space: pre; | ||
| } |
101 changes: 101 additions & 0 deletions
101
packages/base-ui/src/components/date-field/DateField.stories.tsx
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| import type { Meta, StoryObj } from '@storybook/react'; | ||
| import { useState } from 'react'; | ||
| import { DateField, type DateFieldProps } from './DateField'; | ||
|
|
||
| const meta = { | ||
| title: 'Components/DateField', | ||
| component: DateField, | ||
| tags: ['autodocs'], | ||
| args: { | ||
| mode: 'date', | ||
| disabled: false, | ||
| readOnly: false, | ||
| }, | ||
| argTypes: { | ||
| disabled: { control: 'boolean' }, | ||
| readOnly: { control: 'boolean' }, | ||
| mode: { control: 'select', options: ['date', 'time', 'datetime'] }, | ||
| hourCycle: { control: 'inline-radio', options: [12, 24] }, | ||
| showSeconds: { control: 'boolean' }, | ||
| locale: { control: 'text' }, | ||
| }, | ||
| } satisfies Meta<typeof DateField>; | ||
|
|
||
| export default meta; | ||
| type Story = StoryObj<typeof meta>; | ||
|
|
||
| // ─── Story components (keep hooks legal) ────────────────────────────────── | ||
|
|
||
| function DateModeStory(args: DateFieldProps) { | ||
| const [value, setValue] = useState<Date | null>(null); | ||
| return <DateField {...args} mode="date" locale="en-US" value={value} onChange={setValue} />; | ||
| } | ||
|
|
||
| function TimeMode24hStory(args: DateFieldProps) { | ||
| const [value, setValue] = useState<Date | null>(null); | ||
| return <DateField {...args} mode="time" hourCycle={24} value={value} onChange={setValue} />; | ||
| } | ||
|
|
||
| function TimeMode12hStory(args: DateFieldProps) { | ||
| const [value, setValue] = useState<Date | null>(null); | ||
| return ( | ||
| <DateField | ||
| {...args} | ||
| mode="time" | ||
| hourCycle={12} | ||
| showSeconds | ||
| value={value} | ||
| onChange={setValue} | ||
| /> | ||
| ); | ||
| } | ||
|
|
||
| function DateTimeModeStory(args: DateFieldProps) { | ||
| const [value, setValue] = useState<Date | null>(null); | ||
| return <DateField {...args} mode="datetime" value={value} onChange={setValue} />; | ||
| } | ||
|
|
||
| function LocaleGBStory(args: DateFieldProps) { | ||
| const [value, setValue] = useState<Date | null>(new Date()); | ||
| return <DateField {...args} mode="date" locale="en-GB" value={value} onChange={setValue} />; | ||
| } | ||
|
|
||
| function DisabledStory(args: DateFieldProps) { | ||
| const [value, setValue] = useState<Date | null>(new Date()); | ||
| return <DateField {...args} disabled value={value} onChange={setValue} />; | ||
| } | ||
|
|
||
| function ReadOnlyStory(args: DateFieldProps) { | ||
| const [value, setValue] = useState<Date | null>(new Date()); | ||
| return <DateField {...args} readOnly value={value} onChange={setValue} />; | ||
| } | ||
|
|
||
| // ─── Story exports ──────────────────────────────────────────────────────── | ||
|
|
||
| export const DateMode: Story = { | ||
| render: (args) => <DateModeStory {...args} />, | ||
| }; | ||
|
|
||
| export const TimeMode24h: Story = { | ||
| render: (args) => <TimeMode24hStory {...args} />, | ||
| }; | ||
|
|
||
| export const TimeMode12h: Story = { | ||
| render: (args) => <TimeMode12hStory {...args} />, | ||
| }; | ||
|
|
||
| export const DateTimeMode: Story = { | ||
| render: (args) => <DateTimeModeStory {...args} />, | ||
| }; | ||
|
|
||
| export const LocaleGB: Story = { | ||
| render: (args) => <LocaleGBStory {...args} />, | ||
| }; | ||
|
|
||
| export const Disabled: Story = { | ||
| render: (args) => <DisabledStory {...args} />, | ||
| }; | ||
|
|
||
| export const ReadOnly: Story = { | ||
| render: (args) => <ReadOnlyStory {...args} />, | ||
| }; | ||
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.