-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat(web-react): Introduce Drawer component #DS-1580
- Loading branch information
Showing
18 changed files
with
405 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
'use client'; | ||
|
||
import classNames from 'classnames'; | ||
import React from 'react'; | ||
import { AlignmentX } from '../../constants'; | ||
import { useStyleProps, useLastActiveFocus } from '../../hooks'; | ||
import { SpiritDrawerProps } from '../../types'; | ||
import Dialog from '../Dialog/Dialog'; | ||
import { DrawerProvider } from './DrawerContext'; | ||
import { useDrawerStyleProps } from './useDrawerStyleProps'; | ||
|
||
const Drawer = (props: SpiritDrawerProps) => { | ||
const { children, alignment = AlignmentX.RIGHT, isOpen, onClose, id, ...restProps } = props; | ||
const { classProps } = useDrawerStyleProps({ drawerAlignment: alignment }); | ||
const { styleProps, props: otherProps } = useStyleProps(restProps); | ||
|
||
const contextValue = { | ||
id, | ||
isOpen, | ||
onClose, | ||
}; | ||
|
||
useLastActiveFocus(isOpen); | ||
|
||
return ( | ||
<DrawerProvider value={contextValue}> | ||
<Dialog | ||
{...otherProps} | ||
{...styleProps} | ||
id={id} | ||
isOpen={isOpen} | ||
onClose={onClose} | ||
className={classNames(classProps.root, styleProps.className)} | ||
aria-labelledby={`${id}__title`} | ||
> | ||
{children} | ||
</Dialog> | ||
</DrawerProvider> | ||
); | ||
}; | ||
|
||
export default Drawer; |
21 changes: 21 additions & 0 deletions
21
packages/web-react/src/components/Drawer/DrawerCloseButton.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
'use client'; | ||
|
||
import React from 'react'; | ||
import { DrawerCloseButtonProps } from '../../types'; | ||
import { Button } from '../Button'; | ||
import { Icon } from '../Icon'; | ||
import { VisuallyHidden } from '../VisuallyHidden'; | ||
import { useDrawerContext } from './DrawerContext'; | ||
|
||
const DrawerCloseButton = ({ label = 'Close', ...restProps }) => { | ||
const { id, isOpen, onClose } = useDrawerContext(); | ||
|
||
return ( | ||
<Button {...restProps} isSymmetrical color="tertiary" onClick={onClose} aria-expanded={isOpen} aria-controls={id}> | ||
<Icon name="close" /> | ||
<VisuallyHidden>{label}</VisuallyHidden> | ||
</Button> | ||
); | ||
}; | ||
|
||
export default DrawerCloseButton; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
'use client'; | ||
|
||
import { createContext, useContext } from 'react'; | ||
import { DrawerDialogHandlingProps } from '../../types'; | ||
|
||
export type DrawerContextProps = { | ||
id: string; | ||
} & DrawerDialogHandlingProps; | ||
|
||
const defaultContext: DrawerContextProps = { | ||
id: '', | ||
isOpen: false, | ||
onClose: () => null, | ||
}; | ||
|
||
const DrawerContext = createContext<DrawerContextProps>(defaultContext); | ||
const DrawerProvider = DrawerContext.Provider; | ||
const DrawerConsumer = DrawerContext.Consumer; | ||
const useDrawerContext = (): DrawerContextProps => useContext(DrawerContext); | ||
|
||
export default DrawerContext; | ||
export { DrawerProvider, DrawerConsumer, useDrawerContext }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
'use client'; | ||
|
||
import classNames from 'classnames'; | ||
import React, { ElementType, ForwardedRef, forwardRef, HTMLAttributes } from 'react'; | ||
import { useStyleProps } from '../../hooks'; | ||
import { DrawerDialogElementType, DrawerDialogProps } from '../../types'; | ||
import { useDrawerDialogStyleProps } from './useDrawerDialogStyleProps'; | ||
import { useDrawerStyleProps } from './useDrawerStyleProps'; | ||
|
||
const DrawerDialog = <E extends ElementType = DrawerDialogElementType>( | ||
props: DrawerDialogProps<E>, | ||
ref: ForwardedRef<HTMLDivElement>, | ||
) => { | ||
const { elementType: ElementTag = 'div', children, ...restProps } = props; | ||
|
||
const { classProps } = useDrawerStyleProps(restProps); | ||
const { drawerDialogStyleProps, props: otherStyleProps } = useDrawerDialogStyleProps(restProps); | ||
const { styleProps, props: otherProps } = useStyleProps(otherStyleProps); | ||
|
||
const combinedStyleProps = { ...styleProps.style, ...drawerDialogStyleProps }; | ||
|
||
return ( | ||
<ElementTag | ||
ref={ref} | ||
{...(otherProps as HTMLAttributes<HTMLElement>)} | ||
style={{ ...(combinedStyleProps as HTMLAttributes<HTMLElement>) }} | ||
className={classNames(classProps.dialog, styleProps.className)} | ||
> | ||
{children} | ||
</ElementTag> | ||
); | ||
}; | ||
|
||
export default forwardRef(DrawerDialog); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Drawer |
50 changes: 50 additions & 0 deletions
50
packages/web-react/src/components/Drawer/__tests__/Modal.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import '@testing-library/jest-dom'; | ||
import { fireEvent, render, screen } from '@testing-library/react'; | ||
import React from 'react'; | ||
import { classNamePrefixProviderTest } from '../../../../tests/providerTests/classNamePrefixProviderTest'; | ||
import { restPropsTest } from '../../../../tests/providerTests/restPropsTest'; | ||
import { stylePropsTest } from '../../../../tests/providerTests/stylePropsTest'; | ||
import { SpiritModalProps } from '../../../types'; | ||
import Modal from '../Modal'; | ||
|
||
describe('Modal', () => { | ||
const ModalTest = (props: SpiritModalProps) => ( | ||
<Modal {...props} id="modal-example" isOpen={false} onClose={() => null}> | ||
<div>Test</div> | ||
</Modal> | ||
); | ||
|
||
classNamePrefixProviderTest(ModalTest, 'Modal'); | ||
|
||
stylePropsTest(ModalTest); | ||
|
||
restPropsTest(ModalTest, 'dialog'); | ||
|
||
it('should not close modal dialog', () => { | ||
const mockedOnClose = jest.fn(); | ||
render( | ||
<Modal id="test" isOpen onClose={mockedOnClose} closeOnBackdropClick={false}> | ||
<div>Test</div> | ||
</Modal>, | ||
); | ||
|
||
const dialog = screen.getByRole('dialog'); | ||
fireEvent.click(dialog); | ||
|
||
expect(mockedOnClose).not.toHaveBeenCalled(); | ||
}); | ||
|
||
it('should close modal dialog', () => { | ||
const mockedOnClose = jest.fn(); | ||
render( | ||
<Modal id="test" isOpen onClose={mockedOnClose} closeOnBackdropClick> | ||
<div>Test</div> | ||
</Modal>, | ||
); | ||
|
||
const dialog = screen.getByRole('dialog'); | ||
fireEvent.click(dialog); | ||
|
||
expect(mockedOnClose).toHaveBeenCalled(); | ||
}); | ||
}); |
17 changes: 17 additions & 0 deletions
17
packages/web-react/src/components/Drawer/__tests__/useModalStyleProps.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { renderHook } from '@testing-library/react'; | ||
import { useModalStyleProps } from '../useModalStyleProps'; | ||
|
||
describe('useModalStyleProps', () => { | ||
it('should return defaults', () => { | ||
const { result } = renderHook(() => useModalStyleProps({})); | ||
|
||
expect(result.current.classProps.root).toBe('Modal Modal--center'); | ||
expect(result.current.classProps.dialog).toBe('ModalDialog'); | ||
expect(result.current.classProps.title).toBe('ModalHeader__title'); | ||
expect(result.current.classProps.header).toBe('ModalHeader'); | ||
expect(result.current.classProps.body).toBe('ModalBody'); | ||
expect(result.current.classProps.footer.root).toBe('ModalFooter ModalFooter--right'); | ||
expect(result.current.classProps.footer.description).toBe('ModalFooter__description'); | ||
expect(result.current.classProps.footer.actions).toBe('ModalFooter__actions'); | ||
}); | ||
}); |
32 changes: 32 additions & 0 deletions
32
packages/web-react/src/components/Drawer/demo/DrawerDefault.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import React, { useState } from 'react'; | ||
import { Button } from '../..'; | ||
import { AlignmentX } from '../../../constants'; | ||
import { AlignmentXDictionaryType } from '../../../types'; | ||
import Drawer from '../Drawer'; | ||
import DrawerCloseButton from '../DrawerCloseButton'; | ||
import DrawerDialog from '../DrawerDialog'; | ||
|
||
const DrawerDefault = () => { | ||
const [isDrawerBasicOpen, setDrawerBasicOpen] = useState(false); | ||
const [drawerAlign, setDrawerAlign] = useState<AlignmentXDictionaryType>(AlignmentX.RIGHT); | ||
const toggleDrawerBasic = () => setDrawerBasicOpen(!isDrawerBasicOpen); | ||
|
||
const handleDrawerBasicClose = () => setDrawerBasicOpen(false); | ||
|
||
return ( | ||
<> | ||
<Button onClick={toggleDrawerBasic} data-test-id="drawer-basic"> | ||
Open Drawer | ||
</Button> | ||
|
||
<Drawer alignment={drawerAlign} id="example-basic" isOpen={isDrawerBasicOpen} onClose={handleDrawerBasicClose}> | ||
<DrawerDialog> | ||
<DrawerCloseButton label="test" /> | ||
<div>Drawer content</div> | ||
</DrawerDialog> | ||
</Drawer> | ||
</> | ||
); | ||
}; | ||
|
||
export default DrawerDefault; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// Because there is no `dist` directory during the CI run | ||
/* eslint-disable import/no-extraneous-dependencies, import/extensions, import/no-unresolved */ | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore: No declaration file -- @see https://jira.almacareer.tech/browse/DS-561 | ||
import icons from '@lmc-eu/spirit-icons/icons'; | ||
import React from 'react'; | ||
import ReactDOM from 'react-dom/client'; | ||
import DocsSection from '../../../../docs/DocsSections'; | ||
import { IconsProvider } from '../../../context'; | ||
import DrawerDefault from './DrawerDefault'; | ||
|
||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( | ||
<React.StrictMode> | ||
<IconsProvider value={icons}> | ||
<DocsSection title="Drawer"> | ||
<DrawerDefault /> | ||
</DocsSection> | ||
</IconsProvider> | ||
</React.StrictMode>, | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{{> web-react/demo title="Drawer" parentPageName="Components" }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
'use client'; | ||
|
||
export { default as Drawer } from './Drawer'; | ||
export { default as DrawerCloseButton } from './DrawerCloseButton'; | ||
export { default as DrawerDialog } from './DrawerDialog'; | ||
export * from './DrawerContext'; | ||
export * from './useDrawerStyleProps'; |
29 changes: 29 additions & 0 deletions
29
packages/web-react/src/components/Drawer/stories/Drawer.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { Markdown } from '@storybook/blocks'; | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
import { fn } from '@storybook/test'; | ||
import React from 'react'; | ||
import { AlignmentY } from '../../../constants'; | ||
import Drawer from '../Drawer'; | ||
import ReadMe from '../README.md'; | ||
|
||
const meta: Meta<typeof Drawer> = { | ||
title: 'Components/Drawer', | ||
component: Drawer, | ||
parameters: { | ||
docs: { | ||
page: () => <Markdown>{ReadMe}</Markdown>, | ||
}, | ||
}, | ||
argTypes: {}, | ||
args: { | ||
alignmentY: AlignmentY.CENTER, | ||
id: 'modal', | ||
isOpen: false, | ||
onClose: fn(), | ||
closeOnEscapeKeyDown: true, | ||
closeOnBackdropClick: true, | ||
}, | ||
}; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof Drawer>; |
40 changes: 40 additions & 0 deletions
40
packages/web-react/src/components/Drawer/useDrawerDialogStyleProps.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { ElementType, CSSProperties } from 'react'; | ||
import { DrawerDialogCSSHeight, ModalDialogCSSHeightBreakpoints, DrawerDialogProps } from '../../types'; | ||
|
||
interface CustomizedHeightCSSProperties extends CSSProperties { | ||
[key: string]: string | undefined | number; | ||
} | ||
|
||
const setCustomHeight = ( | ||
baseVarName: string, | ||
propValue: DrawerDialogCSSHeight | ModalDialogCSSHeightBreakpoints | undefined, | ||
): CustomizedHeightCSSProperties => { | ||
if (!propValue) return {}; | ||
|
||
if (typeof propValue === 'object') { | ||
return Object.keys(propValue).reduce((acc, key) => { | ||
const suffix = key === 'mobile' ? '' : `-${key}`; | ||
const propName = `--${baseVarName}${suffix}`; | ||
acc[propName] = propValue[key as keyof ModalDialogCSSHeightBreakpoints]?.toString(); | ||
|
||
return acc; | ||
}, {} as CustomizedHeightCSSProperties); | ||
} | ||
const propName = `--${baseVarName}`; | ||
|
||
return { [propName]: propValue?.toString() } as CustomizedHeightCSSProperties; | ||
}; | ||
|
||
export const useDrawerDialogStyleProps = <E extends ElementType>(props: DrawerDialogProps<E>) => { | ||
const { height, maxHeight, ...otherProps } = props; | ||
|
||
const customizedHeightStyle = { | ||
...setCustomHeight('drawer-dialog-height', height), | ||
...setCustomHeight('drawer-dialog-max-height', maxHeight), | ||
}; | ||
|
||
return { | ||
drawerDialogStyleProps: customizedHeightStyle, | ||
props: otherProps, | ||
}; | ||
}; |
35 changes: 35 additions & 0 deletions
35
packages/web-react/src/components/Drawer/useDrawerStyleProps.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import classNames from 'classnames'; | ||
import { useClassNamePrefix } from '../../hooks'; | ||
import { AlignmentXDictionaryType } from '../../types'; | ||
|
||
export interface DrawerStylesProps { | ||
drawerAlignment: AlignmentXDictionaryType; | ||
} | ||
|
||
export interface DrawerStylesReturn { | ||
/** className props */ | ||
classProps: { | ||
root: string; | ||
dialog: string; | ||
}; | ||
} | ||
|
||
export function useDrawerStyleProps(props: Partial<DrawerStylesProps>): DrawerStylesReturn { | ||
const { drawerAlignment } = props; | ||
const drawerClass = useClassNamePrefix('Drawer'); | ||
const drawerAlignClasses = { | ||
left: `${drawerClass}--left`, | ||
center: `${drawerClass}--center`, | ||
right: `${drawerClass}--right`, | ||
}; | ||
const drawerDialogClass = `${drawerClass}Dialog`; | ||
|
||
const classProps = { | ||
root: classNames(drawerClass, drawerAlignment && { [drawerAlignClasses[drawerAlignment]]: drawerAlignment }), | ||
dialog: classNames(drawerDialogClass, {}), | ||
}; | ||
|
||
return { | ||
classProps, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.