diff --git a/apps/examples/calcom/app/(settings)/settings/teams/[id]/members/page.tsx b/apps/examples/calcom/app/(settings)/settings/teams/[id]/members/page.tsx new file mode 100644 index 000000000..f83a5c2f3 --- /dev/null +++ b/apps/examples/calcom/app/(settings)/settings/teams/[id]/members/page.tsx @@ -0,0 +1,5 @@ +import { TeamMembersPageClient } from "@/app/(settings)/settings/teams/[id]/members/team-members-page-client"; + +export default function TeamMembersPage() { + return ; +} diff --git a/apps/examples/calcom/app/(settings)/settings/teams/[id]/members/team-members-page-client.tsx b/apps/examples/calcom/app/(settings)/settings/teams/[id]/members/team-members-page-client.tsx new file mode 100644 index 000000000..71f555722 --- /dev/null +++ b/apps/examples/calcom/app/(settings)/settings/teams/[id]/members/team-members-page-client.tsx @@ -0,0 +1,615 @@ +"use client"; + +import { + Avatar, + AvatarFallback, + AvatarImage, +} from "@coss/ui/components/avatar"; +import { Badge } from "@coss/ui/components/badge"; +import { Button } from "@coss/ui/components/button"; +import { CardFrame } from "@coss/ui/components/card"; +import { Checkbox } from "@coss/ui/components/checkbox"; +import { + Combobox, + ComboboxCollection, + ComboboxEmpty, + ComboboxGroup, + ComboboxGroupLabel, + ComboboxInput, + ComboboxItem, + ComboboxList, + ComboboxPopup, + ComboboxTrigger, + ComboboxValue, +} from "@coss/ui/components/combobox"; +import { Group, GroupSeparator } from "@coss/ui/components/group"; +import { + InputGroup, + InputGroupAddon, + InputGroupInput, +} from "@coss/ui/components/input-group"; +import { Label } from "@coss/ui/components/label"; +import { + Menu, + MenuGroup, + MenuGroupLabel, + MenuItem, + MenuPopup, + MenuSeparator, + MenuTrigger, +} from "@coss/ui/components/menu"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@coss/ui/components/table"; +import { + type ColumnDef, + flexRender, + getCoreRowModel, + getSortedRowModel, + type SortingState, + useReactTable, +} from "@tanstack/react-table"; +import { + ArrowUpRightIcon, + ChevronDownIcon, + ChevronUpIcon, + EllipsisIcon, + FunnelIcon, + PlusIcon, + SearchIcon, + SlidersHorizontalIcon, + UserPlusIcon, +} from "lucide-react"; +import { useEffect, useMemo, useState } from "react"; +import { + AppHeader, + AppHeaderContent, + AppHeaderDescription, +} from "@/components/app/app-header"; + +type TeamRole = "MEMBER" | "OWNER"; +type RoleFilter = "all" | TeamRole; + +type ColumnToggleItem = { label: string; value: "role" | "lastActive" }; + +const COLUMN_TOGGLE_ITEMS: ColumnToggleItem[] = [ + { label: "Role", value: "role" }, + { label: "Last Active", value: "lastActive" }, +]; + +const ROLE_FILTER_ITEMS: { label: string; value: RoleFilter }[] = [ + { label: "All members", value: "all" }, + { label: "Owners", value: "OWNER" }, + { label: "Members", value: "MEMBER" }, +]; + +type TeamMember = { + id: string; + name: string; + email: string; + role: TeamRole; + lastActive: string; + avatarUrl?: string; + hasOptions?: boolean; +}; + +const members: TeamMember[] = [ + { + avatarUrl: + "https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=128&h=128&fit=crop&q=80", + email: "teampro@example.com", + id: "team-pro-example", + lastActive: "Active now", + name: "Team Pro Example", + role: "OWNER", + }, + { + avatarUrl: + "https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=128&h=128&fit=crop&q=80", + email: "teamfree@example.com", + id: "team-free-example", + lastActive: "2 days ago", + name: "Team Free Example", + role: "OWNER", + }, + { + avatarUrl: + "https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?w=128&h=128&fit=crop&q=80", + email: "teampro2@example.com", + id: "team-pro-example-2", + lastActive: "1 week ago", + name: "Team Pro Example 2", + role: "MEMBER", + }, + { + avatarUrl: + "https://images.unsplash.com/photo-1517841905240-472988babdf9?w=128&h=128&fit=crop&q=80", + email: "teampro3@example.com", + hasOptions: false, + id: "team-pro-example-3", + lastActive: "3 hours ago", + name: "Team Pro Example 3", + role: "OWNER", + }, + { + avatarUrl: + "https://images.unsplash.com/photo-1504593811423-6dd665756598?w=128&h=128&fit=crop&q=80", + email: "teampro4@example.com", + id: "team-pro-example-4", + lastActive: "Just now", + name: "Team Pro Example 4", + role: "OWNER", + }, +]; + +function getInitials(name: string): string { + const parts = name.trim().split(/\s+/); + if (parts.length === 0) return ""; + if (parts.length === 1) return parts[0]?.charAt(0).toUpperCase() ?? ""; + return `${parts[0]?.charAt(0) ?? ""}${parts.at(-1)?.charAt(0) ?? ""}`.toUpperCase(); +} + +const ROLE_LABEL: Record = { + MEMBER: "Member", + OWNER: "Owner", +}; + +function RoleBadge({ role }: { role: TeamRole }) { + return ( + + {ROLE_LABEL[role]} + + ); +} + +function MemberActions({ + canManage = true, + memberName, +}: { + canManage?: boolean; + memberName: string; +}) { + const openProfileButton = ( + + ); + + if (!canManage) { + return openProfileButton; + } + + return ( + + {openProfileButton} + + + + } + > + + + + Member + + + + + + + + Permissions + Change role + Remove from team + + + + + ); +} + +function getColumns({ + showRoleColumn, + showLastActiveColumn, +}: { + showRoleColumn: boolean; + showLastActiveColumn: boolean; +}): ColumnDef[] { + const cols: ColumnDef[] = [ + { + cell: ({ row }) => ( + + ), + enableSorting: false, + header: ({ table }) => ( + table.toggleAllRowsSelected(!!value)} + /> + ), + id: "select", + size: 28, + }, + { + accessorKey: "name", + cell: ({ row }) => ( +
+ + {row.original.avatarUrl ? ( + + ) : null} + {getInitials(row.original.name)} + +
+
+ {row.original.name} +
+
+ {row.original.email} +
+
+
+ ), + header: "Member", + size: 240, + }, + ]; + + if (showRoleColumn) { + cols.push({ + accessorKey: "role", + cell: ({ row }) => , + header: "Role", + size: 80, + }); + } + + if (showLastActiveColumn) { + cols.push({ + accessorKey: "lastActive", + cell: ({ row }) => ( + + {row.original.lastActive} + + ), + header: "Last Active", + size: 100, + }); + } + + cols.push({ + cell: ({ row }) => ( +
+ +
+ ), + enableSorting: false, + header: () => Actions, + id: "actions", + size: 80, + }); + + return cols; +} + +export function TeamMembersPageClient() { + const [roleFilter, setRoleFilter] = useState("all"); + const [searchValue, setSearchValue] = useState(""); + const [showRoleColumn, setShowRoleColumn] = useState(true); + const [showLastActiveColumn, setShowLastActiveColumn] = useState(true); + const [rowSelection, setRowSelection] = useState({}); + const [sorting, setSorting] = useState([ + { desc: false, id: "name" }, + ]); + + const filteredMembers = useMemo(() => { + const query = searchValue.trim().toLowerCase(); + + return members.filter((member) => { + const matchesRole = roleFilter === "all" || member.role === roleFilter; + const matchesQuery = + query.length === 0 || + member.name.toLowerCase().includes(query) || + member.email.toLowerCase().includes(query); + + return matchesRole && matchesQuery; + }); + }, [roleFilter, searchValue]); + + const columns = useMemo( + () => + getColumns({ + showLastActiveColumn, + showRoleColumn, + }), + [showLastActiveColumn, showRoleColumn], + ); + + const roleFilterItem = + ROLE_FILTER_ITEMS.find((item) => item.value === roleFilter) ?? + ROLE_FILTER_ITEMS[0]!; + + const columnToggleValue = useMemo((): ColumnToggleItem[] => { + const selected: ColumnToggleItem[] = []; + if (showRoleColumn) selected.push(COLUMN_TOGGLE_ITEMS[0]!); + if (showLastActiveColumn) selected.push(COLUMN_TOGGLE_ITEMS[1]!); + return selected; + }, [showLastActiveColumn, showRoleColumn]); + + useEffect(() => { + setSorting((previous) => { + let next = previous; + if (!showRoleColumn) { + next = next.filter((s) => s.id !== "role"); + } + if (!showLastActiveColumn) { + next = next.filter((s) => s.id !== "lastActive"); + } + return next.length > 0 ? next : [{ desc: false, id: "name" }]; + }); + }, [showLastActiveColumn, showRoleColumn]); + + const table = useReactTable({ + columns, + data: filteredMembers, + enableRowSelection: true, + enableSortingRemoval: false, + getCoreRowModel: getCoreRowModel(), + getRowId: (row) => row.id, + getSortedRowModel: getSortedRowModel(), + onRowSelectionChange: setRowSelection, + onSortingChange: setSorting, + state: { + rowSelection, + sorting, + }, + }); + + return ( + <> + + + + Users that are in the group + + + + +
+
+
+ + setSearchValue(event.target.value)} + placeholder="Search" + type="search" + value={searchValue} + /> + + + + + { + const next = items ?? []; + setShowRoleColumn(next.some((i) => i.value === "role")); + setShowLastActiveColumn( + next.some((i) => i.value === "lastActive"), + ); + }} + value={columnToggleValue} + > + } + > + + +
+
+ No columns found. + + + Toggle columns + + {(item: ColumnToggleItem) => ( + + {item.label} + + )} + + + +
+ +
+
+
+ + item && setRoleFilter(item.value)} + value={roleFilterItem} + > + }> + + + No roles found. + + + Role + + {(item: (typeof ROLE_FILTER_ITEMS)[number]) => ( + + {item.label} + + )} + + + + + +
+ + +
+ + + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + const columnSize = header.column.getSize(); + + return ( + + {header.isPlaceholder ? null : header.column.getCanSort() ? ( +
{ + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + header.column.getToggleSortingHandler()?.( + event, + ); + } + }} + role="button" + tabIndex={0} + > + {flexRender( + header.column.columnDef.header, + header.getContext(), + )} + {{ + asc: ( +
+ ) : ( + flexRender( + header.column.columnDef.header, + header.getContext(), + ) + )} +
+ ); + })} +
+ ))} +
+ + {table.getRowModel().rows.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} + + ))} + + )) + ) : ( + + + No members found. + + + )} + +
+
+
+ + ); +} diff --git a/apps/examples/calcom/package.json b/apps/examples/calcom/package.json index daf7a5dca..5f818680e 100644 --- a/apps/examples/calcom/package.json +++ b/apps/examples/calcom/package.json @@ -6,6 +6,7 @@ "@dnd-kit/utilities": "^3.2.2", "@hugeicons/core-free-icons": "^2.0.0", "@hugeicons/react": "^1.1.1", + "@tanstack/react-table": "^8.21.3", "dompurify": "^3.3.1", "lucide-react": "^0.555.0", "marked": "^17.0.1", diff --git a/apps/ui/public/r/card.json b/apps/ui/public/r/card.json index 16c655819..69ff8405a 100644 --- a/apps/ui/public/r/card.json +++ b/apps/ui/public/r/card.json @@ -7,7 +7,7 @@ "files": [ { "path": "registry/default/ui/card.tsx", - "content": "\"use client\";\n\nimport { mergeProps } from \"@base-ui/react/merge-props\";\nimport { useRender } from \"@base-ui/react/use-render\";\nimport type React from \"react\";\nimport { cn } from \"@/registry/default/lib/utils\";\n\nexport function Card({\n className,\n render,\n ...props\n}: useRender.ComponentProps<\"div\">): React.ReactElement {\n const defaultProps = {\n className: cn(\n \"relative flex flex-col rounded-2xl border bg-card not-dark:bg-clip-padding text-card-foreground shadow-xs/5 before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-2xl)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)]\",\n className,\n ),\n \"data-slot\": \"card\",\n };\n\n return useRender({\n defaultTagName: \"div\",\n props: mergeProps<\"div\">(defaultProps, props),\n render,\n });\n}\n\nexport function CardFrame({\n className,\n render,\n ...props\n}: useRender.ComponentProps<\"div\">): React.ReactElement {\n const defaultProps = {\n className: cn(\n \"relative flex flex-col rounded-2xl border bg-card not-dark:bg-clip-padding text-card-foreground shadow-xs/5 [--clip-bottom:-1rem] [--clip-top:-1rem] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-2xl)-1px)] before:bg-muted/72 before:shadow-[0_1px_--theme(--color-black/4%)] *:data-[slot=card]:-m-px *:not-first:data-[slot=card]:rounded-t-xl *:not-last:data-[slot=card]:rounded-b-xl *:data-[slot=card]:bg-clip-padding *:data-[slot=card]:shadow-none *:data-[slot=card]:before:hidden *:not-first:data-[slot=card]:before:rounded-t-[calc(var(--radius-xl)-1px)] *:not-last:data-[slot=card]:before:rounded-b-[calc(var(--radius-xl)-1px)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)] *:data-[slot=card]:[clip-path:inset(var(--clip-top)_1px_var(--clip-bottom)_1px_round_calc(var(--radius-2xl)-1px))] *:data-[slot=card]:last:[--clip-bottom:1px] *:data-[slot=card]:first:[--clip-top:1px]\",\n className,\n ),\n \"data-slot\": \"card-frame\",\n };\n\n return useRender({\n defaultTagName: \"div\",\n props: mergeProps<\"div\">(defaultProps, props),\n render,\n });\n}\n\nexport function CardFrameHeader({\n className,\n render,\n ...props\n}: useRender.ComponentProps<\"div\">): React.ReactElement {\n const defaultProps = {\n className: cn(\n \"relative flex grid auto-rows-min grid-rows-[auto_auto] flex-col items-start gap-x-4 px-6 py-4 has-data-[slot=card-frame-action]:grid-cols-[1fr_auto]\",\n className,\n ),\n \"data-slot\": \"card-frame-header\",\n };\n\n return useRender({\n defaultTagName: \"div\",\n props: mergeProps<\"div\">(defaultProps, props),\n render,\n });\n}\n\nexport function CardFrameTitle({\n className,\n render,\n ...props\n}: useRender.ComponentProps<\"div\">): React.ReactElement {\n const defaultProps = {\n className: cn(\"self-center font-semibold text-sm\", className),\n \"data-slot\": \"card-frame-title\",\n };\n\n return useRender({\n defaultTagName: \"div\",\n props: mergeProps<\"div\">(defaultProps, props),\n render,\n });\n}\n\nexport function CardFrameDescription({\n className,\n render,\n ...props\n}: useRender.ComponentProps<\"div\">): React.ReactElement {\n const defaultProps = {\n className: cn(\"self-center text-muted-foreground text-sm\", className),\n \"data-slot\": \"card-frame-description\",\n };\n\n return useRender({\n defaultTagName: \"div\",\n props: mergeProps<\"div\">(defaultProps, props),\n render,\n });\n}\n\nexport function CardFrameAction({\n className,\n render,\n ...props\n}: useRender.ComponentProps<\"div\">): React.ReactElement {\n const defaultProps = {\n className: cn(\n \"col-start-2 nth-3:row-span-2 nth-3:row-start-1 inline-flex self-center justify-self-end\",\n className,\n ),\n \"data-slot\": \"card-frame-action\",\n };\n\n return useRender({\n defaultTagName: \"div\",\n props: mergeProps<\"div\">(defaultProps, props),\n render,\n });\n}\n\nexport function CardFrameFooter({\n className,\n render,\n ...props\n}: useRender.ComponentProps<\"div\">): React.ReactElement {\n const defaultProps = {\n className: cn(\"px-6 py-4\", className),\n \"data-slot\": \"card-frame-footer\",\n };\n\n return useRender({\n defaultTagName: \"div\",\n props: mergeProps<\"div\">(defaultProps, props),\n render,\n });\n}\n\nexport function CardHeader({\n className,\n render,\n ...props\n}: useRender.ComponentProps<\"div\">): React.ReactElement {\n const defaultProps = {\n className: cn(\n \"grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 p-6 in-[[data-slot=card]:has(>[data-slot=card-panel])]:pb-4 has-data-[slot=card-action]:grid-cols-[1fr_auto]\",\n className,\n ),\n \"data-slot\": \"card-header\",\n };\n\n return useRender({\n defaultTagName: \"div\",\n props: mergeProps<\"div\">(defaultProps, props),\n render,\n });\n}\n\nexport function CardTitle({\n className,\n render,\n ...props\n}: useRender.ComponentProps<\"div\">): React.ReactElement {\n const defaultProps = {\n className: cn(\"font-semibold text-lg leading-none\", className),\n \"data-slot\": \"card-title\",\n };\n\n return useRender({\n defaultTagName: \"div\",\n props: mergeProps<\"div\">(defaultProps, props),\n render,\n });\n}\n\nexport function CardDescription({\n className,\n render,\n ...props\n}: useRender.ComponentProps<\"div\">): React.ReactElement {\n const defaultProps = {\n className: cn(\"text-muted-foreground text-sm\", className),\n \"data-slot\": \"card-description\",\n };\n\n return useRender({\n defaultTagName: \"div\",\n props: mergeProps<\"div\">(defaultProps, props),\n render,\n });\n}\n\nexport function CardAction({\n className,\n render,\n ...props\n}: useRender.ComponentProps<\"div\">): React.ReactElement {\n const defaultProps = {\n className: cn(\n \"col-start-2 row-span-2 row-start-1 inline-flex self-start justify-self-end\",\n className,\n ),\n \"data-slot\": \"card-action\",\n };\n\n return useRender({\n defaultTagName: \"div\",\n props: mergeProps<\"div\">(defaultProps, props),\n render,\n });\n}\n\nexport function CardPanel({\n className,\n render,\n ...props\n}: useRender.ComponentProps<\"div\">): React.ReactElement {\n const defaultProps = {\n className: cn(\n \"flex-1 p-6 in-[[data-slot=card]:has(>[data-slot=card-header]:not(.border-b))]:pt-0 in-[[data-slot=card]:has(>[data-slot=card-footer]:not(.border-t))]:pb-0\",\n className,\n ),\n \"data-slot\": \"card-panel\",\n };\n\n return useRender({\n defaultTagName: \"div\",\n props: mergeProps<\"div\">(defaultProps, props),\n render,\n });\n}\n\nexport function CardFooter({\n className,\n render,\n ...props\n}: useRender.ComponentProps<\"div\">): React.ReactElement {\n const defaultProps = {\n className: cn(\n \"flex items-center p-6 in-[[data-slot=card]:has(>[data-slot=card-panel])]:pt-4\",\n className,\n ),\n \"data-slot\": \"card-footer\",\n };\n\n return useRender({\n defaultTagName: \"div\",\n props: mergeProps<\"div\">(defaultProps, props),\n render,\n });\n}\n\nexport { CardPanel as CardContent };\n", + "content": "\"use client\";\n\nimport { mergeProps } from \"@base-ui/react/merge-props\";\nimport { useRender } from \"@base-ui/react/use-render\";\nimport type React from \"react\";\nimport { cn } from \"@/registry/default/lib/utils\";\n\nexport function Card({\n className,\n render,\n ...props\n}: useRender.ComponentProps<\"div\">): React.ReactElement {\n const defaultProps = {\n className: cn(\n \"relative flex flex-col rounded-2xl border bg-card not-dark:bg-clip-padding text-card-foreground shadow-xs/5 before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-2xl)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)]\",\n className,\n ),\n \"data-slot\": \"card\",\n };\n\n return useRender({\n defaultTagName: \"div\",\n props: mergeProps<\"div\">(defaultProps, props),\n render,\n });\n}\n\nexport function CardFrame({\n className,\n render,\n ...props\n}: useRender.ComponentProps<\"div\">): React.ReactElement {\n const defaultProps = {\n className: cn(\n \"relative flex flex-col rounded-2xl border bg-card not-dark:bg-clip-padding text-card-foreground shadow-xs/5 [--clip-bottom:-1rem] [--clip-top:-1rem] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-2xl)-1px)] before:bg-muted/72 before:shadow-[0_1px_--theme(--color-black/4%)] *:data-[slot=card]:-m-px *:data-[slot=table-container]:-mx-px *:data-[slot=table-container]:w-[calc(100%+2px)] *:not-first:data-[slot=card]:rounded-t-xl *:not-last:data-[slot=card]:rounded-b-xl *:data-[slot=card]:bg-clip-padding *:data-[slot=card]:shadow-none *:data-[slot=card]:before:hidden *:not-first:data-[slot=card]:before:rounded-t-[calc(var(--radius-xl)-1px)] *:not-last:data-[slot=card]:before:rounded-b-[calc(var(--radius-xl)-1px)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)] *:data-[slot=card]:[clip-path:inset(var(--clip-top)_1px_var(--clip-bottom)_1px_round_calc(var(--radius-2xl)-1px))] *:data-[slot=card]:last:[--clip-bottom:1px] *:data-[slot=card]:first:[--clip-top:1px]\",\n className,\n ),\n \"data-slot\": \"card-frame\",\n };\n\n return useRender({\n defaultTagName: \"div\",\n props: mergeProps<\"div\">(defaultProps, props),\n render,\n });\n}\n\nexport function CardFrameHeader({\n className,\n render,\n ...props\n}: useRender.ComponentProps<\"div\">): React.ReactElement {\n const defaultProps = {\n className: cn(\n \"relative flex grid auto-rows-min grid-rows-[auto_auto] flex-col items-start gap-x-4 px-6 py-4 has-data-[slot=card-frame-action]:grid-cols-[1fr_auto]\",\n className,\n ),\n \"data-slot\": \"card-frame-header\",\n };\n\n return useRender({\n defaultTagName: \"div\",\n props: mergeProps<\"div\">(defaultProps, props),\n render,\n });\n}\n\nexport function CardFrameTitle({\n className,\n render,\n ...props\n}: useRender.ComponentProps<\"div\">): React.ReactElement {\n const defaultProps = {\n className: cn(\"self-center font-semibold text-sm\", className),\n \"data-slot\": \"card-frame-title\",\n };\n\n return useRender({\n defaultTagName: \"div\",\n props: mergeProps<\"div\">(defaultProps, props),\n render,\n });\n}\n\nexport function CardFrameDescription({\n className,\n render,\n ...props\n}: useRender.ComponentProps<\"div\">): React.ReactElement {\n const defaultProps = {\n className: cn(\"self-center text-muted-foreground text-sm\", className),\n \"data-slot\": \"card-frame-description\",\n };\n\n return useRender({\n defaultTagName: \"div\",\n props: mergeProps<\"div\">(defaultProps, props),\n render,\n });\n}\n\nexport function CardFrameAction({\n className,\n render,\n ...props\n}: useRender.ComponentProps<\"div\">): React.ReactElement {\n const defaultProps = {\n className: cn(\n \"col-start-2 nth-3:row-span-2 nth-3:row-start-1 inline-flex self-center justify-self-end\",\n className,\n ),\n \"data-slot\": \"card-frame-action\",\n };\n\n return useRender({\n defaultTagName: \"div\",\n props: mergeProps<\"div\">(defaultProps, props),\n render,\n });\n}\n\nexport function CardFrameFooter({\n className,\n render,\n ...props\n}: useRender.ComponentProps<\"div\">): React.ReactElement {\n const defaultProps = {\n className: cn(\"px-6 py-4\", className),\n \"data-slot\": \"card-frame-footer\",\n };\n\n return useRender({\n defaultTagName: \"div\",\n props: mergeProps<\"div\">(defaultProps, props),\n render,\n });\n}\n\nexport function CardHeader({\n className,\n render,\n ...props\n}: useRender.ComponentProps<\"div\">): React.ReactElement {\n const defaultProps = {\n className: cn(\n \"grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 p-6 in-[[data-slot=card]:has(>[data-slot=card-panel])]:pb-4 has-data-[slot=card-action]:grid-cols-[1fr_auto]\",\n className,\n ),\n \"data-slot\": \"card-header\",\n };\n\n return useRender({\n defaultTagName: \"div\",\n props: mergeProps<\"div\">(defaultProps, props),\n render,\n });\n}\n\nexport function CardTitle({\n className,\n render,\n ...props\n}: useRender.ComponentProps<\"div\">): React.ReactElement {\n const defaultProps = {\n className: cn(\"font-semibold text-lg leading-none\", className),\n \"data-slot\": \"card-title\",\n };\n\n return useRender({\n defaultTagName: \"div\",\n props: mergeProps<\"div\">(defaultProps, props),\n render,\n });\n}\n\nexport function CardDescription({\n className,\n render,\n ...props\n}: useRender.ComponentProps<\"div\">): React.ReactElement {\n const defaultProps = {\n className: cn(\"text-muted-foreground text-sm\", className),\n \"data-slot\": \"card-description\",\n };\n\n return useRender({\n defaultTagName: \"div\",\n props: mergeProps<\"div\">(defaultProps, props),\n render,\n });\n}\n\nexport function CardAction({\n className,\n render,\n ...props\n}: useRender.ComponentProps<\"div\">): React.ReactElement {\n const defaultProps = {\n className: cn(\n \"col-start-2 row-span-2 row-start-1 inline-flex self-start justify-self-end\",\n className,\n ),\n \"data-slot\": \"card-action\",\n };\n\n return useRender({\n defaultTagName: \"div\",\n props: mergeProps<\"div\">(defaultProps, props),\n render,\n });\n}\n\nexport function CardPanel({\n className,\n render,\n ...props\n}: useRender.ComponentProps<\"div\">): React.ReactElement {\n const defaultProps = {\n className: cn(\n \"flex-1 p-6 in-[[data-slot=card]:has(>[data-slot=card-header]:not(.border-b))]:pt-0 in-[[data-slot=card]:has(>[data-slot=card-footer]:not(.border-t))]:pb-0\",\n className,\n ),\n \"data-slot\": \"card-panel\",\n };\n\n return useRender({\n defaultTagName: \"div\",\n props: mergeProps<\"div\">(defaultProps, props),\n render,\n });\n}\n\nexport function CardFooter({\n className,\n render,\n ...props\n}: useRender.ComponentProps<\"div\">): React.ReactElement {\n const defaultProps = {\n className: cn(\n \"flex items-center p-6 in-[[data-slot=card]:has(>[data-slot=card-panel])]:pt-4\",\n className,\n ),\n \"data-slot\": \"card-footer\",\n };\n\n return useRender({\n defaultTagName: \"div\",\n props: mergeProps<\"div\">(defaultProps, props),\n render,\n });\n}\n\nexport { CardPanel as CardContent };\n", "type": "registry:ui" } ], diff --git a/apps/ui/public/r/p-table-2.json b/apps/ui/public/r/p-table-2.json index 367920d11..75e31ce80 100644 --- a/apps/ui/public/r/p-table-2.json +++ b/apps/ui/public/r/p-table-2.json @@ -1,7 +1,7 @@ { "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "p-table-2", - "description": "Framed table", + "description": "Frame with boxed table", "registryDependencies": [ "@coss/badge", "@coss/frame", @@ -10,7 +10,7 @@ "files": [ { "path": "registry/default/particles/p-table-2.tsx", - "content": "import { Badge } from \"@/registry/default/ui/badge\";\nimport { Frame } from \"@/registry/default/ui/frame\";\nimport {\n Table,\n TableBody,\n TableCell,\n TableFooter,\n TableHead,\n TableHeader,\n TableRow,\n} from \"@/registry/default/ui/table\";\n\nexport default function Particle() {\n return (\n \n \n \n \n Project\n Status\n Team\n Budget\n \n \n \n \n Website Redesign\n \n \n \n Paid\n \n \n Frontend Team\n $12,500\n \n \n Mobile App\n \n \n \n Unpaid\n \n \n Mobile Team\n $8,750\n \n \n API Integration\n \n \n \n Pending\n \n \n Backend Team\n $5,200\n \n \n Database Migration\n \n \n \n Paid\n \n \n DevOps Team\n $3,800\n \n \n User Dashboard\n \n \n \n Paid\n \n \n UX Team\n $7,200\n \n \n Security Audit\n \n \n \n Failed\n \n \n Security Team\n $2,100\n \n \n \n \n Total Budget\n $39,550\n \n \n
\n \n );\n}\n", + "content": "import { Badge } from \"@/registry/default/ui/badge\";\nimport { Frame } from \"@/registry/default/ui/frame\";\nimport {\n Table,\n TableBody,\n TableCell,\n TableFooter,\n TableHead,\n TableHeader,\n TableRow,\n} from \"@/registry/default/ui/table\";\n\nexport default function Particle() {\n return (\n \n \n \n \n Project\n Status\n Team\n Budget\n \n \n \n \n Website Redesign\n \n \n \n Paid\n \n \n Frontend Team\n $12,500\n \n \n Mobile App\n \n \n \n Unpaid\n \n \n Mobile Team\n $8,750\n \n \n API Integration\n \n \n \n Pending\n \n \n Backend Team\n $5,200\n \n \n Database Migration\n \n \n \n Paid\n \n \n DevOps Team\n $3,800\n \n \n User Dashboard\n \n \n \n Paid\n \n \n UX Team\n $7,200\n \n \n Security Audit\n \n \n \n Failed\n \n \n Security Team\n $2,100\n \n \n \n \n Total Budget\n $39,550\n \n \n
\n \n );\n}\n", "type": "registry:block" } ], diff --git a/apps/ui/public/r/p-table-3.json b/apps/ui/public/r/p-table-3.json index c1a3c80ad..f403218c9 100644 --- a/apps/ui/public/r/p-table-3.json +++ b/apps/ui/public/r/p-table-3.json @@ -14,7 +14,7 @@ "files": [ { "path": "registry/default/particles/p-table-3.tsx", - "content": "\"use client\";\n\nimport {\n type ColumnDef,\n flexRender,\n getCoreRowModel,\n useReactTable,\n} from \"@tanstack/react-table\";\nimport type React from \"react\";\nimport { useMemo, useState } from \"react\";\nimport { Badge } from \"@/registry/default/ui/badge\";\nimport { Checkbox } from \"@/registry/default/ui/checkbox\";\nimport { Frame } from \"@/registry/default/ui/frame\";\nimport {\n Table,\n TableBody,\n TableCell,\n TableFooter,\n TableHead,\n TableHeader,\n TableRow,\n} from \"@/registry/default/ui/table\";\n\ntype Project = {\n id: string;\n project: string;\n status: \"Paid\" | \"Unpaid\" | \"Pending\" | \"Failed\";\n team: string;\n budget: number;\n};\n\nconst data: Project[] = [\n {\n budget: 12500,\n id: \"1\",\n project: \"Website Redesign\",\n status: \"Paid\",\n team: \"Frontend Team\",\n },\n {\n budget: 8750,\n id: \"2\",\n project: \"Mobile App\",\n status: \"Unpaid\",\n team: \"Mobile Team\",\n },\n {\n budget: 5200,\n id: \"3\",\n project: \"API Integration\",\n status: \"Pending\",\n team: \"Backend Team\",\n },\n {\n budget: 3800,\n id: \"4\",\n project: \"Database Migration\",\n status: \"Paid\",\n team: \"DevOps Team\",\n },\n {\n budget: 7200,\n id: \"5\",\n project: \"User Dashboard\",\n status: \"Paid\",\n team: \"UX Team\",\n },\n {\n budget: 2100,\n id: \"6\",\n project: \"Security Audit\",\n status: \"Failed\",\n team: \"Security Team\",\n },\n];\n\nconst getStatusColor = (status: Project[\"status\"]) => {\n switch (status) {\n case \"Paid\":\n return \"bg-emerald-500\";\n case \"Unpaid\":\n return \"bg-muted-foreground/64\";\n case \"Pending\":\n return \"bg-amber-500\";\n case \"Failed\":\n return \"bg-red-500\";\n default:\n return \"bg-muted-foreground/64\";\n }\n};\n\nconst getColumns = (): ColumnDef[] => [\n {\n cell: ({ row }) => {\n const toggleHandler = row.getToggleSelectedHandler();\n return (\n {\n // Create a synthetic event for the handler\n const syntheticEvent = {\n target: { checked: !!value },\n } as unknown as React.ChangeEvent;\n toggleHandler(syntheticEvent);\n }}\n />\n );\n },\n enableSorting: false,\n header: ({ table }) => {\n const isAllSelected = table.getIsAllPageRowsSelected();\n const isSomeSelected = table.getIsSomePageRowsSelected();\n const toggleHandler = table.getToggleAllPageRowsSelectedHandler();\n return (\n {\n // Create a synthetic event for the handler\n const syntheticEvent = {\n target: { checked: !!value },\n } as unknown as React.ChangeEvent;\n toggleHandler(syntheticEvent);\n }}\n />\n );\n },\n id: \"select\",\n },\n {\n accessorKey: \"project\",\n cell: ({ row }) => (\n
{row.getValue(\"project\")}
\n ),\n header: \"Project\",\n },\n {\n accessorKey: \"status\",\n cell: ({ row }) => {\n const status = row.getValue(\"status\") as Project[\"status\"];\n return (\n \n \n {status}\n \n );\n },\n header: \"Status\",\n },\n {\n accessorKey: \"team\",\n header: \"Team\",\n },\n {\n accessorKey: \"budget\",\n cell: ({ row }) => {\n const amount = Number.parseFloat(row.getValue(\"budget\"));\n const formatted = new Intl.NumberFormat(\"en-US\", {\n currency: \"USD\",\n maximumFractionDigits: 0,\n minimumFractionDigits: 0,\n style: \"currency\",\n }).format(amount);\n return
{formatted}
;\n },\n header: () =>
Budget
,\n },\n];\n\nexport default function Particle() {\n const [tableData] = useState(data);\n const [rowSelection, setRowSelection] = useState({});\n\n const columns = useMemo(() => getColumns(), []);\n\n const table = useReactTable({\n columns,\n data: tableData,\n enableRowSelection: true,\n getCoreRowModel: getCoreRowModel(),\n onRowSelectionChange: setRowSelection,\n state: {\n rowSelection,\n },\n });\n\n const totalBudget = tableData.reduce(\n (sum, project) => sum + project.budget,\n 0,\n );\n const formattedTotal = new Intl.NumberFormat(\"en-US\", {\n currency: \"USD\",\n maximumFractionDigits: 0,\n minimumFractionDigits: 0,\n style: \"currency\",\n }).format(totalBudget);\n\n return (\n \n \n \n {table.getHeaderGroups().map((headerGroup) => (\n \n {headerGroup.headers.map((header) => {\n return (\n \n {header.isPlaceholder\n ? null\n : flexRender(\n header.column.columnDef.header,\n header.getContext(),\n )}\n \n );\n })}\n \n ))}\n \n \n {table.getRowModel().rows?.length ? (\n table.getRowModel().rows.map((row) => (\n \n {row.getVisibleCells().map((cell) => (\n \n {flexRender(cell.column.columnDef.cell, cell.getContext())}\n \n ))}\n \n ))\n ) : (\n \n \n No results.\n \n \n )}\n \n \n \n Total Budget\n {formattedTotal}\n \n \n
\n \n );\n}\n", + "content": "\"use client\";\n\nimport {\n type ColumnDef,\n flexRender,\n getCoreRowModel,\n useReactTable,\n} from \"@tanstack/react-table\";\nimport type React from \"react\";\nimport { useMemo, useState } from \"react\";\nimport { Badge } from \"@/registry/default/ui/badge\";\nimport { Checkbox } from \"@/registry/default/ui/checkbox\";\nimport { Frame } from \"@/registry/default/ui/frame\";\nimport {\n Table,\n TableBody,\n TableCell,\n TableFooter,\n TableHead,\n TableHeader,\n TableRow,\n} from \"@/registry/default/ui/table\";\n\ntype Project = {\n id: string;\n project: string;\n status: \"Paid\" | \"Unpaid\" | \"Pending\" | \"Failed\";\n team: string;\n budget: number;\n};\n\nconst data: Project[] = [\n {\n budget: 12500,\n id: \"1\",\n project: \"Website Redesign\",\n status: \"Paid\",\n team: \"Frontend Team\",\n },\n {\n budget: 8750,\n id: \"2\",\n project: \"Mobile App\",\n status: \"Unpaid\",\n team: \"Mobile Team\",\n },\n {\n budget: 5200,\n id: \"3\",\n project: \"API Integration\",\n status: \"Pending\",\n team: \"Backend Team\",\n },\n {\n budget: 3800,\n id: \"4\",\n project: \"Database Migration\",\n status: \"Paid\",\n team: \"DevOps Team\",\n },\n {\n budget: 7200,\n id: \"5\",\n project: \"User Dashboard\",\n status: \"Paid\",\n team: \"UX Team\",\n },\n {\n budget: 2100,\n id: \"6\",\n project: \"Security Audit\",\n status: \"Failed\",\n team: \"Security Team\",\n },\n];\n\nconst getStatusColor = (status: Project[\"status\"]) => {\n switch (status) {\n case \"Paid\":\n return \"bg-emerald-500\";\n case \"Unpaid\":\n return \"bg-muted-foreground/64\";\n case \"Pending\":\n return \"bg-amber-500\";\n case \"Failed\":\n return \"bg-red-500\";\n default:\n return \"bg-muted-foreground/64\";\n }\n};\n\nconst getColumns = (): ColumnDef[] => [\n {\n cell: ({ row }) => {\n const toggleHandler = row.getToggleSelectedHandler();\n return (\n {\n // Create a synthetic event for the handler\n const syntheticEvent = {\n target: { checked: !!value },\n } as unknown as React.ChangeEvent;\n toggleHandler(syntheticEvent);\n }}\n />\n );\n },\n enableSorting: false,\n header: ({ table }) => {\n const isAllSelected = table.getIsAllPageRowsSelected();\n const isSomeSelected = table.getIsSomePageRowsSelected();\n const toggleHandler = table.getToggleAllPageRowsSelectedHandler();\n return (\n {\n // Create a synthetic event for the handler\n const syntheticEvent = {\n target: { checked: !!value },\n } as unknown as React.ChangeEvent;\n toggleHandler(syntheticEvent);\n }}\n />\n );\n },\n id: \"select\",\n },\n {\n accessorKey: \"project\",\n cell: ({ row }) => (\n
{row.getValue(\"project\")}
\n ),\n header: \"Project\",\n },\n {\n accessorKey: \"status\",\n cell: ({ row }) => {\n const status = row.getValue(\"status\") as Project[\"status\"];\n return (\n \n \n {status}\n \n );\n },\n header: \"Status\",\n },\n {\n accessorKey: \"team\",\n header: \"Team\",\n },\n {\n accessorKey: \"budget\",\n cell: ({ row }) => {\n const amount = Number.parseFloat(row.getValue(\"budget\"));\n const formatted = new Intl.NumberFormat(\"en-US\", {\n currency: \"USD\",\n maximumFractionDigits: 0,\n minimumFractionDigits: 0,\n style: \"currency\",\n }).format(amount);\n return
{formatted}
;\n },\n header: () =>
Budget
,\n },\n];\n\nexport default function Particle() {\n const [tableData] = useState(data);\n const [rowSelection, setRowSelection] = useState({});\n\n const columns = useMemo(() => getColumns(), []);\n\n const table = useReactTable({\n columns,\n data: tableData,\n enableRowSelection: true,\n getCoreRowModel: getCoreRowModel(),\n onRowSelectionChange: setRowSelection,\n state: {\n rowSelection,\n },\n });\n\n const totalBudget = tableData.reduce(\n (sum, project) => sum + project.budget,\n 0,\n );\n const formattedTotal = new Intl.NumberFormat(\"en-US\", {\n currency: \"USD\",\n maximumFractionDigits: 0,\n minimumFractionDigits: 0,\n style: \"currency\",\n }).format(totalBudget);\n\n return (\n \n \n \n {table.getHeaderGroups().map((headerGroup) => (\n \n {headerGroup.headers.map((header) => {\n return (\n \n {header.isPlaceholder\n ? null\n : flexRender(\n header.column.columnDef.header,\n header.getContext(),\n )}\n \n );\n })}\n \n ))}\n \n \n {table.getRowModel().rows?.length ? (\n table.getRowModel().rows.map((row) => (\n \n {row.getVisibleCells().map((cell) => (\n \n {flexRender(cell.column.columnDef.cell, cell.getContext())}\n \n ))}\n \n ))\n ) : (\n \n \n No results.\n \n \n )}\n \n \n \n Total Budget\n {formattedTotal}\n \n \n
\n \n );\n}\n", "type": "registry:block" } ], diff --git a/apps/ui/public/r/p-table-4.json b/apps/ui/public/r/p-table-4.json index 368d9837f..cec1b1457 100644 --- a/apps/ui/public/r/p-table-4.json +++ b/apps/ui/public/r/p-table-4.json @@ -18,7 +18,7 @@ "files": [ { "path": "registry/default/particles/p-table-4.tsx", - "content": "\"use client\";\n\nimport {\n type ColumnDef,\n flexRender,\n getCoreRowModel,\n getPaginationRowModel,\n getSortedRowModel,\n type PaginationState,\n type SortingState,\n useReactTable,\n} from \"@tanstack/react-table\";\nimport { ChevronDownIcon, ChevronUpIcon, PlaneTakeoffIcon } from \"lucide-react\";\nimport { useState } from \"react\";\nimport { cn } from \"@/registry/default/lib/utils\";\nimport { Badge } from \"@/registry/default/ui/badge\";\nimport { Button } from \"@/registry/default/ui/button\";\nimport { Checkbox } from \"@/registry/default/ui/checkbox\";\nimport { Frame, FrameFooter } from \"@/registry/default/ui/frame\";\nimport {\n Pagination,\n PaginationContent,\n PaginationItem,\n PaginationNext,\n PaginationPrevious,\n} from \"@/registry/default/ui/pagination\";\nimport {\n Select,\n SelectItem,\n SelectPopup,\n SelectTrigger,\n SelectValue,\n} from \"@/registry/default/ui/select\";\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from \"@/registry/default/ui/table\";\n\ntype Flight = {\n id: string;\n flightCode: string;\n destination: string;\n departureTime: string;\n arrivalTime: string;\n terminal: string;\n duration: string;\n status: \"On Time\" | \"Delayed\" | \"Cancelled\" | \"Boarding\";\n gate: string;\n};\n\nconst getStatusColor = (status: Flight[\"status\"]) => {\n switch (status) {\n case \"On Time\":\n return \"bg-emerald-500\";\n case \"Delayed\":\n return \"bg-amber-500\";\n case \"Cancelled\":\n return \"bg-red-500\";\n case \"Boarding\":\n return \"bg-blue-500\";\n default:\n return \"bg-muted-foreground/64\";\n }\n};\n\nconst columns: ColumnDef[] = [\n {\n cell: ({ row }) => (\n row.toggleSelected(!!value)}\n />\n ),\n enableSorting: false,\n header: ({ table }) => {\n const isAllSelected = table.getIsAllPageRowsSelected();\n const isSomeSelected = table.getIsSomePageRowsSelected();\n return (\n table.toggleAllPageRowsSelected(!!value)}\n />\n );\n },\n id: \"select\",\n size: 28,\n },\n {\n accessorKey: \"flightCode\",\n cell: ({ row }) => (\n
\n {row.getValue(\"flightCode\")}\n
\n ),\n header: \"Flight\",\n size: 80,\n },\n {\n accessorKey: \"departureTime\",\n cell: ({ row }) => {\n const isCancelled = row.original.status === \"Cancelled\";\n const isDelayed = row.original.status === \"Delayed\";\n return (\n \n
\n {row.original.departureTime}\n
\n \n \n {row.original.duration}\n \n \n
{row.original.arrivalTime}
\n \n );\n },\n header: \"Time\",\n size: 220,\n },\n {\n accessorKey: \"destination\",\n cell: ({ row }) => (\n
{row.getValue(\"destination\")}
\n ),\n header: \"Destination\",\n size: 180,\n },\n {\n accessorKey: \"status\",\n cell: ({ row }) => {\n const status = row.getValue(\"status\") as Flight[\"status\"];\n return (\n \n \n {status}\n \n );\n },\n header: \"Status\",\n size: 120,\n },\n {\n accessorKey: \"terminal\",\n cell: ({ row }) => (\n \n \n {row.getValue(\"terminal\")}\n \n ),\n header: \"Terminal\",\n size: 90,\n },\n {\n accessorKey: \"gate\",\n header: \"Gate\",\n size: 80,\n },\n];\n\nexport default function Particle() {\n const pageSize = 10;\n\n const [pagination, setPagination] = useState({\n pageIndex: 0,\n pageSize: pageSize,\n });\n\n const [sorting, setSorting] = useState([\n {\n desc: false,\n id: \"departureTime\",\n },\n ]);\n\n const table = useReactTable({\n columns,\n data: flights,\n enableSortingRemoval: false,\n getCoreRowModel: getCoreRowModel(),\n getPaginationRowModel: getPaginationRowModel(),\n getSortedRowModel: getSortedRowModel(),\n onPaginationChange: setPagination,\n onSortingChange: setSorting,\n state: {\n pagination,\n sorting,\n },\n });\n\n return (\n \n \n \n {table.getHeaderGroups().map((headerGroup) => (\n \n {headerGroup.headers.map((header) => {\n const columnSize = header.column.getSize();\n return (\n \n {header.isPlaceholder ? null : header.column.getCanSort() ? (\n {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n header.column.getToggleSortingHandler()?.(e);\n }\n }}\n role=\"button\"\n tabIndex={0}\n >\n {flexRender(\n header.column.columnDef.header,\n header.getContext(),\n )}\n {{\n asc: (\n \n ),\n desc: (\n \n ),\n }[header.column.getIsSorted() as string] ?? null}\n \n ) : (\n flexRender(\n header.column.columnDef.header,\n header.getContext(),\n )\n )}\n \n );\n })}\n \n ))}\n \n \n {table.getRowModel().rows.length ? (\n table.getRowModel().rows.map((row) => (\n \n {row.getVisibleCells().map((cell) => (\n \n {flexRender(cell.column.columnDef.cell, cell.getContext())}\n \n ))}\n \n ))\n ) : (\n \n \n No results.\n \n \n )}\n \n
\n \n
\n {/* Results range selector */}\n
\n

Viewing

\n {\n const start = i * table.getState().pagination.pageSize + 1;\n const end = Math.min(\n (i + 1) * table.getState().pagination.pageSize,\n table.getRowCount(),\n );\n const pageNum = i + 1;\n return { label: `${start}-${end}`, value: pageNum };\n })}\n onValueChange={(value) => {\n table.setPageIndex((value as number) - 1);\n }}\n value={table.getState().pagination.pageIndex + 1}\n >\n \n \n \n \n {Array.from({ length: table.getPageCount() }, (_, i) => {\n const start = i * table.getState().pagination.pageSize + 1;\n const end = Math.min(\n (i + 1) * table.getState().pagination.pageSize,\n table.getRowCount(),\n );\n const pageNum = i + 1;\n return (\n \n {`${start}-${end}`}\n \n );\n })}\n \n \n

