Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
5008343
add dashboard rendering engine in routes/dashboard
retrofox Apr 24, 2026
1977c8c
add storybook stories for dashboard engine
retrofox Apr 24, 2026
93231cb
mount WidgetDashboard in dashboard route stage
retrofox Apr 24, 2026
0823aa4
update WidgetType to widget.json shape
retrofox Apr 24, 2026
c10f94e
remove collapseWidth from dashboard engine
retrofox Apr 24, 2026
c7554cd
drop WidgetBadge and WidgetErrorConfig types
retrofox Apr 24, 2026
bd45200
move engine into widgets-dashboard subdirectory
retrofox Apr 24, 2026
4f79925
use type-driven jsdoc for engine components
retrofox Apr 24, 2026
2b9b42e
infer grid settings from DashboardGridProps
retrofox Apr 24, 2026
ac57d43
drop id prop and useWidgetDashboardContext
retrofox Apr 25, 2026
bccaa15
add Composition story to widgets-dashboard
retrofox Apr 25, 2026
458d9b9
rename grid prop to gridSettings
retrofox Apr 25, 2026
5e524c3
document widget-types migration path
retrofox Apr 27, 2026
ad867a4
drop dataviews framing from engine docs
retrofox Apr 27, 2026
14c3ac4
add README for widget-dashboard engine
retrofox Apr 27, 2026
614f9bb
rename WidgetInstance to DashboardWidget
retrofox Apr 27, 2026
49d27c7
rename WidgetContextValue.position to index
retrofox Apr 28, 2026
29389fe
rename widgets-dashboard folder to widget-dashboard
retrofox Apr 28, 2026
1adef5f
group widget-dashboard internals into components/ and context/
retrofox Apr 28, 2026
6a668cb
update lock file
retrofox Apr 28, 2026
aa4e868
add default empty placeholder to widget-dashboard
retrofox Apr 28, 2026
1c06638
split widget-dashboard styles into per-component modules
retrofox Apr 28, 2026
b145a60
move widget-dashboard compounds into per-component folders
retrofox Apr 28, 2026
31e4181
set no-widgets-state padding-block-start to ~112px
retrofox Apr 28, 2026
6664291
use eslint-disable for EmptyState imports
retrofox Apr 28, 2026
968caa4
update lock file
retrofox Apr 28, 2026
97b0159
Merge branch 'trunk' into add/wordpress-dashboard-package
retrofox Apr 28, 2026
e501807
Merge branch 'trunk' into add/wordpress-dashboard-package
retrofox Apr 29, 2026
e61ffbe
remove eslint-disable for EmptyState imports
retrofox Apr 29, 2026
0c5c9fc
update empty state icon and rename story
retrofox Apr 29, 2026
e57573d
refactor widget-render loading/error to Stack
retrofox Apr 29, 2026
30167a1
make widget content inert in edit mode
retrofox Apr 29, 2026
722aaba
add NoWidgetsCustom story
retrofox Apr 29, 2026
bd56878
Merge branch 'trunk' into add/wordpress-dashboard-package
retrofox Apr 29, 2026
d5509d3
Merge branch 'trunk' into add/wordpress-dashboard-package
retrofox Apr 29, 2026
984b63b
Merge branch 'trunk' into add/wordpress-dashboard-package
retrofox Apr 29, 2026
e913665
Merge branch 'trunk' into add/wordpress-dashboard-package
retrofox Apr 30, 2026
3ea30d3
Merge branch 'trunk' into add/wordpress-dashboard-package
retrofox Apr 30, 2026
0dcffb4
Merge branch 'trunk' into add/wordpress-dashboard-package
retrofox Apr 30, 2026
dbb4e50
Merge branch 'trunk' into add/wordpress-dashboard-package
retrofox May 1, 2026
9458708
Merge branch 'trunk' into add/wordpress-dashboard-package
retrofox May 2, 2026
906c988
Merge branch 'trunk' into add/wordpress-dashboard-package
retrofox May 4, 2026
013f00f
Merge branch 'trunk' into add/wordpress-dashboard-package
retrofox May 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion routes/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,16 @@
"dependencies": {
"@wordpress/admin-ui": "file:../../packages/admin-ui",
"@wordpress/base-styles": "file:../../packages/base-styles",
"@wordpress/compose": "file:../../packages/compose",
"@wordpress/data": "file:../../packages/data",
"@wordpress/dataviews": "file:../../packages/dataviews",
"@wordpress/element": "file:../../packages/element",
"@wordpress/grid": "file:../../packages/grid",
"@wordpress/hooks": "file:../../packages/hooks",
"@wordpress/i18n": "file:../../packages/i18n",
"@wordpress/warning": "file:../../packages/warning"
"@wordpress/icons": "file:../../packages/icons",
"@wordpress/ui": "file:../../packages/ui",
"@wordpress/warning": "file:../../packages/warning",
"clsx": "^2.1.1"
}
}
27 changes: 25 additions & 2 deletions routes/dashboard/stage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,35 @@
* WordPress dependencies
*/
import { Page } from '@wordpress/admin-ui';
import { useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import {
WidgetDashboard,
type DashboardWidget,
type WidgetType,
} from './widget-dashboard';

/*
* Widget types will be provided by `@wordpress/widget-types` once that
* package is scaffolded. For now the route mounts the engine with an empty
* registry so the page renders and the surface can be exercised visually.
*/
const widgetTypes: WidgetType[] = [];

function Dashboard() {
const [ layout, setLayout ] = useState< DashboardWidget[] >( [] );

return (
<Page title={ __( 'Dashboard' ) }>
<div className="dashboard-widgets" />
<Page title={ __( 'Dashboard' ) } headingLevel={ 1 }>
<WidgetDashboard
layout={ layout }
onLayoutChange={ setLayout }
widgetTypes={ widgetTypes }
/>
</Page>
);
}
Expand Down
14 changes: 14 additions & 0 deletions routes/dashboard/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "https://json.schemastore.org/tsconfig.json",
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react-jsx",
"rootDir": ".",
"noEmit": true,
"emitDeclarationOnly": false,
"composite": false,
"types": [ "jest", "style-imports" ]
},
"include": [ "**/*.ts", "**/*.tsx" ],
"exclude": [ "build", "node_modules" ]
}
110 changes: 110 additions & 0 deletions routes/dashboard/widget-dashboard/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# `WidgetDashboard`

