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 footer standard props and some improvements #532

Merged
merged 14 commits into from
Jul 12, 2024
1 change: 1 addition & 0 deletions src/components/Button/Button.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
align-items: center;
justify-content: center;
gap: var(--spacing-gap-xs, 4px);
box-shadow: none;
}

/* TODO: add 12px padding to theme */
Expand Down
24 changes: 17 additions & 7 deletions src/components/Drawer/Drawer.Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,24 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { ReactElement, ReactNode } from 'react'
import React, { ReactElement } from 'react'

export type DrawerFooter = ReactNode
import { DrawerFooter, FooterProps } from './Drawer.types'
import styles from './Drawer.module.css'

export type FooterProps = {
footer: DrawerFooter,
}
export const Footer = ({ footer }: FooterProps): ReactElement | null => {
if (React.isValidElement(footer)) {
return footer
}

export const Footer = ({ footer }: FooterProps): ReactElement => {
return <footer>{footer}</footer>
const drawerFooter = footer as DrawerFooter
const { buttons, extra } = drawerFooter
return <footer className={styles.footer}>
{(buttons || extra) && <>
<div className={styles.extra}>{extra}</div>
<div className={styles.footerButtons}>
{buttons}
</div>
</>}
</footer>
}
9 changes: 2 additions & 7 deletions src/components/Drawer/Drawer.Title.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,10 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { ReactElement, ReactNode, useMemo } from 'react'
import { ReactElement, useMemo } from 'react'

import { H4 } from '../Typography/HX/H4'

export type DrawerTitle = ReactNode

export type TitleProps = {
title: DrawerTitle,
}
import { TitleProps } from './Drawer.types'

export const Title = ({ title }: TitleProps): ReactElement => {
const ellipsis = useMemo(() => ({ rows: 1, tooltip: title }), [title])
Expand Down
37 changes: 12 additions & 25 deletions src/components/Drawer/Drawer.mocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@
import { ReactElement } from 'react'

import { Button } from '../Button'
import { Drawer } from './Drawer'
import { DrawerProps } from './Drawer.props'
import { useDrawer } from '../../hooks/useDrawer'
import { Hierarchy } from '../Button/Button.types'

export const DrawerLipsum = (): ReactElement => {
return (
Expand All @@ -40,33 +38,22 @@ export const DrawerLipsum = (): ReactElement => {
)
}

export const DrawerLipumTitle = (): ReactElement => {
export const DrawerLipsumTitle = (): ReactElement => {
return <span>{'Drawer Lipsum'}</span>
}

export const DrawerLipsumFooter = ({ closeDrawer }: {closeDrawer: () => void}): ReactElement => {
export const DrawerLipsumFooterButton = ({ text, hierarchy } : {text: string, hierarchy?: Hierarchy}): ReactElement => {
return (
<div>
<Button onClick={closeDrawer}>
{'Close'}
</Button>
</div>
<Button hierarchy={hierarchy}>
{text}
</Button>
)
}

export const WithOpenButton = (props: DrawerProps): ReactElement => {
const { isVisible, openDrawer, closeDrawer } = useDrawer()
return (
<>
<Button onClick={openDrawer}>Open Drawer</Button>
<Drawer
{...props}
footer={<DrawerLipsumFooter closeDrawer={closeDrawer} />}
isVisible={isVisible}
onClose={closeDrawer}
>
<DrawerLipsum />
</Drawer>
</>
)
export const drawerLipsumFooter = {
buttons: [
<DrawerLipsumFooterButton key="action-button" text="Primary Action" />,
<DrawerLipsumFooterButton hierarchy={Hierarchy.Neutral} key="action-button" text="Secondary Action" />,
],
extra: 'Extra text',
}
42 changes: 42 additions & 0 deletions src/components/Drawer/Drawer.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
.drawer {

:global(.mia-platform-drawer-header-title) {
flex-direction: row-reverse;

:global(.mia-platform-drawer-close) {
margin: 0;
padding: 0;
}

h4 {
margin: 0;
}
}

:global(.mia-platform-drawer-footer) {
padding: var(--spacing-padding-lg, 16px) var(--spacing-padding-xl, 24px);
min-height: var(--shape-size-xl, 32px);
}

.footer {
display: flex;
align-items: center;
justify-content: end;
gap: var(--spacing-gap-sm, 8px);
}

.footerButtons {
margin-inline-start: 0;
display: flex;
flex-direction: row-reverse;
gap: var(--spacing-gap-sm, 8px);

:global(*) {
margin: 0 !important;
}
}

.extra {
flex: 1;
}
}
4 changes: 2 additions & 2 deletions src/components/Drawer/Drawer.props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import { ReactNode } from 'react'

import { DrawerTitle } from './Drawer.Title'
import { CustomDrawerFooter, DrawerFooter, DrawerTitle } from './Drawer.types'

export type DrawerProps = {

Expand All @@ -35,7 +35,7 @@ export type DrawerProps = {
/**
* Drawer footer.
*/
footer?: ReactNode,
footer?: DrawerFooter | CustomDrawerFooter,

/**
* drawer id for DOM node.
Expand Down
49 changes: 45 additions & 4 deletions src/components/Drawer/Drawer.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,66 @@
*/

import type { Meta, StoryObj } from '@storybook/react'
import { useMemo, useState } from 'react'

import { DrawerLipumTitle, WithOpenButton } from './Drawer.mocks'
import { DrawerLipsum, DrawerLipsumTitle, drawerLipsumFooter } from './Drawer.mocks'
import { Button } from '../Button'
import { Drawer } from '.'

const defaults = {
title: <DrawerLipumTitle />,
title: <DrawerLipsumTitle />,
}

const meta = {
component: Drawer,
args: defaults,
argTypes: {
children: { control: false },
isVisible: { control: false },
},
decorators: [
(Story, context) => {
const [isVisible, setIsVisible] = useState(false)

const customContext = useMemo(() => ({
...context,
args: {
...context.args,
isVisible,
onClose: () => setIsVisible(false),
},
}), [context, isVisible])

return <div>
<Button
onClick={() => setIsVisible(true)}
>
Open drawer
</Button>
<Story {...customContext} />
</div>
},
],
render: (_, { args }) => <Drawer {...args}>
<DrawerLipsum />
</Drawer>,
} satisfies Meta<typeof Drawer>

export default meta
type Story = StoryObj<typeof meta>

export const BasicExample: Story = {
decorators: [(_, { args }) => <WithOpenButton {...args} />],
export const BasicExample: Story = {}

export const WithStandardFooterProps: Story = {
args: {
...meta.args,
footer: drawerLipsumFooter,
},
}

export const WithCustomFooter: Story = {
args: {
...meta.args,
footer: <div>{'Custom footer content'}</div>,
},
}
24 changes: 20 additions & 4 deletions src/components/Drawer/Drawer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,20 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { DrawerLipsum, DrawerLipsumFooter, DrawerLipumTitle } from './Drawer.mocks'
import { DrawerLipsum, DrawerLipsumFooterButton, DrawerLipsumTitle } from './Drawer.mocks'
import { render, screen } from '../../test-utils'
import { Drawer } from './Drawer'
import { DrawerProps } from './Drawer.props'

describe('Drawer', () => {
const props: DrawerProps = {
children: 'Drawer Content',
footer: <DrawerLipsumFooter closeDrawer={jest.fn()} />,
footer: {
buttons: [<DrawerLipsumFooterButton key="close-drawer" text="Close drawer" />],
extra: 'extra content',
},
isVisible: true,
title: <DrawerLipumTitle />,
title: <DrawerLipsumTitle />,
onClose: jest.fn(),
}

Expand All @@ -36,12 +39,25 @@ describe('Drawer', () => {
const { baseElement } = render(<Drawer {...props} ><DrawerLipsum /></Drawer>)

expect(screen.getByText(/drawer lipsum/i)).toBeVisible()
expect(screen.getByRole('button', { name: /close/i })).toBeInTheDocument()
expect(screen.getByRole('button', { name: /close drawer/i })).toBeInTheDocument()
expect(screen.getByText(/extra content/i)).toBeInTheDocument()
expect(screen.getByText(/Lorem ipsum dolor sit amet,/i)).toBeInTheDocument()

expect(baseElement).toMatchSnapshot()
})

it('renders drawer with custom footer', () => {
const customProps = {
...props,
footer: <div>Custom footer content</div>,
}
render(<Drawer {...customProps} ><DrawerLipsum /></Drawer>)

expect(screen.getByText(/drawer lipsum/i)).toBeVisible()
expect(screen.getByText(/custom footer content/i)).toBeInTheDocument()
expect(screen.getByText(/Lorem ipsum dolor sit amet,/i)).toBeInTheDocument()
})

it('does not render drawer when isVisible is false', () => {
render(<Drawer {...props} isVisible={false} >{'the-content'}</Drawer>)
expect(screen.queryByText(/drawer lipsum/i)).toBeNull()
Expand Down
18 changes: 10 additions & 8 deletions src/components/Drawer/Drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,14 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { ReactElement, useMemo } from 'react'
import { Drawer as AntdDrawer } from 'antd'
import { ReactElement } from 'react'

import { DrawerProps } from './Drawer.props'
import { Footer } from './Drawer.Footer'
import { Icon } from '../Icon'
import { Title } from './Drawer.Title'

const styles = {
footer: { padding: '24px' },
}
import styles from './Drawer.module.css'

const DRAWER_WIDTH = 512

Expand All @@ -39,15 +37,19 @@ export const Drawer = ({
onClose,
title,
}: DrawerProps): ReactElement => {
const closeIcon = useMemo(() =>
<Icon color="currentColor" name="PiX" size={16} />
, [])

return (
<AntdDrawer
closeIcon={null}
className={styles.drawer}
closeIcon={closeIcon}
destroyOnClose={destroyOnClose}
footer={<Drawer.Footer footer={footer} />}
footer={footer && <Drawer.Footer footer={footer} />}
id={id}
key={key}
open={isVisible}
styles={styles}
title={<Drawer.Title title={title} />}
width={DRAWER_WIDTH}
onClose={onClose}
Expand Down
31 changes: 31 additions & 0 deletions src/components/Drawer/Drawer.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ReactElement, ReactNode } from 'react'

// Header

export type DrawerTitle = ReactNode

export type TitleProps = {
title: DrawerTitle,
}

// Footer

export type DrawerFooter = {

/**
* Array of buttons to be displayed in the right side of the footer.
* Note that the rendering order is the opposite of the list order.
*/
buttons?: ReactElement[]

/**
* Extra information to be displayed in the left side of the footer (such as text or a checkbox).
*/
extra?: ReactNode
}

export type CustomDrawerFooter = ReactElement

export type FooterProps = {
footer?: DrawerFooter | CustomDrawerFooter,
}
Loading
Loading