unnamed-ui
卡片

File Card

File card component with checkbox, icon, title, date, and action button

File Card 组件示例

带操作菜单的文件卡片

项目需求文档.pdf2024-01-15 10:30
设计稿.png2024-01-14 15:20
数据统计表.xlsx2024-01-13 09:45

不同状态

加载中的文件.pdf2024-01-15 10:30
失败文件.jpg2024-01-14 15:20

选中状态

已选中的文件.pdf2024-01-15 10:30

禁用状态

禁用的文件.pdf2024-01-15 10:30

简单操作按钮

简单操作.pdf2024-01-15 10:30

原语组件组合

使用原语组件2024-01-15 10:30
"use client";

import * as React from "react";
import {
  FileCardPrimitive,
  FileCardContainerPrimitive,
  FileCardFileIconPrimitive,
  FileCardInfoPrimitive,
  FileCardActionPopoverPrimitive,
  FileCardActionMenuItemProps,
} from "@/components/wuhan/blocks/file-card/file-card-01";
import {
  FileText,
  FileImage,
  FileSpreadsheet,
  Download,
  Trash2,
  Eye,
  Copy,
} from "lucide-react";

/**
 * File Card 示例数据
 */
const fileExamples = [
  {
    id: "1",
    title: "项目需求文档.pdf",
    date: "2024-01-15 10:30",
    fileIcon: <FileText className="w-6 h-6 text-[var(--Text-text-brand)]" />,
  },
  {
    id: "2",
    title: "设计稿.png",
    date: "2024-01-14 15:20",
    fileIcon: <FileImage className="w-6 h-6 text-[var(--Text-text-success)]" />,
  },
  {
    id: "3",
    title: "数据统计表.xlsx",
    date: "2024-01-13 09:45",
    fileIcon: (
      <FileSpreadsheet className="w-6 h-6 text-[var(--Text-text-warning)]" />
    ),
  },
] as const;

// 默认操作菜单项
const defaultActionMenuItems: FileCardActionMenuItemProps[] = [
  {
    key: "preview",
    label: "预览",
    icon: <Eye className="w-4 h-4" />,
  },
  {
    key: "download",
    label: "下载",
    icon: <Download className="w-4 h-4" />,
  },
  {
    key: "copy",
    label: "复制链接",
    icon: <Copy className="w-4 h-4" />,
  },
  {
    key: "delete",
    label: "删除",
    icon: <Trash2 className="w-4 h-4" />,
    danger: true,
  },
];

export function FileCardDemo() {
  const [selectedFiles, setSelectedFiles] = React.useState<Set<string>>(
    new Set(),
  );

  const toggleSelect = React.useCallback((id: string) => {
    setSelectedFiles((prev) => {
      const newSelected = new Set(prev);
      if (newSelected.has(id)) {
        newSelected.delete(id);
      } else {
        newSelected.add(id);
      }
      return newSelected;
    });
  }, []);

  const handleMenuAction = React.useCallback(
    (action: string, fileId: string) => {
      console.log(`Action: ${action}, FileId: ${fileId}`);
    },
    [],
  );

  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">
        File Card 组件示例
      </h2>

      {/* 带操作菜单的文件卡片 */}
      <div className="space-y-2">
        <h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
          带操作菜单的文件卡片
        </h3>
        {fileExamples.map((file) => (
          <FileCardPrimitive
            key={file.id}
            id={file.id}
            title={file.title}
            date={file.date}
            fileIcon={file.fileIcon}
            selected={selectedFiles.has(file.id)}
            actionMenuItems={defaultActionMenuItems.map((item) => ({
              ...item,
              onClick: () => handleMenuAction(item.key ?? "", file.id),
            }))}
            onSelectChange={() => toggleSelect(file.id)}
          />
        ))}
      </div>

      {/* 不同状态展示 */}
      <div className="mt-6 space-y-3">
        <h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
          不同状态
        </h3>
        <FileCardPrimitive
          id="status-loading"
          title="加载中的文件.pdf"
          date="2024-01-15 10:30"
          fileIcon={
            <FileText className="w-6 h-6 text-[var(--Text-text-brand)]" />
          }
          status="loading"
          actionMenuItems={defaultActionMenuItems}
        />
        <FileCardPrimitive
          id="status-error"
          title="失败文件.jpg"
          date="2024-01-14 15:20"
          fileIcon={
            <FileImage className="w-6 h-6 text-[var(--Text-text-brand)]" />
          }
          status="error"
          actionMenuItems={defaultActionMenuItems}
        />
      </div>

      {/* 选中状态展示 */}
      <div className="mt-6 space-y-3">
        <h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
          选中状态
        </h3>
        <FileCardPrimitive
          id="selected-demo"
          title="已选中的文件.pdf"
          date="2024-01-15 10:30"
          selected={true}
          actionMenuItems={defaultActionMenuItems}
          onSelectChange={(checked) => console.log("Selected:", checked)}
        />
      </div>

      {/* 禁用状态展示 */}
      <div className="mt-6 space-y-3">
        <h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
          禁用状态
        </h3>
        <FileCardPrimitive
          id="disabled-demo"
          title="禁用的文件.pdf"
          date="2024-01-15 10:30"
          disabled={true}
          actionMenuItems={defaultActionMenuItems}
        />
      </div>

      {/* 无操作菜单的卡片 */}
      <div className="mt-6 space-y-3">
        <h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
          简单操作按钮
        </h3>
        <FileCardPrimitive
          id="simple-action"
          title="简单操作.pdf"
          date="2024-01-15 10:30"
          onActionClick={() => console.log("Action clicked")}
        />
      </div>

      {/* 原语组件展示 */}
      <div className="mt-6 space-y-3">
        <h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
          原语组件组合
        </h3>
        <FileCardContainerPrimitive>
          <FileCardFileIconPrimitive
            icon={
              <FileText className="w-6 h-6 text-[var(--Text-text-brand)]" />
            }
          />
          <FileCardInfoPrimitive title="使用原语组件" date="2024-01-15 10:30" />
          <FileCardActionPopoverPrimitive
            items={[
              {
                key: "action1",
                label: "操作1",
                icon: <Download className="w-4 h-4" />,
              },
              { key: "action2", label: "操作2", danger: true },
            ]}
          />
        </FileCardContainerPrimitive>
      </div>
    </div>
  );
}

