import type { ComponentPropsWithRef, HTMLAttributes, ReactNode, Ref, TdHTMLAttributes, ThHTMLAttributes } from "react"; import { createContext, isValidElement, useContext } from "react"; import { ArrowDown, ChevronSelectorVertical, Copy01, Edit01, HelpCircle, Trash01 } from "@untitledui/icons"; import type { CellProps as AriaCellProps, ColumnProps as AriaColumnProps, RowProps as AriaRowProps, TableHeaderProps as AriaTableHeaderProps, TableProps as AriaTableProps, } from "react-aria-components"; import { Cell as AriaCell, Collection as AriaCollection, Column as AriaColumn, Group as AriaGroup, Row as AriaRow, Table as AriaTable, TableBody as AriaTableBody, TableHeader as AriaTableHeader, useTableOptions, } from "react-aria-components"; import { Badge } from "@/components/base/badges/badges"; import { Checkbox } from "@/components/base/checkbox/checkbox"; import { Dropdown } from "@/components/base/dropdown/dropdown"; import { Tooltip, TooltipTrigger } from "@/components/base/tooltip/tooltip"; import { cx } from "@/utils/cx"; export const TableRowActionsDropdown = () => ( Edit Copy link Delete ); const TableContext = createContext<{ size: "sm" | "md" }>({ size: "md" }); const TableCardRoot = ({ children, className, size = "md", ...props }: HTMLAttributes & { size?: "sm" | "md" }) => { return (
{children}
); }; interface TableCardHeaderProps { /** The title of the table card header. */ title: string; /** The badge displayed next to the title. */ badge?: ReactNode; /** The description of the table card header. */ description?: string; /** The content displayed after the title and badge. */ contentTrailing?: ReactNode; /** The class name of the table card header. */ className?: string; } const TableCardHeader = ({ title, badge, description, contentTrailing, className }: TableCardHeaderProps) => { const { size } = useContext(TableContext); return (

{title}

{badge ? ( isValidElement(badge) ? ( badge ) : ( {badge} ) ) : null}
{description &&

{description}

}
{contentTrailing}
); }; interface TableRootProps extends AriaTableProps, Omit, "className" | "slot" | "style"> { size?: "sm" | "md"; } const TableRoot = ({ className, size = "md", ...props }: TableRootProps) => { const context = useContext(TableContext); return (
cx("w-full overflow-x-hidden", typeof className === "function" ? className(state) : className)} {...props} />
); }; TableRoot.displayName = "Table"; interface TableHeaderProps extends AriaTableHeaderProps, Omit, "children" | "className" | "slot" | "style"> { bordered?: boolean; size?: "sm" | "md"; } const TableHeader = ({ columns, children, bordered = true, className, size: sizeProp, ...props }: TableHeaderProps) => { const context = useContext(TableContext); const { selectionBehavior, selectionMode } = useTableOptions(); const size = sizeProp ?? context.size; return ( cx( "relative bg-secondary", size === "sm" ? "h-9" : "h-11", // Row border—using an "after" pseudo-element to avoid the border taking up space. bordered && "[&>tr>th]:after:pointer-events-none [&>tr>th]:after:absolute [&>tr>th]:after:inset-x-0 [&>tr>th]:after:bottom-0 [&>tr>th]:after:h-px [&>tr>th]:after:bg-border-secondary [&>tr>th]:focus-visible:after:bg-transparent", typeof className === "function" ? className(state) : className, ) } > {selectionBehavior === "toggle" && ( {selectionMode === "multiple" && (
)}
)} {children}
); }; TableHeader.displayName = "TableHeader"; interface TableHeadProps extends AriaColumnProps, Omit, "children" | "className" | "style" | "id"> { label?: string; tooltip?: string; } const TableHead = ({ className, tooltip, label, children, ...props }: TableHeadProps) => { const { selectionBehavior } = useTableOptions(); return ( cx( "relative p-0 px-6 py-2 outline-hidden focus-visible:z-1 focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-offset-bg-primary focus-visible:ring-inset", selectionBehavior === "toggle" && "nth-2:pl-3", state.allowsSorting && "cursor-pointer", typeof className === "function" ? className(state) : className, ) } > {(state) => (
{label && {label}} {typeof children === "function" ? children(state) : children}
{tooltip && ( )} {state.allowsSorting && (state.sortDirection ? ( ) : ( ))}
)}
); }; TableHead.displayName = "TableHead"; interface TableRowProps extends AriaRowProps, Omit, "children" | "className" | "onClick" | "slot" | "style" | "id"> { highlightSelectedRow?: boolean; size?: "sm" | "md"; } const TableRow = ({ columns, children, className, highlightSelectedRow = true, size: sizeProp, ...props }: TableRowProps) => { const context = useContext(TableContext); const { selectionBehavior } = useTableOptions(); const size = sizeProp ?? context.size; return ( cx( "relative outline-focus-ring transition-colors after:pointer-events-none hover:bg-secondary focus-visible:outline-2 focus-visible:-outline-offset-2", size === "sm" ? "h-14" : "h-18", highlightSelectedRow && "selected:bg-secondary", // Row border—using an "after" pseudo-element to avoid the border taking up space. "[&>td]:after:absolute [&>td]:after:inset-x-0 [&>td]:after:bottom-0 [&>td]:after:h-px [&>td]:after:w-full [&>td]:after:bg-border-secondary last:[&>td]:after:hidden [&>td]:focus-visible:after:opacity-0 focus-visible:[&>td]:after:opacity-0", typeof className === "function" ? className(state) : className, ) } > {selectionBehavior === "toggle" && (
)} {children}
); }; TableRow.displayName = "TableRow"; interface TableCellProps extends AriaCellProps, Omit, "children" | "className" | "style" | "id"> { ref?: Ref; size?: "sm" | "md"; } const TableCell = ({ className, children, size: sizeProp, ...props }: TableCellProps) => { const context = useContext(TableContext); const { selectionBehavior } = useTableOptions(); const size = sizeProp ?? context.size; return ( cx( "relative text-sm text-tertiary outline-focus-ring focus-visible:z-1 focus-visible:outline-2 focus-visible:-outline-offset-2", size === "sm" && "px-5 py-3", size === "md" && "px-6 py-4", selectionBehavior === "toggle" && "nth-2:pl-3", typeof className === "function" ? className(state) : className, ) } > {children} ); }; TableCell.displayName = "TableCell"; const TableCard = { Root: TableCardRoot, Header: TableCardHeader, }; const Table = TableRoot as typeof TableRoot & { Body: typeof AriaTableBody; Cell: typeof TableCell; Head: typeof TableHead; Header: typeof TableHeader; Row: typeof TableRow; }; Table.Body = AriaTableBody; Table.Cell = TableCell; Table.Head = TableHead; Table.Header = TableHeader; Table.Row = TableRow; export { Table, TableCard };