@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
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* WordPress dependencies
|
|
8
|
+
*/
|
|
9
|
+
import { useState, useMemo } from '@wordpress/element';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Internal dependencies
|
|
13
|
+
*/
|
|
14
|
+
import { DashboardLanes } from '..';
|
|
15
|
+
import type { DashboardLanesLayoutItem } from '../types';
|
|
16
|
+
import type { GridOverlayRenderProps } from '../../shared/types';
|
|
17
|
+
|
|
18
|
+
const meta: Meta< typeof DashboardLanes > = {
|
|
19
|
+
title: 'Grid/DashboardLanes',
|
|
20
|
+
component: DashboardLanes,
|
|
21
|
+
tags: [ 'status-experimental' ],
|
|
22
|
+
args: {
|
|
23
|
+
columns: 4,
|
|
24
|
+
flowTolerance: 16,
|
|
25
|
+
rowUnit: 4,
|
|
26
|
+
editMode: false,
|
|
27
|
+
},
|
|
28
|
+
argTypes: {
|
|
29
|
+
children: { control: false },
|
|
30
|
+
columns: {
|
|
31
|
+
control: { type: 'number', min: 1, max: 12, step: 1 },
|
|
32
|
+
description: 'Total lanes in fixed mode.',
|
|
33
|
+
},
|
|
34
|
+
minColumnWidth: {
|
|
35
|
+
control: { type: 'number', min: 80, max: 600, step: 8 },
|
|
36
|
+
description:
|
|
37
|
+
'Enables responsive mode. Per-lane lower bound in pixels.',
|
|
38
|
+
},
|
|
39
|
+
flowTolerance: {
|
|
40
|
+
control: { type: 'number', min: 0, max: 64, step: 1 },
|
|
41
|
+
description:
|
|
42
|
+
'Pixel tolerance for source-order tiebreaking when two lanes have similar baselines.',
|
|
43
|
+
},
|
|
44
|
+
rowUnit: {
|
|
45
|
+
control: { type: 'number', min: 1, max: 16, step: 1 },
|
|
46
|
+
description:
|
|
47
|
+
'Polyfill snap unit (px). Ignored on browsers with native `display: grid-lanes` support.',
|
|
48
|
+
},
|
|
49
|
+
editMode: {
|
|
50
|
+
control: { type: 'boolean' },
|
|
51
|
+
description: 'Enables drag-to-reorder and horizontal resize.',
|
|
52
|
+
},
|
|
53
|
+
onChangeLayout: { action: 'onChangeLayout' },
|
|
54
|
+
onPreviewLayout: { action: 'onPreviewLayout' },
|
|
55
|
+
},
|
|
56
|
+
parameters: {
|
|
57
|
+
componentStatus: {
|
|
58
|
+
status: 'use-with-caution',
|
|
59
|
+
whereUsed: 'global',
|
|
60
|
+
notes: 'This package is under heavy development and likely to change.',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
export default meta;
|
|
65
|
+
|
|
66
|
+
type Story = StoryObj< typeof DashboardLanes >;
|
|
67
|
+
|
|
68
|
+
type Tone = 'brand' | 'info' | 'success' | 'warning' | 'error' | 'neutral';
|
|
69
|
+
|
|
70
|
+
const bgTokens: Record< Tone, string > = {
|
|
71
|
+
brand: 'var(--wpds-color-background-surface-brand)',
|
|
72
|
+
info: 'var(--wpds-color-background-surface-info)',
|
|
73
|
+
success: 'var(--wpds-color-background-surface-success)',
|
|
74
|
+
warning: 'var(--wpds-color-background-surface-warning)',
|
|
75
|
+
error: 'var(--wpds-color-background-surface-error)',
|
|
76
|
+
neutral: 'var(--wpds-color-background-surface-neutral-weak)',
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const fgTokens: Record< Tone, string > = {
|
|
80
|
+
// `brand` has no dedicated fg-content token in the design system,
|
|
81
|
+
// so neutral content reads safely against the brand surface tint.
|
|
82
|
+
brand: 'var(--wpds-color-foreground-content-neutral)',
|
|
83
|
+
info: 'var(--wpds-color-foreground-content-info)',
|
|
84
|
+
success: 'var(--wpds-color-foreground-content-success)',
|
|
85
|
+
warning: 'var(--wpds-color-foreground-content-warning)',
|
|
86
|
+
error: 'var(--wpds-color-foreground-content-error)',
|
|
87
|
+
neutral: 'var(--wpds-color-foreground-content-neutral)',
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
function Tile( {
|
|
91
|
+
tone,
|
|
92
|
+
height,
|
|
93
|
+
index,
|
|
94
|
+
children,
|
|
95
|
+
...props
|
|
96
|
+
}: {
|
|
97
|
+
tone: Tone;
|
|
98
|
+
height: number;
|
|
99
|
+
index?: number;
|
|
100
|
+
children?: React.ReactNode;
|
|
101
|
+
} & React.HTMLAttributes< HTMLDivElement > ) {
|
|
102
|
+
return (
|
|
103
|
+
<div
|
|
104
|
+
{ ...props }
|
|
105
|
+
style={ {
|
|
106
|
+
backgroundColor: bgTokens[ tone ],
|
|
107
|
+
color: fgTokens[ tone ],
|
|
108
|
+
padding: '12px 16px',
|
|
109
|
+
display: 'flex',
|
|
110
|
+
alignItems: 'flex-end',
|
|
111
|
+
justifyContent: 'center',
|
|
112
|
+
position: 'relative',
|
|
113
|
+
overflow: 'hidden',
|
|
114
|
+
height,
|
|
115
|
+
boxSizing: 'border-box',
|
|
116
|
+
fontFamily: 'var(--wpds-typography-font-family-body)',
|
|
117
|
+
fontSize: 'var(--wpds-typography-font-size-sm)',
|
|
118
|
+
borderRadius: 6,
|
|
119
|
+
...props?.style,
|
|
120
|
+
} }
|
|
121
|
+
>
|
|
122
|
+
{ index !== undefined && (
|
|
123
|
+
<span
|
|
124
|
+
aria-hidden
|
|
125
|
+
style={ {
|
|
126
|
+
position: 'absolute',
|
|
127
|
+
inset: 0,
|
|
128
|
+
display: 'flex',
|
|
129
|
+
alignItems: 'center',
|
|
130
|
+
justifyContent: 'center',
|
|
131
|
+
fontSize: '3rem',
|
|
132
|
+
fontWeight: 700,
|
|
133
|
+
opacity: 0.3,
|
|
134
|
+
pointerEvents: 'none',
|
|
135
|
+
userSelect: 'none',
|
|
136
|
+
} }
|
|
137
|
+
>
|
|
138
|
+
{ index }
|
|
139
|
+
</span>
|
|
140
|
+
) }
|
|
141
|
+
{ children && (
|
|
142
|
+
<span style={ { position: 'relative' } }>{ children }</span>
|
|
143
|
+
) }
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Mixed-height tiles in a fixed lane count. Items pack from
|
|
150
|
+
* left-to-right by source order, falling into whichever lane has
|
|
151
|
+
* the lowest baseline at the moment of placement.
|
|
152
|
+
*/
|
|
153
|
+
export const Default: Story = {
|
|
154
|
+
args: {
|
|
155
|
+
columns: 4,
|
|
156
|
+
layout: [
|
|
157
|
+
{ key: 'a' },
|
|
158
|
+
{ key: 'b' },
|
|
159
|
+
{ key: 'c' },
|
|
160
|
+
{ key: 'd' },
|
|
161
|
+
{ key: 'e' },
|
|
162
|
+
{ key: 'f' },
|
|
163
|
+
{ key: 'g' },
|
|
164
|
+
{ key: 'h' },
|
|
165
|
+
],
|
|
166
|
+
children: [
|
|
167
|
+
<Tile key="a" tone="brand" height={ 120 } index={ 1 }>
|
|
168
|
+
120px
|
|
169
|
+
</Tile>,
|
|
170
|
+
<Tile key="b" tone="info" height={ 200 } index={ 2 }>
|
|
171
|
+
200px
|
|
172
|
+
</Tile>,
|
|
173
|
+
<Tile key="c" tone="success" height={ 80 } index={ 3 }>
|
|
174
|
+
80px
|
|
175
|
+
</Tile>,
|
|
176
|
+
<Tile key="d" tone="warning" height={ 160 } index={ 4 }>
|
|
177
|
+
160px
|
|
178
|
+
</Tile>,
|
|
179
|
+
<Tile key="e" tone="error" height={ 100 } index={ 5 }>
|
|
180
|
+
100px
|
|
181
|
+
</Tile>,
|
|
182
|
+
<Tile key="f" tone="neutral" height={ 240 } index={ 6 }>
|
|
183
|
+
240px
|
|
184
|
+
</Tile>,
|
|
185
|
+
<Tile key="g" tone="brand" height={ 140 } index={ 7 }>
|
|
186
|
+
140px
|
|
187
|
+
</Tile>,
|
|
188
|
+
<Tile key="h" tone="info" height={ 90 } index={ 8 }>
|
|
189
|
+
90px
|
|
190
|
+
</Tile>,
|
|
191
|
+
],
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Responsive lane count: derived from container width using
|
|
197
|
+
* `minColumnWidth` as the per-lane lower bound. Resize the preview
|
|
198
|
+
* frame to see the lane count adapt.
|
|
199
|
+
*/
|
|
200
|
+
export const Responsive: Story = {
|
|
201
|
+
args: {
|
|
202
|
+
minColumnWidth: 200,
|
|
203
|
+
layout: [
|
|
204
|
+
{ key: 'a' },
|
|
205
|
+
{ key: 'b' },
|
|
206
|
+
{ key: 'c' },
|
|
207
|
+
{ key: 'd' },
|
|
208
|
+
{ key: 'e' },
|
|
209
|
+
{ key: 'f' },
|
|
210
|
+
],
|
|
211
|
+
children: [
|
|
212
|
+
<Tile key="a" tone="brand" height={ 120 } index={ 1 } />,
|
|
213
|
+
<Tile key="b" tone="info" height={ 200 } index={ 2 } />,
|
|
214
|
+
<Tile key="c" tone="success" height={ 80 } index={ 3 } />,
|
|
215
|
+
<Tile key="d" tone="warning" height={ 160 } index={ 4 } />,
|
|
216
|
+
<Tile key="e" tone="error" height={ 100 } index={ 5 } />,
|
|
217
|
+
<Tile key="f" tone="neutral" height={ 240 } index={ 6 } />,
|
|
218
|
+
],
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Layered configuration: `columns` caps the lane count and
|
|
224
|
+
* `minColumnWidth` enforces a per-tile width floor. The surface
|
|
225
|
+
* renders up to `columns` lanes on wide containers and reduces the
|
|
226
|
+
* count on narrow ones whenever fitting all of them would push
|
|
227
|
+
* tiles below `minColumnWidth`.
|
|
228
|
+
*/
|
|
229
|
+
export const Layered: Story = {
|
|
230
|
+
args: {
|
|
231
|
+
columns: 4,
|
|
232
|
+
minColumnWidth: 200,
|
|
233
|
+
layout: [
|
|
234
|
+
{ key: 'a' },
|
|
235
|
+
{ key: 'b' },
|
|
236
|
+
{ key: 'c' },
|
|
237
|
+
{ key: 'd' },
|
|
238
|
+
{ key: 'e' },
|
|
239
|
+
{ key: 'f' },
|
|
240
|
+
],
|
|
241
|
+
children: [
|
|
242
|
+
<Tile key="a" tone="brand" height={ 120 } index={ 1 } />,
|
|
243
|
+
<Tile key="b" tone="info" height={ 200 } index={ 2 } />,
|
|
244
|
+
<Tile key="c" tone="success" height={ 80 } index={ 3 } />,
|
|
245
|
+
<Tile key="d" tone="warning" height={ 160 } index={ 4 } />,
|
|
246
|
+
<Tile key="e" tone="error" height={ 100 } index={ 5 } />,
|
|
247
|
+
<Tile key="f" tone="neutral" height={ 240 } index={ 6 } />,
|
|
248
|
+
],
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Items with `width: 2` span two lanes. The skyline picks a span
|
|
254
|
+
* position that minimizes the resulting baseline across spanned
|
|
255
|
+
* lanes.
|
|
256
|
+
*/
|
|
257
|
+
export const Spanning: Story = {
|
|
258
|
+
args: {
|
|
259
|
+
columns: 4,
|
|
260
|
+
layout: [
|
|
261
|
+
{ key: 'a' },
|
|
262
|
+
{ key: 'wide', width: 2 },
|
|
263
|
+
{ key: 'b' },
|
|
264
|
+
{ key: 'c' },
|
|
265
|
+
{ key: 'd' },
|
|
266
|
+
{ key: 'taller-wide', width: 2 },
|
|
267
|
+
{ key: 'e' },
|
|
268
|
+
],
|
|
269
|
+
children: [
|
|
270
|
+
<Tile key="a" tone="brand" height={ 120 } index={ 1 } />,
|
|
271
|
+
<Tile key="wide" tone="info" height={ 100 } index={ 2 }>
|
|
272
|
+
span 2
|
|
273
|
+
</Tile>,
|
|
274
|
+
<Tile key="b" tone="success" height={ 80 } index={ 3 } />,
|
|
275
|
+
<Tile key="c" tone="warning" height={ 200 } index={ 4 } />,
|
|
276
|
+
<Tile key="d" tone="error" height={ 90 } index={ 5 } />,
|
|
277
|
+
<Tile key="taller-wide" tone="neutral" height={ 160 } index={ 6 }>
|
|
278
|
+
span 2
|
|
279
|
+
</Tile>,
|
|
280
|
+
<Tile key="e" tone="brand" height={ 110 } index={ 7 } />,
|
|
281
|
+
],
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Edit mode: drag to reorder, resize from the bottom-right corner
|
|
287
|
+
* (horizontal only — heights are content-driven). Drop commits the
|
|
288
|
+
* new layout via `onChangeLayout`.
|
|
289
|
+
*
|
|
290
|
+
* While `editMode` is on, `<DashboardLanes />` paints its default
|
|
291
|
+
* overlay behind the tiles to mark the lane tracks. Lanes paint
|
|
292
|
+
* columns only — there are no row markers because heights are
|
|
293
|
+
* content-driven.
|
|
294
|
+
*
|
|
295
|
+
* Theme the default look in place via `--wp-grid-overlay-tile-bg`,
|
|
296
|
+
* or replace the visual wholesale
|
|
297
|
+
* by passing `renderGridOverlay`. See the `Custom Grid Overlay`
|
|
298
|
+
* story below for a full override example.
|
|
299
|
+
*/
|
|
300
|
+
export const EditMode: Story = {
|
|
301
|
+
args: {
|
|
302
|
+
columns: 4,
|
|
303
|
+
editMode: true,
|
|
304
|
+
},
|
|
305
|
+
render: function EditModeStory( args ) {
|
|
306
|
+
const initial: ( DashboardLanesLayoutItem & {
|
|
307
|
+
tone: Tone;
|
|
308
|
+
height: number;
|
|
309
|
+
label: string;
|
|
310
|
+
} )[] = [
|
|
311
|
+
{ key: 'a', tone: 'brand', height: 120, label: '120px' },
|
|
312
|
+
{ key: 'b', tone: 'info', height: 200, label: '200px' },
|
|
313
|
+
{
|
|
314
|
+
key: 'wide',
|
|
315
|
+
width: 2,
|
|
316
|
+
tone: 'success',
|
|
317
|
+
height: 100,
|
|
318
|
+
label: 'span 2',
|
|
319
|
+
},
|
|
320
|
+
{ key: 'c', tone: 'warning', height: 160, label: '160px' },
|
|
321
|
+
{ key: 'd', tone: 'error', height: 90, label: '90px' },
|
|
322
|
+
{ key: 'e', tone: 'neutral', height: 240, label: '240px' },
|
|
323
|
+
{ key: 'f', tone: 'brand', height: 140, label: '140px' },
|
|
324
|
+
];
|
|
325
|
+
|
|
326
|
+
const [ tiles, setTiles ] = useState( initial );
|
|
327
|
+
|
|
328
|
+
const layout: DashboardLanesLayoutItem[] = tiles.map(
|
|
329
|
+
( { tone: _tone, height: _height, label: _label, ...item } ) => item
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
const onChangeLayout = ( next: DashboardLanesLayoutItem[] ) => {
|
|
333
|
+
setTiles(
|
|
334
|
+
next.map( ( item ) => {
|
|
335
|
+
const existing = tiles.find( ( t ) => t.key === item.key );
|
|
336
|
+
return {
|
|
337
|
+
...item,
|
|
338
|
+
tone: existing?.tone ?? 'neutral',
|
|
339
|
+
height: existing?.height ?? 100,
|
|
340
|
+
label: existing?.label ?? item.key,
|
|
341
|
+
};
|
|
342
|
+
} )
|
|
343
|
+
);
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const tileElements = useMemo(
|
|
347
|
+
() =>
|
|
348
|
+
tiles.map( ( tile, i ) => (
|
|
349
|
+
<Tile
|
|
350
|
+
key={ tile.key }
|
|
351
|
+
tone={ tile.tone }
|
|
352
|
+
height={ tile.height }
|
|
353
|
+
index={ i + 1 }
|
|
354
|
+
>
|
|
355
|
+
{ tile.label }
|
|
356
|
+
</Tile>
|
|
357
|
+
) ),
|
|
358
|
+
[ tiles ]
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
return (
|
|
362
|
+
<DashboardLanes
|
|
363
|
+
{ ...args }
|
|
364
|
+
layout={ layout }
|
|
365
|
+
onChangeLayout={ onChangeLayout }
|
|
366
|
+
>
|
|
367
|
+
{ tileElements }
|
|
368
|
+
</DashboardLanes>
|
|
369
|
+
);
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Example custom overlay supplied to `<DashboardLanes />` through the
|
|
375
|
+
* `renderGridOverlay` prop. Receives `{ columns, isActive }` from the
|
|
376
|
+
* surface (no `rowHeight` because lane heights are content-driven).
|
|
377
|
+
* The custom must honor `isActive` for the same cross-fade behavior
|
|
378
|
+
* as the default; the surface always mounts the overlay.
|
|
379
|
+
*
|
|
380
|
+
* @param props Render props supplied by the surface.
|
|
381
|
+
* @param props.columns Number of lane tracks to mirror.
|
|
382
|
+
* @param props.isActive Whether the overlay should be visible.
|
|
383
|
+
*/
|
|
384
|
+
function NumberedLanesOverlay( { columns, isActive }: GridOverlayRenderProps ) {
|
|
385
|
+
return (
|
|
386
|
+
<div
|
|
387
|
+
aria-hidden
|
|
388
|
+
style={ {
|
|
389
|
+
position: 'absolute',
|
|
390
|
+
inset: 0,
|
|
391
|
+
display: 'grid',
|
|
392
|
+
gridTemplateColumns: `repeat(${ columns }, minmax(0, 1fr))`,
|
|
393
|
+
gap: 'var(--wpds-dimension-gap-xl)',
|
|
394
|
+
pointerEvents: 'none',
|
|
395
|
+
opacity: isActive ? 1 : 0,
|
|
396
|
+
visibility: isActive ? 'visible' : 'hidden',
|
|
397
|
+
transition: isActive
|
|
398
|
+
? 'opacity 200ms ease, visibility 0s linear 0s'
|
|
399
|
+
: 'opacity 200ms ease, visibility 0s linear 200ms',
|
|
400
|
+
backgroundImage: `repeating-linear-gradient(135deg, color-mix(in srgb, var(--wpds-color-background-surface-info) 24%, transparent) 0 6px, transparent 6px 12px)`,
|
|
401
|
+
} }
|
|
402
|
+
>
|
|
403
|
+
{ Array.from( { length: columns } ).map( ( _, i ) => (
|
|
404
|
+
<div
|
|
405
|
+
key={ i }
|
|
406
|
+
style={ {
|
|
407
|
+
outline:
|
|
408
|
+
'1px dashed var(--wpds-color-stroke-surface-info)',
|
|
409
|
+
backgroundColor:
|
|
410
|
+
'color-mix(in srgb, var(--wpds-color-background-surface-info) 10%, transparent)',
|
|
411
|
+
position: 'relative',
|
|
412
|
+
} }
|
|
413
|
+
>
|
|
414
|
+
<span
|
|
415
|
+
style={ {
|
|
416
|
+
position: 'absolute',
|
|
417
|
+
top: 4,
|
|
418
|
+
insetInlineStart: 4,
|
|
419
|
+
fontSize: 10,
|
|
420
|
+
padding: '1px 6px',
|
|
421
|
+
borderRadius: 2,
|
|
422
|
+
background:
|
|
423
|
+
'var(--wpds-color-background-surface-info)',
|
|
424
|
+
color: 'var(--wpds-color-foreground-content-info)',
|
|
425
|
+
fontFamily:
|
|
426
|
+
'var(--wpds-typography-font-family-mono)',
|
|
427
|
+
} }
|
|
428
|
+
>
|
|
429
|
+
{ i + 1 }
|
|
430
|
+
</span>
|
|
431
|
+
</div>
|
|
432
|
+
) ) }
|
|
433
|
+
</div>
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Replaces the surface's default edit-mode overlay with a custom
|
|
439
|
+
* visual through the `renderGridOverlay` prop. The same contract as
|
|
440
|
+
* `<DashboardGrid />`'s override path, with `rowHeight` omitted from
|
|
441
|
+
* the render props because lanes are content-driven vertically.
|
|
442
|
+
*
|
|
443
|
+
* Pass `renderGridOverlay={ () => null }` to suppress the overlay
|
|
444
|
+
* entirely while keeping `editMode` interactions on.
|
|
445
|
+
*/
|
|
446
|
+
export const CustomGridOverlayStory: Story = {
|
|
447
|
+
name: 'Custom Grid Overlay',
|
|
448
|
+
args: {
|
|
449
|
+
columns: 4,
|
|
450
|
+
editMode: true,
|
|
451
|
+
},
|
|
452
|
+
render: function CustomGridOverlayRender( args ) {
|
|
453
|
+
const initial: ( DashboardLanesLayoutItem & {
|
|
454
|
+
tone: Tone;
|
|
455
|
+
height: number;
|
|
456
|
+
label: string;
|
|
457
|
+
} )[] = [
|
|
458
|
+
{ key: 'a', tone: 'brand', height: 140, label: '140px' },
|
|
459
|
+
{ key: 'b', tone: 'info', height: 200, label: '200px' },
|
|
460
|
+
{
|
|
461
|
+
key: 'wide',
|
|
462
|
+
width: 2,
|
|
463
|
+
tone: 'success',
|
|
464
|
+
height: 120,
|
|
465
|
+
label: 'span 2',
|
|
466
|
+
},
|
|
467
|
+
{ key: 'c', tone: 'warning', height: 180, label: '180px' },
|
|
468
|
+
{ key: 'd', tone: 'error', height: 100, label: '100px' },
|
|
469
|
+
{ key: 'e', tone: 'neutral', height: 220, label: '220px' },
|
|
470
|
+
];
|
|
471
|
+
|
|
472
|
+
const [ tiles, setTiles ] = useState( initial );
|
|
473
|
+
|
|
474
|
+
const layout: DashboardLanesLayoutItem[] = tiles.map(
|
|
475
|
+
( { tone: _tone, height: _height, label: _label, ...item } ) => item
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
const onChangeLayout = ( next: DashboardLanesLayoutItem[] ) => {
|
|
479
|
+
setTiles(
|
|
480
|
+
next.map( ( item ) => {
|
|
481
|
+
const existing = tiles.find( ( t ) => t.key === item.key );
|
|
482
|
+
return {
|
|
483
|
+
...item,
|
|
484
|
+
tone: existing?.tone ?? 'neutral',
|
|
485
|
+
height: existing?.height ?? 100,
|
|
486
|
+
label: existing?.label ?? item.key,
|
|
487
|
+
};
|
|
488
|
+
} )
|
|
489
|
+
);
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
const tileElements = useMemo(
|
|
493
|
+
() =>
|
|
494
|
+
tiles.map( ( tile, i ) => (
|
|
495
|
+
<Tile
|
|
496
|
+
key={ tile.key }
|
|
497
|
+
tone={ tile.tone }
|
|
498
|
+
height={ tile.height }
|
|
499
|
+
index={ i + 1 }
|
|
500
|
+
>
|
|
501
|
+
{ tile.label }
|
|
502
|
+
</Tile>
|
|
503
|
+
) ),
|
|
504
|
+
[ tiles ]
|
|
505
|
+
);
|
|
506
|
+
|
|
507
|
+
return (
|
|
508
|
+
<DashboardLanes
|
|
509
|
+
{ ...args }
|
|
510
|
+
layout={ layout }
|
|
511
|
+
onChangeLayout={ onChangeLayout }
|
|
512
|
+
renderGridOverlay={ NumberedLanesOverlay }
|
|
513
|
+
>
|
|
514
|
+
{ tileElements }
|
|
515
|
+
</DashboardLanes>
|
|
516
|
+
);
|
|
517
|
+
},
|
|
518
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* External dependencies
|
|
7
|
+
*/
|
|
8
|
+
import { render } from '@testing-library/react';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Internal dependencies
|
|
12
|
+
*/
|
|
13
|
+
import { DashboardLanes } from '..';
|
|
14
|
+
|
|
15
|
+
class MockResizeObserver {
|
|
16
|
+
observed: Set< Element > = new Set();
|
|
17
|
+
observe( element: Element ) {
|
|
18
|
+
this.observed.add( element );
|
|
19
|
+
}
|
|
20
|
+
unobserve( element: Element ) {
|
|
21
|
+
this.observed.delete( element );
|
|
22
|
+
}
|
|
23
|
+
disconnect() {
|
|
24
|
+
this.observed.clear();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let originalResizeObserver: typeof ResizeObserver;
|
|
29
|
+
|
|
30
|
+
beforeEach( () => {
|
|
31
|
+
originalResizeObserver = global.ResizeObserver;
|
|
32
|
+
( global as unknown as { ResizeObserver: unknown } ).ResizeObserver =
|
|
33
|
+
MockResizeObserver;
|
|
34
|
+
} );
|
|
35
|
+
|
|
36
|
+
afterEach( () => {
|
|
37
|
+
( global as unknown as { ResizeObserver: unknown } ).ResizeObserver =
|
|
38
|
+
originalResizeObserver;
|
|
39
|
+
} );
|
|
40
|
+
|
|
41
|
+
describe( 'DashboardLanes keyboard activation', () => {
|
|
42
|
+
it( 'places the dnd-kit keyboard activator on the inner wrapper, not the outer item', () => {
|
|
43
|
+
// Verifies the DOM hierarchy: keyboard activation needs the
|
|
44
|
+
// focused node and the keydown listener to share a node, so
|
|
45
|
+
// the activator must live nested inside the outer item.
|
|
46
|
+
/* eslint-disable testing-library/no-container, testing-library/no-node-access */
|
|
47
|
+
const { container } = render(
|
|
48
|
+
<DashboardLanes layout={ [ { key: 'a' } ] } columns={ 2 } editMode>
|
|
49
|
+
<div key="a">A</div>
|
|
50
|
+
</DashboardLanes>
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Edit mode also renders a resize handle with `role="button"`;
|
|
54
|
+
// `aria-roledescription="sortable"` isolates the activator.
|
|
55
|
+
const activator = container.querySelector(
|
|
56
|
+
'[role="button"][aria-roledescription="sortable"]'
|
|
57
|
+
);
|
|
58
|
+
expect( activator ).not.toBeNull();
|
|
59
|
+
expect( activator ).toHaveAttribute( 'tabindex', '0' );
|
|
60
|
+
|
|
61
|
+
// Outer item is identified by `data-wp-grid-item-key`; the activator
|
|
62
|
+
// must be its descendant.
|
|
63
|
+
const lanesItem = container.querySelector(
|
|
64
|
+
'[data-wp-grid-item-key="a"]'
|
|
65
|
+
);
|
|
66
|
+
expect( lanesItem ).not.toBeNull();
|
|
67
|
+
expect( activator ).not.toBe( lanesItem );
|
|
68
|
+
expect( lanesItem!.contains( activator! ) ).toBe( true );
|
|
69
|
+
/* eslint-enable testing-library/no-container, testing-library/no-node-access */
|
|
70
|
+
} );
|
|
71
|
+
} );
|