react-zeugma 0.5.1 → 0.5.2
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 +306 -58
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,34 +1,26 @@
|
|
|
1
|
-
|
|
1
|
+
{/_ @meta title="Introduction" _/}
|
|
2
2
|
|
|
3
3
|
# react-zeugma
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**A recursive, drag-and-drop dashboard layout engine for React.**
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
`react-zeugma` combines the tree-based, arbitrary splitting capabilities of `react-mosaic` with the declarative, state-driven API model of `react-grid-layout`. Built with React 18+, TypeScript, and [`@dnd-kit`](https://dndkit.com).
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/react-zeugma)
|
|
10
10
|
[](https://bundlephobia.com/package/react-zeugma)
|
|
11
11
|
[](./LICENSE)
|
|
12
12
|
[](https://www.typescriptlang.org)
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## Introduction
|
|
19
|
-
|
|
20
|
-
**react-zeugma** is a recursive drag-and-drop dashboard layout engine for React. It combines the tree-based, arbitrary splitting of _react-mosaic_ with the declarative, state-driven API of _react-grid-layout_, built on top of [`@dnd-kit`](https://dndkit.com).
|
|
21
|
-
|
|
14
|
+
> [!TIP]
|
|
22
15
|
> **Headless Design System** — react-zeugma is entirely style-agnostic and relies on your class name configurations for styling visual states. You bring your own CSS/Tailwind rules, and we handle the complex drag-and-drop mechanics, resize handle math, and layout tree calculations.
|
|
23
16
|
|
|
24
17
|
### Core Features
|
|
25
18
|
|
|
26
|
-
- **Recursive Split Trees
|
|
27
|
-
- **5-Zone Docking Previews
|
|
28
|
-
- **Native Flexbox Resizers
|
|
29
|
-
- **Accessible Drag-and-Drop
|
|
30
|
-
- **Fullscreen Zoom Toggle
|
|
31
|
-
- **Tree-shakeable & Tiny** — ESM-first with zero runtime CSS. Bring your own styles.
|
|
19
|
+
- **Recursive Split Trees**: Nest rows and columns to any depth using a simple serialized JSON node structure.
|
|
20
|
+
- **5-Zone Docking Previews**: Drag panels on the top, bottom, left, or right edges of another pane to split it, or onto the center to swap their positions.
|
|
21
|
+
- **Native Flexbox Resizers**: Fluid, non-blocking split handles built on pointer events.
|
|
22
|
+
- **Accessible Drag-and-Drop**: Built on top of the performant and accessible [`@dnd-kit`](https://dndkit.com) toolkit.
|
|
23
|
+
- **Fullscreen Zoom Toggle**: Programmatically expand any pane to cover the entire viewport and snap it back instantly.
|
|
32
24
|
|
|
33
25
|
---
|
|
34
26
|
|
|
@@ -40,7 +32,8 @@ Install the package into your React project using your preferred package manager
|
|
|
40
32
|
npm install react-zeugma
|
|
41
33
|
```
|
|
42
34
|
|
|
43
|
-
>
|
|
35
|
+
> [!NOTE]
|
|
36
|
+
> **Peer Dependencies** — react-zeugma is compatible with both **React 18** and **React 19** (along with matching `react-dom`).
|
|
44
37
|
|
|
45
38
|
---
|
|
46
39
|
|
|
@@ -51,7 +44,6 @@ Import the core components and configure the layout state inside your React appl
|
|
|
51
44
|
```tsx
|
|
52
45
|
import { useState } from 'react'
|
|
53
46
|
import { DashboardProvider, PaneTree, Pane, DragHandle, TreeNode } from 'react-zeugma'
|
|
54
|
-
import './Dashboard.css' // Import your custom styles here
|
|
55
47
|
|
|
56
48
|
const initialLayout: TreeNode = {
|
|
57
49
|
type: 'split',
|
|
@@ -71,16 +63,16 @@ function MyPane({ id }: { id: string }) {
|
|
|
71
63
|
return (
|
|
72
64
|
<Pane id={id}>
|
|
73
65
|
{({ isDragging, remove }) => (
|
|
74
|
-
<div className={`
|
|
66
|
+
<div className={`h-full flex flex-col bg-[#18181b] ${isDragging ? 'opacity-30' : ''}`}>
|
|
75
67
|
<DragHandle>
|
|
76
|
-
<div className="
|
|
77
|
-
<span className="
|
|
78
|
-
<button onClick={remove} className="
|
|
68
|
+
<div className="px-3 py-2 bg-[#27272a] border-b border-[#3f3f46] flex items-center justify-between cursor-grab">
|
|
69
|
+
<span className="text-xs uppercase text-zinc-300 font-bold">{id}</span>
|
|
70
|
+
<button onClick={remove} className="text-zinc-500 hover:text-rose-400 text-xs">
|
|
79
71
|
×
|
|
80
72
|
</button>
|
|
81
73
|
</div>
|
|
82
74
|
</DragHandle>
|
|
83
|
-
<div className="
|
|
75
|
+
<div className="flex-1 p-4 text-sm text-zinc-400">Content for {id}</div>
|
|
84
76
|
</div>
|
|
85
77
|
)}
|
|
86
78
|
</Pane>
|
|
@@ -92,7 +84,7 @@ export default function Dashboard() {
|
|
|
92
84
|
|
|
93
85
|
return (
|
|
94
86
|
<DashboardProvider layout={layout} onChange={setLayout} renderPane={(id) => <MyPane id={id} />}>
|
|
95
|
-
<div className="
|
|
87
|
+
<div className="w-screen h-screen">
|
|
96
88
|
<PaneTree />
|
|
97
89
|
</div>
|
|
98
90
|
</DashboardProvider>
|
|
@@ -108,17 +100,25 @@ export default function Dashboard() {
|
|
|
108
100
|
|
|
109
101
|
The context provider that sets up the drag-and-drop state machine, monitors active drags, and registers layout change notifications.
|
|
110
102
|
|
|
111
|
-
| Prop | Type
|
|
112
|
-
| ------------------------ |
|
|
113
|
-
| `layout` | `TreeNode \| null`
|
|
114
|
-
| `onChange` | `(layout: TreeNode \| null) => void`
|
|
115
|
-
| `renderPane` | `(paneId: string) => ReactNode`
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
118
|
-
| `
|
|
119
|
-
| `onFullscreenChange` | `(paneId: string \| null) => void`
|
|
120
|
-
| `onRemove` | `(paneId: string) => void`
|
|
121
|
-
| `dragActivationDistance` | `number`
|
|
103
|
+
| Prop | Type | Required | Description |
|
|
104
|
+
| ------------------------ | --------------------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
105
|
+
| `layout` | `TreeNode \| null` | Yes | The serializable tree layout schema. |
|
|
106
|
+
| `onChange` | `(layout: TreeNode \| null) => void` | Yes | Fires when resizes, splits, swaps, or removes modify the tree. |
|
|
107
|
+
| `renderPane` | `(paneId: string) => ReactNode` | Yes | Renderer function lookup that returns a `<Pane>` structure. |
|
|
108
|
+
| `classNames` | `ZeugmaClassNames` | No | Custom classes for overriding pane, resizer, and drop preview overlays. |
|
|
109
|
+
| `fullscreenPaneId` | `string \| null` | No | Active ID of the pane taking full viewport coverage. |
|
|
110
|
+
| `renderDragOverlay` | `(activeId: string) => ReactNode` | No | Renders a custom cursor-following drag preview overlay. |
|
|
111
|
+
| `onFullscreenChange` | `(paneId: string \| null) => void` | No | Callback triggered when a pane enters or leaves fullscreen. |
|
|
112
|
+
| `onRemove` | `(paneId: string) => void` | No | Callback triggered when a pane is closed/removed from the layout tree. |
|
|
113
|
+
| `dragActivationDistance` | `number` | No | Minimum pointer drag distance (in pixels) required to activate dragging. Defaults to `8`. |
|
|
114
|
+
| `onDragStart` | `(activeId: string) => void` | No | Callback triggered when dragging starts on a pane. |
|
|
115
|
+
| `onDragEnd` | `(activeId: string, overId: string \| null, dropAction: any) => void` | No | Callback triggered when dragging ends, providing swap or split details. The `overId` is set to `'root'` if dropped onto outer boundaries to split the entire dashboard root. |
|
|
116
|
+
| `onResizeStart` | `(currentNode: SplitNode) => void` | No | Callback triggered when resizing starts on a split node. |
|
|
117
|
+
| `onResize` | `(currentNode: SplitNode, percentage: number) => void` | No | Callback triggered continuously while resizing a split node. |
|
|
118
|
+
| `onResizeEnd` | `(currentNode: SplitNode, percentage: number) => void` | No | Callback triggered when resizing ends on a split node. |
|
|
119
|
+
| `renderResizer` | `(props: ResizerRenderProps) => ReactNode` | No | Custom renderer function for rendering custom-styled resizer bars. |
|
|
120
|
+
| `minSplitPercentage` | `number` | No | Minimum resizing limit percentage. Defaults to `5`. |
|
|
121
|
+
| `maxSplitPercentage` | `number` | No | Maximum resizing limit percentage. Defaults to `95`. |
|
|
122
122
|
|
|
123
123
|
### `<PaneTree>`
|
|
124
124
|
|
|
@@ -153,7 +153,7 @@ Defines the interactive drag region inside a `<Pane>`. **Must be placed inside a
|
|
|
153
153
|
|
|
154
154
|
| Prop | Type | Required | Description |
|
|
155
155
|
| ----------- | --------------------- | -------- | ---------------------------------------------------------------- |
|
|
156
|
-
| `children` | `
|
|
156
|
+
| `children` | `ReactNode` | Yes | Element(s) that function as the drag handle (e.g., pane header). |
|
|
157
157
|
| `className` | `string` | No | Custom CSS class for the drag handle wrapper. |
|
|
158
158
|
| `style` | `React.CSSProperties` | No | Inline styles for the drag handle wrapper. |
|
|
159
159
|
|
|
@@ -179,7 +179,7 @@ Swaps the positions of `idA` and `idB` nodes directly inside the tree structure.
|
|
|
179
179
|
|
|
180
180
|
Splits the targeted `targetId` pane inside the tree with `direction` (_row_ / _column_) and type (_left_, _right_, _top_, _bottom_) to insert `paneToAdd`.
|
|
181
181
|
|
|
182
|
-
#### `splitRoot(tree
|
|
182
|
+
#### `splitRoot(tree, draggingId, splitType)`
|
|
183
183
|
|
|
184
184
|
Splits the entire dashboard tree at the root, placing the dragged `draggingId` pane on one half and the rest of the layout tree on the other.
|
|
185
185
|
|
|
@@ -196,11 +196,12 @@ Use custom CSS or styling rules to style resizers, dragging states, drop preview
|
|
|
196
196
|
renderPane={renderPane}
|
|
197
197
|
classNames={{
|
|
198
198
|
// resizer handles
|
|
199
|
-
resizer:
|
|
199
|
+
resizer:
|
|
200
|
+
'bg-transparent hover:bg-indigo-500/50 active:bg-indigo-500 transition-colors duration-150',
|
|
200
201
|
// split previews
|
|
201
|
-
dropPreview: '
|
|
202
|
+
dropPreview: 'bg-indigo-500/10 border-2 border-dashed border-indigo-500/50 backdrop-blur-xs',
|
|
202
203
|
// swap previews
|
|
203
|
-
swapPreview: '
|
|
204
|
+
swapPreview: 'bg-amber-500/10 border-2 border-dashed border-amber-500/50 backdrop-blur-xs',
|
|
204
205
|
}}
|
|
205
206
|
>
|
|
206
207
|
<PaneTree />
|
|
@@ -245,45 +246,284 @@ export interface PaneRenderProps {
|
|
|
245
246
|
toggleFullscreen: () => void
|
|
246
247
|
remove: () => void
|
|
247
248
|
}
|
|
249
|
+
|
|
250
|
+
export interface ResizerRenderProps {
|
|
251
|
+
direction: SplitDirection
|
|
252
|
+
splitPercentage: number
|
|
253
|
+
resizerSize: number
|
|
254
|
+
isResizing: boolean
|
|
255
|
+
onPointerDown: (e: React.PointerEvent<HTMLDivElement>) => void
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export interface DashboardContextValue {
|
|
259
|
+
layout: TreeNode | null
|
|
260
|
+
onLayoutChange: (newLayout: TreeNode | null) => void
|
|
261
|
+
renderPane: (paneId: string) => ReactNode
|
|
262
|
+
activeId: string | null
|
|
263
|
+
fullscreenPaneId: string | null
|
|
264
|
+
classNames: ZeugmaClassNames
|
|
265
|
+
onRemove?: (paneId: string) => void
|
|
266
|
+
onFullscreenChange?: (paneId: string | null) => void
|
|
267
|
+
snapThreshold?: number
|
|
268
|
+
onResizeStart?: (currentNode: SplitNode) => void
|
|
269
|
+
onResize?: (currentNode: SplitNode, percentage: number) => void
|
|
270
|
+
onResizeEnd?: (currentNode: SplitNode, percentage: number) => void
|
|
271
|
+
renderResizer?: (props: ResizerRenderProps) => ReactNode
|
|
272
|
+
minSplitPercentage?: number
|
|
273
|
+
maxSplitPercentage?: number
|
|
274
|
+
removePane: (paneId: string) => void
|
|
275
|
+
addPane: (paneId: string) => void
|
|
276
|
+
swapPanes: (paneIdA: string, paneIdB: string) => void
|
|
277
|
+
splitPane: (
|
|
278
|
+
targetId: string,
|
|
279
|
+
direction: SplitDirection,
|
|
280
|
+
splitType: 'left' | 'right' | 'top' | 'bottom',
|
|
281
|
+
paneToAdd: string,
|
|
282
|
+
) => void
|
|
283
|
+
updateSplitPercentage: (currentNode: SplitNode, percentage: number) => void
|
|
284
|
+
}
|
|
248
285
|
```
|
|
249
286
|
|
|
250
287
|
---
|
|
251
288
|
|
|
252
289
|
## SKILL.md
|
|
253
290
|
|
|
254
|
-
|
|
291
|
+
Below is the comprehensive developer skill configuration for integrations, tree manipulation, and styling patterns within `react-zeugma`. Copy or download it for AI agents or reference.
|
|
255
292
|
|
|
293
|
+
````markdown
|
|
294
|
+
---
|
|
295
|
+
name: use-react-zeugma
|
|
296
|
+
description: Integrate, configure, style, and programmatically manipulate dashboard layouts using the react-zeugma package.
|
|
256
297
|
---
|
|
257
298
|
|
|
258
|
-
|
|
299
|
+
# Skill: Using react-zeugma
|
|
259
300
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
cd react-zeugma
|
|
264
|
-
npm install
|
|
301
|
+
`react-zeugma` is a recursive drag-and-drop dashboard layout engine for React. It combines tree-based pane splitting (similar to `react-mosaic`) with a declarative, state-driven API (similar to `react-grid-layout`), built using `@dnd-kit/core`.
|
|
302
|
+
|
|
303
|
+
---
|
|
265
304
|
|
|
266
|
-
|
|
267
|
-
npm run demo
|
|
305
|
+
## 1. Data Model (Tree Nodes)
|
|
268
306
|
|
|
269
|
-
|
|
270
|
-
|
|
307
|
+
The entire dashboard layout is represented as a serializable recursive tree structure.
|
|
308
|
+
|
|
309
|
+
### Types & Interface
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
export type SplitDirection = 'row' | 'column'
|
|
313
|
+
|
|
314
|
+
export interface SplitNode {
|
|
315
|
+
type: 'split'
|
|
316
|
+
direction: SplitDirection
|
|
317
|
+
first: TreeNode
|
|
318
|
+
second: TreeNode
|
|
319
|
+
splitPercentage: number // 0 to 100
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export interface PaneNode {
|
|
323
|
+
type: 'pane'
|
|
324
|
+
paneId: string
|
|
325
|
+
}
|
|
271
326
|
|
|
272
|
-
|
|
273
|
-
npm run build
|
|
327
|
+
export type TreeNode = SplitNode | PaneNode
|
|
274
328
|
```
|
|
275
329
|
|
|
330
|
+
- **`PaneNode` (Leaf):** Represents a single content pane. It must have a unique `paneId`.
|
|
331
|
+
- **`SplitNode` (Branch):** Splits its area horizontally (`column`) or vertically (`row`) into two child `TreeNode` nodes (`first` and `second`), based on `splitPercentage`.
|
|
332
|
+
|
|
276
333
|
---
|
|
277
334
|
|
|
278
|
-
##
|
|
335
|
+
## 2. Core Components
|
|
336
|
+
|
|
337
|
+
### `<DashboardProvider>`
|
|
338
|
+
|
|
339
|
+
The root context provider. It handles the drag-and-drop event loop and coordinates the layout state.
|
|
340
|
+
|
|
341
|
+
#### Props
|
|
342
|
+
|
|
343
|
+
- `layout: TreeNode | null` — The current dashboard layout tree.
|
|
344
|
+
- `onChange: (newLayout: TreeNode | null) => void` — Callback triggered when the layout tree changes (resizing, dragging to split, dragging to swap).
|
|
345
|
+
- `renderPane: (paneId: string) => ReactNode` — Callback to render the contents of a pane given its ID.
|
|
346
|
+
- `renderDragOverlay?: (activeId: string) => ReactNode` — (Optional) Renders a custom cursor-following drag preview.
|
|
347
|
+
- `classNames?: ZeugmaClassNames` — (Optional) CSS class overrides for styling various layout elements.
|
|
348
|
+
- `fullscreenPaneId?: string | null` — (Optional) ID of the pane currently in fullscreen mode.
|
|
349
|
+
- `onFullscreenChange?: (paneId: string | null) => void` — (Optional) Callback triggered when a pane enters/leaves fullscreen.
|
|
350
|
+
- `onRemove?: (paneId: string) => void` — (Optional) Callback triggered when a pane is closed/removed.
|
|
351
|
+
- `dragActivationDistance?: number` — (Optional) Minimum pointer drag distance (in pixels) required to activate dragging. Defaults to `8`.
|
|
352
|
+
- `onDragStart?: (activeId: string) => void` — (Optional) Callback triggered when dragging starts on a pane.
|
|
353
|
+
- `onDragEnd?: (activeId: string, overId: string | null, dropAction: any) => void` — (Optional) Callback triggered when dragging ends. The `overId` will be `'root'` if the pane was dropped onto the outer dashboard boundaries to split the root layout.
|
|
354
|
+
- `onResizeStart?: (currentNode: SplitNode) => void` — (Optional) Callback triggered when resizing starts.
|
|
355
|
+
- `onResize?: (currentNode: SplitNode, percentage: number) => void` — (Optional) Callback triggered during resizing.
|
|
356
|
+
- `onResizeEnd?: (currentNode: SplitNode, percentage: number) => void` — (Optional) Callback triggered when resizing ends.
|
|
357
|
+
- `renderResizer?: (props: ResizerRenderProps) => ReactNode` — (Optional) Custom resizer bar component renderer.
|
|
358
|
+
- `minSplitPercentage?: number` — (Optional) Minimum resizing limit percentage (defaults to `5`).
|
|
359
|
+
- `maxSplitPercentage?: number` — (Optional) Maximum resizing limit percentage (defaults to `95`).
|
|
360
|
+
|
|
361
|
+
### `<PaneTree>`
|
|
362
|
+
|
|
363
|
+
Recursively renders the split nodes and pane nodes. Must be placed inside `<DashboardProvider>`.
|
|
364
|
+
|
|
365
|
+
#### Props
|
|
366
|
+
|
|
367
|
+
- `tree?: TreeNode | null` — (Optional) Custom subtree to render. Defaults to the provider's root `layout`.
|
|
368
|
+
- `resizerSize?: number` — (Optional) Thickness of the split resizer bars in pixels. Defaults to `4`.
|
|
369
|
+
|
|
370
|
+
### `<Pane>`
|
|
371
|
+
|
|
372
|
+
Wraps the contents of an individual pane. It sets up draggable and droppable zones.
|
|
279
373
|
|
|
280
|
-
|
|
374
|
+
#### Props
|
|
375
|
+
|
|
376
|
+
- `id: string` — The unique ID corresponding to a `PaneNode`'s `paneId`.
|
|
377
|
+
- `children: (props: PaneRenderProps) => ReactNode` — Render prop function.
|
|
378
|
+
|
|
379
|
+
#### `PaneRenderProps`
|
|
380
|
+
|
|
381
|
+
```ts
|
|
382
|
+
interface PaneRenderProps {
|
|
383
|
+
isDragging: boolean
|
|
384
|
+
isFullscreen: boolean
|
|
385
|
+
toggleFullscreen: () => void
|
|
386
|
+
remove: () => void
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### `<DragHandle>`
|
|
391
|
+
|
|
392
|
+
Defines the interactive drag region inside a `<Pane>`. **Must be placed inside a `<Pane>` component.**
|
|
393
|
+
|
|
394
|
+
#### Props
|
|
395
|
+
|
|
396
|
+
- `children: React.ReactNode` — Element(s) that function as the drag handle (e.g., pane header).
|
|
397
|
+
- `className?: string`
|
|
398
|
+
- `style?: React.CSSProperties`
|
|
281
399
|
|
|
282
400
|
---
|
|
283
401
|
|
|
284
|
-
##
|
|
402
|
+
## 3. Programmatic State Utilities
|
|
403
|
+
|
|
404
|
+
Import these helpers from `react-zeugma` to manipulate the tree layout programmatically in your state handlers:
|
|
405
|
+
|
|
406
|
+
- **`removePane(tree: TreeNode | null, idToRemove: string): TreeNode | null`**
|
|
407
|
+
Removes a pane from the tree and collapses the leftover sibling split node.
|
|
408
|
+
- **`splitPane(tree: TreeNode | null, targetId: string, direction: SplitDirection, splitType: 'left' | 'right' | 'top' | 'bottom', paneToAdd: string): TreeNode | null`**
|
|
409
|
+
Splits a specific target pane by nesting it under a new `SplitNode` along with a new pane.
|
|
410
|
+
- **`splitRoot(tree: TreeNode | null, draggingId: string, splitType: 'left' | 'right' | 'top' | 'bottom'): TreeNode | null`**
|
|
411
|
+
Splits the entire dashboard tree at the root, placing the dragged pane on one half and the remaining layout tree on the other.
|
|
412
|
+
- **`swapPanes(tree: TreeNode | null, idA: string, idB: string): TreeNode | null`**
|
|
413
|
+
Swaps the positions of two panes in the tree.
|
|
285
414
|
|
|
286
|
-
|
|
415
|
+
Alternatively, you can consume the convenient mutation helpers directly from the **`useDashboard()`** context hook inside pane components without importing utilities:
|
|
416
|
+
|
|
417
|
+
- **`removePane(paneId: string) => void`**
|
|
418
|
+
- **`addPane(paneId: string) => void`**
|
|
419
|
+
- **`swapPanes(paneIdA: string, paneIdB: string) => void`**
|
|
420
|
+
- **`splitPane(targetId: string, direction: SplitDirection, splitType: string, paneToAdd: string) => void`**
|
|
421
|
+
- **`updateSplitPercentage(currentNode: SplitNode, percentage: number) => void`**
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## 4. Basic Integration Recipe
|
|
426
|
+
|
|
427
|
+
```tsx
|
|
428
|
+
import { useState } from 'react'
|
|
429
|
+
import { DashboardProvider, PaneTree, Pane, DragHandle, TreeNode } from 'react-zeugma'
|
|
430
|
+
|
|
431
|
+
const initialLayout: TreeNode = {
|
|
432
|
+
type: 'split',
|
|
433
|
+
direction: 'row',
|
|
434
|
+
splitPercentage: 50,
|
|
435
|
+
first: { type: 'pane', paneId: 'sidebar' },
|
|
436
|
+
second: { type: 'pane', paneId: 'main' },
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function CustomPane({ id }: { id: string }) {
|
|
440
|
+
return (
|
|
441
|
+
<Pane id={id}>
|
|
442
|
+
{({ isDragging, isFullscreen, toggleFullscreen, remove }) => (
|
|
443
|
+
<div style={{ height: '100%', border: '1px solid #ccc', opacity: isDragging ? 0.5 : 1 }}>
|
|
444
|
+
<div style={{ display: 'flex', background: '#eee', padding: 8 }}>
|
|
445
|
+
<DragHandle style={{ flex: 1 }}>
|
|
446
|
+
<strong>Header: {id}</strong>
|
|
447
|
+
</DragHandle>
|
|
448
|
+
<button onClick={toggleFullscreen}>
|
|
449
|
+
{isFullscreen ? 'Exit Fullscreen' : 'Fullscreen'}
|
|
450
|
+
</button>
|
|
451
|
+
<button onClick={remove}>Close</button>
|
|
452
|
+
</div>
|
|
453
|
+
<div style={{ padding: 16 }}>Content for {id}</div>
|
|
454
|
+
</div>
|
|
455
|
+
)}
|
|
456
|
+
</Pane>
|
|
457
|
+
)
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
export default function App() {
|
|
461
|
+
const [layout, setLayout] = useState<TreeNode | null>(initialLayout)
|
|
462
|
+
const [fullscreenId, setFullscreenId] = useState<string | null>(null)
|
|
463
|
+
|
|
464
|
+
const handleRemove = (paneId: string) => {
|
|
465
|
+
// Remove the pane and update layout
|
|
466
|
+
setLayout((prev) => removePane(prev, paneId))
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
return (
|
|
470
|
+
<DashboardProvider
|
|
471
|
+
layout={layout}
|
|
472
|
+
onChange={setLayout}
|
|
473
|
+
renderPane={(id) => <CustomPane id={id} />}
|
|
474
|
+
fullscreenPaneId={fullscreenId}
|
|
475
|
+
onFullscreenChange={setFullscreenId}
|
|
476
|
+
onRemove={handleRemove}
|
|
477
|
+
>
|
|
478
|
+
<div style={{ width: '100vw', height: '100vh' }}>
|
|
479
|
+
<PaneTree />
|
|
480
|
+
</div>
|
|
481
|
+
</DashboardProvider>
|
|
482
|
+
)
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## 5. Styling Customization
|
|
489
|
+
|
|
490
|
+
`react-zeugma` is style-agnostic and relies on class name configuration for visual states. Define classes in your styling framework and pass them via the `classNames` prop on `<DashboardProvider>`:
|
|
491
|
+
|
|
492
|
+
```ts
|
|
493
|
+
interface ZeugmaClassNames {
|
|
494
|
+
pane?: string // Applied to the outer wrapper of <Pane>
|
|
495
|
+
dropPreview?: string // Applied to the preview box when hovering over edge dropzones
|
|
496
|
+
swapPreview?: string // Applied to the preview box when hovering over center dropzone
|
|
497
|
+
dragOverlay?: string // Applied to the cursor-following drag preview portal
|
|
498
|
+
resizer?: string // Applied to the drag-to-resize split bar
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### CSS Example:
|
|
503
|
+
|
|
504
|
+
```css
|
|
505
|
+
/* Custom resizer style */
|
|
506
|
+
.my-resizer {
|
|
507
|
+
background-color: #e2e8f0;
|
|
508
|
+
transition: background-color 0.2s;
|
|
509
|
+
}
|
|
510
|
+
.my-resizer:hover {
|
|
511
|
+
background-color: #3b82f6;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/* Edge drop previews */
|
|
515
|
+
.my-drop-preview {
|
|
516
|
+
background-color: rgba(59, 130, 246, 0.2);
|
|
517
|
+
border: 2px dashed #3b82f6;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/* Center swap preview */
|
|
521
|
+
.my-swap-preview {
|
|
522
|
+
background-color: rgba(16, 185, 129, 0.25);
|
|
523
|
+
border: 2px solid #10b981;
|
|
524
|
+
}
|
|
525
|
+
```
|
|
526
|
+
````
|
|
287
527
|
|
|
288
528
|
---
|
|
289
529
|
|
|
@@ -294,3 +534,11 @@ _Zeugma_ is an ancient city of Commagene, located in modern-day **Gaziantep, Tur
|
|
|
294
534
|
During modern excavation efforts, archeologists discovered some of the most breathtaking Greco-Roman mosaic panels in history, now housed inside the **Zeugma Mosaic Museum** in Gaziantep. The famous _"Gypsy Girl" (Çingene Kızı)_ mosaic, with her hauntingly detailed eyes, has become a global icon of the city.
|
|
295
535
|
|
|
296
536
|
> _"We chose the name Zeugma because of this ancient craftsmanship. Mosaics are assembled from hundreds of tiny, individual tesserae tiles to form a magnificent, cohesive picture. In the same spirit, react-zeugma lets you build beautiful, customized application workspaces from simple, individual components. Many tiles, one masterpiece."_
|
|
537
|
+
|
|
538
|
+
---
|
|
539
|
+
|
|
540
|
+
## Links
|
|
541
|
+
|
|
542
|
+
- [GitHub Repository](https://github.com/yusufarsln98/react-zeugma)
|
|
543
|
+
- [npm Package](https://www.npmjs.com/package/react-zeugma)
|
|
544
|
+
- [Contributing Guide](https://github.com/yusufarsln98/react-zeugma/blob/master/CONTRIBUTING.md)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-zeugma",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Recursive drag-and-drop dashboard layout engine for React — combining the tree-based splitting of react-mosaic with the declarative API of react-grid-layout.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|