diff --git a/.changeset/calendar-controlled-empty.md b/.changeset/calendar-controlled-empty.md new file mode 100644 index 0000000000000..16fec51682442 --- /dev/null +++ b/.changeset/calendar-controlled-empty.md @@ -0,0 +1,7 @@ +--- +'@astryxdesign/core': patch +--- + +[fix] Keep DateInput's calendar selection empty after clearing the field. + +@korkt-kim diff --git a/apps/storybook/stories/DateInput.stories.tsx b/apps/storybook/stories/DateInput.stories.tsx index f7650bc5da4a1..c6edb45cc7749 100644 --- a/apps/storybook/stories/DateInput.stories.tsx +++ b/apps/storybook/stories/DateInput.stories.tsx @@ -2,6 +2,7 @@ import {useState} from 'react'; import type {Meta, StoryObj} from '@storybook/react'; +import {expect, userEvent, waitFor, within} from 'storybook/test'; import {DateInput} from '@astryxdesign/core/DateInput'; import type {ISODateString} from '@astryxdesign/core/Calendar'; import {Layout, LayoutContent} from '@astryxdesign/core/Layout'; @@ -555,6 +556,42 @@ export const Clearable: Story = { }, }; +/** + * Selects a date, clears the controlled value, and reopens the calendar. The + * final frame is the regression endpoint: an empty field with no selected day. + */ +export const ClearedCalendarSelection: Story = { + tags: ['visual-baseline'], + render: args => { + const [value, setValue] = useState('2026-04-06'); + return ; + }, + args: { + label: 'Event date', + hasClear: true, + min: '2026-04-01', + max: '2026-04-30', + presentation: 'popover', + }, + play: async ({canvasElement}) => { + const canvas = within(canvasElement); + + await userEvent.click(canvas.getByRole('button', {name: 'Open calendar'})); + await userEvent.click(canvas.getByRole('button', {name: /April 15, 2026/})); + await userEvent.click( + canvas.getByRole('button', {name: 'Clear Event date'}), + ); + await expect(canvas.getByRole('combobox')).toHaveValue(''); + await userEvent.click(canvas.getByRole('button', {name: 'Open calendar'})); + + await waitFor(() => { + expect( + canvas.queryAllByRole('gridcell', {selected: true, hidden: true}), + ).toHaveLength(0); + }); + }, +}; + export const ClearableWithStatus: Story = { render: args => { const [value, setValue] = useState('2026-04-06'); diff --git a/apps/storybook/stories/DateLocaleConsistency.stories.tsx b/apps/storybook/stories/DateLocaleConsistency.stories.tsx index 79a982b175c09..bbdd448feb2dd 100644 --- a/apps/storybook/stories/DateLocaleConsistency.stories.tsx +++ b/apps/storybook/stories/DateLocaleConsistency.stories.tsx @@ -2,7 +2,7 @@ /** * @file DateLocaleConsistency.stories.tsx - * @input InternationalizationProvider locale plus a fixed Gregorian date + * @input InternationalizationProvider locale plus a controlled Gregorian date or empty value * @output Side-by-side French and Thai date-formatting evidence * @position Storybook verification story for shared date semantics */ @@ -54,7 +54,7 @@ function LocaleDateExamples({locale}: {locale: Locale}) { diff --git a/packages/core/src/DateInput/DateInput.test.tsx b/packages/core/src/DateInput/DateInput.test.tsx index f621f25241a4d..42a543a023c67 100644 --- a/packages/core/src/DateInput/DateInput.test.tsx +++ b/packages/core/src/DateInput/DateInput.test.tsx @@ -9,6 +9,7 @@ * SYNC: When DateInput.tsx changes, update tests to match new behavior */ +import {useState} from 'react'; import {describe, it, expect, vi, beforeEach} from 'vitest'; import { render, @@ -27,6 +28,7 @@ import {InputGroupText} from '../InputGroup/InputGroupText'; import {defineTheme} from '../theme/defineTheme'; import {generateThemeCSS} from '../theme/generateThemeRules'; import {InternationalizationProvider} from '../i18n'; +import type {ISODateString} from '../utils'; function generateThemeTestCSS(theme: Parameters[0]) { const {prose, component} = generateThemeCSS(theme); @@ -593,6 +595,42 @@ describe('DateInput', () => { // Full popover interaction is tested in the browser via Storybook. describe('hasClear', () => { + it('shows no selected calendar days after clearing', async () => { + const user = userEvent.setup(); + + function ControlledDateInput() { + const [value, setValue] = useState( + '2026-01-01', + ); + return ( + + ); + } + + render(); + await user.click(getButton('Open calendar')); + await user.click(getButton(/January 15, 2026/)); + expect( + screen.getAllByRole('gridcell', {selected: true, hidden: true}), + ).toHaveLength(1); + + await user.click(getButton('Clear Date')); + expect(screen.getByRole('combobox')).toHaveValue(''); + await user.click(getButton('Open calendar')); + + expect( + screen.queryAllByRole('gridcell', {selected: true, hidden: true}), + ).toHaveLength(0); + }); + it('shows clear button when hasClear is true and value exists', () => { render(