unnamed-ui
卡片

Report Card

A versatile report card component with customizable actions and flexible interaction patterns

候选人评估报告
更新时间:08-04 13:56
"use client";

import { ReportCard } from "@/components/composed/report-card/report-card";

export function ReportCardBasicDemo() {
  return (
    <div className="w-full max-w-[650px]">
      <ReportCard title="候选人评估报告" description="更新时间:08-04 13:56" />
    </div>
  );
}

Report Card 组件是一个功能丰富的报告数据卡片组件,提供灵活的交互模式和完全自定义的操作区域。适用于数据报告、文档管理、内容列表等场景。

概述

  • 渐进式披露 - 三种使用模式:简单 props → 自定义 action → 完全自定义
  • 灵活操作区域 - 支持预设操作或完全自定义右侧操作区域
  • 多种显示模式 - hover 显示、外部始终显示、完全隐藏
  • 向后兼容 - 原有 onEdit/onDelete/onDuplicate 全部保留
  • 类型安全 - 完整的 TypeScript 类型定义

快速开始

使用预设的 onEditonDeleteonDuplicate props,快速实现常见操作。

import { ReportCard } from "@/registry/wuhan/composed/report-card/report-card";

<ReportCard
  title="候选人评估报告"
  description="更新时间:08-04 13:56"
  onEdit={() => console.log("编辑")}
  onDelete={() => console.log("删除")}
  onDuplicate={() => console.log("复制")}
/>

安装

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

代码演示

基础示例

候选人评估报告
更新时间:08-04 13:56
"use client";

import { ReportCard } from "@/components/composed/report-card/report-card";

export function ReportCardSimpleDemo() {
  return (
    <div className="w-full max-w-[650px] space-y-3">
      <ReportCard
        title="候选人评估报告"
        description="更新时间:08-04 13:56"
        onEdit={() => console.log("编辑")}
        onDelete={() => console.log("删除")}
        onDuplicate={() => console.log("复制")}
      />
    </div>
  );
}

自定义操作区域

数据分析报告
包含本月所有数据统计信息
"use client";

import { ReportCard } from "@/components/composed/report-card/report-card";
import { Star, Download } from "lucide-react";

export function ReportCardCustomActionDemo() {
  return (
    <div className="w-full max-w-[650px] space-y-3">
      <ReportCard
        title="数据分析报告"
        description="包含本月所有数据统计信息"
        action={
          <div className="flex items-center gap-2">
            <button className="p-1.5 rounded-md bg-[var(--Container-bg-neutral-light-hover)] cursor-pointer">
              <Star className="size-4 text-[var(--Text-text-warning)]" />
            </button>
            <button className="p-1.5 rounded-md bg-[var(--Container-bg-neutral-light-hover)] cursor-pointer">
              <Download className="size-4 text-[var(--Text-text-secondary)]" />
            </button>
          </div>
        }
      />
    </div>
  );
}

隐藏操作区域

只读报告
不可进行任何操作
"use client";

import { ReportCard } from "@/components/composed/report-card/report-card";

export function ReportCardHiddenActionDemo() {
  return (
    <div className="w-full max-w-[650px]">
      <ReportCard
        title="只读报告"
        description="不可进行任何操作"
        showAction={false}
      />
    </div>
  );
}

卡片列表

候选人评估报告
更新时间:08-04 13:56
数据分析报告
包含本月所有数据统计信息
绩效评估报告
2024年Q4绩效数据汇总
"use client";

import {
  ReportCardList,
  type ReportCardItem,
} from "@/components/composed/report-card/report-card";

export function ReportCardListDemo() {
  const cards: ReportCardItem[] = [
    {
      id: "1",
      title: "候选人评估报告",
      description: "更新时间:08-04 13:56",
    },
    {
      id: "2",
      title: "数据分析报告",
      description: "包含本月所有数据统计信息",
    },
    {
      id: "3",
      title: "绩效评估报告",
      description: "2024年Q4绩效数据汇总",
    },
  ];

  return (
    <div className="w-full max-w-[650px]">
      <ReportCardList
        title="我的报告"
        cards={cards}
        onEdit={(id) => console.log("编辑", id)}
        onDelete={(id) => console.log("删除", id)}
        onDuplicate={(id) => console.log("复制", id)}
      />
    </div>
  );
}

列表自定义操作

候选人评估报告
更新时间:08-04 13:56
数据分析报告
包含本月所有数据统计信息
绩效评估报告
2024年Q4绩效数据汇总
"use client";

import {
  ReportCardList,
  type ReportCardItem,
} from "@/components/composed/report-card/report-card";
import { MoreVertical } from "lucide-react";