File Card 组件用于展示文件信息,支持图标、标题、日期,适用于文件管理、附件展示、下载列表等场景。

概述

  • 简洁设计:包含 checkbox、文件图标、标题、日期和操作按钮
  • 多种状态:支持上传中、下载中、完成、错误等状态
  • 选中功能:支持选中/取消选中,配合 checkbox 使用
  • hover 效果:hover 时显示操作按钮
  • 自定义图标:支持自定义文件和状态图标
  • 类型安全:完整的 TypeScript 类型定义
  • 轻量级:基于原语组件构建,无额外依赖

快速开始

import { FileCard } from "@/registry/wuhan/composed/file-card/file-card";

<FileCard
  id="file-1"
  title="项目需求文档.pdf"
  date="2024-01-15 10:30"
/>

特性

  • 简洁直观:去除复杂状态,只保留核心信息
  • 状态管理:支持上传/下载/完成/错误等状态
  • 选中功能:支持多选,配合 FileCardList 使用更便捷
  • 自定义图标:支持自定义文件和状态图标
  • hover 交互:hover 时显示操作按钮
  • 可访问性:支持 aria 属性,键盘导航友好

安装

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

代码演示

基础

File Card 组件示例

带操作菜单的文件卡片

项目需求文档.pdf2024-01-15 10:30
设计稿.png2024-01-14 15:20
数据统计表.xlsx2024-01-13 09:45

不同状态

加载中的文件.pdf2024-01-15 10:30
失败文件.jpg2024-01-14 15:20

选中状态

已选中的文件.pdf2024-01-15 10:30

禁用状态

禁用的文件.pdf2024-01-15 10:30

简单操作按钮

简单操作.pdf2024-01-15 10:30

原语组件组合

使用原语组件2024-01-15 10:30
"use client";

import * as React from "react";
import {
  FileCardPrimitive,
  FileCardContainerPrimitive,
  FileCardFileIconPrimitive,
  FileCardInfoPrimitive,
  FileCardActionPopoverPrimitive,
  FileCardActionMenuItemProps,
} from "@/components/wuhan/blocks/file-card/file-card-01";
import {
  FileText,
  FileImage,
  FileSpreadsheet,
  Download,
  Trash2,
  Eye,
  Copy,
} from "lucide-react";

/**
 * File Card 示例数据
 */
const fileExamples = [
  {
    id: "1",
    title: "项目需求文档.pdf",
    date: "2024-01-15 10:30",
    fileIcon: <FileText className="w-6 h-6 text-[var(--Text-text-brand)]" />,
  },
  {
    id: "2",
    title: "设计稿.png",
    date: "2024-01-14 15:20",
    fileIcon: <FileImage className="w-6 h-6 text-[var(--Text-text-success)]" />,
  },
  {
    id: "3",
    title: "数据统计表.xlsx",
    date: "2024-01-13 09:45",
    fileIcon: (
      <FileSpreadsheet className="w-6 h-6 text-[var(--Text-text-warning)]" />
    ),
  },
] as const;

// 默认操作菜单项
const defaultActionMenuItems: FileCardActionMenuItemProps[] = [
  {
    key: "preview",
    label: "预览",
    icon: <Eye className="w-4 h-4" />,
  },
  {
    key: "download",
    label: "下载",
    icon: <Download className="w-4 h-4" />,
  },
  {
    key: "copy",
    label: "复制链接",
    icon: <Copy className="w-4 h-4" />,
  },
  {
    key: "delete",
    label: "删除",
    icon: <Trash2 className="w-4 h-4" />,
    danger: true,
  },
];

