Skip to content

Commit 8a460c3

Browse files
authored
fix(ResizablePanel): new drag look (#545)
1 parent c467337 commit 8a460c3

File tree

4 files changed

+96
-14
lines changed

4 files changed

+96
-14
lines changed

.changeset/empty-actors-carry.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@cube-dev/ui-kit': patch
3+
---
4+
5+
New drag appearance for ResizablePanel to avoid confusion with a scrollbar.

src/_internal/hooks/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ export * from './use-timer';
66
export * from './use-effect-once';
77
export * from './use-warn';
88
export * from './use-is-first-render';
9+
export * from './use-debounced-value';
+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { useState, useEffect, useRef } from 'react';
2+
3+
export function useDebouncedValue<T>(
4+
value: T,
5+
delay: number = 300,
6+
maxWait?: number,
7+
): T {
8+
const [debouncedValue, setDebouncedValue] = useState(value);
9+
const lastValueRef = useRef(value);
10+
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
11+
const maxWaitTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
12+
13+
useEffect(() => {
14+
// If value returns to the original while waiting, clear timeouts
15+
if (value === lastValueRef.current) {
16+
if (timeoutRef.current) clearTimeout(timeoutRef.current);
17+
if (maxWaitTimeoutRef.current) clearTimeout(maxWaitTimeoutRef.current);
18+
return;
19+
}
20+
21+
// Handle delay
22+
timeoutRef.current = setTimeout(() => {
23+
setDebouncedValue(value);
24+
lastValueRef.current = value;
25+
if (maxWaitTimeoutRef.current) {
26+
clearTimeout(maxWaitTimeoutRef.current);
27+
maxWaitTimeoutRef.current = null;
28+
}
29+
}, delay);
30+
31+
// Handle maxWait if provided
32+
if (maxWait && !maxWaitTimeoutRef.current) {
33+
maxWaitTimeoutRef.current = setTimeout(() => {
34+
setDebouncedValue(value);
35+
lastValueRef.current = value;
36+
if (timeoutRef.current) {
37+
clearTimeout(timeoutRef.current);
38+
timeoutRef.current = null;
39+
}
40+
}, maxWait);
41+
}
42+
43+
return () => {
44+
if (timeoutRef.current) clearTimeout(timeoutRef.current);
45+
if (maxWaitTimeoutRef.current) clearTimeout(maxWaitTimeoutRef.current);
46+
};
47+
}, [value, delay, maxWait]);
48+
49+
return debouncedValue;
50+
}

src/components/layout/ResizablePanel.tsx

+40-14
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useHover, useMove } from 'react-aria';
33

44
import { BasePropsWithoutChildren, Styles, tasty } from '../../tasty/index';
55
import { mergeProps, useCombinedRefs } from '../../utils/react';
6-
import { useEvent } from '../../_internal/hooks';
6+
import { useDebouncedValue, useEvent } from '../../_internal/hooks';
77

88
import { Panel, CubePanelProps } from './Panel';
99

@@ -103,31 +103,48 @@ const HandlerElement = tasty({
103103
},
104104

105105
Drag: {
106-
hide: {
107-
'': false,
108-
disabled: true,
106+
display: 'grid',
107+
gap: '2bw',
108+
flow: {
109+
'': 'row',
110+
horizontal: 'column',
111+
},
112+
gridColumns: {
113+
'': '3px 3px 3px 3px 3px',
114+
horizontal: 'auto',
115+
},
116+
gridRows: {
117+
'': 'auto',
118+
horizontal: '3px 3px 3px 3px 3px',
109119
},
110120
width: {
111-
'': '3x',
121+
'': 'auto',
112122
horizontal: '3px',
113123
},
114124
height: {
115125
'': '3px',
116-
horizontal: '3x',
117-
},
118-
radius: true,
119-
fill: {
120-
'': '#dark-03',
121-
'hovered | drag': '#dark-02',
122-
disabled: '#dark-04',
126+
horizontal: 'auto',
123127
},
124128
inset: {
125129
'': '3px 50% auto auto',
126130
horizontal: '50% 3px auto auto',
127131
},
132+
transform: {
133+
'': 'translate(-50%, 0)',
134+
horizontal: 'translate(0, -50%)',
135+
},
128136
position: 'absolute',
129137
transition: 'theme',
130138
},
139+
140+
DragPart: {
141+
radius: true,
142+
fill: {
143+
'': '#dark-03',
144+
'hovered | drag': '#dark-02',
145+
disabled: '#dark-04',
146+
},
147+
},
131148
},
132149
});
133150

@@ -139,14 +156,15 @@ const Handler = (props: HandlerProps) => {
139156
const { direction = 'right', isDisabled } = props;
140157
const { hoverProps, isHovered } = useHover({});
141158
const isHorizontal = direction === 'left' || direction === 'right';
159+
const localIsHovered = useDebouncedValue(isHovered, 150);
142160

143161
return (
144162
<HandlerElement
145163
{...mergeProps(
146164
hoverProps,
147165
{
148166
mods: {
149-
hovered: isHovered,
167+
hovered: localIsHovered,
150168
horizontal: isHorizontal,
151169
disabled: isDisabled,
152170
},
@@ -156,7 +174,15 @@ const Handler = (props: HandlerProps) => {
156174
)}
157175
>
158176
<div data-element="Track" />
159-
<div data-element="Drag" />
177+
{!isDisabled ? (
178+
<div data-element="Drag">
179+
<div data-element="DragPart" />
180+
<div data-element="DragPart" />
181+
<div data-element="DragPart" />
182+
<div data-element="DragPart" />
183+
<div data-element="DragPart" />
184+
</div>
185+
) : null}
160186
</HandlerElement>
161187
);
162188
};

0 commit comments

Comments
 (0)