Dashboard: layout settings drawer with grid/masonry models#78202
Conversation
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
|
Size Change: 0 B Total Size: 7.96 MB ℹ️ View Unchanged
|
6ceb373 to
9cf2fe8
Compare
Introduces WidgetGridModel ('grid' | 'masonry'), splits
WidgetGridSettings into a discriminated union by model, exposes
onGridSettingsChange on WidgetDashboardProps, and tightens
DashboardWidget so the placement field is always a
DashboardTilePlacement union.
Pure function that transforms every widget's placement when the active grid model switches. Grid to masonry resolves 'fill' and 'full' widths to numeric spans and drops height; masonry to grid seeds height: 1 and drops the lane field. Covered by unit tests.
WidgetDashboardProvider gains a second staging slice for gridSettings paired with the existing layout staging; commit and cancel operate on both. The Widgets compound renders DashboardGrid or DashboardLanes based on the active model. A new side drawer (LayoutSettings) hosts the model toggle, gap, columns, and row height controls, opened from a cog in the Actions toolbar that is disabled while edit mode is active.
New hook persists grid settings under core/dashboard.dashboardGridSettings with the same shape used at the provider level. The dashboard route reads and forwards them to WidgetDashboard via gridSettings and onGridSettingsChange.
9cf2fe8 to
1076332
Compare
|
Flaky tests detected in 4180522. 🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/25874242560
|
emit gap via --wpds-dimension-gap-* tokens so density propagates via cascade
update settings type, drawer control, and defaults to size keys
gap is a design-system concern; theme/density owns it, not per-dashboard settings
let the design-system gap token own the gap; CSS modules apply --wpds-dimension-gap-sm, math reads computed style
12px reads better than 8px between tiles
| onChange={ handleColumnsModeChange } | ||
| /> | ||
| { isFixedColumns ? ( | ||
| <InputControl |
There was a problem hiding this comment.
+/- controls would fit here super well to improve UX.
There was a problem hiding this comment.
Good call. This will be addressed in a follow-up that migrates the drawer to DataForm from @wordpress/dataviews: its integer control renders +/- steppers natively, so wiring them manually here would be churn we'd remove later.
Tracking it separately to keep this PR focused on the layout-settings UI, if you agree.
| onValueChange={ handleColumnsChange } | ||
| /> | ||
| ) : ( | ||
| <InputControl |
There was a problem hiding this comment.
Similarly to above, stepper +/- could work here too.
| { canEditGridSettings && ( | ||
| <IconButton | ||
| icon={ cog } | ||
| label={ __( 'Layout settings' ) } | ||
| variant="minimal" | ||
| tone="brand" | ||
| size="compact" | ||
| disabled={ editMode } | ||
| onClick={ openLayoutSettings } | ||
| /> | ||
| ) } |
There was a problem hiding this comment.
We can try to find excellent defaults so that most of the time people wouldn't need to adjust these; as such, it probably makes sense to hide it more under the three-dots menu. That would keep the dashboard itself cleaner with fewer buttons visible at first glance.
vs
or perhaps "Dashboard settings".
| </Drawer.Header> | ||
|
|
||
| <Drawer.Content> | ||
| <Stack direction="column" gap="md"> |
There was a problem hiding this comment.
Would it make sense to use DataForm here? Then all spacing is handled and consistent.
There was a problem hiding this comment.
SGTM, yes. I'd like to re-implement it using DataForm in a follow-up, though.
simison
left a comment
There was a problem hiding this comment.
Mergeable already, but I left some copy/design notes which can be addressed here or in a follow-up.
Would love to have design review from you @jameskoster , too!
| @@ -1,5 +1,6 @@ | |||
| .grid { | |||
| display: grid; | |||
| gap: var(--wpds-dimension-gap-md); | |||
There was a problem hiding this comment.
We're trying to align the layout with the DS system. If the token CSS changes, for example, due to density or viewport size, the grid should reflect those changes.
|
I'm going to be that guy again; it's neat that we can do this, but are we certain it's something we want to expose to users? I'd lean towards being opinionated about these aspects now, and only adding options in the UI when we are 100% confident about them. I'd welcome more input from the @WordPress/gutenberg-design group. |
|
For the reset options I agree it might be nice if users could reset just the layout settings, just the widgets, or everything at once. |
…ayout-settings.tsx Co-authored-by: Mikael Korpela <mikael@ihminen.org>
I understand, and I'm not sure whether we want to give the user the ability to customize these UI elements. For instance, this PR also allowed the user to define the Grid However, I think switching the grid model adds value to the dashboard. |
…ayout-settings.tsx Co-authored-by: Mikael Korpela <mikael@ihminen.org>
What do you see to be the main value here? Obviously it's tricky to test without lots of different widget types but I struggle to see situations where the masonry mode is superior. |
Really? I'm ignorant about this. But the question that comes to my mind is, why does Masonry exist? It seems to be part of the standard as well. In fact, when we were working on the dashboard's 2x grid model, we asked for Masonry 😄 So the question is, does it add value to the user? I think it does. |
Drops the cog icon button from the toolbar; the entry now lives inside the more-actions dropdown alongside Reset, and stays disabled while edit mode is active.
Restores staging grid settings to the package defaults; user still needs Save to publish or Cancel to discard.
# Conflicts: # routes/dashboard/widget-dashboard/components/widgets/widgets.tsx
|
All feedback has been addressed. I'd suggest merging it at least to give it a try. We can polish it in follow-ups or remove it entirely. IN the end, it's an experimental feature. But if we agree that exposing this feature to users could be a mistake, let's discard and close it. |
|
Masonry definitely has value certain UIs, I'm just not sure if the Dashboard is one of them. But like I said it's hard to confidently judge without a broader range of widget types to test against, so I don't object to merging and iterating. |
Yes, makes sense. Merged, but let's discard if we agree it adds unnecessary noise for the user. |
* migrate layout settings drawer to DataForm declarative field schema with synthetic toggles for fixed columns and auto row height; spacing now matches the dashboard's form surfaces * minor jsdoc improvement * add +/- steppers to layout settings integers per simison's review on PR #78202: NumberControl with spinControls="custom" for Columns, Min column width, and Row height * set min and max for tile height



What?
Adds a side drawer that lets the user pick the dashboard's grid model and tweak a small set of layout parameters. Two models are now supported:
'grid': the existing 2D packed grid (DashboardGrid), with explicit width/height spans.'masonry': a content-driven, skyline-packed surface backed by the package'sDashboardLanes(added in trunk by the masonry grid PR).The drawer exposes three controls: layout model, column behavior (responsive
minColumnWidthor fixedcolumns), and row height (numeric or auto). Gap is intentionally not exposed: it is a presentational concern owned by the design-system theme / density (and, in the future, viewport tokens once #73361 lands), not a per-dashboard setting. Switching the model migrates the existing placements through a new pure helper so the dashboard reflows live behind the drawer.Part of #77616 #78035
Why?
Surfacing the grid configuration to the user closes the gap between "the engine supports two models" and "the user can pick one". Without this, masonry rendered today only by editing preferences manually.
A drawer (as opposed to a modal) keeps the dashboard visible while controls are adjusted, which is the whole point of a live-preview feature. Splitting it from the existing Customize flow keeps both flows transactional in isolation: a Save in the drawer publishes settings, a Done in the toolbar publishes layout edits, and the cog trigger is disabled while edit mode is active so the two staging buffers do not cross-pollinate.
How?
Types (
routes/dashboard/widget-dashboard/types.ts):WidgetGridModel = 'grid' | 'masonry'.WidgetGridSettingsbecomes a discriminated union ofWidgetGridLayoutSettingsandWidgetMasonryLayoutSettings, sharingBaseWidgetGridSettings(columns,minColumnWidth) and adding per-model extras (rowHeightfor grid,flowTolerancefor masonry).spacingis intentionally absent from the dashboard settings; the underlying grid surface keeps itsspacingprop for programmatic overrides but the dashboard does not propagate it.WidgetDashboardPropsgains an optionalonGridSettingsChange.DashboardTilePlacementdocuments the storage invariant: the union declares which shapes are valid, but every placement in a live layout must match the active model's shape.migrateLayoutis the only correct transition path.Provider (
routes/dashboard/widget-dashboard/context/dashboard-context.tsx):stagingGridSettingsalongside the existingstagingLayoutwith the same re-sync pattern on prop change.hasUncommittedChangesORs layout and settings changes.commitpublishes whichever slice differs from committed (best-effort atomic; no rollback if a callback throws).cancelreverts both.canEditGridSettingsexposes whether the consumer wired uponGridSettingsChange.Migration (
routes/dashboard/widget-dashboard/utils/migrate-layout):Pure function
migrateLayout(widgets, from, to, { columns }). Grid to masonry resolves'full'widths toctx.columns, collapses'fill'to a single column, dropsheight. Masonry to grid seedsheight: 1and drops thelanefield. Both directions preserveorderand numeric widths. 10 unit tests cover the corner cases.Render switch (
routes/dashboard/widget-dashboard/components/widgets/widgets.tsx):Branches on
gridSettings.modelto mountDashboardGridorDashboardLanes. HelperstoMasonryLayoutandapplyMasonryChangemirror the existing grid pair; the function names use the dashboard's model vocabulary while the package'sDashboardLanesLayoutItemshape leaks in only at the boundary.Drawer (
routes/dashboard/widget-dashboard/components/layout-settings):<Drawer>from@wordpress/uiwithswipeDirection="right",modal={false},disablePointerDismissal. Reads from and writes to the staging copy in the internal context. Save callscommit, Cancel callscancel, the X icon and Escape are treated as Cancel. Form controls come from@wordpress/ui(SelectControl,InputControl) and@wordpress/components(ToggleControlfor the auto-fit row height switch). Gap is not surfaced as a control on purpose; see #73361 for the design-system direction on responsive tokens.Actions (
routes/dashboard/widget-dashboard/components/actions/actions.tsx):Adds a cog
IconButtonnext to the toolbar. Click opens the drawer. Disabled while edit mode is active to keep the two flows mutually exclusive. Mounts<LayoutSettings />oncecanEditGridSettingsis true.Persistence (
routes/dashboard/hooks/use-dashboard-grid-settings):New hook reading and writing
core/dashboard.dashboardGridSettingsvia@wordpress/preferences. Defaults shipped in code ({ model: 'grid', minColumnWidth: 350, rowHeight: 200 }).Route wiring (
routes/dashboard/stage.tsx):Calls the new hook and forwards both
gridSettingsandonGridSettingsChangetoWidgetDashboard.Tests
26 unit tests already cover staging-layer behavior. This PR adds:
migrateLayoutcases (utils/migrate-layout/test/migrate-layout.test.ts).test/staging.test.tsx(settings stay in staging, commit publishes both slices, cancel reverts settings, layout-only commit does not republish settings).test/inserter.test.tsxupdated to disambiguate the inserter dialog from the new drawer by accessible name (both sharerole="dialog").46 unit tests pass.
Manual testing
heightand gained numeric widths.Screen.Recording.2026-05-13.at.11.33.13.AM.mov
Follow-ups
useStaged<T>hook if a third slice lands.layoutorgridSettingsupdates from outside; viable strategies include blocking re-sync whilehasUncommittedChanges, surfacing a notice, or accepting the loss with documentation.canEditGridSettingsonly as an internal flag once every consumer wiresonGridSettingsChange.