该软件包仍处于实验阶段。“实验性”意味着这是早期实现,可能会发生剧烈且破坏性的更改。

一组网格布局组件,用于在
仪表板风格的表面上排列图块。

此软件包公开了两个组件,每个组件实现不同的
布局模型:

  • DashboardGrid 是一个二维紧凑网格:图块声明显式的
    (width, height) 跨列/跨行跨度,并且可以跨越多个
    列和行。
  • DashboardLanes 是一种水磨石风格的表面,与新兴的 WebKit 规范 display: grid-lanes 对齐。
    图块仅声明列跨度;高度由内容驱动;
    放置遵循按源排序、最短车道天际线并使用
    flow-tolerance 作为决胜条件的规则。

安装

安装模块:

npm install @wordpress/grid --save

此软件包假设您的代码将在 ES2015+ 环境中运行。
如果您使用的环境对此类语言特性和 API 的支持有限或完全不支持,您应该包含 随
@wordpress/babel-preset-default 提供的 polyfill

到您的代码中。

设置

组件样式是 CSS Modules,在组件挂载时注入运行时;无需排队或导入样式表。

视觉默认值(图块间距、高程、运动、占位符描边)读取由 @wordpress/theme 发布的设计令牌
作为 --wpds-* CSS 自定义属性。

在标准 WordPress 编辑器屏幕内

在标准的 WordPress 编辑器屏幕(如帖子编辑器或站点编辑器)中,设计令牌样式表由 Gutenberg 集中管理。您不需要自行添加任何设置。

在其他地方

在安装并加载应用程序中的设计令牌样式表:

npm install @wordpress/theme
import '@wordpress/theme/design-tokens.css';

如果没有这些令牌,组件仍然功能正常,但间距、
高程和交互视觉效果会失去其预期值。
或者,您可以自行定义软件包消费的 --wpds-* 自定义属性。

选择组件

需求使用
固定单元格图块仪表板,内容填充其单元格。DashboardGrid
水磨石 / 瀑布式表面,内容由高度驱动。DashboardLanes
静态布局原语(无每项状态,无拖拽)。@wordpress/components 中的 __experimentalGrid

这里的两个组件都是更高级别的:用户(而非开发人员)放置和调整图块大小,结果通过
onChangeLayout 发出。对于没有跨度、拖拽或
每项状态的静态 CSS Grid,请使用来自 @wordpress/components 的 __experimentalGrid。


DashboardGrid

一个二维紧凑网格,其中每个子元素都有明确的列和行
跨度。

用法

import { DashboardGrid } from '@wordpress/grid';

const layout = [
    { key: 'a', width: 2, height: 2 },
    { key: 'b', width: 4, height: 1 },
    { key: 'c', width: 'fill', height: 1 },
    { key: 'd', width: 'full', height: 1 },
];

function Dashboard() {
    const [ current, setCurrent ] = useState( layout );

    return (
        <DashboardGrid
            layout={ current }
            columns={ 6 }
            editMode
            onChangeLayout={ setCurrent }
        >
            <div key="a">Tile A</div>
            <div key="b">Tile B</div>
            <div key="c">Tile C</div>
            <div key="d">Tile D</div>
        </DashboardGrid>
    );
}

每个子元素必须具有与 layout 数组中条目匹配的 key 属性。没有匹配布局条目的子元素将渲染在网格末尾,没有显式定位,并回退到 CSS Grid 的自动流。

布局模型

interface DashboardGridLayoutItem {
    key: string; // matches child key
    width?: number | 'fill' | 'full'; // column span (see below)
    height?: number; // rows to span
    order?: number; // lower values render first (responsive mode)
    draggable?: boolean; // default true; false pins it: no drag, holds its index
    resizable?: boolean; // default true: can be resized in edit mode
}

width 是一个区分值的类型:

  • number:跨越那么多列(限制在网格的列数内)。
  • 'fill':填充当前行中剩余的列。
  • 'full':跨越每一列并强制换行;itemLimits 最大值限制跨度。

'fill' 是针对剩余可用空间按行解析的。

属性

