卡片
Goal Card
Goal card component with AI icon, title, description, and circular progress indicator
Goal Card 组件示例
进行中(品牌色)
阅读《设计心理学》
25%
阅读《设计心理学》
50%
阅读《设计心理学》
75%
完成(绿色 + 对号)
阅读《设计心理学》
失败(错误色 + 叉号 + 进度条填满)
每日健身目标
交互式演示
交互式目标卡片
25%
不同尺寸
小型卡片
50%
中型卡片
50%
大型卡片
50%
"use client";
import * as React from "react";
import { useState } from "react";
import {
GoalCardPrimitive,
type GoalCardSemanticStatus,
} from "@/components/wuhan/blocks/goal-card/goal-card-01";
/**
* Goal Card 示例数据
*
* 状态颜色规则:
* - in_progress(进行中):品牌色 + 百分比
* - completed(完成):绿色 + 100% + 对号
* - failed(失败):错误色 + 100% + 叉号
*/
const goalExamples = [
// 进行中 - 品牌色
{
title: "阅读《设计心理学》",
description: "第 1 章:可供性",
progress: 25,
status: "in_progress" as const,
},
{
title: "阅读《设计心理学》",
description: "第 3 章:可供性",
progress: 50,
status: "in_progress" as const,
},
{
title: "阅读《设计心理学》",
description: "第 5 章:可供性",
progress: 75,
status: "in_progress" as const,
},
// 完成 - 绿色 + 对号
{
title: "阅读《设计心理学》",
description: "全书完成",
progress: 100,
status: "completed" as const,
},
] as const;
/**
* 失败状态示例 - 进度条填满 + 错误色 + 叉号
*/
const failedExamples = [
{
title: "每日健身目标",
description: "本周健身计划",
progress: 100,
status: "failed" as const,
},
] as const;
export function GoalCardDemo() {
// 使用 useState 来演示交互式更新
const [progress, setProgress] = useState(25);
const [interactiveStatus, setInteractiveStatus] =
useState<GoalCardSemanticStatus>("in_progress");
// 手动设置状态
const setCompleted = () => {
setProgress(100);
setInteractiveStatus("completed");
};
const setFailed = () => {
setProgress(100);
setInteractiveStatus("failed");
};
const updateProgress = (delta: number) => {
const newProgress = Math.min(100, Math.max(0, progress + delta));
setProgress(newProgress);
// 如果还没完成,保持进行中状态
if (newProgress < 100) {
setInteractiveStatus("in_progress");
} else if (interactiveStatus !== "failed") {
setInteractiveStatus("completed");
}
};
return (
<div className="w-full max-w-[650px] mx-auto p-4 space-y-4">
{/* 标题 */}
<h2 className="text-lg font-semibold text-[var(--Text-text-primary)] mb-4">
Goal Card 组件示例
</h2>
{/* 进行中状态 - 品牌色 */}
<div className="space-y-2">
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
进行中(品牌色)
</h3>
{goalExamples
.filter((g) => g.status === "in_progress")
.map((goal, index) => (
<GoalCardPrimitive
key={index}
title={goal.title}
description={goal.description}
progress={goal.progress}
status={goal.status}
size="md"
/>
))}
</div>
{/* 完成状态 - 绿色 + 对号 */}
<div className="space-y-2">
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
完成(绿色 + 对号)
</h3>
{goalExamples
.filter((g) => g.status === "completed")
.map((goal, index) => (
<GoalCardPrimitive
key={index}
title={goal.title}
description={goal.description}
progress={goal.progress}
status={goal.status}
size="md"
/>
))}
</div>
{/* 失败状态 - 红色 + 叉号 + 进度条填满 */}
<div className="space-y-2">
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
失败(错误色 + 叉号 + 进度条填满)
</h3>
{failedExamples.map((goal, index) => (
<GoalCardPrimitive
key={`failed-${index}`}
title={goal.title}
description={goal.description}
progress={goal.progress}
status={goal.status}
size="md"
/>
))}
</div>
{/* 交互式演示 */}
<div className="mt-6 space-y-3">
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
交互式演示
</h3>
<GoalCardPrimitive
title="交互式目标卡片"
description="点击按钮切换状态"
progress={progress}
status={interactiveStatus}
size="md"
/>
<div className="flex gap-2">
<button
className="px-3 py-1.5 text-sm bg-[var(--Container-bg-brand)] text-white rounded-[var(--radius-md)] hover:bg-[var(--Container-bg-brand-hover)] transition-colors"
onClick={() => updateProgress(10)}
>
+10%
</button>
<button
className="px-3 py-1.5 text-sm bg-[var(--Container-bg-neutral-light)] text-[var(--Text-text-primary)] rounded-[var(--radius-md)] hover:bg-[var(--Container-bg-neutral-light-active)] transition-colors"
onClick={() => updateProgress(-10)}
>
-10%
</button>
<button
className="px-3 py-1.5 text-sm bg-[var(--Container-bg-success)] text-white rounded-[var(--radius-md)] hover:bg-[var(--Container-bg-success-hover)] transition-colors"
onClick={setCompleted}
>
完成
</button>
<button
className="px-3 py-1.5 text-sm bg-[var(--Container-bg-error)] text-white rounded-[var(--radius-md)] hover:bg-[var(--Container-bg-error-hover)] transition-colors"
onClick={setFailed}
>
失败
</button>
</div>
</div>
{/* 不同尺寸展示 */}
<div className="mt-6 space-y-3">
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
不同尺寸
</h3>
<GoalCardPrimitive
title="小型卡片"
description="小尺寸 - 50%"
progress={50}
status="in_progress"
size="sm"
/>
<GoalCardPrimitive
title="中型卡片"
description="中尺寸(默认)- 50%"
progress={50}
status="in_progress"
size="md"
/>
<GoalCardPrimitive
title="大型卡片"
description="大尺寸 - 50%"
progress={50}
status="in_progress"
size="lg"
/>
</div>
</div>
);
}
Goal Card 组件用于展示目标进度追踪,支持环形进度显示、三种进度状态、三种尺寸,适用于目标管理、学习计划、项目进度等场景。
概述
- 环形进度条:带动画效果的 SVG 环形进度显示
- 状态颜色:根据进度自动切换颜色(灰色→品牌色→成功色)
- 三种尺寸:
sm/md/lg - 自定义图标:支持自定义左侧图标
- 语义化状态:
pending/in_progress/completed - 响应式:完全响应式设计
- 类型安全:完整的 TypeScript 类型定义
- 轻量级:基于原语组件构建,无额外依赖
快速开始
import { GoalCard } from "@/registry/wuhan/composed/goal-card/goal-card";
<GoalCard
title="阅读《设计心理学》"
description="第 1 章:可供性"
progress={75}
/>特性
- 智能状态管理:根据 progress 自动计算状态(pending → in_progress → completed)
- 灵活尺寸:支持 sm、md、lg 三种尺寸
- 自定义图标:支持自定义左侧图标
- 可访问性:支持 aria 属性,键盘导航友好
- 响应式:自适应不同屏幕尺寸
安装
代码演示
基础
Goal Card 组件示例
进行中(品牌色)
阅读《设计心理学》
25%
阅读《设计心理学》
50%
阅读《设计心理学》
75%
完成(绿色 + 对号)
阅读《设计心理学》
失败(错误色 + 叉号 + 进度条填满)
每日健身目标
交互式演示
交互式目标卡片
25%
不同尺寸
小型卡片
50%
中型卡片
50%
大型卡片
50%
"use client";
import * as React from "react";
import { useState } from "react";
import {
GoalCardPrimitive,
type GoalCardSemanticStatus,
} from "@/components/wuhan/blocks/goal-card/goal-card-01";
/**
* Goal Card 示例数据
*
* 状态颜色规则:
* - in_progress(进行中):品牌色 + 百分比
* - completed(完成):绿色 + 100% + 对号
* - failed(失败):错误色 + 100% + 叉号
*/
const goalExamples = [
// 进行中 - 品牌色
{
title: "阅读《设计心理学》",
description: "第 1 章:可供性",
progress: 25,
status: "in_progress" as const,
},
{
title: "阅读《设计心理学》",
description: "第 3 章:可供性",
progress: 50,
status: "in_progress" as const,
},
{
title: "阅读《设计心理学》",
description: "第 5 章:可供性",
progress: 75,
status: "in_progress" as const,
},
// 完成 - 绿色 + 对号
{
title: "阅读《设计心理学》",
description: "全书完成",
progress: 100,
status: "completed" as const,
},
] as const;
/**
* 失败状态示例 - 进度条填满 + 错误色 + 叉号
*/
const failedExamples = [
{
title: "每日健身目标",
description: "本周健身计划",
progress: 100,
status: "failed" as const,
},
] as const;
export function GoalCardDemo() {
// 使用 useState 来演示交互式更新
const [progress, setProgress] = useState(25);
const [interactiveStatus, setInteractiveStatus] =
useState<GoalCardSemanticStatus>("in_progress");
// 手动设置状态
const setCompleted = () => {
setProgress(100);
setInteractiveStatus("completed");
};
const setFailed = () => {
setProgress(100);
setInteractiveStatus("failed");
};
const updateProgress = (delta: number) => {
const newProgress = Math.min(100, Math.max(0, progress + delta));
setProgress(newProgress);
// 如果还没完成,保持进行中状态
if (newProgress < 100) {
setInteractiveStatus("in_progress");
} else if (interactiveStatus !== "failed") {
setInteractiveStatus("completed");
}
};
return (
<div className="w-full max-w-[650px] mx-auto p-4 space-y-4">
{/* 标题 */}
<h2 className="text-lg font-semibold text-[var(--Text-text-primary)] mb-4">
Goal Card 组件示例
</h2>
{/* 进行中状态 - 品牌色 */}
<div className="space-y-2">
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
进行中(品牌色)
</h3>
{goalExamples
.filter((g) => g.status === "in_progress")
.map((goal, index) => (
<GoalCardPrimitive
key={index}
title={goal.title}
description={goal.description}
progress={goal.progress}
status={goal.status}
size="md"
/>
))}
</div>
{/* 完成状态 - 绿色 + 对号 */}
<div className="space-y-2">
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
完成(绿色 + 对号)
</h3>
{goalExamples
.filter((g) => g.status === "completed")
.map((goal, index) => (
<GoalCardPrimitive
key={index}
title={goal.title}
description={goal.description}
progress={goal.progress}
status={goal.status}
size="md"
/>
))}
</div>
{/* 失败状态 - 红色 + 叉号 + 进度条填满 */}
<div className="space-y-2">
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
失败(错误色 + 叉号 + 进度条填满)
</h3>
{failedExamples.map((goal, index) => (
<GoalCardPrimitive
key={`failed-${index}`}
title={goal.title}
description={goal.description}
progress={goal.progress}
status={goal.status}
size="md"
/>
))}
</div>
{/* 交互式演示 */}
<div className="mt-6 space-y-3">
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
交互式演示
</h3>
<GoalCardPrimitive
title="交互式目标卡片"
description="点击按钮切换状态"
progress={progress}
status={interactiveStatus}
size="md"
/>
<div className="flex gap-2">
<button
className="px-3 py-1.5 text-sm bg-[var(--Container-bg-brand)] text-white rounded-[var(--radius-md)] hover:bg-[var(--Container-bg-brand-hover)] transition-colors"
onClick={() => updateProgress(10)}
>
+10%
</button>
<button
className="px-3 py-1.5 text-sm bg-[var(--Container-bg-neutral-light)] text-[var(--Text-text-primary)] rounded-[var(--radius-md)] hover:bg-[var(--Container-bg-neutral-light-active)] transition-colors"
onClick={() => updateProgress(-10)}
>
-10%
</button>
<button
className="px-3 py-1.5 text-sm bg-[var(--Container-bg-success)] text-white rounded-[var(--radius-md)] hover:bg-[var(--Container-bg-success-hover)] transition-colors"
onClick={setCompleted}
>
完成
</button>
<button
className="px-3 py-1.5 text-sm bg-[var(--Container-bg-error)] text-white rounded-[var(--radius-md)] hover:bg-[var(--Container-bg-error-hover)] transition-colors"
onClick={setFailed}
>
失败
</button>
</div>
</div>
{/* 不同尺寸展示 */}
<div className="mt-6 space-y-3">
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
不同尺寸
</h3>
<GoalCardPrimitive
title="小型卡片"
description="小尺寸 - 50%"
progress={50}
status="in_progress"
size="sm"
/>
<GoalCardPrimitive
title="中型卡片"
description="中尺寸(默认)- 50%"
progress={50}
status="in_progress"
size="md"
/>
<GoalCardPrimitive
title="大型卡片"
description="大尺寸 - 50%"
progress={50}
status="in_progress"
size="lg"
/>
</div>
</div>
);
}
不同状态
Goal Card 组件示例
进行中(品牌色)
阅读《设计心理学》
25%
阅读《设计心理学》
50%
阅读《设计心理学》
75%
完成(绿色 + 对号)
阅读《设计心理学》
失败(错误色 + 叉号 + 进度条填满)
每日健身目标
交互式演示
交互式目标卡片
25%
不同尺寸
小型卡片
50%
中型卡片
50%
大型卡片
50%
"use client";
import * as React from "react";
import { useState } from "react";
import {
GoalCardPrimitive,
type GoalCardSemanticStatus,
} from "@/components/wuhan/blocks/goal-card/goal-card-01";
/**
* Goal Card 示例数据
*
* 状态颜色规则:
* - in_progress(进行中):品牌色 + 百分比
* - completed(完成):绿色 + 100% + 对号
* - failed(失败):错误色 + 100% + 叉号
*/
const goalExamples = [
// 进行中 - 品牌色
{
title: "阅读《设计心理学》",
description: "第 1 章:可供性",
progress: 25,
status: "in_progress" as const,
},
{
title: "阅读《设计心理学》",
description: "第 3 章:可供性",
progress: 50,
status: "in_progress" as const,
},
{
title: "阅读《设计心理学》",
description: "第 5 章:可供性",
progress: 75,
status: "in_progress" as const,
},
// 完成 - 绿色 + 对号
{
title: "阅读《设计心理学》",
description: "全书完成",
progress: 100,
status: "completed" as const,
},
] as const;
/**
* 失败状态示例 - 进度条填满 + 错误色 + 叉号
*/
const failedExamples = [
{
title: "每日健身目标",
description: "本周健身计划",
progress: 100,
status: "failed" as const,
},
] as const;
export function GoalCardDemo() {
// 使用 useState 来演示交互式更新
const [progress, setProgress] = useState(25);
const [interactiveStatus, setInteractiveStatus] =
useState<GoalCardSemanticStatus>("in_progress");
// 手动设置状态
const setCompleted = () => {
setProgress(100);
setInteractiveStatus("completed");
};
const setFailed = () => {
setProgress(100);
setInteractiveStatus("failed");
};
const updateProgress = (delta: number) => {
const newProgress = Math.min(100, Math.max(0, progress + delta));
setProgress(newProgress);
// 如果还没完成,保持进行中状态
if (newProgress < 100) {
setInteractiveStatus("in_progress");
} else if (interactiveStatus !== "failed") {
setInteractiveStatus("completed");
}
};
return (
<div className="w-full max-w-[650px] mx-auto p-4 space-y-4">
{/* 标题 */}
<h2 className="text-lg font-semibold text-[var(--Text-text-primary)] mb-4">
Goal Card 组件示例
</h2>
{/* 进行中状态 - 品牌色 */}
<div className="space-y-2">
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
进行中(品牌色)
</h3>
{goalExamples
.filter((g) => g.status === "in_progress")
.map((goal, index) => (
<GoalCardPrimitive
key={index}
title={goal.title}
description={goal.description}
progress={goal.progress}
status={goal.status}
size="md"
/>
))}
</div>
{/* 完成状态 - 绿色 + 对号 */}
<div className="space-y-2">
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
完成(绿色 + 对号)
</h3>
{goalExamples
.filter((g) => g.status === "completed")
.map((goal, index) => (
<GoalCardPrimitive
key={index}
title={goal.title}
description={goal.description}
progress={goal.progress}
status={goal.status}
size="md"
/>
))}
</div>
{/* 失败状态 - 红色 + 叉号 + 进度条填满 */}
<div className="space-y-2">
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
失败(错误色 + 叉号 + 进度条填满)
</h3>
{failedExamples.map((goal, index) => (
<GoalCardPrimitive
key={`failed-${index}`}
title={goal.title}
description={goal.description}
progress={goal.progress}
status={goal.status}
size="md"
/>
))}
</div>
{/* 交互式演示 */}
<div className="mt-6 space-y-3">
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
交互式演示
</h3>
<GoalCardPrimitive
title="交互式目标卡片"
description="点击按钮切换状态"
progress={progress}
status={interactiveStatus}
size="md"
/>
<div className="flex gap-2">
<button
className="px-3 py-1.5 text-sm bg-[var(--Container-bg-brand)] text-white rounded-[var(--radius-md)] hover:bg-[var(--Container-bg-brand-hover)] transition-colors"
onClick={() => updateProgress(10)}
>
+10%
</button>
<button
className="px-3 py-1.5 text-sm bg-[var(--Container-bg-neutral-light)] text-[var(--Text-text-primary)] rounded-[var(--radius-md)] hover:bg-[var(--Container-bg-neutral-light-active)] transition-colors"
onClick={() => updateProgress(-10)}
>
-10%
</button>
<button
className="px-3 py-1.5 text-sm bg-[var(--Container-bg-success)] text-white rounded-[var(--radius-md)] hover:bg-[var(--Container-bg-success-hover)] transition-colors"
onClick={setCompleted}
>
完成
</button>
<button
className="px-3 py-1.5 text-sm bg-[var(--Container-bg-error)] text-white rounded-[var(--radius-md)] hover:bg-[var(--Container-bg-error-hover)] transition-colors"
onClick={setFailed}
>
失败
</button>
</div>
</div>
{/* 不同尺寸展示 */}
<div className="mt-6 space-y-3">
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
不同尺寸
</h3>
<GoalCardPrimitive
title="小型卡片"
description="小尺寸 - 50%"
progress={50}
status="in_progress"
size="sm"
/>
<GoalCardPrimitive
title="中型卡片"
description="中尺寸(默认)- 50%"
progress={50}
status="in_progress"
size="md"
/>
<GoalCardPrimitive
title="大型卡片"
description="大尺寸 - 50%"
progress={50}
status="in_progress"
size="lg"
/>
</div>
</div>
);
}
不同尺寸
Goal Card 组件示例
进行中(品牌色)
阅读《设计心理学》
25%
阅读《设计心理学》
50%
阅读《设计心理学》
75%
完成(绿色 + 对号)
阅读《设计心理学》
失败(错误色 + 叉号 + 进度条填满)
每日健身目标
交互式演示
交互式目标卡片
25%
不同尺寸
小型卡片
50%
中型卡片
50%
大型卡片
50%
"use client";
import * as React from "react";
import { useState } from "react";
import {
GoalCardPrimitive,
type GoalCardSemanticStatus,
} from "@/components/wuhan/blocks/goal-card/goal-card-01";
/**
* Goal Card 示例数据
*
* 状态颜色规则:
* - in_progress(进行中):品牌色 + 百分比
* - completed(完成):绿色 + 100% + 对号
* - failed(失败):错误色 + 100% + 叉号
*/
const goalExamples = [
// 进行中 - 品牌色
{
title: "阅读《设计心理学》",
description: "第 1 章:可供性",
progress: 25,
status: "in_progress" as const,
},
{
title: "阅读《设计心理学》",
description: "第 3 章:可供性",
progress: 50,
status: "in_progress" as const,
},
{
title: "阅读《设计心理学》",
description: "第 5 章:可供性",
progress: 75,
status: "in_progress" as const,
},
// 完成 - 绿色 + 对号
{
title: "阅读《设计心理学》",
description: "全书完成",
progress: 100,
status: "completed" as const,
},
] as const;
/**
* 失败状态示例 - 进度条填满 + 错误色 + 叉号
*/
const failedExamples = [
{
title: "每日健身目标",
description: "本周健身计划",
progress: 100,
status: "failed" as const,
},
] as const;
export function GoalCardDemo() {
// 使用 useState 来演示交互式更新
const [progress, setProgress] = useState(25);
const [interactiveStatus, setInteractiveStatus] =
useState<GoalCardSemanticStatus>("in_progress");
// 手动设置状态
const setCompleted = () => {
setProgress(100);
setInteractiveStatus("completed");
};
const setFailed = () => {
setProgress(100);
setInteractiveStatus("failed");
};
const updateProgress = (delta: number) => {
const newProgress = Math.min(100, Math.max(0, progress + delta));
setProgress(newProgress);
// 如果还没完成,保持进行中状态
if (newProgress < 100) {
setInteractiveStatus("in_progress");
} else if (interactiveStatus !== "failed") {
setInteractiveStatus("completed");
}
};
return (
<div className="w-full max-w-[650px] mx-auto p-4 space-y-4">
{/* 标题 */}
<h2 className="text-lg font-semibold text-[var(--Text-text-primary)] mb-4">
Goal Card 组件示例
</h2>
{/* 进行中状态 - 品牌色 */}
<div className="space-y-2">
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
进行中(品牌色)
</h3>
{goalExamples
.filter((g) => g.status === "in_progress")
.map((goal, index) => (
<GoalCardPrimitive
key={index}
title={goal.title}
description={goal.description}
progress={goal.progress}
status={goal.status}
size="md"
/>
))}
</div>
{/* 完成状态 - 绿色 + 对号 */}
<div className="space-y-2">
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
完成(绿色 + 对号)
</h3>
{goalExamples
.filter((g) => g.status === "completed")
.map((goal, index) => (
<GoalCardPrimitive
key={index}
title={goal.title}
description={goal.description}
progress={goal.progress}
status={goal.status}
size="md"
/>
))}
</div>
{/* 失败状态 - 红色 + 叉号 + 进度条填满 */}
<div className="space-y-2">
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
失败(错误色 + 叉号 + 进度条填满)
</h3>
{failedExamples.map((goal, index) => (
<GoalCardPrimitive
key={`failed-${index}`}
title={goal.title}
description={goal.description}
progress={goal.progress}
status={goal.status}
size="md"
/>
))}
</div>
{/* 交互式演示 */}
<div className="mt-6 space-y-3">
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
交互式演示
</h3>
<GoalCardPrimitive
title="交互式目标卡片"
description="点击按钮切换状态"
progress={progress}
status={interactiveStatus}
size="md"
/>
<div className="flex gap-2">
<button
className="px-3 py-1.5 text-sm bg-[var(--Container-bg-brand)] text-white rounded-[var(--radius-md)] hover:bg-[var(--Container-bg-brand-hover)] transition-colors"
onClick={() => updateProgress(10)}
>
+10%
</button>
<button
className="px-3 py-1.5 text-sm bg-[var(--Container-bg-neutral-light)] text-[var(--Text-text-primary)] rounded-[var(--radius-md)] hover:bg-[var(--Container-bg-neutral-light-active)] transition-colors"
onClick={() => updateProgress(-10)}
>
-10%
</button>
<button
className="px-3 py-1.5 text-sm bg-[var(--Container-bg-success)] text-white rounded-[var(--radius-md)] hover:bg-[var(--Container-bg-success-hover)] transition-colors"
onClick={setCompleted}
>
完成
</button>
<button
className="px-3 py-1.5 text-sm bg-[var(--Container-bg-error)] text-white rounded-[var(--radius-md)] hover:bg-[var(--Container-bg-error-hover)] transition-colors"
onClick={setFailed}
>
失败
</button>
</div>
</div>
{/* 不同尺寸展示 */}
<div className="mt-6 space-y-3">
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
不同尺寸
</h3>
<GoalCardPrimitive
title="小型卡片"
description="小尺寸 - 50%"
progress={50}
status="in_progress"
size="sm"
/>
<GoalCardPrimitive
title="中型卡片"
description="中尺寸(默认)- 50%"
progress={50}
status="in_progress"
size="md"
/>
<GoalCardPrimitive
title="大型卡片"
description="大尺寸 - 50%"
progress={50}
status="in_progress"
size="lg"
/>
</div>
</div>
);
}
API
GoalCard
目标卡片主组件,提供完整的进度卡片功能。
Props
| Prop | Type | Default | Description |
|---|---|---|---|
title | React.ReactNode | "目标" | 标题 |
description | React.ReactNode | - | 描述文本 |
icon | React.ReactNode | GoalCardAiIcon | 左侧图标 |
progress | number | 0 | 进度值 0-100 |
max | number | 100 | 最大值 |
status | GoalCardSemanticStatus | 自动推断 | 状态 |
size | "sm" | "md" | "lg" | "md" | 尺寸 |
className | string | - | 自定义类名 |
Example
import { GoalCard } from "@/registry/wuhan/composed/goal-card/goal-card";
function GoalExample() {
return (
<GoalCard
title="阅读《设计心理学》"
description="第 1 章:可供性"
progress={75}
size="md"
/>
);
}GoalCardList
目标卡片列表组件,展示多个目标卡片。
Props
| Prop | Type | Default | Description |
|---|---|---|---|
title | string | "目标列表" | 列表标题 |
goals | GoalItem[] | [] | 目标列表数据 |
size | "sm" | "md" | "lg" | "md" | 尺寸 |
className | string | - | 自定义类名 |
Example
import { GoalCardList, type GoalItem } from "@/registry/wuhan/composed/goal-card/goal-card";
function GoalListExample() {
const goals: GoalItem[] = [
{ id: "1", title: "阅读《设计心理学》", description: "第 1 章", progress: 75 },
{ id: "2", title: "完成项目", description: "第二阶段", progress: 30 },
{ id: "3", title: "学习英语", description: "每天 30 分钟", progress: 0 },
];
return <GoalCardList title="我的目标" goals={goals} />;
}GoalItem
目标项类型定义。
interface GoalItem {
/** 唯一标识符 */
id: string;
/** 目标标题 */
title: string;
/** 目标描述 */
description?: string;
/** 进度值 */
progress: number;
/** 最大值 */
max?: number;
/** 自定义图标 */
icon?: React.ReactNode;
}GoalCardSemanticStatus
目标卡片状态类型定义。
type GoalCardSemanticStatus =
| "pending" // 待开始
| "in_progress" // 进行中
| "completed"; // 已完成GoalCardPrimitive
自包含的目标卡片原语组件,提供更灵活的定制能力。
import { GoalCardPrimitive } from "@/registry/wuhan/blocks/goal-card/goal-card-01";
<GoalCardPrimitive
title="阅读《设计心理学》"
description="第 1 章:可供性"
progress={75}
size="md"
/>GoalCardPrimitive Props
| Prop | Type | Default | Description |
|---|---|---|---|
title | React.ReactNode | - | 标题 |
description | React.ReactNode | - | 描述文本 |
icon | React.ReactNode | - | 左侧图标 |
progress | number | 0 | 进度值 0-100 |
max | number | 100 | 最大值 |
status | GoalCardSemanticStatus | 自动推断 | 状态 |
size | "sm" | "md" | "lg" | "md" | 尺寸 |
className | string | - | 容器自定义类名 |
状态默认配置
| Status | Progress Range | Stroke Color | Text Color |
|---|---|---|---|
pending | 0% | text-tertiary | text-tertiary |
in_progress | 1-99% | text-brand | text-brand |
completed | 100% | text-success | text-success |
使用场景
- 目标管理:展示个人/团队目标完成进度
- 学习计划:展示课程学习进度、阅读进度
- 项目进度:展示项目里程碑完成情况
- OKR 追踪:展示关键结果完成度
- 习惯养成:展示习惯打卡进度
- 任务追踪:展示任务完成百分比
最佳实践
- 进度清晰:使用环形进度条直观展示完成度
- 语义化状态:使用语义化的状态类型,保持状态语义一致
- 尺寸选择:根据使用场景选择合适的尺寸(sm/md/lg)
- 自定义图标:根据业务需求自定义图标,增强视觉效果
- 列表展示:使用 GoalCardList 展示多个目标
注意事项
progress范围为 0-100,超出范围会自动裁剪status可以手动传入,也可以根据 progress 自动推断- 进度条颜色会根据进度值自动切换
- 完成时(100%)会自动显示成功绿色
原语组件
GoalCard 基于以下原语组件构建:
GoalCardContainerPrimitive- 容器GoalCardHeaderPrimitive- 头部(图标 + 标题 + 描述)GoalCardProgressPrimitive- 环形进度条GoalCardPrimitive- 完整组件(自包含)GoalCardAiIcon- 默认 AI 图标
如需更灵活的定制,可以直接使用这些原语组件。
样式定制
组件使用 Tailwind CSS 和 CSS 变量,可以通过以下方式定制:
// 使用原语组件自定义样式
import {
GoalCardPrimitive,
GoalCardContainerPrimitive,
GoalCardHeaderPrimitive,
GoalCardProgressPrimitive,
} from "@/registry/wuhan/blocks/goal-card/goal-card-01";
<GoalCardPrimitive
title="自定义标题"
description="自定义描述"
progress={75}
className="bg-blue-50 hover:bg-blue-100"
/>扩展示例
自定义图标
import { GoalCard } from "@/registry/wuhan/composed/goal-card/goal-card";
import { BookOpen, Target, Trophy } from "lucide-react";
function CustomIconExample() {
return (
<>
<GoalCard
title="阅读目标"
description="本月阅读 3 本书"
progress={66}
icon={<BookOpen className="size-5 text-[var(--Text-text-brand)]" />}
/>
<GoalCard
title="运动目标"
description="每周跑步 20 公里"
progress={40}
icon={<Target className="size-5 text-[var(--Text-text-brand)]" />}
/>
<GoalCard
title="成就解锁"
description="累计完成 10 个目标"
progress={100}
icon={<Trophy className="size-5 text-[var(--Text-text-success)]" />}
/>
</>
);
}目标列表
import { GoalCardList } from "@/registry/wuhan/composed/goal-card/goal-card";
function GoalListExample() {
const goals = [
{
id: "1",
title: "阅读《设计心理学》",
description: "第 1 章:可供性",
progress: 75,
},
{
id: "2",
title: "完成项目开发",
description: "第二阶段:核心功能",
progress: 45,
},
{
id: "3",
title: "学习英语",
description: "每天 30 分钟",
progress: 0,
},
];
return (
<GoalCardList
title="我的目标"
goals={goals}
size="md"
/>
);
}