{"$schema":"https:\/\/ui.particle.academy\/schema\/registry-item.json","name":"table","type":"registry:ui","title":"Table","description":"Table from react-fancy","package":"react-fancy","dependencies":[],"registryDependencies":[],"files":[{"path":"components\/fancy\/table\/Table.context.ts","content":"import { createContext, useContext } from \"react\";\r\nimport type { TableContextValue } from \".\/Table.types\";\r\n\r\nexport const TableContext = createContext<TableContextValue | null>(null);\r\n\r\nexport function useTable(): TableContextValue {\r\n  const ctx = useContext(TableContext);\r\n  if (!ctx) {\r\n    throw new Error(\"Table compound components must be used within <Table>\");\r\n  }\r\n  return ctx;\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/table\/Table.context.ts"},{"path":"components\/fancy\/table\/Table.tsx","content":"import { Children, isValidElement, useCallback, useMemo, useState } from \"react\";\r\nimport type { ReactNode } from \"react\";\r\nimport { cn } from \"..\/..\/utils\/cn\";\r\nimport { TableContext } from \".\/Table.context\";\r\nimport { TableHead } from \".\/TableHead\";\r\nimport { TableBody } from \".\/TableBody\";\r\nimport { TableRow } from \".\/TableRow\";\r\nimport { TableCell } from \".\/TableCell\";\r\nimport { TableColumn } from \".\/TableColumn\";\r\nimport { TablePagination } from \".\/TablePagination\";\r\nimport { TableSearch } from \".\/TableSearch\";\r\nimport { TableTray } from \".\/TableTray\";\r\nimport { TableRowTray } from \".\/TableRowTray\";\r\nimport type { TableProps, TableContextValue } from \".\/Table.types\";\r\n\r\nfunction TableRoot({ children, className }: TableProps) {\r\n  const [sortKey, setSortKey] = useState<string | null>(null);\r\n  const [sortDir, setSortDir] = useState<\"asc\" | \"desc\">(\"asc\");\r\n  const [page, setPage] = useState(0);\r\n  const [searchQuery, setSearchQuery] = useState(\"\");\r\n\r\n  const toggleSort = useCallback(\r\n    (key: string) => {\r\n      if (sortKey === key) {\r\n        setSortDir((d) => (d === \"asc\" ? \"desc\" : \"asc\"));\r\n      } else {\r\n        setSortKey(key);\r\n        setSortDir(\"asc\");\r\n      }\r\n    },\r\n    [sortKey],\r\n  );\r\n\r\n  const ctx = useMemo<TableContextValue>(\r\n    () => ({\r\n      sortKey,\r\n      sortDir,\r\n      toggleSort,\r\n      page,\r\n      setPage,\r\n      pageSize: 10,\r\n      totalRows: 0,\r\n      searchQuery,\r\n      setSearchQuery,\r\n    }),\r\n    [sortKey, sortDir, toggleSort, page, searchQuery],\r\n  );\r\n\r\n  return (\r\n    <TableContext.Provider value={ctx}>\r\n      <div data-react-fancy-table=\"\" className={cn(\"overflow-hidden rounded-lg border border-zinc-200 dark:border-zinc-700\", className)}>\r\n        {splitTableChildren(children)}\r\n      <\/div>\r\n    <\/TableContext.Provider>\r\n  );\r\n}\r\n\r\nfunction splitTableChildren(children: ReactNode): ReactNode {\r\n  const before: ReactNode[] = [];\r\n  const inside: ReactNode[] = [];\r\n  const after: ReactNode[] = [];\r\n  let seenTableContent = false;\r\n\r\n  Children.forEach(children, (child) => {\r\n    if (isValidElement(child) && (child.type === TableHead || child.type === TableBody)) {\r\n      inside.push(child);\r\n      seenTableContent = true;\r\n      return;\r\n    }\r\n    (seenTableContent ? after : before).push(child);\r\n  });\r\n\r\n  return (\r\n    <>\r\n      {before}\r\n      {inside.length > 0 && <table className=\"w-full border-collapse\">{inside}<\/table>}\r\n      {after}\r\n    <\/>\r\n  );\r\n}\r\n\r\nexport const Table = Object.assign(TableRoot, {\r\n  Head: TableHead,\r\n  Body: TableBody,\r\n  Row: TableRow,\r\n  Cell: TableCell,\r\n  Column: TableColumn,\r\n  Pagination: TablePagination,\r\n  Search: TableSearch,\r\n  Tray: TableTray,\r\n  RowTray: TableRowTray,\r\n});\r\n","type":"registry:ui","target":"components\/fancy\/table\/Table.tsx"},{"path":"components\/fancy\/table\/Table.types.ts","content":"import type { ReactNode, TdHTMLAttributes } from \"react\";\r\n\r\nexport interface TableColumn<T = any> {\r\n  key: string;\r\n  label: string;\r\n  sortable?: boolean;\r\n  render?: (value: any, row: T) => ReactNode;\r\n}\r\n\r\nexport interface TableContextValue {\r\n  sortKey: string | null;\r\n  sortDir: \"asc\" | \"desc\";\r\n  toggleSort: (key: string) => void;\r\n  page: number;\r\n  setPage: (page: number) => void;\r\n  pageSize: number;\r\n  totalRows: number;\r\n  searchQuery: string;\r\n  setSearchQuery: (query: string) => void;\r\n}\r\n\r\nexport interface TableProps {\r\n  children: ReactNode;\r\n  className?: string;\r\n}\r\n\r\nexport interface TableHeadProps {\r\n  children: ReactNode;\r\n  className?: string;\r\n}\r\n\r\nexport interface TableBodyProps {\r\n  children: ReactNode;\r\n  className?: string;\r\n}\r\n\r\nexport interface TableRowProps {\r\n  children: ReactNode;\r\n  className?: string;\r\n  onClick?: () => void;\r\n  tray?: ReactNode;\r\n  trayTriggerPosition?: \"start\" | \"end\" | \"hidden\";\r\n  expanded?: boolean;\r\n  defaultExpanded?: boolean;\r\n  onExpandedChange?: (expanded: boolean) => void;\r\n}\r\n\r\nexport interface TableRowTrayProps {\r\n  children: ReactNode;\r\n  className?: string;\r\n}\r\n\r\n\/**\r\n * Extends the native cell attributes so `colSpan`\/`rowSpan` (full-span\r\n * empty-state rows, trays) plus `onClick`, `style`, `scope`, `data-*`, etc. are\r\n * forwarded onto the underlying `<td>`\/`<th>` instead of silently dropped.\r\n *\/\r\nexport interface TableCellProps extends TdHTMLAttributes<HTMLTableCellElement> {\r\n  \/** Render a `<th>` header cell instead of a `<td>`. *\/\r\n  header?: boolean;\r\n}\r\n\r\nexport interface TableColumnProps {\r\n  label: string;\r\n  sortKey?: string;\r\n  className?: string;\r\n}\r\n\r\nexport interface TablePaginationProps {\r\n  className?: string;\r\n  total: number;\r\n  pageSize?: number;\r\n}\r\n\r\nexport interface TableSearchProps {\r\n  className?: string;\r\n  placeholder?: string;\r\n}\r\n\r\nexport interface TableTrayProps {\r\n  children: ReactNode;\r\n  className?: string;\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/table\/Table.types.ts"},{"path":"components\/fancy\/table\/TableBody.tsx","content":"import { cn } from \"..\/..\/utils\/cn\";\r\nimport type { TableBodyProps } from \".\/Table.types\";\r\n\r\nexport function TableBody({ children, className }: TableBodyProps) {\r\n  return <tbody data-react-fancy-table-body=\"\" className={cn(\"divide-y divide-zinc-100 dark:divide-zinc-800\", className)}>{children}<\/tbody>;\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/table\/TableBody.tsx"},{"path":"components\/fancy\/table\/TableCell.tsx","content":"import { cn } from \"..\/..\/utils\/cn\";\r\nimport type { TableCellProps } from \".\/Table.types\";\r\n\r\nexport function TableCell({ children, className, header, ...rest }: TableCellProps) {\r\n  const Tag = header ? \"th\" : \"td\";\r\n  return (\r\n    <Tag\r\n      data-react-fancy-table-cell=\"\"\r\n      className={cn(\r\n        \"px-4 py-3 text-sm\",\r\n        header && \"text-left font-medium text-zinc-500 dark:text-zinc-400\",\r\n        className,\r\n      )}\r\n      {...rest}\r\n    >\r\n      {children}\r\n    <\/Tag>\r\n  );\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/table\/TableCell.tsx"},{"path":"components\/fancy\/table\/TableColumn.tsx","content":"import { cn } from \"..\/..\/utils\/cn\";\r\nimport { useTable } from \".\/Table.context\";\r\nimport type { TableColumnProps } from \".\/Table.types\";\r\n\r\nexport function TableColumn({ label, sortKey, className }: TableColumnProps) {\r\n  const { sortKey: currentSort, sortDir, toggleSort } = useTable();\r\n  const isSorted = sortKey && currentSort === sortKey;\r\n\r\n  return (\r\n    <th\r\n      data-react-fancy-table-column=\"\"\r\n      className={cn(\r\n        \"px-4 py-3 text-left text-sm font-medium text-zinc-500 dark:text-zinc-400\",\r\n        sortKey && \"cursor-pointer select-none\",\r\n        className,\r\n      )}\r\n      onClick={sortKey ? () => toggleSort(sortKey) : undefined}\r\n    >\r\n      <span className=\"inline-flex items-center gap-1\">\r\n        {label}\r\n        {isSorted && (\r\n          <span className=\"text-xs\">{sortDir === \"asc\" ? \"\\u25B2\" : \"\\u25BC\"}<\/span>\r\n        )}\r\n      <\/span>\r\n    <\/th>\r\n  );\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/table\/TableColumn.tsx"},{"path":"components\/fancy\/table\/TableHead.tsx","content":"import { cn } from \"..\/..\/utils\/cn\";\r\nimport type { TableHeadProps } from \".\/Table.types\";\r\n\r\nexport function TableHead({ children, className }: TableHeadProps) {\r\n  return <thead data-react-fancy-table-head=\"\" className={cn(\"border-b border-zinc-200 dark:border-zinc-700\", className)}>{children}<\/thead>;\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/table\/TableHead.tsx"},{"path":"components\/fancy\/table\/TablePagination.tsx","content":"import { cn } from \"..\/..\/utils\/cn\";\r\nimport { useTable } from \".\/Table.context\";\r\nimport type { TablePaginationProps } from \".\/Table.types\";\r\n\r\nexport function TablePagination({\r\n  className,\r\n  total,\r\n  pageSize = 10,\r\n}: TablePaginationProps) {\r\n  const { page, setPage } = useTable();\r\n  const totalPages = Math.ceil(total \/ pageSize);\r\n\r\n  if (totalPages <= 1) return null;\r\n\r\n  return (\r\n    <div data-react-fancy-table-pagination=\"\" className={cn(\"flex items-center justify-between px-4 py-3 text-sm\", className)}>\r\n      <span className=\"text-zinc-500\">\r\n        Page {page + 1} of {totalPages}\r\n      <\/span>\r\n      <div className=\"flex gap-1\">\r\n        <button\r\n          type=\"button\"\r\n          disabled={page === 0}\r\n          onClick={() => setPage(page - 1)}\r\n          className=\"rounded border px-2 py-1 disabled:opacity-50 dark:border-zinc-600\"\r\n        >\r\n          Prev\r\n        <\/button>\r\n        <button\r\n          type=\"button\"\r\n          disabled={page >= totalPages - 1}\r\n          onClick={() => setPage(page + 1)}\r\n          className=\"rounded border px-2 py-1 disabled:opacity-50 dark:border-zinc-600\"\r\n        >\r\n          Next\r\n        <\/button>\r\n      <\/div>\r\n    <\/div>\r\n  );\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/table\/TablePagination.tsx"},{"path":"components\/fancy\/table\/TableRow.tsx","content":"import { cn } from \"..\/..\/utils\/cn\";\r\nimport { useControllableState } from \"..\/..\/hooks\/use-controllable-state\";\r\nimport type { TableRowProps } from \".\/Table.types\";\r\n\r\nexport function TableRow({\r\n  children,\r\n  className,\r\n  onClick,\r\n  tray,\r\n  trayTriggerPosition = \"end\",\r\n  expanded: controlledExpanded,\r\n  defaultExpanded = false,\r\n  onExpandedChange,\r\n}: TableRowProps) {\r\n  const [expanded, setExpanded] = useControllableState(\r\n    controlledExpanded,\r\n    defaultExpanded,\r\n    onExpandedChange,\r\n  );\r\n\r\n  const hasTray = tray != null;\r\n  const showTrigger = hasTray && trayTriggerPosition !== \"hidden\";\r\n\r\n  const triggerCell = showTrigger ? (\r\n    <td className=\"w-8 px-2\">\r\n      <button\r\n        type=\"button\"\r\n        onClick={(e) => {\r\n          e.stopPropagation();\r\n          setExpanded(!expanded);\r\n        }}\r\n        className=\"inline-flex items-center justify-center rounded p-1 text-zinc-400 transition-colors hover:text-zinc-700 dark:text-zinc-500 dark:hover:text-zinc-300\"\r\n        aria-label={expanded ? \"Collapse row\" : \"Expand row\"}\r\n      >\r\n        <svg\r\n          className={cn(\r\n            \"h-4 w-4 transition-transform duration-200\",\r\n            expanded && \"rotate-90\",\r\n          )}\r\n          fill=\"none\"\r\n          viewBox=\"0 0 24 24\"\r\n          stroke=\"currentColor\"\r\n          strokeWidth={2}\r\n        >\r\n          <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M9 5l7 7-7 7\" \/>\r\n        <\/svg>\r\n      <\/button>\r\n    <\/td>\r\n  ) : null;\r\n\r\n  return (\r\n    <>\r\n      <tr\r\n        data-react-fancy-table-row=\"\"\r\n        className={cn(\r\n          \"transition-colors hover:bg-zinc-50 dark:hover:bg-zinc-800\/50\",\r\n          onClick && \"cursor-pointer\",\r\n          className,\r\n        )}\r\n        onClick={onClick}\r\n      >\r\n        {trayTriggerPosition === \"start\" && triggerCell}\r\n        {children}\r\n        {trayTriggerPosition === \"end\" && triggerCell}\r\n      <\/tr>\r\n      {hasTray && expanded && (\r\n        <tr className=\"bg-zinc-50\/50 dark:bg-zinc-800\/30\">\r\n          <td colSpan={999}>\r\n            {tray}\r\n          <\/td>\r\n        <\/tr>\r\n      )}\r\n    <\/>\r\n  );\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/table\/TableRow.tsx"},{"path":"components\/fancy\/table\/TableRowTray.tsx","content":"import { cn } from \"..\/..\/utils\/cn\";\r\nimport type { TableRowTrayProps } from \".\/Table.types\";\r\n\r\nexport function TableRowTray({ children, className }: TableRowTrayProps) {\r\n  return (\r\n    <div\r\n      data-react-fancy-table-row-tray=\"\"\r\n      className={cn(\r\n        \"border-t border-zinc-200 px-4 py-3 dark:border-zinc-700\",\r\n        className,\r\n      )}\r\n    >\r\n      {children}\r\n    <\/div>\r\n  );\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/table\/TableRowTray.tsx"},{"path":"components\/fancy\/table\/TableSearch.tsx","content":"import { cn } from \"..\/..\/utils\/cn\";\r\nimport { useTable } from \".\/Table.context\";\r\nimport type { TableSearchProps } from \".\/Table.types\";\r\n\r\nexport function TableSearch({\r\n  className,\r\n  placeholder = \"Search...\",\r\n}: TableSearchProps) {\r\n  const { searchQuery, setSearchQuery } = useTable();\r\n\r\n  return (\r\n    <input\r\n      data-react-fancy-table-search=\"\"\r\n      type=\"text\"\r\n      value={searchQuery}\r\n      onChange={(e) => setSearchQuery(e.target.value)}\r\n      placeholder={placeholder}\r\n      className={cn(\r\n        \"rounded-lg border border-zinc-200 px-3 py-2 text-sm dark:border-zinc-600 dark:bg-zinc-800\",\r\n        className,\r\n      )}\r\n    \/>\r\n  );\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/table\/TableSearch.tsx"},{"path":"components\/fancy\/table\/TableTray.tsx","content":"import { cn } from \"..\/..\/utils\/cn\";\r\nimport type { TableTrayProps } from \".\/Table.types\";\r\n\r\nexport function TableTray({ children, className }: TableTrayProps) {\r\n  return (\r\n    <div data-react-fancy-table-tray=\"\" className={cn(\"flex items-center justify-between px-4 py-3\", className)}>\r\n      {children}\r\n    <\/div>\r\n  );\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/table\/TableTray.tsx"},{"path":"components\/fancy\/table\/index.ts","content":"export { Table } from \".\/Table\";\r\nexport { TableHead } from \".\/TableHead\";\r\nexport { TableBody } from \".\/TableBody\";\r\nexport { TableRow } from \".\/TableRow\";\r\nexport { TableCell } from \".\/TableCell\";\r\nexport { TableColumn } from \".\/TableColumn\";\r\nexport { TablePagination } from \".\/TablePagination\";\r\nexport { TableSearch } from \".\/TableSearch\";\r\nexport { TableTray } from \".\/TableTray\";\r\nexport { TableRowTray } from \".\/TableRowTray\";\r\nexport type {\r\n  TableProps,\r\n  TableHeadProps,\r\n  TableBodyProps,\r\n  TableRowProps,\r\n  TableCellProps,\r\n  TableColumnProps,\r\n  TablePaginationProps,\r\n  TableSearchProps,\r\n  TableTrayProps,\r\n  TableRowTrayProps,\r\n} from \".\/Table.types\";\r\n","type":"registry:ui","target":"components\/fancy\/table\/index.ts"}]}