react-zeugma 6.2.2 → 6.4.0
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 +133 -484
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +82 -47
- package/dist/index.d.ts +82 -47
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/{types-Yvj0Isvy.d.cts → types-B9LrYapR.d.cts} +113 -128
- package/dist/{types-Yvj0Isvy.d.ts → types-B9LrYapR.d.ts} +113 -128
- package/dist/utils.cjs +1 -1
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.cts +24 -9
- package/dist/utils.d.ts +24 -9
- package/dist/utils.js +1 -1
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ npm install react-zeugma
|
|
|
40
40
|
Import the core components and configure the layout state inside your React application using the `useZeugma` hook.
|
|
41
41
|
|
|
42
42
|
```tsx
|
|
43
|
-
import { useZeugma, Zeugma, PaneTree, Pane,
|
|
43
|
+
import { useZeugma, Zeugma, PaneTree, Pane, TreeNode } from 'react-zeugma'
|
|
44
44
|
|
|
45
45
|
const initialLayout: TreeNode = {
|
|
46
46
|
type: 'split',
|
|
@@ -59,32 +59,27 @@ const initialLayout: TreeNode = {
|
|
|
59
59
|
function MyPane({ id }: { id: string }) {
|
|
60
60
|
return (
|
|
61
61
|
<Pane id={id}>
|
|
62
|
-
|
|
63
|
-
<
|
|
64
|
-
<
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
<div className="flex-1 p-4 text-sm text-zinc-400">Content for {id}</div>
|
|
73
|
-
</div>
|
|
74
|
-
)}
|
|
62
|
+
<div className="h-full flex flex-col bg-[#18181b]">
|
|
63
|
+
<Pane.DragHandle>
|
|
64
|
+
<div className="px-3 py-2 bg-[#27272a] border-b border-[#3f3f46] flex items-center justify-between cursor-grab">
|
|
65
|
+
<span className="text-xs uppercase text-zinc-300 font-bold">{id}</span>
|
|
66
|
+
</div>
|
|
67
|
+
</Pane.DragHandle>
|
|
68
|
+
<Pane.Content className="flex-1 p-4 text-sm text-zinc-400">
|
|
69
|
+
{(tab) => <div>Content for {tab.id}</div>}
|
|
70
|
+
</Pane.Content>
|
|
71
|
+
</div>
|
|
75
72
|
</Pane>
|
|
76
73
|
)
|
|
77
74
|
}
|
|
78
75
|
|
|
79
76
|
export default function Dashboard() {
|
|
80
|
-
const
|
|
77
|
+
const controller = useZeugma({ initialLayout })
|
|
81
78
|
|
|
82
79
|
return (
|
|
83
|
-
<
|
|
84
|
-
<
|
|
85
|
-
|
|
86
|
-
</div>
|
|
87
|
-
</Zeugma>
|
|
80
|
+
<div className="w-screen h-screen">
|
|
81
|
+
<Zeugma controller={controller} renderPane={(id) => <MyPane id={id} />} />
|
|
82
|
+
</div>
|
|
88
83
|
)
|
|
89
84
|
}
|
|
90
85
|
```
|
|
@@ -95,129 +90,119 @@ export default function Dashboard() {
|
|
|
95
90
|
|
|
96
91
|
### `<Zeugma>`
|
|
97
92
|
|
|
98
|
-
The context provider that sets up the drag-and-drop state machine, monitors active drags, and registers layout change notifications. It
|
|
99
|
-
|
|
100
|
-
| Prop
|
|
101
|
-
|
|
|
102
|
-
|
|
|
103
|
-
| `renderPane`
|
|
104
|
-
| `classNames`
|
|
105
|
-
| `renderDragOverlay`
|
|
106
|
-
| `
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
93
|
+
The context provider that sets up the drag-and-drop state machine, monitors active drags, and registers layout change notifications. It accepts a `controller` prop explicitly and takes all configuration settings and lifecycle callbacks.
|
|
94
|
+
|
|
95
|
+
| Prop | Type | Required | Description |
|
|
96
|
+
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------- |
|
|
97
|
+
| `controller` | `ZeugmaController` | Yes | The Zeugma controller object returned by `useZeugma(options)`. |
|
|
98
|
+
| `renderPane` | `(paneId: string) => ReactNode` | Yes | Renderer function lookup that returns a `<Pane>` structure. |
|
|
99
|
+
| `classNames` | `ZeugmaClassNames` | No | Custom classes for overriding pane, resizer, and drop preview overlays. |
|
|
100
|
+
| `renderDragOverlay` | `(active: DragOverlayActiveItem) => ReactNode` | No | Renders a custom cursor-following drag preview overlay. |
|
|
101
|
+
| `resizerSize` | `number` | No | Thickness of the split resizer bars in pixels. Defaults to `4`. |
|
|
102
|
+
| `dragActivationDistance` | `number` | No | Minimum pointer drag distance (in pixels) required to activate dragging (defaults to `8`). |
|
|
103
|
+
| `snapThreshold` | `number` | No | Threshold in pixels to snap layout resizers to adjacent edges (defaults to `8`). |
|
|
104
|
+
| `minSplitPercentage` | `number` | No | Minimum resizing limit percentage (defaults to `5`). |
|
|
105
|
+
| `maxSplitPercentage` | `number` | No | Maximum resizing limit percentage (defaults to `95`). |
|
|
106
|
+
| `enableDragToDismiss` | `boolean` | No | If true, enables the drag-out-to-dismiss gesture to close widgets (defaults to `false`). |
|
|
107
|
+
| `dismissThreshold` | `number` | No | Distance in pixels outside container bounds required to trigger dismissal (defaults to `60`). |
|
|
108
|
+
| `onRemove` | `(paneId: string) => void` | No | Callback triggered when a pane is removed. |
|
|
109
|
+
| `onDragStart` | `(activeId: string) => void` | No | Callback triggered when dragging starts. |
|
|
110
|
+
| `onDragEnd` | `(activeId: string, overId: string \| null, dropAction: { type: 'split' \| 'move'; direction?: SplitDirection; position?: string } \| null) => void` | No | Callback triggered when dragging ends. |
|
|
111
|
+
| `onResizeStart` | `(currentNode: SplitNode) => void` | No | Callback triggered when resizing starts. |
|
|
112
|
+
| `onResize` | `(currentNode: SplitNode, percentage: number) => void` | No | Callback triggered during resizing. |
|
|
113
|
+
| `onResizeEnd` | `(currentNode: SplitNode, percentage: number) => void` | No | Callback triggered when resizing ends. |
|
|
114
|
+
| `onDismissIntentChange` | `(paneId: string \| null) => void` | No | Callback triggered when drag-out intent changes. |
|
|
111
115
|
|
|
112
116
|
### `useZeugma(options)`
|
|
113
117
|
|
|
114
|
-
A custom state hook that initializes and manages the
|
|
115
|
-
|
|
116
|
-
| Option
|
|
117
|
-
|
|
|
118
|
-
| `initialLayout`
|
|
119
|
-
| `layout`
|
|
120
|
-
| `
|
|
121
|
-
| `
|
|
122
|
-
| `
|
|
123
|
-
| `
|
|
124
|
-
| `snapThreshold` | `number` | `8` | Threshold in pixels to snap layout resizers to adjacent edges. |
|
|
125
|
-
| `minSplitPercentage` | `number` | `5` | Minimum resizing limit percentage. |
|
|
126
|
-
| `maxSplitPercentage` | `number` | `95` | Maximum resizing limit percentage. |
|
|
127
|
-
| `enableDragToDismiss` | `boolean` | `false` | If true, enables the drag-out-to-dismiss gesture to close widgets. |
|
|
128
|
-
| `dismissThreshold` | `number` | `60` | Distance in pixels outside container bounds required to trigger dismissal. |
|
|
129
|
-
| `onRemove` | `(paneId: string) => void` | — | Callback triggered when a pane is removed. |
|
|
130
|
-
| `onDragStart` | `(activeId: string) => void` | — | Callback triggered when dragging starts. |
|
|
131
|
-
| `onDragEnd` | `(activeId: string, overId: string \| null, dropAction: { type: 'split' \| 'move'; direction?: SplitDirection; position?: string } \| null) => void` | — | Callback triggered when dragging ends, with drop target and action details. |
|
|
132
|
-
| `onResizeStart` | `(currentNode: SplitNode) => void` | — | Callback triggered when resizing starts. |
|
|
133
|
-
| `onResize` | `(currentNode: SplitNode, percentage: number) => void` | — | Callback triggered during resizing. |
|
|
134
|
-
| `onResizeEnd` | `(currentNode: SplitNode, percentage: number) => void` | — | Callback triggered when resizing ends. |
|
|
135
|
-
| `onDismissIntentChange` | `(paneId: string \| null) => void` | — | Callback triggered when drag-out intent changes. |
|
|
118
|
+
A custom state hook that initializes and manages the layout tree, locked state, and fullscreen mode.
|
|
119
|
+
|
|
120
|
+
| Option | Type | Default | Description |
|
|
121
|
+
| -------------------- | --------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------- |
|
|
122
|
+
| `initialLayout` | `TreeNode \| null` | — | Initial layout tree structure for uncontrolled mode. Only used on initial mount. |
|
|
123
|
+
| `layout` | `TreeNode \| null` | — | Controlled layout tree structure. If provided, the hook runs in controlled mode and synchronizes with it. |
|
|
124
|
+
| `onChange` | `(newLayout: TreeNode \| null) => void` | — | Callback triggered when the layout changes. |
|
|
125
|
+
| `fullscreenPaneId` | `string \| null` | — | Controlled fullscreen pane ID. Pass `null` for no fullscreen pane. |
|
|
126
|
+
| `onFullscreenChange` | `(paneId: string \| null) => void` | — | Callback triggered when a pane is toggled to/from fullscreen mode. |
|
|
127
|
+
| `locked` | `boolean` | `false` | If true, layout resizes and drags are disabled. |
|
|
136
128
|
|
|
137
129
|
### `useZeugmaContext()`
|
|
138
130
|
|
|
139
131
|
A custom React context hook that returns the unified layout controller properties and state actions. Must be used within a `<Zeugma>` provider component.
|
|
140
132
|
|
|
141
|
-
Provides direct access to the current layout state (e.g., `layout`, `
|
|
133
|
+
Provides direct access to the current layout state (e.g., `layout`, `locked`), state setters (e.g., `setLocked`), queries (e.g., `findTabById`, `findPaneContainingTab`, `findPaneById`), and mutation actions (e.g., `addTab`, `removePane`, `selectTab`, etc.).
|
|
142
134
|
|
|
143
135
|
```ts
|
|
144
|
-
const { layout,
|
|
136
|
+
const { layout, locked, findTabById, setLocked, removePane } = useZeugmaContext()
|
|
145
137
|
```
|
|
146
138
|
|
|
147
139
|
### `<PaneTree>`
|
|
148
140
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
| Prop | Type | Required | Description |
|
|
152
|
-
| ------------- | ------------------ | -------- | ------------------------------------------------------------------- |
|
|
153
|
-
| `tree` | `TreeNode \| null` | No | Custom subtree to render. Defaults to the provider's root `layout`. |
|
|
154
|
-
| `resizerSize` | `number` | No | Thickness of the split resizer bars in pixels. Defaults to `4`. |
|
|
141
|
+
An internal component that recursively renders the split nodes and pane nodes. It is automatically managed and rendered internally by `<Zeugma>`, so consumers do not need to import or render it manually. Configuration options like `resizerSize` and `snapThreshold` are passed directly as props to `<Zeugma>` instead.
|
|
155
142
|
|
|
156
143
|
### `<Pane id>`
|
|
157
144
|
|
|
158
|
-
Wraps the individual pane components inside the renderer.
|
|
159
|
-
|
|
160
|
-
| Prop | Type | Required | Description |
|
|
161
|
-
| ---------- | --------------------------------------- | -------- | ---------------------------------------------------------------------- |
|
|
162
|
-
| `id` | `string` | Yes | The unique ID corresponding to a `PaneNode`'s `paneId`. |
|
|
163
|
-
| `children` | `(props: PaneRenderProps) => ReactNode` | Yes | Render prop function. |
|
|
164
|
-
| `style` | `React.CSSProperties` | No | Optional inline CSS styles applied to the pane outer container. |
|
|
165
|
-
| `locked` | `boolean` | No | Optional override to lock this specific pane (disables drag and drop). |
|
|
166
|
-
|
|
167
|
-
#### Render Props: `PaneRenderProps`
|
|
168
|
-
|
|
169
|
-
| Parameter | Type | Description |
|
|
170
|
-
| ------------------- | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
|
|
171
|
-
| `isDragging` | `boolean` | `true` if the pane is actively being dragged. |
|
|
172
|
-
| `isFullscreen` | `boolean` | `true` if the pane currently occupies the fullscreen view. |
|
|
173
|
-
| `toggleFullscreen` | `() => void` | Toggles the pane to and from fullscreen/zoomed mode. |
|
|
174
|
-
| `remove` | `() => void` | Removes this pane (and its active tab) from the layout tree. |
|
|
175
|
-
| `metadata` | `Record<string, unknown> \| undefined` | The metadata values associated with the active tab. |
|
|
176
|
-
| `updateMetadata` | `(updater: (current: Record<string, unknown> \| undefined) => Record<string, unknown> \| undefined) => void` | Updates metadata of the active tab via an updater function. |
|
|
177
|
-
| `locked` | `boolean` | `true` if this specific pane or the dashboard globally is locked. |
|
|
178
|
-
| `tabs` | `string[]` | The array of tab IDs in this pane. |
|
|
179
|
-
| `activeTabId` | `string` | The currently active tab ID. |
|
|
180
|
-
| `selectTab` | `(tabId: string) => void` | Selects a specific tab to make it active. |
|
|
181
|
-
| `removeTab` | `(tabId: string) => void` | Removes/closes a specific tab. |
|
|
182
|
-
| `tabsMetadata` | `Record<string, Record<string, unknown>> \| undefined` | Metadata values associated with all tabs in this pane. |
|
|
183
|
-
| `updateTabMetadata` | `(tabId: string, updater: (current: Record<string, unknown> \| undefined) => Record<string, unknown> \| undefined) => void` | Updates the metadata of a specific tab. |
|
|
184
|
-
| `renderActiveTab` | `() => ReactNode` | Renders the portal placeholder for the currently active tab in the pane. |
|
|
145
|
+
Wraps the individual pane components inside the renderer. It acts as a context provider and container. Any pane state or handlers should be accessed via `usePaneContext()` or compound sub-components.
|
|
185
146
|
|
|
186
|
-
|
|
147
|
+
| Prop | Type | Required | Description |
|
|
148
|
+
| ---------- | --------------------- | -------- | ----------------------------------------------------------------------- |
|
|
149
|
+
| `id` | `string` | Yes | The unique ID corresponding to a `PaneNode`'s `paneId`. |
|
|
150
|
+
| `children` | `React.ReactNode` | Yes | Children components inside the pane (e.g. tabs, drag handles, content). |
|
|
151
|
+
| `style` | `React.CSSProperties` | No | Optional inline CSS styles applied to the pane outer container. |
|
|
152
|
+
| `locked` | `boolean` | No | Optional override to lock this specific pane (disables drag and drop). |
|
|
187
153
|
|
|
188
|
-
|
|
154
|
+
#### Compound Sub-components
|
|
189
155
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
| `removeTab` | `(id: string) => void` | Yes | Callback when a tab is closed/removed. |
|
|
198
|
-
| `classNames` | `{ container?: string; tab?: string \| ((tabId: string) => string) }` | No | Custom class names for the container and individual tab items. |
|
|
199
|
-
| `styles` | `{ container?: CSSProperties; tab?: CSSProperties \| ((tabId: string) => CSSProperties) }` | No | Custom inline CSS styles for the container and individual tab items. |
|
|
200
|
-
| `renderTab` | `(props: { tabId: string; activeTabId: string; isDragging: boolean; isOver: boolean; metadata?: Record<string, unknown>; selectTab: (id: string) => void; removeTab: (id: string) => void }) => ReactNode` | Yes | Render prop function called for each tab item. |
|
|
156
|
+
- **`<Pane.Content>`**: Renders the portal target for the active tab content.
|
|
157
|
+
- `children` can be a callback function `(tab: TabDetails) => React.ReactNode` or static `React.ReactNode`.
|
|
158
|
+
- Accepts `className` and `style` props.
|
|
159
|
+
- **`<Pane.DragHandle>`**: Defines the interactive drag region.
|
|
160
|
+
- **`<Pane.Tabs>`**: Renders the list of tab items for the pane.
|
|
161
|
+
- **`<Pane.Tab>`**: Renders an individual tab item.
|
|
162
|
+
- **`<Pane.Controls>`**: A headless wrapper for pane control buttons (fullscreen, close, etc.).
|
|
201
163
|
|
|
202
|
-
|
|
164
|
+
#### Hook: `usePaneContext()`
|
|
203
165
|
|
|
204
|
-
|
|
166
|
+
Provides direct access to the pane's state and action handlers from within any child component of `<Pane>`. Returns `PaneContextValue` which extends `PaneRenderProps`:
|
|
205
167
|
|
|
206
168
|
```ts
|
|
207
|
-
const {
|
|
208
|
-
|
|
169
|
+
const { id, isDragging, isFullscreen, toggleFullscreen, remove, tabs, activeTabId } =
|
|
170
|
+
usePaneContext()
|
|
209
171
|
```
|
|
210
172
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
|
214
|
-
|
|
|
215
|
-
| `isDragging`
|
|
216
|
-
| `
|
|
217
|
-
| `
|
|
218
|
-
| `
|
|
219
|
-
| `
|
|
220
|
-
| `
|
|
173
|
+
#### Pane Context Value: `PaneRenderProps`
|
|
174
|
+
|
|
175
|
+
| Parameter | Type | Description |
|
|
176
|
+
| ------------------- | --------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
|
|
177
|
+
| `isDragging` | `boolean` | `true` if the pane is actively being dragged. |
|
|
178
|
+
| `isFullscreen` | `boolean` | `true` if the pane currently occupies the fullscreen view. |
|
|
179
|
+
| `toggleFullscreen` | `() => void` | Toggles the pane to and from fullscreen/zoomed mode. |
|
|
180
|
+
| `remove` | `() => void` | Removes this pane (and its active tab) from the layout tree. |
|
|
181
|
+
| `metadata` | `Record<string, unknown> \| undefined` | The metadata values associated with the active tab. |
|
|
182
|
+
| `updateMetadata` | `(updater: (current: Record<string, unknown> \| undefined) => Record<string, unknown> \| undefined) => void` | Updates metadata of the active tab via an updater function. |
|
|
183
|
+
| `locked` | `boolean` | `true` if this specific pane or the dashboard globally is locked. |
|
|
184
|
+
| `tabs` | `string[]` | The array of tab IDs in this pane. |
|
|
185
|
+
| `activeTabId` | `string` | The currently active tab ID. |
|
|
186
|
+
| `selectTab` | `(tabId: string) => void` | Selects a specific tab to make it active. |
|
|
187
|
+
| `removeTab` | `(tabId: string) => void` | Removes/closes a specific tab. |
|
|
188
|
+
| `tabsMetadata` | `Record<string, Record<string, unknown>> \| undefined` | Metadata values associated with all tabs in this pane. |
|
|
189
|
+
| `updateTabMetadata` | `(tabId: string, updater: (current: Record<string, unknown> \| undefined) => Record<string, unknown> \| undefined) => void` | Updates the metadata of a specific tab. |
|
|
190
|
+
|
|
191
|
+
### `<Tabs>`
|
|
192
|
+
|
|
193
|
+
A helper component that renders a list of tab items for a pane, wrapping the internal drag-and-drop tab logic.
|
|
194
|
+
|
|
195
|
+
| Prop | Type | Required | Description |
|
|
196
|
+
| :------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------- | :------------------------------------------------------------------- |
|
|
197
|
+
| `tabs` | `string[]` | Yes | The list of tab IDs in this pane. |
|
|
198
|
+
| `activeTabId` | `string` | Yes | The currently active tab ID. |
|
|
199
|
+
| `locked` | `boolean` | No | Whether dragging/reordering tabs is disabled (defaults to `false`). |
|
|
200
|
+
| `tabsMetadata` | `Record<string, Record<string, any>>` | No | Metadata associated with each tab. |
|
|
201
|
+
| `selectTab` | `(id: string) => void` | Yes | Callback when a tab is selected. |
|
|
202
|
+
| `removeTab` | `(id: string) => void` | Yes | Callback when a tab is closed/removed. |
|
|
203
|
+
| `classNames` | `{ container?: string; tab?: string \| ((tabId: string) => string) }` | No | Custom class names for the container and individual tab items. |
|
|
204
|
+
| `styles` | `{ container?: CSSProperties; tab?: CSSProperties \| ((tabId: string) => CSSProperties) }` | No | Custom inline CSS styles for the container and individual tab items. |
|
|
205
|
+
| `renderTab` | `(props: { tabId: string; activeTabId: string; isDragging: boolean; isOver: boolean; metadata?: Record<string, unknown>; onSelect: () => void; onRemove: () => void }) => ReactNode` | Yes | Render prop function called for each tab item. |
|
|
221
206
|
|
|
222
207
|
### `<DragHandle>`
|
|
223
208
|
|
|
@@ -382,10 +367,10 @@ export interface TabDetails {
|
|
|
382
367
|
|
|
383
368
|
### Component Props
|
|
384
369
|
|
|
385
|
-
|
|
370
|
+
```ts
|
|
386
371
|
export interface PaneProps {
|
|
387
372
|
id: string
|
|
388
|
-
children:
|
|
373
|
+
children: React.ReactNode
|
|
389
374
|
style?: React.CSSProperties
|
|
390
375
|
locked?: boolean
|
|
391
376
|
}
|
|
@@ -409,8 +394,8 @@ export interface TabsProps {
|
|
|
409
394
|
isDragging: boolean
|
|
410
395
|
isOver: boolean
|
|
411
396
|
metadata?: Record<string, unknown>
|
|
412
|
-
|
|
413
|
-
|
|
397
|
+
onSelect: () => void
|
|
398
|
+
onRemove: () => void
|
|
414
399
|
}) => React.ReactNode
|
|
415
400
|
classNames?: {
|
|
416
401
|
container?: string
|
|
@@ -421,6 +406,7 @@ export interface TabsProps {
|
|
|
421
406
|
tab?: React.CSSProperties | ((tabId: string) => React.CSSProperties)
|
|
422
407
|
}
|
|
423
408
|
}
|
|
409
|
+
```
|
|
424
410
|
|
|
425
411
|
### Render Props
|
|
426
412
|
|
|
@@ -444,71 +430,57 @@ export interface PaneRenderProps {
|
|
|
444
430
|
tabId: string,
|
|
445
431
|
updater: (current: Record<string, unknown> | undefined) => Record<string, unknown> | undefined,
|
|
446
432
|
) => void
|
|
447
|
-
renderActiveTab: () => ReactNode
|
|
448
433
|
}
|
|
449
434
|
|
|
450
|
-
export interface TabContextValue {
|
|
451
|
-
tabId: string
|
|
452
|
-
isActive: boolean
|
|
453
|
-
isDragging: boolean
|
|
454
|
-
isOver: boolean
|
|
455
|
-
metadata?: Record<string, unknown>
|
|
456
|
-
locked: boolean
|
|
457
|
-
selectTab: () => void
|
|
458
|
-
removeTab: () => void
|
|
459
435
|
}
|
|
460
|
-
|
|
436
|
+
```
|
|
461
437
|
|
|
462
438
|
### Controller & Context
|
|
463
439
|
|
|
464
440
|
```ts
|
|
465
|
-
export interface
|
|
466
|
-
|
|
467
|
-
export interface ZeugmaController {
|
|
468
|
-
// State
|
|
441
|
+
export interface ZeugmaState {
|
|
469
442
|
layout: TreeNode | null
|
|
470
|
-
setLayout: Dispatch<SetStateAction<TreeNode | null>>
|
|
471
443
|
fullscreenPaneId: string | null
|
|
472
|
-
setFullscreenPaneId: (paneId: string | null) => void
|
|
473
444
|
locked: boolean
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
// DnD state
|
|
477
|
-
activeId: string | null
|
|
478
|
-
activeType: 'pane' | 'tab' | null
|
|
479
|
-
dismissIntentId: string | null
|
|
445
|
+
}
|
|
480
446
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
enableDragToDismiss: boolean
|
|
487
|
-
dismissThreshold: number
|
|
447
|
+
export interface ZeugmaStateSetters {
|
|
448
|
+
setLayout: Dispatch<SetStateAction<TreeNode | null>>
|
|
449
|
+
setFullscreenPaneId: (paneId: string | null) => void
|
|
450
|
+
setLocked: Dispatch<SetStateAction<boolean>>
|
|
451
|
+
}
|
|
488
452
|
|
|
489
|
-
|
|
453
|
+
export interface ZeugmaActions {
|
|
490
454
|
removePane: (paneId: string) => void
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
455
|
+
addTab: (tabId: string, targetPaneId?: string, metadata?: Record<string, unknown>) => void
|
|
456
|
+
updateMetadata: (
|
|
457
|
+
id: string,
|
|
458
|
+
updater: (current: Record<string, unknown> | undefined) => Record<string, unknown> | undefined,
|
|
459
|
+
) => void
|
|
494
460
|
updatePaneLock: (paneId: string, locked: boolean) => void
|
|
495
461
|
selectTab: (paneId: string, tabId: string) => void
|
|
496
462
|
mergeTab: (draggedTabId: string, targetPaneId: string) => void
|
|
497
463
|
removeTab: (tabId: string) => void
|
|
498
|
-
splitPane: (
|
|
464
|
+
splitPane: (
|
|
465
|
+
targetId: string,
|
|
466
|
+
direction: SplitDirection,
|
|
467
|
+
splitType: 'left' | 'right' | 'top' | 'bottom',
|
|
468
|
+
paneToAdd: string,
|
|
469
|
+
) => void
|
|
499
470
|
updateSplitPercentage: (currentNode: SplitNode, percentage: number) => void
|
|
500
471
|
moveTab: (draggedTabId: string, targetTabId: string, position?: 'before' | 'after') => void
|
|
472
|
+
}
|
|
501
473
|
|
|
502
|
-
|
|
474
|
+
export interface ZeugmaQueries {
|
|
503
475
|
findPaneById: (paneId: string) => PaneNode | null
|
|
504
476
|
findPaneContainingTab: (tabId: string) => PaneNode | null
|
|
505
477
|
findTabById: (tabId: string) => TabDetails | null
|
|
478
|
+
getTabMetadata: (tabId: string) => Record<string, unknown> | undefined
|
|
479
|
+
getActiveTabMetadata: (paneId: string) => Record<string, unknown> | undefined
|
|
506
480
|
}
|
|
507
481
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
// plus the renderPane and classNames configuration.
|
|
511
|
-
export interface ZeugmaContextValue extends ZeugmaStateValue, ZeugmaActionsValue {}
|
|
482
|
+
export interface ZeugmaController
|
|
483
|
+
extends ZeugmaState, ZeugmaStateSetters, ZeugmaActions, ZeugmaQueries {}
|
|
512
484
|
```
|
|
513
485
|
|
|
514
486
|
### Computed Layout Types (from `react-zeugma/utils`)
|
|
@@ -537,326 +509,3 @@ export interface ComputedSplitter {
|
|
|
537
509
|
parentHeight: number
|
|
538
510
|
}
|
|
539
511
|
```
|
|
540
|
-
|
|
541
|
-
---
|
|
542
|
-
|
|
543
|
-
## SKILL.md
|
|
544
|
-
|
|
545
|
-
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.
|
|
546
|
-
|
|
547
|
-
````markdown
|
|
548
|
-
---
|
|
549
|
-
name: react-zeugma
|
|
550
|
-
description: Integrate, configure, style, and programmatically manipulate dashboard layouts using the react-zeugma package.
|
|
551
|
-
---
|
|
552
|
-
|
|
553
|
-
# Skill: Using react-zeugma
|
|
554
|
-
|
|
555
|
-
`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`.
|
|
556
|
-
|
|
557
|
-
---
|
|
558
|
-
|
|
559
|
-
## 1. Data Model (Tree Nodes)
|
|
560
|
-
|
|
561
|
-
The entire dashboard layout is represented as a serializable recursive tree structure.
|
|
562
|
-
|
|
563
|
-
### Types & Interface
|
|
564
|
-
|
|
565
|
-
```ts
|
|
566
|
-
export type SplitDirection = 'row' | 'column'
|
|
567
|
-
|
|
568
|
-
export interface SplitNode {
|
|
569
|
-
type: 'split'
|
|
570
|
-
direction: SplitDirection
|
|
571
|
-
first: TreeNode
|
|
572
|
-
second: TreeNode
|
|
573
|
-
splitPercentage: number // 0 to 100
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
export interface PaneNode {
|
|
577
|
-
type: 'pane'
|
|
578
|
-
id: string
|
|
579
|
-
tabs: string[]
|
|
580
|
-
activeTabId: string
|
|
581
|
-
locked?: boolean
|
|
582
|
-
tabsMetadata?: Record<string, Record<string, unknown>>
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
export type TreeNode = SplitNode | PaneNode
|
|
586
|
-
|
|
587
|
-
export interface TabDetails {
|
|
588
|
-
id: string
|
|
589
|
-
paneId: string
|
|
590
|
-
isActive: boolean
|
|
591
|
-
index: number
|
|
592
|
-
metadata: Record<string, unknown> | undefined
|
|
593
|
-
}
|
|
594
|
-
```
|
|
595
|
-
|
|
596
|
-
- **`PaneNode` (Leaf):** Represents a single content pane. It must have a unique `paneId`.
|
|
597
|
-
- **`SplitNode` (Branch):** Splits its area horizontally (`column`) or vertically (`row`) into two child `TreeNode` nodes (`first` and `second`), based on `splitPercentage`.
|
|
598
|
-
|
|
599
|
-
---
|
|
600
|
-
|
|
601
|
-
## 2. Core Components
|
|
602
|
-
|
|
603
|
-
### `<Zeugma>`
|
|
604
|
-
|
|
605
|
-
The root context provider. It handles the drag-and-drop event loop and coordinates the layout state.
|
|
606
|
-
|
|
607
|
-
#### Props
|
|
608
|
-
|
|
609
|
-
- `...controllerProps: ZeugmaController` — The controller properties returned by the `useZeugma` hook (typically passed via `{...zeugma}`).
|
|
610
|
-
- `renderPane: (paneId: string) => ReactNode` — Callback to render the contents of a pane given its ID.
|
|
611
|
-
- `renderDragOverlay?: (activeId: string, type: 'pane' | 'tab') => ReactNode` — (Optional) Renders a custom cursor-following drag preview.
|
|
612
|
-
- `classNames?: ZeugmaClassNames` — (Optional) CSS class overrides for styling various layout elements.
|
|
613
|
-
- `renderWidget?: (tabId: string) => ReactNode` — (Optional) Render function mapping tab IDs to React elements. Used to render tab widgets inside portals.
|
|
614
|
-
|
|
615
|
-
> [!IMPORTANT]
|
|
616
|
-
> **State & Mount Preservation Rule:**
|
|
617
|
-
> Stateful components must be returned by `renderWidget` to leverage the library's portal-based mount preservation. Do not wrap `paneProps.renderActiveTab()` with stateful components or pass active tab IDs as props inside `renderPane`, as this will cause those components and their hooks to unmount and remount when the pane's active tab changes.
|
|
618
|
-
|
|
619
|
-
### `useZeugma(options)`
|
|
620
|
-
|
|
621
|
-
A custom hook to manage the dashboard layout state.
|
|
622
|
-
|
|
623
|
-
#### Options
|
|
624
|
-
|
|
625
|
-
- `initialLayout: TreeNode | null` — Initial layout tree structure.
|
|
626
|
-
- `locked?: boolean` — Whether the layout is globally locked.
|
|
627
|
-
- `dragActivationDistance?: number` — Minimum pointer drag distance (in pixels) required to activate dragging (defaults to `8`).
|
|
628
|
-
- `snapThreshold?: number` — Threshold in pixels to snap layout resizers to adjacent edges (defaults to `8`).
|
|
629
|
-
- `minSplitPercentage?: number` — Minimum resizing limit percentage (defaults to `5`).
|
|
630
|
-
- `maxSplitPercentage?: number` — Maximum resizing limit percentage (defaults to `95`).
|
|
631
|
-
- `enableDragToDismiss?: boolean` — Whether to enable drag-out-to-dismiss (defaults to `false`).
|
|
632
|
-
- `dismissThreshold?: number` — Distance in pixels outside container bounds required to trigger dismissal (defaults to `60`).
|
|
633
|
-
- `onRemove?: (paneId: string) => void` — Callback when a pane is removed.
|
|
634
|
-
- `onDragStart?: (activeId: string) => void` — Callback when dragging starts.
|
|
635
|
-
- `onDragEnd?: (activeId: string, overId: string | null, dropAction: any) => void` — Callback when dragging ends.
|
|
636
|
-
- `onResizeStart?: (currentNode: SplitNode) => void` — Callback when resizing starts.
|
|
637
|
-
- `onResize?: (currentNode: SplitNode, percentage: number) => void` — Callback during resizing.
|
|
638
|
-
- `onResizeEnd?: (currentNode: SplitNode, percentage: number) => void` — Callback when resizing ends.
|
|
639
|
-
- `onDismissIntentChange?: (paneId: string | null) => void` — Callback when drag-out intent changes.
|
|
640
|
-
|
|
641
|
-
### `useZeugmaContext()`
|
|
642
|
-
|
|
643
|
-
A context consumer hook that retrieves the parent `<Zeugma>` controller state and actions.
|
|
644
|
-
|
|
645
|
-
```ts
|
|
646
|
-
const { layout, layoutBeforeDrag, addPane, removeTab } = useZeugmaContext()
|
|
647
|
-
```
|
|
648
|
-
|
|
649
|
-
### `<PaneTree>`
|
|
650
|
-
|
|
651
|
-
Recursively renders the split nodes and pane nodes. Must be placed inside `<Zeugma>`.
|
|
652
|
-
|
|
653
|
-
#### Props
|
|
654
|
-
|
|
655
|
-
- `tree?: TreeNode | null` — (Optional) Custom subtree to render. Defaults to the provider's root `layout`.
|
|
656
|
-
- `resizerSize?: number` — (Optional) Thickness of the split resizer bars in pixels. Defaults to `4`.
|
|
657
|
-
|
|
658
|
-
### `<Pane>`
|
|
659
|
-
|
|
660
|
-
Wraps the contents of an individual pane. It sets up draggable and droppable zones.
|
|
661
|
-
|
|
662
|
-
#### Props
|
|
663
|
-
|
|
664
|
-
- `id: string` — The unique ID corresponding to a `PaneNode`'s `paneId`.
|
|
665
|
-
- `children: (props: PaneRenderProps) => ReactNode` — Render prop function.
|
|
666
|
-
|
|
667
|
-
#### `PaneRenderProps`
|
|
668
|
-
|
|
669
|
-
```ts
|
|
670
|
-
interface PaneRenderProps {
|
|
671
|
-
isDragging: boolean
|
|
672
|
-
isFullscreen: boolean
|
|
673
|
-
toggleFullscreen: () => void
|
|
674
|
-
remove: () => void
|
|
675
|
-
metadata: Record<string, unknown> | undefined
|
|
676
|
-
updateMetadata: (
|
|
677
|
-
updater: (current: Record<string, unknown> | undefined) => Record<string, unknown> | undefined,
|
|
678
|
-
) => void
|
|
679
|
-
tabs: string[]
|
|
680
|
-
activeTabId: string
|
|
681
|
-
selectTab: (tabId: string) => void
|
|
682
|
-
removeTab: (tabId: string) => void
|
|
683
|
-
tabsMetadata: Record<string, Record<string, unknown>> | undefined
|
|
684
|
-
updateTabMetadata: (
|
|
685
|
-
tabId: string,
|
|
686
|
-
updater: (current: Record<string, unknown> | undefined) => Record<string, unknown> | undefined,
|
|
687
|
-
) => void
|
|
688
|
-
renderActiveTab: () => ReactNode
|
|
689
|
-
}
|
|
690
|
-
```
|
|
691
|
-
|
|
692
|
-
### `<Tabs>`
|
|
693
|
-
|
|
694
|
-
Renders a list of tabs inside a pane, wrapping the internal drag-and-drop mechanics.
|
|
695
|
-
|
|
696
|
-
#### Props
|
|
697
|
-
|
|
698
|
-
- `tabs: string[]` — The list of tab IDs.
|
|
699
|
-
- `activeTabId: string` — The currently active tab ID.
|
|
700
|
-
- `locked?: boolean` — Whether dragging is disabled (defaults to `false`).
|
|
701
|
-
- `tabsMetadata?: Record<string, Record<string, unknown>>` — Metadata for the tabs.
|
|
702
|
-
- `selectTab: (id: string) => void` — Callback when a tab is selected.
|
|
703
|
-
- `removeTab: (id: string) => void` — Callback when a tab is closed.
|
|
704
|
-
- `classNames?: { container?: string; tab?: string | ((tabId: string) => string) }` — Custom class names.
|
|
705
|
-
- `styles?: { container?: React.CSSProperties; tab?: React.CSSProperties | ((tabId: string) => React.CSSProperties) }` — Custom styles.
|
|
706
|
-
- `renderTab: (props: { tabId: string; activeTabId: string; isDragging: boolean; isOver: boolean; metadata?: Record<string, unknown>; selectTab: (id: string) => void; removeTab: (id: string) => void; }) => React.ReactNode` — Render prop function.
|
|
707
|
-
|
|
708
|
-
### `<DragHandle>`
|
|
709
|
-
|
|
710
|
-
Defines the interactive drag region inside a `<Pane>`. **Must be placed inside a `<Pane>` component.**
|
|
711
|
-
|
|
712
|
-
#### Props
|
|
713
|
-
|
|
714
|
-
- `children: React.ReactNode` — Element(s) that function as the drag handle (e.g., pane header).
|
|
715
|
-
- `className?: string`
|
|
716
|
-
- `style?: React.CSSProperties`
|
|
717
|
-
|
|
718
|
-
## 3. Programmatic State Utilities
|
|
719
|
-
|
|
720
|
-
Import these helpers from `react-zeugma/utils` to manipulate or query the tree layout programmatically in your state handlers:
|
|
721
|
-
|
|
722
|
-
- **`removePane(tree: TreeNode | null, idToRemove: string): TreeNode | null`**
|
|
723
|
-
Removes a pane from the tree and collapses the leftover sibling split node.
|
|
724
|
-
- **`splitPane(tree: TreeNode | null, targetId: string, direction: SplitDirection, splitType: 'left' | 'right' | 'top' | 'bottom', paneToAdd: string): TreeNode | null`**
|
|
725
|
-
Splits a specific target pane by nesting it under a new `SplitNode` along with a new pane.
|
|
726
|
-
- **`updateTabMetadata(tree: TreeNode | null, tabId: string, updater: (current: Record<string, unknown> | undefined) => Record<string, unknown> | undefined): TreeNode | null`**
|
|
727
|
-
Updates the metadata of a specific tab.
|
|
728
|
-
- **`findPaneById(tree: TreeNode | null, paneId: string): PaneNode | null`**
|
|
729
|
-
Recursively searches the layout tree and returns the target `PaneNode` if found, or `null` otherwise.
|
|
730
|
-
- **`findPaneContainingTab(tree: TreeNode | null, tabId: string): PaneNode | null`**
|
|
731
|
-
Recursively searches the layout tree and returns the `PaneNode` containing the specified `tabId`.
|
|
732
|
-
- **`findTabById(tree: TreeNode | null, tabId: string): TabDetails | null`**
|
|
733
|
-
Searches the layout tree for the given `tabId` and returns computed details (parent `paneId`, `isActive`, `index`, and custom `metadata`).
|
|
734
|
-
- **`calculateTabDropIndex(tabs: string[], activeType: string | null, overTabId: string | null, overTabPosition: 'before' | 'after' | null): number`**
|
|
735
|
-
Calculates the target insertion index for a dragged tab within a list of tabs. Returns `-1` if the drop target is not in the list.
|
|
736
|
-
|
|
737
|
-
---
|
|
738
|
-
|
|
739
|
-
## 4. Basic Integration Recipe
|
|
740
|
-
|
|
741
|
-
```tsx
|
|
742
|
-
import { useZeugma, Zeugma, PaneTree, Pane, DragHandle, TreeNode } from 'react-zeugma'
|
|
743
|
-
|
|
744
|
-
const initialLayout: TreeNode = {
|
|
745
|
-
type: 'split',
|
|
746
|
-
direction: 'row',
|
|
747
|
-
splitPercentage: 50,
|
|
748
|
-
first: { type: 'pane', id: 'sidebar', tabs: ['sidebar'], activeTabId: 'sidebar' },
|
|
749
|
-
second: { type: 'pane', id: 'main', tabs: ['main'], activeTabId: 'main' },
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
function CustomPane({ id }: { id: string }) {
|
|
753
|
-
return (
|
|
754
|
-
<Pane id={id}>
|
|
755
|
-
{({ isDragging, isFullscreen, toggleFullscreen, remove }) => (
|
|
756
|
-
<div style={{ height: '100%', border: '1px solid #ccc', opacity: isDragging ? 0.5 : 1 }}>
|
|
757
|
-
<div style={{ display: 'flex', background: '#eee', padding: 8 }}>
|
|
758
|
-
<DragHandle style={{ flex: 1 }}>
|
|
759
|
-
<strong>Header: {id}</strong>
|
|
760
|
-
</DragHandle>
|
|
761
|
-
<button onClick={toggleFullscreen}>
|
|
762
|
-
{isFullscreen ? 'Exit Fullscreen' : 'Fullscreen'}
|
|
763
|
-
</button>
|
|
764
|
-
<button onClick={remove}>Close</button>
|
|
765
|
-
</div>
|
|
766
|
-
<div style={{ padding: 16 }}>Content for {id}</div>
|
|
767
|
-
</div>
|
|
768
|
-
)}
|
|
769
|
-
</Pane>
|
|
770
|
-
)
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
export default function App() {
|
|
774
|
-
const zeugma = useZeugma({
|
|
775
|
-
initialLayout,
|
|
776
|
-
})
|
|
777
|
-
|
|
778
|
-
return (
|
|
779
|
-
<Zeugma {...zeugma} renderPane={(id) => <CustomPane id={id} />}>
|
|
780
|
-
<div style={{ width: '100vw', height: '100vh' }}>
|
|
781
|
-
<PaneTree />
|
|
782
|
-
</div>
|
|
783
|
-
</Zeugma>
|
|
784
|
-
)
|
|
785
|
-
}
|
|
786
|
-
```
|
|
787
|
-
|
|
788
|
-
---
|
|
789
|
-
|
|
790
|
-
## 5. Styling Customization
|
|
791
|
-
|
|
792
|
-
`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 `<Zeugma>`:
|
|
793
|
-
|
|
794
|
-
> [!IMPORTANT]
|
|
795
|
-
> Starting from version `4.1.2`, `react-zeugma` is 100% headless and does not apply any internal default CSS fallback classes (such as `zeugma-resizer`, `zeugma-locked-preview`, etc.). All layout visual states must be styled by providing custom class names via the `classNames` configuration object.
|
|
796
|
-
|
|
797
|
-
```ts
|
|
798
|
-
interface ZeugmaClassNames {
|
|
799
|
-
dashboard?: string // Applied to the root dashboard container
|
|
800
|
-
dashboardDismissActive?: string // Applied to root container when dismiss intent is active
|
|
801
|
-
dashboardLocked?: string // Applied to root container when dashboard is globally locked
|
|
802
|
-
pane?: string // Applied to the outer wrapper of <Pane>
|
|
803
|
-
paneLocked?: string // Applied to the pane container when locked
|
|
804
|
-
dropPreview?: string // Applied to the preview box when hovering over edge dropzones
|
|
805
|
-
dragOverlay?: string // Applied to the cursor-following drag preview portal
|
|
806
|
-
resizer?: string // Applied to the drag-to-resize split bar
|
|
807
|
-
dismissPreview?: string // Applied to the background dismiss zone indicator during a drag-out dismiss gesture
|
|
808
|
-
lockedPreview?: string // Applied to drop zone indicator when hovering over a locked pane
|
|
809
|
-
tabDropPreview?: string // Applied to the drop placeholder line element during tab drags
|
|
810
|
-
tabSeparator?: string // Applied to the separator line between non-active adjacent tabs
|
|
811
|
-
}
|
|
812
|
-
```
|
|
813
|
-
|
|
814
|
-
### Tab Drop Preview Customization
|
|
815
|
-
|
|
816
|
-
When dragging a tab, the library automatically calculates the target insertion index and renders a placeholder indicator line at that position within the tabs list (between adjacent tabs or at the list boundaries).
|
|
817
|
-
|
|
818
|
-
To style this indicator line, configure a custom CSS class name via `classNames.tabDropPreview`.
|
|
819
|
-
|
|
820
|
-
Use this single class name in your CSS to customize the color and size (width) of the placeholder indicator line:
|
|
821
|
-
|
|
822
|
-
```css
|
|
823
|
-
/* Custom tab drop placeholder styling */
|
|
824
|
-
.my-tab-preview {
|
|
825
|
-
background-color: #6366f1 !important; /* change color */
|
|
826
|
-
width: 3px !important; /* change width/size */
|
|
827
|
-
}
|
|
828
|
-
```
|
|
829
|
-
|
|
830
|
-
### CSS Example:
|
|
831
|
-
|
|
832
|
-
```css
|
|
833
|
-
/* Custom resizer style */
|
|
834
|
-
.my-resizer {
|
|
835
|
-
background-color: #e2e8f0;
|
|
836
|
-
transition: background-color 0.2s;
|
|
837
|
-
}
|
|
838
|
-
.my-resizer:hover {
|
|
839
|
-
background-color: #3b82f6;
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
/* Edge drop previews */
|
|
843
|
-
```
|
|
844
|
-
|
|
845
|
-
---
|
|
846
|
-
|
|
847
|
-
## The Story of Zeugma
|
|
848
|
-
|
|
849
|
-
_Zeugma_ is an ancient city of Commagene, located in modern-day **Gaziantep, Turkey**. Positioned along a critical crossing point of the Euphrates river, Zeugma became a central hub of trade and cultural exchanges.
|
|
850
|
-
|
|
851
|
-
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.
|
|
852
|
-
|
|
853
|
-
> _"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."_
|
|
854
|
-
|
|
855
|
-
---
|
|
856
|
-
|
|
857
|
-
## Links
|
|
858
|
-
|
|
859
|
-
- [GitHub Repository](https://github.com/react-zeugma/react-zeugma)
|
|
860
|
-
- [npm Package](https://www.npmjs.com/package/react-zeugma)
|
|
861
|
-
- [Contributing Guide](https://github.com/react-zeugma/react-zeugma/blob/master/CONTRIBUTING.md)
|
|
862
|
-
````
|