Skip to content

Commit 257ba7c

Browse files
committed
feat: add component resizable
1 parent 51bad09 commit 257ba7c

File tree

10 files changed

+232
-1
lines changed

10 files changed

+232
-1
lines changed

packages/ui/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
"main": "./dist/index.js",
1212
"module": "./dist/index.js",
1313
"types": "./dist/index.d.ts",
14-
"files": ["dist"],
14+
"files": [
15+
"dist"
16+
],
1517
"scripts": {
1618
"build": "pnpm run pgk:prod && pnpm run build:components && pnpm run registry",
1719
"build:components": "tsdown",
@@ -60,6 +62,7 @@
6062
"next-themes": "0.4.6",
6163
"react": "19.1.0",
6264
"react-dom": "19.1.0",
65+
"react-resizable-panels": "^3.0.4",
6366
"sonner": "2.0.6",
6467
"tailwind-merge": "3.3.0",
6568
"tailwind-variants": "1.0.0",
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Slot } from '@radix-ui/react-slot';
2+
import { GripVertical } from 'lucide-react';
3+
import { PanelResizeHandle } from 'react-resizable-panels';
4+
5+
import { cn } from '@/lib/utils';
6+
7+
import { resizableVariants } from './resizable-variants';
8+
import type { ResizableHandleProps } from './types';
9+
10+
const ResizableHandle = (props: ResizableHandleProps) => {
11+
const { children, className, classNames, size, withHandle, ...rest } = props;
12+
13+
const { handle, handleIcon, handleIconRoot } = resizableVariants({ size });
14+
15+
const mergedCls = {
16+
handle: cn(handle(), className),
17+
handleIcon: cn(handleIcon(), classNames?.handleIcon),
18+
handleIconRoot: cn(handleIconRoot(), classNames?.handleIconRoot)
19+
};
20+
21+
return (
22+
<PanelResizeHandle
23+
className={mergedCls.handle}
24+
{...rest}
25+
>
26+
{withHandle && (
27+
<div className={mergedCls.handleIconRoot}>
28+
<Slot className={mergedCls.handleIcon}>{children || <GripVertical />}</Slot>
29+
</div>
30+
)}
31+
</PanelResizeHandle>
32+
);
33+
};
34+
35+
export default ResizableHandle;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { PanelGroup } from 'react-resizable-panels';
2+
3+
import { cn } from '@/lib/utils';
4+
5+
import { resizableVariants } from './resizable-variants';
6+
import type { ResizablePanelGroupProps } from './types';
7+
8+
const ResizablePanelGroup = (props: ResizablePanelGroupProps) => {
9+
const { className, size, ...rest } = props;
10+
11+
const { panelGroup } = resizableVariants({ size });
12+
13+
const mergedCls = cn(panelGroup(), className);
14+
15+
return (
16+
<PanelGroup
17+
className={mergedCls}
18+
{...rest}
19+
/>
20+
);
21+
};
22+
23+
export default ResizablePanelGroup;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
'use client';
2+
3+
export { Panel as ResizablePanel } from 'react-resizable-panels';
4+
5+
export { default as ResizableHandle } from './ResizableHandle';
6+
7+
export { default as ResizablePanelGroup } from './ResizablePanelGroup';
8+
9+
export * from './types';
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { tv } from 'tailwind-variants';
2+
3+
export const resizableVariants = tv({
4+
defaultVariants: {
5+
size: 'md'
6+
},
7+
slots: {
8+
handle: [
9+
'relative flex w-px items-center justify-center bg-border',
10+
'after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2',
11+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:ring-primary',
12+
' data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full',
13+
' data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1 data-[panel-group-direction=vertical]:after:translate-x-0',
14+
'[&[data-panel-group-direction=vertical]>div]:rotate-90'
15+
],
16+
handleIcon: '',
17+
handleIconRoot: 'z-2 flex items-center justify-center rounded-sm border bg-border',
18+
panelGroup: 'flex h-full w-full data-[panel-group-direction=vertical]:flex-col'
19+
},
20+
variants: {
21+
size: {
22+
'2xl': {
23+
handleIconRoot: 'w-4.5 h-6',
24+
panelGroup: 'text-xl'
25+
},
26+
lg: {
27+
handleIconRoot: 'w-3.5 h-4.5',
28+
panelGroup: 'text-base'
29+
},
30+
md: {
31+
handleIconRoot: 'w-3 h-4',
32+
panelGroup: 'text-sm'
33+
},
34+
sm: {
35+
handleIconRoot: 'w-2.5 h-3.25',
36+
panelGroup: 'text-xs'
37+
},
38+
xl: {
39+
handleIconRoot: 'w-4 h-5.25',
40+
panelGroup: 'text-lg'
41+
},
42+
xs: {
43+
handleIconRoot: 'w-2 h-2.625',
44+
panelGroup: 'text-2xs'
45+
}
46+
}
47+
}
48+
});
49+
50+
export type ResizableSlots = keyof typeof resizableVariants.slots;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type { ComponentProps } from 'react';
2+
// eslint-disable-next-line sort-imports
3+
import type { PanelGroupProps as _PanelGroupProps, PanelResizeHandle } from 'react-resizable-panels';
4+
5+
import type { BaseNodeProps, ClassValue } from '@/types/other';
6+
7+
import type { ResizableSlots } from './resizable-variants';
8+
9+
export type ResizableClassNames = Partial<Record<ResizableSlots, ClassValue>>;
10+
11+
export interface ResizableHandleProps extends BaseNodeProps<ComponentProps<typeof PanelResizeHandle>> {
12+
classNames?: Pick<ResizableClassNames, 'handleIcon' | 'handleIconRoot'>;
13+
withHandle?: boolean;
14+
}
15+
16+
export interface ResizablePanelGroupProps extends BaseNodeProps<_PanelGroupProps> {}

