unnamed-ui
卡片

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 属性,键盘导航友好
  • 响应式:自适应不同屏幕尺寸

安装

pnpm dlx shadcn@latest add http://localhost:3000/r/wuhan/goal-card.json

代码演示

基础

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

PropTypeDefaultDescription
titleReact.ReactNode"目标"标题
descriptionReact.ReactNode-描述文本
iconReact.ReactNodeGoalCardAiIcon左侧图标
progressnumber0进度值 0-100
maxnumber100最大值
statusGoalCardSemanticStatus自动推断状态
size"sm" | "md" | "lg""md"尺寸
classNamestring-自定义类名

Example

import { GoalCard } from "@/registry/wuhan/composed/goal-card/goal-card";

function GoalExample() {
  return (
    <GoalCard
      title="阅读《设计心理学》"
      description="第 1 章:可供性"
      progress={75}
      size="md"
    />
  );
}

GoalCardList

目标卡片列表组件,展示多个目标卡片。

Props

PropTypeDefaultDescription
titlestring"目标列表"列表标题
goalsGoalItem[][]目标列表数据
size"sm" | "md" | "lg""md"尺寸
classNamestring-自定义类名

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

PropTypeDefaultDescription
titleReact.ReactNode-标题
descriptionReact.ReactNode-描述文本
iconReact.ReactNode-左侧图标
progressnumber0进度值 0-100
maxnumber100最大值
statusGoalCardSemanticStatus自动推断状态
size"sm" | "md" | "lg""md"尺寸
classNamestring-容器自定义类名

状态默认配置

StatusProgress RangeStroke ColorText Color
pending0%text-tertiarytext-tertiary
in_progress1-99%text-brandtext-brand
completed100%text-successtext-success

使用场景

  • 目标管理:展示个人/团队目标完成进度
  • 学习计划:展示课程学习进度、阅读进度
  • 项目进度:展示项目里程碑完成情况
  • OKR 追踪:展示关键结果完成度
  • 习惯养成:展示习惯打卡进度
  • 任务追踪:展示任务完成百分比

最佳实践

  1. 进度清晰:使用环形进度条直观展示完成度
  2. 语义化状态:使用语义化的状态类型,保持状态语义一致
  3. 尺寸选择:根据使用场景选择合适的尺寸(sm/md/lg)
  4. 自定义图标:根据业务需求自定义图标,增强视觉效果
  5. 列表展示:使用 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"
    />
  );
}