export function FileCardDemo() {
  const [selectedFiles, setSelectedFiles] = React.useState<Set<string>>(
    new Set(),
  );

  const toggleSelect = React.useCallback((id: string) => {
    setSelectedFiles((prev) => {
      const newSelected = new Set(prev);
      if (newSelected.has(id)) {
        newSelected.delete(id);
      } else {
        newSelected.add(id);
      }
      return newSelected;
    });
  }, []);

  const handleMenuAction = React.useCallback(
    (action: string, fileId: string) => {
      console.log(`Action: ${action}, FileId: ${fileId}`);
    },
    [],
  );

  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">
        File Card 组件示例
      </h2>

      {/* 带操作菜单的文件卡片 */}
      <div className="space-y-2">
        <h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
          带操作菜单的文件卡片
        </h3>
        {fileExamples.map((file) => (
          <FileCardPrimitive
            key={file.id}
            id={file.id}
            title={file.title}
            date={file.date}
            fileIcon={file.fileIcon}
            selected={selectedFiles.has(file.id)}
            actionMenuItems={defaultActionMenuItems.map((item) => ({
              ...item,
              onClick: () => handleMenuAction(item.key ?? "", file.id),
            }))}
            onSelectChange={() => toggleSelect(file.id)}
          />
        ))}
      </div>

      {/* 不同状态展示 */}
      <div className="mt-6 space-y-3">
        <h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
          不同状态
        </h3>
        <FileCardPrimitive
          id="status-loading"
          title="加载中的文件.pdf"
          date="2024-01-15 10:30"
          fileIcon={
            <FileText className="w-6 h-6 text-[var(--Text-text-brand)]" />
          }
          status="loading"
          actionMenuItems={defaultActionMenuItems}
        />
        <FileCardPrimitive
          id="status-error"
          title="失败文件.jpg"
          date="2024-01-14 15:20"
          fileIcon={
            <FileImage className="w-6 h-6 text-[var(--Text-text-brand)]" />
          }
          status="error"
          actionMenuItems={defaultActionMenuItems}
        />
      </div>

      {/* 选中状态展示 */}
      <div className="mt-6 space-y-3">
        <h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
          选中状态
        </h3>
        <FileCardPrimitive
          id="selected-demo"
          title="已选中的文件.pdf"
          date="2024-01-15 10:30"
          selected={true}
          actionMenuItems={defaultActionMenuItems}
          onSelectChange={(checked) => console.log("Selected:", checked)}
        />
      </div>

      {/* 禁用状态展示 */}
      <div className="mt-6 space-y-3">
        <h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
          禁用状态
        </h3>
        <FileCardPrimitive
          id="disabled-demo"
          title="禁用的文件.pdf"
          date="2024-01-15 10:30"
          disabled={true}
          actionMenuItems={defaultActionMenuItems}
        />
      </div>

      {/* 无操作菜单的卡片 */}
      <div className="mt-6 space-y-3">
        <h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
          简单操作按钮
        </h3>
        <FileCardPrimitive
          id="simple-action"
          title="简单操作.pdf"
          date="2024-01-15 10:30"
          onActionClick={() => console.log("Action clicked")}
        />
      </div>

      {/* 原语组件展示 */}
      <div className="mt-6 space-y-3">
        <h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
          原语组件组合
        </h3>
        <FileCardContainerPrimitive>
          <FileCardFileIconPrimitive
            icon={
              <FileText className="w-6 h-6 text-[var(--Text-text-brand)]" />
            }
          />
          <FileCardInfoPrimitive title="使用原语组件" date="2024-01-15 10:30" />
          <FileCardActionPopoverPrimitive
            items={[
              {
                key: "action1",
                label: "操作1",
                icon: <Download className="w-4 h-4" />,
              },
              { key: "action2", label: "操作2", danger: true },
            ]}
          />
        </FileCardContainerPrimitive>
      </div>
    </div>
  );
}

不同状态

File Card 组件示例

带操作菜单的文件卡片

项目需求文档.pdf2024-01-15 10:30
设计稿.png2024-01-14 15:20
数据统计表.xlsx2024-01-13 09:45

不同状态

加载中的文件.pdf2024-01-15 10:30
失败文件.jpg2024-01-14 15:20

选中状态

已选中的文件.pdf2024-01-15 10:30

禁用状态

禁用的文件.pdf2024-01-15 10:30

简单操作按钮

简单操作.pdf2024-01-15 10:30

原语组件组合

使用原语组件2024-01-15 10:30
"use client";

import * as React from "react";
import {
  FileCardPrimitive,
  FileCardContainerPrimitive,
  FileCardFileIconPrimitive,
  FileCardInfoPrimitive,
  FileCardActionPopoverPrimitive,
  FileCardActionMenuItemProps,
} from "@/components/wuhan/blocks/file-card/file-card-01";
import {
  FileText,
  FileImage,
  FileSpreadsheet,
  Download,
  Trash2,
  Eye,
  Copy,
} from "lucide-react";

/**
 * File Card 示例数据
 */
const fileExamples = [
  {
    id: "1",
    title: "项目需求文档.pdf",
    date: "2024-01-15 10:30",
    fileIcon: <FileText className="w-6 h-6 text-[var(--Text-text-brand)]" />,
  },
  {
    id: "2",
    title: "设计稿.png",
    date: "2024-01-14 15:20",
    fileIcon: <FileImage className="w-6 h-6 text-[var(--Text-text-success)]" />,
  },
  {
    id: "3",
    title: "数据统计表.xlsx",
    date: "2024-01-13 09:45",
    fileIcon: (
      <FileSpreadsheet className="w-6 h-6 text-[var(--Text-text-warning)]" />
    ),
  },
] as const;

