Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions package-lock.json

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

17 changes: 12 additions & 5 deletions packages/grid/src/dashboard-grid/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ import styles from './grid.module.css';
// effect overwrites this with the actual computed value.
const FALLBACK_GAP_PX = 12;

// Default column cap when no explicit `columns` or `minColumnWidth` is
// supplied. Layered semantics: `columns` acts as a cap and
// `minColumnWidth` as a per-tile floor; if neither is set we still
// need a finite count to render against.
const DEFAULT_COLUMNS = 6;

// Reorder is driven by `temporaryLayout` + CSS Grid, not by dnd-kit
// transforms. Hoist the no-op strategy outside the component so its
// reference is stable across renders — passing a fresh `() => null`
Expand Down Expand Up @@ -91,7 +97,7 @@ export const DashboardGrid = forwardRef< HTMLDivElement, DashboardGridProps >(
function DashboardGrid( props, ref ) {
const {
layout,
columns = 6,
columns,
children,
className,
style,
Expand Down Expand Up @@ -172,14 +178,15 @@ export const DashboardGrid = forwardRef< HTMLDivElement, DashboardGridProps >(
}, [] );
const effectiveColumns = useMemo( () => {
if ( ! minColumnWidth ) {
return columns;
return columns ?? DEFAULT_COLUMNS;
}

const totalWidthPerColumn = minColumnWidth + gapPx;
const maxColumns = Math.floor(
( containerWidth + gapPx ) / totalWidthPerColumn
const maxFit = Math.max(
1,
Math.floor( ( containerWidth + gapPx ) / totalWidthPerColumn )
);
return Math.max( 1, maxColumns );
return columns !== undefined ? Math.min( columns, maxFit ) : maxFit;
}, [ minColumnWidth, gapPx, containerWidth, columns ] );
const columnWidth =
( containerWidth - ( effectiveColumns - 1 ) * gapPx ) /
Expand Down
44 changes: 44 additions & 0 deletions packages/grid/src/dashboard-grid/stories/index.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,50 @@ export const Responsive: Story = {
},
};

/**
* Layered configuration: `columns` caps the count and
* `minColumnWidth` enforces a per-tile width floor. The grid renders
* up to `columns` columns on wide containers and reduces the count
* on narrow ones whenever fitting all of them would push tiles
* below `minColumnWidth`. Resize the preview to see the cap apply
* on wide widths and the floor reduce the count on narrow widths.
*/
export const Layered: Story = {
args: {
layout: [
{ key: 'a', width: 1, order: 1 },
{ key: 'b', width: 2, order: 2 },
{ key: 'c', width: 2, order: 3 },
{ key: 'd', width: 1, order: 4 },
{ key: 'e', width: 2, order: 5 },
{ key: 'f', width: 2, order: 6 },
],
rowHeight: 96,
columns: 6,
minColumnWidth: 240,
children: [
<Tile key="a" tone="brand">
width: 1
</Tile>,
<Tile key="b" tone="info">
width: 2
</Tile>,
<Tile key="c" tone="success">
width: 2
</Tile>,
<Tile key="d" tone="warning">
width: 1
</Tile>,
<Tile key="e" tone="error">
width: 2
</Tile>,
<Tile key="f" tone="neutral">
width: 2
</Tile>,
],
},
};

/**
* A `width: 'fill'` item expands to cover the remaining columns in
* its row. Mix it with fixed-width items on either side to build
Expand Down
47 changes: 25 additions & 22 deletions packages/grid/src/dashboard-grid/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,19 @@ export type GridItemProps = {
};

/**
* Props shared by fixed and responsive DashboardGrid variants. Extends
* the standard div props so consumers can pass `id`, `aria-*`, `data-*`,
* event handlers, etc., directly on the grid root.
* Props for `DashboardGrid`. Extends the standard div props so consumers
* can pass `id`, `aria-*`, `data-*`, event handlers, etc., directly on
* the grid root.
*
* `columns` and `minColumnWidth` compose as a layered model:
* - `columns` alone: fixed N columns; each tile scales with the container.
* - `minColumnWidth` alone: column count derives from container width,
* floored by the per-tile minimum, down to 1 column.
* - Both together: `columns` caps the count, `minColumnWidth` enforces a
* per-tile width floor that can reduce the count below the cap on
* narrow containers ("up to N columns, but never narrower than W px").
*/
interface BaseDashboardGridProps
export interface DashboardGridProps
extends Omit<
React.ComponentPropsWithoutRef< 'div' >,
'children' | 'className' | 'style'
Expand Down Expand Up @@ -215,30 +223,25 @@ interface BaseDashboardGridProps
* the package's default visual is used.
*/
renderGridOverlay?: React.ComponentType< GridOverlayRenderProps >;
}

interface FixedDashboardGridProps extends BaseDashboardGridProps {
/**
* Total number of columns in the grid.
* Target column count (cap). When set alone, the grid renders this
* many columns and tiles scale with the container.
*
* @default 6
* Composes with `minColumnWidth`: if both are set, the effective
* column count is `min( columns, fitsAtMinWidth )`. When omitted
* but `minColumnWidth` is set, the count is uncapped and derives
* purely from the container width. When both are omitted, the
* grid renders six columns.
*/
columns: number;

minColumnWidth?: never;
}
columns?: number;

interface ResponsiveDashboardGridProps extends BaseDashboardGridProps {
/**
* Minimum width in pixels per column. Enables responsive mode:
* the column count is derived from container width, down to a
* minimum of 1 column. Mutually exclusive with `columns`.
* Per-tile minimum width in pixels. The effective column count is
* derived from container width, floored by this value, down to 1.
*
* Composes with `columns`: when both are set, this acts as a floor
* that can reduce the count below `columns` on narrow containers.
*/
minColumnWidth?: number;

columns?: never;
}

export type DashboardGridProps =
| FixedDashboardGridProps
| ResponsiveDashboardGridProps;
17 changes: 12 additions & 5 deletions packages/grid/src/dashboard-lanes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ import styles from './lanes.module.css';
// effect overwrites this with the actual computed value.
const FALLBACK_GAP_PX = 12;

// Default lane cap when no explicit `columns` or `minColumnWidth` is
// supplied. Layered semantics: `columns` acts as a cap and
// `minColumnWidth` as a per-tile floor; if neither is set we still
// need a finite count to render against.
const DEFAULT_COLUMNS = 6;

const NO_SORT_STRATEGY = () => null;

/**
Expand Down Expand Up @@ -91,7 +97,7 @@ export const DashboardLanes = forwardRef< HTMLDivElement, DashboardLanesProps >(
function DashboardLanes( props, ref ) {
const {
layout,
columns = 6,
columns,
children,
className,
style,
Expand Down Expand Up @@ -158,13 +164,14 @@ export const DashboardLanes = forwardRef< HTMLDivElement, DashboardLanesProps >(
}, [ container ] );
const effectiveColumns = useMemo( () => {
if ( ! minColumnWidth ) {
return columns;
return columns ?? DEFAULT_COLUMNS;
}
const totalWidthPerColumn = minColumnWidth + gapPx;
const maxColumns = Math.floor(
( containerWidth + gapPx ) / totalWidthPerColumn
const maxFit = Math.max(
1,
Math.floor( ( containerWidth + gapPx ) / totalWidthPerColumn )
);
return Math.max( 1, maxColumns );
return columns !== undefined ? Math.min( columns, maxFit ) : maxFit;
}, [ minColumnWidth, gapPx, containerWidth, columns ] );
const columnWidth =
( containerWidth - ( effectiveColumns - 1 ) * gapPx ) /
Expand Down
30 changes: 30 additions & 0 deletions packages/grid/src/dashboard-lanes/stories/index.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,36 @@ export const Responsive: Story = {
},
};

/**
* Layered configuration: `columns` caps the lane count and
* `minColumnWidth` enforces a per-tile width floor. The surface
* renders up to `columns` lanes on wide containers and reduces the
* count on narrow ones whenever fitting all of them would push
* tiles below `minColumnWidth`.
*/
export const Layered: Story = {
args: {
columns: 4,
minColumnWidth: 200,
layout: [
{ key: 'a' },
{ key: 'b' },
{ key: 'c' },
{ key: 'd' },
{ key: 'e' },
{ key: 'f' },
],
children: [
<Tile key="a" tone="brand" height={ 120 } index={ 1 } />,
<Tile key="b" tone="info" height={ 200 } index={ 2 } />,
<Tile key="c" tone="success" height={ 80 } index={ 3 } />,
<Tile key="d" tone="warning" height={ 160 } index={ 4 } />,
<Tile key="e" tone="error" height={ 100 } index={ 5 } />,
<Tile key="f" tone="neutral" height={ 240 } index={ 6 } />,
],
},
};

/**
* Items with `width: 2` span two lanes. The skyline picks a span
* position that minimizes the resulting baseline across spanned
Expand Down
41 changes: 22 additions & 19 deletions packages/grid/src/dashboard-lanes/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,16 @@ export type DashboardLanesLayoutItem = {
};

/**
* Props shared by fixed and responsive `DashboardLanes` variants.
* Props for `DashboardLanes`.
*
* `columns` and `minColumnWidth` compose as a layered model:
* - `columns` alone: fixed N lanes; tiles scale with the container.
* - `minColumnWidth` alone: lane count derives from container width,
* floored by the per-tile minimum, down to 1.
* - Both together: `columns` caps the count, `minColumnWidth` enforces
* a per-tile width floor that can reduce the count below the cap.
*/
interface BaseDashboardLanesProps
export interface DashboardLanesProps
extends Omit<
React.ComponentPropsWithoutRef< 'div' >,
'children' | 'className' | 'style'
Expand Down Expand Up @@ -152,29 +159,25 @@ interface BaseDashboardLanesProps
* the package's default visual is used.
*/
renderGridOverlay?: React.ComponentType< GridOverlayRenderProps >;
}

