用途
本技能用于在 Canvas 中建模可重复内容(列表、卡片、条目集合),采用基于插槽(slot)的父子组件模式。当需要构建列表/网格、把数组属性转换为插槽结构时适用。
插槽让组件能够在指定区域接收并渲染子组件。本技能聚焦可重复内容模式,尤其是因为 Canvas 不支持包含复杂对象的数组属性。
关于更广泛的插槽架构与拆分指导,可参考 canvas-component-composability。
Canvas 限制:不支持包含对象的数组属性
Canvas 会拒绝定义了嵌套对象条目数组属性的组件:
# This will fail to upload
props:
properties:
items:
title: Items
type: array
items:
type: object
properties:
title:
type: string
description:
type: string
linkUrl:
type: string
上传会失败并报错:
Drupal Canvas does not know of a field type/widget to allow populating the
items prop, with the shape {...}. [props.items.items] 'items' is not a
supported key. [props.items.type] The value you selected is not a valid
choice.
简单的基本类型数组是受支持的:
# OK: Array of simple strings
props:
properties:
tags:
title: Tags
type: array
items:
type: string
examples:
- - tag1
- tag2
模式:插槽 + 子组件
不要使用数组属性,而是为重复项创建一个独立的子组件,并在父组件中使用插槽。
第 1 步:创建子组件
把条目结构抽取到它自己的组件中:
# src/components/feature-card/component.yml
name: Feature Card
machineName: feature-card
status: true
required:
- title
props:
properties:
title:
title: Title
type: string
examples:
- Feature One
description:
title: Description
type: string
examples:
- A brief description of this feature and its benefits.
linkText:
title: Link Text
type: string
examples:
- Learn more
linkUrl:
title: Link URL
type: string
format: uri-reference
examples:
- /features/feature-one
slots: []
第 2 步:更新父组件以使用插槽
用插槽替换数组属性:
遵循 canvas-component-metadata 的插槽 schema:已定义的插槽使用对象映射,仅当组件没有插槽时才使用 slots: []。
# src/components/features-section/component.yml
name: Features Section
machineName: features-section
status: true
required:
- heading
props:
properties:
heading:
title: Heading
type: string
examples:
- Our Features
slots:
features:
title: Features
第 3 步:更新 JSX 以渲染插槽
在父组件的 JSX 中,把插槽作为 children 渲染:
// src/components/features-section/index.jsx
const FeaturesSection = ({ heading, features, className }) => {
return (
<div className={className}>
<h2>{heading}</h2>
{/* Render the slot content */}
<div className="flex gap-10">{features}</div>
</div>
);
};
第 4 步:更新故事以使用子组件
在 Storybook 故事中,用子组件组合父组件:
// src/stories/features-section.stories.jsx
import FeatureCard from "@/components/feature-card";
import FeaturesSection from "@/components/features-section";
export const Default = {
args: {
heading: "Our Features",
features: (
<>
<FeatureCard
title="Feature One"
description="A brief description of this feature..."
linkText="Learn more"
linkUrl="/features/feature-one"
/>
<FeatureCard
title="Feature Two"
description="Another feature with its own benefits..."
linkText="Learn more"
linkUrl="/features/feature-two"
/>
</>
),
},
};
常见模式
以下组件模式总是需要插槽配合子组件:
| 父组件 | 子组件 | 用例 |
|---|---|---|
| footer | footer-link-group | 页脚导航列 |
| footer-link-group | footer-link | 页脚分组内的链接 |
| card-grid | card | 卡片网格/轮播 |
| testimonials | testimonial-card | 客户评价 |
| cta-section | cta-card | 行动号召卡片 |
| features-section | feature-card | 功能亮点卡片 |
| stats-hero | stat-item | 统计数据显示 |
| metadata-list | metadata-item | 键值元数据对 |
| carousel | carousel-item | 轮播幻灯片 |
插槽容器最小尺寸
当插槽位于根据内容确定尺寸的容器内(flex 项、grid 项、行内元素)时,容器在空置时会塌缩为零尺寸。这会阻止 Canvas 编辑器显示拖放区域覆盖层。
问题示例:
// Container collapses when slot is empty
const Header = ({ branding, navigation }) => {
return (
<header className="flex items-center justify-between">
<div>{branding}</div>
<nav>{navigation}</nav>
</header>
);
};
解决方案:添加最小尺寸,使插槽在空置时仍可交互:
// Minimum width ensures the slot is always interactive
const Header = ({ branding, navigation }) => {
return (
<header className="flex items-center justify-between">
<div className="min-w-32">{branding}</div>
<nav>{navigation}</nav>
</header>
);
};
何时应用最小尺寸:
- Flex 项(flex 父级)——水平布局使用 min-w-,垂直布局使用 min-h-
- Grid 项(grid 父级)——按需使用 min-w-* 和/或 min-h-*
- 行内元素(inline、inline-block、inline-flex)——使用 min-w-*
何时不需要最小尺寸:
- 插槽容器已有固定宽度/高度
- 容器使用块级布局(默认全宽)
- 插槽是唯一内容且父级已有定义尺寸
子组件命名
为插槽创建子组件时:
- 使用能表明条目类型的描述性名称:feature-card、testimonial-card、team-member
- 对于通用条目,加后缀 -item:metadata-item、carousel-item、list-item
- 对于链接相关条目,要具体:footer-link、nav-link、social-link
- 始终为每个子组件创建对应的故事文件。