diff --git a/.jest.config.js b/.jest.config.js
index 0e305ce44..6050b4ee9 100644
--- a/.jest.config.js
+++ b/.jest.config.js
@@ -3,7 +3,7 @@ module.exports = {
testEnvironment: 'jsdom',
- setupFiles: ['./tests/setup.js'],
+ setupFilesAfterEnv: ['./tests/setup.ts'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
@@ -14,7 +14,7 @@ module.exports = {
},
testRegex: '.*\\.test\\.(j|t)sx?$',
- // testRegex: 'scaleable\\/.*\\.test\\.(j|t)sx?$',
+ // testRegex: 'modal\\/.*\\.test\\.(j|t)sx?$',
collectCoverageFrom: [
'components/**/*.{ts,tsx}',
diff --git a/components/drawer/__tests__/__snapshots__/index.test.tsx.snap b/components/drawer/__tests__/__snapshots__/index.test.tsx.snap
new file mode 100644
index 000000000..9f780d245
--- /dev/null
+++ b/components/drawer/__tests__/__snapshots__/index.test.tsx.snap
@@ -0,0 +1,905 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Drawer customization should be supported 1`] = `
+"
"
+`;
+
+exports[`Drawer should render correctly 1`] = `
+"Drawer
This is a drawer
Some content contained within the drawer.
"
+`;
+
+exports[`Drawer should work correctly with different placement 1`] = `
+"Some content contained within the drawer.
"
+`;
+
+exports[`Drawer should work correctly with different placement 2`] = `
+"Some content contained within the drawer.
"
+`;
+
+exports[`Drawer should work correctly with different placement 3`] = `
+"Some content contained within the drawer.
"
+`;
+
+exports[`Drawer should work correctly with different placement 4`] = `
+"Some content contained within the drawer.
"
+`;
diff --git a/components/drawer/__tests__/index.test.tsx b/components/drawer/__tests__/index.test.tsx
new file mode 100644
index 000000000..a35a219aa
--- /dev/null
+++ b/components/drawer/__tests__/index.test.tsx
@@ -0,0 +1,151 @@
+import React from 'react'
+import { mount } from 'enzyme'
+import { Drawer } from 'components'
+import { nativeEvent, updateWrapper } from 'tests/utils'
+import { expectDrawerIsClosed, expectDrawerIsOpened } from './use-modal.test'
+import userEvent from '@testing-library/user-event'
+
+describe('Drawer', () => {
+ it('should render correctly', () => {
+ const wrapper = mount(
+
+ Drawer
+ This is a drawer
+
+ Some content contained within the drawer.
+
+ ,
+ )
+ expect(wrapper.html()).toMatchSnapshot()
+ expect(() => wrapper.unmount()).not.toThrow()
+ })
+
+ it('should work correctly with different placement', () => {
+ const top = mount(
+
+ Some content contained within the drawer.
+ ,
+ )
+ expect(top.html()).toMatchSnapshot()
+ expect(() => top.unmount()).not.toThrow()
+
+ const right = mount(
+
+ Some content contained within the drawer.
+ ,
+ )
+ expect(right.html()).toMatchSnapshot()
+ expect(() => right.unmount()).not.toThrow()
+
+ const bottom = mount(
+
+ Some content contained within the drawer.
+ ,
+ )
+ expect(bottom.html()).toMatchSnapshot()
+ expect(() => bottom.unmount()).not.toThrow()
+
+ const left = mount(
+
+ Some content contained within the drawer.
+ ,
+ )
+ expect(left.html()).toMatchSnapshot()
+ expect(() => left.unmount()).not.toThrow()
+ })
+
+ it('should trigger event when drawer changed', async () => {
+ const closeHandler = jest.fn()
+ const wrapper = mount(
+
+ Modal
+ ,
+ )
+ expectDrawerIsClosed(wrapper)
+
+ wrapper.setProps({ visible: true })
+ await updateWrapper(wrapper, 350)
+ expectDrawerIsOpened(wrapper)
+
+ wrapper.find('.backdrop').simulate('click', nativeEvent)
+ await updateWrapper(wrapper, 500)
+ expectDrawerIsClosed(wrapper)
+ expect(closeHandler).toHaveBeenCalled()
+ })
+
+ it('should disable backdrop event', async () => {
+ const closeHandler = jest.fn()
+ const wrapper = mount(
+
+ Modal
+ ,
+ )
+ wrapper.find('.backdrop').simulate('click', nativeEvent)
+ await updateWrapper(wrapper, 500)
+ expectDrawerIsOpened(wrapper)
+ expect(closeHandler).not.toHaveBeenCalled()
+ })
+
+ it('customization should be supported', () => {
+ const wrapper = mount(
+
+ Modal
+ ,
+ )
+ const html = wrapper.find('.wrapper').html()
+ expect(html).toMatchSnapshot()
+ expect(wrapper.find('.wrapper').at(0).getDOMNode()).toHaveClass('test-class')
+ expect(() => wrapper.unmount()).not.toThrow()
+ })
+
+ it('focus should only be switched within modal', async () => {
+ const wrapper = mount(
+
+
+ ,
+ )
+ const tabStart = wrapper.find('.hide-tab').at(0).getDOMNode()
+ const tabEnd = wrapper.find('.hide-tab').at(1).getDOMNode()
+ const button = wrapper.find('#button').at(0).getDOMNode()
+ const focusTrap = wrapper.find('.wrapper').at(0).getDOMNode()
+
+ expect(tabStart).toHaveFocus()
+ userEvent.tab({ focusTrap })
+ expect(button).toHaveFocus()
+ userEvent.tab()
+ expect(tabEnd).toHaveFocus()
+ userEvent.tab()
+ expect(tabStart).toHaveFocus()
+
+ userEvent.tab({ shift: true, focusTrap })
+ expect(tabEnd).toHaveFocus()
+ userEvent.tab({ shift: true, focusTrap })
+ expect(button).toHaveFocus()
+ userEvent.tab({ shift: true, focusTrap })
+ expect(tabStart).toHaveFocus()
+ })
+
+ it('should close drawer when keyboard event is triggered', async () => {
+ const wrapper = mount(
+
+ Drawer
+ ,
+ )
+ expectDrawerIsOpened(wrapper)
+ userEvent.keyboard('{esc}')
+ await updateWrapper(wrapper, 500)
+ expectDrawerIsClosed(wrapper)
+ })
+
+ it('should prevent close modal when keyboard is false', async () => {
+ const wrapper = mount(
+
+ Drawer
+ ,
+ )
+ expectDrawerIsOpened(wrapper)
+ userEvent.keyboard('{esc}')
+ await updateWrapper(wrapper, 500)
+ expectDrawerIsOpened(wrapper)
+ })
+})
diff --git a/components/drawer/__tests__/use-modal.test.tsx b/components/drawer/__tests__/use-modal.test.tsx
new file mode 100644
index 000000000..9697d65c8
--- /dev/null
+++ b/components/drawer/__tests__/use-modal.test.tsx
@@ -0,0 +1,37 @@
+import React, { useEffect } from 'react'
+import { mount, ReactWrapper } from 'enzyme'
+import { Drawer, useModal } from 'components'
+import { updateWrapper } from 'tests/utils'
+
+export const expectDrawerIsOpened = (wrapper: ReactWrapper) => {
+ expect(wrapper.find('.content').length).not.toBe(0)
+}
+
+export const expectDrawerIsClosed = (wrapper: ReactWrapper) => {
+ expect(wrapper.find('.content').length).toBe(0)
+}
+
+describe('UseModal & Drawer', () => {
+ it('should follow change with use-modal', async () => {
+ const MockModal: React.FC<{ show?: boolean }> = ({ show }) => {
+ const { setVisible, bindings } = useModal()
+ useEffect(() => {
+ if (show !== undefined) setVisible(show)
+ }, [show])
+ return (
+
+ Drawer
+
+ )
+ }
+
+ const wrapper = mount()
+ wrapper.setProps({ show: true })
+ await updateWrapper(wrapper, 300)
+ expectDrawerIsOpened(wrapper)
+
+ wrapper.setProps({ show: false })
+ await updateWrapper(wrapper, 500)
+ expectDrawerIsClosed(wrapper)
+ })
+})
diff --git a/components/drawer/drawer-wrapper.tsx b/components/drawer/drawer-wrapper.tsx
new file mode 100644
index 000000000..87d78c8ed
--- /dev/null
+++ b/components/drawer/drawer-wrapper.tsx
@@ -0,0 +1,160 @@
+import React, { useEffect, useMemo, useRef } from 'react'
+import useTheme from '../use-theme'
+import CssTransition from '../shared/css-transition'
+import { isChildElement } from '../utils/collections'
+import useScaleable from '../use-scaleable'
+import { DrawerPlacement, getDrawerTransform } from './helper'
+
+interface Props {
+ className?: string
+ visible?: boolean
+ placement: DrawerPlacement
+}
+
+const defaultProps = {
+ className: '',
+ visible: false,
+}
+
+export type DrawerWrapperProps = Props
+
+const DrawerWrapper: React.FC> = ({
+ className,
+ children,
+ visible,
+ placement,
+ ...props
+}: React.PropsWithChildren & typeof defaultProps) => {
+ const theme = useTheme()
+ const { SCALES } = useScaleable()
+ const modalContent = useRef(null)
+ const tabStart = useRef(null)
+ const tabEnd = useRef(null)
+ const transform = useMemo(() => getDrawerTransform(placement), [placement])
+
+ useEffect(() => {
+ if (!visible) return
+ const activeElement = document.activeElement
+ const isChild = isChildElement(modalContent.current, activeElement)
+ if (isChild) return
+ tabStart.current && tabStart.current.focus()
+ }, [visible])
+
+ const onKeyDown = (event: React.KeyboardEvent) => {
+ const isTabDown = event.keyCode === 9
+ if (!visible || !isTabDown) return
+ const activeElement = document.activeElement
+ if (event.shiftKey) {
+ if (activeElement === tabStart.current) {
+ tabEnd.current && tabEnd.current.focus()
+ }
+ } else {
+ if (activeElement === tabEnd.current) {
+ tabStart.current && tabStart.current.focus()
+ }
+ }
+ }
+
+ return (
+
+
+
+ )
+}
+
+DrawerWrapper.defaultProps = defaultProps
+DrawerWrapper.displayName = 'GeistDrawerWrapper'
+export default DrawerWrapper
diff --git a/components/drawer/drawer.tsx b/components/drawer/drawer.tsx
new file mode 100644
index 000000000..705d519eb
--- /dev/null
+++ b/components/drawer/drawer.tsx
@@ -0,0 +1,91 @@
+import React, { MouseEvent, useEffect, useState } from 'react'
+import { withScaleable } from '../use-scaleable'
+import usePortal from '../utils/use-portal'
+import useBodyScroll from '../utils/use-body-scroll'
+import useKeyboard, { KeyCode } from '../use-keyboard'
+import { createPortal } from 'react-dom'
+import Backdrop from '../shared/backdrop'
+import { DrawerPlacement } from './helper'
+import DrawerWrapper from './drawer-wrapper'
+
+interface Props {
+ visible?: boolean
+ keyboard?: boolean
+ disableBackdropClick?: boolean
+ onClose?: () => void
+ onContentClick?: (event: MouseEvent) => void
+ wrapClassName?: string
+ placement?: DrawerPlacement
+}
+
+const defaultProps = {
+ wrapClassName: '',
+ keyboard: true,
+ disableBackdropClick: false,
+ placement: 'right' as DrawerPlacement,
+}
+
+type NativeAttrs = Omit, keyof Props>
+export type DrawerProps = Props & NativeAttrs
+
+const DrawerComponent: React.FC> = ({
+ visible: customVisible,
+ keyboard,
+ disableBackdropClick,
+ onClose,
+ onContentClick,
+ wrapClassName,
+ children,
+ ...props
+}: React.PropsWithChildren & typeof defaultProps) => {
+ const portal = usePortal('drawer')
+ const [visible, setVisible] = useState(false)
+ const [, setBodyHidden] = useBodyScroll(null, { scrollLayer: true })
+
+ const closeDrawer = () => {
+ onClose && onClose()
+ setVisible(false)
+ setBodyHidden(false)
+ }
+
+ useEffect(() => {
+ if (typeof customVisible === 'undefined') return
+ setVisible(customVisible)
+ setBodyHidden(customVisible)
+ }, [customVisible])
+
+ const { bindings } = useKeyboard(
+ () => {
+ keyboard && closeDrawer()
+ },
+ KeyCode.Escape,
+ {
+ disableGlobalEvent: true,
+ },
+ )
+
+ const closeFromBackdrop = () => {
+ if (disableBackdropClick) return
+ closeDrawer()
+ }
+
+ if (!portal) return null
+ return createPortal(
+
+
+ {children}
+
+ ,
+ portal,
+ )
+}
+
+DrawerComponent.defaultProps = defaultProps
+DrawerComponent.displayName = 'GeistDrawer'
+const Drawer = withScaleable(DrawerComponent)
+export default Drawer
diff --git a/components/drawer/helper.ts b/components/drawer/helper.ts
new file mode 100644
index 000000000..925befb87
--- /dev/null
+++ b/components/drawer/helper.ts
@@ -0,0 +1,36 @@
+import { tuple } from '../utils/prop-types'
+
+const drawerPlacement = tuple('top', 'right', 'bottom', 'left')
+export type DrawerPlacement = typeof drawerPlacement[number]
+
+export type DrawerTranslateItem = {
+ initial: string
+ hidden: string
+ visible: string
+}
+
+export const getDrawerTransform = (placement: DrawerPlacement): DrawerTranslateItem => {
+ const translates: Record = {
+ top: {
+ initial: 'translate3d(0, -100%, 0)',
+ hidden: 'translate3d(0, -100%, 0)',
+ visible: 'translate3d(0, 0, 0)',
+ },
+ left: {
+ initial: 'translate3d(-100%, 0, 0)',
+ hidden: 'translate3d(-100%, 0, 0)',
+ visible: 'translate3d(0, 0, 0)',
+ },
+ bottom: {
+ initial: 'translate3d(0, 100%, 0)',
+ hidden: 'translate3d(0, 100%, 0)',
+ visible: 'translate3d(0, 0, 0)',
+ },
+ right: {
+ initial: 'translate3d(100%, 0, 0)',
+ hidden: 'translate3d(100%, 0, 0)',
+ visible: 'translate3d(0, 0, 0)',
+ },
+ }
+ return translates[placement]
+}
diff --git a/components/drawer/index.ts b/components/drawer/index.ts
new file mode 100644
index 000000000..425c36e25
--- /dev/null
+++ b/components/drawer/index.ts
@@ -0,0 +1,21 @@
+import Drawer from './drawer'
+import ModalTitle from '../modal/modal-title'
+import ModalSubtitle from '../modal/modal-subtitle'
+import ModalContent from '../modal/modal-content'
+
+export type DrawerComponentType = typeof Drawer & {
+ Title: typeof ModalTitle
+ Subtitle: typeof ModalSubtitle
+ Content: typeof ModalContent
+}
+;(Drawer as DrawerComponentType).Title = ModalTitle
+;(Drawer as DrawerComponentType).Subtitle = ModalSubtitle
+;(Drawer as DrawerComponentType).Content = ModalContent
+
+export type { DrawerProps } from './drawer'
+export type {
+ ModalTitleProps as DrawerTitleProps,
+ ModalSubtitleProps as DrawerSubtitleProps,
+ ModalContentProps as DrawerContentProps,
+} from '../modal'
+export default Drawer as DrawerComponentType
diff --git a/components/index.ts b/components/index.ts
index f52706fdf..0747750f3 100644
--- a/components/index.ts
+++ b/components/index.ts
@@ -51,6 +51,9 @@ export type { DividerProps } from './divider'
export { default as Dot } from './dot'
export type { DotProps } from './dot'
+export { default as Drawer } from './drawer'
+export type { DrawerProps } from './drawer'
+
export { default as Fieldset } from './fieldset'
export type {
FieldsetProps,
diff --git a/components/modal/__tests__/index.test.tsx b/components/modal/__tests__/index.test.tsx
index dae2b3269..27903a689 100644
--- a/components/modal/__tests__/index.test.tsx
+++ b/components/modal/__tests__/index.test.tsx
@@ -1,8 +1,9 @@
import React from 'react'
import { mount } from 'enzyme'
-import { KeyCode, Modal } from 'components'
+import { Modal } from 'components'
import { nativeEvent, updateWrapper } from 'tests/utils'
import { expectModalIsClosed, expectModalIsOpened } from './use-modal.test'
+import userEvent from '@testing-library/user-event'
describe('Modal', () => {
it('should render correctly', () => {
@@ -112,25 +113,35 @@ describe('Modal', () => {
)
const html = wrapper.find('.wrapper').html()
expect(html).toMatchSnapshot()
- expect(html).toContain('test-class')
+ expect(wrapper.find('.wrapper').at(0).getDOMNode()).toHaveClass('test-class')
expect(() => wrapper.unmount()).not.toThrow()
})
- it('focus should only be switched within modal', () => {
+ it('focus should only be switched within modal', async () => {
const wrapper = mount(
- Modal
+
,
)
const tabStart = wrapper.find('.hide-tab').at(0).getDOMNode()
const tabEnd = wrapper.find('.hide-tab').at(1).getDOMNode()
- expect(document.activeElement).toBe(tabStart)
+ const button = wrapper.find('#button').at(0).getDOMNode()
+ const focusTrap = wrapper.find('.wrapper').at(0).getDOMNode()
- document.dispatchEvent(new KeyboardEvent('keydown', { keyCode: KeyCode.Tab }))
+ expect(tabStart).toHaveFocus()
+ userEvent.tab({ focusTrap })
+ expect(button).toHaveFocus()
+ userEvent.tab()
+ expect(tabEnd).toHaveFocus()
+ userEvent.tab()
+ expect(tabStart).toHaveFocus()
- expect(tabEnd.outerHTML).toEqual(document.activeElement?.outerHTML)
- document.dispatchEvent(new KeyboardEvent('keydown', { keyCode: KeyCode.Tab }))
- expect(tabStart.outerHTML).toEqual(document.activeElement?.outerHTML)
+ userEvent.tab({ shift: true, focusTrap })
+ expect(tabEnd).toHaveFocus()
+ userEvent.tab({ shift: true, focusTrap })
+ expect(button).toHaveFocus()
+ userEvent.tab({ shift: true, focusTrap })
+ expect(tabStart).toHaveFocus()
})
it('should close modal when keyboard event is triggered', async () => {
@@ -140,9 +151,7 @@ describe('Modal', () => {
,
)
expectModalIsOpened(wrapper)
- wrapper.simulate('keydown', {
- keyCode: KeyCode.Escape,
- })
+ userEvent.keyboard('{esc}')
await updateWrapper(wrapper, 500)
expectModalIsClosed(wrapper)
})
@@ -154,9 +163,7 @@ describe('Modal', () => {
,
)
expectModalIsOpened(wrapper)
- wrapper.simulate('keydown', {
- keyCode: KeyCode.Escape,
- })
+ userEvent.keyboard('{esc}')
await updateWrapper(wrapper, 500)
expectModalIsOpened(wrapper)
})
diff --git a/lib/data/metadata-en-us.json b/lib/data/metadata-en-us.json
index 54f334014..a5720b6e8 100644
--- a/lib/data/metadata-en-us.json
+++ b/lib/data/metadata-en-us.json
@@ -1 +1 @@
-[{"name":"guide","children":[{"name":"Getting Started","children":[{"name":"Introduction","url":"/en-us/guide/introduction","index":5,"group":"Getting Started"},{"name":"Installation","url":"/en-us/guide/installation","index":10,"group":"Getting Started"},{"name":"Server Render","url":"/en-us/guide/server-render","index":15,"group":"Getting Started"}]},{"name":"Customization","children":[{"name":"Colors","url":"/en-us/guide/colors","index":20,"group":"Customization"},{"name":"Themes","url":"/en-us/guide/themes","index":25,"group":"Customization"},{"name":"Scaleable","url":"/en-us/guide/scaleable","index":30,"group":"Customization"}]}]},{"name":"components","children":[{"name":"General","children":[{"name":"Text","url":"/en-us/components/text","index":10,"group":"General"},{"name":"Button","url":"/en-us/components/button","index":100,"group":"General"},{"name":"Code","url":"/en-us/components/code","index":100,"group":"General"},{"name":"Icons","url":"/en-us/components/icons","index":100,"group":"General"}]},{"name":"Layout","children":[{"name":"Grid","url":"/en-us/components/grid","index":100,"group":"Layout"},{"name":"Page","url":"/en-us/components/page","index":100,"group":"Layout"},{"name":"Spacer","url":"/en-us/components/spacer","index":100,"group":"Layout"}]},{"name":"Surfaces","children":[{"name":"Card","url":"/en-us/components/card","index":100,"group":"Surfaces"},{"name":"Collapse","url":"/en-us/components/collapse","index":100,"group":"Surfaces"},{"name":"Fieldset","url":"/en-us/components/fieldset","index":100,"group":"Surfaces"}]},{"name":"Data Entry","children":[{"name":"AutoComplete","url":"/en-us/components/auto-complete","index":100,"group":"Data Entry"},{"name":"Button Group","url":"/en-us/components/button-group","index":100,"group":"Data Entry"},{"name":"Checkbox","url":"/en-us/components/checkbox","index":100,"group":"Data Entry"},{"name":"Input","url":"/en-us/components/input","index":100,"group":"Data Entry"},{"name":"Radio","url":"/en-us/components/radio","index":100,"group":"Data Entry"},{"name":"Select","url":"/en-us/components/select","index":100,"group":"Data Entry"},{"name":"Slider","url":"/en-us/components/slider","index":100,"group":"Data Entry"},{"name":"Textarea","url":"/en-us/components/textarea","index":100,"group":"Data Entry"},{"name":"Toggle","url":"/en-us/components/toggle","index":100,"group":"Data Entry"}]},{"name":"Data Display","children":[{"name":"Avatar","url":"/en-us/components/avatar","index":100,"group":"Data Display"},{"name":"Badge","url":"/en-us/components/badge","index":100,"group":"Data Display"},{"name":"Capacity","url":"/en-us/components/capacity","index":100,"group":"Data Display"},{"name":"Description","url":"/en-us/components/description","index":100,"group":"Data Display"},{"name":"Display","url":"/en-us/components/display","index":100,"group":"Data Display"},{"name":"Dot","url":"/en-us/components/dot","index":100,"group":"Data Display"},{"name":"File Tree","url":"/en-us/components/file-tree","index":100,"group":"Data Display"},{"name":"Image","url":"/en-us/components/image","index":100,"group":"Data Display"},{"name":"Keyboard","url":"/en-us/components/keyboard","index":100,"group":"Data Display"},{"name":"Popover","url":"/en-us/components/popover","index":100,"group":"Data Display"},{"name":"Table","url":"/en-us/components/table","index":100,"group":"Data Display"},{"name":"Tag","url":"/en-us/components/tag","index":100,"group":"Data Display"},{"name":"Tooltip","url":"/en-us/components/tooltip","index":100,"group":"Data Display"},{"name":"User","url":"/en-us/components/user","index":100,"group":"Data Display"}]},{"name":"Feedback","children":[{"name":"Loading","url":"/en-us/components/loading","index":100,"group":"Feedback"},{"name":"Modal","url":"/en-us/components/modal","index":100,"group":"Feedback"},{"name":"Note","url":"/en-us/components/note","index":100,"group":"Feedback"},{"name":"Progress","url":"/en-us/components/progress","index":100,"group":"Feedback"},{"name":"Rating","url":"/en-us/components/rating","index":100,"group":"Feedback"},{"name":"Spinner","url":"/en-us/components/spinner","index":100,"group":"Feedback"},{"name":"Toast","url":"/en-us/components/toast","index":100,"group":"Feedback"}]},{"name":"Navigation","children":[{"name":"Breadcrumbs","url":"/en-us/components/breadcrumbs","index":100,"group":"Navigation"},{"name":"Link","url":"/en-us/components/link","index":100,"group":"Navigation"},{"name":"Pagination","url":"/en-us/components/pagination","index":100,"group":"Navigation"},{"name":"Tabs","url":"/en-us/components/tabs","index":100,"group":"Navigation"},{"name":"Button Dropdown","url":"/en-us/components/button-dropdown","index":101,"group":"Navigation"}]},{"name":"Others","children":[{"name":"Divider","url":"/en-us/components/divider","index":100,"group":"Others"},{"name":"Snippet","url":"/en-us/components/snippet","index":100,"group":"Others"}]},{"name":"Utils","children":[{"name":"useBodyScroll","url":"/en-us/components/use-body-scroll","index":100,"group":"Utils"},{"name":"useClickAway","url":"/en-us/components/use-click-away","index":100,"group":"Utils"},{"name":"useClipboard","url":"/en-us/components/use-clipboard","index":100,"group":"Utils"},{"name":"useCurrentState","url":"/en-us/components/use-current-state","index":100,"group":"Utils"},{"name":"useKeyboard","url":"/en-us/components/use-keyboard","index":100,"group":"Utils"},{"name":"useMediaQuery","url":"/en-us/components/use-media-query","index":100,"group":"Utils"}]}]},{"name":"customization","children":[]}]
+[{"name":"guide","children":[{"name":"Getting Started","children":[{"name":"Introduction","url":"/en-us/guide/introduction","index":5,"group":"Getting Started"},{"name":"Installation","url":"/en-us/guide/installation","index":10,"group":"Getting Started"},{"name":"Server Render","url":"/en-us/guide/server-render","index":15,"group":"Getting Started"}]},{"name":"Customization","children":[{"name":"Colors","url":"/en-us/guide/colors","index":20,"group":"Customization"},{"name":"Themes","url":"/en-us/guide/themes","index":25,"group":"Customization"},{"name":"Scaleable","url":"/en-us/guide/scaleable","index":30,"group":"Customization"}]}]},{"name":"components","children":[{"name":"General","children":[{"name":"Text","url":"/en-us/components/text","index":10,"group":"General"},{"name":"Button","url":"/en-us/components/button","index":100,"group":"General"},{"name":"Code","url":"/en-us/components/code","index":100,"group":"General"},{"name":"Icons","url":"/en-us/components/icons","index":100,"group":"General"}]},{"name":"Layout","children":[{"name":"Grid","url":"/en-us/components/grid","index":100,"group":"Layout"},{"name":"Page","url":"/en-us/components/page","index":100,"group":"Layout"},{"name":"Spacer","url":"/en-us/components/spacer","index":100,"group":"Layout"}]},{"name":"Surfaces","children":[{"name":"Card","url":"/en-us/components/card","index":100,"group":"Surfaces"},{"name":"Collapse","url":"/en-us/components/collapse","index":100,"group":"Surfaces"},{"name":"Fieldset","url":"/en-us/components/fieldset","index":100,"group":"Surfaces"}]},{"name":"Data Entry","children":[{"name":"AutoComplete","url":"/en-us/components/auto-complete","index":100,"group":"Data Entry"},{"name":"Button Group","url":"/en-us/components/button-group","index":100,"group":"Data Entry"},{"name":"Checkbox","url":"/en-us/components/checkbox","index":100,"group":"Data Entry"},{"name":"Input","url":"/en-us/components/input","index":100,"group":"Data Entry"},{"name":"Radio","url":"/en-us/components/radio","index":100,"group":"Data Entry"},{"name":"Select","url":"/en-us/components/select","index":100,"group":"Data Entry"},{"name":"Slider","url":"/en-us/components/slider","index":100,"group":"Data Entry"},{"name":"Textarea","url":"/en-us/components/textarea","index":100,"group":"Data Entry"},{"name":"Toggle","url":"/en-us/components/toggle","index":100,"group":"Data Entry"}]},{"name":"Data Display","children":[{"name":"Avatar","url":"/en-us/components/avatar","index":100,"group":"Data Display"},{"name":"Badge","url":"/en-us/components/badge","index":100,"group":"Data Display"},{"name":"Capacity","url":"/en-us/components/capacity","index":100,"group":"Data Display"},{"name":"Description","url":"/en-us/components/description","index":100,"group":"Data Display"},{"name":"Display","url":"/en-us/components/display","index":100,"group":"Data Display"},{"name":"Dot","url":"/en-us/components/dot","index":100,"group":"Data Display"},{"name":"File Tree","url":"/en-us/components/file-tree","index":100,"group":"Data Display"},{"name":"Image","url":"/en-us/components/image","index":100,"group":"Data Display"},{"name":"Keyboard","url":"/en-us/components/keyboard","index":100,"group":"Data Display"},{"name":"Popover","url":"/en-us/components/popover","index":100,"group":"Data Display"},{"name":"Table","url":"/en-us/components/table","index":100,"group":"Data Display"},{"name":"Tag","url":"/en-us/components/tag","index":100,"group":"Data Display"},{"name":"Tooltip","url":"/en-us/components/tooltip","index":100,"group":"Data Display"},{"name":"User","url":"/en-us/components/user","index":100,"group":"Data Display"}]},{"name":"Feedback","children":[{"name":"Drawer","url":"/en-us/components/drawer","index":100,"group":"Feedback"},{"name":"Loading","url":"/en-us/components/loading","index":100,"group":"Feedback"},{"name":"Modal","url":"/en-us/components/modal","index":100,"group":"Feedback"},{"name":"Note","url":"/en-us/components/note","index":100,"group":"Feedback"},{"name":"Progress","url":"/en-us/components/progress","index":100,"group":"Feedback"},{"name":"Rating","url":"/en-us/components/rating","index":100,"group":"Feedback"},{"name":"Spinner","url":"/en-us/components/spinner","index":100,"group":"Feedback"},{"name":"Toast","url":"/en-us/components/toast","index":100,"group":"Feedback"}]},{"name":"Navigation","children":[{"name":"Breadcrumbs","url":"/en-us/components/breadcrumbs","index":100,"group":"Navigation"},{"name":"Link","url":"/en-us/components/link","index":100,"group":"Navigation"},{"name":"Pagination","url":"/en-us/components/pagination","index":100,"group":"Navigation"},{"name":"Tabs","url":"/en-us/components/tabs","index":100,"group":"Navigation"},{"name":"Button Dropdown","url":"/en-us/components/button-dropdown","index":101,"group":"Navigation"}]},{"name":"Others","children":[{"name":"Divider","url":"/en-us/components/divider","index":100,"group":"Others"},{"name":"Snippet","url":"/en-us/components/snippet","index":100,"group":"Others"}]},{"name":"Utils","children":[{"name":"useBodyScroll","url":"/en-us/components/use-body-scroll","index":100,"group":"Utils"},{"name":"useClickAway","url":"/en-us/components/use-click-away","index":100,"group":"Utils"},{"name":"useClipboard","url":"/en-us/components/use-clipboard","index":100,"group":"Utils"},{"name":"useCurrentState","url":"/en-us/components/use-current-state","index":100,"group":"Utils"},{"name":"useKeyboard","url":"/en-us/components/use-keyboard","index":100,"group":"Utils"},{"name":"useMediaQuery","url":"/en-us/components/use-media-query","index":100,"group":"Utils"}]}]},{"name":"customization","children":[]}]
diff --git a/lib/data/metadata-zh-cn.json b/lib/data/metadata-zh-cn.json
index 9d2e82461..d2697db5d 100644
--- a/lib/data/metadata-zh-cn.json
+++ b/lib/data/metadata-zh-cn.json
@@ -1 +1 @@
-[{"name":"guide","children":[{"name":"快速上手","children":[{"name":"什么是 Geist UI","url":"/zh-cn/guide/introduction","index":5,"group":"快速上手"},{"name":"安装","url":"/zh-cn/guide/installation","index":10,"group":"快速上手"},{"name":"服务端渲染","url":"/zh-cn/guide/server-render","index":15,"group":"快速上手"}]},{"name":"定制化","children":[{"name":"色彩","url":"/zh-cn/guide/colors","index":5,"group":"定制化"},{"name":"主题","url":"/zh-cn/guide/themes","index":10,"group":"定制化"},{"name":"可伸缩性","url":"/zh-cn/guide/scaleable","index":20,"group":"定制化"}]}],"localeName":"上手指南"},{"name":"components","children":[{"name":"通用","children":[{"name":"文本 Text","url":"/zh-cn/components/text","index":10,"group":"通用"},{"name":"按钮 Button","url":"/zh-cn/components/button","index":100,"group":"通用"},{"name":"代码 Code","url":"/zh-cn/components/code","index":100,"group":"通用"},{"name":"图标 Icons","url":"/zh-cn/components/icons","index":100,"group":"通用"}]},{"name":"布局","children":[{"name":"栅格 Grid","url":"/zh-cn/components/grid","index":100,"group":"布局"},{"name":"页面 Page","url":"/zh-cn/components/page","index":100,"group":"布局"},{"name":"间距 Spacer","url":"/zh-cn/components/spacer","index":100,"group":"布局"}]},{"name":"表面","children":[{"name":"卡片 Card","url":"/zh-cn/components/card","index":100,"group":"表面"},{"name":"折叠框 Collapse","url":"/zh-cn/components/collapse","index":100,"group":"表面"},{"name":"控件组 Fieldset","url":"/zh-cn/components/fieldset","index":100,"group":"表面"}]},{"name":"数据录入","children":[{"name":"按钮组 Button Group","url":"/zh-cn/components/button-group","index":100,"group":"数据录入"},{"name":"复选框 Checkbox","url":"/zh-cn/components/checkbox","index":100,"group":"数据录入"},{"name":"输入框 Input","url":"/zh-cn/components/input","index":100,"group":"数据录入"},{"name":"单选框 Radio","url":"/zh-cn/components/radio","index":100,"group":"数据录入"},{"name":"选择器 Select","url":"/zh-cn/components/select","index":100,"group":"数据录入"},{"name":"滑动输入 Slider","url":"/zh-cn/components/slider","index":100,"group":"数据录入"},{"name":"文本输入框 Textarea","url":"/zh-cn/components/textarea","index":100,"group":"数据录入"},{"name":"开关 Toggle","url":"/zh-cn/components/toggle","index":100,"group":"数据录入"},{"name":"自动完成 Autocomplete","url":"/zh-cn/components/auto-complete","index":104,"group":"数据录入"}]},{"name":"数据展示","children":[{"name":"头像 Avatar","url":"/zh-cn/components/avatar","index":100,"group":"数据展示"},{"name":"徽章 Badge","url":"/zh-cn/components/badge","index":100,"group":"数据展示"},{"name":"容量 Capacity","url":"/zh-cn/components/capacity","index":100,"group":"数据展示"},{"name":"描述 Description","url":"/zh-cn/components/description","index":100,"group":"数据展示"},{"name":"陈列框 Display","url":"/zh-cn/components/display","index":100,"group":"数据展示"},{"name":"点 Dot","url":"/zh-cn/components/dot","index":100,"group":"数据展示"},{"name":"文件树 File Tree","url":"/zh-cn/components/file-tree","index":100,"group":"数据展示"},{"name":"图片 Image","url":"/zh-cn/components/image","index":100,"group":"数据展示"},{"name":"键盘 keyboard","url":"/zh-cn/components/keyboard","index":100,"group":"数据展示"},{"name":"气泡卡片 Popover","url":"/zh-cn/components/popover","index":100,"group":"数据展示"},{"name":"表格 Table","url":"/zh-cn/components/table","index":100,"group":"数据展示"},{"name":"标签 Tag","url":"/zh-cn/components/tag","index":100,"group":"数据展示"},{"name":"文字提示 Tooltip","url":"/zh-cn/components/tooltip","index":100,"group":"数据展示"},{"name":"用户 User","url":"/zh-cn/components/user","index":100,"group":"数据展示"}]},{"name":"反馈","children":[{"name":"加载中 Loading","url":"/zh-cn/components/loading","index":100,"group":"反馈"},{"name":"对话框 Modal","url":"/zh-cn/components/modal","index":100,"group":"反馈"},{"name":"提示 Note","url":"/zh-cn/components/note","index":100,"group":"反馈"},{"name":"进度条 Progress","url":"/zh-cn/components/progress","index":100,"group":"反馈"},{"name":"评分 Rating","url":"/zh-cn/components/rating","index":100,"group":"反馈"},{"name":"指示器 Spinner","url":"/zh-cn/components/spinner","index":100,"group":"反馈"},{"name":"通知 Toast","url":"/zh-cn/components/toast","index":100,"group":"反馈"}]},{"name":"导航","children":[{"name":"面包屑 Breadcrumbs","url":"/zh-cn/components/breadcrumbs","index":100,"group":"导航"},{"name":"链接 Link","url":"/zh-cn/components/link","index":100,"group":"导航"},{"name":"分页 Pagination","url":"/zh-cn/components/pagination","index":100,"group":"导航"},{"name":"选项卡 Tabs","url":"/zh-cn/components/tabs","index":100,"group":"导航"},{"name":"下拉按钮 Btn Dropdown","url":"/zh-cn/components/button-dropdown","index":105,"group":"导航"}]},{"name":"其他","children":[{"name":"分割线 Divider","url":"/zh-cn/components/divider","index":100,"group":"其他"},{"name":"片段 Snippet","url":"/zh-cn/components/snippet","index":100,"group":"其他"}]},{"name":"工具包","children":[{"name":"锁定滚动 useBodyScroll","url":"/zh-cn/components/use-body-scroll","index":100,"group":"工具包"},{"name":"点击他处 useClickAway","url":"/zh-cn/components/use-click-away","index":100,"group":"工具包"},{"name":"剪切板 useClipboard","url":"/zh-cn/components/use-clipboard","index":100,"group":"工具包"},{"name":" 当前值 useCurrentState","url":"/zh-cn/components/use-current-state","index":100,"group":"工具包"},{"name":"键盘事件 useKeyboard","url":"/zh-cn/components/use-keyboard","index":100,"group":"工具包"},{"name":"媒体查询 useMediaQuery","url":"/zh-cn/components/use-media-query","index":100,"group":"工具包"}]}],"localeName":"所有组件"},{"name":"customization","children":[],"localeName":"定制化"}]
+[{"name":"guide","children":[{"name":"快速上手","children":[{"name":"什么是 Geist UI","url":"/zh-cn/guide/introduction","index":5,"group":"快速上手"},{"name":"安装","url":"/zh-cn/guide/installation","index":10,"group":"快速上手"},{"name":"服务端渲染","url":"/zh-cn/guide/server-render","index":15,"group":"快速上手"}]},{"name":"定制化","children":[{"name":"色彩","url":"/zh-cn/guide/colors","index":5,"group":"定制化"},{"name":"主题","url":"/zh-cn/guide/themes","index":10,"group":"定制化"},{"name":"可伸缩性","url":"/zh-cn/guide/scaleable","index":20,"group":"定制化"}]}],"localeName":"上手指南"},{"name":"components","children":[{"name":"通用","children":[{"name":"文本 Text","url":"/zh-cn/components/text","index":10,"group":"通用"},{"name":"按钮 Button","url":"/zh-cn/components/button","index":100,"group":"通用"},{"name":"代码 Code","url":"/zh-cn/components/code","index":100,"group":"通用"},{"name":"图标 Icons","url":"/zh-cn/components/icons","index":100,"group":"通用"}]},{"name":"布局","children":[{"name":"栅格 Grid","url":"/zh-cn/components/grid","index":100,"group":"布局"},{"name":"页面 Page","url":"/zh-cn/components/page","index":100,"group":"布局"},{"name":"间距 Spacer","url":"/zh-cn/components/spacer","index":100,"group":"布局"}]},{"name":"表面","children":[{"name":"卡片 Card","url":"/zh-cn/components/card","index":100,"group":"表面"},{"name":"折叠框 Collapse","url":"/zh-cn/components/collapse","index":100,"group":"表面"},{"name":"控件组 Fieldset","url":"/zh-cn/components/fieldset","index":100,"group":"表面"}]},{"name":"数据录入","children":[{"name":"按钮组 Button Group","url":"/zh-cn/components/button-group","index":100,"group":"数据录入"},{"name":"复选框 Checkbox","url":"/zh-cn/components/checkbox","index":100,"group":"数据录入"},{"name":"输入框 Input","url":"/zh-cn/components/input","index":100,"group":"数据录入"},{"name":"单选框 Radio","url":"/zh-cn/components/radio","index":100,"group":"数据录入"},{"name":"选择器 Select","url":"/zh-cn/components/select","index":100,"group":"数据录入"},{"name":"滑动输入 Slider","url":"/zh-cn/components/slider","index":100,"group":"数据录入"},{"name":"文本输入框 Textarea","url":"/zh-cn/components/textarea","index":100,"group":"数据录入"},{"name":"开关 Toggle","url":"/zh-cn/components/toggle","index":100,"group":"数据录入"},{"name":"自动完成 Autocomplete","url":"/zh-cn/components/auto-complete","index":104,"group":"数据录入"}]},{"name":"数据展示","children":[{"name":"头像 Avatar","url":"/zh-cn/components/avatar","index":100,"group":"数据展示"},{"name":"徽章 Badge","url":"/zh-cn/components/badge","index":100,"group":"数据展示"},{"name":"容量 Capacity","url":"/zh-cn/components/capacity","index":100,"group":"数据展示"},{"name":"描述 Description","url":"/zh-cn/components/description","index":100,"group":"数据展示"},{"name":"陈列框 Display","url":"/zh-cn/components/display","index":100,"group":"数据展示"},{"name":"点 Dot","url":"/zh-cn/components/dot","index":100,"group":"数据展示"},{"name":"文件树 File Tree","url":"/zh-cn/components/file-tree","index":100,"group":"数据展示"},{"name":"图片 Image","url":"/zh-cn/components/image","index":100,"group":"数据展示"},{"name":"键盘 keyboard","url":"/zh-cn/components/keyboard","index":100,"group":"数据展示"},{"name":"气泡卡片 Popover","url":"/zh-cn/components/popover","index":100,"group":"数据展示"},{"name":"表格 Table","url":"/zh-cn/components/table","index":100,"group":"数据展示"},{"name":"标签 Tag","url":"/zh-cn/components/tag","index":100,"group":"数据展示"},{"name":"文字提示 Tooltip","url":"/zh-cn/components/tooltip","index":100,"group":"数据展示"},{"name":"用户 User","url":"/zh-cn/components/user","index":100,"group":"数据展示"}]},{"name":"反馈","children":[{"name":"抽屉 Drawer","url":"/zh-cn/components/drawer","index":100,"group":"反馈"},{"name":"加载中 Loading","url":"/zh-cn/components/loading","index":100,"group":"反馈"},{"name":"对话框 Modal","url":"/zh-cn/components/modal","index":100,"group":"反馈"},{"name":"提示 Note","url":"/zh-cn/components/note","index":100,"group":"反馈"},{"name":"进度条 Progress","url":"/zh-cn/components/progress","index":100,"group":"反馈"},{"name":"评分 Rating","url":"/zh-cn/components/rating","index":100,"group":"反馈"},{"name":"指示器 Spinner","url":"/zh-cn/components/spinner","index":100,"group":"反馈"},{"name":"通知 Toast","url":"/zh-cn/components/toast","index":100,"group":"反馈"}]},{"name":"导航","children":[{"name":"面包屑 Breadcrumbs","url":"/zh-cn/components/breadcrumbs","index":100,"group":"导航"},{"name":"链接 Link","url":"/zh-cn/components/link","index":100,"group":"导航"},{"name":"分页 Pagination","url":"/zh-cn/components/pagination","index":100,"group":"导航"},{"name":"选项卡 Tabs","url":"/zh-cn/components/tabs","index":100,"group":"导航"},{"name":"下拉按钮 Btn Dropdown","url":"/zh-cn/components/button-dropdown","index":105,"group":"导航"}]},{"name":"其他","children":[{"name":"分割线 Divider","url":"/zh-cn/components/divider","index":100,"group":"其他"},{"name":"片段 Snippet","url":"/zh-cn/components/snippet","index":100,"group":"其他"}]},{"name":"工具包","children":[{"name":"锁定滚动 useBodyScroll","url":"/zh-cn/components/use-body-scroll","index":100,"group":"工具包"},{"name":"点击他处 useClickAway","url":"/zh-cn/components/use-click-away","index":100,"group":"工具包"},{"name":"剪切板 useClipboard","url":"/zh-cn/components/use-clipboard","index":100,"group":"工具包"},{"name":" 当前值 useCurrentState","url":"/zh-cn/components/use-current-state","index":100,"group":"工具包"},{"name":"键盘事件 useKeyboard","url":"/zh-cn/components/use-keyboard","index":100,"group":"工具包"},{"name":"媒体查询 useMediaQuery","url":"/zh-cn/components/use-media-query","index":100,"group":"工具包"}]}],"localeName":"所有组件"},{"name":"customization","children":[],"localeName":"定制化"}]
diff --git a/package.json b/package.json
index 16d39a18a..13438b4a3 100644
--- a/package.json
+++ b/package.json
@@ -59,7 +59,10 @@
"@mapbox/rehype-prism": "^0.6.0",
"@mdx-js/loader": "^1.6.22",
"@next/mdx": "^11.0.0",
+ "@testing-library/dom": "^8.1.0",
+ "@testing-library/jest-dom": "^5.14.1",
"@testing-library/react-hooks": "^7.0.0",
+ "@testing-library/user-event": "^13.2.0",
"@types/enzyme": "^3.10.8",
"@types/jest": "^26.0.23",
"@types/react": "^17.0.11",
diff --git a/pages/en-us/components/drawer.mdx b/pages/en-us/components/drawer.mdx
new file mode 100644
index 000000000..84dd2451a
--- /dev/null
+++ b/pages/en-us/components/drawer.mdx
@@ -0,0 +1,105 @@
+import { Layout, Playground, Attributes } from 'lib/components'
+import { Drawer, Button } from 'components'
+
+export const meta = {
+ title: 'Drawer',
+ group: 'Feedback',
+}
+
+## Drawer
+
+An interactive element fixed to the edge of the screen.
+
+ {
+ const [state, setState] = React.useState(false)
+ return (
+
+
+
setState(false)} placement="right">
+ Drawer
+ This is a drawer
+
+ Some content contained within the drawer.
+
+
+
+ )
+}
+`}
+/>
+
+ {
+ const [state, setState] = React.useState(false)
+ const [placement, setPlacement] = React.useState('')
+ const open = (text) => {
+ setPlacement(text)
+ setState(true)
+ }
+ return (
+
+
+
+
+
+
setState(false)} placement={placement}>
+ Drawer
+ This is a drawer
+
+ Some content contained within the drawer.
+
+
+
+ )
+}
+`}
+/>
+
+
+Drawer.Props
+
+| Attribute | Description | Type | Accepted values | Default |
+| ------------------------ | -------------------------------- | ----------------------------------- | -------------------------- | ------- |
+| **visible** | open or close | `boolean` | - | `false` |
+| **onClose** | close event | `() => void` | - | - |
+| **onContentClick** | event from modal content | `(e: MouseEvent) => void` | - | - |
+| **wrapClassName** | className of the drawer dialog | `string` | - | - |
+| **keyboard** | press Esc to close drawer | `boolean` | - | `true` |
+| **disableBackdropClick** | click background and don't close | `boolean` | - | `false` |
+| **placement** | position of the drawer | [DrawerPlacement](#drawerplacement) | - | `right` |
+| ... | native props | `HTMLAttributes` | `'name', 'className', ...` | - |
+
+Drawer.Title.Props
+
+| Attribute | Description | Type | Accepted values | Default |
+| --------- | ------------ | ---------------- | ------------------------ | ------- |
+| ... | native props | `HTMLAttributes` | `'id', 'className', ...` | - |
+
+Drawer.Subtitle.Props
+
+| Attribute | Description | Type | Accepted values | Default |
+| --------- | ------------ | ---------------- | ------------------------ | ------- |
+| ... | native props | `HTMLAttributes` | `'id', 'className', ...` | - |
+
+Drawer.Content.Props
+
+| Attribute | Description | Type | Accepted values | Default |
+| --------- | ------------ | ---------------- | ------------------------ | ------- |
+| ... | native props | `HTMLAttributes` | `'id', 'className', ...` | - |
+
+DrawerPlacement
+
+```ts
+type DrawerPlacement = 'top' | 'bottom' | 'right' | 'left'
+```
+
+
+
+export default ({ children }) => {children}
diff --git a/pages/zh-cn/components/drawer.mdx b/pages/zh-cn/components/drawer.mdx
new file mode 100644
index 000000000..4645b394e
--- /dev/null
+++ b/pages/zh-cn/components/drawer.mdx
@@ -0,0 +1,105 @@
+import { Layout, Playground, Attributes } from 'lib/components'
+import { Drawer, Button } from 'components'
+
+export const meta = {
+ title: '抽屉 Drawer',
+ group: '反馈',
+}
+
+## Drawer / 抽屉
+
+固定在屏幕边缘的可交互元素组。
+
+ {
+ const [state, setState] = React.useState(false)
+ return (
+
+
+
setState(false)} placement="right">
+ 标题
+ 子标题
+
+ Geist UI 是我最爱的组件库。
+
+
+
+ )
+}
+`}
+/>
+
+ {
+ const [state, setState] = React.useState(false)
+ const [placement, setPlacement] = React.useState('')
+ const open = (text) => {
+ setPlacement(text)
+ setState(true)
+ }
+ return (
+
+
+
+
+
+
setState(false)} placement={placement}>
+ 标题
+ 子标题
+
+ Geist UI 是我最爱的组件库。
+
+
+
+ )
+}
+`}
+/>
+
+
+Drawer.Props
+
+| 属性 | 描述 | 类型 | 推荐值 | 默认 |
+| ------------------------ | ---------------------- | ----------------------------------- | -------------------------- | ------- |
+| **visible** | 打开或关闭 | `boolean` | - | `false` |
+| **onClose** | 关闭事件 | `() => void` | - | - |
+| **onContentClick** | 抽屉内部元素点击事件 | `(e: MouseEvent) => void` | - | - |
+| **wrapClassName** | 抽屉弹出内容的类名 | `string` | - | - |
+| **keyboard** | 按下 Esc 键关闭元素 | `boolean` | - | `true` |
+| **disableBackdropClick** | 点击背景层时不关闭抽屉 | `boolean` | - | `false` |
+| **placement** | 抽屉相对于屏幕的位置 | [DrawerPlacement](#drawerplacement) | - | `right` |
+| ... | 原生属性 | `HTMLAttributes` | `'name', 'className', ...` | - |
+
+Drawer.Title.Props
+
+| 属性 | 描述 | 类型 | 推荐值 | 默认 |
+| ---- | -------- | ---------------- | ------------------------ | ---- |
+| ... | 原生属性 | `HTMLAttributes` | `'id', 'className', ...` | - |
+
+Drawer.Subtitle.Props
+
+| 属性 | 描述 | 类型 | 推荐值 | 默认 |
+| ---- | -------- | ---------------- | ------------------------ | ---- |
+| ... | 原生属性 | `HTMLAttributes` | `'id', 'className', ...` | - |
+
+Drawer.Content.Props
+
+| 属性 | 描述 | 类型 | 推荐值 | 默认 |
+| ---- | -------- | ---------------- | ------------------------ | ---- |
+| ... | 原生属性 | `HTMLAttributes` | `'id', 'className', ...` | - |
+
+DrawerPlacement
+
+```ts
+type DrawerPlacement = 'top' | 'bottom' | 'right' | 'left'
+```
+
+
+
+export default ({ children }) => {children}
diff --git a/tests/setup.js b/tests/setup.ts
similarity index 55%
rename from tests/setup.js
rename to tests/setup.ts
index f4eb12b79..1fb2ede15 100644
--- a/tests/setup.js
+++ b/tests/setup.ts
@@ -1,8 +1,9 @@
-const enzyme = require('enzyme')
+import '@testing-library/jest-dom/extend-expect'
+import enzyme from 'enzyme'
/**
* The official repository does not currently support React 17
* https://github.com/enzymejs/enzyme/issues/2429
*/
-const Adapter = require('@wojtekmaj/enzyme-adapter-react-17')
+import Adapter from '@wojtekmaj/enzyme-adapter-react-17'
enzyme.configure({ adapter: new Adapter() })