@wordpress/grid 0.1.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.
Files changed (158) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/LICENSE.md +788 -0
  3. package/README.md +534 -0
  4. package/build/dashboard-grid/grid-item.cjs +308 -0
  5. package/build/dashboard-grid/grid-item.cjs.map +7 -0
  6. package/build/dashboard-grid/index.cjs +591 -0
  7. package/build/dashboard-grid/index.cjs.map +7 -0
  8. package/build/dashboard-grid/resolve-fill-widths.cjs +189 -0
  9. package/build/dashboard-grid/resolve-fill-widths.cjs.map +7 -0
  10. package/build/dashboard-grid/types.cjs +19 -0
  11. package/build/dashboard-grid/types.cjs.map +7 -0
  12. package/build/dashboard-lanes/index.cjs +558 -0
  13. package/build/dashboard-lanes/index.cjs.map +7 -0
  14. package/build/dashboard-lanes/lane-placement.cjs +110 -0
  15. package/build/dashboard-lanes/lane-placement.cjs.map +7 -0
  16. package/build/dashboard-lanes/lanes-item.cjs +295 -0
  17. package/build/dashboard-lanes/lanes-item.cjs.map +7 -0
  18. package/build/dashboard-lanes/types.cjs +19 -0
  19. package/build/dashboard-lanes/types.cjs.map +7 -0
  20. package/build/dashboard-lanes/use-lane-placement.cjs +206 -0
  21. package/build/dashboard-lanes/use-lane-placement.cjs.map +7 -0
  22. package/build/index.cjs +34 -0
  23. package/build/index.cjs.map +7 -0
  24. package/build/shared/drag-overlay-drop-animation.cjs +70 -0
  25. package/build/shared/drag-overlay-drop-animation.cjs.map +7 -0
  26. package/build/shared/grid-item-key.cjs +31 -0
  27. package/build/shared/grid-item-key.cjs.map +7 -0
  28. package/build/shared/grid-overlay.cjs +187 -0
  29. package/build/shared/grid-overlay.cjs.map +7 -0
  30. package/build/shared/item-exit-overlay.cjs +150 -0
  31. package/build/shared/item-exit-overlay.cjs.map +7 -0
  32. package/build/shared/resize-handle.cjs +224 -0
  33. package/build/shared/resize-handle.cjs.map +7 -0
  34. package/build/shared/resize-snap.cjs +47 -0
  35. package/build/shared/resize-snap.cjs.map +7 -0
  36. package/build/shared/types.cjs +19 -0
  37. package/build/shared/types.cjs.map +7 -0
  38. package/build/shared/use-item-exit-animation.cjs +148 -0
  39. package/build/shared/use-item-exit-animation.cjs.map +7 -0
  40. package/build/shared/use-layout-shift-animation.cjs +167 -0
  41. package/build/shared/use-layout-shift-animation.cjs.map +7 -0
  42. package/build-module/dashboard-grid/grid-item.mjs +273 -0
  43. package/build-module/dashboard-grid/grid-item.mjs.map +7 -0
  44. package/build-module/dashboard-grid/index.mjs +579 -0
  45. package/build-module/dashboard-grid/index.mjs.map +7 -0
  46. package/build-module/dashboard-grid/resolve-fill-widths.mjs +164 -0
  47. package/build-module/dashboard-grid/resolve-fill-widths.mjs.map +7 -0
  48. package/build-module/dashboard-grid/types.mjs +1 -0
  49. package/build-module/dashboard-grid/types.mjs.map +7 -0
  50. package/build-module/dashboard-lanes/index.mjs +547 -0
  51. package/build-module/dashboard-lanes/index.mjs.map +7 -0
  52. package/build-module/dashboard-lanes/lane-placement.mjs +85 -0
  53. package/build-module/dashboard-lanes/lane-placement.mjs.map +7 -0
  54. package/build-module/dashboard-lanes/lanes-item.mjs +260 -0
  55. package/build-module/dashboard-lanes/lanes-item.mjs.map +7 -0
  56. package/build-module/dashboard-lanes/types.mjs +1 -0
  57. package/build-module/dashboard-lanes/types.mjs.map +7 -0
  58. package/build-module/dashboard-lanes/use-lane-placement.mjs +181 -0
  59. package/build-module/dashboard-lanes/use-lane-placement.mjs.map +7 -0
  60. package/build-module/index.mjs +8 -0
  61. package/build-module/index.mjs.map +7 -0
  62. package/build-module/shared/drag-overlay-drop-animation.mjs +47 -0
  63. package/build-module/shared/drag-overlay-drop-animation.mjs.map +7 -0
  64. package/build-module/shared/grid-item-key.mjs +6 -0
  65. package/build-module/shared/grid-item-key.mjs.map +7 -0
  66. package/build-module/shared/grid-overlay.mjs +152 -0
  67. package/build-module/shared/grid-overlay.mjs.map +7 -0
  68. package/build-module/shared/item-exit-overlay.mjs +125 -0
  69. package/build-module/shared/item-exit-overlay.mjs.map +7 -0
  70. package/build-module/shared/resize-handle.mjs +193 -0
  71. package/build-module/shared/resize-handle.mjs.map +7 -0
  72. package/build-module/shared/resize-snap.mjs +21 -0
  73. package/build-module/shared/resize-snap.mjs.map +7 -0
  74. package/build-module/shared/types.mjs +1 -0
  75. package/build-module/shared/types.mjs.map +7 -0
  76. package/build-module/shared/use-item-exit-animation.mjs +128 -0
  77. package/build-module/shared/use-item-exit-animation.mjs.map +7 -0
  78. package/build-module/shared/use-layout-shift-animation.mjs +140 -0
  79. package/build-module/shared/use-layout-shift-animation.mjs.map +7 -0
  80. package/build-types/dashboard-grid/grid-item.d.ts +3 -0
  81. package/build-types/dashboard-grid/grid-item.d.ts.map +1 -0
  82. package/build-types/dashboard-grid/index.d.ts +35 -0
  83. package/build-types/dashboard-grid/index.d.ts.map +1 -0
  84. package/build-types/dashboard-grid/resolve-fill-widths.d.ts +26 -0
  85. package/build-types/dashboard-grid/resolve-fill-widths.d.ts.map +1 -0
  86. package/build-types/dashboard-grid/stories/index.story.d.ts +98 -0
  87. package/build-types/dashboard-grid/stories/index.story.d.ts.map +1 -0
  88. package/build-types/dashboard-grid/types.d.ts +232 -0
  89. package/build-types/dashboard-grid/types.d.ts.map +1 -0
  90. package/build-types/dashboard-lanes/index.d.ts +40 -0
  91. package/build-types/dashboard-lanes/index.d.ts.map +1 -0
  92. package/build-types/dashboard-lanes/lane-placement.d.ts +126 -0
  93. package/build-types/dashboard-lanes/lane-placement.d.ts.map +1 -0
  94. package/build-types/dashboard-lanes/lanes-item.d.ts +52 -0
  95. package/build-types/dashboard-lanes/lanes-item.d.ts.map +1 -0
  96. package/build-types/dashboard-lanes/stories/index.story.d.ts +64 -0
  97. package/build-types/dashboard-lanes/stories/index.story.d.ts.map +1 -0
  98. package/build-types/dashboard-lanes/types.d.ts +151 -0
  99. package/build-types/dashboard-lanes/types.d.ts.map +1 -0
  100. package/build-types/dashboard-lanes/use-lane-placement.d.ts +74 -0
  101. package/build-types/dashboard-lanes/use-lane-placement.d.ts.map +1 -0
  102. package/build-types/index.d.ts +6 -0
  103. package/build-types/index.d.ts.map +1 -0
  104. package/build-types/shared/drag-overlay-drop-animation.d.ts +13 -0
  105. package/build-types/shared/drag-overlay-drop-animation.d.ts.map +1 -0
  106. package/build-types/shared/grid-item-key.d.ts +6 -0
  107. package/build-types/shared/grid-item-key.d.ts.map +1 -0
  108. package/build-types/shared/grid-overlay.d.ts +19 -0
  109. package/build-types/shared/grid-overlay.d.ts.map +1 -0
  110. package/build-types/shared/item-exit-overlay.d.ts +20 -0
  111. package/build-types/shared/item-exit-overlay.d.ts.map +1 -0
  112. package/build-types/shared/resize-handle.d.ts +23 -0
  113. package/build-types/shared/resize-handle.d.ts.map +1 -0
  114. package/build-types/shared/resize-snap.d.ts +41 -0
  115. package/build-types/shared/resize-snap.d.ts.map +1 -0
  116. package/build-types/shared/types.d.ts +144 -0
  117. package/build-types/shared/types.d.ts.map +1 -0
  118. package/build-types/shared/use-item-exit-animation.d.ts +37 -0
  119. package/build-types/shared/use-item-exit-animation.d.ts.map +1 -0
  120. package/build-types/shared/use-layout-shift-animation.d.ts +77 -0
  121. package/build-types/shared/use-layout-shift-animation.d.ts.map +1 -0
  122. package/package.json +80 -0
  123. package/src/dashboard-grid/grid-item.module.css +94 -0
  124. package/src/dashboard-grid/grid-item.tsx +205 -0
  125. package/src/dashboard-grid/grid.module.css +134 -0
  126. package/src/dashboard-grid/index.tsx +713 -0
  127. package/src/dashboard-grid/resolve-fill-widths.ts +224 -0
  128. package/src/dashboard-grid/stories/index.story.tsx +930 -0
  129. package/src/dashboard-grid/test/keyboard-activation.test.tsx +76 -0
  130. package/src/dashboard-grid/test/resolve-fill-widths.test.ts +250 -0
  131. package/src/dashboard-grid/types.ts +271 -0
  132. package/src/dashboard-lanes/index.tsx +629 -0
  133. package/src/dashboard-lanes/lane-placement.ts +245 -0
  134. package/src/dashboard-lanes/lanes-item.module.css +93 -0
  135. package/src/dashboard-lanes/lanes-item.tsx +236 -0
  136. package/src/dashboard-lanes/lanes.module.css +152 -0
  137. package/src/dashboard-lanes/stories/index.story.tsx +518 -0
  138. package/src/dashboard-lanes/test/keyboard-activation.test.tsx +71 -0
  139. package/src/dashboard-lanes/test/lane-placement.test.ts +442 -0
  140. package/src/dashboard-lanes/test/use-lane-placement.test.tsx +358 -0
  141. package/src/dashboard-lanes/types.ts +176 -0
  142. package/src/dashboard-lanes/use-lane-placement.ts +313 -0
  143. package/src/index.ts +17 -0
  144. package/src/shared/actionable-area-slot.module.css +16 -0
  145. package/src/shared/drag-overlay-drop-animation.ts +66 -0
  146. package/src/shared/grid-item-key.ts +5 -0
  147. package/src/shared/grid-overlay.module.css +82 -0
  148. package/src/shared/grid-overlay.tsx +93 -0
  149. package/src/shared/item-exit-animation.module.css +49 -0
  150. package/src/shared/item-exit-overlay.tsx +57 -0
  151. package/src/shared/layout-shift-animation.module.css +16 -0
  152. package/src/shared/resize-handle.module.css +88 -0
  153. package/src/shared/resize-handle.tsx +163 -0
  154. package/src/shared/resize-snap.ts +63 -0
  155. package/src/shared/test/resize-snap.test.ts +35 -0
  156. package/src/shared/types.ts +164 -0
  157. package/src/shared/use-item-exit-animation.ts +199 -0
  158. package/src/shared/use-layout-shift-animation.ts +284 -0