属性类型默认值描述
layoutDashboardGridLayoutItem[]—必需。按键子 key 的位置和大小。
childrenReactNode—必需。每个子元素需要一个与布局条目匹配的 key。
columnsnumber6总列数(固定模式)。
minColumnWidthnumber—如果设置,启用响应式模式:从容器宽度派生的列。与 columns 互斥。
rowHeightnumber \| 'auto''auto'行高(像素),或 'auto' 让内容调整行的大小。
itemLimitsRecord< string, GridItemLimits >—每项最小和最大图块大小(像素),按布局项键索引。参见 尺寸限制。
editModebooleanfalse启用拖拽重新排序和调整大小手柄。
onChangeLayout( layout ) => void—当用户提交拖拽或调整大小时触发。
onPreviewLayout( layout ) => void—在拖拽或调整大小期间连续触发,带有进行中的布局。用于实时反馈;onChangeLayout 仍会发出已提交的结果。
renderResizeHandleComponentType< ResizeHandleRenderProps >—覆盖默认的角落三角形调整大小手柄。参见 自定义调整大小手柄。
renderDragPreviewComponentType< DragPreviewRenderProps >—包装在 <DragOverlay> 内部安装的拖拽克隆视觉效果。参见 自定义拖拽预览。
renderGridOverlayComponentType< GridOverlayRenderProps >—覆盖默认的编辑模式叠加层,该叠加层可视化列和行轨道。接收解析后的 columns、rows、rowHeight 和 isActive。
classNamestring—网格根元素的额外类名。
styleCSSProperties—网格根元素的内联样式;网格自身的布局样式优先于它们。

DashboardGrid 将 refs 转发给其根 <div>,并且标准的
<div> 属性(id、aria-*、data-*、事件处理程序、
style 等)都会传递。网格自身的布局样式
(gridTemplateColumns、gridAutoRows)会覆盖用户提供的
style 中这些属性的值。图块间距由设计系统令牌拥有;使用 --wp-grid-gap 覆盖它(参见 使用 CSS 变量进行主题化)。

子级级别属性

子元素使用与其 key 匹配的布局条目进行渲染。可选的属性读取自子元素,让您可以在编辑模式开启时保持控件的交互性:

子级属性类型描述
actionableAreaReactNode渲染在网格项目可拖拽表面上方的内容。适用于关闭按钮、菜单或必须在编辑模式下保持可点击的链接。

模式

固定列

<DashboardGrid layout={ layout } columns={ 12 }>
    { children }
</DashboardGrid>

响应式

列是根据容器宽度计算的,使用 minColumnWidth 作为每列的下限。ResizeObserver 会在容器调整大小时重新计算。

<DashboardGrid layout={ layout } minColumnWidth={ 240 }>
    { children }
</DashboardGrid>

在响应式模式下,布局项可以提供 order 以独立于数组位置控制
显示顺序。

编辑模式

当 editMode 为 true 时:

  • 项目变为可拖拽(由 @dnd-kit 提供支持)。原始图块
    保持原位作为虚线占位符,而克隆体通过 <DragOverlay> 跟随
    光标移动。
  • 每个项目的右下角出现调整大小手柄。随着光标移动,
    实心轮廓会预览目标尺寸。
  • 当任何图块正在拖拽或调整大小时,每个图块上的
    actionableArea 内容被设置为 inert,以便其他图块上的悬停
    无法窃取手势操作。
  • 拖拽或调整大小结束后,onChangeLayout 触发并返回新布局。
  • onPreviewLayout 在交互期间持续触发以提供
    实时反馈;最终提交的布局仍通过
    onChangeLayout 发出。
  • 当布局重新流动时,相邻图块动画移动到它们的新位置。

DashboardLanes

一种与 display: grid-lanes 对齐的瀑布流样式表面。项目
声明列跨度;高度由内容驱动;放置
遵循基于源顺序、最短车道天际线的算法,并使用
flow-tolerance 作为平局决胜条件。

布局模型和放置算法的描述见
Introducing CSS Grid Lanes
WebKit 博客。该软件包在 JavaScript 中实现了相同的模型,
以便在当前尚不支持原生 display: grid-lanes 的浏览器上工作;
天际线 + 容差核心部分改编自 Simon Willison 的
CSS Grid Lanes Polyfill
(MIT)。一旦各浏览器全面支持原生功能,即可在不改变公共 API 的情况下移除 polyfill。

