datocms-react-ui 2.0.13 → 2.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/cjs/SplitView/SplitViewPane/index.js +14 -0
  2. package/dist/cjs/SplitView/SplitViewPane/index.js.map +1 -0
  3. package/dist/cjs/SplitView/SplitViewPane/styles.module.css.json +1 -0
  4. package/dist/cjs/SplitView/SplitViewSash/index.js +76 -0
  5. package/dist/cjs/SplitView/SplitViewSash/index.js.map +1 -0
  6. package/dist/cjs/SplitView/SplitViewSash/styles.module.css.json +1 -0
  7. package/dist/cjs/SplitView/index.js +206 -0
  8. package/dist/cjs/SplitView/index.js.map +1 -0
  9. package/dist/cjs/SplitView/styles.module.css.json +1 -0
  10. package/dist/cjs/SplitView/types.js +3 -0
  11. package/dist/cjs/SplitView/types.js.map +1 -0
  12. package/dist/cjs/VerticalSplit/index.js +337 -0
  13. package/dist/cjs/VerticalSplit/index.js.map +1 -0
  14. package/dist/cjs/VerticalSplit/styles.module.css.json +1 -0
  15. package/dist/cjs/icons.js +19 -1
  16. package/dist/cjs/icons.js.map +1 -1
  17. package/dist/cjs/index.js +5 -3
  18. package/dist/cjs/index.js.map +1 -1
  19. package/dist/esm/SplitView/SplitViewPane/index.d.ts +7 -0
  20. package/dist/esm/SplitView/SplitViewPane/index.js +7 -0
  21. package/dist/esm/SplitView/SplitViewPane/index.js.map +1 -0
  22. package/dist/esm/SplitView/SplitViewPane/styles.module.css.json +1 -0
  23. package/dist/esm/SplitView/SplitViewSash/index.d.ts +17 -0
  24. package/dist/esm/SplitView/SplitViewSash/index.js +46 -0
  25. package/dist/esm/SplitView/SplitViewSash/index.js.map +1 -0
  26. package/dist/esm/SplitView/SplitViewSash/styles.module.css.json +1 -0
  27. package/dist/esm/SplitView/index.d.ts +16 -0
  28. package/dist/esm/SplitView/index.js +176 -0
  29. package/dist/esm/SplitView/index.js.map +1 -0
  30. package/dist/esm/SplitView/styles.module.css.json +1 -0
  31. package/dist/esm/SplitView/types.d.ts +8 -0
  32. package/dist/esm/SplitView/types.js +2 -0
  33. package/dist/esm/SplitView/types.js.map +1 -0
  34. package/dist/esm/VerticalSplit/index.d.ts +238 -0
  35. package/dist/esm/VerticalSplit/index.js +307 -0
  36. package/dist/esm/VerticalSplit/index.js.map +1 -0
  37. package/dist/esm/VerticalSplit/styles.module.css.json +1 -0
  38. package/dist/esm/icons.d.ts +3 -0
  39. package/dist/esm/icons.js +15 -0
  40. package/dist/esm/icons.js.map +1 -1
  41. package/dist/esm/index.d.ts +5 -3
  42. package/dist/esm/index.js +5 -3
  43. package/dist/esm/index.js.map +1 -1
  44. package/dist/types/SplitView/SplitViewPane/index.d.ts +7 -0
  45. package/dist/types/SplitView/SplitViewSash/index.d.ts +17 -0
  46. package/dist/types/SplitView/index.d.ts +16 -0
  47. package/dist/types/SplitView/types.d.ts +8 -0
  48. package/dist/types/VerticalSplit/index.d.ts +238 -0
  49. package/dist/types/icons.d.ts +3 -0
  50. package/dist/types/index.d.ts +5 -3
  51. package/package.json +3 -3
  52. package/src/SplitView/SplitViewPane/index.tsx +19 -0
  53. package/src/SplitView/SplitViewPane/styles.module.css +6 -0
  54. package/src/SplitView/SplitViewPane/styles.module.css.json +1 -0
  55. package/src/SplitView/SplitViewSash/index.tsx +99 -0
  56. package/src/SplitView/SplitViewSash/styles.module.css +68 -0
  57. package/src/SplitView/SplitViewSash/styles.module.css.json +1 -0
  58. package/src/SplitView/index.tsx +256 -0
  59. package/src/SplitView/styles.module.css +22 -0
  60. package/src/SplitView/styles.module.css.json +1 -0
  61. package/src/SplitView/types.ts +9 -0
  62. package/src/VerticalSplit/index.tsx +401 -0
  63. package/src/VerticalSplit/styles.module.css +103 -0
  64. package/src/VerticalSplit/styles.module.css.json +1 -0
  65. package/src/global.css +29 -25
  66. package/src/icons.tsx +60 -0
  67. package/src/index.ts +5 -3
  68. package/styles.css +1 -1
  69. package/types.json +2723 -1850
