diff --git a/.changeset/stupid-worms-learn.md b/.changeset/stupid-worms-learn.md new file mode 100644 index 00000000000..d627d098da0 --- /dev/null +++ b/.changeset/stupid-worms-learn.md @@ -0,0 +1,5 @@ +--- +"@salt-ds/lab": patch +--- + +Fixed DatePicker showing overlay when `readOnly`. Closes #4470. diff --git a/packages/lab/src/__tests__/__e2e__/date-picker/DatePicker.range.cy.tsx b/packages/lab/src/__tests__/__e2e__/date-picker/DatePicker.range.cy.tsx index 3d407e8558f..86d2e0c0d0a 100644 --- a/packages/lab/src/__tests__/__e2e__/date-picker/DatePicker.range.cy.tsx +++ b/packages/lab/src/__tests__/__e2e__/date-picker/DatePicker.range.cy.tsx @@ -38,6 +38,61 @@ const { } = datePickerStories as any; describe("GIVEN a DatePicker where selectionVariant is single", () => { + describe("WHEN default state", () => { + beforeEach(() => { + const today = new Date(2024, 4, 6); + cy.clock(today, ["Date"]); + cy.setDateAdapter(adapterDateFns); + }); + + afterEach(() => { + cy.clock().then((clock) => clock.restore()); + }); + + it("SHOULD show calendar overlay when click the calendar icon button", () => { + cy.mount(); + + // Simulate opening the calendar + cy.findByRole("button", { name: "Open Calendar" }).realClick(); + // Verify that the calendar is displayed + cy.findAllByRole("application").should("have.length", 2); + }); + + it("SHOULD open calendar overlay when using down arrow", () => { + cy.mount(); + + cy.findAllByRole("textbox").eq(0).click().type("{downArrow}"); + // Verify that the calendar is displayed + cy.findAllByRole("application").should("have.length", 2); + }); + }); + + describe("WHEN readOnly", () => { + beforeEach(() => { + const today = new Date(2024, 4, 6); + cy.clock(today, ["Date"]); + cy.setDateAdapter(adapterDateFns); + }); + + afterEach(() => { + cy.clock().then((clock) => clock.restore()); + }); + + it("SHOULD not show calendar icon button", () => { + cy.mount(); + cy.findByRole("button", { name: "Open Calendar" }).should("not.exist"); + }); + + it("SHOULD not open overlay when using down arrow", () => { + cy.mount(); + cy.findAllByRole("textbox") + .eq(0) + .click() + .type("{downArrow}", { force: true }); + cy.findByRole("application").should("not.exist"); + }); + }); + adapters.forEach((adapter: SaltDateAdapter) => { describe(`Tests with ${adapter.lib}`, () => { beforeEach(() => { diff --git a/packages/lab/src/__tests__/__e2e__/date-picker/DatePicker.single.cy.tsx b/packages/lab/src/__tests__/__e2e__/date-picker/DatePicker.single.cy.tsx index 8929ed51c17..7759c061a3a 100644 --- a/packages/lab/src/__tests__/__e2e__/date-picker/DatePicker.single.cy.tsx +++ b/packages/lab/src/__tests__/__e2e__/date-picker/DatePicker.single.cy.tsx @@ -35,9 +35,61 @@ const { SingleWithMinMaxDate, SingleWithTodayButton, SingleCustomFormat, -} = datePickerStories as any; +} = datePickerStories as any; // not using composeStories yet, will break certain test below describe("GIVEN a DatePicker where selectionVariant is single", () => { + describe("WHEN default state", () => { + beforeEach(() => { + const today = new Date(2024, 4, 6); + cy.clock(today, ["Date"]); + cy.setDateAdapter(adapterDateFns); + }); + + afterEach(() => { + cy.clock().then((clock) => clock.restore()); + }); + + it("SHOULD show calendar overlay when click the calendar icon button", () => { + cy.mount(); + + // Simulate opening the calendar + cy.findByRole("button", { name: "Open Calendar" }).realClick(); + // Verify that the calendar is displayed + cy.findByRole("application").should("exist"); + }); + + it("SHOULD open calendar overlay when using down arrow", () => { + cy.mount(); + + cy.findByRole("textbox").click().type("{downArrow}"); + // Verify that the calendar is displayed + cy.findByRole("application").should("exist"); + }); + }); + + describe("WHEN readOnly", () => { + beforeEach(() => { + const today = new Date(2024, 4, 6); + cy.clock(today, ["Date"]); + cy.setDateAdapter(adapterDateFns); + }); + + afterEach(() => { + cy.clock().then((clock) => clock.restore()); + }); + + it("SHOULD not show calendar icon button", () => { + cy.mount(); + cy.findByRole("button", { name: "Open Calendar" }).should("not.exist"); + }); + + it("SHOULD not open overlay when using down arrow", () => { + cy.mount(); + cy.findByRole("textbox").click().type("{downArrow}", { force: true }); + cy.findByRole("application").should("not.exist"); + }); + }); + adapters.forEach((adapter: SaltDateAdapter) => { describe(`Tests with ${adapter.lib}`, () => { beforeEach(() => { diff --git a/packages/lab/src/date-picker/DatePicker.tsx b/packages/lab/src/date-picker/DatePicker.tsx index a4668504bc7..e9a162dd46f 100644 --- a/packages/lab/src/date-picker/DatePicker.tsx +++ b/packages/lab/src/date-picker/DatePicker.tsx @@ -124,15 +124,16 @@ export const DatePickerMain = forwardRef>( export const DatePicker = forwardRef(function DatePicker< TDate extends DateFrameworkType, >(props: DatePickerProps, ref: React.Ref) { - const { open, defaultOpen, onOpen, ...rest } = props; + const { open, defaultOpen, onOpen, readOnly, ...rest } = props; return ( - + ); }); diff --git a/packages/lab/src/date-picker/DatePickerOverlayProvider.tsx b/packages/lab/src/date-picker/DatePickerOverlayProvider.tsx index a9a325e5d13..a8ff0a923f2 100644 --- a/packages/lab/src/date-picker/DatePickerOverlayProvider.tsx +++ b/packages/lab/src/date-picker/DatePickerOverlayProvider.tsx @@ -94,11 +94,15 @@ interface DatePickerOverlayProviderProps { * The content to be rendered inside the overlay provider. */ children: ReactNode; + /** + * When true, shouldn't open the overlay. + */ + readOnly?: boolean; } export const DatePickerOverlayProvider: React.FC< DatePickerOverlayProviderProps -> = ({ open: openProp, defaultOpen, onOpen, children }) => { +> = ({ open: openProp, defaultOpen, onOpen, children, readOnly }) => { const [open, setOpenState] = useControlled({ controlled: openProp, default: Boolean(defaultOpen), @@ -130,6 +134,10 @@ export const DatePickerOverlayProvider: React.FC< reason?: OpenChangeReason | undefined, ) => { if (newOpen) { + if (readOnly) { + // When not open overlay when readOnly + return; + } triggeringElement.current = document.activeElement as HTMLElement; } setOpenState(newOpen); @@ -141,7 +149,7 @@ export const DatePickerOverlayProvider: React.FC< onDismissCallback?.current?.(); } }, - [onOpen], + [onOpen, readOnly], ); const floatingUIResult = useFloatingUI({ diff --git a/packages/lab/src/date-picker/DatePickerRangeInput.tsx b/packages/lab/src/date-picker/DatePickerRangeInput.tsx index 506f1fcb5ec..f8a9db18e6d 100644 --- a/packages/lab/src/date-picker/DatePickerRangeInput.tsx +++ b/packages/lab/src/date-picker/DatePickerRangeInput.tsx @@ -234,15 +234,17 @@ export const DatePickerRangeInput = forwardRef(function DatePickerRangeInput< onDateValueChange={handleDateValueChange} onChange={onChange} endAdornment={ - + !readOnly && ( + + ) } format={format} {...rest} diff --git a/packages/lab/src/date-picker/DatePickerSingleInput.tsx b/packages/lab/src/date-picker/DatePickerSingleInput.tsx index 20ec5056e9b..97c625be45c 100644 --- a/packages/lab/src/date-picker/DatePickerSingleInput.tsx +++ b/packages/lab/src/date-picker/DatePickerSingleInput.tsx @@ -188,15 +188,17 @@ export const DatePickerSingleInput = forwardRef< onDateChange={handleDateChange} onDateValueChange={handleDateValueChange} endAdornment={ - + !readOnly && ( + + ) } onKeyDown={handleOnKeyDown} {...rest} diff --git a/packages/lab/stories/date-picker/date-picker.stories.tsx b/packages/lab/stories/date-picker/date-picker.stories.tsx index 1e82c8fe3b4..8e22b0270bc 100644 --- a/packages/lab/stories/date-picker/date-picker.stories.tsx +++ b/packages/lab/stories/date-picker/date-picker.stories.tsx @@ -173,6 +173,17 @@ Range.args = { selectionVariant: "range", }; +export const SingleReadOnly = DatePickerSingleTemplate.bind({}); +SingleReadOnly.args = { + readOnly: true, +}; + +export const RangeReadOnly = DatePickerRangeTemplate.bind({}); +RangeReadOnly.args = { + readOnly: true, + selectionVariant: "range", +}; + export const SingleControlled: StoryFn< DatePickerSingleProps > = ({ selectionVariant, defaultSelectedDate, ...args }) => {