用法

import { DashboardLanes } from '@wordpress/grid';

const layout = [
    { key: 'a' },
    { key: 'hero', width: 2 },
    { key: 'b' },
    { key: 'c' },
];

function Pinboard() {
    const [ current, setCurrent ] = useState( layout );

    return (
        <DashboardLanes
            layout={ current }
            columns={ 4 }
            editMode
            onChangeLayout={ setCurrent }
        >
            <Tile key="a">A</Tile>
            <Tile key="hero">Hero (spans 2 lanes)</Tile>
            <Tile key="b">B</Tile>
            <Tile key="c">C</Tile>
        </DashboardLanes>
    );
}

每个子元素必须具有一个 key 属性,且该属性需匹配
layout 数组中的条目。没有匹配布局条目的子元素将渲染在
表面的末尾,无明确放置位置,并落入车道的自动流动中。

布局模型

interface DashboardLanesLayoutItem {
    key: string; // matches child key
    width?: number; // lanes to span (default 1)
    lane?: number; // 0-indexed: pin to a specific lane
    order?: number; // lower values render first
    draggable?: boolean; // default true; false pins it: no drag, holds its index
    resizable?: boolean; // default true: can be resized in edit mode
}

没有 height 字段:车道使用每个图块的内含内容高度在垂直方向
打包图块。

没有 'fill':在自动放置模式下,行中没有“剩余”的项目;
算法总能找到一条车道。

表示 'full'(跨越整个表面宽度)的方法是设置
width 为车道数量。

要将图块锚定到特定列,请将其 lane 设置为从 0 开始的
位置。固定图块会在自动放置图块之前放置,因此自动图块会
围绕它们流动;超出范围的值(负数,或超过
columns - width)将被钳制。

Props

PropTypeDefaultDescription
layoutDashboardLanesLayoutItem[]—必填。按子元素 key 键控的跨度和顺序。
childrenReactNode—必填。每个子元素需要一个匹配布局条目的 key。
columnsnumber6总车道数(固定模式)。
minColumnWidthnumber—如果设置,启用响应式模式:车道数由容器宽度推导。与 columns 互斥。
flowTolerancenumber16当两个候选车道基线相似时,用于源顺序平局决胜的像素容差。较大的值使图块更接近阅读顺序,但代价是产生更大的空白区域。
rowUnitnumber4Polyfill 的 grid-row-start 数学计算的吸附单位。较小的值会产生更锐利的放置效果,但代价是隐式行数增加。在支持原生 display: grid-lanes 的浏览器上被忽略。
itemLimitsRecord< string, GridItemWidthLimits >—按布局项 key 键控的每个项目的最小和最大图块宽度(像素)。参见 Size limits。
editModebooleanfalse启用拖拽重排序和水平调整大小。
onChangeLayout( layout ) => void—用户提交拖拽或调整大小时触发。
onPreviewLayout( layout ) => void—在拖拽或调整大小期间持续触发。
renderResizeHandleComponentType< ResizeHandleRenderProps >—覆盖默认侧边 grip 调整大小手柄。参见 Custom resize handle。
renderDragPreviewComponentType< DragPreviewRenderProps >—包装挂载在 <DragOverlay> 内的拖拽克隆体视觉效果。参见 Custom drag preview。
renderGridOverlayComponentType< GridOverlayRenderProps >—覆盖默认的编辑模式叠加层,用于可视化车道轨道。接收解析后的 columns 和 isActive;由于高度由内容驱动,车道不传递行度量数据。
classNamestring—表面根元素的额外类名。
styleCSSProperties—表面根元素的内联样式;表面自身的布局样式优先级高于此样式。

原生 vs Polyfill

DashboardLanes 在挂载时检查一次 CSS.supports( 'display', 'grid-lanes' )。

  • 当受支持时(Safari 26+ 及其他随规范发布的浏览器),组件
    发出 display: grid-lanes 及其规范的 CSS,并由引擎处理布局。
    放置层不挂载每个图块的观察者;剩余的唯一定量 ResizeObserver
    是用于响应式模式和调整大小步长计算的容器宽度观察者。
  • 当不受支持时,内部钩子 (useLanePlacement) 使用 ResizeObserver
    测量每个图块的高度,运行基于源顺序的最短车道算法,并在每个
    图块上发出明确的 grid-column-start 和 grid-row-start /
    grid-row-end: span N 值。

