Skip to content

Commit 77b4fd9

Browse files
authored
feat(modals): adds subcomponent mapping to Modal (#1750)
1 parent 875b153 commit 77b4fd9

File tree

12 files changed

+86
-61
lines changed

12 files changed

+86
-61
lines changed

docs/migration.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ consider additional positioning prop support on a case-by-case basis.
119119
- `DrawerModal`: renamed to `Drawer`
120120
- `TooltipModal`: removed `popperModifiers` prop (see [note](#breaking-changes))
121121
- Removed `GARDEN_PLACEMENT` type export. Use `ITooltipModalProps['placement']` instead.
122+
- Subcomponent exports for `Modal` have been deprecated and will be removed in a future major version.
123+
Update to subcomponent properties (e.g., `Modal.Body`).
122124

123125
#### @zendeskgarden/react-notification
124126

packages/modals/README.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,25 @@ npm install react react-dom styled-components @zendeskgarden/react-theming
1818

1919
```jsx
2020
import { ThemeProvider } from '@zendeskgarden/react-theming';
21-
import { Modal, Header, Body, Footer, FooterItem, Close } from '@zendeskgarden/react-modals';
21+
import { Modal } from '@zendeskgarden/react-modals';
2222
import { Button } from '@zendeskgarden/react-buttons';
2323

2424
/**
2525
* Place a `ThemeProvider` at the root of your React application
2626
*/
2727
<ThemeProvider>
2828
<Modal onClose={() => alert('modal closing')}>
29-
<Header>Example Header</Header>
30-
<Body>Some content</Body>
31-
<Footer>
32-
<FooterItem>
29+
<Modal.Header>Example Header</Modal.Header>
30+
<Modal.Body>Some content</Modal.Body>
31+
<Modal.Footer>
32+
<Modal.FooterItem>
3333
<Button isBasic>Cancel</Button>
34-
</FooterItem>
35-
<FooterItem>
34+
</Modal.FooterItem>
35+
<Modal.FooterItem>
3636
<Button isPrimary>Confirm</Button>
37-
</FooterItem>
38-
</Footer>
39-
<Close aria-label="Close modal" />
37+
</Modal.FooterItem>
38+
</Modal.Footer>
39+
<Modal.Close aria-label="Close modal" />
4040
</Modal>
4141
</ThemeProvider>;
4242
```

packages/modals/demo/modal.stories.mdx

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
import { Meta, ArgsTable, Canvas, Story, Markdown } from '@storybook/addon-docs';
22
import { useArgs } from '@storybook/client-api';
3-
import { Modal, Body, Close, Footer, FooterItem, Header } from '@zendeskgarden/react-modals';
3+
import { Modal } from '@zendeskgarden/react-modals';
44
import { ModalStory } from './stories/ModalStory';
55
import { MODAL_BODY as BODY, MODAL_FOOTER_ITEMS as FOOTER_ITEMS } from './stories/data';
66
import README from '../README.md';
77

88
<Meta
99
title="Packages/Modals/Modal"
1010
component={Modal}
11-
subcomponents={{ Body, Close, Footer, FooterItem, Header }}
11+
subcomponents={{
12+
'Modal.Body': Modal.Body,
13+
'Modal.Close': Modal.Close,
14+
'Modal.Footer': Modal.Footer,
15+
'Modal.FooterItem': Modal.FooterItem,
16+
'Modal.Header': Modal.Header
17+
}}
1218
/>
1319

1420
# API
@@ -37,16 +43,16 @@ import README from '../README.md';
3743
argTypes={{
3844
appendToNode: { control: false },
3945
isVisible: { table: { category: 'Story' } },
40-
hasBody: { name: 'Body', table: { category: 'Story' } },
41-
hasClose: { name: 'Close', table: { category: 'Story' } },
42-
hasFooter: { name: 'Footer', table: { category: 'Story' } },
43-
hasHeader: { name: 'Header', table: { category: 'Story' } },
44-
footerItems: { name: 'FooterItem[]', table: { category: 'Story' } },
45-
body: { name: 'children', table: { category: 'Body' } },
46-
isDanger: { control: 'boolean', table: { category: 'Header' } },
47-
tag: { control: 'text', table: { category: 'Header' } },
48-
header: { name: 'children', table: { category: 'Header' } },
49-
closeAriaLabel: { name: 'aria-label', table: { category: 'Close' } },
46+
hasBody: { name: 'Modal.Body', table: { category: 'Story' } },
47+
hasClose: { name: 'Modal.Close', table: { category: 'Story' } },
48+
hasFooter: { name: 'Modal.Footer', table: { category: 'Story' } },
49+
hasHeader: { name: 'Modal.Header', table: { category: 'Story' } },
50+
footerItems: { name: 'Modal.FooterItem[]', table: { category: 'Story' } },
51+
body: { name: 'children', table: { category: 'Modal.Body' } },
52+
isDanger: { control: 'boolean', table: { category: 'Modal.Header' } },
53+
tag: { control: 'text', table: { category: 'Modal.Header' } },
54+
header: { name: 'children', table: { category: 'Modal.Header' } },
55+
closeAriaLabel: { name: 'aria-label', table: { category: 'Modal.Close' } },
5056
dialogAriaLabel: { name: 'aria-label' }
5157
}}
5258
parameters={{

packages/modals/demo/stories/ModalStory.tsx

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,7 @@
88
import React, { MouseEventHandler } from 'react';
99
import { Story } from '@storybook/react';
1010
import Icon from '@zendeskgarden/svg-icons/src/16/lightning-bolt-stroke.svg';
11-
import {
12-
Body,
13-
Close,
14-
Footer,
15-
FooterItem,
16-
Header,
17-
IModalProps,
18-
Modal
19-
} from '@zendeskgarden/react-modals';
11+
import { IModalProps, Modal } from '@zendeskgarden/react-modals';
2012
import { Button } from '@zendeskgarden/react-buttons';
2113
import { IFooterItem } from './types';
2214

@@ -72,15 +64,15 @@ export const ModalStory: Story<IArgs> = ({
7264
{isVisible && (
7365
<Modal {...args} onClose={onClose} {...ariaProp}>
7466
{hasHeader && (
75-
<Header isDanger={isDanger} tag={tag}>
67+
<Modal.Header isDanger={isDanger} tag={tag}>
7668
{header}
77-
</Header>
69+
</Modal.Header>
7870
)}
79-
{hasBody ? <Body>{body}</Body> : body}
71+
{hasBody ? <Modal.Body>{body}</Modal.Body> : body}
8072
{hasFooter && (
81-
<Footer>
73+
<Modal.Footer>
8274
{footerItems.map(({ text, type }, index) => (
83-
<FooterItem key={index}>
75+
<Modal.FooterItem key={index}>
8476
<Button
8577
isBasic={type === 'basic'}
8678
isPrimary={type === 'primary'}
@@ -89,11 +81,11 @@ export const ModalStory: Story<IArgs> = ({
8981
>
9082
{text}
9183
</Button>
92-
</FooterItem>
84+
</Modal.FooterItem>
9385
))}
94-
</Footer>
86+
</Modal.Footer>
9587
)}
96-
{hasClose && <Close aria-label={closeAriaLabel} />}
88+
{hasClose && <Modal.Close aria-label={closeAriaLabel} />}
9789
</Modal>
9890
)}
9991
</>

packages/modals/src/elements/Body.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { StyledBody } from '../styled';
1010
import { useModalContext } from '../utils/useModalContext';
1111

1212
/**
13+
* @deprecated use `Modal.Body` instead
14+
*
1315
* @extends HTMLAttributes<HTMLDivElement>
1416
*/
1517
export const Body = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>((props, ref) => {

packages/modals/src/elements/Close.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import { useModalContext } from '../utils/useModalContext';
1212
import XStrokeIcon from '@zendeskgarden/svg-icons/src/16/x-stroke.svg';
1313

1414
/**
15+
* @deprecated use `Modal.Close` instead
16+
*
1517
* @extends ButtonHTMLAttributes<HTMLButtonElement>
1618
*/
1719
export const Close = forwardRef<HTMLButtonElement, ButtonHTMLAttributes<HTMLButtonElement>>(

packages/modals/src/elements/Footer.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { StyledFooter } from '../styled';
1010
import { useModalContext } from '../utils/useModalContext';
1111

1212
/**
13+
* @deprecated use `Modal.Footer` instead
14+
*
1315
* @extends HTMLAttributes<HTMLDivElement>
1416
*/
1517
export const Footer = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(

packages/modals/src/elements/FooterItem.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import React from 'react';
99
import { StyledFooterItem } from '../styled';
1010

1111
/**
12+
* @deprecated use `Modal.FooterItem` instead
13+
*
1214
* @extends HTMLAttributes<HTMLSpanElement>
1315
*/
1416
export const FooterItem = React.forwardRef<HTMLSpanElement, React.HTMLAttributes<HTMLSpanElement>>(

packages/modals/src/elements/Header.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import { StyledDangerIcon, StyledHeader } from '../styled';
1212
import { IHeaderProps } from '../types';
1313

1414
/**
15+
* @deprecated use `Modal.Header` instead
16+
*
1517
* @extends HTMLAttributes<HTMLDivElement>
1618
*/
1719
export const Header = forwardRef<HTMLDivElement, IHeaderProps>(

packages/modals/src/elements/Modal.spec.tsx

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,6 @@ import React from 'react';
99
import userEvent from '@testing-library/user-event';
1010
import { render } from 'garden-test-utils';
1111
import { Modal } from './Modal';
12-
import { Body } from './Body';
13-
import { Footer } from './Footer';
14-
import { Header } from './Header';
15-
import { Close } from './Close';
1612
import { IModalProps } from '../types';
1713

1814
describe('Modal', () => {
@@ -33,12 +29,12 @@ describe('Modal', () => {
3329
data-test-id="modal"
3430
backdropProps={{ 'data-test-id': 'backdrop' } as any}
3531
>
36-
{!noHeader && <Header data-test-id="header">Example Header</Header>}
37-
<Body data-test-id="body">Body content</Body>
38-
<Footer data-test-id="footer">
32+
{!noHeader && <Modal.Header data-test-id="header">Example Header</Modal.Header>}
33+
<Modal.Body data-test-id="body">Body content</Modal.Body>
34+
<Modal.Footer data-test-id="footer">
3935
<button onClick={() => onClose}>Confirm</button>
40-
</Footer>
41-
<Close data-test-id="close" />
36+
</Modal.Footer>
37+
<Modal.Close data-test-id="close" />
4238
</Modal>
4339
);
4440

@@ -120,19 +116,19 @@ describe('Modal', () => {
120116
expect(getByTestId('modal')).toHaveAttribute('aria-modal', 'true');
121117
});
122118

123-
it('applies title props to Header element', () => {
119+
it('applies title props to Modal.Header element', () => {
124120
const { getByTestId } = render(<BasicExample />);
125121

126122
expect(getByTestId('header')).toHaveAttribute('id', `${MODAL_ID}__title`);
127123
});
128124

129-
it('applies content props to Body element', () => {
125+
it('applies content props to Modal.Body element', () => {
130126
const { getByTestId } = render(<BasicExample />);
131127

132128
expect(getByTestId('body')).toHaveAttribute('id', `${MODAL_ID}__content`);
133129
});
134130

135-
it('applies close props to Close element', () => {
131+
it('applies close props to Modal.Close element', () => {
136132
const { getByTestId } = render(<BasicExample />);
137133

138134
expect(getByTestId('close')).toHaveAttribute('aria-label', 'Close modal');
@@ -146,7 +142,7 @@ describe('Modal', () => {
146142
expect(onCloseSpy).toHaveBeenCalled();
147143
});
148144

149-
it('is triggered by Close element click', async () => {
145+
it('is triggered by Modal.Close element click', async () => {
150146
const { getByTestId } = render(<BasicExample onClose={onCloseSpy} />);
151147

152148
await user.click(getByTestId('close'));

0 commit comments

Comments
 (0)