diff --git a/src/components/Drawer/Drawer.story.tsx b/src/components/Drawer/Drawer.story.tsx new file mode 100644 index 00000000..cfc0d53a --- /dev/null +++ b/src/components/Drawer/Drawer.story.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { Drawer } from './Drawer'; +import { Story } from '@storybook/react'; +import { DrawerProps } from './Drawer.types'; +import styled from 'styled-components'; + +export default { + title: 'components/Drawer', + component: Drawer, + argTypes: { + isOpen: { control: 'boolean', defaultValue: false }, + placement: { options: ['left', 'right'], defaultValue: 'left' }, + size: { + options: ['xs', 'sm', 'md', 'lg', 'xl'], + defaultValue: 'md', + }, + }, +}; + +const Template: Story = ({ + isOpen, + placement, + size, +}) => { + return ( + + Hi! I'm Drawer Content! + + ); +}; + +const DrawerContent = styled.div` + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: white; + color: black; +`; + +export const DrawerTemplate = Template.bind({}); +DrawerTemplate.storyName = 'Drawer'; diff --git a/src/components/Drawer/Drawer.styles.tsx b/src/components/Drawer/Drawer.styles.tsx new file mode 100644 index 00000000..f579044c --- /dev/null +++ b/src/components/Drawer/Drawer.styles.tsx @@ -0,0 +1,52 @@ +import styled from 'styled-components'; +import { rem } from 'polished'; +import { DrawerSize } from './Drawer.types'; + +export const DRAWER_WIDTH_XS = 276; +export const DRAWER_WIDTH_SM = 308; +export const DRAWER_WIDTH_MD = 340; +export const DRAWER_WIDTH_LG = 372; +export const DRAWER_WIDTH_XL = 404; + +export const DRAWER_SIZE = { + xs: DRAWER_WIDTH_XS, + sm: DRAWER_WIDTH_SM, + md: DRAWER_WIDTH_MD, + lg: DRAWER_WIDTH_LG, + xl: DRAWER_WIDTH_XL, +}; + +function panelSize(size: DrawerSize) { + switch (size) { + case 'xs': + return rem(DRAWER_WIDTH_XS); + case 'md': + return rem(DRAWER_WIDTH_MD); + case 'lg': + return rem(DRAWER_WIDTH_LG); + case 'xl': + return rem(DRAWER_WIDTH_XL); + case 'sm': + default: + return rem(DRAWER_WIDTH_SM); + } +} + +export const WithSize = styled.div<{ size: DrawerSize }>` + position: relative; + width: ${({ size }) => panelSize(size)}; + height: 100vh; +`; + +export const WithPlacement = styled.div<{ left: boolean }>` + position: fixed; + ${({ left }) => + left + ? ` + left: 0; + right: initial;` + : ` + right: 0; + left: initial; + `} +`; diff --git a/src/components/Drawer/Drawer.tsx b/src/components/Drawer/Drawer.tsx new file mode 100644 index 00000000..c3fbd406 --- /dev/null +++ b/src/components/Drawer/Drawer.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { DrawerProps, Minors } from './Drawer.types'; +import { AnimatePresence, motion } from 'framer-motion'; +import { withIris } from '../../utils'; +import { DrawerContainer } from './Minors/DrawerContainer'; +import { getAnimation } from './util'; + +const DrawerComponent = ({ + placement, + isOpen, + size = 'md', + // autoFocus, + // initialFocusRef, + // finalFocusRef, + // returnFocusOnClose, + // preserveScrollBarGap, + children, +}: DrawerProps) => { + return ( + + {isOpen && ( + + + {children} + + + )} + + ); +}; + +export const Drawer = withIris( + DrawerComponent +); diff --git a/src/components/Drawer/Drawer.types.ts b/src/components/Drawer/Drawer.types.ts new file mode 100644 index 00000000..5852145b --- /dev/null +++ b/src/components/Drawer/Drawer.types.ts @@ -0,0 +1,69 @@ +import React, { HTMLProps } from 'react'; +import { IrisProps, MinorComponent } from '../../utils'; + +export type DrawerSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; + +export interface FocusableElement { + focus(options?: FocusOptions): void; +} + +export type DrawerProps = IrisProps<{ + /** + * Whether the drawer is visible or not + * + * @default false + */ + isOpen: boolean; + /** + * The placement of the drawer + * + * @default "left" + */ + placement?: 'left' | 'right'; + /** + * Width of the drawer + * + * @default "md" + */ + size?: DrawerSize; + /** + * If `true`, the modal will autofocus the first enabled and interactive + * element within the `ModalContent` + * + * @default true + */ + autoFocus?: boolean; + /** + * The `ref` of element to receive focus when the modal opens. + */ + initialFocusRef?: React.RefObject; + /** + * The `ref` of element to receive focus when the modal closes. + */ + finalFocusRef?: React.RefObject; + /** + * If `true`, the modal will return focus to the element that triggered it when it closes. + * + * @default true + */ + returnFocusOnClose?: boolean; + /** + * If `true`, a `padding-right` will be applied to the body element + * that's equal to the width of the scrollbar. + * + * This can help prevent some unpleasant flickering effect + * and content adjustment when the modal opens + * + * @default true + */ + preserveScrollBarGap?: boolean; +}>; + +export interface Minors { + DrawerHeader: MinorComponent>; + DrawerContent: MinorComponent>; + DrawerBody: MinorComponent>; + DrawerFooter: MinorComponent>; + DrawerOverlay: MinorComponent>; + DrawerCloseButton: MinorComponent>; +} diff --git a/src/components/Drawer/Minors/DrawerContainer.tsx b/src/components/Drawer/Minors/DrawerContainer.tsx new file mode 100644 index 00000000..2e8a3cd8 --- /dev/null +++ b/src/components/Drawer/Minors/DrawerContainer.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { WithPlacement, WithSize } from '../Drawer.styles'; +import { DrawerSize } from '../Drawer.types'; + +interface Props { + size: DrawerSize; + placement: 'left' | 'right'; +} + +export const DrawerContainer = ({ + size, + placement, + children, +}: React.PropsWithChildren) => { + return ( + + {children} + + ); +}; diff --git a/src/components/Drawer/util.ts b/src/components/Drawer/util.ts new file mode 100644 index 00000000..89aff38e --- /dev/null +++ b/src/components/Drawer/util.ts @@ -0,0 +1,18 @@ +import { DRAWER_SIZE } from './Drawer.styles'; +import { DrawerSize } from './Drawer.types'; + +export function getAnimation( + size: DrawerSize, + placement: 'left' | 'right' +) { + const left = placement === 'left'; + return { + initial: { x: left ? -DRAWER_SIZE[size] : DRAWER_SIZE[size] }, + animate: { x: 0 }, + exit: { x: left ? -DRAWER_SIZE[size] : DRAWER_SIZE[size] }, + transition: { + type: 'tween', + duration: (DRAWER_SIZE[size] / 6 + 90) / 1000, + }, + }; +}