// 默认操作菜单项
const defaultActionMenuItems: FileCardActionMenuItemProps[] = [
  {
    key: "preview",
    label: "预览",
    icon: <Eye className="w-4 h-4" />,
  },
  {
    key: "download",
    label: "下载",
    icon: <Download className="w-4 h-4" />,
  },
  {
    key: "copy",
    label: "复制链接",
    icon: <Copy className="w-4 h-4" />,
  },
  {
    key: "delete",
    label: "删除",
    icon: <Trash2 className="w-4 h-4" />,
    danger: true,
  },
];

export function FileCardDemo() {
  const [selectedFiles, setSelectedFiles] = React.useState<Set<string>>(
    new Set(),
  );

  const toggleSelect = React.useCallback((id: string) => {
    setSelectedFiles((prev) => {
      const newSelected = new Set(prev);
      if (newSelected.has(id)) {
        newSelected.delete(id);
      } else {
        newSelected.add(id);
      }
      return newSelected;
    });
  }, []);

  const handleMenuAction = React.useCallback(
    (action: string, fileId: string) => {
      console.log(`Action: ${action}, FileId: ${fileId}`);
    },
    [],
  );

  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">
        File Card 组件示例
      </h2>

      {/* 带操作菜单的文件卡片 */}
      <div className="space-y-2">
        <h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
          带操作菜单的文件卡片
        </h3>
        {fileExamples.map((file) => (
          <FileCardPrimitive
            key={file.id}
            id={file.id}
            title={file.title}
            date={file.date}
            fileIcon={file.fileIcon}
            selected={selectedFiles.has(file.id)}
            actionMenuItems={defaultActionMenuItems.map((item) => ({
              ...item,
              onClick: () => handleMenuAction(item.key ?? "", file.id),
            }))}
            onSelectChange={() => toggleSelect(file.id)}
          />
        ))}
      </div>

      {/* 不同状态展示 */}
      <div className="mt-6 space-y-3">
        <h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
          不同状态
        </h3>
        <FileCardPrimitive
          id="status-loading"
          title="加载中的文件.pdf"
          date="2024-01-15 10:30"
          fileIcon={
            <FileText className="w-6 h-6 text-[var(--Text-text-brand)]" />
          }
          status="loading"
          actionMenuItems={defaultActionMenuItems}
        />
        <FileCardPrimitive
          id="status-error"
          title="失败文件.jpg"
          date="2024-01-14 15:20"
          fileIcon={
            <FileImage className="w-6 h-6 text-[var(--Text-text-brand)]" />
          }
          status="error"
          actionMenuItems={defaultActionMenuItems}
        />
      </div>

      {/* 选中状态展示 */}
      <div className="mt-6 space-y-3">
        <h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
          选中状态
        </h3>
        <FileCardPrimitive
          id="selected-demo"
          title="已选中的文件.pdf"
          date="2024-01-15 10:30"
          selected={true}
          actionMenuItems={defaultActionMenuItems}
          onSelectChange={(checked) => console.log("Selected:", checked)}
        />
      </div>

      {/* 禁用状态展示 */}
      <div className="mt-6 space-y-3">
        <h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
          禁用状态
        </h3>
        <FileCardPrimitive
          id="disabled-demo"
          title="禁用的文件.pdf"
          date="2024-01-15 10:30"
          disabled={true}
          actionMenuItems={defaultActionMenuItems}
        />
      </div>

      {/* 无操作菜单的卡片 */}
      <div className="mt-6 space-y-3">
        <h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
          简单操作按钮
        </h3>
        <FileCardPrimitive
          id="simple-action"
          title="简单操作.pdf"
          date="2024-01-15 10:30"
          onActionClick={() => console.log("Action clicked")}
        />
      </div>

      {/* 原语组件展示 */}
      <div className="mt-6 space-y-3">
        <h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
          原语组件组合
        </h3>
        <FileCardContainerPrimitive>
          <FileCardFileIconPrimitive
            icon={
              <FileText className="w-6 h-6 text-[var(--Text-text-brand)]" />
            }
          />
          <FileCardInfoPrimitive title="使用原语组件" date="2024-01-15 10:30" />
          <FileCardActionPopoverPrimitive
            items={[
              {
                key: "action1",
                label: "操作1",
                icon: <Download className="w-4 h-4" />,
              },
              { key: "action2", label: "操作2", danger: true },
            ]}
          />
        </FileCardContainerPrimitive>
      </div>
    </div>
  );
}

选中状态

File Card 组件示例

带操作菜单的文件卡片

项目需求文档.pdf2024-01-15 10:30
设计稿.png2024-01-14 15:20
数据统计表.xlsx2024-01-13 09:45

不同状态

加载中的文件.pdf2024-01-15 10:30
失败文件.jpg2024-01-14 15:20

选中状态

已选中的文件.pdf2024-01-15 10:30

禁用状态

禁用的文件.pdf2024-01-15 10:30

简单操作按钮

简单操作.pdf2024-01-15 10:30

原语组件组合

使用原语组件2024-01-15 10:30
"use client";

