1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 01:39:44 +01:00

Adding ModalHeader component and updating PopoverHeader stories (#18311)

This commit is contained in:
George Marshall 2023-04-25 14:27:54 -07:00 committed by GitHub
parent 08f775796c
commit 6a78592af6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 500 additions and 49 deletions

View File

@ -8,7 +8,7 @@ exports[`HeaderBase should render HeaderBase element correctly 1`] = `
title="HeaderBase test"
>
<div
class="box mm-header-base__children box--flex-direction-row box--width-full"
class="box box--flex-direction-row box--width-full"
>
should render HeaderBase element correctly
</div>

View File

@ -70,7 +70,6 @@ export const HeaderBase: React.FC<HeaderBaseProps> = ({
>
{startAccessory && (
<Box
className="mm-header-base__start-accessory"
ref={startAccessoryRef}
style={
children
@ -86,7 +85,6 @@ export const HeaderBase: React.FC<HeaderBaseProps> = ({
)}
{children && (
<Box
className="mm-header-base__children"
width={BLOCK_SIZES.FULL}
style={getTitleStyles}
{...childrenWrapperProps}
@ -98,7 +96,6 @@ export const HeaderBase: React.FC<HeaderBaseProps> = ({
<Box
display={DISPLAY.FLEX}
justifyContent={JustifyContent.flexEnd}
className="mm-header-base__end-accessory"
ref={endAccessoryRef}
style={
children

View File

@ -40,3 +40,4 @@ export { BannerBase } from './banner-base';
export { BannerAlert, BANNER_ALERT_SEVERITIES } from './banner-alert';
export { BannerTip, BannerTipLogoType } from './banner-tip';
export { PopoverHeader } from './popover-header';
export { ModalHeader } from './modal-header';

View File

@ -0,0 +1,123 @@
import { Story, Canvas, ArgsTable } from '@storybook/addon-docs';
import { ModalHeader } from './modal-header';
# ModalHeader
`ModalHeader` handles the title, close button and back button for all Modal components. It is built on top of the [HeaderBase](/docs/components-componentlibrary-headerbase--default-story) component
<Canvas>
<Story id="components-componentlibrary-modalheader--default-story" />
</Canvas>
## Props
The `ModalHeader` accepts all props below as well as all [Box](/docs/ui-components-ui-box-box-stories-js--default-story#props) component props
<ArgsTable of={ModalHeader} />
### Children
The title of the `ModalHeader` component. Passing a `string` will render the content inside of a `Text` component. Passing any other type will render the content as is.
<Canvas>
<Story id="components-componentlibrary-modalheader--children" />
</Canvas>
```jsx
import {
TextVariant,
TextAlign,
DISPLAY,
FLEX_DIRECTION,
AlignItems,
JustifyContent,
} from '../../../helpers/constants/design-system';
import { ModalHeader, AvatarAccount, Text } from '../../component-library';
<ModalHeader {...args} marginBottom={4}>
Children as string
</ModalHeader>
<ModalHeader
{...args}
childrenWrapperProps={{
display: DISPLAY.FLEX,
flexDirection: FLEX_DIRECTION.COLUMN,
alignItems: AlignItems.center,
justifyContent: JustifyContent.center,
}}
>
<AvatarAccount address="0x1234" />
<Text variant={TextVariant.headingSm} textAlign={TextAlign.Center}>
Custom header using multiple components
</Text>
</ModalHeader>
```
### onBack
Use the onClick handler `onBack` prop to render the `ButtonIcon` back button in the startAccessory position.
Use the `backButtonProps` prop to pass additional props to the `ButtonIcon` back button.
<Canvas>
<Story id="components-componentlibrary-modalheader--on-back" />
</Canvas>
```jsx
import { ModalHeader } from '../../component-library';
<ModalHeader onBack={() => console.log('Back button click')}>
OnBack Demo
</ModalHeader>;
```
### onClose
Use the onClick handler `onClose` prop to render the `ButtonIcon` back button in the endAccessory position.
Use the `backButtonProps` prop to pass additional props to the `ButtonIcon` back button.
<Canvas>
<Story id="components-componentlibrary-modalheader--on-close" />
</Canvas>
```jsx
import { ModalHeader } from '../../component-library';
<ModalHeader onClose={() => console.log('Back button click')}>
OnClose Demo
</ModalHeader>;
```
### startAccessory
Use the `startAccessory` prop to render a component in the startAccessory position. This will override the default back `ButtonIcon`.
<Canvas>
<Story id="components-componentlibrary-modalheader--start-accessory" />
</Canvas>
```jsx
import { ModalHeader, Button, BUTTON_SIZES } from '../../component-library';
<ModalHeader startAccessory={<Button size={BUTTON_SIZES.SM}>Demo</Button>}>
StartAccessory
</ModalHeader>;
```
### endAccessory
Use the `endAccessory` prop to render a component in the endAccessory position. This will override the default close `ButtonIcon`.
<Canvas>
<Story id="components-componentlibrary-modalheader--end-accessory" />
</Canvas>
```jsx
import { ModalHeader, Button, BUTTON_SIZES } from '../../component-library';
<ModalHeader endAccessory={<Button size={BUTTON_SIZES.SM}>Demo</Button>}>
EndAccessory
</ModalHeader>;
```

View File

@ -0,0 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ModalHeader should render ModalHeader correctly 1`] = `
<div>
<div
class="box mm-header-base mm-modal-header box--display-flex box--flex-direction-row box--justify-content-space-between"
data-testid="modal-header"
>
<div
class="box box--flex-direction-row box--width-full"
>
<h4
class="box mm-text mm-text--heading-sm mm-text--text-align-center box--flex-direction-row box--color-text-default"
>
Modal header
</h4>
</div>
</div>
</div>
`;

