@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.
- 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,930 @@
|
|
|
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
|
+
import { close, justifyStretch, stretchFullWidth } from '@wordpress/icons';
|
|
11
|
+
// eslint-disable-next-line @wordpress/use-recommended-components -- @wordpress/grid consumes @wordpress/ui in story examples only.
|
|
12
|
+
import { Icon, IconButton, Stack } from '@wordpress/ui';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Internal dependencies
|
|
16
|
+
*/
|
|
17
|
+
import { DashboardGrid } from '..';
|
|
18
|
+
import type { DashboardGridLayoutItem } from '../types';
|
|
19
|
+
import type {
|
|
20
|
+
DragPreviewRenderProps,
|
|
21
|
+
GridOverlayRenderProps,
|
|
22
|
+
ResizeHandleRenderProps,
|
|
23
|
+
} from '../../shared/types';
|
|
24
|
+
|
|
25
|
+
const meta: Meta< typeof DashboardGrid > = {
|
|
26
|
+
title: 'Grid/DashboardGrid',
|
|
27
|
+
component: DashboardGrid,
|
|
28
|
+
tags: [ 'status-experimental' ],
|
|
29
|
+
args: {
|
|
30
|
+
columns: 6,
|
|
31
|
+
rowHeight: 80,
|
|
32
|
+
editMode: false,
|
|
33
|
+
},
|
|
34
|
+
argTypes: {
|
|
35
|
+
children: { control: false },
|
|
36
|
+
columns: {
|
|
37
|
+
control: { type: 'number', min: 1, max: 24, step: 1 },
|
|
38
|
+
description: 'Total columns in fixed mode.',
|
|
39
|
+
},
|
|
40
|
+
minColumnWidth: {
|
|
41
|
+
control: { type: 'number', min: 80, max: 600, step: 8 },
|
|
42
|
+
description:
|
|
43
|
+
'Enables responsive mode. Per-column lower bound in pixels.',
|
|
44
|
+
},
|
|
45
|
+
rowHeight: {
|
|
46
|
+
control: { type: 'number', min: 24, max: 400, step: 4 },
|
|
47
|
+
description: 'Row height in pixels, or `auto`.',
|
|
48
|
+
},
|
|
49
|
+
editMode: {
|
|
50
|
+
control: { type: 'boolean' },
|
|
51
|
+
description: 'Enables drag-to-reorder and resize.',
|
|
52
|
+
},
|
|
53
|
+
className: { control: { type: 'text' } },
|
|
54
|
+
onChangeLayout: { action: 'onChangeLayout' },
|
|
55
|
+
onPreviewLayout: { action: 'onPreviewLayout' },
|
|
56
|
+
},
|
|
57
|
+
parameters: {
|
|
58
|
+
componentStatus: {
|
|
59
|
+
status: 'use-with-caution',
|
|
60
|
+
whereUsed: 'global',
|
|
61
|
+
notes: 'This package is under heavy development and likely to change.',
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
export default meta;
|
|
66
|
+
|
|
67
|
+
type Story = StoryObj< typeof DashboardGrid >;
|
|
68
|
+
|
|
69
|
+
type Tone = 'brand' | 'info' | 'success' | 'warning' | 'error' | 'neutral';
|
|
70
|
+
|
|
71
|
+
// Static token maps so the build-time token fallback plugin can inject
|
|
72
|
+
// fallbacks into each `var()` call. Using literal strings keeps the
|
|
73
|
+
// `@wordpress/no-unknown-ds-tokens` lint rule happy.
|
|
74
|
+
const bgTokens: Record< Tone, string > = {
|
|
75
|
+
brand: 'var(--wpds-color-background-surface-brand)',
|
|
76
|
+
info: 'var(--wpds-color-background-surface-info)',
|
|
77
|
+
success: 'var(--wpds-color-background-surface-success)',
|
|
78
|
+
warning: 'var(--wpds-color-background-surface-warning)',
|
|
79
|
+
error: 'var(--wpds-color-background-surface-error)',
|
|
80
|
+
neutral: 'var(--wpds-color-background-surface-neutral-weak)',
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const fgTokens: Record< Tone, string > = {
|
|
84
|
+
// `brand` has no dedicated fg-content token in the design system,
|
|
85
|
+
// so neutral content reads safely against the brand surface tint.
|
|
86
|
+
brand: 'var(--wpds-color-foreground-content-neutral)',
|
|
87
|
+
info: 'var(--wpds-color-foreground-content-info)',
|
|
88
|
+
success: 'var(--wpds-color-foreground-content-success)',
|
|
89
|
+
warning: 'var(--wpds-color-foreground-content-warning)',
|
|
90
|
+
error: 'var(--wpds-color-foreground-content-error)',
|
|
91
|
+
neutral: 'var(--wpds-color-foreground-content-neutral)',
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
function Tile( {
|
|
95
|
+
tone,
|
|
96
|
+
children,
|
|
97
|
+
actionableArea,
|
|
98
|
+
...props
|
|
99
|
+
}: {
|
|
100
|
+
tone: Tone;
|
|
101
|
+
children: React.ReactNode;
|
|
102
|
+
actionableArea?: React.ReactNode;
|
|
103
|
+
} & React.HTMLAttributes< HTMLDivElement > ) {
|
|
104
|
+
return (
|
|
105
|
+
<div
|
|
106
|
+
{ ...props }
|
|
107
|
+
style={ {
|
|
108
|
+
backgroundColor: bgTokens[ tone ],
|
|
109
|
+
color: fgTokens[ tone ],
|
|
110
|
+
padding: '20px',
|
|
111
|
+
display: 'flex',
|
|
112
|
+
alignItems: 'center',
|
|
113
|
+
justifyContent: 'center',
|
|
114
|
+
height: '100%',
|
|
115
|
+
boxSizing: 'border-box',
|
|
116
|
+
fontFamily: 'var(--wpds-typography-font-family-body)',
|
|
117
|
+
fontSize: 'var(--wpds-typography-font-size-sm)',
|
|
118
|
+
...props?.style,
|
|
119
|
+
} }
|
|
120
|
+
>
|
|
121
|
+
{ children }
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function TileActions( {
|
|
127
|
+
isFill,
|
|
128
|
+
isFull,
|
|
129
|
+
onToggleFill,
|
|
130
|
+
onToggleFull,
|
|
131
|
+
onRemove,
|
|
132
|
+
}: {
|
|
133
|
+
isFill: boolean;
|
|
134
|
+
isFull: boolean;
|
|
135
|
+
onToggleFill: () => void;
|
|
136
|
+
onToggleFull: () => void;
|
|
137
|
+
onRemove: () => void;
|
|
138
|
+
} ) {
|
|
139
|
+
return (
|
|
140
|
+
<div
|
|
141
|
+
style={ {
|
|
142
|
+
position: 'absolute',
|
|
143
|
+
display: 'flex',
|
|
144
|
+
gap: 4,
|
|
145
|
+
top: 4,
|
|
146
|
+
right: 4,
|
|
147
|
+
zIndex: 2,
|
|
148
|
+
} }
|
|
149
|
+
>
|
|
150
|
+
<IconButton
|
|
151
|
+
size="small"
|
|
152
|
+
variant="solid"
|
|
153
|
+
tone={ isFill ? 'brand' : 'neutral' }
|
|
154
|
+
icon={ justifyStretch }
|
|
155
|
+
label="Fill available width"
|
|
156
|
+
aria-pressed={ isFill }
|
|
157
|
+
onClick={ onToggleFill }
|
|
158
|
+
/>
|
|
159
|
+
|
|
160
|
+
<IconButton
|
|
161
|
+
size="small"
|
|
162
|
+
variant="solid"
|
|
163
|
+
tone={ isFull ? 'brand' : 'neutral' }
|
|
164
|
+
icon={ stretchFullWidth }
|
|
165
|
+
label="Full width"
|
|
166
|
+
aria-pressed={ isFull }
|
|
167
|
+
onClick={ onToggleFull }
|
|
168
|
+
/>
|
|
169
|
+
|
|
170
|
+
<IconButton
|
|
171
|
+
size="small"
|
|
172
|
+
variant="solid"
|
|
173
|
+
tone="neutral"
|
|
174
|
+
icon={ close }
|
|
175
|
+
label="Remove"
|
|
176
|
+
onClick={ onRemove }
|
|
177
|
+
/>
|
|
178
|
+
</div>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function formatTileLabel( item: DashboardGridLayoutItem ): string {
|
|
183
|
+
let width: string;
|
|
184
|
+
if ( item.width === 'fill' ) {
|
|
185
|
+
width = 'width: "fill"';
|
|
186
|
+
} else if ( item.width === 'full' ) {
|
|
187
|
+
width = 'width: "full"';
|
|
188
|
+
} else {
|
|
189
|
+
width = `width: ${ item.width ?? 1 }`;
|
|
190
|
+
}
|
|
191
|
+
const height = ( item.height ?? 1 ) > 1 ? `, height: ${ item.height }` : '';
|
|
192
|
+
return width + height;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Static token maps so the build-time token fallback plugin can inject
|
|
196
|
+
// fallbacks into each `var()` call.
|
|
197
|
+
const panelBgTokens: Record< 'warning' | 'success', string > = {
|
|
198
|
+
warning: 'var(--wpds-color-background-surface-warning)',
|
|
199
|
+
success: 'var(--wpds-color-background-surface-success)',
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const panelFgTokens: Record< 'warning' | 'success', string > = {
|
|
203
|
+
warning: 'var(--wpds-color-foreground-content-warning)',
|
|
204
|
+
success: 'var(--wpds-color-foreground-content-success)',
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const panelStrokeTokens: Record< 'warning' | 'success', string > = {
|
|
208
|
+
warning: 'var(--wpds-color-stroke-surface-warning)',
|
|
209
|
+
success: 'var(--wpds-color-stroke-surface-success)',
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
function LayoutStatePanel( {
|
|
213
|
+
label,
|
|
214
|
+
layout,
|
|
215
|
+
tone,
|
|
216
|
+
}: {
|
|
217
|
+
label: string;
|
|
218
|
+
layout: DashboardGridLayoutItem[];
|
|
219
|
+
tone: 'warning' | 'success';
|
|
220
|
+
} ) {
|
|
221
|
+
return (
|
|
222
|
+
<Stack
|
|
223
|
+
direction="column"
|
|
224
|
+
gap="sm"
|
|
225
|
+
style={ {
|
|
226
|
+
width: 280,
|
|
227
|
+
padding: 16,
|
|
228
|
+
background: panelBgTokens[ tone ],
|
|
229
|
+
border: `1px solid ${ panelStrokeTokens[ tone ] }`,
|
|
230
|
+
borderRadius: 8,
|
|
231
|
+
fontFamily: 'var(--wpds-typography-font-family-mono)',
|
|
232
|
+
fontSize: 12,
|
|
233
|
+
color: panelFgTokens[ tone ],
|
|
234
|
+
} }
|
|
235
|
+
>
|
|
236
|
+
<strong
|
|
237
|
+
style={ {
|
|
238
|
+
fontFamily: 'var(--wpds-typography-font-family-body)',
|
|
239
|
+
fontSize: 11,
|
|
240
|
+
textTransform: 'uppercase',
|
|
241
|
+
letterSpacing: '0.04em',
|
|
242
|
+
} }
|
|
243
|
+
>
|
|
244
|
+
{ label }
|
|
245
|
+
</strong>
|
|
246
|
+
<pre
|
|
247
|
+
style={ {
|
|
248
|
+
margin: 0,
|
|
249
|
+
overflow: 'auto',
|
|
250
|
+
lineHeight: 1.5,
|
|
251
|
+
} }
|
|
252
|
+
>
|
|
253
|
+
{ JSON.stringify( layout, null, 2 ) }
|
|
254
|
+
</pre>
|
|
255
|
+
</Stack>
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Static grid with a fixed number of columns. Each item declares its
|
|
261
|
+
* column span via `width`. Items flow left-to-right and wrap to new
|
|
262
|
+
* rows as the total exceeds `columns`.
|
|
263
|
+
*/
|
|
264
|
+
export const Default: Story = {
|
|
265
|
+
args: {
|
|
266
|
+
layout: [
|
|
267
|
+
{ key: 'a', width: 1 },
|
|
268
|
+
{ key: 'b', width: 3 },
|
|
269
|
+
{ key: 'c', width: 2 },
|
|
270
|
+
{ key: 'd', width: 4 },
|
|
271
|
+
{ key: 'e', width: 2 },
|
|
272
|
+
],
|
|
273
|
+
columns: 6,
|
|
274
|
+
children: [
|
|
275
|
+
<Tile key="a" tone="brand">
|
|
276
|
+
width: 1
|
|
277
|
+
</Tile>,
|
|
278
|
+
<Tile key="b" tone="info">
|
|
279
|
+
width: 3
|
|
280
|
+
</Tile>,
|
|
281
|
+
<Tile key="c" tone="success">
|
|
282
|
+
width: 2
|
|
283
|
+
</Tile>,
|
|
284
|
+
<Tile key="d" tone="warning">
|
|
285
|
+
width: 4
|
|
286
|
+
</Tile>,
|
|
287
|
+
<Tile key="e" tone="error">
|
|
288
|
+
width: 2
|
|
289
|
+
</Tile>,
|
|
290
|
+
],
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Responsive grid: the column count is derived from the container
|
|
296
|
+
* width using `minColumnWidth` as the lower bound per column. A
|
|
297
|
+
* `ResizeObserver` recomputes the count on container resize.
|
|
298
|
+
*/
|
|
299
|
+
export const Responsive: Story = {
|
|
300
|
+
args: {
|
|
301
|
+
layout: [
|
|
302
|
+
{ key: 'a', width: 1, order: 1 },
|
|
303
|
+
{ key: 'b', width: 2, order: 2 },
|
|
304
|
+
{ key: 'c', width: 2, order: 3 },
|
|
305
|
+
{ key: 'd', width: 1, order: 4 },
|
|
306
|
+
{ key: 'e', width: 2, order: 5 },
|
|
307
|
+
{ key: 'f', width: 2, order: 6 },
|
|
308
|
+
],
|
|
309
|
+
rowHeight: 96,
|
|
310
|
+
minColumnWidth: 192,
|
|
311
|
+
children: [
|
|
312
|
+
<Tile key="a" tone="brand">
|
|
313
|
+
width: 1
|
|
314
|
+
</Tile>,
|
|
315
|
+
<Tile key="b" tone="info">
|
|
316
|
+
width: 2
|
|
317
|
+
</Tile>,
|
|
318
|
+
<Tile key="c" tone="success">
|
|
319
|
+
width: 2
|
|
320
|
+
</Tile>,
|
|
321
|
+
<Tile key="d" tone="warning">
|
|
322
|
+
width: 1
|
|
323
|
+
</Tile>,
|
|
324
|
+
<Tile key="e" tone="error">
|
|
325
|
+
width: 2
|
|
326
|
+
</Tile>,
|
|
327
|
+
<Tile key="f" tone="neutral">
|
|
328
|
+
width: 2
|
|
329
|
+
</Tile>,
|
|
330
|
+
],
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Layered configuration: `columns` caps the count and
|
|
336
|
+
* `minColumnWidth` enforces a per-tile width floor. The grid renders
|
|
337
|
+
* up to `columns` columns on wide containers and reduces the count
|
|
338
|
+
* on narrow ones whenever fitting all of them would push tiles
|
|
339
|
+
* below `minColumnWidth`. Resize the preview to see the cap apply
|
|
340
|
+
* on wide widths and the floor reduce the count on narrow widths.
|
|
341
|
+
*/
|
|
342
|
+
export const Layered: Story = {
|
|
343
|
+
args: {
|
|
344
|
+
layout: [
|
|
345
|
+
{ key: 'a', width: 1, order: 1 },
|
|
346
|
+
{ key: 'b', width: 2, order: 2 },
|
|
347
|
+
{ key: 'c', width: 2, order: 3 },
|
|
348
|
+
{ key: 'd', width: 1, order: 4 },
|
|
349
|
+
{ key: 'e', width: 2, order: 5 },
|
|
350
|
+
{ key: 'f', width: 2, order: 6 },
|
|
351
|
+
],
|
|
352
|
+
rowHeight: 96,
|
|
353
|
+
columns: 6,
|
|
354
|
+
minColumnWidth: 240,
|
|
355
|
+
children: [
|
|
356
|
+
<Tile key="a" tone="brand">
|
|
357
|
+
width: 1
|
|
358
|
+
</Tile>,
|
|
359
|
+
<Tile key="b" tone="info">
|
|
360
|
+
width: 2
|
|
361
|
+
</Tile>,
|
|
362
|
+
<Tile key="c" tone="success">
|
|
363
|
+
width: 2
|
|
364
|
+
</Tile>,
|
|
365
|
+
<Tile key="d" tone="warning">
|
|
366
|
+
width: 1
|
|
367
|
+
</Tile>,
|
|
368
|
+
<Tile key="e" tone="error">
|
|
369
|
+
width: 2
|
|
370
|
+
</Tile>,
|
|
371
|
+
<Tile key="f" tone="neutral">
|
|
372
|
+
width: 2
|
|
373
|
+
</Tile>,
|
|
374
|
+
],
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* A `width: 'fill'` item expands to cover the remaining columns in
|
|
380
|
+
* its row. Mix it with fixed-width items on either side to build
|
|
381
|
+
* sidebar-like layouts that adapt to the column count.
|
|
382
|
+
*/
|
|
383
|
+
export const FillWidth: Story = {
|
|
384
|
+
args: {
|
|
385
|
+
layout: [
|
|
386
|
+
{ key: 'left', width: 1 },
|
|
387
|
+
{ key: 'fill', width: 'fill' },
|
|
388
|
+
{ key: 'right', width: 2 },
|
|
389
|
+
{ key: 'solo', width: 'fill' },
|
|
390
|
+
],
|
|
391
|
+
columns: 6,
|
|
392
|
+
children: [
|
|
393
|
+
<Tile key="left" tone="brand">
|
|
394
|
+
width: 1
|
|
395
|
+
</Tile>,
|
|
396
|
+
<Tile key="fill" tone="info">
|
|
397
|
+
width: "fill"
|
|
398
|
+
</Tile>,
|
|
399
|
+
<Tile key="right" tone="success">
|
|
400
|
+
width: 2
|
|
401
|
+
</Tile>,
|
|
402
|
+
<Tile key="solo" tone="warning">
|
|
403
|
+
width: "fill" (alone in row)
|
|
404
|
+
</Tile>,
|
|
405
|
+
],
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* A `width: 'full'` item spans every column (`grid-column: 1 / -1`),
|
|
411
|
+
* forcing a row break around it. Useful for dividers, hero banners,
|
|
412
|
+
* or embedded content that should always take the full width.
|
|
413
|
+
*/
|
|
414
|
+
export const FullWidth: Story = {
|
|
415
|
+
args: {
|
|
416
|
+
layout: [
|
|
417
|
+
{ key: 'a', width: 2 },
|
|
418
|
+
{ key: 'b', width: 4 },
|
|
419
|
+
{ key: 'hero', width: 'full', height: 1 },
|
|
420
|
+
{ key: 'c', width: 3 },
|
|
421
|
+
{ key: 'd', width: 3 },
|
|
422
|
+
],
|
|
423
|
+
columns: 6,
|
|
424
|
+
children: [
|
|
425
|
+
<Tile key="a" tone="brand">
|
|
426
|
+
width: 2
|
|
427
|
+
</Tile>,
|
|
428
|
+
<Tile key="b" tone="info">
|
|
429
|
+
width: 4
|
|
430
|
+
</Tile>,
|
|
431
|
+
<Tile key="hero" tone="success">
|
|
432
|
+
width: "full"
|
|
433
|
+
</Tile>,
|
|
434
|
+
<Tile key="c" tone="warning">
|
|
435
|
+
width: 3
|
|
436
|
+
</Tile>,
|
|
437
|
+
<Tile key="d" tone="error">
|
|
438
|
+
width: 3
|
|
439
|
+
</Tile>,
|
|
440
|
+
],
|
|
441
|
+
},
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Numeric `rowHeight` lets items span multiple rows via `height`.
|
|
446
|
+
* Combined with `width`, this produces tile-based dashboards where
|
|
447
|
+
* each cell can be tuned independently.
|
|
448
|
+
*/
|
|
449
|
+
export const RowHeight: Story = {
|
|
450
|
+
args: {
|
|
451
|
+
layout: [
|
|
452
|
+
{ key: 'a', width: 2, height: 2, order: 1 },
|
|
453
|
+
{ key: 'b', width: 2, height: 1, order: 2 },
|
|
454
|
+
{ key: 'c', width: 2, height: 3, order: 3 },
|
|
455
|
+
{ key: 'd', width: 4, height: 1, order: 4 },
|
|
456
|
+
{ key: 'e', width: 2, height: 1, order: 5 },
|
|
457
|
+
],
|
|
458
|
+
columns: 6,
|
|
459
|
+
rowHeight: 80,
|
|
460
|
+
children: [
|
|
461
|
+
<Tile key="a" tone="brand">
|
|
462
|
+
2 cols × 2 rows
|
|
463
|
+
</Tile>,
|
|
464
|
+
<Tile key="b" tone="info">
|
|
465
|
+
2 cols × 1 row
|
|
466
|
+
</Tile>,
|
|
467
|
+
<Tile key="c" tone="success">
|
|
468
|
+
2 cols × 3 rows
|
|
469
|
+
</Tile>,
|
|
470
|
+
<Tile key="d" tone="warning">
|
|
471
|
+
4 cols × 1 row
|
|
472
|
+
</Tile>,
|
|
473
|
+
<Tile key="e" tone="error">
|
|
474
|
+
2 cols × 1 row
|
|
475
|
+
</Tile>,
|
|
476
|
+
],
|
|
477
|
+
},
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Edit mode with drag, resize, and all width modes. While `editMode`
|
|
482
|
+
* is on, `<DashboardGrid />` paints its default overlay behind the
|
|
483
|
+
* tiles to visualize the underlying template: rounded row-marker
|
|
484
|
+
* tiles in each column when `rowHeight` is numeric. The overlay
|
|
485
|
+
* disappears when `editMode` flips back to `false`.
|
|
486
|
+
*
|
|
487
|
+
* Theme the default look in place via `--wp-grid-overlay-tile-bg`,
|
|
488
|
+
* or replace the visual wholesale by passing `renderGridOverlay`.
|
|
489
|
+
* See the `Custom Grid Overlay` story for a full override example.
|
|
490
|
+
*
|
|
491
|
+
* A state panel shows the raw layout JSON. Drag items to reorder;
|
|
492
|
+
* resize from the bottom-right handle. Keyboard sensor is enabled:
|
|
493
|
+
* use Tab to focus an item, Space to grab, arrow keys to move, Space
|
|
494
|
+
* to drop.
|
|
495
|
+
*/
|
|
496
|
+
export const EditMode: Story = {
|
|
497
|
+
args: {
|
|
498
|
+
columns: 12,
|
|
499
|
+
rowHeight: 80,
|
|
500
|
+
editMode: true,
|
|
501
|
+
},
|
|
502
|
+
|
|
503
|
+
render: function EditModeStory( args ) {
|
|
504
|
+
const initialLayout: ( DashboardGridLayoutItem & {
|
|
505
|
+
tone: Tone;
|
|
506
|
+
} )[] = [
|
|
507
|
+
{
|
|
508
|
+
key: 'fixed-1',
|
|
509
|
+
width: 1,
|
|
510
|
+
height: 1,
|
|
511
|
+
order: 1,
|
|
512
|
+
tone: 'success',
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
key: 'fixed-1-1',
|
|
516
|
+
width: 5,
|
|
517
|
+
height: 1,
|
|
518
|
+
order: 2,
|
|
519
|
+
tone: 'info',
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
key: 'fixed-2',
|
|
523
|
+
width: 5,
|
|
524
|
+
height: 1,
|
|
525
|
+
order: 3,
|
|
526
|
+
tone: 'brand',
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
key: 'full',
|
|
530
|
+
width: 'full',
|
|
531
|
+
height: 1,
|
|
532
|
+
order: 4,
|
|
533
|
+
tone: 'neutral',
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
key: 'fixed-3',
|
|
537
|
+
width: 2,
|
|
538
|
+
height: 1,
|
|
539
|
+
order: 5,
|
|
540
|
+
tone: 'warning',
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
key: 'fixed-4',
|
|
544
|
+
width: 2,
|
|
545
|
+
height: 1,
|
|
546
|
+
order: 6,
|
|
547
|
+
tone: 'error',
|
|
548
|
+
},
|
|
549
|
+
];
|
|
550
|
+
|
|
551
|
+
const [ tiles, setTiles ] = useState( initialLayout );
|
|
552
|
+
const [ previewLayout, setPreviewLayout ] = useState<
|
|
553
|
+
DashboardGridLayoutItem[] | null
|
|
554
|
+
>( null );
|
|
555
|
+
|
|
556
|
+
const layout: DashboardGridLayoutItem[] = tiles.map(
|
|
557
|
+
( { tone: _tone, ...item } ) => item
|
|
558
|
+
);
|
|
559
|
+
|
|
560
|
+
const onChangeLayout = ( next: DashboardGridLayoutItem[] ) => {
|
|
561
|
+
setTiles(
|
|
562
|
+
next.map( ( item ) => {
|
|
563
|
+
const existing = tiles.find( ( t ) => t.key === item.key );
|
|
564
|
+
return {
|
|
565
|
+
...item,
|
|
566
|
+
tone: existing?.tone ?? 'neutral',
|
|
567
|
+
};
|
|
568
|
+
} )
|
|
569
|
+
);
|
|
570
|
+
setPreviewLayout( null );
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
const removeTile = ( key: string ) => {
|
|
574
|
+
setTiles( tiles.filter( ( tile ) => tile.key !== key ) );
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
const toggleFill = ( key: string ) => {
|
|
578
|
+
setTiles(
|
|
579
|
+
tiles.map( ( tile ) =>
|
|
580
|
+
tile.key === key
|
|
581
|
+
? {
|
|
582
|
+
...tile,
|
|
583
|
+
width:
|
|
584
|
+
tile.width === 'fill' ? undefined : 'fill',
|
|
585
|
+
}
|
|
586
|
+
: tile
|
|
587
|
+
)
|
|
588
|
+
);
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
const toggleFull = ( key: string ) => {
|
|
592
|
+
setTiles(
|
|
593
|
+
tiles.map( ( tile ) =>
|
|
594
|
+
tile.key === key
|
|
595
|
+
? {
|
|
596
|
+
...tile,
|
|
597
|
+
width:
|
|
598
|
+
tile.width === 'full' ? undefined : 'full',
|
|
599
|
+
}
|
|
600
|
+
: tile
|
|
601
|
+
)
|
|
602
|
+
);
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
// Memoize the Tile elements so the grid's `children` prop keeps
|
|
606
|
+
// a stable reference across parent re-renders driven by
|
|
607
|
+
// onPreviewLayout. Without this, every preview tick produces a
|
|
608
|
+
// fresh array of elements and the grid's children walk has to
|
|
609
|
+
// re-run on each frame of a resize gesture.
|
|
610
|
+
const tileElements = useMemo(
|
|
611
|
+
() =>
|
|
612
|
+
tiles.map( ( tile ) => (
|
|
613
|
+
<Tile
|
|
614
|
+
key={ tile.key }
|
|
615
|
+
tone={ tile.tone }
|
|
616
|
+
actionableArea={
|
|
617
|
+
<TileActions
|
|
618
|
+
isFill={ tile.width === 'fill' }
|
|
619
|
+
isFull={ tile.width === 'full' }
|
|
620
|
+
onToggleFill={ () => toggleFill( tile.key ) }
|
|
621
|
+
onToggleFull={ () => toggleFull( tile.key ) }
|
|
622
|
+
onRemove={ () => removeTile( tile.key ) }
|
|
623
|
+
/>
|
|
624
|
+
}
|
|
625
|
+
>
|
|
626
|
+
{ formatTileLabel( tile ) }
|
|
627
|
+
</Tile>
|
|
628
|
+
) ),
|
|
629
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
630
|
+
[ tiles ]
|
|
631
|
+
);
|
|
632
|
+
|
|
633
|
+
return (
|
|
634
|
+
<Stack direction="row" gap="lg" align="flex-start">
|
|
635
|
+
<div style={ { width: '800px' } }>
|
|
636
|
+
<DashboardGrid
|
|
637
|
+
{ ...args }
|
|
638
|
+
layout={ layout }
|
|
639
|
+
onChangeLayout={ onChangeLayout }
|
|
640
|
+
onPreviewLayout={ setPreviewLayout }
|
|
641
|
+
>
|
|
642
|
+
{ tileElements }
|
|
643
|
+
</DashboardGrid>
|
|
644
|
+
</div>
|
|
645
|
+
|
|
646
|
+
<LayoutStatePanel
|
|
647
|
+
label={ previewLayout ? 'Staging' : 'Committed' }
|
|
648
|
+
layout={ previewLayout ?? layout }
|
|
649
|
+
tone={ previewLayout ? 'warning' : 'success' }
|
|
650
|
+
/>
|
|
651
|
+
</Stack>
|
|
652
|
+
);
|
|
653
|
+
},
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Custom corner-resize glyph: a diagonal line plus a filled triangle,
|
|
658
|
+
* both leaning toward the bottom-right corner of the tile.
|
|
659
|
+
*/
|
|
660
|
+
const resizeCornerSE = (
|
|
661
|
+
<svg
|
|
662
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
663
|
+
viewBox="0 0 24 24"
|
|
664
|
+
aria-hidden="true"
|
|
665
|
+
>
|
|
666
|
+
<path
|
|
667
|
+
d="M0 24L24 0"
|
|
668
|
+
stroke="currentColor"
|
|
669
|
+
strokeWidth="3"
|
|
670
|
+
strokeLinecap="round"
|
|
671
|
+
fill="none"
|
|
672
|
+
/>
|
|
673
|
+
|
|
674
|
+
<polygon points="24,24 10,24 24,10" fill="currentColor" />
|
|
675
|
+
</svg>
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Override the default corner-triangle resize handle with a custom
|
|
680
|
+
* element via `renderResizeHandle`. The grid keeps the gesture
|
|
681
|
+
* machinery (dnd-kit context, throttled delta loop) and passes the
|
|
682
|
+
* wiring (`ref`, `listeners`, `attributes`) to the consumer — so the
|
|
683
|
+
* custom visual still drives the same resize behavior.
|
|
684
|
+
*/
|
|
685
|
+
function CustomResizeHandle( {
|
|
686
|
+
ref,
|
|
687
|
+
listeners,
|
|
688
|
+
attributes,
|
|
689
|
+
isResizing,
|
|
690
|
+
}: ResizeHandleRenderProps ) {
|
|
691
|
+
return (
|
|
692
|
+
<div
|
|
693
|
+
ref={ ref }
|
|
694
|
+
{ ...listeners }
|
|
695
|
+
{ ...attributes }
|
|
696
|
+
style={ {
|
|
697
|
+
position: 'absolute',
|
|
698
|
+
bottom: 0,
|
|
699
|
+
insetInlineEnd: 0,
|
|
700
|
+
display: 'flex',
|
|
701
|
+
cursor: 'nwse-resize',
|
|
702
|
+
opacity: isResizing ? 0.5 : 1,
|
|
703
|
+
transition: 'opacity 120ms ease',
|
|
704
|
+
} }
|
|
705
|
+
>
|
|
706
|
+
<Icon icon={ resizeCornerSE } size={ 16 } />
|
|
707
|
+
</div>
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* Example `renderDragPreview` wrapper: keeps the clone height chain
|
|
713
|
+
* intact. Lift, shadow, and motion live on the grid
|
|
714
|
+
* `.drag-preview-frame`; set `--wp-grid-drag-preview-radius` on the
|
|
715
|
+
* surface when the lift shadow should match rounded tiles (see widget
|
|
716
|
+
* dashboard).
|
|
717
|
+
*/
|
|
718
|
+
function CustomDragPreview( { children }: DragPreviewRenderProps ) {
|
|
719
|
+
return <div style={ { height: '100%' } }>{ children }</div>;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Exercises the three customization vectors on a single grid:
|
|
724
|
+
*
|
|
725
|
+
* 1. `renderResizeHandle` swaps the default corner triangle for a
|
|
726
|
+
* custom diagonal-arrow icon.
|
|
727
|
+
* 2. `renderDragPreview` wraps the dragged clone (here only for the
|
|
728
|
+
* height chain; lift and shadow stay on the grid frame).
|
|
729
|
+
* 3. CSS custom properties on an ancestor retheme the lift scale,
|
|
730
|
+
* placeholder opacity, placeholder outline color, and placeholder
|
|
731
|
+
* border-radius without touching the package.
|
|
732
|
+
*
|
|
733
|
+
* Toggle `editMode`, then drag and resize a tile to see all three
|
|
734
|
+
* respond.
|
|
735
|
+
*/
|
|
736
|
+
export const Customization: Story = {
|
|
737
|
+
args: {
|
|
738
|
+
columns: 6,
|
|
739
|
+
rowHeight: 80,
|
|
740
|
+
editMode: true,
|
|
741
|
+
layout: [
|
|
742
|
+
{ key: 'a', width: 2, height: 1 },
|
|
743
|
+
{ key: 'b', width: 4, height: 1 },
|
|
744
|
+
{ key: 'c', width: 3, height: 2 },
|
|
745
|
+
{ key: 'd', width: 3, height: 1 },
|
|
746
|
+
{ key: 'e', width: 3, height: 1 },
|
|
747
|
+
],
|
|
748
|
+
},
|
|
749
|
+
render: function CustomizationRender( args ) {
|
|
750
|
+
const [ layout, setLayout ] = useState< DashboardGridLayoutItem[] >(
|
|
751
|
+
args.layout
|
|
752
|
+
);
|
|
753
|
+
|
|
754
|
+
const tiles = useMemo(
|
|
755
|
+
() => [
|
|
756
|
+
<Tile key="a" tone="brand">
|
|
757
|
+
A
|
|
758
|
+
</Tile>,
|
|
759
|
+
<Tile key="b" tone="info">
|
|
760
|
+
B
|
|
761
|
+
</Tile>,
|
|
762
|
+
<Tile key="c" tone="success">
|
|
763
|
+
C
|
|
764
|
+
</Tile>,
|
|
765
|
+
<Tile key="d" tone="warning">
|
|
766
|
+
D
|
|
767
|
+
</Tile>,
|
|
768
|
+
<Tile key="e" tone="error">
|
|
769
|
+
E
|
|
770
|
+
</Tile>,
|
|
771
|
+
],
|
|
772
|
+
[]
|
|
773
|
+
);
|
|
774
|
+
|
|
775
|
+
const customTokens = {
|
|
776
|
+
'--wp-grid-drag-preview-scale': '1.08',
|
|
777
|
+
'--wp-grid-placeholder-opacity': '0.2',
|
|
778
|
+
'--wp-grid-placeholder-outline-color':
|
|
779
|
+
'var(--wpds-color-foreground-content-warning)',
|
|
780
|
+
'--wp-grid-placeholder-radius': '12px',
|
|
781
|
+
} as React.CSSProperties;
|
|
782
|
+
|
|
783
|
+
return (
|
|
784
|
+
<div style={ customTokens }>
|
|
785
|
+
<DashboardGrid
|
|
786
|
+
{ ...args }
|
|
787
|
+
layout={ layout }
|
|
788
|
+
onChangeLayout={ setLayout }
|
|
789
|
+
renderResizeHandle={ CustomResizeHandle }
|
|
790
|
+
renderDragPreview={ CustomDragPreview }
|
|
791
|
+
>
|
|
792
|
+
{ tiles }
|
|
793
|
+
</DashboardGrid>
|
|
794
|
+
</div>
|
|
795
|
+
);
|
|
796
|
+
},
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* Example custom overlay supplied to `<DashboardGrid />` through the
|
|
801
|
+
* `renderGridOverlay` prop. Receives the grid's resolved column
|
|
802
|
+
* count, gap, row height, and `isActive` flag; this implementation
|
|
803
|
+
* drops the row dividers, swaps to an info tone, labels each column
|
|
804
|
+
* track with its index, and fades in/out on `isActive` toggles. The
|
|
805
|
+
* grid always mounts the overlay; the consumer owns the visual and
|
|
806
|
+
* its transition.
|
|
807
|
+
*
|
|
808
|
+
* @param props Render props supplied by the grid.
|
|
809
|
+
* @param props.columns Number of column tracks to mirror.
|
|
810
|
+
* @param props.isActive Whether the overlay should be visible.
|
|
811
|
+
*/
|
|
812
|
+
function NumberedOverlay( { columns, isActive }: GridOverlayRenderProps ) {
|
|
813
|
+
return (
|
|
814
|
+
<div
|
|
815
|
+
aria-hidden
|
|
816
|
+
style={ {
|
|
817
|
+
position: 'absolute',
|
|
818
|
+
inset: 0,
|
|
819
|
+
display: 'grid',
|
|
820
|
+
gridTemplateColumns: `repeat(${ columns }, minmax(0, 1fr))`,
|
|
821
|
+
gap: 'var(--wpds-dimension-gap-xl)',
|
|
822
|
+
pointerEvents: 'none',
|
|
823
|
+
opacity: isActive ? 1 : 0,
|
|
824
|
+
visibility: isActive ? 'visible' : 'hidden',
|
|
825
|
+
transition: isActive
|
|
826
|
+
? 'opacity 200ms ease, visibility 0s linear 0s'
|
|
827
|
+
: 'opacity 200ms ease, visibility 0s linear 200ms',
|
|
828
|
+
backgroundImage: `repeating-linear-gradient(135deg, color-mix(in srgb, var(--wpds-color-background-surface-info) 24%, transparent) 0 6px, transparent 6px 12px)`,
|
|
829
|
+
} }
|
|
830
|
+
>
|
|
831
|
+
{ Array.from( { length: columns } ).map( ( _, i ) => (
|
|
832
|
+
<div
|
|
833
|
+
key={ i }
|
|
834
|
+
style={ {
|
|
835
|
+
outline:
|
|
836
|
+
'1px dashed var(--wpds-color-stroke-surface-info)',
|
|
837
|
+
backgroundColor:
|
|
838
|
+
'color-mix(in srgb, var(--wpds-color-background-surface-info) 10%, transparent)',
|
|
839
|
+
position: 'relative',
|
|
840
|
+
} }
|
|
841
|
+
>
|
|
842
|
+
<span
|
|
843
|
+
style={ {
|
|
844
|
+
position: 'absolute',
|
|
845
|
+
top: 4,
|
|
846
|
+
insetInlineStart: 4,
|
|
847
|
+
fontSize: 10,
|
|
848
|
+
padding: '1px 6px',
|
|
849
|
+
borderRadius: 2,
|
|
850
|
+
background:
|
|
851
|
+
'var(--wpds-color-background-surface-info)',
|
|
852
|
+
color: 'var(--wpds-color-foreground-content-info)',
|
|
853
|
+
fontFamily:
|
|
854
|
+
'var(--wpds-typography-font-family-mono)',
|
|
855
|
+
} }
|
|
856
|
+
>
|
|
857
|
+
{ i + 1 }
|
|
858
|
+
</span>
|
|
859
|
+
</div>
|
|
860
|
+
) ) }
|
|
861
|
+
</div>
|
|
862
|
+
);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* Replaces the package's default edit-mode overlay with a custom
|
|
867
|
+
* visual through the `renderGridOverlay` prop. The grid mounts the
|
|
868
|
+
* supplied component as a sibling behind the tiles whenever
|
|
869
|
+
* `editMode` is on, passing the resolved `{ columns, rowHeight }`
|
|
870
|
+
* so the override can reproduce the column and row tracks
|
|
871
|
+
* pixel-accurately without re-deriving them.
|
|
872
|
+
*
|
|
873
|
+
* Here the override (see `NumberedOverlay` above) swaps the warning
|
|
874
|
+
* tone for info, drops the row dividers, and labels each column
|
|
875
|
+
* track with its index. Pass `renderGridOverlay={ () => null }` to
|
|
876
|
+
* suppress the overlay entirely while keeping `editMode` interactions
|
|
877
|
+
* on.
|
|
878
|
+
*/
|
|
879
|
+
export const CustomGridOverlayStory: Story = {
|
|
880
|
+
name: 'Custom Grid Overlay',
|
|
881
|
+
args: {
|
|
882
|
+
columns: 12,
|
|
883
|
+
rowHeight: 80,
|
|
884
|
+
editMode: true,
|
|
885
|
+
layout: [
|
|
886
|
+
{ key: 'a', width: 3, height: 1 },
|
|
887
|
+
{ key: 'b', width: 5, height: 1 },
|
|
888
|
+
{ key: 'c', width: 4, height: 1 },
|
|
889
|
+
{ key: 'd', width: 2, height: 2 },
|
|
890
|
+
{ key: 'e', width: 6, height: 1 },
|
|
891
|
+
],
|
|
892
|
+
},
|
|
893
|
+
render: function CustomGridOverlayRender( args ) {
|
|
894
|
+
const [ layout, setLayout ] = useState< DashboardGridLayoutItem[] >(
|
|
895
|
+
args.layout
|
|
896
|
+
);
|
|
897
|
+
|
|
898
|
+
const tiles = useMemo(
|
|
899
|
+
() => [
|
|
900
|
+
<Tile key="a" tone="brand">
|
|
901
|
+
A
|
|
902
|
+
</Tile>,
|
|
903
|
+
<Tile key="b" tone="info">
|
|
904
|
+
B
|
|
905
|
+
</Tile>,
|
|
906
|
+
<Tile key="c" tone="success">
|
|
907
|
+
C
|
|
908
|
+
</Tile>,
|
|
909
|
+
<Tile key="d" tone="warning">
|
|
910
|
+
D
|
|
911
|
+
</Tile>,
|
|
912
|
+
<Tile key="e" tone="error">
|
|
913
|
+
E
|
|
914
|
+
</Tile>,
|
|
915
|
+
],
|
|
916
|
+
[]
|
|
917
|
+
);
|
|
918
|
+
|
|
919
|
+
return (
|
|
920
|
+
<DashboardGrid
|
|
921
|
+
{ ...args }
|
|
922
|
+
layout={ layout }
|
|
923
|
+
onChangeLayout={ setLayout }
|
|
924
|
+
renderGridOverlay={ NumberedOverlay }
|
|
925
|
+
>
|
|
926
|
+
{ tiles }
|
|
927
|
+
</DashboardGrid>
|
|
928
|
+
);
|
|
929
|
+
},
|
|
930
|
+
};
|