diff --git a/README.md b/README.md index 64ffdfec..4e45b9d0 100755 --- a/README.md +++ b/README.md @@ -41,35 +41,39 @@ ReactDom.render( ## API -| props | type | default | description | -| ------------------ | --------------------------------------------------------------------------- | -------------------------------------- | ----------------------------------------------------------------------------- | -| className | string | null | - | -| classNames | { mask?: string; content?: string; wrapper?: string; } | - | pass className to target area | -| styles | { mask?: CSSProperties; content?: CSSProperties; wrapper?: CSSProperties; } | - | pass style to target area | -| prefixCls | string | 'drawer' | prefix class | -| width | string \| number | null | drawer content wrapper width, drawer level transition width | -| height | string \| number | null | drawer content wrapper height, drawer level transition height | -| open | boolean | false | open or close menu | -| defaultOpen | boolean | false | default open menu | -| placement | string | `left` | `left` `top` `right` `bottom` | -| level | string \| array | `all` | With the drawer level element. `all`/ null / className / id / tagName / array | -| levelMove | number \| array \| func | null | level move value. default is drawer width | -| duration | string | `.3s` | level animation duration | -| ease | string | `cubic-bezier(0.78, 0.14, 0.15, 0.86)` | level animation timing function | -| getContainer | string \| func \| HTMLElement | `body` | Return the mount node for Drawer. if is `null` use React.creactElement | -| 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 | -| afterOpenChange | func | null | transition end callback(open) | -| onClose | func | null | close click function | -| keyboard | boolean | true | Whether support press esc to close | -| 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 | +| props | type | default | description | +| --------------- | --------------------------------------------------------------------------- | -------------------------------------- | ----------------------------------------------------------------------------- | +| className | string | null | - | +| classNames | { mask?: string; content?: string; wrapper?: string; } | - | pass className to target area | +| styles | { mask?: CSSProperties; content?: CSSProperties; wrapper?: CSSProperties; } | - | pass style to target area | +| prefixCls | string | 'drawer' | prefix class | +| width | string \| number | null | drawer content wrapper width, drawer level transition width | +| height | string \| number | null | drawer content wrapper height, drawer level transition height | +| open | boolean | false | open or close menu | +| defaultOpen | boolean | false | default open menu | +| placement | string | `left` | `left` `top` `right` `bottom` | +| level | string \| array | `all` | With the drawer level element. `all`/ null / className / id / tagName / array | +| levelMove | number \| array \| func | null | level move value. default is drawer width | +| duration | string | `.3s` | level animation duration | +| ease | string | `cubic-bezier(0.78, 0.14, 0.15, 0.86)` | level animation timing function | +| getContainer | string \| func \| HTMLElement | `body` | Return the mount node for Drawer. if is `null` use React.creactElement | +| 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 | +| afterOpenChange | func | null | transition end callback(open) | +| onClose | func | null | close click function | +| keyboard | boolean | true | Whether support press esc to close | +| autoFocus | boolean | true | Whether focusing on the drawer after it opened | +| resizable | boolean | false | Whether the drawer can be resized by dragging | +| 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 | +| onResize | (size: number) => void | - | Trigger when drawer is being resized | +| onResizeStart | () => void | - | Trigger when resize starts | +| onResizeEnd | () => void | - | Trigger when resize ends | > 2.0 Rename `onMaskClick` -> `onClose`, add `maskClosable`. diff --git a/assets/index.less b/assets/index.less index c08cc6a6..ea5d875b 100755 --- a/assets/index.less +++ b/assets/index.less @@ -30,7 +30,7 @@ &-content-wrapper { position: absolute; z-index: @zIndex; - overflow: hidden; + // overflow: hidden; transition: transform @duration; &-hidden { @@ -58,223 +58,301 @@ background: #fff; pointer-events: auto; } + + // Resizable styles + &-resizable-line { + position: absolute; + z-index: 2; + pointer-events: auto; + + &:hover { + background-color: #1890ff !important; + } + + &-dragging { + background-color: #1890ff !important; + } + + &-left { + top: 0; + bottom: 0; + right: -3px; + width: 6px; + cursor: ew-resize; + } + + &-right { + top: 0; + bottom: 0; + left: -3px; + width: 6px; + cursor: ew-resize; + } + + &-top { + left: 0; + right: 0; + bottom: -3px; + height: 6px; + cursor: ns-resize; + } + + &-bottom { + left: 0; + right: 0; + top: -3px; + height: 6px; + cursor: ns-resize; + } + } } -// .@{drawer} { -// position: fixed; -// z-index: 9999; -// transition: width 0s ease @duration, height 0s ease @duration, transform @duration @ease-in-out-circ; -// >* { -// transition: transform @duration @ease-in-out-circ, opacity @duration @ease-in-out-circ, box-shadow @duration @ease-in-out-circ; -// } -// &.@{drawer}-open { -// transition: transform @duration @ease-in-out-circ; -// } -// & &-mask { -// background: #000; -// opacity: 0; -// width: 100%; -// height: 0; -// position: absolute; -// top: 0; -// left: 0; -// transition: opacity @duration @ease-in-out-circ, -// height 0s ease @duration; -// } -// &-content-wrapper { -// position: absolute; -// background: #fff; -// } -// &-content { -// overflow: auto; -// z-index: 1; -// position: relative; -// } -// &-handle { -// position: absolute; -// top: 72px; -// width: 41px; -// height: 40px; -// cursor: pointer; -// z-index: 0; -// text-align: center; -// line-height: 40px; -// font-size: 16px; -// display: flex; -// justify-content: center; -// align-items: center; -// background: #fff; -// &-icon { -// width: 14px; -// height: 2px; -// background: #333; -// position: relative; -// transition: background @duration @ease-in-out-circ; -// &:before, -// &:after { -// content: ''; -// display: block; -// position: absolute; -// background: #333; -// width: 100%; -// height: 2px; -// transition: transform @duration @ease-in-out-circ; -// } -// &:before { -// top: -5px; -// } -// &:after { -// top: 5px; -// } -// } -// } -// &-left, -// &-right { -// width: 0%; -// height: 100%; -// .@{drawer}-content-wrapper, -// .@{drawer}-content { -// height: 100%; -// } -// &.@{drawer}-open { -// width: 100%; -// &.no-mask { -// width: 0%; -// } -// } -// } -// &-left { -// top: 0; -// left: 0; -// .@{drawer} { -// &-handle { -// right: -40px; -// box-shadow: 2px 0 8px rgba(0, 0, 0, .15); -// border-radius: 0 4px 4px 0; -// } -// } -// &.@{drawer}-open { -// .@{drawer} { -// &-content-wrapper { -// box-shadow: 2px 0 8px rgba(0, 0, 0, .15); -// } -// } -// } -// } -// &-right { -// top: 0; -// right: 0; -// .@{drawer} { -// &-content-wrapper { -// right: 0; -// } -// &-handle { -// left: -40px; -// box-shadow: -2px 0 8px rgba(0, 0, 0, .15); -// border-radius: 4px 0 0 4px; -// } -// } -// &.@{drawer}-open { -// & .@{drawer} { -// &-content-wrapper { -// box-shadow: -2px 0 8px rgba(0, 0, 0, .15); -// } -// } -// &.no-mask { -// // https://github.com/ant-design/ant-design/issues/18607 -// right: 1px; -// transform: translateX(1px); -// } -// } -// } -// &-top, -// &-bottom { -// width: 100%; -// height: 0%; -// .@{drawer}-content-wrapper, -// .@{drawer}-content { -// width: 100%; -// } -// .@{drawer}-content { -// height: 100%; -// } -// &.@{drawer}-open { -// height: 100%; -// &.no-mask { -// height: 0%; -// } -// } - -// .@{drawer} { -// &-handle { -// left: 50%; -// margin-left: -20px; -// } -// } -// } -// &-top { -// top: 0; -// left: 0; -// .@{drawer} { -// &-handle { -// top: auto; -// bottom: -40px; -// box-shadow: 0 2px 8px rgba(0, 0, 0, .15); -// border-radius: 0 0 4px 4px; -// } -// } -// &.@{drawer}-open { -// .@{drawer} { -// &-content-wrapper { -// box-shadow: 0 2px 8px rgba(0, 0, 0, .15); -// } -// } -// } -// } -// &-bottom { -// bottom: 0; -// left: 0; -// .@{drawer} { -// &-content-wrapper { -// bottom: 0; -// } -// &-handle { -// top: -40px; -// box-shadow: 0 -2px 8px rgba(0, 0, 0, .15); -// border-radius: 4px 4px 0 0; -// } -// } -// &.@{drawer}-open { -// .@{drawer} { -// &-content-wrapper { -// box-shadow: 0 -2px 8px rgba(0, 0, 0, .15); -// } -// } -// &.no-mask { -// // https://github.com/ant-design/ant-design/issues/18607 -// bottom: 1px; -// transform: translateY(1px); -// } -// } -// } -// &.@{drawer}-open { -// .@{drawer} { -// &-mask { -// opacity: .3; -// height: 100%; -// transition: opacity 0.3s @ease-in-out-circ; -// } -// &-handle { -// &-icon { -// background: transparent; -// &:before { -// transform: translateY(5px) rotate(45deg); -// } -// &:after { -// transform: translateY(-5px) rotate(-45deg); -// } -// } -// } -// } -// } -// } +.@{prefixCls} { + overflow: hidden; + z-index: 9999; + transition: + width 0s ease @duration, + height 0s ease @duration, + transform @duration @ease-in-out-circ; + + > * { + transition: + transform @duration @ease-in-out-circ, + opacity @duration @ease-in-out-circ, + box-shadow @duration @ease-in-out-circ; + } + &.@{prefixCls}-open { + transition: transform @duration @ease-in-out-circ; + } + + & &-mask { + background: #000; + opacity: 0; + width: 100%; + height: 0; + position: absolute; + top: 0; + left: 0; + transition: + opacity @duration @ease-in-out-circ, + height 0s ease @duration; + } + + &-content-wrapper { + position: absolute; + background: #fff; + } + + &-content { + overflow: auto; + z-index: 1; + position: relative; + } + + &-handle { + position: absolute; + top: 72px; + width: 41px; + height: 40px; + cursor: pointer; + z-index: 0; + text-align: center; + line-height: 40px; + font-size: 16px; + display: flex; + justify-content: center; + align-items: center; + background: #fff; + + &-icon { + width: 14px; + height: 2px; + background: #333; + position: relative; + transition: background @duration @ease-in-out-circ; + + &::before, + &::after { + content: ''; + display: block; + position: absolute; + background: #333; + width: 100%; + height: 2px; + transition: transform @duration @ease-in-out-circ; + } + + &::before { + top: -5px; + } + + &::after { + top: 5px; + } + } + } + + &-left, + &-right { + width: 0%; + height: 100%; + .@{prefixCls}-content-wrapper, + .@{prefixCls}-content { + height: 100%; + } + &.@{prefixCls}-open { + width: 100%; + + &.no-mask { + width: 0%; + } + } + } + + &-left { + top: 0; + left: 0; + .@{prefixCls} { + &-handle { + right: -40px; + box-shadow: 2px 0 8px rgb(0 0 0 / 15%); + border-radius: 0 4px 4px 0; + } + } + &.@{prefixCls}-open { + .@{prefixCls} { + &-content-wrapper { + box-shadow: 2px 0 8px rgb(0 0 0 / 15%); + } + } + } + } + + &-right { + top: 0; + right: 0; + .@{prefixCls} { + &-content-wrapper { + right: 0; + } + + &-handle { + left: -40px; + box-shadow: -2px 0 8px rgb(0 0 0 / 15%); + border-radius: 4px 0 0 4px; + } + } + &.@{prefixCls}-open { + & .@{prefixCls} { + &-content-wrapper { + box-shadow: -2px 0 8px rgb(0 0 0 / 15%); + } + } + + &.no-mask { + // https://github.com/ant-design/ant-design/issues/18607 + right: 1px; + transform: translateX(1px); + } + } + } + + &-top, + &-bottom { + width: 100%; + height: 0%; + .@{prefixCls}-content-wrapper, + .@{prefixCls}-content { + width: 100%; + } + .@{prefixCls}-content { + height: 100%; + } + &.@{prefixCls}-open { + height: 100%; + + &.no-mask { + height: 0%; + } + } + + .@{prefixCls} { + &-handle { + left: 50%; + margin-left: -20px; + } + } + } + + &-top { + top: 0; + left: 0; + .@{prefixCls} { + &-handle { + top: auto; + bottom: -40px; + box-shadow: 0 2px 8px rgb(0 0 0 / 15%); + border-radius: 0 0 4px 4px; + } + } + &.@{prefixCls}-open { + .@{prefixCls} { + &-content-wrapper { + box-shadow: 0 2px 8px rgb(0 0 0 / 15%); + } + } + } + } + + &-bottom { + bottom: 0; + left: 0; + .@{prefixCls} { + &-content-wrapper { + bottom: 0; + } + + &-handle { + top: -40px; + box-shadow: 0 -2px 8px rgb(0 0 0 / 15%); + border-radius: 4px 4px 0 0; + } + } + &.@{prefixCls}-open { + .@{prefixCls} { + &-content-wrapper { + box-shadow: 0 -2px 8px rgb(0 0 0 / 15%); + } + } + + &.no-mask { + // https://github.com/ant-design/ant-design/issues/18607 + bottom: 1px; + transform: translateY(1px); + } + } + } + &.@{prefixCls}-open { + .@{prefixCls} { + &-mask { + opacity: 0.3; + height: 100%; + transition: opacity 0.3s @ease-in-out-circ; + } + + &-handle { + &-icon { + background: transparent; + + &::before { + transform: translateY(5px) rotate(45deg); + } + + &::after { + transform: translateY(-5px) rotate(-45deg); + } + } + } + } + } +} diff --git a/docs/demo/resizable.md b/docs/demo/resizable.md new file mode 100644 index 00000000..92fdad22 --- /dev/null +++ b/docs/demo/resizable.md @@ -0,0 +1,8 @@ +--- +title: resizable +nav: + title: Resizable + path: /resizable +--- + + diff --git a/docs/examples/assets/motion.less b/docs/examples/assets/motion.less index 5607b491..4aa5a2ed 100644 --- a/docs/examples/assets/motion.less +++ b/docs/examples/assets/motion.less @@ -83,4 +83,46 @@ } } } + + &-top { + .panel-motion(); + + &-enter, + &-appear { + transform: translateY(-100%); + + &-active { + transform: translateX(0); + } + } + + &-leave { + transform: translateX(0); + + &-active { + transform: translateY(-100%) !important; + } + } + } + + &-bottom { + .panel-motion(); + + &-enter, + &-appear { + transform: translateY(100%); + + &-active { + transform: translateX(0); + } + } + + &-leave { + transform: translateX(0); + + &-active { + transform: translateY(100%) !important; + } + } + } } diff --git a/docs/examples/getContainer-false.tsx b/docs/examples/getContainer-false.tsx index 28c7c3a0..daae79d0 100644 --- a/docs/examples/getContainer-false.tsx +++ b/docs/examples/getContainer-false.tsx @@ -52,6 +52,7 @@ export default () => { open={open} onClose={() => setOpen(false)} {...motionProps} + resizable > { + const [open, setOpen] = React.useState(false); + const [placement, setPlacement] = React.useState('right'); + const [resizable, setResizable] = React.useState(true); + + return ( +
+
+ + + Placement: + +
+ setOpen(false)} + resizable={resizable} + {...motionProps} + > +
+