export function ReportCardListCustomDemo() {
  const cards: ReportCardItem[] = [
    {
      id: "1",
      title: "候选人评估报告",
      description: "更新时间:08-04 13:56",
    },
    {
      id: "2",
      title: "数据分析报告",
      description: "包含本月所有数据统计信息",
    },
    {
      id: "3",
      title: "绩效评估报告",
      description: "2024年Q4绩效数据汇总",
    },
  ];

  return (
    <div className="w-full max-w-[650px]">
      <ReportCardList
        title="可管理的报告"
        cards={cards}
        cardAction={(card) => (
          <button
            className="flex items-center justify-center w-6 h-6 rounded-md cursor-pointer bg-[var(--Container-bg-neutral-light-hover)]"
            onClick={() => console.log("自定义操作", card.id)}
          >
            <MoreVertical className="size-4 text-[var(--Text-text-secondary)]" />
          </button>
        )}
        showCardAction={false}
      />
    </div>
  );
}

状态标签组合

已发布报告
该报告已发布到团队
已发布
"use client";

import { ReportCard } from "@/components/composed/report-card/report-card";
import { MoreVertical } from "lucide-react";

export function ReportCardStatusDemo() {
  return (
    <div className="w-full max-w-[650px] space-y-3">
      <ReportCard
        title="已发布报告"
        description="该报告已发布到团队"
        action={
          <div className="flex items-center gap-2">
            <span className="px-2 py-0.5 rounded-full text-xs font-medium bg-[var(--Container-bg-success-light)] text-[var(--Text-text-success)]">
              已发布
            </span>
            <button className="p-1 rounded-md bg-[var(--Container-bg-neutral-light-hover)] cursor-pointer">
              <MoreVertical className="size-4 text-[var(--Text-text-secondary)]" />
            </button>
          </div>
        }
      />
    </div>
  );
}

选择功能

单个卡片选择

选中状态: 未选中

候选人评估报告
更新时间:08-04 13:56

列表卡片选择

已选中 0 / 3 个卡片

候选人评估报告
更新时间:08-04 13:56
数据分析报告
包含本月所有数据统计信息
团队绩效报告
2024年Q4绩效数据汇总
"use client";

import * as React from "react";
import {
  ReportCard,
  ReportCardList,
  type ReportCardItem,
} from "@/components/composed/report-card/report-card";
import { FileText, BarChart3, Users } from "lucide-react";

export function ReportCardSelectionDemo() {
  // 单个卡片的选中状态
  const [singleSelected, setSingleSelected] = React.useState(false);

  // 列表卡片的选中状态
  const [listCards, setListCards] = React.useState<ReportCardItem[]>([
    {
      id: "1",
      title: "候选人评估报告",
      description: "更新时间:08-04 13:56",
      icon: <FileText className="text-[var(--Text-text-brand)]" size={16} />,
      selected: false,
    },
    {
      id: "2",
      title: "数据分析报告",
      description: "包含本月所有数据统计信息",
      icon: <BarChart3 className="text-[var(--Text-text-success)]" size={16} />,
      selected: false,
    },
    {
      id: "3",
      title: "团队绩效报告",
      description: "2024年Q4绩效数据汇总",
      icon: <Users className="text-[var(--Text-text-warning)]" size={16} />,
      selected: false,
    },
  ]);

  // 处理单个卡片选择
  const handleSingleSelectChange = (selected: boolean) => {
    setSingleSelected(selected);
    console.log("单个卡片选中状态:", selected);
  };

  // 处理列表卡片选择
  const handleListSelectChange = (selected: boolean, id: string) => {
    setListCards((prev) =>
      prev.map((card) => (card.id === id ? { ...card, selected } : card)),
    );
    console.log(`卡片 ${id} 选中状态:`, selected);
  };

  // 计算已选中的卡片数量
  const selectedCount = listCards.filter((card) => card.selected).length;

  return (
    <div className="w-full space-y-8">
      {/* 单个卡片选择 */}
      <div className="space-y-4">
        <div className="space-y-2">
          <h3 className="text-sm font-medium text-[var(--Text-text-primary)]">
            单个卡片选择
          </h3>
          <p className="text-xs text-[var(--Text-text-secondary)]">
            选中状态: {singleSelected ? "已选中" : "未选中"}
          </p>
        </div>
        <ReportCard
          title="候选人评估报告"
          description="更新时间:08-04 13:56"
          icon={
            <FileText className="text-[var(--Text-text-brand)]" size={16} />
          }
          showCheckbox={true}
          selected={singleSelected}
          onSelectChange={handleSingleSelectChange}
        />
      </div>

      {/* 列表卡片选择 */}
      <div className="space-y-4">
        <div className="space-y-2">
          <h3 className="text-sm font-medium text-[var(--Text-text-primary)]">
            列表卡片选择
          </h3>
          <p className="text-xs text-[var(--Text-text-secondary)]">
            已选中 {selectedCount} / {listCards.length} 个卡片
          </p>
        </div>
        <ReportCardList
          title="我的报告"
          cards={listCards}
          showCheckbox={true}
          onSelectChange={handleListSelectChange}
          onEdit={(id) => console.log("编辑", id)}
          onDelete={(id) => console.log("删除", id)}
        />
      </div>
    </div>
  );
}