View File

@ -0,0 +1,2 @@
export { ModalHeader } from './modal-header';
export type { ModalHeaderProps } from './modal-header.types';

View File

@ -0,0 +1,86 @@
import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import {
TextVariant,
TextAlign,
DISPLAY,
FLEX_DIRECTION,
AlignItems,
JustifyContent,
} from '../../../helpers/constants/design-system';
import { AvatarAccount, BUTTON_SIZES, Button, Text } from '..';
import { ModalHeader } from './modal-header';
import README from './README.mdx';
export default {
title: 'Components/ComponentLibrary/ModalHeader',
component: ModalHeader,
parameters: {
docs: {
page: README,
},
},
argTypes: {
children: { control: 'text' },
className: { control: 'text' },
onBack: { action: 'onBack' },
onClose: { action: 'onClose' },
},
args: {
children: 'ModalHeader',
},
} as ComponentMeta<typeof ModalHeader>;
const Template: ComponentStory<typeof ModalHeader> = (args) => {
return <ModalHeader {...args} />;
};
export const DefaultStory = Template.bind({});
DefaultStory.storyName = 'Default';
export const Children: ComponentStory<typeof ModalHeader> = (args) => (
<>
<ModalHeader {...args} marginBottom={4}>
Children as string
</ModalHeader>
<ModalHeader
{...args}
childrenWrapperProps={{
display: DISPLAY.FLEX,
flexDirection: FLEX_DIRECTION.COLUMN,
alignItems: AlignItems.center,
justifyContent: JustifyContent.center,
}}
>
<AvatarAccount address="0x1234" />
<Text variant={TextVariant.headingSm} textAlign={TextAlign.Center}>
Custom header using multiple components
</Text>
</ModalHeader>
</>
);
export const OnBack = Template.bind({});
OnBack.args = {
children: 'OnBack demo',
};
export const OnClose = Template.bind({});
OnClose.args = {
children: 'OnClose demo',
};
export const StartAccessory = Template.bind({});
StartAccessory.args = {
children: 'StartAccessory demo',
startAccessory: <Button size={BUTTON_SIZES.SM}>Demo</Button>,
};
export const EndAccessory = Template.bind({});
EndAccessory.args = {
children: 'EndAccessory demo',
endAccessory: <Button size={BUTTON_SIZES.SM}>Demo</Button>,
};

View File