Stateless rendering engine for widget dashboards. Renders an editable grid of widget instances, with drag-to-reorder and resize when edit mode is on.
Widget types flow in as a prop and every layout mutation fires `onLayoutChange` with the fully updated array.
The engine owns no data of its own.

## Usage

```tsx
import { useState } from '@wordpress/element';
import { WidgetDashboard } from './widget-dashboard';

function Dashboard() {
const [ layout, setLayout ] = useState( defaultLayout );

return (
<WidgetDashboard
layout={ layout }
onLayoutChange={ setLayout }
widgetTypes={ widgetTypes }
/>
);
}
```

`<WidgetDashboard>` renders `<WidgetDashboard.Widgets />` by default. Pass `children` to compose the surface — header, empty state, footer — around the grid:

```tsx
<WidgetDashboard
layout={ layout }
onLayoutChange={ setLayout }
widgetTypes={ widgetTypes }
>
<WidgetDashboard.NoWidgetsState>
<p>{ __( 'No widgets yet.' ) }</p>
</WidgetDashboard.NoWidgetsState>
<WidgetDashboard.Widgets />
</WidgetDashboard>
```

## Properties

#### `layout`: `DashboardWidget[]`

Widget instances to render. Each instance carries a stable `uuid`, a `type` reference, optional `attributes`, and a `placement` describing its slot in the grid.

#### `onLayoutChange`: `( layout: DashboardWidget[] ) => void`

Called on every mutation — reorder, resize, or `setAttributes` from a widget render module. Receives the fully updated array; the consumer owns the storage.

#### `widgetTypes`: `WidgetType[]`

The widget types available to the dashboard.

#### `editMode`: `boolean`

When `true`, the grid enables drag and resize. Defaults to `false`.

#### `onEditChange`: `( next: boolean ) => void`

