Skip to content

Commit 0bfe0a6

Browse files
authored
feat: add BaseDrawer Component (#4)
1 parent d4d6b08 commit 0bfe0a6

File tree

9 files changed

+267
-0
lines changed

9 files changed

+267
-0
lines changed

config/menus/desktop.ts

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const desktopComponent: ComponentMenus = {
1414
],
1515
feedback: [
1616
'src/base-modal',
17+
'src/base-drawer',
1718
],
1819
guidance: [],
1920
other: [],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { useUnmount } from 'ahooks'
2+
import React from 'react'
3+
import BaseDrawer from '..'
4+
import { act, fireEvent, render, sleep } from '../../../../../tests/utils'
5+
6+
describe('BaseModal', () => {
7+
const clear = async function clear() {
8+
await act(async () => {
9+
jest.runAllTimers()
10+
await sleep()
11+
})
12+
}
13+
14+
it('custom classname should take effect', () => {
15+
render(
16+
<BaseDrawer
17+
drawerProps={{ visible: true, className: 'custom-classname' }}
18+
/>,
19+
)
20+
21+
expect(document.body.querySelectorAll('.custom-classname').length).toBe(1)
22+
})
23+
24+
it('click children should open modal', async () => {
25+
const Demo = () => {
26+
return (
27+
<BaseDrawer
28+
drawerProps={{ className: 'modal-open' }}
29+
>
30+
<button className='open-modal-btn'>open</button>
31+
</BaseDrawer>
32+
)
33+
}
34+
35+
const { container } = render(<Demo />)
36+
37+
fireEvent.click(container.querySelectorAll('.open-modal-btn')[0])
38+
39+
await clear()
40+
41+
expect(document.body.querySelectorAll('.modal-open')).toHaveLength(1)
42+
})
43+
44+
it('默认情况下,点击关闭时应该销毁弹窗内容', async () => {
45+
const unMountFn = jest.fn()
46+
47+
const Content = () => {
48+
useUnmount(unMountFn)
49+
return <>Content</>
50+
}
51+
52+
const { container } = render(
53+
<BaseDrawer drawerContent={<Content />}>
54+
<button className='open-modal-btn'>open</button>
55+
</BaseDrawer>,
56+
)
57+
58+
fireEvent.click(container.querySelectorAll('.open-modal-btn')[0])
59+
60+
await clear()
61+
62+
// Click the close icon to close
63+
fireEvent.click(document.body.querySelectorAll('.ant-modal-close')[0])
64+
65+
await clear()
66+
67+
expect(unMountFn).toHaveBeenCalled()
68+
})
69+
70+
it('存在 onClick 时,点击触发器应该触发 onClick 事件,默认不打开弹窗,需手动打开', async () => {
71+
const onClick = jest.fn()
72+
const { container } = render(
73+
<BaseDrawer
74+
onClick={onClick}
75+
drawerProps={{ className: 'modal-open' }}
76+
>
77+
<button className='open-modal-btn'>open</button>
78+
</BaseDrawer>,
79+
)
80+
81+
fireEvent.click(container.querySelectorAll('.open-modal-btn')[0])
82+
83+
await clear()
84+
85+
expect(document.body.querySelectorAll('.modal-open')).toHaveLength(0)
86+
87+
expect(onClick).toHaveBeenCalled()
88+
expect(onClick.mock.calls[0][1]).toEqual({
89+
open: expect.any(Function),
90+
close: expect.any(Function),
91+
})
92+
93+
onClick.mockImplementation((_, modalAction) => {
94+
modalAction.open()
95+
})
96+
97+
fireEvent.click(container.querySelectorAll('.open-modal-btn')[0])
98+
99+
expect(document.body.querySelectorAll('.modal-open')).toHaveLength(1)
100+
})
101+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import React, { useRef } from 'react'
2+
import { Button } from 'antd'
3+
import type { BaseModalAction } from '@template-pro/desktop-ui'
4+
import { BaseDrawer } from '@template-pro/desktop-ui'
5+
6+
const Content = ({ drawerAction }: any) => (
7+
<>
8+
<h3>这是抽屉内容</h3>
9+
<Button onClick={drawerAction.close}>
10+
点击关闭抽屉
11+
</Button>
12+
</>
13+
)
14+
15+
function BaseModalDemo() {
16+
const modalRef = useRef<BaseModalAction>()
17+
18+
return (
19+
<BaseDrawer
20+
ref={modalRef}
21+
drawerProps={{
22+
title: '标题',
23+
footer: (
24+
<Button
25+
onClick={() => modalRef.current?.close()}>
26+
通过 Ref 关闭
27+
</Button>
28+
),
29+
}}
30+
drawerContent={<Content />}
31+
>
32+
<Button>打开抽屉</Button>
33+
</BaseDrawer>
34+
)
35+
}
36+
37+
export default BaseModalDemo
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React from 'react'
2+
import { BaseDrawer } from '@template-pro/desktop-ui'
3+
import { Button, message } from 'antd'
4+
5+
function BaseModalBaseDemo() {
6+
return (
7+
<BaseDrawer
8+
drawerContent="这是抽屉内容"
9+
onClick={(event, drawerAction) => {
10+
window.console.log({ event, drawerAction })
11+
message.info({
12+
content: '1秒后打开抽屉',
13+
duration: 1,
14+
onClose() {
15+
drawerAction.open()
16+
},
17+
})
18+
}}
19+
>
20+
<Button>Click me</Button>
21+
</BaseDrawer>
22+
)
23+
}
24+
25+
export default BaseModalBaseDemo
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# BaseDrawer
2+
3+
## 基础用法
4+
5+
<code hideActions='["CSB", "EXTERNAL"]' src="./demo/Base.tsx" />
6+
7+
8+
## 通过事件手动控制打开
9+
10+
<code hideActions='["CSB", "EXTERNAL"]' src="./demo/Event.tsx" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import Drawer, { type DrawerProps } from 'antd/es/drawer'
2+
import { useBoolean } from 'ahooks'
3+
import classNames from 'classnames'
4+
import React, { useImperativeHandle, useRef } from 'react'
5+
import { defaultPrefixCls } from '../constants'
6+
import { isDOMTypeElement, isElement } from '../_utils/is'
7+
8+
export interface BaseDrawerAction {
9+
close: () => void
10+
open: () => void
11+
}
12+
13+
export interface BaseDrawerProps {
14+
children?: React.ReactNode
15+
drawerContent?: React.ReactNode
16+
onClick?: (
17+
e: React.MouseEvent<HTMLElement>,
18+
modalAction: BaseDrawerAction
19+
) => void
20+
drawerProps?: DrawerProps
21+
}
22+
23+
const BaseDrawer: React.ForwardRefRenderFunction<unknown, BaseDrawerProps> = (props, ref) => {
24+
const [drawerVisible, { setTrue: open, setFalse: close }] = useBoolean(false)
25+
26+
const drawerActionRef = useRef<BaseDrawerAction>({ open, close })
27+
28+
const { children, drawerContent, onClick, drawerProps = {} } = props
29+
30+
const {
31+
onClose,
32+
className,
33+
footer = null,
34+
...restDrawerProps
35+
} = drawerProps
36+
37+
useImperativeHandle(ref, () => drawerActionRef.current, [drawerActionRef])
38+
39+
const handleButtonClick = (event: React.MouseEvent<HTMLElement>) => {
40+
if (onClick)
41+
return onClick(event, drawerActionRef.current)
42+
43+
return open()
44+
}
45+
46+
const handleDrawerClose = (event: React.MouseEvent<HTMLElement>) => {
47+
if (onClose)
48+
onClose(event)
49+
50+
return close()
51+
}
52+
53+
// ======================== buttonNode ========================
54+
let buttonNode: React.ReactNode = children
55+
if (React.isValidElement(children))
56+
buttonNode = React.cloneElement<any>(children, { onClick: handleButtonClick })
57+
58+
// ======================== drawerContent ========================
59+
let childrenNode: React.ReactNode = drawerContent
60+
if (isElement(childrenNode) && !isDOMTypeElement(childrenNode)) {
61+
childrenNode = React.cloneElement<any>(childrenNode, {
62+
drawerAction: drawerActionRef.current,
63+
})
64+
}
65+
66+
return (
67+
<>
68+
{buttonNode}
69+
<Drawer
70+
open={drawerVisible}
71+
onClose={handleDrawerClose}
72+
className={classNames(`${defaultPrefixCls}-base-drawer`, className)}
73+
footer={footer}
74+
destroyOnClose
75+
{...restDrawerProps}
76+
>
77+
{childrenNode}
78+
</Drawer>
79+
</>
80+
)
81+
}
82+
83+
export default React.forwardRef(BaseDrawer)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@import '../../styles/themes/index';
2+
@import '~antd/es/drawer/style/index.less';
3+
4+
.@{cls-prefix}-base-drawer {
5+
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import './index.less'

packages/desktop-ui/src/index.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
export type { BaseModalAction, BaseModalProps } from './base-modal'
22
export { default as BaseModal } from './base-modal'
33

4+
export type { BaseDrawerProps, BaseDrawerAction } from './base-drawer'
5+
export { default as BaseDrawer } from './base-drawer'
6+
47
export type { IgnoreEmojiInputProps } from './ignore-emoji-input'
58
export { default as IgnoreEmojiInput } from './ignore-emoji-input'
69

0 commit comments

Comments
 (0)