Skip to content

Commit b10e55e

Browse files
authored
feat: add Panel and ResizablePanel components (#536)
1 parent 792ec9f commit b10e55e

File tree

7 files changed

+586
-0
lines changed

7 files changed

+586
-0
lines changed

.changeset/shaggy-windows-float.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@cube-dev/ui-kit': minor
3+
---
4+
5+
Add Panel component.

.changeset/silent-plants-drum.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@cube-dev/ui-kit': minor
3+
---
4+
5+
Add ResizablePanel component.

src/components/layout/Panel.tsx

+193
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
import {
2+
createContext,
3+
ForwardedRef,
4+
forwardRef,
5+
ReactNode,
6+
useMemo,
7+
} from 'react';
8+
9+
import {
10+
BASE_STYLES,
11+
BaseProps,
12+
BaseStyleProps,
13+
BLOCK_STYLES,
14+
BlockStyleProps,
15+
COLOR_STYLES,
16+
ColorStyleProps,
17+
DIMENSION_STYLES,
18+
OUTER_STYLES,
19+
OuterStyleProps,
20+
Styles,
21+
tasty,
22+
} from '../../tasty';
23+
24+
export interface PanelContextData {
25+
layout: 'grid' | 'flex';
26+
}
27+
28+
export const PanelContext = createContext<PanelContextData>({
29+
layout: 'grid',
30+
});
31+
32+
const PanelElement = tasty({
33+
as: 'section',
34+
qa: 'Panel',
35+
styles: {
36+
position: {
37+
'': 'relative',
38+
'stretched | floating': 'absolute',
39+
},
40+
inset: {
41+
'': 'initial',
42+
stretched: true,
43+
},
44+
display: 'block',
45+
radius: {
46+
'': '0',
47+
card: '1r',
48+
},
49+
border: {
50+
'': '0',
51+
card: '1bw',
52+
},
53+
flexGrow: 1,
54+
},
55+
});
56+
57+
const PanelInnerElement = tasty({
58+
styles: {
59+
position: 'absolute',
60+
display: {
61+
'': 'grid',
62+
flex: 'flex',
63+
},
64+
top: 0,
65+
left: 0,
66+
right: 0,
67+
bottom: 0,
68+
overflow: 'auto',
69+
styledScrollbar: true,
70+
gridColumns: 'minmax(100%, 100%)',
71+
gridRows: {
72+
'': 'initial',
73+
stretched: 'minmax(0, 1fr)',
74+
},
75+
radius: {
76+
'': '0',
77+
card: '(1r - 1bw)',
78+
},
79+
flow: 'row',
80+
placeContent: 'start stretch',
81+
},
82+
styleProps: [...OUTER_STYLES, ...BASE_STYLES, ...COLOR_STYLES],
83+
});
84+
85+
export interface CubePanelProps
86+
extends OuterStyleProps,
87+
BlockStyleProps,
88+
BaseStyleProps,
89+
ColorStyleProps,
90+
BaseProps {
91+
isStretched?: boolean;
92+
isCard?: boolean;
93+
isFloating?: boolean;
94+
styles?: Styles;
95+
innerStyles?: Styles;
96+
placeContent?: Styles['placeContent'];
97+
placeItems?: Styles['placeItems'];
98+
gridColumns?: Styles['gridTemplateColumns'];
99+
gridRows?: Styles['gridTemplateRows'];
100+
flow?: Styles['flow'];
101+
gap?: Styles['gap'];
102+
isFlex?: boolean;
103+
children?: ReactNode;
104+
extra?: ReactNode;
105+
}
106+
107+
const STYLES = [
108+
'placeContent',
109+
'placeItems',
110+
'gridColumns',
111+
'gridRows',
112+
'flow',
113+
'gap',
114+
'padding',
115+
'overflow',
116+
'fill',
117+
'color',
118+
'preset',
119+
] as const;
120+
121+
function Panel(props: CubePanelProps, ref: ForwardedRef<HTMLDivElement>) {
122+
let {
123+
qa,
124+
mods,
125+
isStretched,
126+
isFloating,
127+
isCard,
128+
isFlex,
129+
styles,
130+
innerStyles,
131+
children,
132+
extra,
133+
style,
134+
...otherProps
135+
} = props;
136+
137+
STYLES.forEach((style) => {
138+
if (props[style]) {
139+
innerStyles = { ...innerStyles, [style]: props[style] };
140+
}
141+
});
142+
143+
[
144+
...OUTER_STYLES,
145+
...BASE_STYLES,
146+
...BLOCK_STYLES,
147+
...COLOR_STYLES,
148+
...DIMENSION_STYLES,
149+
].forEach((style) => {
150+
if (style in props) {
151+
styles = { ...styles, [style]: props[style] };
152+
}
153+
});
154+
155+
const appliedMods = useMemo(
156+
() => ({
157+
floating: isFloating,
158+
stretched: isStretched,
159+
card: isCard,
160+
flex: isFlex,
161+
...mods,
162+
}),
163+
[isStretched, isCard, mods],
164+
);
165+
166+
return (
167+
<PanelContext.Provider
168+
value={{
169+
layout: isFlex ? 'flex' : 'grid',
170+
}}
171+
>
172+
<PanelElement
173+
ref={ref}
174+
qa={qa}
175+
mods={appliedMods}
176+
styles={styles}
177+
style={style}
178+
{...otherProps}
179+
>
180+
<PanelInnerElement mods={appliedMods} styles={innerStyles}>
181+
{children}
182+
</PanelInnerElement>
183+
{extra}
184+
</PanelElement>
185+
</PanelContext.Provider>
186+
);
187+
}
188+
189+
const _Panel = forwardRef(Panel);
190+
191+
_Panel.displayName = 'Panel';
192+
193+
export { _Panel as Panel };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { Meta, StoryFn } from '@storybook/react';
2+
import { useState } from 'react';
3+
4+
import { Panel } from './Panel';
5+
import { ResizablePanel, CubeResizablePanelProps } from './ResizablePanel';
6+
7+
export default {
8+
title: 'Layout/ResizablePanel',
9+
component: ResizablePanel,
10+
args: {},
11+
} as Meta<CubeResizablePanelProps>;
12+
13+
const TemplateRight: StoryFn<CubeResizablePanelProps> = (args) => (
14+
<Panel isFlex isStretched height="min 30x" fill="#white">
15+
<ResizablePanel {...args} />
16+
<Panel fill="#light"></Panel>
17+
</Panel>
18+
);
19+
20+
const TemplateLeft: StoryFn<CubeResizablePanelProps> = (args) => {
21+
return (
22+
<Panel isFlex isStretched height="min 30x" fill="#white">
23+
<Panel fill="#light"></Panel>
24+
<ResizablePanel {...args} />
25+
</Panel>
26+
);
27+
};
28+
29+
const TemplateBottom: StoryFn<CubeResizablePanelProps> = (args) => (
30+
<Panel isFlex isStretched flow="column" height="min 30x" fill="#white">
31+
<ResizablePanel {...args} />
32+
<Panel fill="#light"></Panel>
33+
</Panel>
34+
);
35+
36+
const TemplateTop: StoryFn<CubeResizablePanelProps> = (args) => {
37+
return (
38+
<Panel isFlex isStretched flow="column" height="min 30x" fill="#white">
39+
<Panel fill="#light"></Panel>
40+
<ResizablePanel {...args} />
41+
</Panel>
42+
);
43+
};
44+
45+
const TemplateControllable: StoryFn<CubeResizablePanelProps> = (args) => {
46+
const [size, setSize] = useState(200);
47+
48+
return (
49+
<ResizablePanel
50+
size={size}
51+
flow="column"
52+
height="min 30x"
53+
fill="#light"
54+
onSizeChange={(size) => setSize(Math.min(500, Math.max(100, size)))}
55+
{...args}
56+
></ResizablePanel>
57+
);
58+
};
59+
60+
export const ResizeRight = TemplateRight.bind({});
61+
ResizeRight.args = {
62+
direction: 'right',
63+
};
64+
65+
export const ResizeLeft = TemplateLeft.bind({});
66+
ResizeLeft.args = {
67+
direction: 'left',
68+
};
69+
70+
export const ResizeBottom = TemplateBottom.bind({});
71+
ResizeBottom.args = {
72+
direction: 'bottom',
73+
};
74+
75+
export const ResizeTop = TemplateTop.bind({});
76+
ResizeTop.args = {
77+
direction: 'top',
78+
};
79+
80+
export const Controllable = TemplateControllable.bind({});
81+
Controllable.args = {
82+
direction: 'right',
83+
};
84+
85+
export const Disabled = TemplateRight.bind({});
86+
Disabled.args = {
87+
isDisabled: true,
88+
};

0 commit comments

Comments
 (0)