源码
这里展示 registry 中会被 shadcn 安装到目标项目的组件源码。
import { MoreHorizontalIcon, SlidersHorizontalIcon } from "lucide-react"
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
type DataTableRow = {
id: string
name: string
email: string
initials: string
role: string
status: string
plan: string
activity: string
}
type DataTableCopy = {
title: string
description: string
filterLabel: string
selectedLabel: string
moreLabel: string
menuLabel: string
menuItems: [string, string, string]
columns: {
member: string
role: string
status: string
plan: string
activity: string
}
previous: string
next: string
rows: DataTableRow[]
}
type DataTableProps = {
copy?: DataTableCopy
}
const defaultCopy: DataTableCopy = {
title: "Members",
description: "A compact table for admin lists, ownership reviews, and account pages.",
filterLabel: "Filters",
selectedLabel: "3 selected",
moreLabel: "Open row actions",
menuLabel: "Row actions",
menuItems: ["Open profile", "Copy email", "Remove access"],
columns: {
member: "Member",
role: "Role",
status: "Status",
plan: "Plan",
activity: "Last active",
},
previous: "Previous",
next: "Next",
rows: [
{
id: "1",
name: "Avery Brooks",
email: "avery@example.com",
initials: "AB",
role: "Owner",
status: "Active",
plan: "Pro",
activity: "2m ago",
},
{
id: "2",
name: "Mina Park",
email: "mina@example.com",
initials: "MP",
role: "Designer",
status: "Invited",
plan: "Team",
activity: "1h ago",
},
{
id: "3",
name: "Noah Chen",
email: "noah@example.com",
initials: "NC",
role: "Engineer",
status: "Active",
plan: "Pro",
activity: "Yesterday",
},
],
}
export function DataTable({ copy = defaultCopy }: DataTableProps) {
return (
<Card className="w-full max-w-4xl">
<CardHeader>
<div className="flex flex-wrap items-start justify-between gap-3">
<div className="flex min-w-0 flex-col gap-1">
<CardTitle>{copy.title}</CardTitle>
<CardDescription>{copy.description}</CardDescription>
</div>
<div className="flex flex-wrap gap-2">
<Badge variant="secondary">{copy.selectedLabel}</Badge>
<Button variant="outline" size="sm">
<SlidersHorizontalIcon data-icon="inline-start" />
{copy.filterLabel}
</Button>
</div>
</div>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-10">
<Checkbox aria-label="Select all rows" />
</TableHead>
<TableHead>{copy.columns.member}</TableHead>
<TableHead>{copy.columns.role}</TableHead>
<TableHead>{copy.columns.status}</TableHead>
<TableHead>{copy.columns.plan}</TableHead>
<TableHead>{copy.columns.activity}</TableHead>
<TableHead className="w-10">
<span className="sr-only">{copy.moreLabel}</span>
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{copy.rows.map((row) => (
<TableRow key={row.id}>
<TableCell>
<Checkbox aria-label={`Select ${row.name}`} />
</TableCell>
<TableCell>
<div className="flex min-w-48 items-center gap-3">
<Avatar size="sm">
<AvatarFallback>{row.initials}</AvatarFallback>
</Avatar>
<div className="flex min-w-0 flex-col">
<span className="truncate font-medium">{row.name}</span>
<span className="truncate text-xs text-muted-foreground">
{row.email}
</span>
</div>
</div>
</TableCell>
<TableCell>{row.role}</TableCell>
<TableCell>
<Badge
variant={
row.status === "Active" || row.status === "已启用"
? "default"
: "outline"
}
>
{row.status}
</Badge>
</TableCell>
<TableCell>{row.plan}</TableCell>
<TableCell className="text-muted-foreground">
{row.activity}
</TableCell>
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button
aria-label={copy.moreLabel}
size="icon-sm"
variant="ghost"
/>
}
>
<MoreHorizontalIcon />
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-40">
<DropdownMenuLabel>{copy.menuLabel}</DropdownMenuLabel>
<DropdownMenuGroup>
<DropdownMenuItem>{copy.menuItems[0]}</DropdownMenuItem>
<DropdownMenuItem>{copy.menuItems[1]}</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem variant="destructive">
{copy.menuItems[2]}
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
<CardFooter className="justify-between gap-3">
<Pagination className="mx-0 justify-start">
<PaginationContent>
<PaginationItem>
<PaginationPrevious href="#" text={copy.previous} />
</PaginationItem>
<PaginationItem>
<PaginationLink href="#" isActive>
1
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">2</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext href="#" text={copy.next} />
</PaginationItem>
</PaginationContent>
</Pagination>
</CardFooter>
</Card>
)
}