卡片
Task Card
Collapsible task card component with progress tracking and status indicators
"use client";
import { useState } from "react";
import { CheckCircle2, Circle, Loader2 } from "lucide-react";
import {
TaskCard,
type TaskCardItem,
} from "@/components/composed/task-card/task-card";
const initialItems: TaskCardItem[] = [
{ id: "1", text: "简历筛选", status: "completed" },
{ id: "2", text: "初试", status: "completed" },
{ id: "3", text: "复试", status: "running" },
{ id: "4", text: "终面", status: "pending" },
{ id: "5", text: "发放 Offer", status: "pending" },
];
// 根据状态获取图标
const getStatusIcon = (status: string) => {
switch (status) {
case "completed":
return (
<CheckCircle2 className="size-4 text-[var(--Text-text-success)]" />
);
case "running":
return (
<Loader2 className="size-4 text-[var(--Text-text-brand)] animate-spin" />
);
case "pending":
default:
return <Circle className="size-4 text-[var(--Text-text-tertiary)]" />;
}
};
export function TaskCardDemo() {
const [items] = useState<TaskCardItem[]>(initialItems);
const [isOpen, setIsOpen] = useState(false);
// 获取当前进行中的步骤
const currentItem =
items.find((item) => item.status === "running") ||
items.find((item) => item.status === "pending");
// 当前步骤的图标和文本
const currentStepIcon = currentItem
? getStatusIcon(currentItem.status)
: null;
const currentStepText = currentItem?.text || "暂无任务";
return (
<div className="w-full max-w-[650px] mx-auto p-4 space-y-4">
<TaskCard
title="招聘流程"
stepText={currentStepText}
stepIcon={currentStepIcon}
items={items}
open={isOpen}
onOpenChange={setIsOpen}
/>
</div>
);
}
Task Card 组件用于展示任务流程和进度追踪,支持可折叠交互、三种任务状态、动画效果,适用于招聘流程、项目进度、工作流等场景。
概述
- 三种状态:pending(待处理)、running(进行中)、completed(已完成)
- 可折叠交互:点击头部展开/收起任务列表,支持受控和非受控模式
- 智能进度显示:自动计算当前步骤序号(找到第一个 running 或 pending 的步骤)
- 动画效果:展开收起有平滑过渡,进行中状态显示旋转动画
- 完全自定义:支持自定义标题、图标、内容等
- 类型安全:完整的 TypeScript 类型定义
- 轻量级:基于原语组件构建,无额外依赖
快速开始
import { TaskCard } from "@/registry/wuhan/composed/task-card/task-card";
const items = [
{ id: "1", text: "简历筛选", status: "completed" },
{ id: "2", text: "初试", status: "completed" },
{ id: "3", text: "复试", status: "running" },
{ id: "4", text: "终面", status: "pending" },
];
<TaskCard
title="招聘流程"
stepText="复试"
items={items}
/>特性
- 智能状态管理:根据 items 自动计算当前步骤和进度
- 灵活控制:支持受控模式(controlled)和非受控模式(uncontrolled)
- 自定义图标:支持自定义步骤图标和头部图标
- 可访问性:支持 aria 属性,键盘导航友好
- 响应式:自适应不同屏幕尺寸
安装
代码演示
基础
"use client";
import { useState } from "react";
import { CheckCircle2, Circle, Loader2 } from "lucide-react";
import {
TaskCard,
type TaskCardItem,
} from "@/components/composed/task-card/task-card";
const initialItems: TaskCardItem[] = [
{ id: "1", text: "简历筛选", status: "completed" },
{ id: "2", text: "初试", status: "completed" },
{ id: "3", text: "复试", status: "running" },
{ id: "4", text: "终面", status: "pending" },
{ id: "5", text: "发放 Offer", status: "pending" },
];
// 根据状态获取图标
const getStatusIcon = (status: string) => {
switch (status) {
case "completed":
return (
<CheckCircle2 className="size-4 text-[var(--Text-text-success)]" />
);
case "running":
return (
<Loader2 className="size-4 text-[var(--Text-text-brand)] animate-spin" />
);
case "pending":
default:
return <Circle className="size-4 text-[var(--Text-text-tertiary)]" />;
}
};
export function TaskCardDemo() {
const [items] = useState<TaskCardItem[]>(initialItems);
const [isOpen, setIsOpen] = useState(false);
// 获取当前进行中的步骤
const currentItem =
items.find((item) => item.status === "running") ||
items.find((item) => item.status === "pending");
// 当前步骤的图标和文本
const currentStepIcon = currentItem
? getStatusIcon(currentItem.status)
: null;
const currentStepText = currentItem?.text || "暂无任务";
return (
<div className="w-full max-w-[650px] mx-auto p-4 space-y-4">
<TaskCard
title="招聘流程"
stepText={currentStepText}
stepIcon={currentStepIcon}
items={items}
open={isOpen}
onOpenChange={setIsOpen}
/>
</div>
);
}
不同状态
"use client";
import { useState } from "react";
import { CheckCircle2, Circle, Loader2 } from "lucide-react";
import {
TaskCard,
type TaskCardItem,
} from "@/components/composed/task-card/task-card";
const initialItems: TaskCardItem[] = [
{ id: "1", text: "简历筛选", status: "completed" },
{ id: "2", text: "初试", status: "completed" },
{ id: "3", text: "复试", status: "running" },
{ id: "4", text: "终面", status: "pending" },
{ id: "5", text: "发放 Offer", status: "pending" },
];
// 根据状态获取图标
const getStatusIcon = (status: string) => {
switch (status) {
case "completed":
return (
<CheckCircle2 className="size-4 text-[var(--Text-text-success)]" />
);
case "running":
return (
<Loader2 className="size-4 text-[var(--Text-text-brand)] animate-spin" />
);
case "pending":
default:
return <Circle className="size-4 text-[var(--Text-text-tertiary)]" />;
}
};
export function TaskCardDemo() {
const [items] = useState<TaskCardItem[]>(initialItems);
const [isOpen, setIsOpen] = useState(false);
// 获取当前进行中的步骤
const currentItem =
items.find((item) => item.status === "running") ||
items.find((item) => item.status === "pending");
// 当前步骤的图标和文本
const currentStepIcon = currentItem
? getStatusIcon(currentItem.status)
: null;
const currentStepText = currentItem?.text || "暂无任务";
return (
<div className="w-full max-w-[650px] mx-auto p-4 space-y-4">
<TaskCard
title="招聘流程"
stepText={currentStepText}
stepIcon={currentStepIcon}
items={items}
open={isOpen}
onOpenChange={setIsOpen}
/>
</div>
);
}
API
TaskCard
任务卡片主组件,支持可折叠交互和状态管理。
Props
| Prop | Type | Default | Description |
|---|---|---|---|
title | string | "任务列表" | 标题(展开时显示) |
stepText | string | - | 当前步骤文本(收起时显示) |
stepIcon | React.ReactNode | - | 当前步骤图标(收起时显示) |
items | TaskCardItem[] | [] | 任务列表数据 |
defaultOpen | boolean | false | 非受控模式下的默认展开状态 |
open | boolean | - | 受控模式下的展开状态 |
onOpenChange | (open: boolean) => void | - | 展开状态变化回调函数 |
className | string | - | 容器自定义类名 |
Example
import { TaskCard, type TaskCardItem } from "@/registry/wuhan/composed/task-card/task-card";
import { useState } from "react";
import { CheckCircle2, Circle, Loader2 } from "lucide-react";
function TaskExample() {
const [items, setItems] = useState<TaskCardItem[]>([
{ id: "1", text: "简历筛选", status: "completed" },
{ id: "2", text: "初试", status: "completed" },
{ id: "3", text: "复试", status: "running" },
{ id: "4", text: "终面", status: "pending" },
]);
// 获取当前步骤
const currentItem = items.find(
(item) => item.status === "running" || item.status === "pending"
);
return (
<TaskCard
title="招聘流程"
stepText={currentItem?.text || "暂无任务"}
stepIcon={
currentItem?.status === "running" ? (
<Loader2 className="size-4 animate-spin text-[var(--Text-text-brand)]" />
) : (
<Circle className="size-4 text-[var(--Text-text-tertiary)]" />
)
}
items={items}
/>
);
}TaskCardItem
任务项类型定义。
interface TaskCardItem {
/** 唯一标识符 */
id: string;
/** 任务文本 */
text: string;
/** 任务状态 */
status: "pending" | "running" | "completed";
}TaskCardStatus
任务状态类型定义。
type TaskCardStatus =
| "pending" // 待处理
| "running" // 进行中
| "completed"; // 已完成TaskCardPrimitive
自包含折叠功能的原语组件,提供更灵活的定制能力。
import { TaskCardPrimitive } from "@/registry/wuhan/blocks/task-card/task-card-01";
<TaskCardPrimitive
heading="招聘流程"
stepText="复试"
stepIcon={<Loader2 className="size-4 animate-spin" />}
currentStep={3}
total={5}
defaultOpen={false}
steps={[
{ id: "1", text: "简历筛选", status: "completed" },
{ id: "2", text: "初试", status: "completed" },
{ id: "3", text: "复试", status: "running" },
{ id: "4", text: "终面", status: "pending" },
]}
/>TaskCardPrimitive Props
| Prop | Type | Default | Description |
|---|---|---|---|
heading | React.ReactNode | - | 标题(展开时显示) |
stepText | React.ReactNode | - | 当前步骤文本(收起时显示) |
stepIcon | React.ReactNode | - | 当前步骤图标(收起时显示) |
currentStep | number | 0 | 当前步骤序号(从1开始) |
total | number | 0 | 总步骤数 |
steps | Array<{id, text, status, icon}> | [] | 步骤列表 |
defaultOpen | boolean | false | 非受控模式下的默认展开状态 |
open | boolean | - | 受控模式下的展开状态 |
onOpenChange | (open: boolean) => void | - | 展开状态变化回调函数 |
className | string | - | 自定义类名 |
containerClassName | string | - | 容器自定义类名 |
状态默认配置
| Status | Default Icon | Text Color |
|---|---|---|
pending | Circle | text-tertiary |
running | Loader2 (旋转动画) | text-brand |
completed | CheckCircle2 | text-success |
使用场景
- 招聘流程:展示候选人筛选进度(简历筛选→初试→复试→终面)
- 项目进度:展示项目各阶段完成情况
- 工作流:展示审批、任务处理等流程状态
- 教程步骤:展示学习路径、操作步骤等
- 订单状态:展示订单处理各环节
最佳实践
- 状态清晰:使用语义化的状态类型,保持状态语义一致
- 步骤顺序:确保 items 中的步骤按实际顺序排列
- 进度计算:组件会自动计算当前步骤(第一个 running 或 pending)
- 自定义图标:根据业务需求自定义图标,增强视觉效果
- 受控模式:需要外部控制展开状态时使用受控模式
注意事项
currentStep组件会自动计算,无需手动传入stepIcon和stepText配合使用,展示当前进行中步骤- 收起状态始终显示进度:
currentStep / total - 展开状态显示完整步骤列表
原语组件
TaskCard 基于以下原语组件构建:
TaskCardContainerPrimitive- 容器TaskCardCollapsedHeaderPrimitive- 收起状态头部TaskCardTitlePrimitive- 标题(展开状态)TaskCardStepListPrimitive- 步骤列表容器TaskCardStepItemPrimitive- 步骤项TaskCardCollapseArrowPrimitive- 折叠箭头TaskCardPrimitive- 完整组件(自包含折叠功能)
如需更灵活的定制,可以直接使用这些原语组件。
样式定制
组件使用 Tailwind CSS 和 CSS 变量,可以通过以下方式定制:
// 使用原语组件自定义样式
import {
TaskCardPrimitive,
TaskCardContainerPrimitive,
TaskCardStepListPrimitive,
} from "@/registry/wuhan/blocks/task-card/task-card-01";
<TaskCardPrimitive
heading="招聘流程"
currentStep={3}
total={5}
steps={[...]}
className="bg-blue-50 hover:bg-blue-100"
/>扩展示例
带点击交互的卡片
import { TaskCard, type TaskCardItem } from "@/registry/wuhan/composed/task-card/task-card";
import { useState } from "react";
function InteractiveTaskCard() {
const [items, setItems] = useState<TaskCardItem[]>([
{ id: "1", text: "简历筛选", status: "completed" },
{ id: "2", text: "初试", status: "completed" },
{ id: "3", text: "复试", status: "pending" },
]);
// 点击任务项更新状态
const handleItemClick = (id: string) => {
setItems((prev) =>
prev.map((item) => {
if (item.id === id) {
const nextStatus: TaskCardItem["status"] =
item.status === "completed"
? "pending"
: item.status === "pending"
? "running"
: "completed";
return { ...item, status: nextStatus };
}
return item;
}),
);
};
return (
<TaskCard
title="招聘流程"
items={items}
/>
);
}自定义步骤图标
import { TaskCard } from "@/registry/wuhan/composed/task-card/task-card";
import { User, FileText, MessageSquare, Briefcase } from "lucide-react";
function CustomIconsCard() {
const items = [
{
id: "1",
text: "简历筛选",
status: "completed",
icon: <FileText className="size-4" />,
},
{
id: "2",
text: "初试沟通",
status: "completed",
icon: <MessageSquare className="size-4" />,
},
{
id: "3",
text: "技术面试",
status: "running",
icon: <User className="size-4" />,
},
{
id: "4",
text: "发放 Offer",
status: "pending",
icon: <Briefcase className="size-4" />,
},
];
return (
<TaskCard
title="面试流程"
stepText="技术面试"
items={items}
/>
);
}