Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/proud-kangaroos-switch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@cube-dev/ui-kit': patch
---

Smoother transition for ResizablePanel.'
5 changes: 5 additions & 0 deletions .changeset/soft-months-beam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@cube-dev/ui-kit': patch
---

Round the output size style in ResizablePanel.
24 changes: 16 additions & 8 deletions src/components/layout/ResizablePanel.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,30 @@ export default {
const TemplateRight: StoryFn<CubeResizablePanelProps> = (args) => (
<Panel isFlex isStretched height="min 30x" fill="#white">
<ResizablePanel {...args} />
<Panel fill="#light"></Panel>
<Panel fill="#purple-04.10"></Panel>
</Panel>
);

const TemplateLeft: StoryFn<CubeResizablePanelProps> = (args) => {
return (
<Panel isFlex isStretched height="min 30x" fill="#white">
<Panel fill="#light"></Panel>
<Panel fill="#purple-04.10"></Panel>
<ResizablePanel {...args} />
</Panel>
);
};

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

const TemplateTop: StoryFn<CubeResizablePanelProps> = (args) => {
return (
<Panel isFlex isStretched flow="column" height="min 30x" fill="#white">
<Panel fill="#light"></Panel>
<Panel isFlex isStretched flow="column" fill="#white">
<Panel fill="#purple-04.10"></Panel>
<ResizablePanel {...args} />
</Panel>
);
Expand All @@ -60,7 +60,14 @@ const TemplateControllable: StoryFn<CubeResizablePanelProps> = (args) => {
const GridTemplate: StoryFn<CubeResizablePanelProps> = (args) => (
<Panel isStretched height="min 30x" fill="#white" gridColumns="auto 1fr">
<ResizablePanel size={300} {...args} />
<Panel fill="#light"></Panel>
<Panel fill="#purple-04.10"></Panel>
</Panel>
);

const TemplateDisabled: StoryFn<CubeResizablePanelProps> = (args) => (
<Panel isFlex isStretched height="min 30x" fill="#white">
<ResizablePanel {...args} />
<Panel fill="#purple-04.10"></Panel>
</Panel>
);

Expand All @@ -87,9 +94,10 @@ ResizeTop.args = {
export const Controllable = TemplateControllable.bind({});
Controllable.args = {
direction: 'right',
maxSize: 500,
};

export const Disabled = TemplateRight.bind({});
export const Disabled = TemplateDisabled.bind({});
Disabled.args = {
isDisabled: true,
};
Expand Down
97 changes: 73 additions & 24 deletions src/components/layout/ResizablePanel.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ForwardedRef, forwardRef, useEffect, useState } from 'react';
import { ForwardedRef, forwardRef, useEffect, useMemo, useState } from 'react';
import { useHover, useMove } from 'react-aria';

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

const HandlerElement = tasty({
qa: 'ResizeHandler',
styles: {
// The real size is slightly bigger than the visual one.
width: {
'': 'auto',
'': 'initial',
horizontal: '9px',
'disabled & horizontal': '1bw',
},
height: {
'': '9px',
horizontal: 'auto',
horizontal: 'initial',
'disabled & !horizontal': '1bw',
},
top: {
'': 'initial',
horizontal: 0,
'[data-direction="top"]': -2,
'': 0,
'[data-direction="top"]': 'initial',
},
bottom: {
'': 'initial',
horizontal: 0,
'[data-direction="bottom"]': -2,
'': 0,
'[data-direction="bottom"]': 'initial',
},
right: {
'': 0,
horizontal: 'initial',
'[data-direction="right"]': -2,
'[data-direction="right"]': 'initial',
},
left: {
'': 0,
horizontal: 'initial',
'[data-direction="left"]': -2,
'[data-direction="left"]': 'initial',
},
// Transform requires a separate visual size property to respect size boundaries
transform: {
'[data-direction="top"]':
'translate(0, (@size-compensation - @visual-size))',
'[data-direction="right"]':
'translate((@visual-size - @size-compensation), 0)',
'[data-direction="bottom"]':
'translate(0, (@visual-size - @size-compensation))',
'[data-direction="left"]':
'translate((@size-compensation - @visual-size), 0)',
},
position: 'absolute',
zIndex: 1,
Expand All @@ -62,20 +72,27 @@ const HandlerElement = tasty({
padding: 0,
boxSizing: 'border-box',
transition: 'theme',
'--size-compensation': {
'': '7px',
disabled: '1bw',
},

Track: {
width: {
'': 'initial',
horizontal: '5px',
'disabled & horizontal': '1px',
},
height: {
'': '5px',
horizontal: 'initial',
'disabled & !horizontal': '1px',
},
position: 'absolute',
inset: {
'': '2px 0',
horizontal: '0 2px',
disabled: '0 0',
},
fill: {
'': '#border-opaque',
Expand All @@ -85,6 +102,10 @@ const HandlerElement = tasty({
},

Drag: {
hide: {
'': false,
disabled: true,
},
width: {
'': '3x',
horizontal: '3px',
Expand Down Expand Up @@ -152,6 +173,11 @@ const PanelElement = tasty(Panel, {
},
placeSelf: 'stretch',
touchAction: 'none',

'--indent-compensation': {
'': '5px',
disabled: '1bw',
},
},
});

Expand All @@ -166,7 +192,7 @@ function ResizablePanel(
size: providedSize,
onSizeChange,
minSize = 200,
maxSize = isControllable ? undefined : 400,
maxSize = isControllable ? undefined : 'min(50%, 400px)',
} = props;

const [isDragging, setIsDragging] = useState(false);
Expand All @@ -189,6 +215,20 @@ function ResizablePanel(
let [size, setSize] = useState<number>(
providedSize != null ? clamp(providedSize) : 200,
);
let [visualSize, setVisualSize] = useState<number | null>(null);

useEffect(() => {
if (ref.current) {
const offsetProp = isHorizontal ? 'offsetWidth' : 'offsetHeight';
const containerSize = ref.current[offsetProp];

if (Math.abs(containerSize - size) < 1 && !isDisabled) {
setVisualSize(size);
} else {
setVisualSize(containerSize);
}
}
}, [size, isDisabled]);

let { moveProps } = useMove({
onMoveStart(e) {
Expand All @@ -200,8 +240,8 @@ function ResizablePanel(

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

if (ref.current && Math.abs(ref.current[offsetProp] - size) > 1) {
setSize(ref.current[offsetProp]);
if (ref.current && Math.abs(ref.current[offsetProp] - size) >= 1) {
setSize(Math.round(ref.current[offsetProp]));
}
},
onMove(e) {
Expand Down Expand Up @@ -229,7 +269,7 @@ function ResizablePanel(

useEffect(() => {
if (providedSize == null || Math.abs(providedSize - size) > 0.5) {
onSizeChange?.(size);
onSizeChange?.(Math.round(size));
}
}, [size]);

Expand All @@ -239,30 +279,39 @@ function ResizablePanel(
}
}, [providedSize]);

const mods = useMemo(() => {
return {
drag: isDragging,
horizontal: isHorizontal,
disabled: isDisabled,
};
}, [isDragging, isHorizontal, isDisabled]);

return (
<PanelElement
ref={ref}
data-direction={direction}
mods={mods}
extra={
<Handler
isDisabled={isDisabled}
isDisabled={isDisabled || visualSize == null}
direction={direction}
{...moveProps}
mods={{
drag: isDragging,
horizontal: isHorizontal,
disabled: isDisabled,
}}
mods={mods}
/>
}
{...mergeProps(props, {
style: {
// We set a current size further via width/min-width/max-width styles to respect size boundaries
'--size': `${size}px`,
// We use a separate visual size to paint the handler for smoother experience
'--visual-size': `${visualSize}px`,
'--min-size': typeof minSize === 'number' ? `${minSize}px` : minSize,
'--max-size': typeof maxSize === 'number' ? `${maxSize}px` : maxSize,
},
innerStyles: {
margin: `5px ${direction}`,
// The panel inner space compensation for the handler
margin: `@indent-compensation ${direction}`,
},
})}
/>
Expand Down
Loading