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:
@@ -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
|
||||
/>`;
|
||||
@@ -58,6 +58,10 @@ export const components: Page[] = [
|
||||
title: 'Container',
|
||||
slug: 'container',
|
||||
},
|
||||
{
|
||||
title: 'DatePicker',
|
||||
slug: 'date-picker',
|
||||
},
|
||||
{
|
||||
title: 'DateRangePicker',
|
||||
slug: 'date-range-picker',
|
||||
|
||||
Reference in New Issue
Block a user