Skip to content

Commit 5341916

Browse files
committed
feat: improve Drawer resizable logic and add tests
1 parent dac7c38 commit 5341916

File tree

3 files changed

+190
-35
lines changed

3 files changed

+190
-35
lines changed

src/DrawerPopup.tsx

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,9 @@ const DrawerPopup: React.ForwardRefRenderFunction<
259259
// =========================== Panel ============================
260260
const motionProps = typeof motion === 'function' ? motion(placement) : motion;
261261

262+
// ============================ Resizable ============================
263+
const [currentSize, setCurrentSize] = React.useState<number>();
264+
262265
const wrapperStyle: React.CSSProperties = {};
263266

264267
if (pushed && pushDistance) {
@@ -280,9 +283,13 @@ const DrawerPopup: React.ForwardRefRenderFunction<
280283
}
281284

282285
if (placement === 'left' || placement === 'right') {
283-
wrapperStyle.width = parseWidthHeight(width);
286+
// Use currentSize if available (from resizing), otherwise use original width
287+
const finalWidth = currentSize !== undefined ? currentSize : width;
288+
wrapperStyle.width = parseWidthHeight(finalWidth);
284289
} else {
285-
wrapperStyle.height = parseWidthHeight(height);
290+
// Use currentSize if available (from resizing), otherwise use original height
291+
const finalHeight = currentSize !== undefined ? currentSize : height;
292+
wrapperStyle.height = parseWidthHeight(finalHeight);
286293
}
287294

288295
const eventHandlers = {
@@ -293,12 +300,25 @@ const DrawerPopup: React.ForwardRefRenderFunction<
293300
onKeyDown,
294301
onKeyUp,
295302
};
296-
297-
// ============================ Resizable ============================
298-
const [currentSize, setCurrentSize] = React.useState<number>();
299303
const [maxSize, setMaxSize] = React.useState(0);
300304
const wrapperRef = React.useRef<HTMLDivElement>(null);
301305

