From 6a78592af64996675d3504c4ad7740006f1e9ced Mon Sep 17 00:00:00 2001 From: George Marshall Date: Tue, 25 Apr 2023 14:27:54 -0700 Subject: [PATCH] Adding ModalHeader component and updating PopoverHeader stories (#18311) --- .../__snapshots__/header-base.test.tsx.snap | 2 +- .../header-base/header-base.tsx | 3 - ui/components/component-library/index.js | 1 + .../component-library/modal-header/README.mdx | 123 ++++++++++++++++++ .../__snapshots__/modal-header.test.tsx.snap | 20 +++ .../component-library/modal-header/index.ts | 2 + .../modal-header/modal-header.stories.tsx | 86 ++++++++++++ .../modal-header/modal-header.test.tsx | 67 ++++++++++ .../modal-header/modal-header.tsx | 61 +++++++++ .../modal-header/modal-header.types.ts | 42 ++++++ .../popover-header/README.mdx | 33 ++++- .../popover-header.test.tsx.snap | 4 +- .../popover-header/popover-header.stories.tsx | 77 +++++++---- .../popover-header/popover-header.test.tsx | 26 ++-- .../popover-header/popover-header.types.ts | 2 +- 15 files changed, 500 insertions(+), 49 deletions(-) create mode 100644 ui/components/component-library/modal-header/README.mdx create mode 100644 ui/components/component-library/modal-header/__snapshots__/modal-header.test.tsx.snap create mode 100644 ui/components/component-library/modal-header/index.ts create mode 100644 ui/components/component-library/modal-header/modal-header.stories.tsx create mode 100644 ui/components/component-library/modal-header/modal-header.test.tsx create mode 100644 ui/components/component-library/modal-header/modal-header.tsx create mode 100644 ui/components/component-library/modal-header/modal-header.types.ts diff --git a/ui/components/component-library/header-base/__snapshots__/header-base.test.tsx.snap b/ui/components/component-library/header-base/__snapshots__/header-base.test.tsx.snap index 6253be824..c6217297f 100644 --- a/ui/components/component-library/header-base/__snapshots__/header-base.test.tsx.snap +++ b/ui/components/component-library/header-base/__snapshots__/header-base.test.tsx.snap @@ -8,7 +8,7 @@ exports[`HeaderBase should render HeaderBase element correctly 1`] = ` title="HeaderBase test" >
should render HeaderBase element correctly
diff --git a/ui/components/component-library/header-base/header-base.tsx b/ui/components/component-library/header-base/header-base.tsx index 220a596d4..8f73c7b17 100644 --- a/ui/components/component-library/header-base/header-base.tsx +++ b/ui/components/component-library/header-base/header-base.tsx @@ -70,7 +70,6 @@ export const HeaderBase: React.FC = ({ > {startAccessory && ( = ({ )} {children && ( = ({ + + + +## 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 + + + +### 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. + + + + + +```jsx +import { + TextVariant, + TextAlign, + DISPLAY, + FLEX_DIRECTION, + AlignItems, + JustifyContent, +} from '../../../helpers/constants/design-system'; + +import { ModalHeader, AvatarAccount, Text } from '../../component-library'; + + + Children as string + + + + + Custom header using multiple components + + +``` + +### 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. + + + + + +```jsx +import { ModalHeader } from '../../component-library'; + + console.log('Back button click')}> + OnBack Demo +; +``` + +### 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. + + + + + +```jsx +import { ModalHeader } from '../../component-library'; + + console.log('Back button click')}> + OnClose Demo +; +``` + +### startAccessory + +Use the `startAccessory` prop to render a component in the startAccessory position. This will override the default back `ButtonIcon`. + + + + + +```jsx +import { ModalHeader, Button, BUTTON_SIZES } from '../../component-library'; + +Demo}> + StartAccessory +; +``` + +### endAccessory + +Use the `endAccessory` prop to render a component in the endAccessory position. This will override the default close `ButtonIcon`. + + + + + +```jsx +import { ModalHeader, Button, BUTTON_SIZES } from '../../component-library'; + +Demo}> + EndAccessory +; +``` diff --git a/ui/components/component-library/modal-header/__snapshots__/modal-header.test.tsx.snap b/ui/components/component-library/modal-header/__snapshots__/modal-header.test.tsx.snap new file mode 100644 index 000000000..d5c717bcf --- /dev/null +++ b/ui/components/component-library/modal-header/__snapshots__/modal-header.test.tsx.snap @@ -0,0 +1,20 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ModalHeader should render ModalHeader correctly 1`] = ` +
+
+
+

+ Modal header +

+
+
+
+`; diff --git a/ui/components/component-library/modal-header/index.ts b/ui/components/component-library/modal-header/index.ts new file mode 100644 index 000000000..54a9313c0 --- /dev/null +++ b/ui/components/component-library/modal-header/index.ts @@ -0,0 +1,2 @@ +export { ModalHeader } from './modal-header'; +export type { ModalHeaderProps } from './modal-header.types'; diff --git a/ui/components/component-library/modal-header/modal-header.stories.tsx b/ui/components/component-library/modal-header/modal-header.stories.tsx new file mode 100644 index 000000000..525379dc9 --- /dev/null +++ b/ui/components/component-library/modal-header/modal-header.stories.tsx @@ -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; + +const Template: ComponentStory = (args) => { + return ; +}; + +export const DefaultStory = Template.bind({}); +DefaultStory.storyName = 'Default'; + +export const Children: ComponentStory = (args) => ( + <> + + Children as string + + + + + Custom header using multiple components + + + +); + +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: , +}; + +export const EndAccessory = Template.bind({}); +EndAccessory.args = { + children: 'EndAccessory demo', + endAccessory: , +}; diff --git a/ui/components/component-library/modal-header/modal-header.test.tsx b/ui/components/component-library/modal-header/modal-header.test.tsx new file mode 100644 index 000000000..00fb648b9 --- /dev/null +++ b/ui/components/component-library/modal-header/modal-header.test.tsx @@ -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( + Modal header, + ); + expect(getByTestId('modal-header')).toHaveClass('mm-modal-header'); + expect(container).toMatchSnapshot(); + }); + + it('should render modal header children as a string', () => { + const { getByText } = render( + Modal header test, + ); + expect(getByText('Modal header test')).toBeDefined(); + }); + + it('should render modal header children as a node', () => { + const { getByText, getByTestId } = render( + +
Modal header test
+
, + ); + 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 + , + ); + + const backButton = getByTestId('back'); + fireEvent.click(backButton); + + expect(onBackTest).toHaveBeenCalled(); + }); + + it('should render modal header close button', () => { + const onCloseTest = jest.fn(); + const { getByTestId } = render( + + Modal header + , + ); + + const closeButton = getByTestId('close'); + fireEvent.click(closeButton); + + expect(onCloseTest).toHaveBeenCalled(); + }); +}); diff --git a/ui/components/component-library/modal-header/modal-header.tsx b/ui/components/component-library/modal-header/modal-header.tsx new file mode 100644 index 000000000..9cd4ca5d3 --- /dev/null +++ b/ui/components/component-library/modal-header/modal-header.tsx @@ -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 = ({ + children, + className = '', + startAccessory, + endAccessory, + onClose, + closeButtonProps, + onBack, + backButtonProps, + ...props +}) => { + const t = useI18nContext(); + return ( + + )) + } + endAccessory={ + endAccessory || + (onClose && ( + + )) + } + {...props} + > + {typeof children === 'string' ? ( + + {children} + + ) : ( + children + )} + + ); +}; diff --git a/ui/components/component-library/modal-header/modal-header.types.ts b/ui/components/component-library/modal-header/modal-header.types.ts new file mode 100644 index 000000000..47bb752a0 --- /dev/null +++ b/ui/components/component-library/modal-header/modal-header.types.ts @@ -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; +} diff --git a/ui/components/component-library/popover-header/README.mdx b/ui/components/component-library/popover-header/README.mdx index 6910ec10c..0680c2b6a 100644 --- a/ui/components/component-library/popover-header/README.mdx +++ b/ui/components/component-library/popover-header/README.mdx @@ -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 @@ -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. ```jsx -import { PopoverHeader } from '../../component-library'; +import { + TextVariant, + TextAlign, + DISPLAY, + FLEX_DIRECTION, + AlignItems, + JustifyContent, +} from '../../../helpers/constants/design-system'; -Title is sentence case no period; +import { PopoverHeader, AvatarAccount, Text } from '../../component-library'; + + + Children as string + + + + + Custom header using multiple components + + ``` ### onBack diff --git a/ui/components/component-library/popover-header/__snapshots__/popover-header.test.tsx.snap b/ui/components/component-library/popover-header/__snapshots__/popover-header.test.tsx.snap index ff2a28597..d6bd840f2 100644 --- a/ui/components/component-library/popover-header/__snapshots__/popover-header.test.tsx.snap +++ b/ui/components/component-library/popover-header/__snapshots__/popover-header.test.tsx.snap @@ -7,12 +7,12 @@ exports[`PopoverHeader should render PopoverHeader correctly 1`] = ` data-testid="popover-header" >

- Popover Header + PopoverHeader

diff --git a/ui/components/component-library/popover-header/popover-header.stories.tsx b/ui/components/component-library/popover-header/popover-header.stories.tsx index a5f5d175d..92c188d81 100644 --- a/ui/components/component-library/popover-header/popover-header.stories.tsx +++ b/ui/components/component-library/popover-header/popover-header.stories.tsx @@ -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; const Template: ComponentStory = (args) => { - return PopoverHeader; + return ; }; export const DefaultStory = Template.bind({}); DefaultStory.storyName = 'Default'; export const Children: ComponentStory = (args) => ( - + <> + + Children as string + + + + + Custom header using multiple components + + + ); -Children.args = { - children: 'PopoverHeader Title', +export const OnBack = Template.bind({}); +OnBack.args = { + children: 'OnBack demo', }; -export const OnBack: ComponentStory = (args) => ( - OnBack Demo -); +export const OnClose = Template.bind({}); +OnClose.args = { + children: 'OnClose demo', +}; -export const OnClose: ComponentStory = (args) => ( - OnClose Demo -); +export const StartAccessory = Template.bind({}); +StartAccessory.args = { + children: 'StartAccessory demo', + startAccessory: , +}; -export const StartAccessory: ComponentStory = (args) => ( - Demo} - {...args} - > - StartAccessory - -); - -export const EndAccessory: ComponentStory = (args) => ( - Demo} - {...args} - > - EndAccessory - -); +export const EndAccessory = Template.bind({}); +EndAccessory.args = { + children: 'EndAccessory demo', + endAccessory: , +}; diff --git a/ui/components/component-library/popover-header/popover-header.test.tsx b/ui/components/component-library/popover-header/popover-header.test.tsx index ec5e4e203..8918ed6f0 100644 --- a/ui/components/component-library/popover-header/popover-header.test.tsx +++ b/ui/components/component-library/popover-header/popover-header.test.tsx @@ -6,32 +6,39 @@ import { PopoverHeader } from './popover-header'; describe('PopoverHeader', () => { it('should render PopoverHeader correctly', () => { const { getByTestId, container } = render( - - Popover Header - , + 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( - Popover Header Test + PopoverHeader test , ); - 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 test
+
, + ); + expect(getByText('PopoverHeader test')).toBeDefined(); + expect(getByTestId('div')).toBeDefined(); }); it('should render popover header back button', () => { const onBackTest = jest.fn(); const { getByTestId } = render( - Popover + PopoverHeader , ); @@ -45,11 +52,10 @@ describe('PopoverHeader', () => { const onCloseTest = jest.fn(); const { getByTestId } = render( - Popover + PopoverHeader , ); diff --git a/ui/components/component-library/popover-header/popover-header.types.ts b/ui/components/component-library/popover-header/popover-header.types.ts index 66f8c7379..a965dbd54 100644 --- a/ui/components/component-library/popover-header/popover-header.types.ts +++ b/ui/components/component-library/popover-header/popover-header.types.ts @@ -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; /**