import * as React from "react";
import {
  FileCardPrimitive,
  FileCardContainerPrimitive,
  FileCardFileIconPrimitive,
  FileCardInfoPrimitive,
  FileCardActionPopoverPrimitive,
  FileCardActionMenuItemProps,
} from "@/components/wuhan/blocks/file-card/file-card-01";
import {
  FileText,
  FileImage,
  FileSpreadsheet,
  Download,
  Trash2,
  Eye,
  Copy,
} from "lucide-react";

/**
 * File Card 示例数据
 */
const fileExamples = [
  {
    id: "1",
    title: "项目需求文档.pdf",
    date: "2024-01-15 10:30",
    fileIcon: <FileText className="w-6 h-6 text-[var(--Text-text-brand)]" />,
  },
  {
    id: "2",
    title: "设计稿.png",
    date: "2024-01-14 15:20",
    fileIcon: <FileImage className="w-6 h-6 text-[var(--Text-text-success)]" />,
  },
  {
    id: "3",
    title: "数据统计表.xlsx",
    date: "2024-01-13 09:45",
    fileIcon: (
      <FileSpreadsheet className="w-6 h-6 text-[var(--Text-text-warning)]" />
    ),
  },
] as const;

// 默认操作菜单项
const defaultActionMenuItems: FileCardActionMenuItemProps[] = [
  {
    key: "preview",
    label: "预览",
    icon: <Eye className="w-4 h-4" />,
  },
  {
    key: "download",
    label: "下载",
    icon: <Download className="w-4 h-4" />,
  },
  {
    key: "copy",
    label: "复制链接",
    icon: <Copy className="w-4 h-4" />,
  },
  {
    key: "delete",
    label: "删除",
    icon: <Trash2 className="w-4 h-4" />,
    danger: true,
  },
];

export function FileCardDemo() {
  const [selectedFiles, setSelectedFiles] = React.useState<Set<string>>(
    new Set(),
  );

  const toggleSelect = React.useCallback((id: string) => {
    setSelectedFiles((prev) => {
      const newSelected = new Set(prev);
      if (newSelected.has(id)) {
        newSelected.delete(id);
      } else {
        newSelected.add(id);
      }
      return newSelected;
    });
  }, []);

  const handleMenuAction = React.useCallback(
    (action: string, fileId: string) => {
      console.log(`Action: ${action}, FileId: ${fileId}`);
    },
    [],
  );

  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">
        File Card 组件示例
      </h2>

      {/* 带操作菜单的文件卡片 */}
      <div className="space-y-2">
        <h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
          带操作菜单的文件卡片
        </h3>
        {fileExamples.map((file) => (
          <FileCardPrimitive
            key={file.id}
            id={file.id}
            title={file.title}
            date={file.date}
            fileIcon={file.fileIcon}
            selected={selectedFiles.has(file.id)}
            actionMenuItems={defaultActionMenuItems.map((item) => ({
              ...item,
              onClick: () => handleMenuAction(item.key ?? "", file.id),
            }))}
            onSelectChange={() => toggleSelect(file.id)}
          />
        ))}
      </div>

      {/* 不同状态展示 */}
      <div className="mt-6 space-y-3">
        <h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
          不同状态
        </h3>
        <FileCardPrimitive
          id="status-loading"
          title="加载中的文件.pdf"
          date="2024-01-15 10:30"
          fileIcon={
            <FileText className="w-6 h-6 text-[var(--Text-text-brand)]" />
          }
          status="loading"
          actionMenuItems={defaultActionMenuItems}
        />
        <FileCardPrimitive
          id="status-error"
          title="失败文件.jpg"
          date="2024-01-14 15:20"
          fileIcon={
            <FileImage className="w-6 h-6 text-[var(--Text-text-brand)]" />
          }
          status="error"
          actionMenuItems={defaultActionMenuItems}
        />
      </div>

      {/* 选中状态展示 */}
      <div className="mt-6 space-y-3">
        <h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
          选中状态
        </h3>
        <FileCardPrimitive
          id="selected-demo"
          title="已选中的文件.pdf"
          date="2024-01-15 10:30"
          selected={true}
          actionMenuItems={defaultActionMenuItems}
          onSelectChange={(checked) => console.log("Selected:", checked)}
        />
      </div>

      {/* 禁用状态展示 */}
      <div className="mt-6 space-y-3">
        <h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
          禁用状态
        </h3>
        <FileCardPrimitive
          id="disabled-demo"
          title="禁用的文件.pdf"
          date="2024-01-15 10:30"
          disabled={true}
          actionMenuItems={defaultActionMenuItems}
        />
      </div>

      {/* 无操作菜单的卡片 */}
      <div className="mt-6 space-y-3">
        <h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
          简单操作按钮
        </h3>
        <FileCardPrimitive
          id="simple-action"
          title="简单操作.pdf"
          date="2024-01-15 10:30"
          onActionClick={() => console.log("Action clicked")}
        />
      </div>

      {/* 原语组件展示 */}
      <div className="mt-6 space-y-3">
        <h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
          原语组件组合
        </h3>
        <FileCardContainerPrimitive>
          <FileCardFileIconPrimitive
            icon={
              <FileText className="w-6 h-6 text-[var(--Text-text-brand)]" />
            }
          />
          <FileCardInfoPrimitive title="使用原语组件" date="2024-01-15 10:30" />
          <FileCardActionPopoverPrimitive
            items={[
              {
                key: "action1",
                label: "操作1",
                icon: <Download className="w-4 h-4" />,
              },
              { key: "action2", label: "操作2", danger: true },
            ]}
          />
        </FileCardContainerPrimitive>
      </div>
    </div>
  );
}