\n of{\" \"}\n \n {table.getRowCount()}\n {\" \"}\n results\n

\n
\n\n {/* Pagination */}\n \n \n \n table.previousPage()}\n size=\"sm\"\n variant=\"outline\"\n />\n }\n />\n \n \n table.nextPage()}\n size=\"sm\"\n variant=\"outline\"\n />\n }\n />\n \n \n \n
\n
\n \n );\n}\n\nconst flights: Flight[] = [\n {\n arrivalTime: \"11:45\",\n departureTime: \"08:30\",\n destination: \"Los Angeles\",\n duration: \"5h 15m\",\n flightCode: \"AA1234\",\n gate: \"A12\",\n id: \"1\",\n status: \"On Time\",\n terminal: \"1\",\n },\n {\n arrivalTime: \"17:10\",\n departureTime: \"14:20\",\n destination: \"San Francisco\",\n duration: \"4h 50m\",\n flightCode: \"DL5678\",\n gate: \"B24\",\n id: \"2\",\n status: \"Delayed\",\n terminal: \"2\",\n },\n {\n arrivalTime: \"13:30\",\n departureTime: \"10:15\",\n destination: \"Miami\",\n duration: \"3h 15m\",\n flightCode: \"UA9012\",\n gate: \"C8\",\n id: \"3\",\n status: \"On Time\",\n terminal: \"1\",\n },\n {\n arrivalTime: \"18:20\",\n departureTime: \"16:45\",\n destination: \"Seattle\",\n duration: \"2h 35m\",\n flightCode: \"SW3456\",\n gate: \"D15\",\n id: \"4\",\n status: \"On Time\",\n terminal: \"3\",\n },\n {\n arrivalTime: \"12:30\",\n departureTime: \"09:00\",\n destination: \"Salt Lake City\",\n duration: \"5h 30m\",\n flightCode: \"JB7890\",\n gate: \"E3\",\n id: \"5\",\n status: \"Cancelled\",\n terminal: \"2\",\n },\n {\n arrivalTime: \"14:15\",\n departureTime: \"11:30\",\n destination: \"Phoenix\",\n duration: \"2h 45m\",\n flightCode: \"AS2345\",\n gate: \"F7\",\n id: \"6\",\n status: \"On Time\",\n terminal: \"1\",\n },\n {\n arrivalTime: \"20:30\",\n departureTime: \"13:00\",\n destination: \"Las Vegas\",\n duration: \"5h 30m\",\n flightCode: \"HA6789\",\n gate: \"G12\",\n id: \"7\",\n status: \"Delayed\",\n terminal: \"2\",\n },\n {\n arrivalTime: \"09:00\",\n departureTime: \"07:15\",\n destination: \"Dallas\",\n duration: \"1h 45m\",\n flightCode: \"FX0123\",\n gate: \"H5\",\n id: \"8\",\n status: \"Boarding\",\n terminal: \"1\",\n },\n {\n arrivalTime: \"08:30\",\n departureTime: \"06:00\",\n destination: \"Denver\",\n duration: \"2h 30m\",\n flightCode: \"WN4567\",\n gate: \"I9\",\n id: \"9\",\n status: \"Boarding\",\n terminal: \"2\",\n },\n {\n arrivalTime: \"15:20\",\n departureTime: \"12:45\",\n destination: \"Portland\",\n duration: \"2h 35m\",\n flightCode: \"B61234\",\n gate: \"J14\",\n id: \"10\",\n status: \"On Time\",\n terminal: \"3\",\n },\n {\n arrivalTime: \"18:45\",\n departureTime: \"15:30\",\n destination: \"Atlanta\",\n duration: \"3h 15m\",\n flightCode: \"NK8901\",\n gate: \"K6\",\n id: \"11\",\n status: \"On Time\",\n terminal: \"1\",\n },\n {\n arrivalTime: \"12:00\",\n departureTime: \"09:45\",\n destination: \"Chicago\",\n duration: \"2h 15m\",\n flightCode: \"F92345\",\n gate: \"L11\",\n id: \"12\",\n status: \"Delayed\",\n terminal: \"2\",\n },\n {\n arrivalTime: \"14:15\",\n departureTime: \"11:00\",\n destination: \"Boston\",\n duration: \"3h 15m\",\n flightCode: \"SY6789\",\n gate: \"M3\",\n id: \"13\",\n status: \"On Time\",\n terminal: \"1\",\n },\n {\n arrivalTime: \"16:45\",\n departureTime: \"13:30\",\n destination: \"New York\",\n duration: \"3h 15m\",\n flightCode: \"G40123\",\n gate: \"N8\",\n id: \"14\",\n status: \"On Time\",\n terminal: \"3\",\n },\n {\n arrivalTime: \"11:20\",\n departureTime: \"08:00\",\n destination: \"Washington\",\n duration: \"3h 20m\",\n flightCode: \"YX5678\",\n gate: \"O12\",\n id: \"15\",\n status: \"Delayed\",\n terminal: \"2\",\n },\n {\n arrivalTime: \"13:50\",\n departureTime: \"10:30\",\n destination: \"Orlando\",\n duration: \"3h 20m\",\n flightCode: \"4U9012\",\n gate: \"P5\",\n id: \"16\",\n status: \"Delayed\",\n terminal: \"1\",\n },\n {\n arrivalTime: \"16:30\",\n departureTime: \"14:00\",\n destination: \"Houston\",\n duration: \"2h 30m\",\n flightCode: \"QF3456\",\n gate: \"Q9\",\n id: \"17\",\n status: \"On Time\",\n terminal: \"3\",\n },\n {\n arrivalTime: \"10:00\",\n departureTime: \"07:30\",\n destination: \"Minneapolis\",\n duration: \"2h 30m\",\n flightCode: \"LH7890\",\n gate: \"R7\",\n id: \"18\",\n status: \"Cancelled\",\n terminal: \"2\",\n },\n {\n arrivalTime: \"19:30\",\n departureTime: \"16:15\",\n destination: \"Detroit\",\n duration: \"3h 15m\",\n flightCode: \"KL2345\",\n gate: \"S4\",\n id: \"19\",\n status: \"Cancelled\",\n terminal: \"1\",\n },\n {\n arrivalTime: \"15:10\",\n departureTime: \"12:00\",\n destination: \"Philadelphia\",\n duration: \"3h 10m\",\n flightCode: \"AF6789\",\n gate: \"T16\",\n id: \"20\",\n status: \"On Time\",\n terminal: \"3\",\n },\n {\n arrivalTime: \"12:25\",\n departureTime: \"09:15\",\n destination: \"Charlotte\",\n duration: \"3h 10m\",\n flightCode: \"BA0123\",\n gate: \"U10\",\n id: \"21\",\n status: \"On Time\",\n terminal: \"2\",\n },\n {\n arrivalTime: \"18:00\",\n departureTime: \"15:45\",\n destination: \"Nashville\",\n duration: \"2h 15m\",\n flightCode: \"IB4567\",\n gate: \"V8\",\n id: \"22\",\n status: \"Delayed\",\n terminal: \"1\",\n },\n {\n arrivalTime: \"14:00\",\n departureTime: \"11:45\",\n destination: \"Austin\",\n duration: \"2h 15m\",\n flightCode: \"EK8901\",\n gate: \"W13\",\n id: \"23\",\n status: \"Cancelled\",\n terminal: \"3\",\n },\n {\n arrivalTime: \"16:40\",\n departureTime: \"13:15\",\n destination: \"Tampa\",\n duration: \"3h 25m\",\n flightCode: \"QR2345\",\n gate: \"X6\",\n id: \"24\",\n status: \"On Time\",\n terminal: \"2\",\n },\n {\n arrivalTime: \"11:30\",\n departureTime: \"08:45\",\n destination: \"Raleigh\",\n duration: \"2h 45m\",\n flightCode: \"TK6789\",\n gate: \"Y11\",\n id: \"25\",\n status: \"On Time\",\n terminal: \"1\",\n },\n {\n arrivalTime: \"12:45\",\n departureTime: \"10:00\",\n destination: \"Indianapolis\",\n duration: \"2h 45m\",\n flightCode: \"VS3456\",\n gate: \"Z4\",\n id: \"26\",\n status: \"On Time\",\n terminal: \"2\",\n },\n {\n arrivalTime: \"20:00\",\n departureTime: \"17:30\",\n destination: \"Kansas City\",\n duration: \"2h 30m\",\n flightCode: \"LX7890\",\n gate: \"A8\",\n id: \"27\",\n status: \"Delayed\",\n terminal: \"3\",\n },\n {\n arrivalTime: \"15:20\",\n departureTime: \"12:30\",\n destination: \"Columbus\",\n duration: \"2h 50m\",\n flightCode: \"OS1234\",\n gate: \"B19\",\n id: \"28\",\n status: \"On Time\",\n terminal: \"1\",\n },\n {\n arrivalTime: \"20:15\",\n departureTime: \"18:00\",\n destination: \"Milwaukee\",\n duration: \"2h 15m\",\n flightCode: \"SN5678\",\n gate: \"C22\",\n id: \"29\",\n status: \"On Time\",\n terminal: \"2\",\n },\n {\n arrivalTime: \"21:30\",\n departureTime: \"19:15\",\n destination: \"Memphis\",\n duration: \"2h 15m\",\n flightCode: \"TP9012\",\n gate: \"D6\",\n id: \"30\",\n status: \"On Time\",\n terminal: \"3\",\n },\n];\n", + "content": "\"use client\";\n\nimport {\n type ColumnDef,\n flexRender,\n getCoreRowModel,\n getPaginationRowModel,\n getSortedRowModel,\n type PaginationState,\n type SortingState,\n useReactTable,\n} from \"@tanstack/react-table\";\nimport { ChevronDownIcon, ChevronUpIcon, PlaneTakeoffIcon } from \"lucide-react\";\nimport { useState } from \"react\";\nimport { cn } from \"@/registry/default/lib/utils\";\nimport { Badge } from \"@/registry/default/ui/badge\";\nimport { Button } from \"@/registry/default/ui/button\";\nimport { Checkbox } from \"@/registry/default/ui/checkbox\";\nimport { Frame, FrameFooter } from \"@/registry/default/ui/frame\";\nimport {\n Pagination,\n PaginationContent,\n PaginationItem,\n PaginationNext,\n PaginationPrevious,\n} from \"@/registry/default/ui/pagination\";\nimport {\n Select,\n SelectItem,\n SelectPopup,\n SelectTrigger,\n SelectValue,\n} from \"@/registry/default/ui/select\";\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from \"@/registry/default/ui/table\";\n\ntype Flight = {\n id: string;\n flightCode: string;\n destination: string;\n departureTime: string;\n arrivalTime: string;\n terminal: string;\n duration: string;\n status: \"On Time\" | \"Delayed\" | \"Cancelled\" | \"Boarding\";\n gate: string;\n};\n\nconst getStatusColor = (status: Flight[\"status\"]) => {\n switch (status) {\n case \"On Time\":\n return \"bg-emerald-500\";\n case \"Delayed\":\n return \"bg-amber-500\";\n case \"Cancelled\":\n return \"bg-red-500\";\n case \"Boarding\":\n return \"bg-blue-500\";\n default:\n return \"bg-muted-foreground/64\";\n }\n};\n\nconst columns: ColumnDef[] = [\n {\n cell: ({ row }) => (\n row.toggleSelected(!!value)}\n />\n ),\n enableSorting: false,\n header: ({ table }) => {\n const isAllSelected = table.getIsAllPageRowsSelected();\n const isSomeSelected = table.getIsSomePageRowsSelected();\n return (\n table.toggleAllPageRowsSelected(!!value)}\n />\n );\n },\n id: \"select\",\n size: 28,\n },\n {\n accessorKey: \"flightCode\",\n cell: ({ row }) => (\n
\n {row.getValue(\"flightCode\")}\n
\n ),\n header: \"Flight\",\n size: 80,\n },\n {\n accessorKey: \"departureTime\",\n cell: ({ row }) => {\n const isCancelled = row.original.status === \"Cancelled\";\n const isDelayed = row.original.status === \"Delayed\";\n return (\n \n
\n {row.original.departureTime}\n
\n \n \n {row.original.duration}\n \n \n
{row.original.arrivalTime}
\n \n );\n },\n header: \"Time\",\n size: 220,\n },\n {\n accessorKey: \"destination\",\n cell: ({ row }) => (\n
{row.getValue(\"destination\")}
\n ),\n header: \"Destination\",\n size: 180,\n },\n {\n accessorKey: \"status\",\n cell: ({ row }) => {\n const status = row.getValue(\"status\") as Flight[\"status\"];\n return (\n \n \n {status}\n \n );\n },\n header: \"Status\",\n size: 120,\n },\n {\n accessorKey: \"terminal\",\n cell: ({ row }) => (\n \n \n {row.getValue(\"terminal\")}\n \n ),\n header: \"Terminal\",\n size: 90,\n },\n {\n accessorKey: \"gate\",\n header: \"Gate\",\n size: 80,\n },\n];\n\nexport default function Particle() {\n const pageSize = 10;\n\n const [pagination, setPagination] = useState({\n pageIndex: 0,\n pageSize: pageSize,\n });\n\n const [sorting, setSorting] = useState([\n {\n desc: false,\n id: \"departureTime\",\n },\n ]);\n\n const table = useReactTable({\n columns,\n data: flights,\n enableSortingRemoval: false,\n getCoreRowModel: getCoreRowModel(),\n getPaginationRowModel: getPaginationRowModel(),\n getSortedRowModel: getSortedRowModel(),\n onPaginationChange: setPagination,\n onSortingChange: setSorting,\n state: {\n pagination,\n sorting,\n },\n });\n\n return (\n \n \n \n {table.getHeaderGroups().map((headerGroup) => (\n \n {headerGroup.headers.map((header) => {\n const columnSize = header.column.getSize();\n return (\n \n {header.isPlaceholder ? null : header.column.getCanSort() ? (\n {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n header.column.getToggleSortingHandler()?.(e);\n }\n }}\n role=\"button\"\n tabIndex={0}\n >\n {flexRender(\n header.column.columnDef.header,\n header.getContext(),\n )}\n {{\n asc: (\n \n ),\n desc: (\n \n ),\n }[header.column.getIsSorted() as string] ?? null}\n \n ) : (\n flexRender(\n header.column.columnDef.header,\n header.getContext(),\n )\n )}\n \n );\n })}\n \n ))}\n \n \n {table.getRowModel().rows.length ? (\n table.getRowModel().rows.map((row) => (\n \n {row.getVisibleCells().map((cell) => (\n \n {flexRender(cell.column.columnDef.cell, cell.getContext())}\n \n ))}\n \n ))\n ) : (\n \n \n No results.\n \n \n )}\n \n
\n \n
\n {/* Results range selector */}\n
\n

Viewing

\n {\n const start = i * table.getState().pagination.pageSize + 1;\n const end = Math.min(\n (i + 1) * table.getState().pagination.pageSize,\n table.getRowCount(),\n );\n const pageNum = i + 1;\n return { label: `${start}-${end}`, value: pageNum };\n })}\n onValueChange={(value) => {\n table.setPageIndex((value as number) - 1);\n }}\n value={table.getState().pagination.pageIndex + 1}\n >\n \n \n \n \n {Array.from({ length: table.getPageCount() }, (_, i) => {\n const start = i * table.getState().pagination.pageSize + 1;\n const end = Math.min(\n (i + 1) * table.getState().pagination.pageSize,\n table.getRowCount(),\n );\n const pageNum = i + 1;\n return (\n \n {`${start}-${end}`}\n \n );\n })}\n \n \n

