react-split-pane 2.0.3 → 3.0.1
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.
- package/README.md +451 -144
- package/dist/components/Divider.d.ts +31 -0
- package/dist/components/Divider.d.ts.map +1 -0
- package/dist/components/Pane.d.ts +27 -0
- package/dist/components/Pane.d.ts.map +1 -0
- package/dist/components/SplitPane.d.ts +29 -0
- package/dist/components/SplitPane.d.ts.map +1 -0
- package/dist/hooks/useKeyboardResize.d.ts +34 -0
- package/dist/hooks/useKeyboardResize.d.ts.map +1 -0
- package/dist/hooks/usePaneSize.d.ts +16 -0
- package/dist/hooks/usePaneSize.d.ts.map +1 -0
- package/dist/hooks/useResizer.d.ts +44 -0
- package/dist/hooks/useResizer.d.ts.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/keyboard.cjs +2 -0
- package/dist/keyboard.cjs.map +1 -0
- package/dist/keyboard.d.ts +3 -0
- package/dist/keyboard.d.ts.map +1 -0
- package/dist/keyboard.js +2 -0
- package/dist/keyboard.js.map +1 -0
- package/dist/persistence.cjs +2 -0
- package/dist/persistence.cjs.map +1 -0
- package/dist/persistence.d.ts +24 -0
- package/dist/persistence.d.ts.map +1 -0
- package/dist/persistence.js +2 -0
- package/dist/persistence.js.map +1 -0
- package/dist/test/setup.d.ts +2 -0
- package/dist/test/setup.d.ts.map +1 -0
- package/dist/types/index.d.ts +89 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/utils/accessibility.d.ts +17 -0
- package/dist/utils/accessibility.d.ts.map +1 -0
- package/dist/utils/calculations.d.ts +29 -0
- package/dist/utils/calculations.d.ts.map +1 -0
- package/package.json +82 -67
- package/styles.css +106 -0
- package/.editorconfig +0 -6
- package/.storybook/addons.js +0 -2
- package/.storybook/config.js +0 -9
- package/.storybook/preview-head.html +0 -33
- package/.travis.yml +0 -10
- package/index.d.ts +0 -53
- package/index.js +0 -3
- package/lcov.info +0 -700
- package/lib/Pane.js +0 -130
- package/lib/Resizer.js +0 -105
- package/lib/SplitPane.js +0 -512
- package/stories/index.stories.js +0 -40
- package/tsconfig.json +0 -11
package/README.md
CHANGED
|
@@ -1,183 +1,490 @@
|
|
|
1
|
-
# React Split Pane
|
|
1
|
+
# React Split Pane v3
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Modern, accessible, TypeScript-first split pane component for React.
|
|
4
4
|
|
|
5
|
+
[](https://www.npmjs.com/package/react-split-pane)
|
|
6
|
+

|
|
7
|
+
[](https://bundlephobia.com/package/react-split-pane)
|
|
8
|
+
|
|
9
|
+
**[Live Examples](https://tomkp.github.io/react-split-pane)**
|
|
10
|
+
|
|
11
|
+
## ✨ Features
|
|
12
|
+
|
|
13
|
+
- 🪝 **Hooks-based** - Built with modern React patterns
|
|
14
|
+
- 📘 **TypeScript** - Full type safety out of the box
|
|
15
|
+
- ♿ **Accessible** - Keyboard navigation, ARIA attributes, screen reader support
|
|
16
|
+
- 📱 **Touch-friendly** - Full mobile/tablet support
|
|
17
|
+
- 🎯 **Flexible** - Controlled/uncontrolled modes, nested layouts, 2+ panes
|
|
18
|
+
- 🪶 **Lightweight** - < 5KB gzipped
|
|
19
|
+
- ⚡ **Performant** - RAF-throttled resize, optimized renders
|
|
20
|
+
- 🎨 **Customizable** - Full styling control
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install react-split-pane@next
|
|
26
|
+
|
|
27
|
+
# or
|
|
28
|
+
yarn add react-split-pane@next
|
|
29
|
+
|
|
30
|
+
# or
|
|
31
|
+
pnpm add react-split-pane@next
|
|
5
32
|
```
|
|
6
|
-
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { SplitPane, Pane } from 'react-split-pane';
|
|
38
|
+
|
|
39
|
+
function App() {
|
|
40
|
+
return (
|
|
41
|
+
<SplitPane direction="horizontal">
|
|
42
|
+
<Pane minSize="200px" defaultSize="300px">
|
|
43
|
+
<Sidebar />
|
|
44
|
+
</Pane>
|
|
45
|
+
<Pane>
|
|
46
|
+
<MainContent />
|
|
47
|
+
</Pane>
|
|
48
|
+
</SplitPane>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
7
51
|
```
|
|
8
52
|
|
|
9
|
-
|
|
10
|
-
|
|
53
|
+
## Basic Usage
|
|
54
|
+
|
|
55
|
+
### Horizontal Split (Side-by-Side)
|
|
11
56
|
|
|
57
|
+
```tsx
|
|
58
|
+
<SplitPane direction="horizontal">
|
|
59
|
+
<Pane defaultSize="25%">
|
|
60
|
+
<LeftPanel />
|
|
61
|
+
</Pane>
|
|
62
|
+
<Pane>
|
|
63
|
+
<RightPanel />
|
|
64
|
+
</Pane>
|
|
65
|
+
</SplitPane>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Vertical Split (Top-Bottom)
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
<SplitPane direction="vertical">
|
|
72
|
+
<Pane defaultSize="100px">
|
|
73
|
+
<Header />
|
|
74
|
+
</Pane>
|
|
75
|
+
<Pane>
|
|
76
|
+
<Content />
|
|
77
|
+
</Pane>
|
|
78
|
+
</SplitPane>
|
|
79
|
+
```
|
|
12
80
|
|
|
13
|
-
|
|
81
|
+
### Controlled Mode
|
|
14
82
|
|
|
83
|
+
```tsx
|
|
84
|
+
function App() {
|
|
85
|
+
const [sizes, setSizes] = useState([300, 500]);
|
|
15
86
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
87
|
+
return (
|
|
88
|
+
<SplitPane onResize={setSizes}>
|
|
89
|
+
<Pane size={sizes[0]} minSize="200px">
|
|
90
|
+
<Sidebar />
|
|
91
|
+
</Pane>
|
|
92
|
+
<Pane size={sizes[1]}>
|
|
93
|
+
<Main />
|
|
94
|
+
</Pane>
|
|
95
|
+
</SplitPane>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
21
98
|
```
|
|
22
99
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
100
|
+
### Nested Layouts
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
<SplitPane direction="vertical">
|
|
104
|
+
<Pane defaultSize="60px">
|
|
105
|
+
<Header />
|
|
106
|
+
</Pane>
|
|
107
|
+
|
|
108
|
+
<SplitPane direction="horizontal">
|
|
109
|
+
<Pane defaultSize="250px" minSize="150px">
|
|
110
|
+
<Sidebar />
|
|
111
|
+
</Pane>
|
|
112
|
+
|
|
113
|
+
<SplitPane direction="vertical">
|
|
114
|
+
<Pane>
|
|
115
|
+
<Editor />
|
|
116
|
+
</Pane>
|
|
117
|
+
<Pane defaultSize="200px">
|
|
118
|
+
<Console />
|
|
119
|
+
</Pane>
|
|
30
120
|
</SplitPane>
|
|
121
|
+
</SplitPane>
|
|
122
|
+
</SplitPane>
|
|
31
123
|
```
|
|
32
124
|
|
|
33
|
-
|
|
125
|
+
## Advanced Features
|
|
34
126
|
|
|
35
|
-
|
|
36
|
-
The first pane keeps then its size while the second pane is resized by browser window.
|
|
37
|
-
By default it is the left pane for 'vertical' SplitPane and the top pane for 'horizontal' SplitPane.
|
|
38
|
-
If you want to keep size of the second pane and let the first pane to shrink or grow by browser window dimensions,
|
|
39
|
-
set SplitPane prop `primary` to `second`. In case of 'horizontal' SplitPane the height of bottom pane remains the same.
|
|
127
|
+
### Persistence
|
|
40
128
|
|
|
41
|
-
|
|
129
|
+
The `usePersistence` hook saves and restores pane sizes to localStorage (or sessionStorage):
|
|
42
130
|
|
|
43
|
-
|
|
131
|
+
```tsx
|
|
132
|
+
import { usePersistence } from 'react-split-pane/persistence';
|
|
44
133
|
|
|
45
|
-
|
|
134
|
+
function App() {
|
|
135
|
+
const [sizes, setSizes] = usePersistence({ key: 'my-layout' });
|
|
46
136
|
|
|
47
|
-
|
|
48
|
-
<SplitPane
|
|
49
|
-
|
|
50
|
-
<
|
|
137
|
+
return (
|
|
138
|
+
<SplitPane onResize={setSizes}>
|
|
139
|
+
<Pane size={sizes[0] || 300}>
|
|
140
|
+
<Sidebar />
|
|
141
|
+
</Pane>
|
|
142
|
+
<Pane size={sizes[1]}>
|
|
143
|
+
<Main />
|
|
144
|
+
</Pane>
|
|
51
145
|
</SplitPane>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
52
148
|
```
|
|
53
149
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
150
|
+
#### usePersistence Options
|
|
151
|
+
|
|
152
|
+
| Option | Type | Default | Description |
|
|
153
|
+
|--------|------|---------|-------------|
|
|
154
|
+
| `key` | `string` | Required | Storage key for persisting sizes |
|
|
155
|
+
| `storage` | `Storage` | `localStorage` | Storage backend (localStorage or sessionStorage) |
|
|
156
|
+
| `debounce` | `number` | `300` | Debounce delay in ms before saving |
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
// Use sessionStorage instead of localStorage
|
|
160
|
+
const [sizes, setSizes] = usePersistence({
|
|
161
|
+
key: 'my-layout',
|
|
162
|
+
storage: sessionStorage,
|
|
163
|
+
debounce: 500,
|
|
164
|
+
});
|
|
165
|
+
```
|
|
62
166
|
|
|
63
|
-
###
|
|
64
|
-
You can use the step prop to only allow resizing in fixed increments.
|
|
167
|
+
### Snap Points
|
|
65
168
|
|
|
66
|
-
|
|
169
|
+
```tsx
|
|
170
|
+
<SplitPane
|
|
171
|
+
snapPoints={[200, 400, 600]}
|
|
172
|
+
snapTolerance={20}
|
|
173
|
+
>
|
|
174
|
+
{/* panes */}
|
|
175
|
+
</SplitPane>
|
|
176
|
+
```
|
|
67
177
|
|
|
68
|
-
|
|
69
|
-
defaultSize and a persistence layer, you can ensure that your splitter choices
|
|
70
|
-
survive a refresh of your app.
|
|
178
|
+
### Custom Divider
|
|
71
179
|
|
|
72
|
-
|
|
73
|
-
|
|
180
|
+
```tsx
|
|
181
|
+
function CustomDivider(props) {
|
|
182
|
+
return (
|
|
183
|
+
<div {...props} style={{ ...props.style, background: 'blue' }}>
|
|
184
|
+
<GripIcon />
|
|
185
|
+
</div>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
74
188
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
189
|
+
<SplitPane divider={CustomDivider}>
|
|
190
|
+
{/* panes */}
|
|
191
|
+
</SplitPane>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Keyboard Navigation
|
|
195
|
+
|
|
196
|
+
The divider is fully keyboard accessible:
|
|
197
|
+
|
|
198
|
+
- **Arrow Keys**: Resize by `step` pixels (default: 10px)
|
|
199
|
+
- **Shift + Arrow**: Resize by larger step (default: 50px)
|
|
200
|
+
- **Home**: Minimize left/top pane
|
|
201
|
+
- **End**: Maximize left/top pane
|
|
202
|
+
- **Tab**: Navigate between dividers
|
|
203
|
+
|
|
204
|
+
## API Reference
|
|
205
|
+
|
|
206
|
+
### SplitPane Props
|
|
207
|
+
|
|
208
|
+
| Prop | Type | Default | Description |
|
|
209
|
+
|------|------|---------|-------------|
|
|
210
|
+
| `direction` | `'horizontal' \| 'vertical'` | `'horizontal'` | Layout direction |
|
|
211
|
+
| `resizable` | `boolean` | `true` | Whether panes can be resized |
|
|
212
|
+
| `snapPoints` | `number[]` | `[]` | Snap points in pixels |
|
|
213
|
+
| `snapTolerance` | `number` | `10` | Snap tolerance in pixels |
|
|
214
|
+
| `step` | `number` | `10` | Keyboard resize step |
|
|
215
|
+
| `onResizeStart` | `(event) => void` | - | Called when resize starts |
|
|
216
|
+
| `onResize` | `(sizes, event) => void` | - | Called during resize |
|
|
217
|
+
| `onResizeEnd` | `(sizes, event) => void` | - | Called when resize ends |
|
|
218
|
+
| `className` | `string` | - | CSS class name |
|
|
219
|
+
| `style` | `CSSProperties` | - | Inline styles |
|
|
220
|
+
| `divider` | `ComponentType` | - | Custom divider component |
|
|
221
|
+
| `dividerClassName` | `string` | - | Divider class name |
|
|
222
|
+
| `dividerStyle` | `CSSProperties` | - | Divider inline styles |
|
|
223
|
+
|
|
224
|
+
### Pane Props
|
|
225
|
+
|
|
226
|
+
| Prop | Type | Default | Description |
|
|
227
|
+
|------|------|---------|-------------|
|
|
228
|
+
| `defaultSize` | `string \| number` | `'50%'` | Initial size (uncontrolled) |
|
|
229
|
+
| `size` | `string \| number` | - | Controlled size |
|
|
230
|
+
| `minSize` | `string \| number` | `0` | Minimum size |
|
|
231
|
+
| `maxSize` | `string \| number` | `Infinity` | Maximum size |
|
|
232
|
+
| `className` | `string` | - | CSS class name |
|
|
233
|
+
| `style` | `CSSProperties` | - | Inline styles |
|
|
234
|
+
|
|
235
|
+
## Styling
|
|
236
|
+
|
|
237
|
+
### Default Stylesheet
|
|
238
|
+
|
|
239
|
+
Import the optional default styles with CSS custom properties:
|
|
240
|
+
|
|
241
|
+
```tsx
|
|
242
|
+
import 'react-split-pane/styles.css';
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Customize via CSS variables:
|
|
246
|
+
|
|
247
|
+
```css
|
|
248
|
+
.my-split-pane {
|
|
249
|
+
--split-pane-divider-size: 8px;
|
|
250
|
+
--split-pane-divider-color: #e0e0e0;
|
|
251
|
+
--split-pane-divider-color-hover: #b0b0b0;
|
|
252
|
+
--split-pane-focus-color: #2196f3;
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
The default styles include dark mode support via `prefers-color-scheme`.
|
|
257
|
+
|
|
258
|
+
### Basic Styles
|
|
259
|
+
|
|
260
|
+
```css
|
|
261
|
+
.split-pane {
|
|
262
|
+
height: 100vh;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.split-pane-divider {
|
|
266
|
+
background: #e0e0e0;
|
|
267
|
+
transition: background 0.2s;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.split-pane-divider:hover {
|
|
271
|
+
background: #b0b0b0;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.split-pane-divider:focus {
|
|
275
|
+
outline: 2px solid #2196f3;
|
|
276
|
+
outline-offset: -2px;
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Expanded Hover Area
|
|
281
|
+
|
|
282
|
+
This classic pattern creates a thin visible divider with a larger grabbable area that reveals on hover:
|
|
283
|
+
|
|
284
|
+
```css
|
|
285
|
+
.split-pane-divider {
|
|
286
|
+
background: #000;
|
|
287
|
+
opacity: 0.2;
|
|
288
|
+
z-index: 1;
|
|
289
|
+
box-sizing: border-box;
|
|
290
|
+
background-clip: padding-box;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.split-pane-divider:hover {
|
|
294
|
+
transition: all 0.2s ease;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.split-pane-divider.horizontal {
|
|
298
|
+
width: 11px;
|
|
299
|
+
margin: 0 -5px;
|
|
300
|
+
border-left: 5px solid rgba(255, 255, 255, 0);
|
|
301
|
+
border-right: 5px solid rgba(255, 255, 255, 0);
|
|
302
|
+
cursor: col-resize;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.split-pane-divider.horizontal:hover {
|
|
306
|
+
border-left: 5px solid rgba(0, 0, 0, 0.5);
|
|
307
|
+
border-right: 5px solid rgba(0, 0, 0, 0.5);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.split-pane-divider.vertical {
|
|
311
|
+
height: 11px;
|
|
312
|
+
margin: -5px 0;
|
|
313
|
+
border-top: 5px solid rgba(255, 255, 255, 0);
|
|
314
|
+
border-bottom: 5px solid rgba(255, 255, 255, 0);
|
|
315
|
+
cursor: row-resize;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.split-pane-divider.vertical:hover {
|
|
319
|
+
border-top: 5px solid rgba(0, 0, 0, 0.5);
|
|
320
|
+
border-bottom: 5px solid rgba(0, 0, 0, 0.5);
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Minimal Divider
|
|
325
|
+
|
|
326
|
+
A subtle single-pixel divider:
|
|
327
|
+
|
|
328
|
+
```css
|
|
329
|
+
.split-pane-divider.horizontal {
|
|
330
|
+
width: 1px;
|
|
331
|
+
margin: 0;
|
|
332
|
+
background: linear-gradient(to right, transparent, #ccc, transparent);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.split-pane-divider.vertical {
|
|
336
|
+
height: 1px;
|
|
337
|
+
margin: 0;
|
|
338
|
+
background: linear-gradient(to bottom, transparent, #ccc, transparent);
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
## Tailwind CSS & shadcn/ui
|
|
343
|
+
|
|
344
|
+
React Split Pane works seamlessly with Tailwind CSS and shadcn/ui. The component uses plain CSS and inline styles (no CSS-in-JS), so there are no conflicts with utility-first frameworks.
|
|
345
|
+
|
|
346
|
+
### Using Tailwind Classes
|
|
347
|
+
|
|
348
|
+
Apply Tailwind classes directly via `className` props. Skip importing the default stylesheet for full Tailwind control:
|
|
349
|
+
|
|
350
|
+
```tsx
|
|
351
|
+
import { SplitPane, Pane } from 'react-split-pane';
|
|
352
|
+
// Don't import 'react-split-pane/styles.css' if using Tailwind
|
|
353
|
+
|
|
354
|
+
<SplitPane
|
|
355
|
+
className="h-screen w-full"
|
|
356
|
+
dividerClassName="bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 transition-colors"
|
|
357
|
+
>
|
|
358
|
+
<Pane defaultSize="300px" className="bg-white dark:bg-gray-900 p-4">
|
|
359
|
+
<Sidebar />
|
|
360
|
+
</Pane>
|
|
361
|
+
<Pane className="bg-gray-50 dark:bg-gray-800 p-4">
|
|
362
|
+
<MainContent />
|
|
363
|
+
</Pane>
|
|
364
|
+
</SplitPane>
|
|
82
365
|
```
|
|
83
366
|
|
|
84
|
-
|
|
85
|
-
as Firefox have now optimized localStorage use so that they will asynchronously
|
|
86
|
-
initiate a read of all saved localStorage data for an origin once they know the
|
|
87
|
-
page will load. If the data has not fully loaded by the time code accesses
|
|
88
|
-
localStorage, the code will cause the page's main thread to block until the
|
|
89
|
-
database load completes. When the main thread is blocked, no other JS code will
|
|
90
|
-
run or layout will occur. In multiprocess browsers and for users with fast
|
|
91
|
-
disk storage, this will be less of a problem. You *are* likely to get yelled at
|
|
92
|
-
if you use localStorage.
|
|
367
|
+
### shadcn/ui Integration
|
|
93
368
|
|
|
94
|
-
|
|
95
|
-
https://github.com/mozilla/localForage although hooking it up will be slightly
|
|
96
|
-
more involved. You are likely to be admired by all for judiciously avoiding
|
|
97
|
-
use of localStorage.
|
|
369
|
+
Use shadcn's CSS variables and utilities for consistent theming:
|
|
98
370
|
|
|
99
|
-
|
|
371
|
+
```tsx
|
|
372
|
+
import { SplitPane, Pane } from 'react-split-pane';
|
|
100
373
|
|
|
101
|
-
|
|
102
|
-
|
|
374
|
+
<SplitPane
|
|
375
|
+
className="h-full w-full"
|
|
376
|
+
dividerClassName="bg-border hover:bg-accent transition-colors"
|
|
377
|
+
>
|
|
378
|
+
<Pane defaultSize="280px" className="bg-background border-r">
|
|
379
|
+
<Sidebar />
|
|
380
|
+
</Pane>
|
|
381
|
+
<Pane className="bg-muted/50">
|
|
382
|
+
<MainContent />
|
|
383
|
+
</Pane>
|
|
384
|
+
</SplitPane>
|
|
385
|
+
```
|
|
103
386
|
|
|
104
|
-
###
|
|
387
|
+
### Custom Divider with shadcn
|
|
388
|
+
|
|
389
|
+
Create a themed divider component using shadcn's `cn` utility:
|
|
390
|
+
|
|
391
|
+
```tsx
|
|
392
|
+
import { cn } from '@/lib/utils';
|
|
393
|
+
import type { DividerProps } from 'react-split-pane';
|
|
394
|
+
|
|
395
|
+
function ThemedDivider({ direction, isDragging, disabled, ...props }: DividerProps) {
|
|
396
|
+
return (
|
|
397
|
+
<div
|
|
398
|
+
className={cn(
|
|
399
|
+
'flex items-center justify-center transition-colors',
|
|
400
|
+
'bg-border hover:bg-accent focus:outline-none focus:ring-2 focus:ring-ring',
|
|
401
|
+
direction === 'horizontal'
|
|
402
|
+
? 'w-1 cursor-col-resize'
|
|
403
|
+
: 'h-1 cursor-row-resize',
|
|
404
|
+
isDragging && 'bg-primary',
|
|
405
|
+
disabled && 'cursor-not-allowed opacity-50'
|
|
406
|
+
)}
|
|
407
|
+
{...props}
|
|
408
|
+
/>
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
<SplitPane divider={ThemedDivider}>
|
|
413
|
+
<Pane>Left</Pane>
|
|
414
|
+
<Pane>Right</Pane>
|
|
415
|
+
</SplitPane>
|
|
416
|
+
```
|
|
105
417
|
|
|
106
|
-
|
|
418
|
+
### CSS Variables with Tailwind
|
|
107
419
|
|
|
108
|
-
|
|
420
|
+
Override the default CSS variables in your `globals.css` to match your Tailwind theme:
|
|
421
|
+
|
|
422
|
+
```css
|
|
423
|
+
/* globals.css */
|
|
424
|
+
@layer base {
|
|
425
|
+
:root {
|
|
426
|
+
--split-pane-divider-size: 4px;
|
|
427
|
+
--split-pane-divider-color: theme('colors.gray.200');
|
|
428
|
+
--split-pane-divider-color-hover: theme('colors.gray.300');
|
|
429
|
+
--split-pane-focus-color: theme('colors.blue.500');
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
.dark {
|
|
433
|
+
--split-pane-divider-color: theme('colors.gray.700');
|
|
434
|
+
--split-pane-divider-color-hover: theme('colors.gray.600');
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
```
|
|
109
438
|
|
|
439
|
+
Or with shadcn/ui CSS variables:
|
|
110
440
|
|
|
111
441
|
```css
|
|
442
|
+
@layer base {
|
|
443
|
+
:root {
|
|
444
|
+
--split-pane-divider-color: hsl(var(--border));
|
|
445
|
+
--split-pane-divider-color-hover: hsl(var(--accent));
|
|
446
|
+
--split-pane-focus-color: hsl(var(--ring));
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
## Migration from v0.1.x
|
|
452
|
+
|
|
453
|
+
See [MIGRATION.md](./MIGRATION.md) for detailed migration guide.
|
|
454
|
+
|
|
455
|
+
**Quick changes:**
|
|
456
|
+
|
|
457
|
+
```tsx
|
|
458
|
+
// v0.1.x
|
|
459
|
+
<SplitPane split="vertical" minSize={50} defaultSize={100}>
|
|
460
|
+
<div>Pane 1</div>
|
|
461
|
+
<div>Pane 2</div>
|
|
462
|
+
</SplitPane>
|
|
463
|
+
|
|
464
|
+
// v3
|
|
465
|
+
<SplitPane direction="horizontal">
|
|
466
|
+
<Pane minSize="50px" defaultSize="100px">
|
|
467
|
+
<div>Pane 1</div>
|
|
468
|
+
</Pane>
|
|
469
|
+
<Pane>
|
|
470
|
+
<div>Pane 2</div>
|
|
471
|
+
</Pane>
|
|
472
|
+
</SplitPane>
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
## Browser Support
|
|
476
|
+
|
|
477
|
+
- Chrome/Edge (latest 2 versions)
|
|
478
|
+
- Firefox (latest 2 versions)
|
|
479
|
+
- Safari (latest 2 versions)
|
|
480
|
+
- Mobile browsers (iOS Safari, Chrome Android)
|
|
481
|
+
|
|
482
|
+
**Note:** IE11 is not supported. Use v0.1.x for IE11 compatibility.
|
|
483
|
+
|
|
484
|
+
## Contributing
|
|
485
|
+
|
|
486
|
+
Contributions are welcome! Please see [CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
487
|
+
|
|
488
|
+
## License
|
|
112
489
|
|
|
113
|
-
|
|
114
|
-
background: #000;
|
|
115
|
-
opacity: .2;
|
|
116
|
-
z-index: 1;
|
|
117
|
-
-moz-box-sizing: border-box;
|
|
118
|
-
-webkit-box-sizing: border-box;
|
|
119
|
-
box-sizing: border-box;
|
|
120
|
-
-moz-background-clip: padding;
|
|
121
|
-
-webkit-background-clip: padding;
|
|
122
|
-
background-clip: padding-box;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
.Resizer:hover {
|
|
126
|
-
-webkit-transition: all 2s ease;
|
|
127
|
-
transition: all 2s ease;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
.Resizer.horizontal {
|
|
131
|
-
height: 11px;
|
|
132
|
-
margin: -5px 0;
|
|
133
|
-
border-top: 5px solid rgba(255, 255, 255, 0);
|
|
134
|
-
border-bottom: 5px solid rgba(255, 255, 255, 0);
|
|
135
|
-
cursor: row-resize;
|
|
136
|
-
width: 100%;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
.Resizer.horizontal:hover {
|
|
140
|
-
border-top: 5px solid rgba(0, 0, 0, 0.5);
|
|
141
|
-
border-bottom: 5px solid rgba(0, 0, 0, 0.5);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
.Resizer.vertical {
|
|
145
|
-
width: 11px;
|
|
146
|
-
margin: 0 -5px;
|
|
147
|
-
border-left: 5px solid rgba(255, 255, 255, 0);
|
|
148
|
-
border-right: 5px solid rgba(255, 255, 255, 0);
|
|
149
|
-
cursor: col-resize;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
.Resizer.vertical:hover {
|
|
153
|
-
border-left: 5px solid rgba(0, 0, 0, 0.5);
|
|
154
|
-
border-right: 5px solid rgba(0, 0, 0, 0.5);
|
|
155
|
-
}
|
|
156
|
-
.Resizer.disabled {
|
|
157
|
-
cursor: not-allowed;
|
|
158
|
-
}
|
|
159
|
-
.Resizer.disabled:hover {
|
|
160
|
-
border-color: transparent;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
```
|
|
164
|
-
### Inline Styles
|
|
165
|
-
|
|
166
|
-
You can also pass inline styles to the components via props. These are:
|
|
167
|
-
|
|
168
|
-
* `paneStyle` - Styling to be applied to both panes
|
|
169
|
-
* `pane1Style` - Styling to be applied to the first pane, with precedence over `paneStyle`
|
|
170
|
-
* `pane2Style` - Styling to be applied to the second pane, with precedence over `paneStyle`
|
|
171
|
-
* `resizerStyle` - Styling to be applied to the resizer bar
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
### Contributing
|
|
175
|
-
|
|
176
|
-
I'm always happy to receive Pull Requests for contributions of any kind.
|
|
177
|
-
|
|
178
|
-
Please include tests and/or update the examples if possible.
|
|
179
|
-
|
|
180
|
-
I've been working on an updated version of this library - if you'd like to get involved in any way please ping me a message.
|
|
181
|
-
|
|
182
|
-
Thanks, Tom
|
|
183
|
-
|
|
490
|
+
MIT © [tomkp](https://github.com/tomkp)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { DividerProps } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* The divider component that separates panes and handles resize interactions.
|
|
4
|
+
*
|
|
5
|
+
* This component is automatically rendered between panes by SplitPane.
|
|
6
|
+
* You can provide a custom divider via the `divider` prop on SplitPane.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Keyboard accessible (arrow keys, Home, End)
|
|
10
|
+
* - Touch-friendly for mobile devices
|
|
11
|
+
* - ARIA attributes for screen readers
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* // Custom divider component
|
|
16
|
+
* function CustomDivider(props: DividerProps) {
|
|
17
|
+
* return (
|
|
18
|
+
* <div {...props} className="my-divider">
|
|
19
|
+
* <GripIcon />
|
|
20
|
+
* </div>
|
|
21
|
+
* );
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* <SplitPane divider={CustomDivider}>
|
|
25
|
+
* <Pane>Left</Pane>
|
|
26
|
+
* <Pane>Right</Pane>
|
|
27
|
+
* </SplitPane>
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare function Divider(props: DividerProps): import("react/jsx-runtime").JSX.Element;
|
|
31
|
+
//# sourceMappingURL=Divider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Divider.d.ts","sourceRoot":"","sources":["../../src/components/Divider.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAQ7C;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,2CAgF1C"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { PaneProps } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* A pane component that must be used as a direct child of SplitPane.
|
|
4
|
+
*
|
|
5
|
+
* Panes can have size constraints and can be either controlled (with `size`)
|
|
6
|
+
* or uncontrolled (with `defaultSize`).
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* // Uncontrolled with constraints
|
|
11
|
+
* <Pane minSize="100px" maxSize="500px" defaultSize="300px">
|
|
12
|
+
* Content here
|
|
13
|
+
* </Pane>
|
|
14
|
+
*
|
|
15
|
+
* // Controlled
|
|
16
|
+
* <Pane size={sizes[0]} minSize={100}>
|
|
17
|
+
* Content here
|
|
18
|
+
* </Pane>
|
|
19
|
+
*
|
|
20
|
+
* // Percentage-based sizing
|
|
21
|
+
* <Pane defaultSize="25%" minSize="10%">
|
|
22
|
+
* Sidebar
|
|
23
|
+
* </Pane>
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare const Pane: import("react").ForwardRefExoticComponent<PaneProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
27
|
+
//# sourceMappingURL=Pane.d.ts.map
|