Skip to content

Commit 40c6072

Browse files
authored
fix(ResizablePanel): round the size style (#541)
1 parent 09599a3 commit 40c6072

File tree

4 files changed

+99
-32
lines changed

4 files changed

+99
-32
lines changed

.changeset/proud-kangaroos-switch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@cube-dev/ui-kit': patch
3+
---
4+
5+
Smoother transition for ResizablePanel.'

.changeset/soft-months-beam.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@cube-dev/ui-kit': patch
3+
---
4+
5+
Round the output size style in ResizablePanel.

src/components/layout/ResizablePanel.stories.tsx

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,30 +13,30 @@ export default {
1313
const TemplateRight: StoryFn<CubeResizablePanelProps> = (args) => (
1414
<Panel isFlex isStretched height="min 30x" fill="#white">
1515
<ResizablePanel {...args} />
16-
<Panel fill="#light"></Panel>
16+
<Panel fill="#purple-04.10"></Panel>
1717
</Panel>
1818
);
1919

2020
const TemplateLeft: StoryFn<CubeResizablePanelProps> = (args) => {
2121
return (
2222
<Panel isFlex isStretched height="min 30x" fill="#white">
23-
<Panel fill="#light"></Panel>
23+
<Panel fill="#purple-04.10"></Panel>
2424
<ResizablePanel {...args} />
2525
</Panel>
2626
);
2727
};
2828

2929
const TemplateBottom: StoryFn<CubeResizablePanelProps> = (args) => (
30-
<Panel isFlex isStretched flow="column" height="min 30x" fill="#white">
30+
<Panel isFlex isStretched flow="column" fill="#white">
3131
<ResizablePanel {...args} />
32-
<Panel fill="#light"></Panel>
32+
<Panel fill="#purple-04.10"></Panel>
3333
</Panel>
3434
);
3535

3636
const TemplateTop: StoryFn<CubeResizablePanelProps> = (args) => {
3737
return (
38-
<Panel isFlex isStretched flow="column" height="min 30x" fill="#white">
39-
<Panel fill="#light"></Panel>
38+
<Panel isFlex isStretched flow="column" fill="#white">
39+
<Panel fill="#purple-04.10"></Panel>
4040
<ResizablePanel {...args} />
4141
</Panel>
4242
);
@@ -60,7 +60,14 @@ const TemplateControllable: StoryFn<CubeResizablePanelProps> = (args) => {
6060
const GridTemplate: StoryFn<CubeResizablePanelProps> = (args) => (
6161
<Panel isStretched height="min 30x" fill="#white" gridColumns="auto 1fr">
6262
<ResizablePanel size={300} {...args} />
63-
<Panel fill="#light"></Panel>
63+
<Panel fill="#purple-04.10"></Panel>
64+
</Panel>
65+
);
66+
67+
const TemplateDisabled: StoryFn<CubeResizablePanelProps> = (args) => (
68+
<Panel isFlex isStretched height="min 30x" fill="#white">
69+
<ResizablePanel {...args} />
70+
<Panel fill="#purple-04.10"></Panel>
6471
</Panel>
6572
);
6673

@@ -87,9 +94,10 @@ ResizeTop.args = {
8794
export const Controllable = TemplateControllable.bind({});
8895
Controllable.args = {
8996
direction: 'right',
97+
maxSize: 500,
9098
};
9199

92-
export const Disabled = TemplateRight.bind({});
100+
export const Disabled = TemplateDisabled.bind({});
93101
Disabled.args = {
94102
isDisabled: true,
95103
};

src/components/layout/ResizablePanel.tsx

Lines changed: 73 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ForwardedRef, forwardRef, useEffect, useState } from 'react';
1+
import { ForwardedRef, forwardRef, useEffect, useMemo, useState } from 'react';
22
import { useHover, useMove } from 'react-aria';
33

44
import { BasePropsWithoutChildren, Styles, tasty } from '../../tasty/index';
@@ -19,35 +19,45 @@ export interface CubeResizablePanelProps extends CubePanelProps {
1919
}
2020

2121
const HandlerElement = tasty({
22+
qa: 'ResizeHandler',
2223
styles: {
2324
// The real size is slightly bigger than the visual one.
2425
width: {
25-
'': 'auto',
26+
'': 'initial',
2627
horizontal: '9px',
28+
'disabled & horizontal': '1bw',
2729
},
2830
height: {
2931
'': '9px',
30-
horizontal: 'auto',
32+
horizontal: 'initial',
33+
'disabled & !horizontal': '1bw',
3134
},
3235
top: {
33-
'': 'initial',
34-
horizontal: 0,
35-
'[data-direction="top"]': -2,
36+
'': 0,
37+
'[data-direction="top"]': 'initial',
3638
},
3739
bottom: {
38-
'': 'initial',
39-
horizontal: 0,
40-
'[data-direction="bottom"]': -2,
40+
'': 0,
41+
'[data-direction="bottom"]': 'initial',
4142
},
4243
right: {
4344
'': 0,
44-
horizontal: 'initial',
45-
'[data-direction="right"]': -2,
45+
'[data-direction="right"]': 'initial',
4646
},
4747
left: {
4848
'': 0,
49-
horizontal: 'initial',
50-
'[data-direction="left"]': -2,
49+
'[data-direction="left"]': 'initial',
50+
},
51+
// Transform requires a separate visual size property to respect size boundaries
52+
transform: {
53+
'[data-direction="top"]':
54+
'translate(0, (@size-compensation - @visual-size))',
55+
'[data-direction="right"]':
56+
'translate((@visual-size - @size-compensation), 0)',
57+
'[data-direction="bottom"]':
58+
'translate(0, (@visual-size - @size-compensation))',
59+
'[data-direction="left"]':
60+
'translate((@size-compensation - @visual-size), 0)',
5161
},
5262
position: 'absolute',
5363
zIndex: 1,
@@ -62,20 +72,27 @@ const HandlerElement = tasty({
6272
padding: 0,
6373
boxSizing: 'border-box',
6474
transition: 'theme',
75+
'--size-compensation': {
76+
'': '7px',
77+
disabled: '1bw',
78+
},
6579

6680
Track: {
6781
width: {
6882
'': 'initial',
6983
horizontal: '5px',
84+
'disabled & horizontal': '1px',
7085
},
7186
height: {
7287
'': '5px',
7388
horizontal: 'initial',
89+
'disabled & !horizontal': '1px',
7490
},
7591
position: 'absolute',
7692
inset: {
7793
'': '2px 0',
7894
horizontal: '0 2px',
95+
disabled: '0 0',
7996
},
8097
fill: {
8198
'': '#border-opaque',
@@ -85,6 +102,10 @@ const HandlerElement = tasty({
85102
},
86103

87104
Drag: {
105+
hide: {
106+
'': false,
107+
disabled: true,
108+
},
88109
width: {
89110
'': '3x',
90111
horizontal: '3px',
@@ -152,6 +173,11 @@ const PanelElement = tasty(Panel, {
152173
},
153174
placeSelf: 'stretch',
154175
touchAction: 'none',
176+
177+
'--indent-compensation': {
178+
'': '5px',
179+
disabled: '1bw',
180+
},
155181
},
156182
});
157183

@@ -166,7 +192,7 @@ function ResizablePanel(
166192
size: providedSize,
167193
onSizeChange,
168194
minSize = 200,
169-
maxSize = isControllable ? undefined : 400,
195+
maxSize = isControllable ? undefined : 'min(50%, 400px)',
170196
} = props;
171197

172198
const [isDragging, setIsDragging] = useState(false);
@@ -189,6 +215,20 @@ function ResizablePanel(
189215
let [size, setSize] = useState<number>(
190216
providedSize != null ? clamp(providedSize) : 200,
191217
);
218+
let [visualSize, setVisualSize] = useState<number | null>(null);
219+
220+
useEffect(() => {
221+
if (ref.current) {
222+
const offsetProp = isHorizontal ? 'offsetWidth' : 'offsetHeight';
223+
const containerSize = ref.current[offsetProp];
224+
225+
if (Math.abs(containerSize - size) < 1 && !isDisabled) {
226+
setVisualSize(size);
227+
} else {
228+
setVisualSize(containerSize);
229+
}
230+
}
231+
}, [size, isDisabled]);
192232

193233
let { moveProps } = useMove({
194234
onMoveStart(e) {
@@ -200,8 +240,8 @@ function ResizablePanel(
200240

201241
const offsetProp = isHorizontal ? 'offsetWidth' : 'offsetHeight';
202242

203-
if (ref.current && Math.abs(ref.current[offsetProp] - size) > 1) {
204-
setSize(ref.current[offsetProp]);
243+
if (ref.current && Math.abs(ref.current[offsetProp] - size) >= 1) {
244+
setSize(Math.round(ref.current[offsetProp]));
205245
}
206246
},
207247
onMove(e) {
@@ -229,7 +269,7 @@ function ResizablePanel(
229269

230270
useEffect(() => {
231271
if (providedSize == null || Math.abs(providedSize - size) > 0.5) {
232-
onSizeChange?.(size);
272+
onSizeChange?.(Math.round(size));
233273
}
234274
}, [size]);
235275

@@ -239,30 +279,39 @@ function ResizablePanel(
239279
}
240280
}, [providedSize]);
241281

282+
const mods = useMemo(() => {
283+
return {
284+
drag: isDragging,
285+
horizontal: isHorizontal,
286+
disabled: isDisabled,
287+
};
288+
}, [isDragging, isHorizontal, isDisabled]);
289+
242290
return (
243291
<PanelElement
244292
ref={ref}
245293
data-direction={direction}
294+
mods={mods}
246295
extra={
247296
<Handler
248-
isDisabled={isDisabled}
297+
isDisabled={isDisabled || visualSize == null}
249298
direction={direction}
250299
{...moveProps}
251-
mods={{
252-
drag: isDragging,
253-
horizontal: isHorizontal,
254-
disabled: isDisabled,
255-
}}
300+
mods={mods}
256301
/>
257302
}
258303
{...mergeProps(props, {
259304
style: {
305+
// We set a current size further via width/min-width/max-width styles to respect size boundaries
260306
'--size': `${size}px`,
307+
// We use a separate visual size to paint the handler for smoother experience
308+
'--visual-size': `${visualSize}px`,
261309
'--min-size': typeof minSize === 'number' ? `${minSize}px` : minSize,
262310
'--max-size': typeof maxSize === 'number' ? `${maxSize}px` : maxSize,
263311
},
264312
innerStyles: {
265-
margin: `5px ${direction}`,
313+
// The panel inner space compensation for the handler
314+
margin: `@indent-compensation ${direction}`,
266315
},
267316
})}
268317
/>

0 commit comments

Comments
 (0)