Skip to content

Commit 7433b19

Browse files
committed
feat: 添加弹窗组件增强 hook
1 parent b3f5ed8 commit 7433b19

File tree

9 files changed

+277
-2
lines changed

9 files changed

+277
-2
lines changed

config/menus/utils.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ const utilsMenu = [
55
},
66
{
77
title: 'React Hooks',
8-
children: ['src/use-props-value'],
8+
children: [
9+
'src/use-props-value',
10+
'src/use-modal-enhanced',
11+
],
912
},
1013
// {
1114
// title: "Date & Time",

packages/utils/package.json

+4
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,9 @@
2020
},
2121
"publishConfig": {
2222
"registry": "http://nas.wxhboy.cn:98/"
23+
},
24+
"devDependencies": {
25+
"@types/react-transition-group": "^4.4.5",
26+
"react-transition-group": "^4.4.5"
2327
}
2428
}

packages/utils/src/index.ts

+7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
export { default as usePropsValue } from './use-props-value'
22
export { default as mergeProps } from './with-default-props'
33

4+
export type {
5+
UseModalEnhancedAction,
6+
UseModalEnhancedProps,
7+
UseModalEnhancedContext,
8+
} from './use-modal-enhanced'
9+
export { default as useModalEnhanced } from './use-modal-enhanced'
10+
411
// RegExp
512
export * as anyRule from './any-rule'
613

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import React from 'react'
2+
import type { UseModalEnhancedAction, UseModalEnhancedContext, UseModalEnhancedProps } from '@template-pro/utils'
3+
import { useModalEnhanced } from '@template-pro/utils'
4+
import type { ModalProps } from './Modal'
5+
import Modal from './Modal'
6+
7+
type EnhancedModalProps = ModalProps & UseModalEnhancedProps
8+
9+
const EnhancedModal = (props: EnhancedModalProps) => {
10+
const [
11+
visible,
12+
{ close },
13+
{ content, trigger },
14+
] = useModalEnhanced(props)
15+
16+
return (
17+
<>
18+
{trigger}
19+
<Modal
20+
visible={visible}
21+
title="EnhancedModal"
22+
{...props}
23+
onClose={close}
24+
>
25+
{content}
26+
</Modal>
27+
</>
28+
)
29+
}
30+
31+
// Final usage
32+
const Content = ({ enhancedAction }: Partial<UseModalEnhancedContext>) => (
33+
<>
34+
hello world
35+
<p>Content</p>
36+
<button onClick={enhancedAction?.close}>Content Close</button>
37+
</>
38+
)
39+
40+
function App() {
41+
const actionRef = React.useRef<UseModalEnhancedAction>(null)
42+
43+
return (
44+
<>
45+
<EnhancedModal
46+
actionRef={actionRef}
47+
content={<Content />}
48+
>
49+
<button>App Trigger</button>
50+
</EnhancedModal>
51+
52+
<button onClick={() => actionRef.current?.open()}>App actionRef.open</button>
53+
</>
54+
)
55+
}
56+
57+
export default App
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// copy: https://medium.com/tinyso/how-to-create-a-modal-component-in-react-from-basic-to-advanced-a3357a2a716a
2+
3+
import React, { useEffect } from 'react'
4+
import { CSSTransition } from 'react-transition-group'
5+
import './modal.css'
6+
7+
export interface ModalProps {
8+
visible?: boolean
9+
title?: string
10+
onClose?: () => void
11+
children?: React.ReactNode
12+
}
13+
14+
const Modal = (props: ModalProps) => {
15+
const closeOnEscapeKeyDown = (e: any) => {
16+
if ((e.charCode || e.keyCode) === 27)
17+
props.onClose?.()
18+
}
19+
20+
useEffect(() => {
21+
document.body.addEventListener('keydown', closeOnEscapeKeyDown)
22+
return function cleanup() {
23+
document.body.removeEventListener('keydown', closeOnEscapeKeyDown)
24+
}
25+
}, [])
26+
27+
return (
28+
<CSSTransition
29+
in={props.visible}
30+
unmountOnExit
31+
timeout={{ enter: 0, exit: 300 }}
32+
>
33+
<div className="modal" onClick={props.onClose}>
34+
<div className="modal-content" onClick={e => e.stopPropagation()}>
35+
<div className="modal-header">
36+
<h4 className="modal-title">{props.title}</h4>
37+
</div>
38+
<div className="modal-body">{props.children}</div>
39+
<div className="modal-footer">
40+
<button onClick={props.onClose} className="button">
41+
Close
42+
</button>
43+
</div>
44+
</div>
45+
</div>
46+
</CSSTransition>
47+
)
48+
}
49+
50+
export default Modal
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
.modal {
2+
position: fixed;
3+
left: 0;
4+
top: 0;
5+
right: 0;
6+
bottom: 0;
7+
background-color: rgba(0, 0, 0, 0.5);
8+
display: flex;
9+
align-items: center;
10+
justify-content: center;
11+
opacity: 0;
12+
transition: all 0.3s ease-in-out;
13+
pointer-events: none;
14+
z-index: 1000;
15+
}
16+
17+
.modal.enter-done {
18+
opacity: 1;
19+
pointer-events: visible;
20+
}
21+
22+
.modal.exit {
23+
opacity: 0;
24+
}
25+
26+
.modal-content {
27+
width: 500px;
28+
background-color: #fff;
29+
transition: all 0.3s ease-in-out;
30+
transform: translateY(-200px);
31+
}
32+
33+
.modal.enter-done .modal-content {
34+
transform: translateY(0);
35+
}
36+
37+
.modal.exit .modal-content {
38+
transform: translateY(-200px);
39+
}
40+
41+
.modal-header,
42+
.modal-footer {
43+
padding: 10px;
44+
}
45+
46+
.modal-title {
47+
margin: 0;
48+
}
49+
50+
.modal-body {
51+
padding: 10px;
52+
border-top: 1px solid #eee;
53+
border-bottom: 1px solid #eee;
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# useModalEnhanced
2+
3+
对弹窗组件进行增强
4+
5+
## 基础示例
6+
7+
<code hideActions='["CSB", "EXTERNAL"]' src="./demo/Basic.tsx" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import useBoolean from 'ahooks/es/useBoolean'
2+
import React from 'react'
3+
import { isDOMTypeElement, isElement } from '../is'
4+
5+
export interface UseModalEnhancedContext {
6+
enhancedAction: UseModalEnhancedAction
7+
}
8+
9+
export interface UseModalEnhancedProps {
10+
children?: React.ReactNode
11+
content?: React.ReactNode
12+
onClick?: (e: React.MouseEvent<HTMLElement>, enhancedAction: UseModalEnhancedAction) => void
13+
actionRef?: React.RefObject<UseModalEnhancedAction>
14+
}
15+
16+
export interface UseModalEnhancedAction {
17+
close: () => void
18+
open: () => void
19+
}
20+
21+
function useModalEnhanced(props: UseModalEnhancedProps = {}) {
22+
const {
23+
children,
24+
content,
25+
onClick,
26+
actionRef: actionRefProp,
27+
} = props
28+
29+
const [visible, { setTrue: open, setFalse: close }] = useBoolean(false)
30+
31+
const actionRef = React.useRef<UseModalEnhancedAction>({ open, close })
32+
33+
React.useImperativeHandle(actionRefProp, () => actionRef.current)
34+
35+
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
36+
if (onClick)
37+
return onClick(event, actionRef.current)
38+
39+
return open()
40+
}
41+
42+
// ======================== Trigger ========================
43+
let trigger: React.ReactNode = children
44+
if (React.isValidElement(children))
45+
trigger = React.cloneElement<any>(children, { onClick: handleClick })
46+
47+
// ======================== Content ========================
48+
let contentNode: React.ReactNode = content
49+
if (isElement<UseModalEnhancedContext>(contentNode) && !isDOMTypeElement(contentNode)) {
50+
contentNode = React.cloneElement<UseModalEnhancedContext>(contentNode, {
51+
enhancedAction: actionRef.current,
52+
})
53+
}
54+
55+
const contextHolder = { trigger, content: contentNode }
56+
const action = { open, close }
57+
58+
return [visible, action, contextHolder] as const
59+
}
60+
61+
export default useModalEnhanced

pnpm-lock.yaml

+33-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)