{
  "name": "animated-table",
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "files": [
    {
      "path": "ui/animated-table.tsx",
      "content": "import {\n  Check,\n  ChevronDown,\n  ChevronLeft,\n  ChevronRight,\n  ChevronsLeft,\n  ChevronsRight,\n  ChevronsUpDown,\n  ChevronUp,\n  Columns3,\n  Search,\n  X,\n} from \"lucide-react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\n\n// Types\nexport type SortDirection = \"asc\" | \"desc\" | null;\n\nexport interface ColumnDef<T> {\n  id: string;\n  header: React.ReactNode;\n  accessorKey?: keyof T;\n  cell?: (row: T, index: number) => React.ReactNode;\n  sortable?: boolean;\n  filterable?: boolean;\n  align?: \"left\" | \"center\" | \"right\";\n  width?: string;\n  hideable?: boolean;\n}\n\nexport interface PaginationConfig {\n  page: number;\n  pageSize: number;\n  totalItems: number;\n  pageSizeOptions?: number[];\n  onPageChange: (page: number) => void;\n  onPageSizeChange?: (pageSize: number) => void;\n}\n\nexport interface AnimatedTableProps<T extends { id: string | number }> {\n  data: T[];\n  columns: ColumnDef<T>[];\n  selectable?: boolean;\n  selectedIds?: (string | number)[];\n  onSelectionChange?: (ids: (string | number)[]) => void;\n  onRowClick?: (row: T) => void;\n  sortColumn?: string;\n  sortDirection?: SortDirection;\n  onSort?: (columnId: string, direction: SortDirection) => void;\n  striped?: boolean;\n  stickyHeader?: boolean;\n  className?: string;\n  emptyMessage?: React.ReactNode;\n  loading?: boolean;\n  loadingRows?: number;\n  // New features\n  searchable?: boolean;\n  searchValue?: string;\n  onSearchChange?: (value: string) => void;\n  searchPlaceholder?: string;\n  pagination?: PaginationConfig;\n  expandable?: boolean;\n  renderExpandedRow?: (row: T) => React.ReactNode;\n  columnVisibility?: boolean;\n  visibleColumns?: string[];\n  onVisibleColumnsChange?: (columns: string[]) => void;\n}\n\n// Search Input Component\nconst TableSearch = ({\n  value,\n  onChange,\n  placeholder = \"Search...\",\n}: {\n  value: string;\n  onChange: (value: string) => void;\n  placeholder?: string;\n}) => (\n  <div className=\"relative\">\n    <Search className=\"absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-muted-foreground\" />\n    <input\n      type=\"text\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n      placeholder={placeholder}\n      className=\"h-9 w-full rounded-md border border-table-border bg-background pr-8 pl-9 text-foreground text-sm placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\n    />\n    <AnimatePresence>\n      {value && (\n        <motion.button\n          initial={{ opacity: 0, scale: 0.8 }}\n          animate={{ opacity: 1, scale: 1 }}\n          exit={{ opacity: 0, scale: 0.8 }}\n          onClick={() => onChange(\"\")}\n          className=\"absolute top-1/2 right-2 -translate-y-1/2 rounded p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground\"\n        >\n          <X className=\"h-3.5 w-3.5\" />\n        </motion.button>\n      )}\n    </AnimatePresence>\n  </div>\n);\n\n// Column Visibility Dropdown\nconst ColumnVisibilityDropdown = <T,>({\n  columns,\n  visibleColumns,\n  onChange,\n}: {\n  columns: ColumnDef<T>[];\n  visibleColumns: string[];\n  onChange: (columns: string[]) => void;\n}) => {\n  const [open, setOpen] = React.useState(false);\n  const dropdownRef = React.useRef<HTMLDivElement>(null);\n\n  React.useEffect(() => {\n    const handleClickOutside = (e: MouseEvent) => {\n      if (\n        dropdownRef.current &&\n        !dropdownRef.current.contains(e.target as Node)\n      ) {\n        setOpen(false);\n      }\n    };\n    document.addEventListener(\"mousedown\", handleClickOutside);\n    return () => document.removeEventListener(\"mousedown\", handleClickOutside);\n  }, []);\n\n  const hideableColumns = columns.filter((col) => col.hideable !== false);\n\n  const toggleColumn = (columnId: string) => {\n    if (visibleColumns.includes(columnId)) {\n      if (visibleColumns.length > 1) {\n        onChange(visibleColumns.filter((id) => id !== columnId));\n      }\n    } else {\n      onChange([...visibleColumns, columnId]);\n    }\n  };\n\n  return (\n    <div className=\"relative\" ref={dropdownRef}>\n      <button\n        onClick={() => setOpen(!open)}\n        className={cn(\n          \"flex h-9 items-center gap-2 rounded-md border border-table-border bg-background px-3 text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground\",\n          open && \"border-primary text-foreground\",\n        )}\n      >\n        <Columns3 className=\"h-4 w-4\" />\n        <span>Columns</span>\n      </button>\n      <AnimatePresence>\n        {open && (\n          <motion.div\n            initial={{ opacity: 0, y: -8, scale: 0.96 }}\n            animate={{ opacity: 1, y: 0, scale: 1 }}\n            exit={{ opacity: 0, y: -8, scale: 0.96 }}\n            transition={{ duration: 0.15 }}\n            className=\"absolute top-full right-0 z-20 mt-2 min-w-[180px] rounded-lg border border-table-border bg-card p-1 shadow-lg\"\n          >\n            {hideableColumns.map((column) => (\n              <button\n                key={column.id}\n                onClick={() => toggleColumn(column.id)}\n                className=\"flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm transition-colors hover:bg-muted\"\n              >\n                <div\n                  className={cn(\n                    \"flex h-4 w-4 items-center justify-center rounded border transition-all\",\n                    visibleColumns.includes(column.id)\n                      ? \"border-primary bg-primary text-primary-foreground\"\n                      : \"border-muted-foreground/40\",\n                  )}\n                >\n                  {visibleColumns.includes(column.id) && (\n                    <Check className=\"h-3 w-3\" strokeWidth={3} />\n                  )}\n                </div>\n                <span className=\"text-foreground\">{column.header}</span>\n              </button>\n            ))}\n          </motion.div>\n        )}\n      </AnimatePresence>\n    </div>\n  );\n};\n\n// Pagination Component\nconst TablePagination = ({\n  page,\n  pageSize,\n  totalItems,\n  pageSizeOptions = [5, 10, 20, 50],\n  onPageChange,\n  onPageSizeChange,\n}: PaginationConfig) => {\n  const totalPages = Math.ceil(totalItems / pageSize);\n  const startItem = (page - 1) * pageSize + 1;\n  const endItem = Math.min(page * pageSize, totalItems);\n\n  const canGoPrev = page > 1;\n  const canGoNext = page < totalPages;\n\n  return (\n    <div className=\"flex flex-wrap items-center justify-between gap-4 border-table-border border-t bg-table-header px-4 py-3\">\n      <div className=\"flex items-center gap-2 text-muted-foreground text-sm\">\n        <span>Rows per page:</span>\n        <select\n          value={pageSize}\n          onChange={(e) => onPageSizeChange?.(Number(e.target.value))}\n          className=\"h-8 rounded border border-table-border bg-background px-2 text-foreground focus:border-primary focus:outline-none\"\n        >\n          {pageSizeOptions.map((size) => (\n            <option key={size} value={size}>\n              {size}\n            </option>\n          ))}\n        </select>\n      </div>\n\n      <div className=\"flex items-center gap-1 text-muted-foreground text-sm\">\n        <span>\n          {totalItems > 0\n            ? `${startItem}-${endItem} of ${totalItems}`\n            : \"0 items\"}\n        </span>\n      </div>\n\n      <div className=\"flex items-center gap-1\">\n        <motion.button\n          whileHover={{ scale: canGoPrev ? 1.05 : 1 }}\n          whileTap={{ scale: canGoPrev ? 0.95 : 1 }}\n          disabled={!canGoPrev}\n          onClick={() => onPageChange(1)}\n          className={cn(\n            \"flex h-8 w-8 items-center justify-center rounded border border-table-border transition-colors\",\n            canGoPrev\n              ? \"text-foreground hover:bg-muted\"\n              : \"cursor-not-allowed text-muted-foreground/40\",\n          )}\n        >\n          <ChevronsLeft className=\"h-4 w-4\" />\n        </motion.button>\n        <motion.button\n          whileHover={{ scale: canGoPrev ? 1.05 : 1 }}\n          whileTap={{ scale: canGoPrev ? 0.95 : 1 }}\n          disabled={!canGoPrev}\n          onClick={() => onPageChange(page - 1)}\n          className={cn(\n            \"flex h-8 w-8 items-center justify-center rounded border border-table-border transition-colors\",\n            canGoPrev\n              ? \"text-foreground hover:bg-muted\"\n              : \"cursor-not-allowed text-muted-foreground/40\",\n          )}\n        >\n          <ChevronLeft className=\"h-4 w-4\" />\n        </motion.button>\n\n        <div className=\"flex items-center gap-1 px-2\">\n          {Array.from({ length: Math.min(5, totalPages) }, (_, i) => {\n            let pageNum: number;\n            if (totalPages <= 5) {\n              pageNum = i + 1;\n            } else if (page <= 3) {\n              pageNum = i + 1;\n            } else if (page >= totalPages - 2) {\n              pageNum = totalPages - 4 + i;\n            } else {\n              pageNum = page - 2 + i;\n            }\n\n            return (\n              <motion.button\n                key={pageNum}\n                whileHover={{ scale: 1.1 }}\n                whileTap={{ scale: 0.9 }}\n                onClick={() => onPageChange(pageNum)}\n                className={cn(\n                  \"flex h-8 w-8 items-center justify-center rounded text-sm transition-colors\",\n                  page === pageNum\n                    ? \"bg-primary text-primary-foreground\"\n                    : \"text-muted-foreground hover:bg-muted hover:text-foreground\",\n                )}\n              >\n                {pageNum}\n              </motion.button>\n            );\n          })}\n        </div>\n\n        <motion.button\n          whileHover={{ scale: canGoNext ? 1.05 : 1 }}\n          whileTap={{ scale: canGoNext ? 0.95 : 1 }}\n          disabled={!canGoNext}\n          onClick={() => onPageChange(page + 1)}\n          className={cn(\n            \"flex h-8 w-8 items-center justify-center rounded border border-table-border transition-colors\",\n            canGoNext\n              ? \"text-foreground hover:bg-muted\"\n              : \"cursor-not-allowed text-muted-foreground/40\",\n          )}\n        >\n          <ChevronRight className=\"h-4 w-4\" />\n        </motion.button>\n        <motion.button\n          whileHover={{ scale: canGoNext ? 1.05 : 1 }}\n          whileTap={{ scale: canGoNext ? 0.95 : 1 }}\n          disabled={!canGoNext}\n          onClick={() => onPageChange(totalPages)}\n          className={cn(\n            \"flex h-8 w-8 items-center justify-center rounded border border-table-border transition-colors\",\n            canGoNext\n              ? \"text-foreground hover:bg-muted\"\n              : \"cursor-not-allowed text-muted-foreground/40\",\n          )}\n        >\n          <ChevronsRight className=\"h-4 w-4\" />\n        </motion.button>\n      </div>\n    </div>\n  );\n};\n\n// Animated Table Root\nconst AnimatedTableRoot = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement> & { stickyHeader?: boolean }\n>(({ className, stickyHeader, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\n      \"relative w-full overflow-hidden rounded-lg border border-table-border bg-card\",\n      className,\n    )}\n    {...props}\n  />\n));\nAnimatedTableRoot.displayName = \"AnimatedTableRoot\";\n\n// Table Scroll Container\nconst TableScrollContainer = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement> & { stickyHeader?: boolean }\n>(({ className, stickyHeader, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\"overflow-auto\", stickyHeader && \"max-h-[400px]\", className)}\n    {...props}\n  />\n));\nTableScrollContainer.displayName = \"TableScrollContainer\";\n\n// Table Element\nconst TableElement = React.forwardRef<\n  HTMLTableElement,\n  React.HTMLAttributes<HTMLTableElement>\n>(({ className, ...props }, ref) => (\n  <table\n    ref={ref}\n    className={cn(\"w-full caption-bottom text-sm\", className)}\n    {...props}\n  />\n));\nTableElement.displayName = \"TableElement\";\n\n// Table Header\nconst TableHeader = React.forwardRef<\n  HTMLTableSectionElement,\n  React.HTMLAttributes<HTMLTableSectionElement> & { sticky?: boolean }\n>(({ className, sticky, ...props }, ref) => (\n  <thead\n    ref={ref}\n    className={cn(\n      \"bg-table-header\",\n      sticky && \"sticky top-0 z-10 shadow-sm\",\n      className,\n    )}\n    {...props}\n  />\n));\nTableHeader.displayName = \"TableHeader\";\n\n// Table Body\nconst TableBody = React.forwardRef<\n  HTMLTableSectionElement,\n  React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n  <tbody\n    ref={ref}\n    className={cn(\"[&_tr:last-child]:border-0\", className)}\n    {...props}\n  />\n));\nTableBody.displayName = \"TableBody\";\n\n// Table Row\ninterface TableRowProps {\n  isSelected?: boolean;\n  striped?: boolean;\n  index?: number;\n  onClick?: () => void;\n  className?: string;\n  children?: React.ReactNode;\n}\n\nconst TableRow = React.forwardRef<HTMLTableRowElement, TableRowProps>(\n  ({ className, isSelected, striped, index = 0, onClick, children }, ref) => (\n    <motion.tr\n      ref={ref}\n      initial={{ opacity: 0, y: 8 }}\n      animate={{ opacity: 1, y: 0 }}\n      exit={{ opacity: 0, y: -8 }}\n      transition={{ duration: 0.2, delay: index * 0.03 }}\n      onClick={onClick}\n      className={cn(\n        \"group border-table-border border-b transition-colors\",\n        \"hover:bg-table-row-hover\",\n        isSelected && \"bg-table-row-selected\",\n        striped && index % 2 === 1 && \"bg-table-row-stripe\",\n        className,\n      )}\n    >\n      {children}\n    </motion.tr>\n  ),\n);\nTableRow.displayName = \"TableRow\";\n\n// Expanded Row\nconst ExpandedRow = ({\n  children,\n  colSpan,\n}: {\n  children: React.ReactNode;\n  colSpan: number;\n}) => (\n  <motion.tr\n    initial={{ opacity: 0, height: 0 }}\n    animate={{ opacity: 1, height: \"auto\" }}\n    exit={{ opacity: 0, height: 0 }}\n    transition={{ duration: 0.2 }}\n    className=\"border-table-border border-b bg-muted/30\"\n  >\n    <td colSpan={colSpan} className=\"p-0\">\n      <motion.div\n        initial={{ opacity: 0 }}\n        animate={{ opacity: 1 }}\n        exit={{ opacity: 0 }}\n        transition={{ duration: 0.15, delay: 0.05 }}\n        className=\"px-4 py-3\"\n      >\n        {children}\n      </motion.div>\n    </td>\n  </motion.tr>\n);\n\n// Expand Button\nconst ExpandButton = ({\n  isExpanded,\n  onClick,\n}: {\n  isExpanded: boolean;\n  onClick: () => void;\n}) => (\n  <motion.button\n    onClick={(e) => {\n      e.stopPropagation();\n      onClick();\n    }}\n    className=\"flex h-6 w-6 items-center justify-center rounded transition-colors hover:bg-muted\"\n    animate={{ rotate: isExpanded ? 90 : 0 }}\n    transition={{ duration: 0.2 }}\n  >\n    <ChevronRight className=\"h-4 w-4 text-muted-foreground\" />\n  </motion.button>\n);\n\n// Table Head Cell\ninterface TableHeadProps extends React.ThHTMLAttributes<HTMLTableCellElement> {\n  sortable?: boolean;\n  sortDirection?: SortDirection;\n  onSort?: () => void;\n  align?: \"left\" | \"center\" | \"right\";\n}\n\nconst TableHead = React.forwardRef<HTMLTableCellElement, TableHeadProps>(\n  (\n    {\n      className,\n      children,\n      sortable,\n      sortDirection,\n      onSort,\n      align = \"left\",\n      ...props\n    },\n    ref,\n  ) => {\n    const alignClass = {\n      left: \"text-left\",\n      center: \"text-center\",\n      right: \"text-right\",\n    }[align];\n\n    return (\n      <th\n        ref={ref}\n        className={cn(\n          \"h-12 px-4 font-medium text-muted-foreground\",\n          alignClass,\n          sortable && \"cursor-pointer select-none hover:text-foreground\",\n          className,\n        )}\n        onClick={sortable ? onSort : undefined}\n        {...props}\n      >\n        <div\n          className={cn(\n            \"flex items-center gap-2\",\n            align === \"center\" && \"justify-center\",\n            align === \"right\" && \"justify-end\",\n          )}\n        >\n          <span>{children}</span>\n          {sortable && (\n            <motion.span\n              className=\"flex-shrink-0\"\n              animate={sortDirection ? { scale: 1 } : { scale: 0.9 }}\n            >\n              {sortDirection === \"asc\" ? (\n                <ChevronUp className=\"h-4 w-4 animate-sort-bounce text-table-sort-active\" />\n              ) : sortDirection === \"desc\" ? (\n                <ChevronDown className=\"h-4 w-4 animate-sort-bounce text-table-sort-active\" />\n              ) : (\n                <ChevronsUpDown className=\"h-4 w-4 opacity-40 group-hover:opacity-70\" />\n              )}\n            </motion.span>\n          )}\n        </div>\n      </th>\n    );\n  },\n);\nTableHead.displayName = \"TableHead\";\n\n// Table Cell\ninterface TableCellProps extends React.TdHTMLAttributes<HTMLTableCellElement> {\n  align?: \"left\" | \"center\" | \"right\";\n}\n\nconst TableCell = React.forwardRef<HTMLTableCellElement, TableCellProps>(\n  ({ className, align = \"left\", ...props }, ref) => {\n    const alignClass = {\n      left: \"text-left\",\n      center: \"text-center\",\n      right: \"text-right\",\n    }[align];\n\n    return (\n      <td\n        ref={ref}\n        className={cn(\"p-4 align-middle\", alignClass, className)}\n        {...props}\n      />\n    );\n  },\n);\nTableCell.displayName = \"TableCell\";\n\n// Checkbox Cell\ninterface CheckboxCellProps {\n  checked: boolean;\n  indeterminate?: boolean;\n  onChange: () => void;\n}\n\nconst CheckboxCell = ({\n  checked,\n  indeterminate,\n  onChange,\n}: CheckboxCellProps) => (\n  <div\n    role=\"checkbox\"\n    aria-checked={indeterminate ? \"mixed\" : checked}\n    tabIndex={0}\n    onClick={(e) => {\n      e.stopPropagation();\n      onChange();\n    }}\n    onKeyDown={(e) => {\n      if (e.key === \" \" || e.key === \"Enter\") {\n        e.preventDefault();\n        onChange();\n      }\n    }}\n    className={cn(\n      \"flex h-4 w-4 cursor-pointer items-center justify-center rounded border transition-all duration-150\",\n      checked || indeterminate\n        ? \"border-primary bg-primary text-primary-foreground\"\n        : \"border-muted-foreground/40 hover:border-muted-foreground\",\n    )}\n  >\n    <AnimatePresence mode=\"wait\">\n      {(checked || indeterminate) && (\n        <motion.div\n          initial={{ scale: 0, opacity: 0 }}\n          animate={{ scale: 1, opacity: 1 }}\n          exit={{ scale: 0, opacity: 0 }}\n          transition={{ duration: 0.1 }}\n        >\n          {indeterminate ? (\n            <div className=\"h-0.5 w-2 rounded-full bg-current\" />\n          ) : (\n            <Check className=\"h-3 w-3\" strokeWidth={3} />\n          )}\n        </motion.div>\n      )}\n    </AnimatePresence>\n  </div>\n);\n\n// Skeleton Row for Loading State\nconst SkeletonRow = ({\n  columns,\n  index,\n}: {\n  columns: number;\n  index: number;\n}) => (\n  <motion.tr\n    initial={{ opacity: 0 }}\n    animate={{ opacity: 1 }}\n    transition={{ delay: index * 0.05 }}\n    className=\"border-table-border border-b\"\n  >\n    <td colSpan={columns} className=\"p-4\">\n      <div className=\"flex items-center gap-4\">\n        <div className=\"h-4 w-4 animate-pulse rounded bg-muted\" />\n        <div className=\"flex-1 space-y-2\">\n          <div\n            className=\"h-4 animate-pulse rounded bg-muted\"\n            style={{ width: `${60 + Math.random() * 30}%` }}\n          />\n        </div>\n        {Array.from({ length: columns - 2 }).map((_, i) => (\n          <div\n            key={i}\n            className=\"h-4 animate-pulse rounded bg-muted\"\n            style={{ width: `${40 + Math.random() * 40}px` }}\n          />\n        ))}\n      </div>\n    </td>\n  </motion.tr>\n);\n\n// Empty State\nconst EmptyState = ({\n  message,\n  colSpan,\n}: {\n  message: React.ReactNode;\n  colSpan: number;\n}) => (\n  <motion.tr\n    initial={{ opacity: 0, y: 20 }}\n    animate={{ opacity: 1, y: 0 }}\n    transition={{ duration: 0.3 }}\n  >\n    <td colSpan={colSpan} className=\"h-32 text-center\">\n      <div className=\"flex flex-col items-center justify-center gap-2 text-muted-foreground\">\n        {message || \"No data available\"}\n      </div>\n    </td>\n  </motion.tr>\n);\n\n// Main AnimatedTable Component\nexport function AnimatedTable<T extends { id: string | number }>({\n  data,\n  columns,\n  selectable = false,\n  selectedIds = [],\n  onSelectionChange,\n  onRowClick,\n  sortColumn,\n  sortDirection,\n  onSort,\n  striped = false,\n  stickyHeader = false,\n  className,\n  emptyMessage,\n  loading = false,\n  loadingRows = 5,\n  // New features\n  searchable = false,\n  searchValue = \"\",\n  onSearchChange,\n  searchPlaceholder,\n  pagination,\n  expandable = false,\n  renderExpandedRow,\n  columnVisibility = false,\n  visibleColumns: controlledVisibleColumns,\n  onVisibleColumnsChange,\n}: AnimatedTableProps<T>) {\n  const [expandedRows, setExpandedRows] = React.useState<Set<string | number>>(\n    new Set(),\n  );\n  const [internalVisibleColumns, setInternalVisibleColumns] = React.useState<\n    string[]\n  >(columns.map((col) => col.id));\n\n  const visibleColumns = controlledVisibleColumns || internalVisibleColumns;\n  const setVisibleColumns = onVisibleColumnsChange || setInternalVisibleColumns;\n\n  const displayedColumns = columns.filter((col) =>\n    visibleColumns.includes(col.id),\n  );\n\n  const allSelected = data.length > 0 && selectedIds.length === data.length;\n  const someSelected =\n    selectedIds.length > 0 && selectedIds.length < data.length;\n\n  const handleSelectAll = () => {\n    if (onSelectionChange) {\n      if (allSelected) {\n        onSelectionChange([]);\n      } else {\n        onSelectionChange(data.map((row) => row.id));\n      }\n    }\n  };\n\n  const handleSelectRow = (id: string | number) => {\n    if (onSelectionChange) {\n      if (selectedIds.includes(id)) {\n        onSelectionChange(\n          selectedIds.filter((selectedId) => selectedId !== id),\n        );\n      } else {\n        onSelectionChange([...selectedIds, id]);\n      }\n    }\n  };\n\n  const handleSort = (columnId: string) => {\n    if (onSort) {\n      let newDirection: SortDirection;\n      if (sortColumn !== columnId) {\n        newDirection = \"asc\";\n      } else if (sortDirection === \"asc\") {\n        newDirection = \"desc\";\n      } else if (sortDirection === \"desc\") {\n        newDirection = null;\n      } else {\n        newDirection = \"asc\";\n      }\n      onSort(columnId, newDirection);\n    }\n  };\n\n  const toggleRowExpanded = (id: string | number) => {\n    setExpandedRows((prev) => {\n      const next = new Set(prev);\n      if (next.has(id)) {\n        next.delete(id);\n      } else {\n        next.add(id);\n      }\n      return next;\n    });\n  };\n\n  const extraColumns = (selectable ? 1 : 0) + (expandable ? 1 : 0);\n  const totalColumns = displayedColumns.length + extraColumns;\n\n  const showToolbar = searchable || columnVisibility;\n\n  return (\n    <AnimatedTableRoot className={className}>\n      {/* Toolbar */}\n      {showToolbar && (\n        <div className=\"flex flex-wrap items-center justify-between gap-3 border-table-border border-b bg-table-header p-3\">\n          {searchable && (\n            <div className=\"w-full sm:w-64\">\n              <TableSearch\n                value={searchValue}\n                onChange={onSearchChange || (() => {})}\n                placeholder={searchPlaceholder}\n              />\n            </div>\n          )}\n          <div className=\"flex items-center gap-2\">\n            {columnVisibility && (\n              <ColumnVisibilityDropdown\n                columns={columns}\n                visibleColumns={visibleColumns}\n                onChange={setVisibleColumns}\n              />\n            )}\n          </div>\n        </div>\n      )}\n\n      {/* Table */}\n      <TableScrollContainer stickyHeader={stickyHeader}>\n        <TableElement>\n          <TableHeader sticky={stickyHeader}>\n            <tr className=\"border-table-border border-b\">\n              {expandable && <TableHead className=\"w-10\" />}\n              {selectable && (\n                <TableHead className=\"w-12\">\n                  <CheckboxCell\n                    checked={allSelected}\n                    indeterminate={someSelected}\n                    onChange={handleSelectAll}\n                  />\n                </TableHead>\n              )}\n              {displayedColumns.map((column) => (\n                <TableHead\n                  key={column.id}\n                  sortable={column.sortable && !!onSort}\n                  sortDirection={\n                    sortColumn === column.id ? sortDirection : null\n                  }\n                  onSort={() => handleSort(column.id)}\n                  align={column.align}\n                  style={{ width: column.width }}\n                >\n                  {column.header}\n                </TableHead>\n              ))}\n            </tr>\n          </TableHeader>\n          <TableBody>\n            <AnimatePresence mode=\"popLayout\">\n              {loading ? (\n                Array.from({ length: loadingRows }).map((_, index) => (\n                  <SkeletonRow\n                    key={`skeleton-${index}`}\n                    columns={totalColumns}\n                    index={index}\n                  />\n                ))\n              ) : data.length === 0 ? (\n                <EmptyState message={emptyMessage} colSpan={totalColumns} />\n              ) : (\n                data.map((row, index) => (\n                  <React.Fragment key={row.id}>\n                    <TableRow\n                      isSelected={selectedIds.includes(row.id)}\n                      striped={striped}\n                      index={index}\n                      onClick={() => onRowClick?.(row)}\n                      className={onRowClick ? \"cursor-pointer\" : undefined}\n                    >\n                      {expandable && (\n                        <TableCell className=\"w-10\">\n                          <ExpandButton\n                            isExpanded={expandedRows.has(row.id)}\n                            onClick={() => toggleRowExpanded(row.id)}\n                          />\n                        </TableCell>\n                      )}\n                      {selectable && (\n                        <TableCell className=\"w-12\">\n                          <CheckboxCell\n                            checked={selectedIds.includes(row.id)}\n                            onChange={() => handleSelectRow(row.id)}\n                          />\n                        </TableCell>\n                      )}\n                      {displayedColumns.map((column) => (\n                        <TableCell key={column.id} align={column.align}>\n                          {column.cell\n                            ? column.cell(row, index)\n                            : column.accessorKey\n                              ? String(row[column.accessorKey] ?? \"\")\n                              : null}\n                        </TableCell>\n                      ))}\n                    </TableRow>\n                    <AnimatePresence>\n                      {expandable &&\n                        expandedRows.has(row.id) &&\n                        renderExpandedRow && (\n                          <ExpandedRow colSpan={totalColumns}>\n                            {renderExpandedRow(row)}\n                          </ExpandedRow>\n                        )}\n                    </AnimatePresence>\n                  </React.Fragment>\n                ))\n              )}\n            </AnimatePresence>\n          </TableBody>\n        </TableElement>\n      </TableScrollContainer>\n\n      {/* Pagination */}\n      {pagination && <TablePagination {...pagination} />}\n    </AnimatedTableRoot>\n  );\n}\n\nexport {\n  AnimatedTableRoot,\n  CheckboxCell,\n  ColumnVisibilityDropdown,\n  EmptyState,\n  ExpandButton,\n  ExpandedRow,\n  SkeletonRow,\n  TableBody,\n  TableCell,\n  TableElement,\n  TableHead,\n  TableHeader,\n  TablePagination,\n  TableRow,\n  TableScrollContainer,\n  TableSearch,\n};\n",
      "type": "registry:ui",
      "target": ""
    }
  ],
  "type": "registry:ui"
}