@wordpress/grid 0.1.1-next.v.202606191442.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/CHANGELOG.md +33 -0
- package/LICENSE.md +788 -0
- package/README.md +534 -0
- package/build/dashboard-grid/grid-item.cjs +308 -0
- package/build/dashboard-grid/grid-item.cjs.map +7 -0
- package/build/dashboard-grid/index.cjs +591 -0
- package/build/dashboard-grid/index.cjs.map +7 -0
- package/build/dashboard-grid/resolve-fill-widths.cjs +189 -0
- package/build/dashboard-grid/resolve-fill-widths.cjs.map +7 -0
- package/build/dashboard-grid/types.cjs +19 -0
- package/build/dashboard-grid/types.cjs.map +7 -0
- package/build/dashboard-lanes/index.cjs +558 -0
- package/build/dashboard-lanes/index.cjs.map +7 -0
- package/build/dashboard-lanes/lane-placement.cjs +110 -0
- package/build/dashboard-lanes/lane-placement.cjs.map +7 -0
- package/build/dashboard-lanes/lanes-item.cjs +295 -0
- package/build/dashboard-lanes/lanes-item.cjs.map +7 -0
- package/build/dashboard-lanes/types.cjs +19 -0
- package/build/dashboard-lanes/types.cjs.map +7 -0
- package/build/dashboard-lanes/use-lane-placement.cjs +206 -0
- package/build/dashboard-lanes/use-lane-placement.cjs.map +7 -0
- package/build/index.cjs +34 -0
- package/build/index.cjs.map +7 -0
- package/build/shared/drag-overlay-drop-animation.cjs +70 -0
- package/build/shared/drag-overlay-drop-animation.cjs.map +7 -0
- package/build/shared/grid-item-key.cjs +31 -0
- package/build/shared/grid-item-key.cjs.map +7 -0
- package/build/shared/grid-overlay.cjs +187 -0
- package/build/shared/grid-overlay.cjs.map +7 -0
- package/build/shared/item-exit-overlay.cjs +150 -0
- package/build/shared/item-exit-overlay.cjs.map +7 -0
- package/build/shared/resize-handle.cjs +224 -0
- package/build/shared/resize-handle.cjs.map +7 -0
- package/build/shared/resize-snap.cjs +47 -0
- package/build/shared/resize-snap.cjs.map +7 -0
- package/build/shared/types.cjs +19 -0
- package/build/shared/types.cjs.map +7 -0
- package/build/shared/use-item-exit-animation.cjs +148 -0
- package/build/shared/use-item-exit-animation.cjs.map +7 -0
- package/build/shared/use-layout-shift-animation.cjs +167 -0
- package/build/shared/use-layout-shift-animation.cjs.map +7 -0
- package/build-module/dashboard-grid/grid-item.mjs +273 -0
- package/build-module/dashboard-grid/grid-item.mjs.map +7 -0
- package/build-module/dashboard-grid/index.mjs +579 -0
- package/build-module/dashboard-grid/index.mjs.map +7 -0
- package/build-module/dashboard-grid/resolve-fill-widths.mjs +164 -0
- package/build-module/dashboard-grid/resolve-fill-widths.mjs.map +7 -0
- package/build-module/dashboard-grid/types.mjs +1 -0
- package/build-module/dashboard-grid/types.mjs.map +7 -0
- package/build-module/dashboard-lanes/index.mjs +547 -0
- package/build-module/dashboard-lanes/index.mjs.map +7 -0
- package/build-module/dashboard-lanes/lane-placement.mjs +85 -0
- package/build-module/dashboard-lanes/lane-placement.mjs.map +7 -0
- package/build-module/dashboard-lanes/lanes-item.mjs +260 -0
- package/build-module/dashboard-lanes/lanes-item.mjs.map +7 -0
- package/build-module/dashboard-lanes/types.mjs +1 -0
- package/build-module/dashboard-lanes/types.mjs.map +7 -0
- package/build-module/dashboard-lanes/use-lane-placement.mjs +181 -0
- package/build-module/dashboard-lanes/use-lane-placement.mjs.map +7 -0
- package/build-module/index.mjs +8 -0
- package/build-module/index.mjs.map +7 -0
- package/build-module/shared/drag-overlay-drop-animation.mjs +47 -0
- package/build-module/shared/drag-overlay-drop-animation.mjs.map +7 -0
- package/build-module/shared/grid-item-key.mjs +6 -0
- package/build-module/shared/grid-item-key.mjs.map +7 -0
- package/build-module/shared/grid-overlay.mjs +152 -0
- package/build-module/shared/grid-overlay.mjs.map +7 -0
- package/build-module/shared/item-exit-overlay.mjs +125 -0
- package/build-module/shared/item-exit-overlay.mjs.map +7 -0
- package/build-module/shared/resize-handle.mjs +193 -0
- package/build-module/shared/resize-handle.mjs.map +7 -0
- package/build-module/shared/resize-snap.mjs +21 -0
- package/build-module/shared/resize-snap.mjs.map +7 -0
- package/build-module/shared/types.mjs +1 -0
- package/build-module/shared/types.mjs.map +7 -0
- package/build-module/shared/use-item-exit-animation.mjs +128 -0
- package/build-module/shared/use-item-exit-animation.mjs.map +7 -0
- package/build-module/shared/use-layout-shift-animation.mjs +140 -0
- package/build-module/shared/use-layout-shift-animation.mjs.map +7 -0
- package/build-types/dashboard-grid/grid-item.d.ts +3 -0
- package/build-types/dashboard-grid/grid-item.d.ts.map +1 -0
- package/build-types/dashboard-grid/index.d.ts +35 -0
- package/build-types/dashboard-grid/index.d.ts.map +1 -0
- package/build-types/dashboard-grid/resolve-fill-widths.d.ts +26 -0
- package/build-types/dashboard-grid/resolve-fill-widths.d.ts.map +1 -0
- package/build-types/dashboard-grid/stories/index.story.d.ts +98 -0
- package/build-types/dashboard-grid/stories/index.story.d.ts.map +1 -0
- package/build-types/dashboard-grid/types.d.ts +232 -0
- package/build-types/dashboard-grid/types.d.ts.map +1 -0
- package/build-types/dashboard-lanes/index.d.ts +40 -0
- package/build-types/dashboard-lanes/index.d.ts.map +1 -0
- package/build-types/dashboard-lanes/lane-placement.d.ts +126 -0
- package/build-types/dashboard-lanes/lane-placement.d.ts.map +1 -0
- package/build-types/dashboard-lanes/lanes-item.d.ts +52 -0
- package/build-types/dashboard-lanes/lanes-item.d.ts.map +1 -0
- package/build-types/dashboard-lanes/stories/index.story.d.ts +64 -0
- package/build-types/dashboard-lanes/stories/index.story.d.ts.map +1 -0
- package/build-types/dashboard-lanes/types.d.ts +151 -0
- package/build-types/dashboard-lanes/types.d.ts.map +1 -0
- package/build-types/dashboard-lanes/use-lane-placement.d.ts +74 -0
- package/build-types/dashboard-lanes/use-lane-placement.d.ts.map +1 -0
- package/build-types/index.d.ts +6 -0
- package/build-types/index.d.ts.map +1 -0
- package/build-types/shared/drag-overlay-drop-animation.d.ts +13 -0
- package/build-types/shared/drag-overlay-drop-animation.d.ts.map +1 -0
- package/build-types/shared/grid-item-key.d.ts +6 -0
- package/build-types/shared/grid-item-key.d.ts.map +1 -0
- package/build-types/shared/grid-overlay.d.ts +19 -0
- package/build-types/shared/grid-overlay.d.ts.map +1 -0
- package/build-types/shared/item-exit-overlay.d.ts +20 -0
- package/build-types/shared/item-exit-overlay.d.ts.map +1 -0
- package/build-types/shared/resize-handle.d.ts +23 -0
- package/build-types/shared/resize-handle.d.ts.map +1 -0
- package/build-types/shared/resize-snap.d.ts +41 -0
- package/build-types/shared/resize-snap.d.ts.map +1 -0
- package/build-types/shared/types.d.ts +144 -0
- package/build-types/shared/types.d.ts.map +1 -0
- package/build-types/shared/use-item-exit-animation.d.ts +37 -0
- package/build-types/shared/use-item-exit-animation.d.ts.map +1 -0
- package/build-types/shared/use-layout-shift-animation.d.ts +77 -0
- package/build-types/shared/use-layout-shift-animation.d.ts.map +1 -0
- package/package.json +80 -0
- package/src/dashboard-grid/grid-item.module.css +94 -0
- package/src/dashboard-grid/grid-item.tsx +205 -0
- package/src/dashboard-grid/grid.module.css +134 -0
- package/src/dashboard-grid/index.tsx +713 -0
- package/src/dashboard-grid/resolve-fill-widths.ts +224 -0
- package/src/dashboard-grid/stories/index.story.tsx +930 -0
- package/src/dashboard-grid/test/keyboard-activation.test.tsx +76 -0
- package/src/dashboard-grid/test/resolve-fill-widths.test.ts +250 -0
- package/src/dashboard-grid/types.ts +271 -0
- package/src/dashboard-lanes/index.tsx +629 -0
- package/src/dashboard-lanes/lane-placement.ts +245 -0
- package/src/dashboard-lanes/lanes-item.module.css +93 -0
- package/src/dashboard-lanes/lanes-item.tsx +236 -0
- package/src/dashboard-lanes/lanes.module.css +152 -0
- package/src/dashboard-lanes/stories/index.story.tsx +518 -0
- package/src/dashboard-lanes/test/keyboard-activation.test.tsx +71 -0
- package/src/dashboard-lanes/test/lane-placement.test.ts +442 -0
- package/src/dashboard-lanes/test/use-lane-placement.test.tsx +358 -0
- package/src/dashboard-lanes/types.ts +176 -0
- package/src/dashboard-lanes/use-lane-placement.ts +313 -0
- package/src/index.ts +17 -0
- package/src/shared/actionable-area-slot.module.css +16 -0
- package/src/shared/drag-overlay-drop-animation.ts +66 -0
- package/src/shared/grid-item-key.ts +5 -0
- package/src/shared/grid-overlay.module.css +82 -0
- package/src/shared/grid-overlay.tsx +93 -0
- package/src/shared/item-exit-animation.module.css +49 -0
- package/src/shared/item-exit-overlay.tsx +57 -0
- package/src/shared/layout-shift-animation.module.css +16 -0
- package/src/shared/resize-handle.module.css +88 -0
- package/src/shared/resize-handle.tsx +163 -0
- package/src/shared/resize-snap.ts +63 -0
- package/src/shared/test/resize-snap.test.ts +35 -0
- package/src/shared/types.ts +164 -0
- package/src/shared/use-item-exit-animation.ts +199 -0
- 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>
|