API

ReportCard

报告卡片主组件,提供完整的卡片功能和灵活的操作区域。

Props

PropTypeDefaultDescription
titleReact.ReactNode-标题
descriptionReact.ReactNode-描述文本
iconReact.ReactNodeReportCardDefaultIcon左侧图标
widthstring"280px"卡片宽度
onEdit() => void-编辑回调(简单模式)
onDelete() => void-删除回调(简单模式)
onDuplicate() => void-复制回调(简单模式)
actionReact.ReactNode-自定义右侧操作区域
showActionbooleantrue是否显示默认操作按钮(仅 action 未提供时生效)
classNamestring-自定义类名

ReportCardItem

interface ReportCardItem {
  /** 唯一标识符 */
  id: string;
  /** 卡片标题 */
  title: string;
  /** 描述文本 */
  description?: string;
  /** 自定义图标 */
  icon?: React.ReactNode;
}

ReportCardList

报告卡片列表组件,展示多个报告卡片。

Props

PropTypeDefaultDescription
titleReact.ReactNode"报告列表"列表标题
cardsReportCardItem[][]卡片列表数据
onEdit(id: string) => void-编辑回调
onDelete(id: string) => void-删除回调
onDuplicate(id: string) => void-复制回调
cardAction(item: ReportCardItem) => React.ReactNode-自定义每个卡片右侧操作区域
showCardActionbooleantrue是否显示默认操作按钮
classNamestring-自定义类名

使用场景

  • 数据报告 - 展示各类数据分析报告卡片
  • 文档管理 - 展示文档、文件列表,支持编辑操作
  • 内容列表 - 展示文章、帖子等内容的卡片列表
  • 报表展示 - 展示统计报表、评估报告等
  • 项目报告 - 展示项目进度报告、里程碑报告

最佳实践

  1. 选择合适的模式 - 简单需求用 props,复杂需求用 action
  2. 向后兼容 - 原有代码无需修改,继续使用 onEdit/onDelete/onDuplicate
  3. 列表展示 - 多个卡片使用 ReportCardList 统一管理
  4. 图标定制 - 根据报告类型自定义图标,增强辨识度
  5. 宽度设置 - 根据容器宽度调整卡片宽度(默认 280px)
  6. 描述信息 - 提供清晰的描述信息,帮助用户理解报告内容
  7. 自定义操作 - 使用 action prop 实现任意复杂的操作区域

注意事项

  • onEditonDeleteonDuplicate 都是可选的,按需传入
  • 卡片宽度默认为 280px,可通过 width 属性自定义
  • 标题和描述文本超出时会自动截断显示省略号
  • 菜单有 150ms 延迟关闭,优化 hover 体验
  • action prop 会完全替换默认操作区域
  • showAction 仅在 action 未提供时生效
  • 列表模式下使用 cardAction 自定义每个卡片操作

原语组件

ReportCard 基于以下原语组件构建:

  • ReportCardContainerPrimitive - 容器
  • ReportCardHeaderPrimitive - 头部(图标 + 标题 + 描述)
  • ReportCardPrimitive - 完整原语组件
  • ReportCardDefaultIcon - 默认报告图标

如需更灵活的定制,可以直接使用这些原语组件。

样式定制

组件使用 Tailwind CSS 和 CSS 变量,可以通过以下方式定制:

// 使用原语组件自定义样式
import {
  ReportCardPrimitive,
  ReportCardContainerPrimitive,
  ReportCardHeaderPrimitive,
} from "@/registry/wuhan/blocks/report-card/report-card-01";

<ReportCardPrimitive
  title="自定义标题"
  description="自定义描述"
  className="bg-blue-50 hover:bg-blue-100"
/>

交互说明

操作区域显示逻辑

  1. 有 action prop → 显示自定义 action,忽略默认操作
  2. 无 action + showAction=true + 有回调 → 显示默认 ellipsis 按钮(hover 时)
  3. 无 action + showAction=false → 不显示任何操作区域
  4. 无 action + showAction=true + 无回调 → 不显示任何操作区域

Hover 流程(默认模式)

  1. 移入卡片 → 显示 ellipsis 操作按钮
  2. 移入按钮 → 展开操作菜单
  3. 移入菜单 → 保持菜单打开
  4. 移出卡片区域 → 150ms 后关闭所有

防闪烁机制

组件使用 150ms 延迟关闭策略,当用户鼠标从触发元素移动到菜单内容之间有间隙时,可以有效防止菜单意外关闭。