Skip to content

Commit d82872d

Browse files
ECHOES-1018 Allow onOpenChange for an uncontrolled Modal
1 parent 801240e commit d82872d

File tree

3 files changed

+53
-2
lines changed

3 files changed

+53
-2
lines changed

src/components/modals/ModalTypes.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,18 @@ interface CommonProps {
4545
* Callback function triggered when the dialog is closed.
4646
*/
4747
onClose?: VoidFunction;
48-
/** An accessible title to be announced when the dialog is opened. */
48+
/**
49+
* Callback function triggered when the dialog state changes.
50+
*/
51+
onOpenChange?: (isOpen: boolean) => void;
52+
/**
53+
* An accessible title to be announced when the dialog is opened.
54+
*/
4955
title?: TextNodeOptional;
5056
}
5157

5258
interface UncontrolledProps extends CommonProps {
5359
isOpen?: never;
54-
onOpenChange?: never;
5560
}
5661

5762
interface ControlledProps extends CommonProps {

src/components/modals/__tests__/Modal-test.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ it('should appear/disappear as expected', async () => {
4141

4242
it('should allow to be controlled', async () => {
4343
const { user } = renderControlledModal();
44+
4445
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
4546

4647
await user.click(screen.getByRole('button', { name: 'Toggle' }));
@@ -112,8 +113,31 @@ it("shouldn't have any a11y violation", async () => {
112113
await expect(container).toHaveNoA11yViolations();
113114
});
114115

116+
it('should call onOpenChange for uncontrolled modal', async () => {
117+
const onOpenChange = jest.fn();
118+
119+
const { user } = render(
120+
<Modal
121+
content="Modal content"
122+
onOpenChange={onOpenChange}
123+
secondaryButton={<Button>Close</Button>}
124+
title="Test Modal">
125+
<Button>Toggle</Button>
126+
</Modal>,
127+
);
128+
129+
expect(onOpenChange).not.toHaveBeenCalled();
130+
131+
await user.click(screen.getByRole('button', { name: 'Toggle' }));
132+
expect(onOpenChange).toHaveBeenCalledWith(true);
133+
134+
await user.click(screen.getByText('Close'));
135+
expect(onOpenChange).toHaveBeenCalledWith(false);
136+
});
137+
115138
function renderModal(args: Partial<ModalProps> = {}) {
116139
const { isOpen, onOpenChange, ...overrides } = args;
140+
117141
return render(
118142
<Modal {...overrides}>
119143
<Button>Toggle</Button>

stories/Modal-stories.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,3 +210,25 @@ export const WithADropdownItemTrigger: Story = {
210210
</DropdownMenu>
211211
),
212212
};
213+
214+
export const UncontrolledWithOnOpenChange: Story = {
215+
args: {
216+
description: 'This is an uncontrolled modal using onOpenChange to alert open/close events.',
217+
footerLink: 'link',
218+
primaryButton: 'default',
219+
secondaryButton: 'default',
220+
title: 'Uncontrolled Modal with onOpenChange',
221+
},
222+
223+
render: (args) => (
224+
<Modal
225+
content={<div>Modal content, anything can be set in there.</div>}
226+
{...args}
227+
onOpenChange={(open) => {
228+
// eslint-disable-next-line no-alert
229+
alert(`Modal open state changed: ${open}`);
230+
}}>
231+
<Button>Show Modal</Button>
232+
</Modal>
233+
),
234+
};

0 commit comments

Comments
 (0)