datocms-react-ui 2.0.13 → 2.0.14

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 +2 -2
  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 +402 -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 +2721 -1848
@@ -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,402 @@
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
+ /**
41
+ * @example Resizable, left primary panel
42
+ *
43
+ * ```js
44
+ * <Canvas ctx={ctx}>
45
+ * <div style={{ height: 500, position: 'relative' }}>
46
+ * <VerticalSplit primaryPane="left" size="25%" minSize={220}>
47
+ * <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
48
+ * <Toolbar>
49
+ * <ToolbarStack stackSize="l">
50
+ * <ToolbarTitle>Primary</ToolbarTitle>
51
+ * </ToolbarStack>
52
+ * </Toolbar>
53
+ * <div
54
+ * style={{
55
+ * flex: '1',
56
+ * display: 'flex',
57
+ * justifyContent: 'center',
58
+ * alignItems: 'center',
59
+ * height: '150px',
60
+ * }}
61
+ * >
62
+ * Main content
63
+ * </div>
64
+ * </div>
65
+ * <div style={{ display: 'flex', flexDirection: 'column', height: '100%', borderLeft: '1px solid var(--border-color)' }}>
66
+ * <Toolbar>
67
+ * <ToolbarStack stackSize="l">
68
+ * <ToolbarTitle>Secondary</ToolbarTitle>
69
+ * </ToolbarStack>
70
+ * </Toolbar>
71
+ * <div
72
+ * style={{
73
+ * flex: '1',
74
+ * display: 'flex',
75
+ * justifyContent: 'center',
76
+ * alignItems: 'center',
77
+ * height: '150px',
78
+ * }}
79
+ * >
80
+ * Sidebar
81
+ * </div>
82
+ * </div>
83
+ * </VerticalSplit>
84
+ * </div>
85
+ * </Canvas>;
86
+ * ```
87
+ *
88
+ * @example Resizable, right primary panel
89
+ *
90
+ * ```js
91
+ * <Canvas ctx={ctx}>
92
+ * <div style={{ height: 500, position: 'relative' }}>
93
+ * <VerticalSplit primaryPane="right" size="25%" minSize={220}>
94
+ * <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
95
+ * <Toolbar>
96
+ * <ToolbarStack stackSize="l">
97
+ * <ToolbarTitle>Secondary</ToolbarTitle>
98
+ * </ToolbarStack>
99
+ * </Toolbar>
100
+ * <div
101
+ * style={{
102
+ * flex: '1',
103
+ * display: 'flex',
104
+ * justifyContent: 'center',
105
+ * alignItems: 'center',
106
+ * height: '150px',
107
+ * }}
108
+ * >
109
+ * Sidebar
110
+ * </div>
111
+ * </div>
112
+ * <div style={{ display: 'flex', flexDirection: 'column', height: '100%', borderLeft: '1px solid var(--border-color)' }}>
113
+ * <Toolbar>
114
+ * <ToolbarStack stackSize="l">
115
+ * <ToolbarTitle>Primary</ToolbarTitle>
116
+ * </ToolbarStack>
117
+ * </Toolbar>
118
+ * <div
119
+ * style={{
120
+ * flex: '1',
121
+ * display: 'flex',
122
+ * justifyContent: 'center',
123
+ * alignItems: 'center',
124
+ * height: '150px',
125
+ * }}
126
+ * >
127
+ * Main content
128
+ * </div>
129
+ * </div>
130
+ * </VerticalSplit>
131
+ * </div>
132
+ * </Canvas>;
133
+ * ```
134
+ *
135
+ * @example Collapsible
136
+ *
137
+ * ```js
138
+ * <Canvas ctx={ctx}>
139
+ * <div style={{ height: 500, position: 'relative' }}>
140
+ * <StateManager initial={true}>
141
+ * {(isCollapsed, setCollapsed) => (
142
+ * <VerticalSplit
143
+ * primaryPane="left"
144
+ * size="25%"
145
+ * minSize={220}
146
+ * isSecondaryCollapsed={isCollapsed}
147
+ * onSecondaryToggle={setCollapsed}
148
+ * >
149
+ * <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
150
+ * <Toolbar>
151
+ * <ToolbarStack stackSize="l">
152
+ * <ToolbarTitle>Primary</ToolbarTitle>
153
+ * </ToolbarStack>
154
+ * </Toolbar>
155
+ * <div
156
+ * style={{
157
+ * flex: '1',
158
+ * display: 'flex',
159
+ * justifyContent: 'center',
160
+ * alignItems: 'center',
161
+ * height: '150px',
162
+ * }}
163
+ * >
164
+ * Main content
165
+ * </div>
166
+ * </div>
167
+ * <div
168
+ * style={{
169
+ * display: 'flex',
170
+ * flexDirection: 'column',
171
+ * height: '100%',
172
+ * borderLeft: '1px solid var(--border-color)',
173
+ * }}
174
+ * >
175
+ * <Toolbar>
176
+ * <ToolbarStack stackSize="l">
177
+ * <ToolbarTitle>Secondary</ToolbarTitle>
178
+ * </ToolbarStack>
179
+ * </Toolbar>
180
+ * <div
181
+ * style={{
182
+ * flex: '1',
183
+ * display: 'flex',
184
+ * justifyContent: 'center',
185
+ * alignItems: 'center',
186
+ * height: '150px',
187
+ * }}
188
+ * >
189
+ * Sidebar
190
+ * </div>
191
+ * </div>
192
+ * </VerticalSplit>
193
+ * )}
194
+ * </StateManager>
195
+ * </div>
196
+ * </Canvas>;
197
+ * ```
198
+ *
199
+ * @example Overlay mode
200
+ *
201
+ * ```js
202
+ * <Canvas ctx={ctx}>
203
+ * <div style={{ height: 500, position: 'relative' }}>
204
+ * <StateManager initial={true}>
205
+ * {(isCollapsed, setCollapsed) => (
206
+ * <VerticalSplit
207
+ * mode="overlay"
208
+ * primaryPane="left"
209
+ * size="25%"
210
+ * minSize={220}
211
+ * isSecondaryCollapsed={isCollapsed}
212
+ * onSecondaryToggle={setCollapsed}
213
+ * >
214
+ * <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
215
+ * <Toolbar>
216
+ * <ToolbarStack stackSize="l">
217
+ * <ToolbarTitle>Primary</ToolbarTitle>
218
+ * </ToolbarStack>
219
+ * </Toolbar>
220
+ * <div
221
+ * style={{
222
+ * flex: '1',
223
+ * display: 'flex',
224
+ * justifyContent: 'center',
225
+ * alignItems: 'center',
226
+ * height: '150px',
227
+ * }}
228
+ * >
229
+ * Main content
230
+ * </div>
231
+ * </div>
232
+ * <div
233
+ * style={{
234
+ * display: 'flex',
235
+ * flexDirection: 'column',
236
+ * height: '100%',
237
+ * borderLeft: '1px solid var(--border-color)',
238
+ * }}
239
+ * >
240
+ * <Toolbar>
241
+ * <ToolbarStack stackSize="l">
242
+ * <ToolbarTitle>Secondary</ToolbarTitle>
243
+ * </ToolbarStack>
244
+ * </Toolbar>
245
+ * <div
246
+ * style={{
247
+ * flex: '1',
248
+ * display: 'flex',
249
+ * justifyContent: 'center',
250
+ * alignItems: 'center',
251
+ * height: '150px',
252
+ * }}
253
+ * >
254
+ * Sidebar
255
+ * </div>
256
+ * </div>
257
+ * </VerticalSplit>
258
+ * )}
259
+ * </StateManager>
260
+ * </div>
261
+ * </Canvas>;
262
+ * ```
263
+ */
264
+ export function VerticalSplit({
265
+ mode = 'split',
266
+ minSize = 200,
267
+ maxSize,
268
+ size = initialSidebarWidth,
269
+ primaryPane,
270
+ children,
271
+ allowResize = true,
272
+ onDragFinished,
273
+ onSecondaryToggle,
274
+ isSecondaryCollapsed,
275
+ }: VerticalSplitProps) {
276
+ const [sizes, setSizes] = useState<Size[]>(
277
+ calculateSizes({ size, primaryPane, isSecondaryCollapsed }),
278
+ );
279
+ const currentSizes = useRef<Size[]>(sizes);
280
+
281
+ const SashActionIcon = onSecondaryToggle
282
+ ? primaryPane === 'left'
283
+ ? isSecondaryCollapsed
284
+ ? SidebarFlipIcon
285
+ : ChevronsRightIcon
286
+ : isSecondaryCollapsed
287
+ ? ChevronsRightIcon
288
+ : ChevronsLeftIcon
289
+ : undefined;
290
+
291
+ useEffect(() => {
292
+ setSizes(calculateSizes({ size, primaryPane, isSecondaryCollapsed }));
293
+ }, [size, primaryPane, isSecondaryCollapsed]);
294
+
295
+ function handleChange(newSizes: Size[]) {
296
+ setSizes(newSizes);
297
+ currentSizes.current = newSizes;
298
+ }
299
+
300
+ function handleDragEnd() {
301
+ onDragFinished?.(currentSizes.current[0] as number);
302
+ }
303
+
304
+ function handleSecondaryClick() {
305
+ onSecondaryToggle?.(!isSecondaryCollapsed);
306
+ }
307
+
308
+ function renderPane(pane: 'left' | 'right') {
309
+ const isSecondaryPane = primaryPane !== pane;
310
+
311
+ return (
312
+ <SplitViewPane
313
+ {...(isSecondaryPane && !isSecondaryCollapsed
314
+ ? { minSize, maxSize }
315
+ : {})}
316
+ >
317
+ {isSecondaryPane && isSecondaryCollapsed ? (
318
+ <div
319
+ className={classNames(
320
+ s.VerticalSplitPane__expand,
321
+ s[`VerticalSplitPane__expand--${pane}`],
322
+ )}
323
+ onClick={handleSecondaryClick}
324
+ />
325
+ ) : (
326
+ children[pane === 'left' ? 0 : 1]
327
+ )}
328
+ </SplitViewPane>
329
+ );
330
+ }
331
+
332
+ if (mode === 'overlay' && !isSecondaryCollapsed && SashActionIcon) {
333
+ const primaryPaneChild = children[primaryPane === 'left' ? 0 : 1];
334
+ const secondaryPaneChild = children[primaryPane === 'left' ? 1 : 0];
335
+
336
+ return (
337
+ <>
338
+ <div
339
+ className={s.VerticalSplitPaneOverlay}
340
+ onClick={handleSecondaryClick}
341
+ >
342
+ <div
343
+ className={classNames(
344
+ s.VerticalSplitPaneOverlay__secondary,
345
+ s[
346
+ `VerticalSplitPaneOverlay__secondary--${
347
+ primaryPane === 'left' ? 'right' : 'left'
348
+ }`
349
+ ],
350
+ )}
351
+ style={{
352
+ width: size,
353
+ maxWidth: maxSize,
354
+ minWidth: minSize,
355
+ }}
356
+ onClick={(e) => {
357
+ e.stopPropagation();
358
+ }}
359
+ >
360
+ {secondaryPaneChild}
361
+ <div className={s.VerticalSplitPaneOverlay__sash}>
362
+ <div
363
+ className={sashS.SplitViewSash__content}
364
+ onClick={handleSecondaryClick}
365
+ >
366
+ <div className={sashS.SplitViewSash__content__button}>
367
+ <SashActionIcon />
368
+ </div>
369
+ </div>
370
+ </div>
371
+ </div>
372
+ </div>
373
+ <div
374
+ className={classNames(
375
+ s.VerticalSplitPaneOverlay__primary,
376
+ s[`VerticalSplitPaneOverlay__primary--${primaryPane}`],
377
+ )}
378
+ >
379
+ {primaryPaneChild}
380
+ </div>
381
+ </>
382
+ );
383
+ }
384
+
385
+ return (
386
+ <SplitView
387
+ allowResize={!isSecondaryCollapsed && allowResize}
388
+ sizes={sizes}
389
+ onChange={handleChange}
390
+ onDragEnd={handleDragEnd}
391
+ sashAction={
392
+ SashActionIcon && {
393
+ icon: <SashActionIcon />,
394
+ onClick: handleSecondaryClick,
395
+ }
396
+ }
397
+ >
398
+ {renderPane('left')}
399
+ {renderPane('right')}
400
+ </SplitView>
401
+ );
402
+ }