@ -0,0 +1,67 @@
/* eslint-disable jest/require-top-level-describe */
import { render, fireEvent } from '@testing-library/react';
import React from 'react';
import { ModalHeader } from './modal-header';
describe('ModalHeader', () => {
it('should render ModalHeader correctly', () => {
const { getByTestId, container } = render(
<ModalHeader data-testid="modal-header">Modal header</ModalHeader>,
);
expect(getByTestId('modal-header')).toHaveClass('mm-modal-header');
expect(container).toMatchSnapshot();
});
it('should render modal header children as a string', () => {
const { getByText } = render(
<ModalHeader data-testid="modal-header">Modal header test</ModalHeader>,
);
expect(getByText('Modal header test')).toBeDefined();
});
it('should render modal header children as a node', () => {
const { getByText, getByTestId } = render(
<ModalHeader data-testid="modal-header">
<div data-testid="div">Modal header test</div>
</ModalHeader>,
);
expect(getByText('Modal header test')).toBeDefined();
expect(getByTestId('div')).toBeDefined();
});
it('should render modal header back button', () => {
const onBackTest = jest.fn();
const { getByTestId } = render(
<ModalHeader
data-testid="modal-header"
onBack={onBackTest}
backButtonProps={{ 'data-testid': 'back' }}
>
ModalHeader
</ModalHeader>,
);
const backButton = getByTestId('back');
fireEvent.click(backButton);
expect(onBackTest).toHaveBeenCalled();
});
it('should render modal header close button', () => {
const onCloseTest = jest.fn();
const { getByTestId } = render(
<ModalHeader
data-testid="modal-header"
onClose={onCloseTest}
closeButtonProps={{ 'data-testid': 'close' }}
>
Modal header
</ModalHeader>,
);
const closeButton = getByTestId('close');
fireEvent.click(closeButton);
expect(onCloseTest).toHaveBeenCalled();
});
});

View File

@ -0,0 +1,61 @@
import React from 'react';
import classnames from 'classnames';
import { HeaderBase, Text, ButtonIcon, ButtonIconSize, IconName } from '..';
import {
TextVariant,
TextAlign,
} from '../../../helpers/constants/design-system';
import { useI18nContext } from '../../../hooks/useI18nContext';
import { ModalHeaderProps } from '.';
export const ModalHeader: React.FC<ModalHeaderProps> = ({
children,
className = '',
startAccessory,
endAccessory,
onClose,
closeButtonProps,
onBack,
backButtonProps,
...props
}) => {
const t = useI18nContext();
return (
<HeaderBase
className={classnames('mm-modal-header', className)}
startAccessory={
startAccessory ||
(onBack && (
<ButtonIcon
iconName={IconName.ArrowLeft}
ariaLabel={t('back')}
size={ButtonIconSize.Sm}
onClick={onBack}
{...backButtonProps}
/>
))
}
endAccessory={
endAccessory ||
(onClose && (
<ButtonIcon
iconName={IconName.Close}
ariaLabel={t('close')}
size={ButtonIconSize.Sm}
onClick={onClose}
{...closeButtonProps}
/>
))
}
{...props}
>
{typeof children === 'string' ? (
<Text variant={TextVariant.headingSm} textAlign={TextAlign.Center}>
{children}
</Text>
) : (
children
)}
</HeaderBase>
);
};

View File

@ -0,0 +1,42 @@
import React from 'react';
import type { ButtonIconProps } from '../button-icon/button-icon.types';
import type { HeaderBaseProps } from '../header-base';
export interface ModalHeaderProps extends HeaderBaseProps {
/**
* The contents within the ModalHeader positioned middle (popular for title use case)
*/
children?: React.ReactNode;
/**
* Additional classNames to be added to the ModalHeader component
*/
className?: string;
/**
* The onClick handler for the back `ButtonIcon`
* When passed this will allow for the back `ButtonIcon` to show
*/
onBack?: () => void;
/**
* The props to pass to the back `ButtonIcon`
*/
backButtonProps?: ButtonIconProps;
/**
* The start (left) content area of ModalHeader
* Default to have the back `ButtonIcon` when `onBack` is passed, but passing a `startAccessory` will override this
*/
startAccessory?: React.ReactNode;
/**
* The onClick handler for the close `ButtonIcon`
* When passed this will allow for the close `ButtonIcon` to show
*/
onClose?: () => void;
/**
* The props to pass to the close `ButtonIcon`
*/
closeButtonProps?: ButtonIconProps;
/**
* The end (right) content area of ModalHeader
* Default to have the close `ButtonIcon` when `onClose` is passed, but passing a `endAccessory` will override this
*/
endAccessory?: React.ReactNode;
}