两条路径均保留相同的 DOM 契约;视觉效果相同。

编辑模式

拖拽重排序的工作原理与 DashboardGrid 相同。调整大小
仅水平方向:图块高度由内容驱动,因此没有垂直调整大小手势。
默认手柄是一个位于尾边缘中心的垂直条;光标样式为 ew-resize。

当布局重新流动时,相邻图块动画移动到它们的新位置。


共享主题

性能

onPreviewLayout 会在每次手势帧重新渲染父组件。为了
防止组件内部的子元素遍历在每帧重新运行,当内容稳定时,请对子元素数组进行记忆化:

const tiles = useMemo(
    () => layout.map( ( item ) => <Tile key={ item.key }>...</Tile> ),
    [ layout ]
);

return (
    <DashboardLanes layout={ layout } editMode onPreviewLayout={ ... }>
        { tiles }
    </DashboardLanes>
);

如果不这样做,表面仍然可以工作,但会在每次预览更新时遍历子元素;
开销在约 50 个图块以内较小,之后会逐渐增大。对于 DashboardLanes,
放置运行在一个 useLayoutEffect 中,并被节流至每次测量爆发一帧。

尺寸限制

itemLimits 声明按布局项 key 键控的每个项目的像素级下限和上限:

<DashboardGrid
    layout={ layout }
    rowHeight={ 80 }
    itemLimits={ {
        chart: { minWidth: 320, minHeight: 200 },
        note: { maxWidth: 500 },
    } }
>
    { tiles }
</DashboardGrid>

每个表面对其当前几何结构的限制进行量化:最小值向上取整,最大值向下取整,且量化的最小值优先于较小的量化最大值。结果限制了渲染跨度调整手势的范围,因此调整大小永远不会提交超出该范围的跨度。

限制不会写入布局。存储在限制之外的跨度在渲染时会被限制,而存储的数据保持不变,因此限制更改适用于所有现有布局。调整大小会将调整后的图块以其新跨度提交,重新排序仅提交顺序;其他所有图块保持其存储的跨度,即使在渲染受限时也是如此。

'full' 和 'fill' 宽度遵循最大值:'full' 以裁剪后的跨度渲染,并像具有该宽度的固定项一样放置,'fill' 至少保留其最小值且不超过其最大值。当 rowHeight 为数值时应用高度限制,并且在与 'auto' 行一起使用时保持开放。DashboardLanes 接受 GridItemWidthLimits:列高由内容驱动。

无障碍性

拖放重排序可通过键盘使用 @dnd-kit 的键盘传感器操作:

  • Tab 聚焦图块。
  • Space 拾取它。
  • 方向键在位置之间移动它。
  • Space 放下,或 Escape 取消。

调整大小句柄目前仅支持指针设备。

自定义调整大小句柄

两个组件都接受 renderResizeHandle 属性以覆盖默认视觉效果。表面拥有调整大小的数学逻辑(列/行步进、节流增量循环、布局提交),并将手势接线(ref、listeners、attributes)作为属性传递给消费者,以便将其展开到应接收指针事件的元素上。用于调整大小手势的 dnd-kit <DndContext> 位于句柄包装器内部;消费者无需挂载自己的上下文。

import { Icon } from '@wordpress/ui';
import { resizeCornerNE } from '@wordpress/icons';

function CustomResizeHandle( {
    ref,
    listeners,
    attributes,
    verticalResizable,
} ) {
    return (
        <div
            ref={ ref }
            { ...listeners }
            { ...attributes }
            style={ {
                position: 'absolute',
                bottom: 4,
                insetInlineEnd: 4,
                cursor: verticalResizable ? 'nwse-resize' : 'ew-resize',
            } }
        >
            <Icon icon={ resizeCornerNE } size={ 16 } />
        </div>
    );
}

<DashboardGrid
    layout={ layout }
    editMode
    renderResizeHandle={ CustomResizeHandle }
>
    { tiles }
</DashboardGrid>;

该组件接收以下属性:

