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