package/README.md ADDED
@@ -0,0 +1,534 @@
1
+ # Grid
2
+
3
+ <div class="callout callout-alert">
4
+ This package is still experimental. “Experimental” means this is an early implementation subject to drastic and breaking changes.
5
+ </div>
6
+
7
+ A collection of grid layout components for arranging tiles in
8
+ dashboard-style surfaces.
9
+
10
+ This package exposes two components, each implementing a different
11
+ layout model:
12
+
13
+ - **`DashboardGrid`** is a 2D packed grid: tiles declare explicit
14
+ `(width, height)` spans in column/row units and can span multiple
15
+ columns and rows.
16
+ - **`DashboardLanes`** is a masonry-style surface aligned with the
17
+ emerging WebKit spec [`display: grid-lanes`](https://webkit.org/blog/17660/introducing-css-grid-lanes/).
18
+ Tiles declare a column span only; heights are driven by content;
19
+ placement follows a source-ordered, shortest-lane skyline with a
20
+ `flow-tolerance` tiebreaker.
21
+
22
+ ## Installation
23
+
24
+ Install the module:
25
+
26
+ ```bash
27
+ npm install @wordpress/grid --save
28
+ ```
29
+
30
+ _This package assumes that your code will run in an **ES2015+** environment.
31
+ If you're using an environment that has limited or no support for such
32
+ language features and APIs, you should include [the polyfill shipped in
33
+ `@wordpress/babel-preset-default`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/babel-preset-default#polyfill)
34
+ in your code._
35
+
36
+ ## Setup
37
+
38
+ Component styles are CSS Modules injected at runtime when a component
39
+ mounts; there is no stylesheet to enqueue or import.
40
+
41
+ Visual defaults (tile gap, elevation, motion, placeholder strokes)
42
+ read the design tokens that `@wordpress/theme` publishes as
43
+ `--wpds-*` CSS custom properties.
44
+
45
+ ### Within standard WordPress editor screens
46
+
47
+ In standard WordPress editor screens (such as the post editor or the
48
+ site editor), the design tokens stylesheet is managed centrally by
49
+ Gutenberg. You don't need to add any setup yourself.
50
+
51
+ ### Elsewhere
52
+
53
+ Install and load the design tokens stylesheet in your application:
54
+
55
+ ```bash
56
+ npm install @wordpress/theme
57
+ ```
58
+
59
+ ```js
60
+ import '@wordpress/theme/design-tokens.css';
61
+ ```
62
+
63
+ Without the tokens the components stay functional, but gaps,
64
+ elevations, and interaction visuals lose their intended values.
65
+ Alternatively, define the `--wpds-*` custom properties the package
66
+ consumes yourself.
67
+
68
+ ## Choosing a component
69
+
70
+ | Need | Use |
71
+ | ----------------------------------------------------- | ------------------------------------------------- |
72
+ | Fixed-cell tile dashboard, content fills its cell. | `DashboardGrid` |
73
+ | Masonry / waterfall surface, content drives height. | `DashboardLanes` |
74
+ | Static layout primitive (no per-item state, no drag). | `__experimentalGrid` from `@wordpress/components` |
75
+
76
+ Both components here are higher-level: the user (not the developer)
77
+ places and resizes tiles, and the result is emitted through
78
+ `onChangeLayout`. For a static CSS Grid with no spans, drag, or
79
+ per-item state, use `__experimentalGrid` from `@wordpress/components`.
80
+
81
+ ---
82
+
83
+ ## `DashboardGrid`
84
+
85
+ A 2D packed grid where each child has an explicit column and row
86
+ span.
87
+
88
+ ### Usage
89
+
90
+ ```jsx
91
+ import { DashboardGrid } from '@wordpress/grid';
92
+
93
+ const layout = [
94
+ { key: 'a', width: 2, height: 2 },
95
+ { key: 'b', width: 4, height: 1 },
96
+ { key: 'c', width: 'fill', height: 1 },
97
+ { key: 'd', width: 'full', height: 1 },
98
+ ];
99
+
100
+ function Dashboard() {
101
+ const [ current, setCurrent ] = useState( layout );
102
+
103
+ return (
104
+ <DashboardGrid
105
+ layout={ current }
106
+ columns={ 6 }
107
+ editMode
108
+ onChangeLayout={ setCurrent }
109
+ >
110
+ <div key="a">Tile A</div>
111
+ <div key="b">Tile B</div>
112
+ <div key="c">Tile C</div>
113
+ <div key="d">Tile D</div>
114
+ </DashboardGrid>
115
+ );
116
+ }
117
+ ```
118
+
119
+ Each child **must** have a `key` prop that matches an entry in the
120
+ `layout` array. Children without a matching layout entry render at
121
+ the end of the grid without explicit placement and fall through
122
+ CSS Grid's auto-flow.
123
+
124
+ ### Layout model
125
+
126
+ ```ts
127
+ interface DashboardGridLayoutItem {
128
+ key: string; // matches child key
129
+ width?: number | 'fill' | 'full'; // column span (see below)
130
+ height?: number; // rows to span
131
+ order?: number; // lower values render first (responsive mode)
132
+ }
133
+ ```
134
+
135
+ `width` is a discriminated value:
136
+
137
+ - `number`: span that many columns (clamped to the grid's column count).
138
+ - `'fill'`: fill the remaining columns in the current row.
139
+ - `'full'`: span every column (`grid-column: 1 / -1`), forcing a row break.
140
+
141
+ `'fill'` is resolved per-row against the remaining free space.
142
+
143
+ ### Props
144
+
145
+ | Prop | Type | Default | Description |
146
+ | -------------------- | ------------------------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
147
+ | `layout` | `DashboardGridLayoutItem[]` | — | Required. Positions and sizes keyed by child `key`. |
148
+ | `children` | `ReactNode` | — | Required. Each child needs a `key` matching a layout entry. |
149
+ | `columns` | `number` | `6` | Total columns (fixed mode). |
150
+ | `minColumnWidth` | `number` | — | If set, enables responsive mode: columns derived from container width. Mutually exclusive with `columns`. |
151
+ | `rowHeight` | `number \| 'auto'` | `'auto'` | Row height in pixels, or `'auto'` to let content size rows. |
152
+ | `editMode` | `boolean` | `false` | Enables drag-to-reorder and resize handles. |
153
+ | `onChangeLayout` | `( layout ) => void` | — | Fired when the user commits a drag or resize. |
154
+ | `onPreviewLayout` | `( layout ) => void` | — | Fired continuously during a drag or resize with the in-progress layout. Use for live feedback; `onChangeLayout` still emits the committed result. |
155
+ | `renderResizeHandle` | `ComponentType< ResizeHandleRenderProps >` | — | Override the default corner-triangle resize handle. See [Custom resize handle](#custom-resize-handle). |
156
+ | `renderDragPreview` | `ComponentType< DragPreviewRenderProps >` | — | Wrap the dragged-clone visual mounted inside `<DragOverlay>`. See [Custom drag preview](#custom-drag-preview). |
157
+ | `renderGridOverlay` | `ComponentType< GridOverlayRenderProps >` | — | Override the default edit-mode overlay that visualizes the column and row tracks. Receives the resolved `columns`, `rows`, `rowHeight`, and `isActive`. |
158
+ | `className` | `string` | — | Extra class on the grid root. |
159
+ | `style` | `CSSProperties` | — | Inline styles on the grid root; the grid's own layout styles win over them. |
160
+
161
+ `DashboardGrid` forwards refs to its root `<div>`, and standard
162
+ `<div>` attributes (`id`, `aria-*`, `data-*`, event handlers,
163
+ `style`, etc.) flow through. The grid's own layout styles
164
+ (`gridTemplateColumns`, `gridAutoRows`) override any user-supplied
165
+ `style` for those properties. Tile gap is owned by a design-system
166
+ token; override it with `--wp-grid-gap` (see [Theming with CSS
167
+ variables](#theming-with-css-variables)).
168
+
169
+ #### Child-level props
170
+
171
+ Children render with the layout entry that matches their `key`. An
172
+ optional prop read off the child lets you keep controls interactive
173
+ while edit mode is on:
174
+
175
+ | Child prop | Type | Description |
176
+ | ---------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
177
+ | `actionableArea` | `ReactNode` | Content rendered above the draggable surface of the grid item. Useful for close buttons, menus, or links that must stay clickable in edit mode. |
178
+
179
+ ### Modes
180
+
181
+ #### Fixed columns
182
+
183
+ ```jsx
184
+ <DashboardGrid layout={ layout } columns={ 12 }>
185
+ { children }
186
+ </DashboardGrid>
187
+ ```
188
+
189
+ #### Responsive
190
+
191
+ Columns are computed from container width using `minColumnWidth` as
192
+ the lower bound per column. A `ResizeObserver` recomputes on
193
+ container resize.
194
+
195
+ ```jsx
196
+ <DashboardGrid layout={ layout } minColumnWidth={ 240 }>
197
+ { children }
198
+ </DashboardGrid>
199
+ ```
200
+
201
+ In responsive mode, layout items can provide an `order` to control
202
+ display order independently of array position.
203
+
204
+ #### Edit mode
205
+
206
+ When `editMode` is true:
207
+
208
+ - Items become draggable (powered by `@dnd-kit`). The original tile
209
+ stays in place as a dashed placeholder while a clone follows the
210
+ cursor through `<DragOverlay>`.
211
+ - A resize handle appears on the bottom-right of each item. A
212
+ solid outline previews the target size as the cursor moves.
213
+ - While any tile is dragging or resizing, `actionableArea` content
214
+ on every tile is set `inert` so hovers on other tiles can't steal
215
+ the gesture.
216
+ - `onChangeLayout` fires after drop or resize with the new layout.
217
+ - `onPreviewLayout` fires continuously during the interaction for
218
+ live feedback; the committed layout is still emitted via
219
+ `onChangeLayout`.
220
+ - Sibling tiles animate into their new positions when the layout
221
+ reflows.
222
+
223
+ ---
224
+
225
+ ## `DashboardLanes`
226
+
227
+ A masonry-style surface aligned with `display: grid-lanes`. Items
228
+ declare a column span; heights are driven by content; placement
229
+ follows a source-ordered, shortest-lane skyline with a
230
+ `flow-tolerance` tiebreaker.
231
+
232
+ The layout model and the placement algorithm are described in
233
+ [Introducing CSS Grid Lanes](https://webkit.org/blog/17660/introducing-css-grid-lanes/)
234
+ on the WebKit blog. This package implements the same model in
235
+ JavaScript so it works today on browsers that do not yet support
236
+ `display: grid-lanes` natively; the skyline + tolerance core is
237
+ adapted from Simon Willison's
238
+ [CSS Grid Lanes Polyfill](https://tools.simonwillison.net/grid-lanes-polyfill.js)
239
+ (MIT). Once native support lands across browsers, the polyfill can
240
+ be removed without any public API change.
241
+
242
+ ### Usage
243
+
244
+ ```jsx
245
+ import { DashboardLanes } from '@wordpress/grid';
246
+
247
+ const layout = [
248
+ { key: 'a' },
249
+ { key: 'hero', width: 2 },
250
+ { key: 'b' },
251
+ { key: 'c' },
252
+ ];
253
+
254
+ function Pinboard() {
255
+ const [ current, setCurrent ] = useState( layout );
256
+
257
+ return (
258
+ <DashboardLanes
259
+ layout={ current }
260
+ columns={ 4 }
261
+ editMode
262
+ onChangeLayout={ setCurrent }
263
+ >
264
+ <Tile key="a">A</Tile>
265
+ <Tile key="hero">Hero (spans 2 lanes)</Tile>
266
+ <Tile key="b">B</Tile>
267
+ <Tile key="c">C</Tile>
268
+ </DashboardLanes>
269
+ );
270
+ }
271
+ ```
272
+
273
+ Each child **must** have a `key` prop that matches an entry in the
274
+ `layout` array. Children without a matching layout entry render at
275
+ the end of the surface without explicit placement and fall through
276
+ the lanes auto-flow.
277
+
278
+ ### Layout model
279
+
280
+ ```ts
281
+ interface DashboardLanesLayoutItem {
282
+ key: string; // matches child key
283
+ width?: number; // lanes to span (default 1)
284
+ lane?: number; // 0-indexed: pin to a specific lane
285
+ order?: number; // lower values render first
286
+ }
287
+ ```
288
+
289
+ There is no `height` field: lanes pack tiles vertically using each
290
+ tile's intrinsic content height.
291
+
292
+ There is no `'fill'`: with auto-placement, no item is "left over"
293
+ in a row; the algorithm always finds a lane.
294
+
295
+ `'full'` (span the entire surface width) is expressed by setting
296
+ `width` to the lane count.
297
+
298
+ To anchor a tile to a specific column, set `lane` to its 0-indexed
299
+ position. Pinned tiles are placed before auto-placed ones, so auto
300
+ items flow around them; out-of-range values (negative, or beyond
301
+ `columns - width`) are clamped.
302
+
303
+ ### Props
304
+
305
+ | Prop | Type | Default | Description |
306
+ | -------------------- | ------------------------------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
307
+ | `layout` | `DashboardLanesLayoutItem[]` | — | Required. Span and order keyed by child `key`. |
308
+ | `children` | `ReactNode` | — | Required. Each child needs a `key` matching a layout entry. |
309
+ | `columns` | `number` | `6` | Total lanes (fixed mode). |
310
+ | `minColumnWidth` | `number` | — | If set, enables responsive mode: lane count derived from container width. Mutually exclusive with `columns`. |
311
+ | `flowTolerance` | `number` | `16` | Pixel tolerance for source-order tiebreaking when two candidate lanes have similar baselines. Larger values keep tiles closer to reading order at the cost of bigger empty regions. |
312
+ | `rowUnit` | `number` | `4` | Snap unit for the polyfill's `grid-row-start` math. Smaller values produce sharper placement at the cost of a larger implicit row count. Ignored on browsers with native `display: grid-lanes` support. |
313
+ | `editMode` | `boolean` | `false` | Enables drag-to-reorder and horizontal resize. |
314
+ | `onChangeLayout` | `( layout ) => void` | — | Fired when the user commits a drag or resize. |
315
+ | `onPreviewLayout` | `( layout ) => void` | — | Fired continuously during a drag or resize. |
316
+ | `renderResizeHandle` | `ComponentType< ResizeHandleRenderProps >` | — | Override the default side-grip resize handle. See [Custom resize handle](#custom-resize-handle). |
317
+ | `renderDragPreview` | `ComponentType< DragPreviewRenderProps >` | — | Wrap the dragged-clone visual mounted inside `<DragOverlay>`. See [Custom drag preview](#custom-drag-preview). |
318
+ | `renderGridOverlay` | `ComponentType< GridOverlayRenderProps >` | — | Override the default edit-mode overlay that visualizes the lane tracks. Receives the resolved `columns` and `isActive`; lanes pass no row metrics because heights are content-driven. |
319
+ | `className` | `string` | — | Extra class on the surface root. |
320
+ | `style` | `CSSProperties` | — | Inline styles on the surface root; the surface's own layout styles win over them. |
321
+
322
+ ### Native vs polyfill
323
+
324
+ `DashboardLanes` checks `CSS.supports( 'display', 'grid-lanes' )`
325
+ once at mount.
326
+
327
+ - When supported (Safari 26+, others as the spec ships), the
328
+ component emits `display: grid-lanes` and the spec's CSS, and lets
329
+ the engine handle layout. The placement layer mounts no per-tile
330
+ observers; the only `ResizeObserver` left is the container-width
331
+ one used for responsive mode and resize-step math.
332
+ - When unsupported, an internal hook (`useLanePlacement`) measures
333
+ each tile's height with a `ResizeObserver`, runs the source-ordered
334
+ shortest-lane algorithm, and emits explicit `grid-column-start`
335
+ and `grid-row-start` / `grid-row-end: span N` values on each tile.
336
+
337
+ The same DOM contract is preserved in both paths; the visual is the
338
+ same.
339
+
340
+ ### Edit mode
341
+
342
+ Drag-to-reorder works the same as in `DashboardGrid`. Resize is
343
+ **horizontal-only**: tile heights are content-driven, so there is
344
+ no vertical resize gesture. The default handle is a vertical bar
345
+ centered on the trailing edge; the cursor is `ew-resize`.
346
+
347
+ Sibling tiles animate into their new positions when the layout
348
+ reflows.
349
+
350
+ ---
351
+
352
+ ## Shared topics
353
+
354
+ ### Performance
355
+
356
+ `onPreviewLayout` re-renders the parent on every gesture frame. To
357
+ keep the components' internal children walk from re-running each
358
+ frame, **memoize the children array** when its content is stable:
359
+
360
+ ```jsx
361
+ const tiles = useMemo(
362
+ () => layout.map( ( item ) => <Tile key={ item.key }>...</Tile> ),
363
+ [ layout ]
364
+ );
365
+
366
+ return (
367
+ <DashboardLanes layout={ layout } editMode onPreviewLayout={ ... }>
368
+ { tiles }
369
+ </DashboardLanes>
370
+ );
371
+ ```
372
+
373
+ Without it the surface still works but walks the children on every
374
+ preview update; the overhead is minor up to ~50 tiles and grows
375
+ from there. For `DashboardLanes`, placement runs in a
376
+ `useLayoutEffect` throttled to one frame per measurement burst.
377
+
378
+ ### Accessibility
379
+
380
+ Drag-to-reorder is operable from the keyboard via `@dnd-kit`'s
381
+ keyboard sensor:
382
+
383
+ - `Tab` to focus a tile.
384
+ - `Space` to pick it up.
385
+ - Arrow keys to move it between positions.
386
+ - `Space` to drop, or `Escape` to cancel.
387
+
388
+ Resize handles are currently pointer-only.
389
+
390
+ ### Custom resize handle
391
+
392
+ Both components accept a `renderResizeHandle` prop to override the
393
+ default visual. The surface owns the resize math (column/row
394
+ stepping, throttled delta loop, layout commit) and passes the
395
+ gesture wiring (`ref`, `listeners`, `attributes`) as props for the
396
+ consumer to spread on the element that should receive pointer
397
+ events. The dnd-kit `<DndContext>` for the resize gesture is
398
+ internal to the handle wrapper; consumers do not need to mount
399
+ their own.
400
+
401
+ ```jsx
402
+ import { Icon } from '@wordpress/ui';
403
+ import { resizeCornerNE } from '@wordpress/icons';
404
+
405
+ function CustomResizeHandle( {
406
+ ref,
407
+ listeners,
408
+ attributes,
409
+ verticalResizable,
410
+ } ) {
411
+ return (
412
+ <div
413
+ ref={ ref }
414
+ { ...listeners }
415
+ { ...attributes }
416
+ style={ {
417
+ position: 'absolute',
418
+ bottom: 4,
419
+ insetInlineEnd: 4,
420
+ cursor: verticalResizable ? 'nwse-resize' : 'ew-resize',
421
+ } }
422
+ >
423
+ <Icon icon={ resizeCornerNE } size={ 16 } />
424
+ </div>
425
+ );
426
+ }
427
+
428
+ <DashboardGrid
429
+ layout={ layout }
430
+ editMode
431
+ renderResizeHandle={ CustomResizeHandle }
432
+ >
433
+ { tiles }
434
+ </DashboardGrid>;
435
+ ```
436
+
437
+ The component receives:
438
+
439
+ | Prop | Type | Description |
440
+ | ------------------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
441
+ | `ref` | `( node ) => void` | dnd-kit ref; assign on the gesture-bearing element. |
442
+ | `listeners` | `SyntheticListenerMap \| undefined` | Pointer/keyboard listeners; spread on the same element. |
443
+ | `attributes` | `DraggableAttributes` | Accessibility/dnd-kit attributes; spread alongside `listeners`. |
444
+ | `verticalResizable` | `boolean` | False on `DashboardLanes` and on `DashboardGrid` with `rowHeight: 'auto'`. Useful for adapting cursor or visual cue. |
445
+ | `isResizing` | `boolean` | True while the user is actively dragging this handle. Use it to swap colors, icons, or transforms during the gesture. |
446
+ | `itemId` | `string` | Owning tile's `key`. |
447
+
448
+ The handle is only mounted while the surface is in edit mode
449
+ (`editMode={ true }`), so the custom component never has to
450
+ short-circuit on a disabled state.
451
+
452
+ ### Custom drag preview
453
+
454
+ While a tile is being dragged, dnd-kit clones it into a `<DragOverlay>`
455
+ that follows the cursor. Both surfaces wrap that clone with a thin
456
+ **functional frame** (`scale`, `cursor: grabbing`, `pointer-events:
457
+ none`) that advertises the lift, but they do not impose visual
458
+ chrome on top: any styles the consumer applied to the tile children
459
+ carry through to the dragged clone unchanged.
460
+
461
+ When the dragged state should look structurally different from the
462
+ persistent tile (a stronger shadow, a different border, an extra
463
+ badge…), pass a `renderDragPreview` component. The surface mounts
464
+ it inside the functional frame and supplies the cloned children
465
+ plus the active tile's `key`:
466
+
467
+ ```jsx
468
+ import { DashboardGrid } from '@wordpress/grid';
469
+ import type { DragPreviewRenderProps } from '@wordpress/grid';
470
+
471
+ function DragPreview( { children }: DragPreviewRenderProps ) {
472
+ return (
473
+ <div className="my-tile-while-dragging">
474
+ { children }
475
+ </div>
476
+ );
477
+ }
478
+
479
+ <DashboardGrid
480
+ layout={ layout }
481
+ editMode
482
+ renderDragPreview={ DragPreview }
483
+ >
484
+ { tiles }
485
+ </DashboardGrid>;
486
+ ```
487
+
488
+ The component receives:
489
+
490
+ | Prop | Type | Description |
491
+ | ---------- | ----------- | --------------------------------------------------------------------------------------------------------------------------- |
492
+ | `children` | `ReactNode` | The cloned tile content the surface mounts inside `<DragOverlay>`. Place it where the visual wrapper expects the tile body. |
493
+ | `itemId` | `string` | Owning tile's `key`. Useful when chrome varies by tile. |
494
+
495
+ For token-only tweaks (lift scale, placeholder opacity, outline
496
+ color, placeholder radius), prefer the [CSS variables](#theming-with-css-variables)
497
+ below; reach for `renderDragPreview` only when the dragged state
498
+ needs markup the persistent tile does not have.
499
+
500
+ ### Theming with CSS variables
501
+
502
+ Both surfaces expose a small set of CSS custom properties for
503
+ visuals that need to flex between consumers without writing a render
504
+ prop. Override them on any ancestor of the surface root (or on the
505
+ root itself via `style`). All values fall back to sensible defaults.
506
+
507
+ | Variable | Default | Applies to |
508
+ | ---------------------------------------- | --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
509
+ | `--wp-grid-gap` | `var(--wpds-dimension-gap-xl)` | Gap between tiles on `DashboardGrid`, `DashboardLanes`, and the edit overlay. |
510
+ | `--wp-grid-drag-preview-scale` | `1.05` | Lift scale of the drag-preview functional frame. Set to `1` to disable the lift. |
511
+ | `--wp-grid-drag-preview-radius` | `0` | Border radius of the drag-preview functional frame so the lift shadow follows the consumer's tile shape. |
512
+ | `--wp-grid-placeholder-opacity` | `0.4` | Opacity of the placeholder tile (the original item while a drag is in flight). |
513
+ | `--wp-grid-placeholder-outline-style` | `dashed` | Outline style of the drag placeholder (for example `solid` or `dotted`). |
514
+ | `--wp-grid-resize-preview-outline-style` | `solid` | Border style of the resize-preview overlay (for example `dashed` or `dotted`). |
515
+ | `--wp-grid-placeholder-outline-color` | `var(--wpds-color-stroke-interactive-brand)` | Outline color of the placeholder and of the resize-preview overlay. |
516
+ | `--wp-grid-placeholder-radius` | `0` | Border radius of the placeholder, used to match the consumer's tile shape so the outline traces the right silhouette. |
517
+ | `--wp-grid-overlay-tile-bg` | `var(--wpds-color-background-surface-neutral-weak)` | Background of the marker tiles painted by the default edit-mode overlay. |
518
+
519
+ ---
520
+
521
+ ## Contributing to this package
522
+
523
+ This is an individual package that's part of the Gutenberg project.
524
+ The project is organized as a monorepo. It's made up of multiple
525
+ self-contained software packages, each with a specific purpose. The
526
+ packages in this monorepo are published to [npm](https://www.npmjs.com/)
527
+ and used by [WordPress](https://make.wordpress.org/core/) as well as
528
+ other software projects.
529
+
530
+ To find out more about contributing to this package or Gutenberg as a
531
+ whole, please read the project's main
532
+ [contributor guide](https://github.com/WordPress/gutenberg/tree/HEAD/CONTRIBUTING.md).
533
+
534
+ <br /><br /><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p>