Skip to content

Commit

Permalink
feat(Drawer): new component (#1306)
Browse files Browse the repository at this point in the history
https://jira.tid.es/browse/WEB-2113

---------

Co-authored-by: Pedro Ladaria <pedro.jose.ladaria.linden@telefonica.com>
  • Loading branch information
pladaria and Pedro Ladaria authored Jan 9, 2025
1 parent 73e33eb commit 2543c67
Show file tree
Hide file tree
Showing 17 changed files with 706 additions and 16 deletions.
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: 300, 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'],
},
]);
});
Loading

1 comment on commit 2543c67

@github-actions
Copy link

Choose a reason for hiding this comment

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

Deploy preview for mistica-web ready!

✅ Preview
https://mistica-i8sbklyj1-flows-projects-65bb050e.vercel.app

Built with commit 2543c67.
This pull request is being automatically deployed with vercel-action

Please sign in to comment.