View File

@ -3,7 +3,7 @@ import { PopoverHeader } from './popover-header';
# PopoverHeader
PopoverHeader is built on top of [HeaderBase](/docs/components-componentlibrary-headerbase--default-story) component with the most common use case of a back button in the startAccessory position, title, and close button in the endAccessory position.
`PopoverHeader` handles the title, close button and back button for all `Popover` components. It is built on top of the [HeaderBase](/docs/components-componentlibrary-headerbase--default-story) component
<Canvas>
<Story id="components-componentlibrary-popoverheader--default-story" />
@ -17,16 +17,41 @@ The `PopoverHeader` accepts all props below as well as all [Box](/docs/ui-compon
### Children
Wrapping string content in the `PopoverHeader` component will be rendered in the center of the header with the default title `Text` component.
The title of the `PopoverHeader` component. Passing a `string` will render the content inside of a `Text` component. Passing any other type will render the content as is.
<Canvas>
<Story id="components-componentlibrary-popoverheader--children" />
</Canvas>
```jsx
import { PopoverHeader } from '../../component-library';
import {
TextVariant,
TextAlign,
DISPLAY,
FLEX_DIRECTION,
AlignItems,
JustifyContent,
} from '../../../helpers/constants/design-system';
<PopoverHeader>Title is sentence case no period</PopoverHeader>;
import { PopoverHeader, AvatarAccount, Text } from '../../component-library';
<PopoverHeader {...args} marginBottom={4}>
Children as string
</PopoverHeader>
<PopoverHeader
{...args}
childrenWrapperProps={{
display: DISPLAY.FLEX,
flexDirection: FLEX_DIRECTION.COLUMN,
alignItems: AlignItems.center,
justifyContent: JustifyContent.center,
}}
>
<AvatarAccount address="0x1234" />
<Text variant={TextVariant.headingSm} textAlign={TextAlign.Center}>
Custom header using multiple components
</Text>
</PopoverHeader>
```
### onBack

View File

@ -7,12 +7,12 @@ exports[`PopoverHeader should render PopoverHeader correctly 1`] = `
data-testid="popover-header"
>
<div
class="box mm-header-base__children box--flex-direction-row box--width-full"
class="box box--flex-direction-row box--width-full"
>
<h4
class="box mm-text mm-text--heading-sm mm-text--text-align-center box--flex-direction-row box--color-text-default"
>
Popover Header
PopoverHeader
</h4>
</div>
</div>

View File

@ -1,6 +1,17 @@
import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { BUTTON_SIZES, Button } from '..';
import {
TextVariant,
TextAlign,
DISPLAY,
FLEX_DIRECTION,
AlignItems,
JustifyContent,
} from '../../../helpers/constants/design-system';
import { AvatarAccount, BUTTON_SIZES, Button, Text } from '..';
import { PopoverHeader } from './popover-header';
import README from './README.mdx';
@ -24,42 +35,52 @@ export default {
} as ComponentMeta<typeof PopoverHeader>;
const Template: ComponentStory<typeof PopoverHeader> = (args) => {
return <PopoverHeader {...args}>PopoverHeader</PopoverHeader>;
return <PopoverHeader {...args} />;
};
export const DefaultStory = Template.bind({});
DefaultStory.storyName = 'Default';
export const Children: ComponentStory<typeof PopoverHeader> = (args) => (
<PopoverHeader {...args} />
<>
<PopoverHeader {...args} marginBottom={4}>
Children as string
</PopoverHeader>
<PopoverHeader
{...args}
childrenWrapperProps={{
display: DISPLAY.FLEX,
flexDirection: FLEX_DIRECTION.COLUMN,
alignItems: AlignItems.center,
justifyContent: JustifyContent.center,
}}
>
<AvatarAccount address="0x1234" />
<Text variant={TextVariant.headingSm} textAlign={TextAlign.Center}>
Custom header using multiple components
</Text>
</PopoverHeader>
</>
);
Children.args = {
children: 'PopoverHeader Title',
export const OnBack = Template.bind({});
OnBack.args = {
children: 'OnBack demo',
};
export const OnBack: ComponentStory<typeof PopoverHeader> = (args) => (
<PopoverHeader {...args}>OnBack Demo</PopoverHeader>
);
export const OnClose = Template.bind({});
OnClose.args = {
children: 'OnClose demo',
};
export const OnClose: ComponentStory<typeof PopoverHeader> = (args) => (
<PopoverHeader {...args}>OnClose Demo</PopoverHeader>
);
export const StartAccessory = Template.bind({});
StartAccessory.args = {
children: 'StartAccessory demo',
startAccessory: <Button size={BUTTON_SIZES.SM}>Demo</Button>,
};
export const StartAccessory: ComponentStory<typeof PopoverHeader> = (args) => (
<PopoverHeader
startAccessory={<Button size={BUTTON_SIZES.SM}>Demo</Button>}
{...args}
>
StartAccessory
</PopoverHeader>
);
export const EndAccessory: ComponentStory<typeof PopoverHeader> = (args) => (
<PopoverHeader
endAccessory={<Button size={BUTTON_SIZES.SM}>Demo</Button>}
{...args}
>
EndAccessory
</PopoverHeader>
);
export const EndAccessory = Template.bind({});
EndAccessory.args = {
children: 'EndAccessory demo',
endAccessory: <Button size={BUTTON_SIZES.SM}>Demo</Button>,
};

View File

@ -6,32 +6,39 @@ import { PopoverHeader } from './popover-header';
describe('PopoverHeader', () => {
it('should render PopoverHeader correctly', () => {
const { getByTestId, container } = render(
<PopoverHeader data-testid="popover-header">
Popover Header
</PopoverHeader>,
<PopoverHeader data-testid="popover-header">PopoverHeader</PopoverHeader>,
);
expect(getByTestId('popover-header')).toHaveClass('mm-popover-header');
expect(container).toMatchSnapshot();
});
it('should render popover header title', () => {
it('should render popover header children as a string', () => {
const { getByText } = render(
<PopoverHeader data-testid="popover-header">
Popover Header Test
PopoverHeader test
</PopoverHeader>,
);
expect(getByText('Popover Header Test')).toBeDefined();
expect(getByText('PopoverHeader test')).toBeDefined();
});
it('should render popover header children as a node', () => {
const { getByText, getByTestId } = render(
<PopoverHeader data-testid="popover-header">
<div data-testid="div">PopoverHeader test</div>
</PopoverHeader>,
);
expect(getByText('PopoverHeader test')).toBeDefined();
expect(getByTestId('div')).toBeDefined();
});
it('should render popover header back button', () => {
const onBackTest = jest.fn();
const { getByTestId } = render(
<PopoverHeader
data-testid="popover"
onBack={onBackTest}
backButtonProps={{ 'data-testid': 'back' }}
>
Popover
PopoverHeader
</PopoverHeader>,
);
@ -45,11 +52,10 @@ describe('PopoverHeader', () => {
const onCloseTest = jest.fn();
const { getByTestId } = render(
<PopoverHeader
data-testid="popover"
onClose={onCloseTest}
closeButtonProps={{ 'data-testid': 'close' }}
>
Popover
PopoverHeader
</PopoverHeader>,
);

View File

@ -8,7 +8,7 @@ export interface PopoverHeaderProps extends HeaderBaseProps {
*/
children?: React.ReactNode;
/**
* Additional classNames to be added to the Popover component
* Additional classNames to be added to the PopoverHeader component
*/
className?: string;
/**