packages/ui/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ export * from './components/progress';
5656

5757
export * from './components/radio';
5858

59+
export * from './components/resizable';
60+
5961
export * from './components/scroll-area';
6062

6163
export * from './components/select';
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { CircleDashed } from 'lucide-react';
2+
import { Card, ResizableHandle, ResizablePanel, ResizablePanelGroup } from 'soybean-react-ui';
3+
4+
const Default = () => {
5+
return (
6+
<Card
7+
split
8+
title="Resizable"
9+
>
10+
<ResizablePanelGroup
11+
className="max-w-md border rounded-lg"
12+
direction="horizontal"
13+
id="demo-group-1"
14+
>
15+
<ResizablePanel
16+
collapsible
17+
collapsedSize={10}
18+
defaultSize={50}
19+
id="demo-panel-1"
20+
minSize={20}
21+
>
22+
<div className="h-[200px] flex items-center justify-center p-6">
23+
<span className="font-semibold">One</span>
24+
</div>
25+
</ResizablePanel>
26+
<ResizableHandle
27+
withHandle
28+
id="demo-handle-1"
29+
/>
30+
<ResizablePanel
31+
defaultSize={50}
32+
id="demo-panel-2"
33+
minSize={20}
34+
>
35+
<ResizablePanelGroup
36+
direction="vertical"
37+
id="demo-group-2"
38+
>
39+
<ResizablePanel
40+
defaultSize={25}
41+
id="demo-panel-3"
42+
minSize={25}
43+
>
44+
<div className="h-full flex items-center justify-center p-6">
45+
<span className="font-semibold">Two</span>
46+
</div>
47+
</ResizablePanel>
48+
49+
<ResizableHandle
50+
withHandle
51+
id="demo-handle-2"
52+
>
53+
<CircleDashed />
54+
</ResizableHandle>
55+
56+
<ResizablePanel
57+
defaultSize={75}
58+
id="demo-panel-4"
59+
minSize={25}
60+
>
61+
<div className="h-full flex items-center justify-center p-6">
62+
<span className="font-semibold">Three</span>
63+
</div>
64+
</ResizablePanel>
65+
</ResizablePanelGroup>
66+
</ResizablePanel>
67+
</ResizablePanelGroup>
68+
</Card>
69+
);
70+
};
71+
72+
export default Default;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import Default from './modules/Default';
2+
3+
const ResizablePage = () => {
4+
return <Default />;
5+
};
6+
7+
export default ResizablePage;

pnpm-lock.yaml

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)