Component Panel
Composed component panel with tabs and cascading selection
"use client";
import * as React from "react";
import { ComponentPanel } from "@/components/composed/component-panel/component-panel";
export function ComponentPanelDefault() {
const categories = [
{
value: "all",
label: "全部",
options: [
{ value: "comp1", label: "组件1", tooltip: "这是组件1的说明" },
{ value: "comp2", label: "组件2", tooltip: "这是组件2的说明" },
{ value: "comp3", label: "组件3", tooltip: "这是组件3的说明" },
{ value: "comp4", label: "组件4", tooltip: "这是组件4的说明" },
{ value: "comp5", label: "组件5", tooltip: "这是组件5的说明" },
{ value: "comp6", label: "组件6", tooltip: "这是组件6的说明" },
{ value: "comp7", label: "组件7", tooltip: "这是组件7的说明" },
{ value: "comp8", label: "组件8", tooltip: "这是组件8的说明" },
],
},
{
value: "mcp",
label: "MCP",
options: [
{ value: "mcp1", label: "MCP组件1", tooltip: "MCP组件1" },
{ value: "mcp2", label: "MCP组件2", tooltip: "MCP组件2" },
{ value: "mcp3", label: "MCP组件3", tooltip: "MCP组件3" },
{ value: "mcp4", label: "MCP组件4", tooltip: "MCP组件4" },
],
},
{
value: "tool",
label: "工具",
options: [
{ value: "tool1", label: "工具1", tooltip: "工具1说明" },
{ value: "tool2", label: "工具2", tooltip: "工具2说明" },
{ value: "tool3", label: "工具3", tooltip: "工具3说明" },
{ value: "tool4", label: "工具4", tooltip: "工具4说明" },
{ value: "tool5", label: "工具5", tooltip: "工具5说明" },
],
},
{
value: "workflow",
label: "工作流",
options: [
{ value: "wf1", label: "工作流1", tooltip: "工作流1" },
{ value: "wf2", label: "工作流2", tooltip: "工作流2" },
{ value: "wf3", label: "工作流3", tooltip: "工作流3" },
{ value: "wf4", label: "工作流4", tooltip: "工作流4" },
{ value: "wf5", label: "工作流5", tooltip: "工作流5" },
{ value: "wf6", label: "工作流6", tooltip: "工作流6" },
],
},
];
return (
<div className="w-full h-full max-w-4xl">
<ComponentPanel
categories={categories}
defaultValue={["comp1", "comp3", "mcp1", "tool1", "wf1"]}
defaultActiveTab="all"
onChange={(values) => {
console.log("选中的值:", values);
}}
/>
</div>
);
}
Component Panel 是一个级联选择面板组件,通过选项卡切换不同分类,在每个分类下选择选项。支持单选和多选模式,受控和非受控状态,适用于功能开关、工具选择、权限配置等场景。
概述
- 级联选择:通过选项卡切换分类,在分类下选择具体选项
- 灵活模式:支持单选和多选两种模式
- 受控/非受控:同时支持受控和非受控状态管理
- 标准数据格式:使用业界标准的 value/label 数据结构
- 类型安全:完整的 TypeScript 类型定义
- 响应式布局:自适应不同屏幕尺寸,移动端友好
快速开始
import { ComponentPanel } from "@/registry/wuhan/composed/component-panel";
export function Example() {
const categories = [
{
value: "all",
label: "全部",
options: [
{ value: "comp1", label: "组件1" },
{ value: "comp2", label: "组件2" },
],
},
{
value: "tools",
label: "工具",
options: [
{ value: "tool1", label: "工具1" },
{ value: "tool2", label: "工具2" },
],
},
];
return (
<ComponentPanel
categories={categories}
defaultValue={["comp1", "tool1"]}
onChange={(values) => console.log(values)}
/>
);
}特性
- 标准化数据结构:使用 value/label 格式,与主流组件库保持一致
- 多种选择模式:支持多选(默认)和单选模式
- 双向状态控制:选项选择和选项卡切换均支持受控/非受控
- 禁用状态:支持禁用整个分类或单个选项
- 图标支持:选项可配置自定义图标
- 工具提示:支持为选项添加 tooltip 说明
- 响应式网格:选项列表自适应屏幕宽度
安装
代码演示
基本
基础多选模式,使用非受控状态。
"use client";
import * as React from "react";
import { ComponentPanel } from "@/components/composed/component-panel/component-panel";
export function ComponentPanelDefault() {
const categories = [
{
value: "all",
label: "全部",
options: [
{ value: "comp1", label: "组件1", tooltip: "这是组件1的说明" },
{ value: "comp2", label: "组件2", tooltip: "这是组件2的说明" },
{ value: "comp3", label: "组件3", tooltip: "这是组件3的说明" },
{ value: "comp4", label: "组件4", tooltip: "这是组件4的说明" },
{ value: "comp5", label: "组件5", tooltip: "这是组件5的说明" },
{ value: "comp6", label: "组件6", tooltip: "这是组件6的说明" },
{ value: "comp7", label: "组件7", tooltip: "这是组件7的说明" },
{ value: "comp8", label: "组件8", tooltip: "这是组件8的说明" },
],
},
{
value: "mcp",
label: "MCP",
options: [
{ value: "mcp1", label: "MCP组件1", tooltip: "MCP组件1" },
{ value: "mcp2", label: "MCP组件2", tooltip: "MCP组件2" },
{ value: "mcp3", label: "MCP组件3", tooltip: "MCP组件3" },
{ value: "mcp4", label: "MCP组件4", tooltip: "MCP组件4" },
],
},
{
value: "tool",
label: "工具",
options: [
{ value: "tool1", label: "工具1", tooltip: "工具1说明" },
{ value: "tool2", label: "工具2", tooltip: "工具2说明" },
{ value: "tool3", label: "工具3", tooltip: "工具3说明" },
{ value: "tool4", label: "工具4", tooltip: "工具4说明" },
{ value: "tool5", label: "工具5", tooltip: "工具5说明" },
],
},
{
value: "workflow",
label: "工作流",
options: [
{ value: "wf1", label: "工作流1", tooltip: "工作流1" },
{ value: "wf2", label: "工作流2", tooltip: "工作流2" },
{ value: "wf3", label: "工作流3", tooltip: "工作流3" },
{ value: "wf4", label: "工作流4", tooltip: "工作流4" },
{ value: "wf5", label: "工作流5", tooltip: "工作流5" },
{ value: "wf6", label: "工作流6", tooltip: "工作流6" },
],
},
];
return (
<div className="w-full h-full max-w-4xl">
<ComponentPanel
categories={categories}
defaultValue={["comp1", "comp3", "mcp1", "tool1", "wf1"]}
defaultActiveTab="all"
onChange={(values) => {
console.log("选中的值:", values);
}}
/>
</div>
);
}
受控模式
完全受控模式,外部管理选中状态。
"use client";
import * as React from "react";
import { ComponentPanel } from "@/components/composed/component-panel/component-panel";
import { Button } from "@/components/ui/button";
export function ComponentPanelControlled() {
const [selectedValues, setSelectedValues] = React.useState<string[]>([
"comp1",
"comp3",
]);
const categories = [
{
value: "all",
label: "全部",
options: [
{ value: "comp1", label: "组件1" },
{ value: "comp2", label: "组件2" },
{ value: "comp3", label: "组件3" },
{ value: "comp4", label: "组件4" },
{ value: "comp5", label: "组件5" },
{ value: "comp6", label: "组件6" },
],
},
{
value: "tools",
label: "工具",
options: [
{ value: "tool1", label: "工具1" },
{ value: "tool2", label: "工具2" },
{ value: "tool3", label: "工具3" },
],
},
];
return (
<div className="space-y-4 w-full max-w-4xl">
<div className="flex gap-2">
<Button
size="sm"
variant="outline"
onClick={() => setSelectedValues([])}
>
清空选择
</Button>
<Button
size="sm"
variant="outline"
onClick={() =>
setSelectedValues([
"comp1",
"comp2",
"comp3",
"comp4",
"comp5",
"comp6",
"tool1",
"tool2",
"tool3",
])
}
>
全选
</Button>
<Button
size="sm"
variant="outline"
onClick={() => setSelectedValues(["comp1", "tool1"])}
>
重置
</Button>
</div>
<div className="text-sm text-muted-foreground">
已选择 {selectedValues.length} 项: {selectedValues.join(", ")}
</div>
<ComponentPanel
categories={categories}
value={selectedValues}
onChange={setSelectedValues}
/>
</div>
);
}
单选模式
单选模式,每次只能选中一个选项。
"use client";
import * as React from "react";
import { ComponentPanel } from "@/components/composed/component-panel/component-panel";
export function ComponentPanelSingleSelect() {
const [selected, setSelected] = React.useState<string[]>(["comp1"]);
const categories = [
{
value: "framework",
label: "框架",
options: [
{
value: "react",
label: "React",
tooltip: "用于构建用户界面的 JavaScript 库",
},
{ value: "vue", label: "Vue", tooltip: "渐进式 JavaScript 框架" },
{ value: "angular", label: "Angular", tooltip: "企业级前端框架" },
{ value: "svelte", label: "Svelte", tooltip: "编译型前端框架" },
],
},
{
value: "backend",
label: "后端",
options: [
{ value: "node", label: "Node.js", tooltip: "JavaScript 运行时" },
{ value: "python", label: "Python", tooltip: "通用编程语言" },
{ value: "go", label: "Go", tooltip: "高效的并发编程语言" },
{ value: "java", label: "Java", tooltip: "面向对象的编程语言" },
],
},
{
value: "database",
label: "数据库",
options: [
{
value: "postgres",
label: "PostgreSQL",
tooltip: "强大的开源关系数据库",
},
{ value: "mysql", label: "MySQL", tooltip: "流行的关系数据库" },
{ value: "mongodb", label: "MongoDB", tooltip: "文档型 NoSQL 数据库" },
{ value: "redis", label: "Redis", tooltip: "内存数据结构存储" },
],
},
];
return (
<div className="space-y-4 w-full max-w-4xl">
<div className="text-sm text-muted-foreground">
当前选择: {selected[0] || "无"}
</div>
<ComponentPanel
categories={categories}
value={selected}
onChange={setSelected}
multiple={false}
defaultActiveTab="framework"
/>
</div>
);
}
选项图标
为选项添加图标,增强视觉识别。
"use client";
import * as React from "react";
import { ComponentPanel } from "@/components/composed/component-panel/component-panel";
import {
Package,
Wrench,
Workflow,
Zap,
Database,
Code,
Terminal,
Box,
} from "lucide-react";
export function ComponentPanelWithIcons() {
const categories = [
{
value: "components",
label: "组件",
options: [
{ value: "button", label: "Button", icon: <Box className="size-4" /> },
{
value: "input",
label: "Input",
icon: <Terminal className="size-4" />,
},
{ value: "select", label: "Select", icon: <Code className="size-4" /> },
{
value: "table",
label: "Table",
icon: <Database className="size-4" />,
},
],
},
{
value: "tools",
label: "工具",
options: [
{
value: "builder",
label: "Builder",
icon: <Wrench className="size-4" />,
},
{
value: "packager",
label: "Packager",
icon: <Package className="size-4" />,
},
{
value: "optimizer",
label: "Optimizer",
icon: <Zap className="size-4" />,
},
],
},
{
value: "workflows",
label: "工作流",
options: [
{ value: "ci", label: "CI/CD", icon: <Workflow className="size-4" /> },
{ value: "deploy", label: "Deploy", icon: <Zap className="size-4" /> },
{ value: "test", label: "Test", icon: <Code className="size-4" /> },
],
},
];
return (
<div className="w-full max-w-4xl">
<ComponentPanel
categories={categories}
defaultValue={["button", "input", "builder"]}
/>
</div>
);
}
禁用
禁用分类或选项,用于权限控制。
"use client";
import * as React from "react";
import { ComponentPanel } from "@/components/composed/component-panel/component-panel";
export function ComponentPanelDisabled() {
const categories = [
{
value: "available",
label: "可用功能",
options: [
{ value: "feature1", label: "功能 1" },
{ value: "feature2", label: "功能 2" },
{ value: "feature3", label: "功能 3" },
{
value: "feature4",
label: "功能 4",
disabled: true,
tooltip: "此功能暂不可用",
},
],
},
{
value: "premium",
label: "高级功能",
disabled: true,
options: [
{ value: "premium1", label: "高级功能 1" },
{ value: "premium2", label: "高级功能 2" },
{ value: "premium3", label: "高级功能 3" },
],
},
{
value: "experimental",
label: "实验性功能",
options: [
{ value: "exp1", label: "实验功能 1" },
{
value: "exp2",
label: "实验功能 2",
disabled: true,
tooltip: "开发中",
},
{ value: "exp3", label: "实验功能 3" },
],
},
];
return (
<div className="w-full max-w-4xl">
<ComponentPanel
categories={categories}
defaultValue={["feature1", "feature2"]}
/>
</div>
);
}
选项卡受控
受控选项卡切换,外部控制当前分类。
"use client";
import * as React from "react";
import { ComponentPanel } from "@/components/composed/component-panel/component-panel";
export function ComponentPanelTabControlled() {
const [activeTab, setActiveTab] = React.useState("dev");
const [selected, setSelected] = React.useState<string[]>(["vscode"]);
const categories = [
{
value: "dev",
label: "开发工具",
options: [
{ value: "vscode", label: "VS Code" },
{ value: "webstorm", label: "WebStorm" },
{ value: "sublime", label: "Sublime Text" },
],
},
{
value: "design",
label: "设计工具",
options: [
{ value: "figma", label: "Figma" },
{ value: "sketch", label: "Sketch" },
{ value: "adobe", label: "Adobe XD" },
],
},
{
value: "productivity",
label: "生产力",
options: [
{ value: "notion", label: "Notion" },
{ value: "slack", label: "Slack" },
{ value: "trello", label: "Trello" },
],
},
];
return (
<div className="space-y-4 w-full max-w-4xl">
<div className="flex gap-2">
<button
className="px-3 py-1.5 text-sm rounded border"
onClick={() => setActiveTab("dev")}
disabled={activeTab === "dev"}
>
切换到开发工具
</button>
<button
className="px-3 py-1.5 text-sm rounded border"
onClick={() => setActiveTab("design")}
disabled={activeTab === "design"}
>
切换到设计工具
</button>
<button
className="px-3 py-1.5 text-sm rounded border"
onClick={() => setActiveTab("productivity")}
disabled={activeTab === "productivity"}
>
切换到生产力
</button>
</div>
<div className="text-sm text-muted-foreground">
当前选项卡: {activeTab}
</div>
<ComponentPanel
categories={categories}
value={selected}
onChange={setSelected}
activeTab={activeTab}
onTabChange={setActiveTab}
/>
</div>
);
}
API
ComponentPanel
主组件,级联选择面板。
Props
| Prop | Type | Default | Description |
|---|---|---|---|
categories | ComponentPanelCategory[] | - | 分类列表(必填) |
value | string[] | - | 选中的选项值数组(受控) |
defaultValue | string[] | [] | 默认选中的选项值数组(非受控) |
onChange | (value: string[]) => void | - | 选中值变化回调 |
activeTab | string | - | 当前激活的选项卡(受控) |
defaultActiveTab | string | - | 默认激活的选项卡(非受控) |
onTabChange | (tab: string) => void | - | 选项卡切换回调 |
multiple | boolean | true | 是否支持多选 |
className | string | - | 自定义样式类名 |
Example
import { ComponentPanel } from "@/registry/wuhan/composed/component-panel";
import { useState } from "react";
function App() {
const [selected, setSelected] = useState(["comp1"]);
const [activeTab, setActiveTab] = useState("all");
return (
<ComponentPanel
categories={categories}
// 受控选择
value={selected}
onChange={setSelected}
// 受控选项卡
activeTab={activeTab}
onTabChange={setActiveTab}
// 单选模式
multiple={false}
/>
);
}ComponentPanelCategory
分类配置接口。
interface ComponentPanelCategory {
/** 分类的唯一值 */
value: string;
/** 分类的显示文本 */
label: React.ReactNode;
/** 该分类下的选项列表 */
options: ComponentPanelOption[];
/** 是否禁用该分类 */
disabled?: boolean;
}示例:
const category: ComponentPanelCategory = {
value: "tools",
label: "工具集",
disabled: false,
options: [
{ value: "tool1", label: "工具1" },
{ value: "tool2", label: "工具2" },
],
};ComponentPanelOption
选项配置接口。
interface ComponentPanelOption {
/** 选项的唯一值 */
value: string;
/** 选项的显示文本 */
label: React.ReactNode;
/** 选项图标 */
icon?: React.ReactNode;
/** 选项提示信息 */
tooltip?: React.ReactNode;
/** 是否禁用该选项 */
disabled?: boolean;
}示例:
import { Wrench } from "lucide-react";
const option: ComponentPanelOption = {
value: "builder",
label: "构建工具",
icon: <Wrench className="size-4" />,
tooltip: "用于构建和打包项目",
disabled: false,
};使用场景
- 功能开关面板:管理应用中各模块功能的启用/禁用状态
- 工具选择器:在开发工具、设计工具等分类中选择常用工具
- 权限配置:按模块配置用户或角色的权限
- 插件管理:管理各类插件的启用状态
- 组件库选择:在组件库文档中选择要查看的组件
- 筛选面板:多维度筛选数据,如电商网站的商品筛选
最佳实践
- 合理分类:将选项按逻辑归类到不同 tab,每个 tab 的选项数量适中(建议 3-12 个)
- 使用 tooltip:为复杂或缩写的选项添加 tooltip 说明,提升用户体验
- 添加图标:为选项添加图标可以增强视觉识别,特别适用于工具类选择
- 状态持久化:考虑将选中状态保存到 localStorage 或后端,避免刷新丢失
- 受控使用:涉及复杂业务逻辑时建议使用受控模式,便于状态管理和同步
- 禁用反馈:禁用的选项应该提供 tooltip 说明原因,如"需要高级会员"
注意事项
- 组件基于 Radix UI Tabs 构建,需要安装
@radix-ui/react-tabs依赖 categories数组不应为空,至少包含一个分类- 选项的
value在所有分类中应该是唯一的 - 单选模式下
value数组只会包含一个元素 - 禁用的分类无法切换,禁用的选项无法选中
- 受控模式下,
value和onChange必须配合使用
原语组件
Component Panel 基于以下原语组件构建:
主要原语
ComponentPanelContainerPrimitive- 主容器(基于 Radix Tabs.Root)ComponentPanelTabsListPrimitive- 选项卡列表ComponentPanelTabsTriggerPrimitive- 选项卡触发器ComponentPanelTabsContentPrimitive- 选项卡内容
列表原语
ComponentPanelListPrimitive- 选项列表容器ComponentPanelListItemPrimitive- 选项列表项ComponentPanelListItemIconPrimitive- 选项默认图标
原语组件提供了更底层的控制,可以在 component-panel-01.tsx 中找到。
样式定制
组件使用 Tailwind CSS 构建,可以通过 className 自定义样式:
<ComponentPanel
className="custom-panel"
categories={categories}
/>响应式布局
选项列表默认使用响应式网格布局:
- 移动端:每行 2 个选项
- 小屏幕:每行 3 个选项
- 中等及以上屏幕:每行 4 个选项
可以通过 CSS 覆盖原语组件的网格样式自定义布局。
扩展示例
动态分类
根据权限动态显示分类。
import { ComponentPanel } from "@/registry/wuhan/composed/component-panel";
function DynamicCategories() {
const userRole = "admin"; // 从上下文获取
const allCategories = [
{
value: "basic",
label: "基础功能",
options: [
{ value: "view", label: "查看" },
{ value: "edit", label: "编辑" },
],
},
{
value: "advanced",
label: "高级功能",
options: [
{ value: "delete", label: "删除" },
{ value: "export", label: "导出" },
],
},
];
// 根据角色过滤分类
const categories =
userRole === "admin"
? allCategories
: allCategories.filter((c) => c.value === "basic");
return <ComponentPanel categories={categories} />;
}全选/反选
提供全选和反选功能。
import { ComponentPanel } from "@/registry/wuhan/composed/component-panel/component-panel";
import { getAllOptions } from "@/registry/wuhan/composed/component-panel/component-panel-utils";
import { Button } from "@/components/ui/button";
import { useState } from "react";
function SelectAll() {
const [selected, setSelected] = useState<string[]>([]);
const categories = [
/* ... */
];
const allOptions = getAllOptions(categories);
const allValues = allOptions.map((opt) => opt.value);
return (
<div className="space-y-4">
<div className="flex gap-2">
<Button onClick={() => setSelected(allValues)}>全选</Button>
<Button onClick={() => setSelected([])}>清空</Button>
<Button
onClick={() =>
setSelected(
allValues.filter((v) => !selected.includes(v))
)
}
>
反选
</Button>
</div>
<ComponentPanel
categories={categories}
value={selected}
onChange={setSelected}
/>
</div>
);
}搜索过滤
为选项添加搜索功能。
import { ComponentPanel } from "@/registry/wuhan/composed/component-panel";
import { Input } from "@/components/ui/input";
import { useState, useMemo } from "react";
function SearchablePanel() {
const [search, setSearch] = useState("");
const [selected, setSelected] = useState<string[]>([]);
const allCategories = [
/* ... */
];
// 根据搜索词过滤选项
const filteredCategories = useMemo(() => {
if (!search) return allCategories;
return allCategories.map((category) => ({
...category,
options: category.options.filter((opt) =>
String(opt.label).toLowerCase().includes(search.toLowerCase())
),
})).filter((category) => category.options.length > 0);
}, [search, allCategories]);
return (
<div className="space-y-4">
<Input
placeholder="搜索选项..."
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
<ComponentPanel
categories={filteredCategories}
value={selected}
onChange={setSelected}
/>
</div>
);
}分组统计
显示每个分类的选中统计。
import { ComponentPanel } from "@/registry/wuhan/composed/component-panel";
import { useState } from "react";
function GroupStats() {
const [selected, setSelected] = useState<string[]>([]);
const categories = [
{
value: "cat1",
label: "分类1",
options: [
{ value: "c1-1", label: "选项1" },
{ value: "c1-2", label: "选项2" },
{ value: "c1-3", label: "选项3" },
],
},
{
value: "cat2",
label: "分类2",
options: [
{ value: "c2-1", label: "选项1" },
{ value: "c2-2", label: "选项2" },
],
},
];
// 统计每个分类的选中数
const stats = categories.map((cat) => ({
...cat,
selectedCount: cat.options.filter((opt) =>
selected.includes(opt.value)
).length,
totalCount: cat.options.length,
}));
return (
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
{stats.map((stat) => (
<div key={stat.value} className="p-3 border rounded">
<div className="font-medium">{stat.label}</div>
<div className="text-sm text-muted-foreground">
已选 {stat.selectedCount} / {stat.totalCount}
</div>
</div>
))}
</div>
<ComponentPanel
categories={categories}
value={selected}
onChange={setSelected}
/>
</div>
);
}自定义渲染
完全自定义选项的渲染方式。
import { ComponentPanelContainerPrimitive, /* ... */ } from "@/registry/wuhan/blocks/component-panel/component-panel-01";
import { Badge } from "@/components/ui/badge";
import { useState } from "react";
function CustomRender() {
const [selected, setSelected] = useState<string[]>([]);
const categories = [
{
value: "plugins",
label: "插件",
options: [
{ value: "p1", label: "插件1", meta: { downloads: "1.2k", rating: 4.5 } },
{ value: "p2", label: "插件2", meta: { downloads: "890", rating: 4.2 } },
],
},
];
return (
<ComponentPanelContainerPrimitive defaultValue="plugins">
<ComponentPanelTabsListPrimitive>
{categories.map((cat) => (
<ComponentPanelTabsTriggerPrimitive key={cat.value} value={cat.value}>
{cat.label}
</ComponentPanelTabsTriggerPrimitive>
))}
</ComponentPanelTabsListPrimitive>
{categories.map((cat) => (
<ComponentPanelTabsContentPrimitive key={cat.value} value={cat.value}>
<div className="grid grid-cols-1 gap-2">
{cat.options.map((opt) => (
<div
key={opt.value}
className="p-3 border rounded cursor-pointer hover:bg-muted"
onClick={() =>
setSelected((prev) =>
prev.includes(opt.value)
? prev.filter((v) => v !== opt.value)
: [...prev, opt.value]
)
}
>
<div className="flex items-center justify-between">
<span className="font-medium">{opt.label}</span>
{selected.includes(opt.value) && (
<Badge variant="secondary">已选</Badge>
)}
</div>
<div className="flex gap-4 text-sm text-muted-foreground mt-1">
<span>下载: {opt.meta.downloads}</span>
<span>评分: {opt.meta.rating}</span>
</div>
</div>
))}
</div>
</ComponentPanelTabsContentPrimitive>
))}
</ComponentPanelContainerPrimitive>
);
}状态持久化
将选择状态保存到 localStorage。
import { ComponentPanel } from "@/registry/wuhan/composed/component-panel";
import { useState, useEffect } from "react";
function PersistentPanel() {
const [selected, setSelected] = useState<string[]>(() => {
const saved = localStorage.getItem("panel-selection");
return saved ? JSON.parse(saved) : [];
});
useEffect(() => {
localStorage.setItem("panel-selection", JSON.stringify(selected));
}, [selected]);
const categories = [
/* ... */
];
return (
<ComponentPanel
categories={categories}
value={selected}
onChange={setSelected}
/>
);
}API
ComponentPanelContainerPrimitive
组件面板容器,使用 Tabs 组件实现标签页功能。
Props:
defaultValue?: string- 默认激活的 tabvalue?: string- 当前激活的 tab(受控模式)onValueChange?: (value: string) => void- tab 变化回调children?: React.ReactNode- 子元素
ComponentPanelTabsListPrimitive
标签页列表容器。
Props:
children?: React.ReactNode- 子元素(通常是 ComponentPanelTabsTriggerPrimitive)
ComponentPanelTabsTriggerPrimitive
标签页触发器。
Props:
value: string- 标签值children?: React.ReactNode- 标签内容
ComponentPanelTabsContentPrimitive
标签页内容容器。
Props:
value: string- 标签值children?: React.ReactNode- 内容
ComponentPanelListPrimitive
列表容器,使用 flex-wrap 实现自适应布局。
Props:
children?: React.ReactNode- 子元素(通常是 ComponentPanelListItemPrimitive)
ComponentPanelListItemPrimitive
列表项样式原语,基于 ToggleButtonPrimitive 实现,提供自适应布局样式。 只提供样式,不包含任何业务逻辑和状态管理,用户需要自己处理点击事件和状态。
Props:
继承自 ToggleButtonPrimitive 的所有属性,包括:
selected?: boolean- 是否选中(由用户控制)onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void- 点击事件处理(由用户实现)disabled?: boolean- 是否禁用variant?: "default" | "compact"- 按钮变体样式multiple?: boolean- 是否多选模式children?: React.ReactNode- 按钮内容(通常是标签文本)
注意: 组件只提供样式和布局,状态管理和事件处理完全由用户控制。