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
5 changes: 5 additions & 0 deletions packages/grid/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@

### Enhancements

- Rework the default edit-mode `GridOverlay` to paint per-row marker
tiles (with `border-radius` md) inside each column instead of
column backgrounds, outlines, and repeating row dividers. Theme via
`--wp-grid-overlay-tile-bg`.
`GridOverlayRenderProps` now includes `rows` for uniform-row grids.
- Add `--wp-grid-placeholder-outline-style` and
`--wp-grid-resize-preview-outline-style` CSS custom properties for
the drag-placeholder outline (default `dashed`) and resize-preview
Expand Down
37 changes: 29 additions & 8 deletions packages/grid/src/dashboard-grid/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,12 @@ export const DashboardGrid = forwardRef< HTMLDivElement, DashboardGridProps >(

const rootRef = useRef< HTMLDivElement >( null );
const [ containerWidth, setContainerWidth ] = useState( 0 );
const [ containerHeight, setContainerHeight ] = useState( 0 );
const [ gapPx, setGapPx ] = useState( FALLBACK_GAP_PX );
const resizeObserverRef = useResizeObserver(
( [ { contentRect } ] ) => {
setContainerWidth( contentRect.width );
setContainerHeight( contentRect.height );
}
);
const mergedGridRef = useMergeRefs( [
Expand All @@ -174,10 +176,13 @@ export const DashboardGrid = forwardRef< HTMLDivElement, DashboardGridProps >(
if ( ! rootRef.current ) {
return;
}
const { width } = rootRef.current.getBoundingClientRect();
const { width, height } = rootRef.current.getBoundingClientRect();
if ( width > 0 ) {
setContainerWidth( width );
}
if ( height > 0 ) {
setContainerHeight( height );
}
const parsed = Number.parseFloat(
window.getComputedStyle( rootRef.current ).columnGap
);
Expand Down Expand Up @@ -520,28 +525,44 @@ export const DashboardGrid = forwardRef< HTMLDivElement, DashboardGridProps >(
</div>
) : null;

// Edit-mode background visual. Default paints diagonal stripes
// and dashed track guides; a consumer can replace it via
// `renderGridOverlay` while reusing the resolved column count,
// gap, and row height. `'auto'` collapses to `undefined` for
// the overlay so it falls back to columns-only (row dividers
// have no anchor when the row height is content-driven).
// Edit-mode background visual. Default paints row-marker tiles
// per column; a consumer can replace it via `renderGridOverlay`
// while reusing the resolved column count, row height, and row
// count. `'auto'` collapses to `undefined` for the overlay so
// row markers are omitted when the row height is content-driven.
// Rendered unconditionally so the overlay can cross-fade on
// edit-mode toggles; `isActive` drives the opacity transition
// inside the overlay. Memoized so drag/resize re-renders skip
// reconciliation while inputs are stable.
const Overlay = renderGridOverlay ?? GridOverlay;
const overlayRowHeight =
typeof rowHeight === 'number' ? rowHeight : undefined;
const overlayRows = useMemo( () => {
if ( overlayRowHeight === undefined || containerHeight <= 0 ) {
return undefined;
}
const rowTile = overlayRowHeight + gapPx;
return Math.max(
1,
Math.floor( ( containerHeight + gapPx ) / rowTile )
);
}, [ overlayRowHeight, containerHeight, gapPx ] );
const gridOverlay = useMemo(
() => (
<Overlay
columns={ effectiveColumns }
rowHeight={ overlayRowHeight }
rows={ overlayRows }
isActive={ editMode }
/>
),
[ Overlay, editMode, effectiveColumns, overlayRowHeight ]
[
Overlay,
editMode,
effectiveColumns,
overlayRowHeight,
overlayRows,
]
);

return (
Expand Down
12 changes: 4 additions & 8 deletions packages/grid/src/dashboard-grid/stories/index.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -480,15 +480,11 @@ export const RowHeight: Story = {
/**
* Edit mode with drag, resize, and all width modes. While `editMode`
* is on, `<DashboardGrid />` paints its default overlay behind the
* tiles to visualize the underlying template: diagonal stripes, a
* dashed outline on each column track, a subtle column fill that
* marks the drop zones against the gaps, and a 1px row divider when
* `rowHeight` is numeric. The overlay disappears when `editMode`
* flips back to `false`.
* tiles to visualize the underlying template: rounded row-marker
* tiles in each column when `rowHeight` is numeric. The overlay
* disappears when `editMode` flips back to `false`.
*
* Theme the default look in place via CSS custom properties exposed
* by the package (`--wp-grid-overlay-stripe-color`,
* `--wp-grid-overlay-track-color`, `--wp-grid-overlay-column-fill`),
* Theme the default look in place via `--wp-grid-overlay-tile-bg`,
* or replace the visual wholesale by passing `renderGridOverlay`.
* See the `Custom Grid Overlay` story for a full override example.
*
Expand Down
8 changes: 4 additions & 4 deletions packages/grid/src/dashboard-grid/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,10 @@ export interface DashboardGridProps
renderDragPreview?: React.ComponentType< DragPreviewRenderProps >;

/**
* Override the default edit-mode overlay (diagonal stripes plus
* dashed column and row tracks) with a custom component. The grid
* supplies the resolved column count, gap, and row height; the
* consumer is responsible for the visual.
* Override the default edit-mode overlay (row-marker tiles per
* column) with a custom component. The grid supplies the resolved
* column count, row height, and row count; the consumer is
* responsible for the visual.
*
* The overlay only renders when `editMode` is true. When omitted,
* the package's default visual is used.
Expand Down
10 changes: 4 additions & 6 deletions packages/grid/src/dashboard-lanes/stories/index.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -288,14 +288,12 @@ export const Spanning: Story = {
* new layout via `onChangeLayout`.
*
* While `editMode` is on, `<DashboardLanes />` paints its default
* overlay behind the tiles to mark the lane tracks: diagonal stripes
* plus a dashed outline and subtle fill on each column. Lanes paint
* columns only — there are no row dividers because heights are
* overlay behind the tiles to mark the lane tracks. Lanes paint
* columns only — there are no row markers because heights are
* content-driven.
*
* Theme the default look in place via CSS custom properties
* (`--wp-grid-overlay-stripe-color`, `--wp-grid-overlay-track-color`,
* `--wp-grid-overlay-column-fill`), or replace the visual wholesale
* Theme the default look in place via `--wp-grid-overlay-tile-bg`,
* or replace the visual wholesale
* by passing `renderGridOverlay`. See the `Custom Grid Overlay`
* story below for a full override example.
*/
Expand Down
8 changes: 4 additions & 4 deletions packages/grid/src/dashboard-lanes/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,10 @@ export interface DashboardLanesProps
renderDragPreview?: React.ComponentType< DragPreviewRenderProps >;

/**
* Override the default edit-mode overlay (diagonal stripes plus
* dashed column track guides) with a custom component. Lanes are
* content-driven vertically, so no `rowHeight` is supplied and the
* default visual paints columns only.
* Override the default edit-mode overlay (empty column tracks) with
* a custom component. Lanes are content-driven vertically, so no
* `rowHeight` or `rows` is supplied and the default visual paints
* columns only.
*
* The overlay only renders when `editMode` is true. When omitted,
* the package's default visual is used.
Expand Down
87 changes: 58 additions & 29 deletions packages/grid/src/shared/grid-overlay.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,48 +6,77 @@
pointer-events: none;
opacity: 0;
visibility: hidden;
transition:
opacity 200ms ease,
visibility 0s linear 200ms;
background-image:
repeating-linear-gradient(135deg, var(--wp-grid-overlay-stripe-color, color-mix(in srgb, var(--wpds-color-bg-surface-info) 15%, transparent))
0 6px, transparent 6px 12px);
}

.overlay.is-active {
opacity: 1;
visibility: visible;
transition:
opacity 200ms ease,
visibility 0s linear 0s;
}

/*
* Alpha wave: row-marker tiles (or column tracks on lanes) reveal in a
* diagonal sweep from the top-left using staggered delays.
*/
@media not (prefers-reduced-motion) {
.overlay.is-active .row,
.overlay.is-active:not(.has-rows) .column {
opacity: 0;
transform: scale(0.64);
animation:
grid-overlay-tile-in
var(--wpds-motion-duration-md) var(--wpds-motion-easing-subtle)
both;
animation-delay: calc(var(--wp-grid-overlay-wave-delay-step, 28ms) * (var(--wp-grid-overlay-column-index, 0) + var(--wp-grid-overlay-row-index, 0)));
}

@keyframes grid-overlay-tile-in {
to {
opacity: 1;
transform: scale(1);
}
}

.overlay:not(.is-active) {
transition:
opacity var(--wpds-motion-duration-sm)
var(--wpds-motion-easing-subtle),
visibility 0s linear var(--wpds-motion-duration-sm);
}

.overlay:not(.is-active) .row,
.overlay:not(.is-active) .column {
animation: none;
transform: none;
}
}

@media (prefers-reduced-motion: reduce) {
.overlay {
transition: none;
transition:
opacity var(--wpds-motion-duration-sm)
var(--wpds-motion-easing-subtle),
visibility 0s linear var(--wpds-motion-duration-sm);
}

.overlay.is-active {
transition:
opacity var(--wpds-motion-duration-sm)
var(--wpds-motion-easing-subtle),
visibility 0s linear 0s;
}
}

.column {
background-color: var(--wp-grid-overlay-column-fill, color-mix(in srgb, var(--wpds-color-bg-surface-info) 20%, transparent));
position: relative;
display: flex;
flex-direction: column;
gap: var(--wpds-dimension-gap-md);
min-width: 0;
}

/*
* Dashed row dividers framing every row. A `::after` pseudo-element
* tiles two 1px strokes per row (top of the row area and bottom of
* the row area, leaving the gap clear between them) and is masked
* with a horizontal dash pattern so the strokes render dashed instead
* of solid.
*/
.has-rows .column::after {
content: "";
position: absolute;
inset: 0;
pointer-events: none;
background-image: linear-gradient(to bottom, var(--wp-grid-overlay-track-color, var(--wpds-color-stroke-surface-neutral)) 0, var(--wp-grid-overlay-track-color, var(--wpds-color-stroke-surface-neutral)) 1px, transparent 1px, transparent calc(var(--wp-grid-overlay-row-height) - 1px), var(--wp-grid-overlay-track-color, var(--wpds-color-stroke-surface-neutral)) calc(var(--wp-grid-overlay-row-height) - 1px), var(--wp-grid-overlay-track-color, var(--wpds-color-stroke-surface-neutral)) var(--wp-grid-overlay-row-height), transparent var(--wp-grid-overlay-row-height), transparent var(--wp-grid-overlay-row-tile));
background-size: 100% var(--wp-grid-overlay-row-tile);
background-repeat: repeat;
mask-image: repeating-linear-gradient(to right, #000 0 4px, transparent 4px 8px);
-webkit-mask-image: repeating-linear-gradient(to right, #000 0 4px, transparent 4px 8px);
.row {
flex: 0 0 var(--wp-grid-overlay-row-height);
height: var(--wp-grid-overlay-row-height);
box-sizing: border-box;
border-radius: var(--wpds-border-radius-lg);
background-color: var(--wp-grid-overlay-tile-bg, var(--wpds-color-bg-surface-neutral-weak));
}
69 changes: 52 additions & 17 deletions packages/grid/src/shared/grid-overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,60 +3,73 @@
*/
import clsx from 'clsx';

/**
* WordPress dependencies
*/
import { useEffect, useState } from '@wordpress/element';

/**
* Internal dependencies
*/
import type { GridOverlayRenderProps } from './types';
import styles from './grid-overlay.module.css';

/**
* Default edit-mode overlay. Paints diagonal stripes behind the tiles
* to mark the dashboard surface, plus dashed outlines on each column
* track and (when `rowHeight` is supplied) repeating dividers for the
* row tracks.
* Default edit-mode overlay. Renders one column per track; when
* `rowHeight` and `rows` are supplied, each column holds that many
* row-marker tiles sized to the uniform row height.
*
* Used by both `DashboardGrid` and `DashboardLanes`. Replaced wholesale
* by passing a `renderGridOverlay` to either surface; themed in place
* via the CSS custom properties documented in the package README.
*
* Cross-fades in and out on `isActive` toggles via a CSS opacity
* transition; while inactive, `visibility: hidden` releases paint cost.
* Reveals with a diagonal alpha wave from the top-left corner when
* `isActive` becomes true (motion design tokens for duration and
* easing). Fades out on deactivate; while inactive, `visibility:
* hidden` releases paint cost.
*
* The overlay inherits its gap from the same design-system gap token
* the surfaces use, so columns and row dividers stay pixel-aligned
* the surfaces use, so columns and row markers stay pixel-aligned
* without the surface having to forward a `spacing` value.
*
* @param props Render props supplied by the surface.
* @param props.columns Number of column tracks to mirror.
* @param props.rowHeight Row height in pixels for surfaces with uniform
* rows. Omitted on lane surfaces or auto-sized
* grids; in that case row dividers are skipped.
* grids; in that case row markers are skipped.
* @param props.rows Number of row tracks to mirror in each column.
* Omitted when row height is unknown.
* @param props.isActive When `false`, the overlay fades out and stops
* consuming paint cost.
*/
export function GridOverlay( {
columns,
rowHeight,
rows,
isActive,
}: GridOverlayRenderProps ) {
const showRows = typeof rowHeight === 'number';
// `--wp-grid-overlay-row-height` and `--wp-grid-overlay-row-tile`
// drive the row-divider stops so the CSS file stays static while
// the values come from the live grid. The tile uses the same gap
// token the overlay's `gap` resolves to, so the math stays aligned
// without the surface passing a numeric value through.
const showRows =
typeof rowHeight === 'number' && typeof rows === 'number' && rows > 0;
// Bump the key when edit mode activates so CSS animations restart on
// each enter (the overlay stays mounted across toggles).
const [ waveKey, setWaveKey ] = useState( 0 );
useEffect( () => {
if ( isActive ) {
setWaveKey( ( key ) => key + 1 );
}
}, [ isActive ] );
const style: React.CSSProperties = {
gridTemplateColumns: `repeat(${ columns }, minmax(0, 1fr))`,
...( showRows
? ( {
'--wp-grid-overlay-row-height': `${ rowHeight }px`,
'--wp-grid-overlay-row-tile': `calc(${ rowHeight }px + var(--wpds-dimension-gap-md))`,
} as React.CSSProperties )
: {} ),
};

return (
<div
key={ waveKey }
aria-hidden
className={ clsx(
styles.overlay,
Expand All @@ -65,8 +78,30 @@ export function GridOverlay( {
) }
style={ style }
>
{ Array.from( { length: columns } ).map( ( _, i ) => (
<div key={ i } className={ styles.column } />
{ Array.from( { length: columns }, ( _column, columnIndex ) => (
<div
key={ columnIndex }
className={ styles.column }
style={
{
'--wp-grid-overlay-column-index': columnIndex,
'--wp-grid-overlay-row-index': 0,
} as React.CSSProperties
}
>
{ showRows &&
Array.from( { length: rows }, ( _row, rowIndex ) => (
<div
key={ rowIndex }
className={ styles.row }
style={
{
'--wp-grid-overlay-row-index': rowIndex,
} as React.CSSProperties
}
/>
) ) }
</div>
) ) }
</div>
);
Expand Down
Loading
Loading