FileCardList 受控组件

展示如何使用受控组件方式管理文件选中状态。

我的文件

项目文档.pdf2024-01-15
设计稿.png2024-01-14
源代码.tsx2024-01-13

已选中:

"use client";

import { useState } from "react";
import { FileCardList } from "@/components/composed/file-card/file-card";
import { FileText, Image as ImageIcon, FileCode } from "lucide-react";

export function FileCardListControlled() {
  const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());

  const files = [
    {
      id: "1",
      title: "项目文档.pdf",
      date: "2024-01-15",
      fileIcon: <FileText className="w-6 h-6 text-blue-500" />,
    },
    {
      id: "2",
      title: "设计稿.png",
      date: "2024-01-14",
      fileIcon: <ImageIcon className="w-6 h-6 text-green-500" />,
    },
    {
      id: "3",
      title: "源代码.tsx",
      date: "2024-01-13",
      fileIcon: <FileCode className="w-6 h-6 text-purple-500" />,
    },
  ];

  return (
    <div className="space-y-4">
      <FileCardList
        title="我的文件"
        files={files}
        value={selectedIds}
        onChange={setSelectedIds}
      />

      <div className="p-4 bg-muted rounded-lg">
        <p className="text-sm text-muted-foreground">
          已选中:{" "}
          {selectedIds.size > 0 ? Array.from(selectedIds).join(", ") : "无"}
        </p>
      </div>
    </div>
  );
}

FileCardList 横向布局

文件列表横向排列,支持自定义间距。

横向布局文件列表

文档.pdf2024-01-15
图片.png2024-01-14
代码.tsx2024-01-13
视频.mp42024-01-12
"use client";

import { FileCardList } from "@/components/composed/file-card/file-card";
import { FileText, Image as ImageIcon, FileCode, Video } from "lucide-react";

export function FileCardListHorizontal() {
  const files = [
    {
      id: "1",
      title: "文档.pdf",
      date: "2024-01-15",
      fileIcon: <FileText className="w-6 h-6 text-blue-500" />,
    },
    {
      id: "2",
      title: "图片.png",
      date: "2024-01-14",
      fileIcon: <ImageIcon className="w-6 h-6 text-green-500" />,
    },
    {
      id: "3",
      title: "代码.tsx",
      date: "2024-01-13",
      fileIcon: <FileCode className="w-6 h-6 text-purple-500" />,
    },
    {
      id: "4",
      title: "视频.mp4",
      date: "2024-01-12",
      fileIcon: <Video className="w-6 h-6 text-red-500" />,
    },
  ];

  return (
    <FileCardList
      title="横向布局文件列表"
      files={files}
      layout="horizontal"
      spacing={12}
    />
  );
}

FileCardList 纵向布局

文件列表纵向排列,支持自定义间距。

纵向布局文件列表

需求文档.pdf2024-01-15
UI设计.png2024-01-14
实现代码.tsx2024-01-13
"use client";

import { FileCardList } from "@/components/composed/file-card/file-card";
import { FileText, Image as ImageIcon, FileCode } from "lucide-react";

export function FileCardListVertical() {
  const files = [
    {
      id: "1",
      title: "需求文档.pdf",
      date: "2024-01-15",
      fileIcon: <FileText className="w-6 h-6 text-blue-500" />,
    },
    {
      id: "2",
      title: "UI设计.png",
      date: "2024-01-14",
      fileIcon: <ImageIcon className="w-6 h-6 text-green-500" />,
    },
    {
      id: "3",
      title: "实现代码.tsx",
      date: "2024-01-13",
      fileIcon: <FileCode className="w-6 h-6 text-purple-500" />,
    },
  ];

  return (
    <FileCardList
      title="纵向布局文件列表"
      files={files}
      layout="vertical"
      spacing={20}
    />
  );
}

FileCardList 自定义间距

展示不同间距设置的效果。

紧凑间距 (8px)

文档A.pdf2024-01-15
图片B.png2024-01-14
代码C.tsx2024-01-13

宽松间距 (32px)

文档A.pdf2024-01-15
图片B.png2024-01-14
代码C.tsx2024-01-13
"use client";

import { FileCardList } from "@/components/composed/file-card/file-card";
import { FileText, Image as ImageIcon, FileCode } from "lucide-react";

export function FileCardListCustomSpacing() {
  const files = [
    {
      id: "1",
      title: "文档A.pdf",
      date: "2024-01-15",
      fileIcon: <FileText className="w-6 h-6 text-blue-500" />,
    },
    {
      id: "2",
      title: "图片B.png",
      date: "2024-01-14",
      fileIcon: <ImageIcon className="w-6 h-6 text-green-500" />,
    },
    {
      id: "3",
      title: "代码C.tsx",
      date: "2024-01-13",
      fileIcon: <FileCode className="w-6 h-6 text-purple-500" />,
    },
  ];

  return (
    <div className="space-y-8">
      <FileCardList
        title="紧凑间距 (8px)"
        files={files}
        layout="vertical"
        spacing={8}
      />

      <FileCardList
        title="宽松间距 (32px)"
        files={files}
        layout="vertical"
        spacing={32}
      />
    </div>
  );
}

