diff --git a/lib/compound/DateRangePicker/DateRangePicker.stories.tsx b/lib/compound/DateRangePicker/DateRangePicker.stories.tsx index 2d75fe4..e2b5add 100644 --- a/lib/compound/DateRangePicker/DateRangePicker.stories.tsx +++ b/lib/compound/DateRangePicker/DateRangePicker.stories.tsx @@ -189,6 +189,84 @@ export const SinglePlaceholder: Story = { }, }; +// ── Input-trigger variant ─────────────────────────────────────────────────── + +const WIDE_CONTAINER_WIDTH = 320; + +export const InputTrigger: Story = { + args: { + fromDate: FIXED_FROM_DATE, + toDate: FIXED_TO_DATE, + trigger: 'input', + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +export const InputTriggerEmpty: Story = { + args: { + trigger: 'input', + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +export const SingleInputTrigger: Story = { + args: { + mode: 'single', + value: FIXED_FROM_DATE, + trigger: 'input', + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +export const SingleInputTriggerEmpty: Story = { + args: { + mode: 'single', + trigger: 'input', + placeholder: 'Select due date', + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +export const InputTriggerDisabled: Story = { + args: { + mode: 'single', + value: FIXED_FROM_DATE, + trigger: 'input', + disabled: true, + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + // ── Chromatic visual regression: popover open states ──────────────────────── // // The Radix popover renders into a portal at document.body, so the opened @@ -196,7 +274,11 @@ export const SinglePlaceholder: Story = { const openPopoverPlay = async ({ canvasElement }: { canvasElement: HTMLElement }) => { const canvas = within(canvasElement); - const trigger = await canvas.findByRole('button'); + // Button variant renders a - + void; + /** Id of the `PopoverContent`. Used only by the input variant to wire `role="combobox"` + `aria-controls` so `aria-expanded` is valid for axe. */ + readonly popoverId: string; +} + +/** + * Renders the trigger element that opens the date picker popover. + * + * - **`variant='button'`** — uses `` with a Radix + * ` + + ); + } +); + +DateTrigger.displayName = 'DateTrigger'; + +export default DateTrigger; diff --git a/lib/compound/DateRangePicker/parts/SingleCalendar.tsx b/lib/compound/DateRangePicker/parts/SingleCalendar.tsx index 4da3b8e..7258977 100644 --- a/lib/compound/DateRangePicker/parts/SingleCalendar.tsx +++ b/lib/compound/DateRangePicker/parts/SingleCalendar.tsx @@ -1,15 +1,13 @@ 'use client'; -import { useCallback, useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useId, useRef, useState } from 'react'; import { Button, Flex, Theme } from '@radix-ui/themes'; import { Popover, PopoverContent, PopoverPortal, - PopoverTrigger, } from '@radix-ui/react-popover'; import { format } from 'date-fns'; -import { CalendarIcon } from '@phosphor-icons/react'; import { type DayPickerProps, DayPicker } from 'react-day-picker'; import styles from '../DateRangePicker.module.css'; @@ -17,6 +15,7 @@ import type { IDateRangePickerSingleProps } from '../DateRangePicker.types'; import { ContentWrapper } from '../../../atom'; import DateSelection from './DateSelection'; +import DateTrigger from './DateTrigger'; import NextMonthButton from './NextMonthButton'; import SinglePresetsColumn from './SinglePresetsColumn'; import { DEFAULT_DATE_PRESETS } from './presets'; @@ -41,6 +40,7 @@ const SingleCalendar = ({ disabled, placeholder = DEFAULT_PLACEHOLDER, presets = DEFAULT_DATE_PRESETS, + trigger = 'button', }: Omit) => { const isControlled = value !== undefined; const controlledDate = value ?? undefined; @@ -49,6 +49,7 @@ const SingleCalendar = ({ const [isPopoverOpen, setIsPopoverOpen] = useState(false); const popoverContentRef = useRef(null); + const popoverId = useId(); const displayDate = isControlled && !isPopoverOpen ? controlledDate : draftDate; const [today, setToday] = useState(() => new Date()); @@ -96,27 +97,51 @@ const SingleCalendar = ({ setDraftDate(date); }; + // Shared popover-open initialization. Called from both `onOpenChange(true)` + // (button trigger path — Radix-initiated) and `onToggleRequest(true)` (input + // trigger path — externally-initiated via `PopoverAnchor`). Radix does not + // fire `onOpenChange` when the controlled `open` prop is changed from + // outside, so the input variant needs to run this initialization explicitly. + const handleOpenPopover = () => { + setIsPopoverOpen(true); + setToday(new Date()); + setDraftDate(isControlled ? controlledDate : draftDate); + }; + + const handleToggleRequest = (nextOpen: boolean) => { + if (nextOpen) { + handleOpenPopover(); + } else { + setIsPopoverOpen(false); + } + }; + return ( { - setIsPopoverOpen(open); if (open) { - setToday(new Date()); - setDraftDate(isControlled ? controlledDate : draftDate); + handleOpenPopover(); + } else { + setIsPopoverOpen(false); } }} > - - - +