Loading...
Loading...
Design Canvas-ready React components with slots and decomposition-first patterns. Use when (1) Designing a component's prop/slot structure, (2) A component is growing too large, (3) Deciding between props vs slots, (4) Refactoring monolithic components. Ensures Canvas compatibility.
npx skill4agent add drupal-canvas/skills canvas-component-composability// Wrong
const ResourceDetail = ({
metadata: [
{ label: "Type", value: "Report" },
{ label: "Author", value: "UNICEF" },
],
}) => (
<div>
{metadata.map((item) => (
<MetadataItem key={item.label} {...item} />
))}
</div>
);
// Correct
const ResourceMetadata = ({ items }) => (
<div className="flex flex-col gap-2">{items}</div>
);
// Usage: pass MetadataItem components through the slot
<ResourceMetadata
items={
<>
<MetadataItem label="Type" value="Report" />
<MetadataItem label="Author" value="UNICEF" />
</>
}
/>;| Use slots for | Use props for |
|---|---|
| Variable number of child components | Single, required values (text, URL) |
| Content that users should compose | Configuration options (size, color) |
| Complex nested structures | Simple data (strings, booleans) |
| Content that varies between instances | Content consistent across instances |
component.ymlslots:
content:
title: Content
sidebar:
title: Sidebarconst TwoColumnLayout = ({ content, sidebar }) => (
<div className="grid grid-cols-[1fr_300px] gap-8">
<div>{content}</div>
<aside>{sidebar}</aside>
</div>
);// Wrong
const ResourceDetail = ({
breadcrumbItems,
title,
date,
taxonomyTag,
coverImage,
downloadButtonUrl,
metadata,
description,
}) => (
<div>
<Breadcrumb items={breadcrumbItems} />
<Heading text={title} element="h1" />
{/* ... */}
</div>
);
// Correct
const ResourceDetailPage = () => (
<PageLayout>
<Section width="wide" content={<Breadcrumb items={breadcrumbItems} />} />
<Section width="wide" content={<Heading text={title} element="h1" />} />
<Section width="wide" content={<ArticleMeta date="May 2023" taxonomyTag="Climate" />} />
<Section width="wide" content={/* ... */} />
</PageLayout>
);| Pattern | Extract to component |
|---|---|
| Date + category/tag | |
| Cover image + download button | |
| Label + value pairs | |
| Icon + text link | |
grid-container// Wrong
const ResourceDetail = ({ leftContent, rightContent }) => (
<div className="flex gap-10">
<div className="w-[300px]">{leftContent}</div>
<div className="flex-1">{rightContent}</div>
</div>
);
// Correct
<GridContainer
layout="25-75"
gap="extra_large"
content={
<>
<ResourceCover image={coverImage} />
<div className="flex flex-col gap-5">
<ResourceMetadata items={metadata} />
<Text text={description} />
</div>
</>
}
/>;