带边框样式

展示带边框的文件卡片和列表。

单个文件卡片(有边框)

带边框的文件卡片.pdf2024-01-15

文件列表(有边框)

带边框的文件列表

项目文档.pdf2024-01-15
设计稿.png2024-01-14
源代码.tsx2024-01-13

横向布局(有边框)

横向布局

文档.pdf2024-01-15
图片.png2024-01-14
视频.mp42024-01-13
"use client";

import {
  FileCard,
  FileCardList,
} from "@/components/composed/file-card/file-card";
import { FileText, Image as ImageIcon, FileCode, Video } from "lucide-react";

export function FileCardBordered() {
  const files = [
    {
      id: "1",
      title: "项目文档.pdf",
      date: "2024-01-15",
      fileIcon: <FileText className="w-6 h-6 text-blue-500" />,
    },
    {
      id: "2",
      title: "设计稿.png",
      date: "2024-01-14",
      fileIcon: <ImageIcon className="w-6 h-6 text-green-500" />,
    },
    {
      id: "3",
      title: "源代码.tsx",
      date: "2024-01-13",
      fileIcon: <FileCode className="w-6 h-6 text-purple-500" />,
    },
  ];

  return (
    <div className="space-y-8">
      {/* 单个文件卡片 - 有边框 */}
      <div>
        <h3 className="text-sm font-medium mb-4">单个文件卡片(有边框)</h3>
        <FileCard
          id="single-1"
          title="带边框的文件卡片.pdf"
          date="2024-01-15"
          fileIcon={<FileText className="w-6 h-6 text-blue-500" />}
          bordered
        />
      </div>

      {/* 文件列表 - 有边框 */}
      <div>
        <h3 className="text-sm font-medium mb-4">文件列表(有边框)</h3>
        <FileCardList
          title="带边框的文件列表"
          files={files}
          bordered
          layout="vertical"
        />
      </div>

      {/* 横向布局 - 有边框 */}
      <div>
        <h3 className="text-sm font-medium mb-4">横向布局(有边框)</h3>
        <FileCardList
          title="横向布局"
          files={[
            {
              id: "h1",
              title: "文档.pdf",
              date: "2024-01-15",
              fileIcon: <FileText className="w-6 h-6 text-blue-500" />,
            },
            {
              id: "h2",
              title: "图片.png",
              date: "2024-01-14",
              fileIcon: <ImageIcon className="w-6 h-6 text-green-500" />,
            },
            {
              id: "h3",
              title: "视频.mp4",
              date: "2024-01-13",
              fileIcon: <Video className="w-6 h-6 text-red-500" />,
            },
          ]}
          bordered
          layout="horizontal"
          spacing={12}
        />
      </div>
    </div>
  );
}

API

FileCard

文件卡片主组件,提供完整的文件卡片功能。

Props

PropTypeDefaultDescription
idstring-必填。唯一标识符
titleReact.ReactNode-文件标题
dateReact.ReactNode-文件日期
fileIconReact.ReactNode-文件图标
statusFileCardStatus"default"状态
selectedbooleanfalse选中状态
borderedbooleanfalse是否显示边框
onSelectChange(selected: boolean) => void-选中状态变化回调
onActionClick() => void-操作按钮点击回调
classNamestring-自定义类名

Example

import { FileCard } from "@/registry/wuhan/composed/file-card/file-card";

function FileExample() {
  return (
    <FileCard
      id="file-1"
      title="项目需求文档.pdf"
      date="2024-01-15 10:30"
    />
  );
}

FileCardList

文件卡片列表组件,展示多个文件卡片,支持选中状态管理和灵活的布局配置。

Props

PropTypeDefaultDescription
titlestring"文件列表"列表标题
filesFileItem[][]文件列表数据
valueSet<string> | string[]-选中的文件 ID(表单组件属性,推荐)
selectedIdsSet<string> | string[][]选中的文件 ID(已废弃,请使用 value)
onChange(selectedIds: Set<string>) => void-选中状态变化回调(表单组件属性,推荐)
onSelectionChange(selectedIds: Set<string>) => void-选中状态变化回调(已废弃,请使用 onChange)
onSelectAll(select: boolean) => void-全选/取消全选回调
onActionClick(id: string) => void-操作按钮点击回调
layout"vertical" | "horizontal""vertical"布局方向(纵向或横向)
spacingnumber | string16 (var(--Gap-gap-sm))卡片间距,数字表示 px,字符串支持 CSS 变量
borderedbooleanfalse是否显示边框
multiSelectbooleantrue是否支持多选
classNamestring-自定义类名

FileItem

文件项类型定义。

interface FileItem {
  /** 唯一标识符 */
  id: string;
  /** 文件标题 */
  title: string;
  /** 文件日期 */
  date?: string;
  /** 文件图标 */
  fileIcon?: React.ReactNode;
  /** 状态图标 */
  statusIcon?: React.ReactNode;
}

FileCardStatus

文件卡片状态类型。