306+
// Update currentSize based on width/height and current placement
307+
const updateCurrentSize = React.useCallback(() => {
308+
const isHorizontal = placement === 'left' || placement === 'right';
309+
const targetSize = isHorizontal ? width : height;
310+
if (typeof targetSize === 'number') {
311+
setCurrentSize(targetSize);
312+
} else {
313+
setCurrentSize(undefined);
314+
}
315+
}, [placement, width, height]);
316+
317+
// Initialize and update currentSize
318+
React.useEffect(() => {
319+
updateCurrentSize();
320+
}, [updateCurrentSize]);
321+
302322
// Calculate maxSize based on container dimensions
303323
const calculateMaxSize = React.useCallback(() => {
304324
if (wrapperRef.current) {
@@ -338,7 +358,8 @@ const DrawerPopup: React.ForwardRefRenderFunction<
338358
minSize: 0,
339359
maxSize,
340360
disabled: !resizable,
341-
container: wrapperRef.current,
361+
containerRef: wrapperRef,
362+
currentSize,
342363
onResize: handleResize,
343364
onResizeStart: handleResizeStart,
344365
onResizeEnd: handleResizeEnd,
@@ -347,22 +368,14 @@ const DrawerPopup: React.ForwardRefRenderFunction<
347368
const dynamicWrapperStyle = React.useMemo(() => {
348369
const style: React.CSSProperties = { ...wrapperStyle };
349370

350-
if (currentSize !== undefined && resizable) {
351-
if (placement === 'left' || placement === 'right') {
352-
style.width = currentSize;
353-
} else {
354-
style.height = currentSize;
355-
}
356-
}
357-
358371
if (resizable) {
359372
style.overflow = 'visible';
360373
} else {
361374
style.overflow = 'hidden';
362375
}
363376

364377
return style;
365-
}, [wrapperStyle, currentSize, resizable, placement]);
378+
}, [wrapperStyle, resizable]);
366379

367380
// Initialize maxSize calculation
368381
React.useEffect(() => {

src/hooks/useDrag.ts

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ export interface UseDragOptions {
1010
minSize?: number;
1111
maxSize?: number;
1212
disabled?: boolean;
13-
container?: HTMLElement | null;
13+
containerRef?: React.RefObject<HTMLElement>;
14+
currentSize?: number;
1415
onResize?: (size: number) => void;
1516
onResizeEnd?: (size: number) => void;
1617
onResizeStart?: (size: number) => void;
@@ -34,15 +35,16 @@ export default function useDrag(options: UseDragOptions): UseDragReturn {
3435
minSize = 100,
3536
maxSize,
3637
disabled = false,
37-
container,
38+
containerRef,
39+
currentSize,
3840
onResize,
3941
onResizeEnd,
4042
onResizeStart,
4143
} = options;
4244

43-
const [isDragging, setIsDragging] = React.useState(false);
44-
const [startPos, setStartPos] = React.useState(0);
45-
const [startSize, setStartSize] = React.useState(0);
45+
const [isDragging, setIsDragging] = React.useState<boolean>(false);
46+
const [startPos, setStartPos] = React.useState<number>(0);
47+
const [startSize, setStartSize] = React.useState<number>(0);
4648

4749
const isHorizontal = direction === 'left' || direction === 'right';
4850

@@ -61,15 +63,21 @@ export default function useDrag(options: UseDragOptions): UseDragReturn {
6163
setStartPos(e.clientY);
6264
}
6365

64-
// Get the current size of the container
65-
if (container) {
66-
const rect = container.getBoundingClientRect();
67-
const currentSize = isHorizontal ? rect.width : rect.height;
68-
setStartSize(currentSize);
69-
onResizeStart?.(currentSize);
66+
// Use provided currentSize, or fallback to container size
67+
let startSize: number;
68+
if (currentSize !== undefined) {
69+
startSize = currentSize;
70+
} else if (containerRef?.current) {
71+
const rect = containerRef.current.getBoundingClientRect();
72+
startSize = isHorizontal ? rect.width : rect.height;
73+
} else {
74+
return; // No size information available
7075
}
76+
77+
setStartSize(startSize);
78+
onResizeStart?.(startSize);
7179
},
72-
[disabled, isHorizontal, container, onResizeStart],
80+
[disabled, isHorizontal, containerRef, currentSize, onResizeStart],
7381
);
7482

7583
const handleMouseMove = React.useCallback(
@@ -115,13 +123,13 @@ export default function useDrag(options: UseDragOptions): UseDragReturn {
115123
setIsDragging(false);
116124

117125
// Get the final size after resize
118-
if (container) {
119-
const rect = container.getBoundingClientRect();
126+
if (containerRef?.current) {
127+
const rect = containerRef.current.getBoundingClientRect();
120128
const finalSize = isHorizontal ? rect.width : rect.height;
121129
onResizeEnd?.(finalSize);
122130
}
123131
}
124-
}, [isDragging, disabled, container, onResizeEnd, isHorizontal]);
132+
}, [isDragging, disabled, containerRef, onResizeEnd, isHorizontal]);
125133

126134
React.useEffect(() => {
127135
if (isDragging) {

tests/index.spec.tsx

Lines changed: 139 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -476,16 +476,44 @@ describe('rc-drawer-menu', () => {
476476
unmount();
477477
});
478478

479-
it('should support resizable', () => {
479+
it('should support resizable horizontal', () => {
480480
const { unmount } = render(
481-
<Drawer resizable open placement="left" width={200} />,
481+
<div
482+
id="container"
483+
style={{ width: '400px', height: '400px', position: 'relative' }}
484+
>
485+
<Drawer
486+
getContainer={false}
487+
resizable
488+
open
489+
placement="left"
490+
width={200}
491+
/>
492+
</div>,
482493
);
483494

484-
// Mock getBoundingClientRect for the content wrapper to simulate real DOM dimensions
495+
// Mock getBoundingClientRect for the container and content wrapper
496+
const container = document.querySelector('#container') as HTMLElement;
485497
const contentWrapper = document.querySelector(
486498
'.rc-drawer-content-wrapper',
487499
) as HTMLElement;
488-
const mockGetBoundingClientRect = jest.fn(
500+
501+
const mockContainerGetBoundingClientRect = jest.fn(
502+
() =>
503+
({
504+
width: 400,
505+
height: 400,
506+
top: 0,
507+
left: 0,
508+
bottom: 400,
509+
right: 400,
510+
x: 0,
511+
y: 0,
512+
toJSON: () => ({}),
513+
}) as DOMRect,
514+
);
515+
516+
const mockWrapperGetBoundingClientRect = jest.fn(
489517
() =>
490518
({
491519
width: 200,
@@ -499,7 +527,9 @@ describe('rc-drawer-menu', () => {
499527
toJSON: () => ({}),
500528
}) as DOMRect,
501529
);
502-
contentWrapper.getBoundingClientRect = mockGetBoundingClientRect;
530+
531+
container.getBoundingClientRect = mockContainerGetBoundingClientRect;
532+
contentWrapper.getBoundingClientRect = mockWrapperGetBoundingClientRect;
503533

504534
const dragger = document.querySelector('.rc-drawer-resizable-dragger');
505535
expect(dragger).toBeTruthy();
@@ -512,6 +542,110 @@ describe('rc-drawer-menu', () => {
512542
expect(document.querySelector('.rc-drawer-content-wrapper')).toHaveStyle({
513543
width: '100px',
514544
});
545+
546+
unmount();
547+
});
548+
549+
it('should respect minSize and maxSize constraints', () => {
550+
const { unmount } = render(
551+
<div style={{ width: '500px', height: '400px', position: 'relative' }}>
552+
<Drawer
553+
getContainer={false}
554+
resizable
555+
open
556+
placement="left"
557+
width={200}
558+
/>
559+
</div>,
560+
);
561+
562+
// Helper to create mock getBoundingClientRect
563+
const createMockRect = (width: number): (() => DOMRect) =>
564+
jest.fn(
565+
() =>
566+
({
567+
width,
568+
height: 400,
569+
top: 0,
570+
left: 0,
571+
bottom: 400,
572+
right: width,
573+
x: 0,
574+
y: 0,
575+
toJSON: () => ({}),
576+
}) as DOMRect,
577+
);
578+
579+
// Mock wrapper (for useDrag) and its parent (for calculateMaxSize)
580+
const contentWrapper = document.querySelector(
581+
'.rc-drawer-content-wrapper',
582+
) as HTMLElement;
583+
const wrapperParent = contentWrapper.parentElement as HTMLElement;
584+
585+
contentWrapper.getBoundingClientRect = createMockRect(200);
586+
if (wrapperParent) {
587+
wrapperParent.getBoundingClientRect = createMockRect(500);
588+
}
589+
590+
const dragger = document.querySelector('.rc-drawer-resizable-dragger');
591+
expect(dragger).toBeTruthy();
592+
593+
// Test minSize constraint (minSize = 0)
594+
fireEvent.mouseDown(dragger, { clientX: 200 });
595+
fireEvent.mouseMove(document, { clientX: -100 });
596+
fireEvent.mouseUp(document, { clientX: -100 });
597+
598+
expect(document.querySelector('.rc-drawer-content-wrapper')).toHaveStyle({
599+
width: '0px',
600+
});
601+
602+
// Test maxSize constraint (maxSize = 500px from parent container)
603+
fireEvent.mouseDown(dragger, { clientX: 200 });
604+
fireEvent.mouseMove(document, { clientX: 800 });
605+
fireEvent.mouseUp(document, { clientX: 800 });
606+
607+
expect(document.querySelector('.rc-drawer-content-wrapper')).toHaveStyle({
608+
width: '500px',
609+
});
610+
611+
unmount();
612+
});
613+
614+
it('should support resizable vertical', () => {
615+
const { unmount } = render(
616+
<Drawer resizable open placement="top" height={200} />,
617+
);
618+
619+
// Mock getBoundingClientRect for the content wrapper to simulate real DOM dimensions
620+
const contentWrapper = document.querySelector(
621+
'.rc-drawer-content-wrapper',
622+
) as HTMLElement;
623+
const mockGetBoundingClientRect = jest.fn(
624+
() =>
625+
({
626+
width: 200,
627+
height: 400,
628+
top: 0,
629+
left: 0,
630+
bottom: 400,
631+
right: 200,
632+
x: 0,
633+
y: 0,
634+
toJSON: () => ({}),
635+
}) as DOMRect,
636+
);
637+
contentWrapper.getBoundingClientRect = mockGetBoundingClientRect;
638+
639+
const dragger = document.querySelector('.rc-drawer-resizable-dragger');
640+
expect(dragger).toBeTruthy();
641+
642+
fireEvent.mouseDown(dragger, { clientY: 200 });
643+
fireEvent.mouseMove(document, { clientX: 0, clientY: 100 });
644+
fireEvent.mouseUp(document, { clientX: 0, clientY: 100 });
645+
646+
expect(document.querySelector('.rc-drawer-content-wrapper')).toHaveStyle({
647+
height: '100px',
648+
});
515649
unmount();
516650
});
517651
});

0 commit comments

Comments
 (0)