feat(ui): add DatePicker component (#34184)

* feat(ui): add DatePicker component

Add a single-date picker built on React Aria's DatePicker, mirroring the
existing DateRangePicker implementation. Includes the date field with
segmented input, calendar popover, BUI design tokens, bg consumer
pattern, and full keyboard/screen reader accessibility.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Erik Hughes <erikh@spotify.com>

* fix(ui): address DatePicker PR feedback

- Remove unused dataAttributes spread from DatePickerGroup
- Mark DatePickerGroupDefinition and DatePickerCalendarDefinition as
  public so CSS class name changes appear in API reports
- Add Affected components line to changeset

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Erik Hughes <erikh@spotify.com>

* fix(ui): restore dataAttributes spread in DatePickerGroup

The bg: 'consumer' config on DatePickerGroupDefinition emits
data-on-bg attributes via useDefinition. Without spreading
dataAttributes onto <Group>, the CSS [data-on-bg] selectors
never match and background auto-increment doesn't work.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Erik Hughes <erikh@spotify.com>

---------

Signed-off-by: Erik Hughes <erikh@spotify.com>
Co-authored-by: Erik Hughes <erikh@spotify.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik Hughes
2026-05-12 10:46:46 +02:00
committed by GitHub
parent dbeb7aab3e
commit d726bcd842
17 changed files with 1126 additions and 0 deletions
@@ -0,0 +1,36 @@
'use client';
import { DatePicker } from '../../../../../packages/ui/src/components/DatePicker/DatePicker';
import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
import { parseDate } from '@internationalized/date';
export const WithLabel = () => {
return <DatePicker label="Date" style={{ maxWidth: '220px' }} />;
};
export const Sizes = () => {
return (
<Flex
direction="column"
gap="4"
style={{ width: '100%', maxWidth: '280px' }}
>
<DatePicker label="Small" size="small" />
<DatePicker label="Medium" size="medium" />
</Flex>
);
};
export const WithDefaultValue = () => {
return (
<DatePicker
label="Booking date"
defaultValue={parseDate('2025-02-03')}
style={{ maxWidth: '280px' }}
/>
);
};
export const Disabled = () => {
return <DatePicker label="Date" isDisabled style={{ maxWidth: '280px' }} />;
};
@@ -0,0 +1,82 @@
import { PropsTable } from '@/components/PropsTable';
import { Snippet } from '@/components/Snippet';
import { datePickerPropDefs } from './props-definition';
import {
datePickerUsageSnippet,
withLabelSnippet,
sizesSnippet,
withDefaultValueSnippet,
disabledSnippet,
} from './snippets';
import { WithLabel, Sizes, WithDefaultValue, Disabled } from './components';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
import { DatePickerDefinition } from '../../../utils/definitions';
import { ChangelogComponent } from '@/components/ChangelogComponent';
import { CodeBlock } from '@/components/CodeBlock';
import { ReactAriaLink } from '@/components/ReactAriaLink';
export const reactAriaUrls = {
datePicker: 'https://react-aria.adobe.com/DatePicker',
};
<PageTitle
title="DatePicker"
description="A date picker that combines a date field and a calendar popover for selecting a date."
/>
<Snippet
align="center"
py={4}
preview={<WithLabel />}
code={withLabelSnippet}
/>
## Usage
<CodeBlock code={datePickerUsageSnippet} />
## API reference
<PropsTable data={datePickerPropDefs} />
<ReactAriaLink component="DatePicker" href={reactAriaUrls.datePicker} />
## Examples
### Sizes
<Snippet
align="center"
py={4}
open
preview={<Sizes />}
code={sizesSnippet}
layout="side-by-side"
/>
### With default value
<Snippet
align="center"
py={4}
open
preview={<WithDefaultValue />}
code={withDefaultValueSnippet}
layout="side-by-side"
/>
### Disabled
<Snippet
align="center"
py={4}
open
preview={<Disabled />}
code={disabledSnippet}
layout="side-by-side"
/>
<Theming definition={DatePickerDefinition} />
<ChangelogComponent component="date-picker" />
@@ -0,0 +1,94 @@
import {
classNamePropDefs,
stylePropDefs,
type PropDef,
} from '@/utils/propDefs';
import { Chip } from '@/components/Chip';
export const datePickerPropDefs: Record<string, PropDef> = {
size: {
type: 'enum',
values: ['small', 'medium'],
default: 'small',
responsive: true,
description: (
<>
Visual size of the picker. Use <Chip>small</Chip> for dense layouts,{' '}
<Chip>medium</Chip> for prominent fields.
</>
),
},
label: {
type: 'string',
description: 'Visible label displayed above the picker.',
},
secondaryLabel: {
type: 'string',
description: (
<>
Secondary text shown next to the label. If not provided and isRequired
is true, displays <Chip>Required</Chip>.
</>
),
},
description: {
type: 'string',
description: 'Help text displayed below the label.',
},
value: {
type: 'enum',
values: ['DateValue'],
description: 'Controlled value of the date.',
},
defaultValue: {
type: 'enum',
values: ['DateValue'],
description: 'Default value for uncontrolled usage.',
},
onChange: {
type: 'enum',
values: ['(value: DateValue | null) => void'],
description: 'Handler called when the selected date changes.',
},
granularity: {
type: 'enum',
values: ['day', 'hour', 'minute', 'second'],
default: 'day',
description:
'Smallest unit displayed. Defaults to "day" for dates and "minute" for times.',
},
minValue: {
type: 'enum',
values: ['DateValue'],
description: 'Minimum allowed date. Dates before this are disabled.',
},
maxValue: {
type: 'enum',
values: ['DateValue'],
description: 'Maximum allowed date. Dates after this are disabled.',
},
isDateUnavailable: {
type: 'enum',
values: ['(date: DateValue) => boolean'],
description:
'Callback invoked for each calendar date. Return true to mark a date as unavailable.',
},
name: {
type: 'string',
description: 'Form field name for the date, submitted as ISO 8601.',
},
isRequired: {
type: 'boolean',
description: 'Whether the field is required for form submission.',
},
isDisabled: {
type: 'boolean',
description: 'Whether the picker is disabled.',
},
isReadOnly: {
type: 'boolean',
description: 'Whether the picker is read-only.',
},
...classNamePropDefs,
...stylePropDefs,
};
@@ -0,0 +1,25 @@
export const datePickerUsageSnippet = `import { DatePicker } from '@backstage/ui';
<DatePicker label="Date" />`;
export const withLabelSnippet = `<DatePicker
label="Date"
description="Select the date of your event."
/>`;
export const sizesSnippet = `<Flex direction="column" gap="4">
<DatePicker label="Small" size="small" />
<DatePicker label="Medium" size="medium" />
</Flex>`;
export const withDefaultValueSnippet = `import { parseDate } from '@internationalized/date';
<DatePicker
label="Booking date"
defaultValue={parseDate('2025-02-03')}
/>`;
export const disabledSnippet = `<DatePicker
label="Date"
isDisabled
/>`;
+4
View File
@@ -58,6 +58,10 @@ export const components: Page[] = [
title: 'Container',
slug: 'container',
},
{
title: 'DatePicker',
slug: 'date-picker',
},
{
title: 'DateRangePicker',
slug: 'date-range-picker',