type FileCardStatus = "default" | "uploading" | "downloading" | "completed" | "error";

FileCardPrimitive

自包含的文件卡片原语组件,提供更灵活的定制能力。

import { FileCardPrimitive } from "@/registry/wuhan/blocks/file-card/file-card-01";

<FileCardPrimitive
  id="file-1"
  title="项目文档.pdf"
  date="2024-01-15"
  selected={false}
  onSelectChange={(checked) => console.log(checked)}
  onActionClick={() => console.log("Action")}
/>

FileCardPrimitive Props

PropTypeDefaultDescription
idstring-必填。唯一标识符
titleReact.ReactNode-文件标题
dateReact.ReactNode-文件日期
fileIconReact.ReactNode-文件图标
statusIconReact.ReactNode-状态图标
selectedbooleanfalse选中状态
onSelectChange(selected: boolean) => void-选中状态变化回调
onActionClick() => void-操作按钮点击回调
classNamestring-容器自定义类名

使用场景

  • 文件管理:展示和管理文件列表
  • 附件展示:展示消息或表单中的附件
  • 下载列表:展示正在下载或已下载的文件
  • 上传列表:展示正在上传的文件
  • 云盘文件:展示云盘中的文件列表

最佳实践

  1. 简洁设计:去除复杂状态,只保留核心信息
  2. 状态图标:使用状态图标明确文件当前状态
  3. 多选功能:配合 FileCardList 使用,支持全选/取消全选
  4. 自定义图标:根据文件类型使用不同的图标

原语组件

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

  • FileCardContainerPrimitive - 容器(hover/selected 状态背景)
  • FileCardFileIconPrimitive - 文件图标容器
  • FileCardStatusIconPrimitive - 状态图标容器
  • FileCardInfoPrimitive - 文件信息(标题 + 日期)
  • FileCardActionPrimitive - 操作按钮(hover 时显示)
  • FileCardPrimitive - 完整组件(自包含)

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

状态图标

组件内置了四种状态图标:

  • FileCardUploadingIcon - 上传中
  • FileCardDownloadingIcon - 下载中
  • FileCardCompletedIcon - 已完成
  • FileCardErrorIcon - 错误

也可以使用 getStatusIcon 函数根据状态获取对应图标:

import { getStatusIcon } from "@/registry/wuhan/composed/file-card/file-card-get-status-icon";
import type { FileCardStatus } from "@/registry/wuhan/blocks/file-card/file-card-01";

const status: FileCardStatus = "uploading";
const icon = getStatusIcon(status);

样式定制

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

// 使用原语组件自定义样式
import {
  FileCardPrimitive,
  FileCardContainerPrimitive,
  FileCardFileIconPrimitive,
  FileCardInfoPrimitive,
} from "@/registry/wuhan/blocks/file-card/file-card-01";

<FileCardContainerPrimitive className="bg-blue-50 hover:bg-blue-100">
  <FileCardFileIconPrimitive icon={<FileIcon className="text-blue-500" />} />
  <FileCardInfoPrimitive title="自定义文件" date="2024-01-15" />
</FileCardContainerPrimitive>

扩展示例

不同文件类型

import { FileCard } from "@/registry/wuhan/composed/file-card/file-card";
import { FileText, FileImage, FileSpreadsheet, FileVideo } from "lucide-react";

function FileTypeExample() {
  return (
    <>
      <FileCard
        id="doc"
        title="文档.pdf"
        date="2024-01-15"
        fileIcon={<FileText className="text-blue-500" />}
      />
      <FileCard
        id="image"
        title="图片.png"
        date="2024-01-14"
        fileIcon={<FileImage className="text-green-500" />}
      />
      <FileCard
        id="spreadsheet"
        title="表格.xlsx"
        date="2024-01-13"
        fileIcon={<FileSpreadsheet className="text-orange-500" />}
      />
      <FileCard
        id="video"
        title="视频.mp4"
        date="2024-01-12"
        fileIcon={<FileVideo className="text-purple-500" />}
      />
    </>
  );
}

带状态的文件列表

import { FileCard, type FileItem } from "@/registry/wuhan/composed/file-card/file-card";

function StatusExample() {
  const files: FileItem[] = [
    { id: "1", title: "正在上传.pdf", status: "uploading" },
    { id: "2", title: "正在下载.docx", status: "downloading" },
    { id: "3", title: "已完成.txt", status: "completed" },
    { id: "4", title: "失败.jpg", status: "error" },
  ];

  return <FileCardList title="下载队列" files={files} />;
}

选中状态管理

import { FileCardList, type FileItem } from "@/registry/wuhan/composed/file-card/file-card";
import { useState } from "react";

function SelectionExample() {
  const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
  const [allFiles] = useState<FileItem[]>([
    { id: "1", title: "文档1.pdf" },
    { id: "2", title: "文档2.pdf" },
    { id: "3", title: "文档3.pdf" },
  ]);

  const handleSelectionChange = (ids: Set<string>) => {
    setSelectedIds(ids);
    console.log("Selected:", Array.from(ids));
  };

  return (
    <FileCardList
      title="我的文件"
      files={allFiles}
      selectedIds={selectedIds}
      onSelectionChange={handleSelectionChange}
    />
  );
}