@@ -0,0 +1,256 @@
1
+ import classNames from 'classnames';
2
+ import React, {
3
+ useCallback,
4
+ useEffect,
5
+ useMemo,
6
+ useRef,
7
+ useState,
8
+ } from 'react';
9
+ import { SplitViewPane, type SplitViewPaneProps } from './SplitViewPane';
10
+ import { SashAction, SplitViewSash } from './SplitViewSash';
11
+ import s from './styles.module.css.json';
12
+ import { IAxis, ICacheSizes } from './types';
13
+
14
+ type SplitViewProps = {
15
+ children: JSX.Element[];
16
+ allowResize?: boolean;
17
+ split?: 'vertical' | 'horizontal';
18
+ sizes: (string | number)[];
19
+ onChange: (sizes: number[]) => void;
20
+ onDragStart?: () => void;
21
+ onDragEnd?: () => void;
22
+ performanceMode?: boolean;
23
+ resizerSize?: number;
24
+ sashAction?: SashAction;
25
+ };
26
+
27
+ export const SplitView = ({
28
+ children,
29
+ sizes: propSizes,
30
+ allowResize = true,
31
+ split = 'vertical',
32
+ resizerSize = 10,
33
+ performanceMode = false,
34
+ onChange = () => null,
35
+ onDragStart = () => null,
36
+ onDragEnd = () => null,
37
+ sashAction,
38
+ }: SplitViewProps) => {
39
+ const axis = useRef<IAxis>({ x: 0, y: 0 });
40
+ const wrapper = useRef<HTMLDivElement>(null);
41
+ const cacheSizes = useRef<ICacheSizes>({ sizes: [], sashPosSizes: [] });
42
+ const [wrapperRect, setWrapperRect] = useState<DOMRect | undefined>(
43
+ undefined,
44
+ );
45
+ const [isDragging, setDragging] = useState<boolean>(false);
46
+
47
+ useEffect(() => {
48
+ const resizeObserver = new ResizeObserver(() => {
49
+ if (wrapper?.current) {
50
+ setWrapperRect(wrapper.current.getBoundingClientRect());
51
+ }
52
+ });
53
+ resizeObserver.observe(wrapper.current!);
54
+ return () => {
55
+ resizeObserver.disconnect();
56
+ };
57
+ }, []);
58
+
59
+ const { sizeName, splitPos, splitAxis } = useMemo<{
60
+ sizeName: 'width' | 'height';
61
+ splitPos: 'left' | 'top';
62
+ splitAxis: 'x' | 'y';
63
+ }>(
64
+ () => ({
65
+ sizeName: split === 'vertical' ? 'width' : 'height',
66
+ splitPos: split === 'vertical' ? 'left' : 'top',
67
+ splitAxis: split === 'vertical' ? 'x' : 'y',
68
+ }),
69
+ [split],
70
+ );
71
+
72
+ const wrapSize: number = wrapperRect ? wrapperRect[sizeName] : 0;
73
+
74
+ // Get limit sizes via children
75
+ const paneLimitSizes = useMemo(
76
+ () =>
77
+ children.map((childNode) => {
78
+ const limits = [0, Infinity];
79
+ if (childNode.type === SplitViewPane) {
80
+ const { minSize, maxSize } = childNode.props as SplitViewPaneProps;
81
+ limits[0] = assertSize(minSize, wrapSize, 0);
82
+ limits[1] = assertSize(maxSize, wrapSize);
83
+ }
84
+ return limits as [number, number];
85
+ }),
86
+ [children, wrapSize],
87
+ );
88
+
89
+ const sizes = useMemo(() => {
90
+ let count = 0;
91
+ let curSum = 0;
92
+
93
+ const res = children.map((_child, index) => {
94
+ const size = clampSize(
95
+ assertSize(propSizes[index], wrapSize),
96
+ paneLimitSizes[index],
97
+ );
98
+ if (size === Infinity) {
99
+ count = count + 1;
100
+ } else {
101
+ curSum = curSum + size;
102
+ }
103
+ return size;
104
+ });
105
+
106
+ // resize or illegal size input,recalculate pane sizes
107
+ if (curSum > wrapSize || (!count && curSum < wrapSize)) {
108
+ const cacheNum = (curSum - wrapSize) / curSum;
109
+ return res.map((size) => {
110
+ return size === Infinity ? 0 : size - size * cacheNum;
111
+ });
112
+ }
113
+
114
+ if (count > 0) {
115
+ const average = (wrapSize - curSum) / count;
116
+ return res.map((size) => {
117
+ return size === Infinity ? average : size;
118
+ });
119
+ }
120
+
121
+ return res;
122
+ }, [
123
+ JSON.stringify(propSizes),
124
+ JSON.stringify(paneLimitSizes),
125
+ children.length,
126
+ wrapSize,
127
+ ]);
128
+
129
+ const sashPosSizes = useMemo(
130
+ () => sizes.reduce((a, b) => [...a, a[a.length - 1] + b], [0]),
131
+ [JSON.stringify(sizes)],
132
+ );
133
+
134
+ const handleMouseDown = useCallback(
135
+ (x: number, y: number) => {
136
+ document?.body?.classList?.add(s['SplitView--disable-select']);
137
+ axis.current = { x, y };
138
+ cacheSizes.current = { sizes, sashPosSizes };
139
+ setDragging(true);
140
+ onDragStart();
141
+ },
142
+ [onDragStart, sizes, sashPosSizes],
143
+ );
144
+
145
+ const handleMouseUp = useCallback(() => {
146
+ document?.body?.classList?.remove(s['SplitView--disable-select']);
147
+ setDragging(false);
148
+ onDragEnd();
149
+ }, [onDragEnd]);
150
+
151
+ const handleMouseMove = useCallback(
152
+ (x: number, y: number, i: number) => {
153
+ const curAxis = { x, y };
154
+ let distanceX = curAxis[splitAxis] - axis.current[splitAxis];
155
+
156
+ const leftBorder = -Math.min(
157
+ sizes[i] - paneLimitSizes[i][0],
158
+ paneLimitSizes[i + 1][1] - sizes[i + 1],
159
+ );
160
+ const rightBorder = Math.min(
161
+ sizes[i + 1] - paneLimitSizes[i + 1][0],
162
+ paneLimitSizes[i][1] - sizes[i],
163
+ );
164
+
165
+ if (distanceX < leftBorder) {
166
+ distanceX = leftBorder;
167
+ }
168
+ if (distanceX > rightBorder) {
169
+ distanceX = rightBorder;
170
+ }
171
+
172
+ const nextSizes = [...sizes];
173
+ nextSizes[i] += distanceX;
174
+ nextSizes[i + 1] -= distanceX;
175
+
176
+ onChange(nextSizes);
177
+ },
178
+ [paneLimitSizes, onChange],
179
+ );
180
+
181
+ const paneFollow = !(performanceMode && isDragging);
182
+ const paneSizes = paneFollow ? sizes : cacheSizes.current.sizes;
183
+ const panePoses = paneFollow ? sashPosSizes : cacheSizes.current.sashPosSizes;
184
+
185
+ return (
186
+ <div
187
+ className={classNames(
188
+ s.SplitView,
189
+ split === 'vertical' && s['SplitView--vertical'],
190
+ split === 'horizontal' && s['SplitView--horizontal'],
191
+ isDragging && s['SplitView--dragging'],
192
+ )}
193
+ ref={wrapper}
194
+ >
195
+ {children.map((childNode, childIndex) => {
196
+ const isPane = childNode.type === SplitViewPane;
197
+ const paneProps = isPane ? childNode.props : {};
198
+
199
+ return (
200
+ <SplitViewPane
201
+ // biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
202
+ key={childIndex}
203
+ style={{
204
+ ...paneProps.style,
205
+ [sizeName]: paneSizes[childIndex],
206
+ [splitPos]: panePoses[childIndex],
207
+ }}
208
+ >
209
+ {isPane ? paneProps.children : childNode}
210
+ </SplitViewPane>
211
+ );
212
+ })}
213
+ {sashPosSizes.slice(1, -1).map((posSize, index) => (
214
+ <SplitViewSash
215
+ // biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
216
+ key={index}
217
+ allowResize={allowResize}
218
+ split={split}
219
+ style={{
220
+ [sizeName]: resizerSize,
221
+ [splitPos]: posSize - resizerSize / 2,
222
+ }}
223
+ onMouseDown={handleMouseDown}
224
+ onMouseMove={(x, y) => handleMouseMove(x, y, index)}
225
+ onMouseUp={handleMouseUp}
226
+ action={sashAction}
227
+ />
228
+ ))}
229
+ </div>
230
+ );
231
+ };
232
+
233
+ /**
234
+ * Convert size to absolute number or Infinity
235
+ * SplitPane allows sizes in string and number, but the state sizes only support number,
236
+ * so convert string and number to number in here
237
+ * 'auto' -> divide the remaining space equally
238
+ * 'xxxpx' -> xxx
239
+ * 'xxx%' -> wrapper.size * xxx/100
240
+ * xxx -> xxx
241
+ */
242
+ function assertSize(
243
+ size: string | number | undefined,
244
+ sum: number,
245
+ defaultValue = Infinity,
246
+ ) {
247
+ if (typeof size === 'undefined') return defaultValue;
248
+ if (typeof size === 'number') return size;
249
+ if (size.endsWith('%')) return sum * (+size.replace('%', '') / 100);
250
+ if (size.endsWith('px')) return +size.replace('px', '');
251
+ return defaultValue;
252
+ }
253
+
254
+ function clampSize(size: number, minMax: [number, number]) {
255
+ return Math.max(minMax[0], Math.min(minMax[1], size));
256
+ }
@@ -0,0 +1,22 @@
1
+ .SplitView {
2
+ flex: 1;
3
+ height: 100%;
4
+ position: relative;
5
+ width: 100%;
6
+ }
7
+
8
+ .SplitView--dragging.SplitView--vertical {
9
+ cursor: col-resize;
10
+ }
11
+
12
+ .SplitView--dragging.SplitView--horizontal {
13
+ cursor: row-resize;
14
+ }
15
+
16
+ .SplitView--disable-select {
17
+ user-select: none;
18
+ }
19
+
20
+ .SplitView--disable-select .SplitViewPane {
21
+ pointer-events: none;
22
+ }
@@ -0,0 +1 @@
1
+ {"SplitView":"_SplitView_1oi17_1","SplitView--dragging":"_SplitView--dragging_1oi17_8","SplitView--vertical":"_SplitView--vertical_1oi17_8","SplitView--horizontal":"_SplitView--horizontal_1oi17_12","SplitView--disable-select":"_SplitView--disable-select_1oi17_16","SplitViewPane":"_SplitViewPane_1oi17_20"}
@@ -0,0 +1,9 @@
1
+ export type IAxis = {
2
+ x: number;
3
+ y: number;
4
+ };
5
+
6
+ export type ICacheSizes = {
7
+ sizes: (string | number)[];
8
+ sashPosSizes: (string | number)[];
9
+ };
@@ -0,0 +1,401 @@
1
+ import classNames from 'classnames';
2
+ import React, { useEffect, useRef, useState } from 'react';
3
+ import { SplitView } from '../SplitView';
4
+ import { SplitViewPane } from '../SplitView/SplitViewPane';
5
+ import sashS from '../SplitView/SplitViewSash/styles.module.css.json';
6
+ import { ChevronsLeftIcon, ChevronsRightIcon, SidebarFlipIcon } from '../icons';
7
+ import s from './styles.module.css.json';
8
+
9
+ const initialSidebarWidth = 220;
10
+
11
+ export type VerticalSplitProps = {
12
+ mode?: 'overlay' | 'split';
13
+ minSize?: number | string;
14
+ maxSize?: number | string;
15
+ size?: number | string;
16
+ primaryPane: 'left' | 'right';
17
+ isSecondaryCollapsed?: boolean;
18
+ allowResize?: boolean;
19
+ children: [React.ReactNode, React.ReactNode];
20
+ onDragFinished?: (newSize: number) => void;
21
+ onSecondaryToggle?: (secondaryExpanded: boolean) => void;
22
+ };
23
+
24
+ type Size = string | number;
25
+
26
+ function calculateSizes({
27
+ size,
28
+ primaryPane,
29
+ isSecondaryCollapsed,
30
+ }: {
31
+ size: number | string;
32
+ primaryPane: 'left' | 'right';
33
+ isSecondaryCollapsed?: boolean;
34
+ }): Size[] {
35
+ const realSize = isSecondaryCollapsed ? 20 : size;
36
+ return primaryPane === 'right' ? [realSize] : ['auto', realSize];
37
+ }
38
+
39
+ /**
40
+ * @example Resizable, left primary panel
41
+ *
42
+ * ```js
43
+ * <Canvas ctx={ctx}>
44
+ * <div style={{ height: 500, position: 'relative' }}>
45
+ * <VerticalSplit primaryPane="left" size="25%" minSize={220}>
46
+ * <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
47
+ * <Toolbar>
48
+ * <ToolbarStack stackSize="l">
49
+ * <ToolbarTitle>Primary</ToolbarTitle>
50
+ * </ToolbarStack>
51
+ * </Toolbar>
52
+ * <div
53
+ * style={{
54
+ * flex: '1',
55
+ * display: 'flex',
56
+ * justifyContent: 'center',
57
+ * alignItems: 'center',
58
+ * height: '150px',
59
+ * }}
60
+ * >
61
+ * Main content
62
+ * </div>
63
+ * </div>
64
+ * <div style={{ display: 'flex', flexDirection: 'column', height: '100%', borderLeft: '1px solid var(--border-color)' }}>
65
+ * <Toolbar>
66
+ * <ToolbarStack stackSize="l">
67
+ * <ToolbarTitle>Secondary</ToolbarTitle>
68
+ * </ToolbarStack>
69
+ * </Toolbar>
70
+ * <div
71
+ * style={{
72
+ * flex: '1',
73
+ * display: 'flex',
74
+ * justifyContent: 'center',
75
+ * alignItems: 'center',
76
+ * height: '150px',
77
+ * }}
78
+ * >
79
+ * Sidebar
80
+ * </div>
81
+ * </div>
82
+ * </VerticalSplit>
83
+ * </div>
84
+ * </Canvas>;
85
+ * ```
86
+ *
87
+ * @example Resizable, right primary panel
88
+ *
89
+ * ```js
90
+ * <Canvas ctx={ctx}>
91
+ * <div style={{ height: 500, position: 'relative' }}>
92
+ * <VerticalSplit primaryPane="right" size="25%" minSize={220}>
93
+ * <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
94
+ * <Toolbar>
95
+ * <ToolbarStack stackSize="l">
96
+ * <ToolbarTitle>Secondary</ToolbarTitle>
97
+ * </ToolbarStack>
98
+ * </Toolbar>
99
+ * <div
100
+ * style={{
101
+ * flex: '1',
102
+ * display: 'flex',
103
+ * justifyContent: 'center',
104
+ * alignItems: 'center',
105
+ * height: '150px',
106
+ * }}
107
+ * >
108
+ * Sidebar
109
+ * </div>
110
+ * </div>
111
+ * <div style={{ display: 'flex', flexDirection: 'column', height: '100%', borderLeft: '1px solid var(--border-color)' }}>
112
+ * <Toolbar>
113
+ * <ToolbarStack stackSize="l">
114
+ * <ToolbarTitle>Primary</ToolbarTitle>
115
+ * </ToolbarStack>
116
+ * </Toolbar>
117
+ * <div
118
+ * style={{
119
+ * flex: '1',
120
+ * display: 'flex',
121
+ * justifyContent: 'center',
122
+ * alignItems: 'center',
123
+ * height: '150px',
124
+ * }}
125
+ * >
126
+ * Main content
127
+ * </div>
128
+ * </div>
129
+ * </VerticalSplit>
130
+ * </div>
131
+ * </Canvas>;
132
+ * ```
133
+ *
134
+ * @example Collapsible
135
+ *
136
+ * ```js
137
+ * <Canvas ctx={ctx}>
138
+ * <div style={{ height: 500, position: 'relative' }}>
139
+ * <StateManager initial={true}>
140
+ * {(isCollapsed, setCollapsed) => (
141
+ * <VerticalSplit
142
+ * primaryPane="left"
143
+ * size="25%"
144
+ * minSize={220}
145
+ * isSecondaryCollapsed={isCollapsed}
146
+ * onSecondaryToggle={setCollapsed}
147
+ * >
148
+ * <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
149
+ * <Toolbar>
150
+ * <ToolbarStack stackSize="l">
151
+ * <ToolbarTitle>Primary</ToolbarTitle>
152
+ * </ToolbarStack>
153
+ * </Toolbar>
154
+ * <div
155
+ * style={{
156
+ * flex: '1',
157
+ * display: 'flex',
158
+ * justifyContent: 'center',
159
+ * alignItems: 'center',
160
+ * height: '150px',
161
+ * }}
162
+ * >
163
+ * Main content
164
+ * </div>
165
+ * </div>
166
+ * <div
167
+ * style={{
168
+ * display: 'flex',
169
+ * flexDirection: 'column',
170
+ * height: '100%',
171
+ * borderLeft: '1px solid var(--border-color)',
172
+ * }}
173
+ * >
174
+ * <Toolbar>
175
+ * <ToolbarStack stackSize="l">
176
+ * <ToolbarTitle>Secondary</ToolbarTitle>
177
+ * </ToolbarStack>
178
+ * </Toolbar>
179
+ * <div
180
+ * style={{
181
+ * flex: '1',
182
+ * display: 'flex',
183
+ * justifyContent: 'center',
184
+ * alignItems: 'center',
185
+ * height: '150px',
186
+ * }}
187
+ * >
188
+ * Sidebar
189
+ * </div>
190
+ * </div>
191
+ * </VerticalSplit>
192
+ * )}
193
+ * </StateManager>
194
+ * </div>
195
+ * </Canvas>;
196
+ * ```
197
+ *
198
+ * @example Overlay mode
199
+ *
200
+ * ```js
201
+ * <Canvas ctx={ctx}>
202
+ * <div style={{ height: 500, position: 'relative' }}>
203
+ * <StateManager initial={true}>
204
+ * {(isCollapsed, setCollapsed) => (
205
+ * <VerticalSplit
206
+ * mode="overlay"
207
+ * primaryPane="left"
208
+ * size="25%"
209
+ * minSize={220}
210
+ * isSecondaryCollapsed={isCollapsed}
211
+ * onSecondaryToggle={setCollapsed}
212
+ * >
213
+ * <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
214
+ * <Toolbar>
215
+ * <ToolbarStack stackSize="l">
216
+ * <ToolbarTitle>Primary</ToolbarTitle>
217
+ * </ToolbarStack>
218
+ * </Toolbar>
219
+ * <div
220
+ * style={{
221
+ * flex: '1',
222
+ * display: 'flex',
223
+ * justifyContent: 'center',
224
+ * alignItems: 'center',
225
+ * height: '150px',
226
+ * }}
227
+ * >
228
+ * Main content
229
+ * </div>
230
+ * </div>
231
+ * <div
232
+ * style={{
233
+ * display: 'flex',
234
+ * flexDirection: 'column',
235
+ * height: '100%',
236
+ * borderLeft: '1px solid var(--border-color)',
237
+ * }}
238
+ * >
239
+ * <Toolbar>
240
+ * <ToolbarStack stackSize="l">
241
+ * <ToolbarTitle>Secondary</ToolbarTitle>
242
+ * </ToolbarStack>
243
+ * </Toolbar>
244
+ * <div
245
+ * style={{
246
+ * flex: '1',
247
+ * display: 'flex',
248
+ * justifyContent: 'center',
249
+ * alignItems: 'center',
250
+ * height: '150px',
251
+ * }}
252
+ * >
253
+ * Sidebar
254
+ * </div>
255
+ * </div>
256
+ * </VerticalSplit>
257
+ * )}
258
+ * </StateManager>
259
+ * </div>
260
+ * </Canvas>;
261
+ * ```
262
+ */
263
+ export function VerticalSplit({
264
+ mode = 'split',
265
+ minSize = 200,
266
+ maxSize,
267
+ size = initialSidebarWidth,
268
+ primaryPane,
269
+ children,
270
+ allowResize = true,
271
+ onDragFinished,
272
+ onSecondaryToggle,
273
+ isSecondaryCollapsed,
274
+ }: VerticalSplitProps) {
275
+ const [sizes, setSizes] = useState<Size[]>(
276
+ calculateSizes({ size, primaryPane, isSecondaryCollapsed }),
277
+ );
278
+ const currentSizes = useRef<Size[]>(sizes);
279
+
280
+ const SashActionIcon = onSecondaryToggle
281
+ ? primaryPane === 'left'
282
+ ? isSecondaryCollapsed
283
+ ? SidebarFlipIcon
284
+ : ChevronsRightIcon
285
+ : isSecondaryCollapsed
286
+ ? ChevronsRightIcon
287
+ : ChevronsLeftIcon
288
+ : undefined;
289
+
290
+ useEffect(() => {
291
+ setSizes(calculateSizes({ size, primaryPane, isSecondaryCollapsed }));
292
+ }, [size, primaryPane, isSecondaryCollapsed]);
293
+
294
+ function handleChange(newSizes: Size[]) {
295
+ setSizes(newSizes);
296
+ currentSizes.current = newSizes;
297
+ }
298
+
299
+ function handleDragEnd() {
300
+ onDragFinished?.(currentSizes.current[0] as number);
301
+ }
302
+
303
+ function handleSecondaryClick() {
304
+ onSecondaryToggle?.(!isSecondaryCollapsed);
305
+ }
306
+
307
+ function renderPane(pane: 'left' | 'right') {
308
+ const isSecondaryPane = primaryPane !== pane;
309
+
310
+ return (
311
+ <SplitViewPane
312
+ {...(isSecondaryPane && !isSecondaryCollapsed
313
+ ? { minSize, maxSize }
314
+ : {})}
315
+ >
316
+ {isSecondaryPane && isSecondaryCollapsed ? (
317
+ <div
318
+ className={classNames(
319
+ s.VerticalSplitPane__expand,
320
+ s[`VerticalSplitPane__expand--${pane}`],
321
+ )}
322
+ onClick={handleSecondaryClick}
323
+ />
324
+ ) : (
325
+ children[pane === 'left' ? 0 : 1]
326
+ )}
327
+ </SplitViewPane>
328
+ );
329
+ }
330
+
331
+ if (mode === 'overlay' && !isSecondaryCollapsed && SashActionIcon) {
332
+ const primaryPaneChild = children[primaryPane === 'left' ? 0 : 1];
333
+ const secondaryPaneChild = children[primaryPane === 'left' ? 1 : 0];
334
+
335
+ return (
336
+ <>
337
+ <div
338
+ className={s.VerticalSplitPaneOverlay}
339
+ onClick={handleSecondaryClick}
340
+ >
341
+ <div
342
+ className={classNames(
343
+ s.VerticalSplitPaneOverlay__secondary,
344
+ s[
345
+ `VerticalSplitPaneOverlay__secondary--${
346
+ primaryPane === 'left' ? 'right' : 'left'
347
+ }`
348
+ ],
349
+ )}
350
+ style={{
351
+ width: size,
352
+ maxWidth: maxSize,
353
+ minWidth: minSize,
354
+ }}
355
+ onClick={(e) => {
356
+ e.stopPropagation();
357
+ }}
358
+ >
359
+ {secondaryPaneChild}
360
+ <div className={s.VerticalSplitPaneOverlay__sash}>
361
+ <div
362
+ className={sashS.SplitViewSash__content}
363
+ onClick={handleSecondaryClick}
364
+ >
365
+ <div className={sashS.SplitViewSash__content__button}>
366
+ <SashActionIcon />
367
+ </div>
368
+ </div>
369
+ </div>
370
+ </div>
371
+ </div>
372
+ <div
373
+ className={classNames(
374
+ s.VerticalSplitPaneOverlay__primary,
375
+ s[`VerticalSplitPaneOverlay__primary--${primaryPane}`],
376
+ )}
377
+ >
378
+ {primaryPaneChild}
379
+ </div>
380
+ </>
381
+ );
382
+ }
383
+
384
+ return (
385
+ <SplitView
386
+ allowResize={!isSecondaryCollapsed && allowResize}
387
+ sizes={sizes}
388
+ onChange={handleChange}
389
+ onDragEnd={handleDragEnd}
390
+ sashAction={
391
+ SashActionIcon && {
392
+ icon: <SashActionIcon />,
393
+ onClick: handleSecondaryClick,
394
+ }
395
+ }
396
+ >
397
+ {renderPane('left')}
398
+ {renderPane('right')}
399
+ </SplitView>
400
+ );
401
+ }