interface FixedDashboardLanesProps extends BaseDashboardLanesProps {
/**
* Total number of lanes in the surface.
* Target lane count (cap). When set alone, the surface renders
* this many lanes and tiles scale with the container.
*
* @default 6
* Composes with `minColumnWidth`: if both are set, the effective
* lane count is `min( columns, fitsAtMinWidth )`. When omitted but
* `minColumnWidth` is set, the count is uncapped and derives purely
* from container width. When both are omitted, the surface
* renders six lanes.
*/
columns: number;

minColumnWidth?: never;
}
columns?: number;

interface ResponsiveDashboardLanesProps extends BaseDashboardLanesProps {
/**
* Minimum width in pixels per lane. Enables responsive mode: the
* lane count is derived from container width, down to 1.
* Per-tile minimum width in pixels. The effective lane count is
* derived from container width, floored by this value, down to 1.
*
* Composes with `columns`: when both are set, this acts as a floor
* that can reduce the count below `columns` on narrow containers.
*/
minColumnWidth?: number;

columns?: never;
}

export type DashboardLanesProps =
| FixedDashboardLanesProps
| ResponsiveDashboardLanesProps;
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const KEY = 'dashboardGridSettings';
*/
const DEFAULT_GRID_SETTINGS: WidgetGridSettings = {
model: 'grid',
columns: 6,
minColumnWidth: 350,
rowHeight: 200,
};
Expand Down
1 change: 1 addition & 0 deletions routes/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@wordpress/icons": "file:../../packages/icons",
"@wordpress/notices": "file:../../packages/notices",
"@wordpress/preferences": "file:../../packages/preferences",
"@wordpress/primitives": "file:../../packages/primitives",
"@wordpress/private-apis": "file:../../packages/private-apis",
"@wordpress/ui": "file:../../packages/ui",
"@wordpress/warning": "file:../../packages/warning",
Expand Down
Loading
Loading