Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Drawer): new component #1306

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
30 changes: 30 additions & 0 deletions playroom/snippets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,35 @@ const menuSnippet = {
group: 'Menu',
};

const drawerSnippet = {
name: 'Drawer',
group: 'Drawer',
code: `
<ButtonPrimary onPress={() => setState("openDrawer", true)}>
Open Drawer
</ButtonPrimary>
{getState("openDrawer", false) && (
<Drawer
title="Title"
subtitle="Subtitle"
description="Description"
onClose={() => setState("openDrawer", false)}
button={{ text: "Primary", onPress: () => {} }}
secondaryButton={{ text: "Secondary", onPress: () => {} }}
buttonLink={{ text: "Link", onPress: () => {} }}
onDismiss={() => {}}
>
<Stack space={16}>
<Placeholder height={300} />
<Placeholder height={300} />
<Placeholder height={300} />
<Placeholder height={300} />
</Stack>
</Drawer>
)}
`,
};

const accordionSnippets: Array<Snippet> = [
{
group: 'Accordion',
Expand Down Expand Up @@ -4337,4 +4366,5 @@ export default [
...ProgressBlockSnippets,
...loadingScreenSnippets,
...tableSnippets,
drawerSnippet,
].sort((s1, s2) => s1.group.localeCompare(s2.group)) as Array<Snippet>;
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 36 additions & 0 deletions src/__screenshot_tests__/drawer-screenshot-test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {openStoryPage, screen} from '../test-utils';

import type {Device} from '../test-utils';

test.each`
device | withActions | dismissible | contentLength
${'MOBILE_IOS'} | ${true} | ${true} | ${1}
${'MOBILE_IOS'} | ${true} | ${true} | ${5}
${'MOBILE_IOS'} | ${true} | ${false} | ${1}
${'DESKTOP'} | ${true} | ${true} | ${1}
${'TABLET'} | ${true} | ${true} | ${1}
${'MOBILE_IOS'} | ${false} | ${true} | ${1}
${'DESKTOP'} | ${false} | ${true} | ${1}
${'TABLET'} | ${false} | ${true} | ${1}
`(
'Drawer $device actions:$withActions dismissible:$dismissible contentLength:$contentLength',
async ({device, withActions, dismissible, contentLength}) => {
const page = await openStoryPage({
id: 'components-modals-drawer--default',
device: device as Device,
args: {
showButton: withActions,
showSecondaryButton: withActions,
showButtonLink: withActions,
onDismissHandler: dismissible,
contentLength,
},
});

const button = await screen.findByRole('button', {name: 'Open Drawer'});
await button.click();

const image = await page.screenshot();
expect(image).toMatchImageSnapshot();
}
);
94 changes: 94 additions & 0 deletions src/__stories__/drawer-story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import * as React from 'react';
import Drawer from '../drawer';
import {Placeholder} from '../placeholder';
import Stack from '../stack';
import {ButtonPrimary} from '../button';
import {Text3} from '../text';

export default {
title: 'Components/Modals/Drawer',
component: Drawer,
argTypes: {
width: {
control: {type: 'range', min: 0, max: 1000, step: 1},
},
},
};

type Args = {
title: string;
subtitle: string;
description: string;
contentLength: number;
onDismissHandler: boolean;
showButton: boolean;
showSecondaryButton: boolean;
showButtonLink: boolean;
width: number;
};

export const Default = ({
title,
subtitle,
description,
contentLength,
onDismissHandler,
showButton,
showSecondaryButton,
showButtonLink,
width,
}: Args): JSX.Element => {
const [isOpen, setIsOpen] = React.useState(false);
const [result, setResult] = React.useState('');
const content = (
<Stack space={16}>
{Array.from({length: contentLength}).map((_, index) => (
<Placeholder key={index} height={200} />
))}
</Stack>
);

return (
<>
<Stack space={16}>
<ButtonPrimary onPress={() => setIsOpen(true)}>Open Drawer</ButtonPrimary>
<Text3 regular>
Result: <span data-testid="result">{result}</span>
</Text3>
</Stack>
{isOpen && (
<Drawer
width={width}
title={title}
subtitle={subtitle}
description={description}
onClose={() => setIsOpen(false)}
onDismiss={onDismissHandler ? () => setResult('dismiss') : undefined}
button={showButton ? {text: 'Primary', onPress: () => setResult('primary')} : undefined}
secondaryButton={
showSecondaryButton
? {text: 'Secondary', onPress: () => setResult('secondary')}
: undefined
}
buttonLink={showButtonLink ? {text: 'Link', onPress: () => setResult('link')} : undefined}
>
{content}
</Drawer>
)}
</>
);
};

Default.storyName = 'Drawer';

Default.args = {
title: 'Title',
subtitle: 'Subtitle',
description: 'Description',
contentLength: 2,
onDismissHandler: true,
showButton: true,
showSecondaryButton: true,
showButtonLink: true,
width: 0,
};
115 changes: 115 additions & 0 deletions src/__tests__/drawer-test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import * as React from 'react';
import {makeTheme} from './test-utils';
import {render, screen, waitFor} from '@testing-library/react';
import ThemeContextProvider from '../theme-context-provider';
import Drawer from '../drawer';
import userEvent from '@testing-library/user-event';

const DrawerTest = ({
onDismiss,
onButtonPrimaryPress,
onButtonSecondaryPress,
onButtonLinkPress,
}: {
onDismiss?: () => void;
onButtonPrimaryPress?: () => void;
onButtonSecondaryPress?: () => void;
onButtonLinkPress?: () => void;
}) => {
const [isOpen, setIsOpen] = React.useState(false);
return (
<ThemeContextProvider theme={makeTheme()}>
<button onClick={() => setIsOpen(true)}>open</button>
{isOpen && (
<Drawer
dismissLabel="CustomDismissLabel"
onDismiss={onDismiss}
onClose={() => setIsOpen(false)}
button={{text: 'Primary', onPress: onButtonPrimaryPress}}
secondaryButton={{text: 'Secondary', onPress: onButtonSecondaryPress}}
buttonLink={{text: 'Link', onPress: onButtonLinkPress}}
/>
)}
</ThemeContextProvider>
);
};

test.each(['esc', 'overlay', 'x'])('Drawer dismiss: %s', async (dismissMethod: string) => {
const onDismissSpy = jest.fn();
render(<DrawerTest onDismiss={onDismissSpy} />);

const openButton = screen.getByRole('button', {name: 'open'});
await userEvent.click(openButton);

const drawer = await screen.findByRole('dialog');

switch (dismissMethod) {
case 'esc':
await userEvent.keyboard('{Escape}');
break;
case 'overlay':
await userEvent.click(screen.getByTestId('drawerOverlay'));
break;
case 'x':
await userEvent.click(screen.getByRole('button', {name: 'CustomDismissLabel'}));
break;
default:
throw new Error('unexpected dismiss method');
}

await waitFor(() => {
expect(onDismissSpy).toHaveBeenCalledTimes(1);
expect(drawer).not.toBeInTheDocument();
});
});

test.each(['primary', 'secondary', 'link'])('Drawer close: %s', async (closeMethod: string) => {
const onButtonPrimaryPress = jest.fn();
const onButtonSecondaryPress = jest.fn();
const onButtonLinkPress = jest.fn();

render(
<DrawerTest
onButtonPrimaryPress={onButtonPrimaryPress}
onButtonSecondaryPress={onButtonSecondaryPress}
onButtonLinkPress={onButtonLinkPress}
/>
);

const openButton = screen.getByRole('button', {name: 'open'});
await userEvent.click(openButton);

const drawer = await screen.findByRole('dialog');

switch (closeMethod) {
case 'primary':
await userEvent.click(screen.getByRole('button', {name: 'Primary'}));
break;
case 'secondary':
await userEvent.click(screen.getByRole('button', {name: 'Secondary'}));
break;
case 'link':
await userEvent.click(screen.getByRole('button', {name: 'Link'}));
break;
default:
throw new Error('unexpected dismiss method');
}

await waitFor(() => {
expect(drawer).not.toBeInTheDocument();
});

switch (closeMethod) {
case 'primary':
expect(onButtonPrimaryPress).toHaveBeenCalledTimes(1);
break;
case 'secondary':
expect(onButtonSecondaryPress).toHaveBeenCalledTimes(1);
break;
case 'link':
expect(onButtonLinkPress).toHaveBeenCalledTimes(1);
break;
default:
throw new Error('unexpected dismiss method');
}
});
10 changes: 10 additions & 0 deletions src/__tests__/testid-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
CoverHero,
Header,
MainSectionHeader,
Drawer,
} from '..';
import {makeTheme} from './test-utils';

Expand Down Expand Up @@ -288,3 +289,12 @@ test('Meter test ids', () => {
},
]);
});

test('Drawer test ids', () => {
checkTestIds(<Drawer title="Title" subtitle="Subtitle" description="Description" onClose={() => {}} />, [
{
componentName: 'Drawer',
internalTestIds: ['title', 'subtitle', 'description'],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are these defined in the spec?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not in spec, but I was assuming the same testids for these elements that we were using in cards, etc

},
]);
});
Loading
Loading