Optional. Called when edit mode toggles via a future `WidgetDashboard.Actions` compound.

#### `resolveWidgetModule`: `( moduleId: string ) => Promise< { default: ComponentType } >`

Optional. Maps a `WidgetType.renderModule` id to the React component that renders the widget. Defaults to a dynamic `import( /* webpackIgnore */ moduleId )`. Override for tests, Storybook, or remote-URL loading.

#### `gridSettings`: `WidgetGridSettings`

Optional. Configures the underlying grid.

#### `children`: `ReactNode`

Optional. Composition slot for arbitrary surface markup. When omitted, the engine renders `<WidgetDashboard.Widgets />` directly.

## Compound components

#### `<WidgetDashboard.Widgets />`

Iterates `layout`, renders each entry through `<WidgetDashboard.Widget />`, and feeds the resulting tree into the underlying grid (`@wordpress/grid`).

#### `<WidgetDashboard.Widget />`

Per-instance wrapper. Provides widget identity to the render tree via context and hosts the widget's render module under a `Suspense` boundary and an error boundary. The instance is read from `layout`; consumers don't pass it manually.

#### `<WidgetDashboard.NoWidgetsState>`

Renders its children only when `layout` is empty. Pair it with `<WidgetDashboard.Widgets />` so the empty state shows up in place of the grid until widgets are added.

## Authoring widgets

Widget render modules receive only what they need to render and edit:

```ts
interface WidgetRenderProps< Item = unknown > {
attributes: Item;
setAttributes?: ( next: Partial< Item > ) => void;
}
```

`setAttributes` flows back through `onLayoutChange` on the dashboard. Removal, badges, and error chrome are not part of this contract — those belong to the surface.

## Types

- `DashboardWidget` — a placement of a widget on the dashboard. Carries `uuid`, `type`, `attributes`, `placement`.
- `WidgetType` — runtime widget type. Extends the `widget.json` shape with `renderModule`.
- `WidgetRenderProps` — widget render contract.
- `ResolveWidgetModule` — module resolver signature.
- `WidgetGridSettings` — grid configuration.

`WidgetName`, `WidgetTypeMetadata`, and `WidgetType` are declared locally in `types.ts` until `@wordpress/widget-types` lands in trunk; at that point those three collapse into a re-export from the package.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { NoWidgetsState } from './no-widgets-state';
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.root {
padding-block-start: calc(var(--wpds-dimension-padding-3xl) * 3.5);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* External dependencies
*/
import type { ReactNode } from 'react';

/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { home } from '@wordpress/icons';
import { EmptyState, Stack } from '@wordpress/ui';

/**
* Internal dependencies
*/
import { useDashboardInternalContext } from '../../context/dashboard-context';
import styles from './no-widgets-state.module.css';

export interface NoWidgetsStateProps {
children?: ReactNode;
}

function NoWidgetsStateImpl( { children }: NoWidgetsStateProps ) {
const { layout } = useDashboardInternalContext();
if ( layout.length > 0 ) {
return null;
}

return (
<Stack justify="center" align="center" className={ styles.root }>
{ children ?? (
<EmptyState.Root>
<EmptyState.Icon icon={ home } />
<EmptyState.Title>
{ __( 'Your dashboard is empty' ) }
</EmptyState.Title>
<EmptyState.Description>
{ __(
'Add widgets to start customizing your dashboard.'
) }
</EmptyState.Description>
</EmptyState.Root>
) }
</Stack>
);
}

/**
* Renders an empty-state placeholder when the dashboard's `layout` has no
* widgets. Pair with `WidgetDashboard.Widgets` inside `WidgetDashboard` so
* the placeholder shows up in place of the grid until widgets are added.
* Without children, falls back to a built-in placeholder; pass children to
* override.
*/
export const NoWidgetsState = NoWidgetsStateImpl;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { WidgetRender } from './widget-render';
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.loading,
.error {
height: 100%;
padding: var(--wpds-dimension-padding-md);
color: var(--wpds-color-fg-content-neutral-weak);
}

.error {
text-align: center;
}
Loading
Loading