PropTypeDescription
ref( node ) => voiddnd-kit ref;分配给承载手势的元素。
listenersSyntheticListenerMap \| undefined指针/键盘监听器;展开到同一元素。
attributesDraggableAttributes无障碍/dnd-kit 属性;与 listeners 一起展开。
verticalResizableboolean在 DashboardLanes 和带有 rowHeight: 'auto' 的 DashboardGrid 上为 False。可用于适应光标或视觉提示。
isResizingboolean用户正在主动拖动此句柄时为 True。在手势期间用于交换颜色、图标或变换。
itemIdstring所属图块的 key。

句柄仅在表面处于编辑模式时挂载(editMode={ true }),因此自定义组件无需对禁用状态进行短路处理。

自定义拖放预览

当图块被拖动时,dnd-kit 会将其克隆到 <DragOverlay> 中跟随光标。两个表面都用一个薄的功能框架(scale、cursor: grabbing、pointer-events:
none
)包裹该克隆体以显示提升效果,但它们不会施加额外的视觉装饰:消费者应用到图块子级上的任何样式都会原样传递到拖动的克隆体上。

当拖动状态需要在结构上与持久图块不同(更强的阴影、不同的边框、额外的徽章等)时,请传递 renderDragPreview 组件。表面将其安装在功能框架内,并提供克隆的子级以及活动图块的 key:

import { DashboardGrid } from '@wordpress/grid';
import type { DragPreviewRenderProps } from '@wordpress/grid';

function DragPreview( { children }: DragPreviewRenderProps ) {
    return (
        <div className="my-tile-while-dragging">
            { children }
        </div>
    );
}

<DashboardGrid
    layout={ layout }
    editMode
    renderDragPreview={ DragPreview }
>
    { tiles }
</DashboardGrid>;

该组件接收以下属性:

PropTypeDescription
childrenReactNode表面在 <DragOverlay> 内安装的克隆图块内容。将其放置在视觉包装器期望图块主体的位置。
itemIdstring所属图块的 key。当装饰因图块而异时非常有用。

对于仅涉及 token 的调整(提升缩放比例、占位符不透明度、轮廓颜色、占位符半径),请优先使用下面的 CSS 变量;仅在拖动状态需要持久图块所没有的标记时才使用 renderDragPreview。

使用 CSS 变量进行主题设置

两个表面公开了一组小的 CSS 自定义属性,用于需要在消费者之间灵活变化的视觉效果,而无需编写渲染属性。在表面的根祖先元素上(或通过 style 在根元素本身)覆盖它们。所有值都有合理的默认回退。

VariableDefaultApplies to
--wp-grid-gapvar(--wpds-dimension-gap-xl)DashboardGrid、DashboardLanes 和编辑叠加层上图块之间的间隙。
--wp-grid-drag-preview-scale1.05拖放预览功能框架的提升缩放比例。设置为 1 可禁用提升效果。
--wp-grid-drag-preview-radius0拖放预览功能框架的边框半径,以便提升阴影跟随消费者的图块形状。
--wp-grid-placeholder-opacity0.4占位符图块的不透明度(拖动飞行中的原始项目)。
--wp-grid-placeholder-outline-styledashed拖放占位符的轮廓样式(例如 solid 或 dotted)。
--wp-grid-resize-preview-outline-stylesolid调整大小预览叠加层的边框样式(例如 dashed 或 dotted)。
--wp-grid-placeholder-outline-colorvar(--wpds-color-stroke-interactive-brand)占位符和调整大小预览叠加层的轮廓颜色。
--wp-grid-placeholder-radius0占位符的边框半径,用于匹配消费者的图块形状,以便轮廓跟踪正确的剪影。
--wp-grid-overlay-tile-bgvar(--wpds-color-background-surface-neutral-weak)默认编辑模式叠加层绘制的标记图块的背景。

为此包做出贡献

这是 Gutenberg 项目的一个独立包。
该项目组织为一个 monorepo。它由多个自包含的软件包组成,每个包都有特定的用途。此 monorepo 中的包发布到 npm
并被 WordPress 以及其他软件项目使用。

要了解有关为此包或整个 Gutenberg 做出贡献的更多信息,请阅读项目的
贡献者指南。