卡片
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 属性,键盘导航友好
安装
代码演示
基础
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
| Prop | Type | Default | Description |
|---|---|---|---|
id | string | - | 必填。唯一标识符 |
title | React.ReactNode | - | 文件标题 |
date | React.ReactNode | - | 文件日期 |
fileIcon | React.ReactNode | - | 文件图标 |
status | FileCardStatus | "default" | 状态 |
selected | boolean | false | 选中状态 |
bordered | boolean | false | 是否显示边框 |
onSelectChange | (selected: boolean) => void | - | 选中状态变化回调 |
onActionClick | () => void | - | 操作按钮点击回调 |
className | string | - | 自定义类名 |
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
| Prop | Type | Default | Description |
|---|---|---|---|
title | string | "文件列表" | 列表标题 |
files | FileItem[] | [] | 文件列表数据 |
value | Set<string> | string[] | - | 选中的文件 ID(表单组件属性,推荐) |
selectedIds | Set<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" | 布局方向(纵向或横向) |
spacing | number | string | 16 (var(--Gap-gap-sm)) | 卡片间距,数字表示 px,字符串支持 CSS 变量 |
bordered | boolean | false | 是否显示边框 |
multiSelect | boolean | true | 是否支持多选 |
className | string | - | 自定义类名 |
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
| Prop | Type | Default | Description |
|---|---|---|---|
id | string | - | 必填。唯一标识符 |
title | React.ReactNode | - | 文件标题 |
date | React.ReactNode | - | 文件日期 |
fileIcon | React.ReactNode | - | 文件图标 |
statusIcon | React.ReactNode | - | 状态图标 |
selected | boolean | false | 选中状态 |
onSelectChange | (selected: boolean) => void | - | 选中状态变化回调 |
onActionClick | () => void | - | 操作按钮点击回调 |
className | string | - | 容器自定义类名 |
使用场景
- 文件管理:展示和管理文件列表
- 附件展示:展示消息或表单中的附件
- 下载列表:展示正在下载或已下载的文件
- 上传列表:展示正在上传的文件
- 云盘文件:展示云盘中的文件列表
最佳实践
- 简洁设计:去除复杂状态,只保留核心信息
- 状态图标:使用状态图标明确文件当前状态
- 多选功能:配合 FileCardList 使用,支持全选/取消全选
- 自定义图标:根据文件类型使用不同的图标
原语组件
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}
/>
);
}