\n of{\" \"}\n \n {table.getRowCount()}\n {\" \"}\n results\n

\n
\n\n {/* Pagination */}\n \n \n \n table.previousPage()}\n size=\"sm\"\n variant=\"outline\"\n />\n }\n />\n \n \n table.nextPage()}\n size=\"sm\"\n variant=\"outline\"\n />\n }\n />\n \n \n \n
\n
\n \n );\n}\n\nconst flights: Flight[] = [\n {\n arrivalTime: \"11:45\",\n departureTime: \"08:30\",\n destination: \"Los Angeles\",\n duration: \"5h 15m\",\n flightCode: \"AA1234\",\n gate: \"A12\",\n id: \"1\",\n status: \"On Time\",\n terminal: \"1\",\n },\n {\n arrivalTime: \"17:10\",\n departureTime: \"14:20\",\n destination: \"San Francisco\",\n duration: \"4h 50m\",\n flightCode: \"DL5678\",\n gate: \"B24\",\n id: \"2\",\n status: \"Delayed\",\n terminal: \"2\",\n },\n {\n arrivalTime: \"13:30\",\n departureTime: \"10:15\",\n destination: \"Miami\",\n duration: \"3h 15m\",\n flightCode: \"UA9012\",\n gate: \"C8\",\n id: \"3\",\n status: \"On Time\",\n terminal: \"1\",\n },\n {\n arrivalTime: \"18:20\",\n departureTime: \"16:45\",\n destination: \"Seattle\",\n duration: \"2h 35m\",\n flightCode: \"SW3456\",\n gate: \"D15\",\n id: \"4\",\n status: \"On Time\",\n terminal: \"3\",\n },\n {\n arrivalTime: \"12:30\",\n departureTime: \"09:00\",\n destination: \"Salt Lake City\",\n duration: \"5h 30m\",\n flightCode: \"JB7890\",\n gate: \"E3\",\n id: \"5\",\n status: \"Cancelled\",\n terminal: \"2\",\n },\n {\n arrivalTime: \"14:15\",\n departureTime: \"11:30\",\n destination: \"Phoenix\",\n duration: \"2h 45m\",\n flightCode: \"AS2345\",\n gate: \"F7\",\n id: \"6\",\n status: \"On Time\",\n terminal: \"1\",\n },\n {\n arrivalTime: \"20:30\",\n departureTime: \"13:00\",\n destination: \"Las Vegas\",\n duration: \"5h 30m\",\n flightCode: \"HA6789\",\n gate: \"G12\",\n id: \"7\",\n status: \"Delayed\",\n terminal: \"2\",\n },\n {\n arrivalTime: \"09:00\",\n departureTime: \"07:15\",\n destination: \"Dallas\",\n duration: \"1h 45m\",\n flightCode: \"FX0123\",\n gate: \"H5\",\n id: \"8\",\n status: \"Boarding\",\n terminal: \"1\",\n },\n {\n arrivalTime: \"08:30\",\n departureTime: \"06:00\",\n destination: \"Denver\",\n duration: \"2h 30m\",\n flightCode: \"WN4567\",\n gate: \"I9\",\n id: \"9\",\n status: \"Boarding\",\n terminal: \"2\",\n },\n {\n arrivalTime: \"15:20\",\n departureTime: \"12:45\",\n destination: \"Portland\",\n duration: \"2h 35m\",\n flightCode: \"B61234\",\n gate: \"J14\",\n id: \"10\",\n status: \"On Time\",\n terminal: \"3\",\n },\n {\n arrivalTime: \"18:45\",\n departureTime: \"15:30\",\n destination: \"Atlanta\",\n duration: \"3h 15m\",\n flightCode: \"NK8901\",\n gate: \"K6\",\n id: \"11\",\n status: \"On Time\",\n terminal: \"1\",\n },\n {\n arrivalTime: \"12:00\",\n departureTime: \"09:45\",\n destination: \"Chicago\",\n duration: \"2h 15m\",\n flightCode: \"F92345\",\n gate: \"L11\",\n id: \"12\",\n status: \"Delayed\",\n terminal: \"2\",\n },\n {\n arrivalTime: \"14:15\",\n departureTime: \"11:00\",\n destination: \"Boston\",\n duration: \"3h 15m\",\n flightCode: \"SY6789\",\n gate: \"M3\",\n id: \"13\",\n status: \"On Time\",\n terminal: \"1\",\n },\n {\n arrivalTime: \"16:45\",\n departureTime: \"13:30\",\n destination: \"New York\",\n duration: \"3h 15m\",\n flightCode: \"G40123\",\n gate: \"N8\",\n id: \"14\",\n status: \"On Time\",\n terminal: \"3\",\n },\n {\n arrivalTime: \"11:20\",\n departureTime: \"08:00\",\n destination: \"Washington\",\n duration: \"3h 20m\",\n flightCode: \"YX5678\",\n gate: \"O12\",\n id: \"15\",\n status: \"Delayed\",\n terminal: \"2\",\n },\n {\n arrivalTime: \"13:50\",\n departureTime: \"10:30\",\n destination: \"Orlando\",\n duration: \"3h 20m\",\n flightCode: \"4U9012\",\n gate: \"P5\",\n id: \"16\",\n status: \"Delayed\",\n terminal: \"1\",\n },\n {\n arrivalTime: \"16:30\",\n departureTime: \"14:00\",\n destination: \"Houston\",\n duration: \"2h 30m\",\n flightCode: \"QF3456\",\n gate: \"Q9\",\n id: \"17\",\n status: \"On Time\",\n terminal: \"3\",\n },\n {\n arrivalTime: \"10:00\",\n departureTime: \"07:30\",\n destination: \"Minneapolis\",\n duration: \"2h 30m\",\n flightCode: \"LH7890\",\n gate: \"R7\",\n id: \"18\",\n status: \"Cancelled\",\n terminal: \"2\",\n },\n {\n arrivalTime: \"19:30\",\n departureTime: \"16:15\",\n destination: \"Detroit\",\n duration: \"3h 15m\",\n flightCode: \"KL2345\",\n gate: \"S4\",\n id: \"19\",\n status: \"Cancelled\",\n terminal: \"1\",\n },\n {\n arrivalTime: \"15:10\",\n departureTime: \"12:00\",\n destination: \"Philadelphia\",\n duration: \"3h 10m\",\n flightCode: \"AF6789\",\n gate: \"T16\",\n id: \"20\",\n status: \"On Time\",\n terminal: \"3\",\n },\n {\n arrivalTime: \"12:25\",\n departureTime: \"09:15\",\n destination: \"Charlotte\",\n duration: \"3h 10m\",\n flightCode: \"BA0123\",\n gate: \"U10\",\n id: \"21\",\n status: \"On Time\",\n terminal: \"2\",\n },\n {\n arrivalTime: \"18:00\",\n departureTime: \"15:45\",\n destination: \"Nashville\",\n duration: \"2h 15m\",\n flightCode: \"IB4567\",\n gate: \"V8\",\n id: \"22\",\n status: \"Delayed\",\n terminal: \"1\",\n },\n {\n arrivalTime: \"14:00\",\n departureTime: \"11:45\",\n destination: \"Austin\",\n duration: \"2h 15m\",\n flightCode: \"EK8901\",\n gate: \"W13\",\n id: \"23\",\n status: \"Cancelled\",\n terminal: \"3\",\n },\n {\n arrivalTime: \"16:40\",\n departureTime: \"13:15\",\n destination: \"Tampa\",\n duration: \"3h 25m\",\n flightCode: \"QR2345\",\n gate: \"X6\",\n id: \"24\",\n status: \"On Time\",\n terminal: \"2\",\n },\n {\n arrivalTime: \"11:30\",\n departureTime: \"08:45\",\n destination: \"Raleigh\",\n duration: \"2h 45m\",\n flightCode: \"TK6789\",\n gate: \"Y11\",\n id: \"25\",\n status: \"On Time\",\n terminal: \"1\",\n },\n {\n arrivalTime: \"12:45\",\n departureTime: \"10:00\",\n destination: \"Indianapolis\",\n duration: \"2h 45m\",\n flightCode: \"VS3456\",\n gate: \"Z4\",\n id: \"26\",\n status: \"On Time\",\n terminal: \"2\",\n },\n {\n arrivalTime: \"20:00\",\n departureTime: \"17:30\",\n destination: \"Kansas City\",\n duration: \"2h 30m\",\n flightCode: \"LX7890\",\n gate: \"A8\",\n id: \"27\",\n status: \"Delayed\",\n terminal: \"3\",\n },\n {\n arrivalTime: \"15:20\",\n departureTime: \"12:30\",\n destination: \"Columbus\",\n duration: \"2h 50m\",\n flightCode: \"OS1234\",\n gate: \"B19\",\n id: \"28\",\n status: \"On Time\",\n terminal: \"1\",\n },\n {\n arrivalTime: \"20:15\",\n departureTime: \"18:00\",\n destination: \"Milwaukee\",\n duration: \"2h 15m\",\n flightCode: \"SN5678\",\n gate: \"C22\",\n id: \"29\",\n status: \"On Time\",\n terminal: \"2\",\n },\n {\n arrivalTime: \"21:30\",\n departureTime: \"19:15\",\n destination: \"Memphis\",\n duration: \"2h 15m\",\n flightCode: \"TP9012\",\n gate: \"D6\",\n id: \"30\",\n status: \"On Time\",\n terminal: \"3\",\n },\n];\n", "type": "registry:block" } ], diff --git a/apps/ui/public/r/p-table-5.json b/apps/ui/public/r/p-table-5.json new file mode 100644 index 000000000..406505389 --- /dev/null +++ b/apps/ui/public/r/p-table-5.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "p-table-5", + "description": "Boxed table variant", + "registryDependencies": [ + "@coss/badge", + "@coss/table" + ], + "files": [ + { + "path": "registry/default/particles/p-table-5.tsx", + "content": "import { Badge } from \"@/registry/default/ui/badge\";\nimport {\n Table,\n TableBody,\n TableCell,\n TableFooter,\n TableHead,\n TableHeader,\n TableRow,\n} from \"@/registry/default/ui/table\";\n\nexport default function Particle() {\n return (\n \n \n \n Project\n Status\n Team\n Budget\n \n \n \n \n Website Redesign\n \n \n \n Paid\n \n \n Frontend Team\n $12,500\n \n \n Mobile App\n \n \n \n Unpaid\n \n \n Mobile Team\n $8,750\n \n \n API Integration\n \n \n \n Pending\n \n \n Backend Team\n $5,200\n \n \n Database Migration\n \n \n \n Paid\n \n \n DevOps Team\n $3,800\n \n \n User Dashboard\n \n \n \n Paid\n \n \n UX Team\n $7,200\n \n \n Security Audit\n \n \n \n Failed\n \n \n Security Team\n $2,100\n \n \n \n \n Total Budget\n $39,550\n \n \n
\n );\n}\n", + "type": "registry:block" + } + ], + "meta": { + "className": "**:data-[slot=preview]:w-full sm:**:data-[slot=preview]:max-w-4xl", + "colSpan": 2 + }, + "categories": [ + "table" + ], + "type": "registry:block" +} \ No newline at end of file diff --git a/apps/ui/public/r/p-table-6.json b/apps/ui/public/r/p-table-6.json new file mode 100644 index 000000000..9fbf08fa9 --- /dev/null +++ b/apps/ui/public/r/p-table-6.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "p-table-6", + "description": "CardFrame with TanStack Table and checkboxes", + "dependencies": [ + "@tanstack/react-table" + ], + "registryDependencies": [ + "@coss/badge", + "@coss/card", + "@coss/checkbox", + "@coss/table" + ], + "files": [ + { + "path": "registry/default/particles/p-table-6.tsx", + "content": "\"use client\";\n\nimport {\n type ColumnDef,\n flexRender,\n getCoreRowModel,\n useReactTable,\n} from \"@tanstack/react-table\";\nimport type React from \"react\";\nimport { useMemo, useState } from \"react\";\nimport { Badge } from \"@/registry/default/ui/badge\";\nimport { CardFrame } from \"@/registry/default/ui/card\";\nimport { Checkbox } from \"@/registry/default/ui/checkbox\";\nimport {\n Table,\n TableBody,\n TableCell,\n TableFooter,\n TableHead,\n TableHeader,\n TableRow,\n} from \"@/registry/default/ui/table\";\n\ntype Project = {\n id: string;\n project: string;\n status: \"Paid\" | \"Unpaid\" | \"Pending\" | \"Failed\";\n team: string;\n budget: number;\n};\n\nconst data: Project[] = [\n {\n budget: 12500,\n id: \"1\",\n project: \"Website Redesign\",\n status: \"Paid\",\n team: \"Frontend Team\",\n },\n {\n budget: 8750,\n id: \"2\",\n project: \"Mobile App\",\n status: \"Unpaid\",\n team: \"Mobile Team\",\n },\n {\n budget: 5200,\n id: \"3\",\n project: \"API Integration\",\n status: \"Pending\",\n team: \"Backend Team\",\n },\n {\n budget: 3800,\n id: \"4\",\n project: \"Database Migration\",\n status: \"Paid\",\n team: \"DevOps Team\",\n },\n {\n budget: 7200,\n id: \"5\",\n project: \"User Dashboard\",\n status: \"Paid\",\n team: \"UX Team\",\n },\n {\n budget: 2100,\n id: \"6\",\n project: \"Security Audit\",\n status: \"Failed\",\n team: \"Security Team\",\n },\n];\n\nconst getStatusColor = (status: Project[\"status\"]) => {\n switch (status) {\n case \"Paid\":\n return \"bg-emerald-500\";\n case \"Unpaid\":\n return \"bg-muted-foreground/64\";\n case \"Pending\":\n return \"bg-amber-500\";\n case \"Failed\":\n return \"bg-red-500\";\n default:\n return \"bg-muted-foreground/64\";\n }\n};\n\nconst getColumns = (): ColumnDef[] => [\n {\n cell: ({ row }) => {\n const toggleHandler = row.getToggleSelectedHandler();\n return (\n {\n // Create a synthetic event for the handler\n const syntheticEvent = {\n target: { checked: !!value },\n } as unknown as React.ChangeEvent;\n toggleHandler(syntheticEvent);\n }}\n />\n );\n },\n enableSorting: false,\n header: ({ table }) => {\n const isAllSelected = table.getIsAllPageRowsSelected();\n const isSomeSelected = table.getIsSomePageRowsSelected();\n const toggleHandler = table.getToggleAllPageRowsSelectedHandler();\n return (\n {\n // Create a synthetic event for the handler\n const syntheticEvent = {\n target: { checked: !!value },\n } as unknown as React.ChangeEvent;\n toggleHandler(syntheticEvent);\n }}\n />\n );\n },\n id: \"select\",\n },\n {\n accessorKey: \"project\",\n cell: ({ row }) => (\n
{row.getValue(\"project\")}
\n ),\n header: \"Project\",\n },\n {\n accessorKey: \"status\",\n cell: ({ row }) => {\n const status = row.getValue(\"status\") as Project[\"status\"];\n return (\n \n \n {status}\n \n );\n },\n header: \"Status\",\n },\n {\n accessorKey: \"team\",\n header: \"Team\",\n },\n {\n accessorKey: \"budget\",\n cell: ({ row }) => {\n const amount = Number.parseFloat(row.getValue(\"budget\"));\n const formatted = new Intl.NumberFormat(\"en-US\", {\n currency: \"USD\",\n maximumFractionDigits: 0,\n minimumFractionDigits: 0,\n style: \"currency\",\n }).format(amount);\n return
{formatted}
;\n },\n header: () =>
Budget
,\n },\n];\n\nexport default function Particle() {\n const [tableData] = useState(data);\n const [rowSelection, setRowSelection] = useState({});\n\n const columns = useMemo(() => getColumns(), []);\n\n const table = useReactTable({\n columns,\n data: tableData,\n enableRowSelection: true,\n getCoreRowModel: getCoreRowModel(),\n onRowSelectionChange: setRowSelection,\n state: {\n rowSelection,\n },\n });\n\n const totalBudget = tableData.reduce(\n (sum, project) => sum + project.budget,\n 0,\n );\n const formattedTotal = new Intl.NumberFormat(\"en-US\", {\n currency: \"USD\",\n maximumFractionDigits: 0,\n minimumFractionDigits: 0,\n style: \"currency\",\n }).format(totalBudget);\n\n return (\n \n \n \n {table.getHeaderGroups().map((headerGroup) => (\n \n {headerGroup.headers.map((header) => {\n return (\n \n {header.isPlaceholder\n ? null\n : flexRender(\n header.column.columnDef.header,\n header.getContext(),\n )}\n \n );\n })}\n \n ))}\n \n \n {table.getRowModel().rows?.length ? (\n table.getRowModel().rows.map((row) => (\n \n {row.getVisibleCells().map((cell) => (\n \n {flexRender(cell.column.columnDef.cell, cell.getContext())}\n \n ))}\n \n ))\n ) : (\n \n \n No results.\n \n \n )}\n \n \n \n Total Budget\n {formattedTotal}\n \n \n
\n
\n );\n}\n", + "type": "registry:block" + } + ], + "meta": { + "className": "**:data-[slot=preview]:w-full sm:**:data-[slot=preview]:max-w-4xl", + "colSpan": 2 + }, + "categories": [ + "card", + "checkbox", + "frame", + "table", + "tanstack" + ], + "type": "registry:block" +} \ No newline at end of file diff --git a/apps/ui/public/r/registry.json b/apps/ui/public/r/registry.json index e2582516b..9e6d55275 100644 --- a/apps/ui/public/r/registry.json +++ b/apps/ui/public/r/registry.json @@ -9585,7 +9585,7 @@ "frame", "table" ], - "description": "Framed table", + "description": "Frame with boxed table", "files": [ { "path": "registry/default/particles/p-table-2.tsx", @@ -9668,6 +9668,59 @@ ], "type": "registry:block" }, + { + "categories": [ + "table" + ], + "description": "Boxed table variant", + "files": [ + { + "path": "registry/default/particles/p-table-5.tsx", + "type": "registry:block" + } + ], + "meta": { + "className": "**:data-[slot=preview]:w-full sm:**:data-[slot=preview]:max-w-4xl", + "colSpan": 2 + }, + "name": "p-table-5", + "registryDependencies": [ + "@coss/badge", + "@coss/table" + ], + "type": "registry:block" + }, + { + "categories": [ + "card", + "checkbox", + "frame", + "table", + "tanstack" + ], + "dependencies": [ + "@tanstack/react-table" + ], + "description": "CardFrame with TanStack Table and checkboxes", + "files": [ + { + "path": "registry/default/particles/p-table-6.tsx", + "type": "registry:block" + } + ], + "meta": { + "className": "**:data-[slot=preview]:w-full sm:**:data-[slot=preview]:max-w-4xl", + "colSpan": 2 + }, + "name": "p-table-6", + "registryDependencies": [ + "@coss/badge", + "@coss/card", + "@coss/checkbox", + "@coss/table" + ], + "type": "registry:block" + }, { "categories": [ "tabs" diff --git a/apps/ui/public/r/table.json b/apps/ui/public/r/table.json index f49279b1a..98727c027 100644 --- a/apps/ui/public/r/table.json +++ b/apps/ui/public/r/table.json @@ -4,7 +4,7 @@ "files": [ { "path": "registry/default/ui/table.tsx", - "content": "import type * as React from \"react\";\nimport { cn } from \"@/registry/default/lib/utils\";\n\nexport function Table({\n className,\n ...props\n}: React.ComponentProps<\"table\">): React.ReactElement {\n return (\n \n \n \n );\n}\n\nexport function TableHeader({\n className,\n ...props\n}: React.ComponentProps<\"thead\">): React.ReactElement {\n return (\n \n );\n}\n\nexport function TableBody({\n className,\n ...props\n}: React.ComponentProps<\"tbody\">): React.ReactElement {\n return (\n \n );\n}\n\nexport function TableFooter({\n className,\n ...props\n}: React.ComponentProps<\"tfoot\">): React.ReactElement {\n return (\n tr]:last:border-b-0 in-data-[slot=frame]:*:[tr]:hover:bg-transparent\",\n className,\n )}\n data-slot=\"table-footer\"\n {...props}\n />\n );\n}\n\nexport function TableRow({\n className,\n ...props\n}: React.ComponentProps<\"tr\">): React.ReactElement {\n return (\n \n );\n}\n\nexport function TableHead({\n className,\n ...props\n}: React.ComponentProps<\"th\">): React.ReactElement {\n return (\n \n );\n}\n\nexport function TableCell({\n className,\n ...props\n}: React.ComponentProps<\"td\">): React.ReactElement {\n return (\n \n );\n}\n\nexport function TableCaption({\n className,\n ...props\n}: React.ComponentProps<\"caption\">): React.ReactElement {\n return (\n \n );\n}\n", + "content": "import type * as React from \"react\";\nimport { cn } from \"@/registry/default/lib/utils\";\n\nexport type TableVariant = \"default\" | \"boxed\";\n\nexport function Table({\n className,\n variant = \"default\",\n ...props\n}: React.ComponentProps<\"table\"> & {\n variant?: TableVariant;\n}): React.ReactElement {\n return (\n \n \n \n );\n}\n\nexport function TableHeader({\n className,\n ...props\n}: React.ComponentProps<\"thead\">): React.ReactElement {\n return (\n \n );\n}\n\nexport function TableBody({\n className,\n ...props\n}: React.ComponentProps<\"tbody\">): React.ReactElement {\n return (\n \n );\n}\n\nexport function TableFooter({\n className,\n ...props\n}: React.ComponentProps<\"tfoot\">): React.ReactElement {\n return (\n tr]:last:border-b-0 in-data-[variant=boxed]:**:[td]:min-h-10\",\n className,\n )}\n data-slot=\"table-footer\"\n {...props}\n />\n );\n}\n\nexport function TableRow({\n className,\n ...props\n}: React.ComponentProps<\"tr\">): React.ReactElement {\n return (\n \n );\n}\n\nexport function TableHead({\n className,\n ...props\n}: React.ComponentProps<\"th\">): React.ReactElement {\n return (\n \n );\n}\n\nexport function TableCell({\n className,\n ...props\n}: React.ComponentProps<\"td\">): React.ReactElement {\n return (\n \n );\n}\n\nexport function TableCaption({\n className,\n ...props\n}: React.ComponentProps<\"caption\">): React.ReactElement {\n return (\n \n );\n}\n", "type": "registry:ui" } ], diff --git a/apps/ui/registry.json b/apps/ui/registry.json index e2582516b..9e6d55275 100644 --- a/apps/ui/registry.json +++ b/apps/ui/registry.json @@ -9585,7 +9585,7 @@ "frame", "table" ], - "description": "Framed table", + "description": "Frame with boxed table", "files": [ { "path": "registry/default/particles/p-table-2.tsx", @@ -9668,6 +9668,59 @@ ], "type": "registry:block" }, + { + "categories": [ + "table" + ], + "description": "Boxed table variant", + "files": [ + { + "path": "registry/default/particles/p-table-5.tsx", + "type": "registry:block" + } + ], + "meta": { + "className": "**:data-[slot=preview]:w-full sm:**:data-[slot=preview]:max-w-4xl", + "colSpan": 2 + }, + "name": "p-table-5", + "registryDependencies": [ + "@coss/badge", + "@coss/table" + ], + "type": "registry:block" + }, + { + "categories": [ + "card", + "checkbox", + "frame", + "table", + "tanstack" + ], + "dependencies": [ + "@tanstack/react-table" + ], + "description": "CardFrame with TanStack Table and checkboxes", + "files": [ + { + "path": "registry/default/particles/p-table-6.tsx", + "type": "registry:block" + } + ], + "meta": { + "className": "**:data-[slot=preview]:w-full sm:**:data-[slot=preview]:max-w-4xl", + "colSpan": 2 + }, + "name": "p-table-6", + "registryDependencies": [ + "@coss/badge", + "@coss/card", + "@coss/checkbox", + "@coss/table" + ], + "type": "registry:block" + }, { "categories": [ "tabs" diff --git a/apps/ui/registry/__index__.tsx b/apps/ui/registry/__index__.tsx index 9f7fd936e..b94726d3c 100644 --- a/apps/ui/registry/__index__.tsx +++ b/apps/ui/registry/__index__.tsx @@ -8379,7 +8379,7 @@ export const Index: Record = { }, "p-table-2": { name: "p-table-2", - description: "Framed table", + description: "Frame with boxed table", type: "registry:block", registryDependencies: ["@coss/badge","@coss/frame","@coss/table"], files: [{ @@ -8431,6 +8431,42 @@ export const Index: Record = { categories: ["checkbox","pagination","select","table","tanstack"], meta: {"className":"**:data-[slot=preview]:w-full sm:**:data-[slot=preview]:max-w-4xl","colSpan":2}, }, + "p-table-5": { + name: "p-table-5", + description: "Boxed table variant", + type: "registry:block", + registryDependencies: ["@coss/badge","@coss/table"], + files: [{ + path: "registry/default/particles/p-table-5.tsx", + type: "registry:block", + target: "" + }], + component: React.lazy(async () => { + const mod = await import("@/registry/default/particles/p-table-5.tsx") + const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + return { default: mod.default || mod[exportName] } + }), + categories: ["table"], + meta: {"className":"**:data-[slot=preview]:w-full sm:**:data-[slot=preview]:max-w-4xl","colSpan":2}, + }, + "p-table-6": { + name: "p-table-6", + description: "CardFrame with TanStack Table and checkboxes", + type: "registry:block", + registryDependencies: ["@coss/badge","@coss/card","@coss/checkbox","@coss/table"], + files: [{ + path: "registry/default/particles/p-table-6.tsx", + type: "registry:block", + target: "" + }], + component: React.lazy(async () => { + const mod = await import("@/registry/default/particles/p-table-6.tsx") + const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + return { default: mod.default || mod[exportName] } + }), + categories: ["card","checkbox","frame","table","tanstack"], + meta: {"className":"**:data-[slot=preview]:w-full sm:**:data-[slot=preview]:max-w-4xl","colSpan":2}, + }, "p-tabs-1": { name: "p-tabs-1", description: "Basic tabs", diff --git a/apps/ui/registry/default/particles/p-table-2.tsx b/apps/ui/registry/default/particles/p-table-2.tsx index b578f5bdf..e65d4265a 100644 --- a/apps/ui/registry/default/particles/p-table-2.tsx +++ b/apps/ui/registry/default/particles/p-table-2.tsx @@ -13,7 +13,7 @@ import { export default function Particle() { return ( - +
Project diff --git a/apps/ui/registry/default/particles/p-table-3.tsx b/apps/ui/registry/default/particles/p-table-3.tsx index 5748e34cf..ad93c23f8 100644 --- a/apps/ui/registry/default/particles/p-table-3.tsx +++ b/apps/ui/registry/default/particles/p-table-3.tsx @@ -203,7 +203,7 @@ export default function Particle() { return ( -
+
{table.getHeaderGroups().map((headerGroup) => ( diff --git a/apps/ui/registry/default/particles/p-table-4.tsx b/apps/ui/registry/default/particles/p-table-4.tsx index 776996792..127f8c229 100644 --- a/apps/ui/registry/default/particles/p-table-4.tsx +++ b/apps/ui/registry/default/particles/p-table-4.tsx @@ -215,7 +215,7 @@ export default function Particle() { return ( -
+
{table.getHeaderGroups().map((headerGroup) => ( diff --git a/apps/ui/registry/default/particles/p-table-5.tsx b/apps/ui/registry/default/particles/p-table-5.tsx new file mode 100644 index 000000000..2ae7a53a0 --- /dev/null +++ b/apps/ui/registry/default/particles/p-table-5.tsx @@ -0,0 +1,117 @@ +import { Badge } from "@/registry/default/ui/badge"; +import { + Table, + TableBody, + TableCell, + TableFooter, + TableHead, + TableHeader, + TableRow, +} from "@/registry/default/ui/table"; + +export default function Particle() { + return ( +
+ + + Project + Status + Team + Budget + + + + + Website Redesign + + + + + Frontend Team + $12,500 + + + Mobile App + + + + + Mobile Team + $8,750 + + + API Integration + + + + + Backend Team + $5,200 + + + Database Migration + + + + + DevOps Team + $3,800 + + + User Dashboard + + + + + UX Team + $7,200 + + + Security Audit + + + + + Security Team + $2,100 + + + + + Total Budget + $39,550 + + +
+ ); +} diff --git a/apps/ui/registry/default/particles/p-table-6.tsx b/apps/ui/registry/default/particles/p-table-6.tsx new file mode 100644 index 000000000..66b29666b --- /dev/null +++ b/apps/ui/registry/default/particles/p-table-6.tsx @@ -0,0 +1,256 @@ +"use client"; + +import { + type ColumnDef, + flexRender, + getCoreRowModel, + useReactTable, +} from "@tanstack/react-table"; +import type React from "react"; +import { useMemo, useState } from "react"; +import { Badge } from "@/registry/default/ui/badge"; +import { CardFrame } from "@/registry/default/ui/card"; +import { Checkbox } from "@/registry/default/ui/checkbox"; +import { + Table, + TableBody, + TableCell, + TableFooter, + TableHead, + TableHeader, + TableRow, +} from "@/registry/default/ui/table"; + +type Project = { + id: string; + project: string; + status: "Paid" | "Unpaid" | "Pending" | "Failed"; + team: string; + budget: number; +}; + +const data: Project[] = [ + { + budget: 12500, + id: "1", + project: "Website Redesign", + status: "Paid", + team: "Frontend Team", + }, + { + budget: 8750, + id: "2", + project: "Mobile App", + status: "Unpaid", + team: "Mobile Team", + }, + { + budget: 5200, + id: "3", + project: "API Integration", + status: "Pending", + team: "Backend Team", + }, + { + budget: 3800, + id: "4", + project: "Database Migration", + status: "Paid", + team: "DevOps Team", + }, + { + budget: 7200, + id: "5", + project: "User Dashboard", + status: "Paid", + team: "UX Team", + }, + { + budget: 2100, + id: "6", + project: "Security Audit", + status: "Failed", + team: "Security Team", + }, +]; + +const getStatusColor = (status: Project["status"]) => { + switch (status) { + case "Paid": + return "bg-emerald-500"; + case "Unpaid": + return "bg-muted-foreground/64"; + case "Pending": + return "bg-amber-500"; + case "Failed": + return "bg-red-500"; + default: + return "bg-muted-foreground/64"; + } +}; + +const getColumns = (): ColumnDef[] => [ + { + cell: ({ row }) => { + const toggleHandler = row.getToggleSelectedHandler(); + return ( + { + // Create a synthetic event for the handler + const syntheticEvent = { + target: { checked: !!value }, + } as unknown as React.ChangeEvent; + toggleHandler(syntheticEvent); + }} + /> + ); + }, + enableSorting: false, + header: ({ table }) => { + const isAllSelected = table.getIsAllPageRowsSelected(); + const isSomeSelected = table.getIsSomePageRowsSelected(); + const toggleHandler = table.getToggleAllPageRowsSelectedHandler(); + return ( + { + // Create a synthetic event for the handler + const syntheticEvent = { + target: { checked: !!value }, + } as unknown as React.ChangeEvent; + toggleHandler(syntheticEvent); + }} + /> + ); + }, + id: "select", + }, + { + accessorKey: "project", + cell: ({ row }) => ( +
{row.getValue("project")}
+ ), + header: "Project", + }, + { + accessorKey: "status", + cell: ({ row }) => { + const status = row.getValue("status") as Project["status"]; + return ( + + + ); + }, + header: "Status", + }, + { + accessorKey: "team", + header: "Team", + }, + { + accessorKey: "budget", + cell: ({ row }) => { + const amount = Number.parseFloat(row.getValue("budget")); + const formatted = new Intl.NumberFormat("en-US", { + currency: "USD", + maximumFractionDigits: 0, + minimumFractionDigits: 0, + style: "currency", + }).format(amount); + return
{formatted}
; + }, + header: () =>
Budget
, + }, +]; + +export default function Particle() { + const [tableData] = useState(data); + const [rowSelection, setRowSelection] = useState({}); + + const columns = useMemo(() => getColumns(), []); + + const table = useReactTable({ + columns, + data: tableData, + enableRowSelection: true, + getCoreRowModel: getCoreRowModel(), + onRowSelectionChange: setRowSelection, + state: { + rowSelection, + }, + }); + + const totalBudget = tableData.reduce( + (sum, project) => sum + project.budget, + 0, + ); + const formattedTotal = new Intl.NumberFormat("en-US", { + currency: "USD", + maximumFractionDigits: 0, + minimumFractionDigits: 0, + style: "currency", + }).format(totalBudget); + + return ( + + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + )) + ) : ( + + + No results. + + + )} + + + + Total Budget + {formattedTotal} + + +
+
+ ); +} diff --git a/apps/ui/registry/default/ui/card.tsx b/apps/ui/registry/default/ui/card.tsx index a73cab4f5..3a86923bb 100644 --- a/apps/ui/registry/default/ui/card.tsx +++ b/apps/ui/registry/default/ui/card.tsx @@ -32,7 +32,7 @@ export function CardFrame({ }: useRender.ComponentProps<"div">): React.ReactElement { const defaultProps = { className: cn( - "relative flex flex-col rounded-2xl border bg-card not-dark:bg-clip-padding text-card-foreground shadow-xs/5 [--clip-bottom:-1rem] [--clip-top:-1rem] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-2xl)-1px)] before:bg-muted/72 before:shadow-[0_1px_--theme(--color-black/4%)] *:data-[slot=card]:-m-px *:not-first:data-[slot=card]:rounded-t-xl *:not-last:data-[slot=card]:rounded-b-xl *:data-[slot=card]:bg-clip-padding *:data-[slot=card]:shadow-none *:data-[slot=card]:before:hidden *:not-first:data-[slot=card]:before:rounded-t-[calc(var(--radius-xl)-1px)] *:not-last:data-[slot=card]:before:rounded-b-[calc(var(--radius-xl)-1px)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)] *:data-[slot=card]:[clip-path:inset(var(--clip-top)_1px_var(--clip-bottom)_1px_round_calc(var(--radius-2xl)-1px))] *:data-[slot=card]:last:[--clip-bottom:1px] *:data-[slot=card]:first:[--clip-top:1px]", + "relative flex flex-col rounded-2xl border bg-card not-dark:bg-clip-padding text-card-foreground shadow-xs/5 [--clip-bottom:-1rem] [--clip-top:-1rem] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-2xl)-1px)] before:bg-muted/72 before:shadow-[0_1px_--theme(--color-black/4%)] *:data-[slot=card]:-m-px *:data-[slot=table-container]:-mx-px *:data-[slot=table-container]:w-[calc(100%+2px)] *:not-first:data-[slot=card]:rounded-t-xl *:not-last:data-[slot=card]:rounded-b-xl *:data-[slot=card]:bg-clip-padding *:data-[slot=card]:shadow-none *:data-[slot=card]:before:hidden *:not-first:data-[slot=card]:before:rounded-t-[calc(var(--radius-xl)-1px)] *:not-last:data-[slot=card]:before:rounded-b-[calc(var(--radius-xl)-1px)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)] *:data-[slot=card]:[clip-path:inset(var(--clip-top)_1px_var(--clip-bottom)_1px_round_calc(var(--radius-2xl)-1px))] *:data-[slot=card]:last:[--clip-bottom:1px] *:data-[slot=card]:first:[--clip-top:1px] has-data-[slot=table-container]:overflow-hidden", className, ), "data-slot": "card-frame", diff --git a/apps/ui/registry/default/ui/table.tsx b/apps/ui/registry/default/ui/table.tsx index 3c4ebfc78..548fb6d64 100644 --- a/apps/ui/registry/default/ui/table.tsx +++ b/apps/ui/registry/default/ui/table.tsx @@ -1,18 +1,24 @@ import type * as React from "react"; import { cn } from "@/registry/default/lib/utils"; +export type TableVariant = "default" | "boxed"; + export function Table({ className, + variant = "default", ...props -}: React.ComponentProps<"table">): React.ReactElement { +}: React.ComponentProps<"table"> & { + variant?: TableVariant; +}): React.ReactElement { return (
): React.ReactElement { return ( @@ -45,7 +48,7 @@ export function TableBody({ return ( tr]:last:border-b-0 in-data-[slot=frame]:*:[tr]:hover:bg-transparent", + "border-t in-data-[variant=boxed]:border-none bg-muted/32 in-data-[variant=boxed]:bg-transparent font-medium [&>tr]:last:border-b-0 in-data-[variant=boxed]:**:[td]:min-h-10", className, )} data-slot="table-footer" @@ -77,7 +80,7 @@ export function TableRow({ return ( ): React.ReactElement { const defaultProps = { className: cn( - "relative flex flex-col rounded-2xl border bg-card not-dark:bg-clip-padding text-card-foreground shadow-xs/5 [--clip-bottom:-1rem] [--clip-top:-1rem] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-2xl)-1px)] before:bg-muted/72 before:shadow-[0_1px_--theme(--color-black/4%)] *:data-[slot=card]:-m-px *:not-first:data-[slot=card]:rounded-t-xl *:not-last:data-[slot=card]:rounded-b-xl *:data-[slot=card]:bg-clip-padding *:data-[slot=card]:shadow-none *:data-[slot=card]:before:hidden *:not-first:data-[slot=card]:before:rounded-t-[calc(var(--radius-xl)-1px)] *:not-last:data-[slot=card]:before:rounded-b-[calc(var(--radius-xl)-1px)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)] *:data-[slot=card]:[clip-path:inset(var(--clip-top)_1px_var(--clip-bottom)_1px_round_calc(var(--radius-2xl)-1px))] *:data-[slot=card]:last:[--clip-bottom:1px] *:data-[slot=card]:first:[--clip-top:1px]", + "relative flex flex-col rounded-2xl border bg-card not-dark:bg-clip-padding text-card-foreground shadow-xs/5 [--clip-bottom:-1rem] [--clip-top:-1rem] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-2xl)-1px)] before:bg-muted/72 before:shadow-[0_1px_--theme(--color-black/4%)] *:data-[slot=card]:-m-px *:data-[slot=table-container]:-mx-px *:data-[slot=table-container]:w-[calc(100%+2px)] *:not-first:data-[slot=card]:rounded-t-xl *:not-last:data-[slot=card]:rounded-b-xl *:data-[slot=card]:bg-clip-padding *:data-[slot=card]:shadow-none *:data-[slot=card]:before:hidden *:not-first:data-[slot=card]:before:rounded-t-[calc(var(--radius-xl)-1px)] *:not-last:data-[slot=card]:before:rounded-b-[calc(var(--radius-xl)-1px)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)] *:data-[slot=card]:[clip-path:inset(var(--clip-top)_1px_var(--clip-bottom)_1px_round_calc(var(--radius-2xl)-1px))] *:data-[slot=card]:last:[--clip-bottom:1px] *:data-[slot=card]:first:[--clip-top:1px] has-data-[slot=table-container]:overflow-hidden", className, ), "data-slot": "card-frame", diff --git a/packages/ui/src/components/table.tsx b/packages/ui/src/components/table.tsx index 221bcf64e..1208a8d1a 100644 --- a/packages/ui/src/components/table.tsx +++ b/packages/ui/src/components/table.tsx @@ -1,18 +1,24 @@ import { cn } from "@coss/ui/lib/utils"; import type * as React from "react"; +export type TableVariant = "default" | "boxed"; + export function Table({ className, + variant = "default", ...props -}: React.ComponentProps<"table">): React.ReactElement { +}: React.ComponentProps<"table"> & { + variant?: TableVariant; +}): React.ReactElement { return (
): React.ReactElement { return ( @@ -45,7 +48,7 @@ export function TableBody({ return ( tr]:last:border-b-0 in-data-[slot=frame]:*:[tr]:hover:bg-transparent", + "border-t in-data-[variant=boxed]:border-none bg-muted/32 in-data-[variant=boxed]:bg-transparent font-medium [&>tr]:last:border-b-0 in-data-[variant=boxed]:**:[td]:min-h-10", className, )} data-slot="table-footer" @@ -77,7 +80,7 @@ export function TableRow({ return (