From 2e8b67ada81fd6857a6fb3c6959f704088b2c41d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?kiner-tang=28=E6=96=87=E8=BE=89=29?= <1127031143@qq.com> Date: Tue, 30 May 2023 10:10:18 +0800 Subject: [PATCH] feat: Support body props (#419) * feat: support bodyProps * feat: support bodyProps * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code --- .dumirc.ts | 2 +- README.md | 11 +- docs/changelog.md | 2 +- docs/demo/bodyProps.md | 8 ++ docs/examples/bodyProps.tsx | 43 ++++++ docs/examples/change-remove.tsx | 168 ++++++++++++------------ docs/examples/change.tsx | 56 ++++---- docs/examples/{motion.tsx => motion.ts} | 0 docs/examples/multiple.tsx | 146 ++++++++++---------- docs/examples/no-mask.tsx | 30 ++--- docs/examples/placement.tsx | 59 ++++----- package.json | 2 +- src/Drawer.tsx | 18 ++- src/DrawerPanel.tsx | 37 +++++- src/DrawerPopup.tsx | 28 +++- tests/index.spec.tsx | 20 ++- 16 files changed, 367 insertions(+), 263 deletions(-) create mode 100644 docs/demo/bodyProps.md create mode 100755 docs/examples/bodyProps.tsx rename docs/examples/{motion.tsx => motion.ts} (100%) diff --git a/.dumirc.ts b/.dumirc.ts index 27e19900..263f4161 100644 --- a/.dumirc.ts +++ b/.dumirc.ts @@ -9,7 +9,7 @@ export default defineConfig({ mfsu: false, favicons: ['https://avatars0.githubusercontent.com/u/9441414?s=200&v=4'], themeConfig: { - name: 'Image', + name: 'Drawer', logo: 'https://avatars0.githubusercontent.com/u/9441414?s=200&v=4', }, }); diff --git a/README.md b/README.md index 8c008d3b..70d396ad 100755 --- a/README.md +++ b/README.md @@ -59,12 +59,17 @@ ReactDom.render( | showMask | boolean | true | mask is show | | maskClosable | boolean | true | Clicking on the mask (area outside the Drawer) to close the Drawer or not. | | maskStyle | CSSProperties | null | mask style | -| onChange | func | null | change callback(open) | | afterVisibleChange | func | null | transition end callback(open) | | onClose | func | null | close click function | -| keyboard | Boolean | true | Whether support press esc to close | +| keyboard | boolean | true | Whether support press esc to close | | contentWrapperStyle | CSSProperties | null | content wrapper style | -| autoFocus | Boolean | true | Whether focusing on the drawer after it opened | +| autoFocus | boolean | true | Whether focusing on the drawer after it opened | +| onMouseEnter | React.MouseEventHandler\ | - | Trigger when mouse enter drawer panel | +| onMouseOver | React.MouseEventHandler\ | - | Trigger when mouse over drawer panel | +| onMouseLeave | React.MouseEventHandler\ | - | Trigger when mouse leave drawer panel | +| onClick | React.MouseEventHandler\ | - | Trigger when mouse click drawer panel | +| onKeyDown | React.MouseEventHandler\ | - | Trigger when mouse keydown on drawer panel | +| onKeyUp | React.MouseEventHandler\ | - | Trigger when mouse keyup on drawer panel | > 2.0 Rename `onMaskClick` -> `onClose`, add `maskClosable`. diff --git a/docs/changelog.md b/docs/changelog.md index 94e06c89..1d722832 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,3 +1,3 @@ # ChangeLog - + diff --git a/docs/demo/bodyProps.md b/docs/demo/bodyProps.md new file mode 100644 index 00000000..d3125202 --- /dev/null +++ b/docs/demo/bodyProps.md @@ -0,0 +1,8 @@ +--- +title: bodyProps +nav: + title: Demo + path: /demo +--- + + diff --git a/docs/examples/bodyProps.tsx b/docs/examples/bodyProps.tsx new file mode 100755 index 00000000..15216937 --- /dev/null +++ b/docs/examples/bodyProps.tsx @@ -0,0 +1,43 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import React, { useState } from 'react'; +import Drawer from 'rc-drawer'; +import motionProps from './motion'; + +const Demo = () => { + const [open, setOpen] = useState(false); + const onTouchEnd = () => { + setOpen(false); + }; + const onSwitch = () => { + setOpen(c => !c); + }; + return ( +
+ { + console.log('transitionEnd: ', c); + }} + placement="right" + // width={400} + width="60%" + // Motion + {...motionProps} + onMouseEnter={() => { + console.log('mouseEnter'); + }} + onMouseLeave={() => { + console.log('mouseLeave'); + }} + > + content + +
+ +
+
+ ); +}; +export default Demo; diff --git a/docs/examples/change-remove.tsx b/docs/examples/change-remove.tsx index 4257f1df..5f70d8c1 100755 --- a/docs/examples/change-remove.tsx +++ b/docs/examples/change-remove.tsx @@ -14,97 +14,93 @@ import motionProps from './motion'; const SubMenu = Menu.SubMenu; const MenuItemGroup = Menu.ItemGroup; -class Demo extends React.Component { - public state = { - show: true, - }; - public componentDidMount() { + +function Demo() { + const [show, setShow] = React.useState(true); + React.useEffect(() => { setTimeout(() => { - this.setState({ - show: false, - }); + setShow(false); }, 2000); - } - public render() { - return ( -
- {this.state.show && ( - + {show && ( + + - + + Navigation One + + } > - - - Navigation One - - } - > - - Option 1 - Option 2 - - - Option 3 - Option 4 - - - - - Navigation Two - - } - > - Option 5 - Option 6 - - Option 7 - Option 8 - - - - - Navigation Three - - } - > - Option 9 - Option 10 - Option 11 - Option 12 + + Option 1 + Option 2 + + + Option 3 + Option 4 + + + + + Navigation Two + + } + > + Option 5 + Option 6 + + Option 7 + Option 8 - - - )} -
- 内容区块 -
+ + + + Navigation Three + + } + > + Option 9 + Option 10 + Option 11 + Option 12 + +
+
+ )} +
+ 内容区块
- ); - } +
+ ); } + export default Demo; diff --git a/docs/examples/change.tsx b/docs/examples/change.tsx index 57688169..7f093560 100755 --- a/docs/examples/change.tsx +++ b/docs/examples/change.tsx @@ -17,40 +17,30 @@ import motionProps from './motion'; const SubMenu = Menu.SubMenu; const MenuItemGroup = Menu.ItemGroup; -class Demo extends React.Component { - public state = { - open: true, - }; - public componentDidMount() { + +function Demo() { + const [open, setOpen] = React.useState(true); + + React.useEffect(() => { setTimeout(() => { - this.setState({ - open: false, - }); + setOpen(false); }, 2000); - } - public onChange = (bool: boolean) => { - console.log('change: ', bool); - }; - public onTouchEnd = () => { - this.setState({ - open: false, - }); - }; - public onSwitch = () => { - this.setState({ - open: !this.state.open, - }); + }, []); + + const onTouchEnd = () => { + setOpen(false); }; - public render() { - return ( -
+ + const onSwitch = () => { + this.setState(p => !p); + } + + return ( +
{ + open={open} + onClose={onTouchEnd} + afterOpenChange={(c: boolean) => { console.log('transitionEnd: ', c); }} width="20vw" @@ -124,7 +114,7 @@ class Demo extends React.Component { > 内容区块
- ); - } + ); } + export default Demo; diff --git a/docs/examples/motion.tsx b/docs/examples/motion.ts similarity index 100% rename from docs/examples/motion.tsx rename to docs/examples/motion.ts diff --git a/docs/examples/multiple.tsx b/docs/examples/multiple.tsx index 77c0481b..04eab18c 100755 --- a/docs/examples/multiple.tsx +++ b/docs/examples/multiple.tsx @@ -11,83 +11,77 @@ import '../../assets/index.less'; import './assets/index.less'; import motionProps from './motion'; -class Demo extends React.Component { - public state = { - open: true, - openChild: true, - openChildren: true, - }; - public onClick = () => { - this.setState({ - open: !this.state.open, - }); - }; - public onChildClick = () => { - this.setState({ - openChild: !this.state.openChild, - }); - }; - public onChildrenClick = e => { - this.setState({ - openChildren: e.currentTarget instanceof HTMLButtonElement, - }); - }; +function Demo() { + const [open, setOpen] = React.useState(true); + const [openChild, setOpenChild] = React.useState(true); + const [openChildren, setOpenChildren] = React.useState(true); - public render() { - return ( -
-
- -
- -
- - -
- 二级抽屉 - - -
三级抽屉
-
-
-
-
-
-
- ); + function onClick() { + setOpen(!open); + } + + function onChildClick() { + setOpenChild(!openChild); + } + + function onChildrenClick(e) { + setOpenChildren(e.currentTarget instanceof HTMLButtonElement); } + + return ( +
+
+ +
+ +
+ + +
+ 二级抽屉 + + +
三级抽屉
+
+
+
+
+
+
+ ); } + export default Demo; diff --git a/docs/examples/no-mask.tsx b/docs/examples/no-mask.tsx index 8940dd2f..3e6a3c71 100755 --- a/docs/examples/no-mask.tsx +++ b/docs/examples/no-mask.tsx @@ -16,21 +16,16 @@ import './assets/index.less'; const { SubMenu } = Menu; const MenuItemGroup = Menu.ItemGroup; -class Demo extends React.Component { - public state = { - open: false, - }; - public onSwitch = () => { - const { open } = this.state; - this.setState({ - open: !open, - }); - }; +function Demo() { + const [open, setOpen] = React.useState(true); - public render() { - return ( -
+ const onSwitch = () => { + setOpen(!open); + } + + return ( +
内容区块
- ); - } + ); } export default Demo; diff --git a/docs/examples/placement.tsx b/docs/examples/placement.tsx index 14b075d7..7563ae49 100755 --- a/docs/examples/placement.tsx +++ b/docs/examples/placement.tsx @@ -11,41 +11,35 @@ import 'antd/lib/style'; import '../../assets/index.less'; import './assets/index.less'; +import type { Placement } from '@/Drawer'; const SubMenu = Menu.SubMenu; const MenuItemGroup = Menu.ItemGroup; const Option = Select.Option; -class Demo extends React.Component { - public state = { - placement: 'right', - childShow: true, - width: '20vw', - height: null, - }; - public onChange = (value: string) => { - this.setState( - { - placement: value, - width: value === 'right' || value === 'left' ? '20vw' : null, - height: value === 'right' || value === 'left' ? null : '20vh', - childShow: false, // 删除子级,删除切换时的过渡动画。。。 - }, - () => { - this.setState({ - childShow: true, - }); - }, - ); - }; - public render() { - return ( -
- {this.state.childShow && ( +function Demo() { + const [placement, setPlacement] = React.useState('right'); + const [childShow, setChildShow] = React.useState(true); + const [width, setWidth] = React.useState('20vw'); + const [height, setHeight] = React.useState(null); + + const onChange = (value: string) => { + setPlacement(value as Placement); + setWidth(value === 'right' || value === 'left' ? '20vw' : null); + setHeight(value === 'right' || value === 'left' ? null : '20vh'); + setChildShow(false); // 删除子级,删除切换时的过渡动画。。。 + setTimeout(() => { + setChildShow(true); + }); + } + + return ( +
+ {childShow && ( @@ -126,8 +120,7 @@ class Demo extends React.Component {
- ); - } + ); } export default Demo; diff --git a/package.json b/package.json index 37941e6c..0b1385ed 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "devDependencies": { "@ant-design/icons": "^4.7.0", "@testing-library/jest-dom": "^5.11.9", - "@testing-library/react": "^13.0.0", + "@testing-library/react": "^14.0.0", "@types/classnames": "^2.2.9", "@types/jest": "^27.0.2", "@types/raf": "^3.4.0", diff --git a/src/Drawer.tsx b/src/Drawer.tsx index a87985fc..4879e150 100644 --- a/src/Drawer.tsx +++ b/src/Drawer.tsx @@ -5,11 +5,12 @@ import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect'; import DrawerPopup from './DrawerPopup'; import type { DrawerPopupProps } from './DrawerPopup'; import { warnCheck } from './util'; +import type { DrawerPanelEvents } from './DrawerPanel'; export type Placement = 'left' | 'top' | 'right' | 'bottom'; export interface DrawerProps - extends Omit { + extends Omit, DrawerPanelEvents { prefixCls?: string; open?: boolean; onClose?: (e: React.MouseEvent | React.KeyboardEvent) => void; @@ -31,6 +32,12 @@ const Drawer: React.FC = props => { forceRender, afterOpenChange, destroyOnClose, + onMouseEnter, + onMouseOver, + onMouseLeave, + onClick, + onKeyDown, + onKeyUp, } = props; const [animatedVisible, setAnimatedVisible] = React.useState(false); @@ -79,6 +86,14 @@ const Drawer: React.FC = props => { return null; } + const eventHandlers = { + onMouseEnter, + onMouseOver, + onMouseLeave, + onClick, + onKeyDown, + onKeyUp, + }; const drawerPopupProps = { ...props, open: mergedOpen, @@ -92,6 +107,7 @@ const Drawer: React.FC = props => { inline: getContainer === false, afterOpenChange: internalAfterOpenChange, ref: panelRef, + ...eventHandlers, }; return ( diff --git a/src/DrawerPanel.tsx b/src/DrawerPanel.tsx index db5a3e44..b610a606 100644 --- a/src/DrawerPanel.tsx +++ b/src/DrawerPanel.tsx @@ -1,11 +1,20 @@ -import * as React from 'react'; import classNames from 'classnames'; +import * as React from 'react'; export interface DrawerPanelRef { focus: VoidFunction; } -export interface DrawerPanelProps { +export interface DrawerPanelEvents { + onMouseEnter?: React.MouseEventHandler; + onMouseOver?: React.MouseEventHandler; + onMouseLeave?: React.MouseEventHandler; + onClick?: React.MouseEventHandler; + onKeyDown?: React.KeyboardEventHandler; + onKeyUp?: React.KeyboardEventHandler; +} + +export interface DrawerPanelProps extends DrawerPanelEvents { prefixCls: string; className?: string; style?: React.CSSProperties; @@ -14,7 +23,28 @@ export interface DrawerPanelProps { } const DrawerPanel = (props: DrawerPanelProps) => { - const { prefixCls, className, style, children, containerRef } = props; + const { + prefixCls, + className, + style, + children, + containerRef, + onMouseEnter, + onMouseOver, + onMouseLeave, + onClick, + onKeyDown, + onKeyUp, + } = props; + + const eventHandlers = { + onMouseEnter, + onMouseOver, + onMouseLeave, + onClick, + onKeyDown, + onKeyUp, + }; // =============================== Render =============================== @@ -28,6 +58,7 @@ const DrawerPanel = (props: DrawerPanelProps) => { aria-modal="true" role="dialog" ref={containerRef} + {...eventHandlers} > {children}
diff --git a/src/DrawerPopup.tsx b/src/DrawerPopup.tsx index 69412bdb..a6cbe340 100644 --- a/src/DrawerPopup.tsx +++ b/src/DrawerPopup.tsx @@ -1,12 +1,12 @@ -import * as React from 'react'; import classNames from 'classnames'; -import CSSMotion from 'rc-motion'; import type { CSSMotionProps } from 'rc-motion'; -import DrawerPanel from './DrawerPanel'; -import DrawerContext from './context'; -import type { DrawerContextProps } from './context'; +import CSSMotion from 'rc-motion'; import KeyCode from 'rc-util/lib/KeyCode'; import pickAttrs from 'rc-util/lib/pickAttrs'; +import * as React from 'react'; +import type { DrawerContextProps } from './context'; +import DrawerContext from './context'; +import DrawerPanel, { DrawerPanelEvents } from './DrawerPanel'; import { parseWidthHeight } from './util'; const sentinelStyle: React.CSSProperties = { @@ -23,7 +23,7 @@ export interface PushConfig { distance?: number | string; } -export interface DrawerPopupProps { +export interface DrawerPopupProps extends DrawerPanelEvents { prefixCls: string; open?: boolean; inline?: boolean; @@ -98,6 +98,12 @@ function DrawerPopup(props: DrawerPopupProps, ref: React.Ref) { // Events afterOpenChange, onClose, + onMouseEnter, + onMouseOver, + onMouseLeave, + onClick, + onKeyDown, + onKeyUp, } = props; // ================================ Refs ================================ @@ -250,6 +256,15 @@ function DrawerPopup(props: DrawerPopupProps, ref: React.Ref) { wrapperStyle.height = parseWidthHeight(height); } + const eventHandlers = { + onMouseEnter, + onMouseOver, + onMouseLeave, + onClick, + onKeyDown, + onKeyUp, + }; + const panelNode: React.ReactNode = ( ) { prefixCls={prefixCls} className={className} style={style} + {...eventHandlers} > {children} diff --git a/tests/index.spec.tsx b/tests/index.spec.tsx index 550703e2..8596a482 100755 --- a/tests/index.spec.tsx +++ b/tests/index.spec.tsx @@ -359,11 +359,29 @@ describe('rc-drawer-menu', () => { ); errSpy.mockRestore(); }); - + + it('pass data props to internal div', () => { const value = 'bamboo'; const { unmount } = render(); expect(document.querySelector('.rc-drawer-content-wrapper')).toHaveAttribute('data-attr',value); unmount(); }); + + it('support bodyProps', () => { + const enter = jest.fn(); + const leave = jest.fn(); + const { baseElement } = render( + , + ); + fireEvent.mouseOver(baseElement.querySelector('.rc-drawer-content')); + expect(enter).toHaveBeenCalled(); + fireEvent.mouseLeave(baseElement.querySelector('.rc-drawer-content')); + expect(leave).toHaveBeenCalled(); + }); });