+ You can drag the drawer edge to resize (only works when + "Resizable" is checked). +

+
+
+
+ ); +}; diff --git a/src/Drawer.tsx b/src/Drawer.tsx index 7231c481..474252ec 100644 --- a/src/Drawer.tsx +++ b/src/Drawer.tsx @@ -26,6 +26,11 @@ export interface DrawerProps panelRef?: React.Ref; classNames?: DrawerClassNames; styles?: DrawerStyles; + /** Whether to enable width resize */ + resizable?: boolean; + onResize?: (size: number) => void; + onResizeStart?: () => void; + onResizeEnd?: () => void; } const Drawer: React.FC = props => { @@ -48,6 +53,9 @@ const Drawer: React.FC = props => { onClick, onKeyDown, onKeyUp, + onResize, + onResizeStart, + onResizeEnd, // Refs panelRef, @@ -109,6 +117,9 @@ const Drawer: React.FC = props => { onClick, onKeyDown, onKeyUp, + onResize, + onResizeStart, + onResizeEnd, }; const drawerPopupProps = { diff --git a/src/DrawerPopup.tsx b/src/DrawerPopup.tsx index ce3dd7d5..171cda09 100644 --- a/src/DrawerPopup.tsx +++ b/src/DrawerPopup.tsx @@ -11,6 +11,7 @@ import type { DrawerPanelEvents, } from './DrawerPanel'; import DrawerPanel from './DrawerPanel'; +import ResizableLine from './ResizableLine'; import { parseWidthHeight } from './util'; import type { DrawerClassNames, DrawerStyles } from './inter'; @@ -75,6 +76,12 @@ export interface DrawerPopupProps // styles styles?: DrawerStyles; drawerRender?: (node: React.ReactNode) => React.ReactNode; + + // resizable + resizable?: boolean; + onResize?: (size: number) => void; + onResizeStart?: () => void; + onResizeEnd?: () => void; } const DrawerPopup: React.ForwardRefRenderFunction< @@ -123,9 +130,13 @@ const DrawerPopup: React.ForwardRefRenderFunction< onClick, onKeyDown, onKeyUp, + onResize, + onResizeStart, + onResizeEnd, styles, drawerRender, + resizable, } = props; // ================================ Refs ================================ @@ -283,6 +294,71 @@ const DrawerPopup: React.ForwardRefRenderFunction< onKeyUp, }; + // ============================ Resizable ============================ + const [currentSize, setCurrentSize] = React.useState(); + const [isDragging, setIsDragging] = React.useState(false); + const [maxSize, setMaxSize] = React.useState(0); + const wrapperRef = React.useRef(null); + + // Calculate maxSize based on container dimensions + const calculateMaxSize = React.useCallback(() => { + if (wrapperRef.current) { + const rect = wrapperRef.current.parentElement?.getBoundingClientRect(); + const newMaxSize = + placement === 'left' || placement === 'right' + ? (rect?.width ?? 0) + : (rect?.height ?? 0); + setMaxSize(newMaxSize); + } + }, [placement]); + + const handleResize = React.useCallback( + (size: number) => { + setCurrentSize(size); + onResize?.(size); + }, + [onResize], + ); + + const handleResizeStart = React.useCallback(() => { + // Recalculate maxSize to get the latest container size + calculateMaxSize(); + onResizeStart?.(); + }, [onResizeStart, calculateMaxSize]); + + const handleResizeEnd = React.useCallback(() => { + onResizeEnd?.(); + }, [onResizeEnd]); + + const handleDraggingChange = React.useCallback((dragging: boolean) => { + setIsDragging(dragging); + }, []); + + const dynamicWrapperStyle = React.useMemo(() => { + const style: React.CSSProperties = { ...wrapperStyle }; + + if (currentSize !== undefined && resizable) { + if (placement === 'left' || placement === 'right') { + style.width = currentSize; + } else { + style.height = currentSize; + } + } + + if (resizable) { + style.overflow = 'none'; + } else { + style.overflow = 'auto'; + } + + return style; + }, [wrapperStyle, currentSize, resizable, placement]); + + // Initialize maxSize calculation + React.useEffect(() => { + calculateMaxSize(); + }, [calculateMaxSize]); + const panelNode: React.ReactNode = ( + {resizable && ( + + )} {drawerRender ? drawerRender(content) : content} ); diff --git a/src/ResizableLine.tsx b/src/ResizableLine.tsx new file mode 100644 index 00000000..d81af804 --- /dev/null +++ b/src/ResizableLine.tsx @@ -0,0 +1,152 @@ +import classNames from 'classnames'; +import * as React from 'react'; +import type { Placement } from './Drawer'; + +export interface ResizableLineProps { + prefixCls?: string; + direction: Placement; + className?: string; + style?: React.CSSProperties; + minSize?: number; + maxSize?: number; + isDragging?: boolean; + onResize?: (size: number) => void; + onResizeEnd?: (size: number) => void; + onResizeStart?: (size: number) => void; + onDraggingChange?: (dragging: boolean) => void; +} + +const ResizableLine: React.FC = ({ + prefixCls = 'resizable', + direction, + className, + style, + minSize = 100, + maxSize, + isDragging = false, + onResize, + onResizeEnd, + onResizeStart, + onDraggingChange, +}) => { + const lineRef = React.useRef(null); + const [startPos, setStartPos] = React.useState(0); + const [startSize, setStartSize] = React.useState(0); + + const isHorizontal = direction === 'left' || direction === 'right'; + + const handleMouseDown = React.useCallback( + (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + + onDraggingChange?.(true); + + if (isHorizontal) { + setStartPos(e.clientX); + } else { + setStartPos(e.clientY); + } + + // Get the current size of the parent container + const parentElement = lineRef.current?.parentElement; + if (parentElement) { + const rect = parentElement.getBoundingClientRect(); + const currentSize = isHorizontal ? rect.width : rect.height; + setStartSize(currentSize); + onResizeStart?.(currentSize); + } + }, + [isHorizontal, onResizeStart, onDraggingChange], + ); + + const handleMouseMove = React.useCallback( + (e: MouseEvent) => { + if (!isDragging) return; + + const currentPos = isHorizontal ? e.clientX : e.clientY; + let delta = currentPos - startPos; + + // Adjust delta direction based on placement + if (direction === 'right' || direction === 'bottom') { + delta = -delta; + } + + let newSize = startSize + delta; + + // Apply min/max size limits + if (newSize < minSize) { + newSize = minSize; + } + // Only apply maxSize if it's a valid positive number + if (maxSize !== undefined && maxSize > 0 && newSize > maxSize) { + newSize = maxSize; + } + + onResize?.(newSize); + }, + [ + isDragging, + startPos, + startSize, + direction, + minSize, + maxSize, + onResize, + isHorizontal, + ], + ); + + const handleMouseUp = React.useCallback(() => { + if (isDragging) { + onDraggingChange?.(false); + + // Get the final size after resize + const parentElement = lineRef.current?.parentElement; + if (parentElement) { + const rect = parentElement.getBoundingClientRect(); + const finalSize = isHorizontal ? rect.width : rect.height; + onResizeEnd?.(finalSize); + } + } + }, [isDragging, onResizeEnd, isHorizontal, onDraggingChange]); + + React.useEffect(() => { + if (isDragging) { + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + + return () => { + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + }; + } + }, [isDragging, handleMouseMove, handleMouseUp]); + + const resizeLineClassName = classNames( + `${prefixCls}-line`, + `${prefixCls}-line-${direction}`, + { + [`${prefixCls}-line-dragging`]: isDragging, + }, + className, + ); + + const resizeLineStyle: React.CSSProperties = { + position: 'absolute', + zIndex: 2, + cursor: isHorizontal ? 'col-resize' : 'row-resize', + ...style, + }; + + return ( +
+ ); +}; + +export default ResizableLine; diff --git a/src/index.ts b/src/index.ts index 757f964f..ae3d3a68 100755 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ // export this package's api import Drawer from './Drawer'; -import type { DrawerProps } from './Drawer'; +import type { DrawerProps, Placement } from './Drawer'; -export type { DrawerProps }; +export type { DrawerProps, Placement }; export default Drawer; diff --git a/src/inter.ts b/src/inter.ts index f3f4fde1..8e96f953 100644 --- a/src/inter.ts +++ b/src/inter.ts @@ -2,10 +2,12 @@ export interface DrawerClassNames { mask?: string; wrapper?: string; section?: string; + resizableLine?: string; } export interface DrawerStyles { mask?: React.CSSProperties; wrapper?: React.CSSProperties; section?: React.CSSProperties; + resizableLine?: React.CSSProperties; } diff --git a/tests/index.spec.tsx b/tests/index.spec.tsx index d41e9493..4e58cd25 100755 --- a/tests/index.spec.tsx +++ b/tests/index.spec.tsx @@ -475,4 +475,43 @@ describe('rc-drawer-menu', () => { expect(document.querySelector('#test')).toBeTruthy(); unmount(); }); + + it('should support resizable', () => { + const { unmount } = render( + , + ); + + // Mock getBoundingClientRect for the content wrapper to simulate real DOM dimensions + const contentWrapper = document.querySelector( + '.rc-drawer-content-wrapper', + ) as HTMLElement; + const mockGetBoundingClientRect = jest.fn( + () => + ({ + width: 200, + height: 400, + top: 0, + left: 0, + bottom: 400, + right: 200, + x: 0, + y: 0, + toJSON: () => ({}), + }) as DOMRect, + ); + contentWrapper.getBoundingClientRect = mockGetBoundingClientRect; + + const resizableLine = document.querySelector('.rc-drawer-resizable-line'); + expect(resizableLine).toBeTruthy(); + + // Simulate drag from 200px to 100px (should reduce width by 100px) + fireEvent.mouseDown(resizableLine, { clientX: 200 }); + fireEvent.mouseMove(document, { clientX: 100, clientY: 0 }); + fireEvent.mouseUp(document, { clientX: 100, clientY: 0 }); + + expect(document.querySelector('.rc-drawer-content-wrapper')).toHaveStyle({ + width: '100px', + }); + unmount(); + }); });