{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "resizable-table",
  "title": "Resizable table",
  "description": "Resizable table is a production-ready tables component from BJORK UI, packaged for shadcn registries.",
  "dependencies": [
    "framer-motion",
    "lucide-react",
    "next-themes",
    "react-resizable"
  ],
  "files": [
    {
      "path": "components/bjork-ui/tables/resizable-table.tsx",
      "content": "\"use client\";\n\nimport { useState, useEffect, useMemo } from \"react\";\nimport { motion, useReducedMotion, AnimatePresence } from \"framer-motion\";\nimport { useTheme } from \"next-themes\";\nimport { Download, ChevronDown } from \"lucide-react\";\nimport { Resizable } from \"react-resizable\";\nimport \"react-resizable/css/styles.css\";\nimport {\n  type BjorkTableThemeMode,\n  getBjorkSignalPalette,\n  getBjorkTablePalette,\n  useBjorkTableIsDark,\n} from \"./table-theme\";\n\nexport interface Employee {\n  id: string;\n  name: string;\n  email: string;\n  department: string;\n  position: string;\n  salary: number;\n  hireDate: string;\n  status: \"active\" | \"inactive\" | \"on-leave\";\n  avatar?: string;\n}\n\ninterface ResizableTableProps {\n  title?: string;\n  employees?: Employee[];\n  onEmployeeSelect?: (employeeId: string) => void;\n  onColumnResize?: (columnKey: string, newWidth: number) => void;\n  className?: string;\n  theme?: BjorkTableThemeMode;\n  enableAnimations?: boolean;\n}\n\nconst defaultEmployees: Employee[] = [\n  {\n    id: \"1\",\n    name: \"Sarah Chen\",\n    email: \"sarah.chen@company.com\",\n    department: \"Engineering\",\n    position: \"Senior Software Engineer\",\n    salary: 125000,\n    hireDate: \"2022-03-15\",\n    status: \"active\"\n  },\n  {\n    id: \"2\",\n    name: \"Michael Rodriguez\",\n    email: \"michael.rodriguez@company.com\",\n    department: \"Marketing\",\n    position: \"Marketing Manager\",\n    salary: 95000,\n    hireDate: \"2021-08-22\",\n    status: \"active\"\n  },\n  {\n    id: \"3\",\n    name: \"Emily Watson\",\n    email: \"emily.watson@company.com\",\n    department: \"Design\",\n    position: \"UX Designer\",\n    salary: 88000,\n    hireDate: \"2023-01-10\",\n    status: \"active\"\n  },\n  {\n    id: \"4\",\n    name: \"David Kim\",\n    email: \"david.kim@company.com\",\n    department: \"Engineering\",\n    position: \"Tech Lead\",\n    salary: 145000,\n    hireDate: \"2020-11-05\",\n    status: \"active\"\n  },\n  {\n    id: \"5\",\n    name: \"Lisa Anderson\",\n    email: \"lisa.anderson@company.com\",\n    department: \"HR\",\n    position: \"HR Director\",\n    salary: 110000,\n    hireDate: \"2019-06-12\",\n    status: \"on-leave\"\n  },\n  {\n    id: \"6\",\n    name: \"James Mitchell\",\n    email: \"james.mitchell@company.com\",\n    department: \"Sales\",\n    position: \"Sales Director\",\n    salary: 130000,\n    hireDate: \"2021-02-28\",\n    status: \"active\"\n  },\n  {\n    id: \"7\",\n    name: \"Jennifer Lee\",\n    email: \"jennifer.lee@company.com\",\n    department: \"Finance\",\n    position: \"Financial Analyst\",\n    salary: 75000,\n    hireDate: \"2023-04-18\",\n    status: \"active\"\n  },\n  {\n    id: \"8\",\n    name: \"Robert Chang\",\n    email: \"robert.chang@company.com\",\n    department: \"Engineering\",\n    position: \"DevOps Engineer\",\n    salary: 105000,\n    hireDate: \"2022-09-14\",\n    status: \"active\"\n  },\n  {\n    id: \"9\",\n    name: \"Amanda Pierce\",\n    email: \"amanda.pierce@company.com\",\n    department: \"Marketing\",\n    position: \"Content Manager\",\n    salary: 72000,\n    hireDate: \"2023-07-03\",\n    status: \"inactive\"\n  },\n  {\n    id: \"10\",\n    name: \"Christopher Hayes\",\n    email: \"chris.hayes@company.com\",\n    department: \"Operations\",\n    position: \"Operations Manager\",\n    salary: 98000,\n    hireDate: \"2021-12-01\",\n    status: \"active\"\n  },\n  {\n    id: \"11\",\n    name: \"Victoria Moore\",\n    email: \"victoria.moore@company.com\",\n    department: \"Design\",\n    position: \"Product Designer\",\n    salary: 92000,\n    hireDate: \"2022-05-20\",\n    status: \"active\"\n  },\n  {\n    id: \"12\",\n    name: \"Nicholas Brown\",\n    email: \"nicholas.brown@company.com\",\n    department: \"Engineering\",\n    position: \"Frontend Developer\",\n    salary: 85000,\n    hireDate: \"2023-03-08\",\n    status: \"active\"\n  },\n  {\n    id: \"13\",\n    name: \"Rebecca Sullivan\",\n    email: \"rebecca.sullivan@company.com\",\n    department: \"Sales\",\n    position: \"Account Executive\",\n    salary: 78000,\n    hireDate: \"2022-11-15\",\n    status: \"active\"\n  },\n  {\n    id: \"14\",\n    name: \"Thomas Wright\",\n    email: \"thomas.wright@company.com\",\n    department: \"Finance\",\n    position: \"Senior Financial Analyst\",\n    salary: 95000,\n    hireDate: \"2021-04-30\",\n    status: \"active\"\n  },\n  {\n    id: \"15\",\n    name: \"Maria Garcia\",\n    email: \"maria.garcia@company.com\",\n    department: \"HR\",\n    position: \"HR Specialist\",\n    salary: 68000,\n    hireDate: \"2023-08-12\",\n    status: \"active\"\n  }\n];\n\ntype SortField = \"name\" | \"department\" | \"position\" | \"salary\" | \"hireDate\";\ntype SortOrder = \"asc\" | \"desc\";\n\nexport function ResizableTable({\n  title = \"Employee\",\n  employees: initialEmployees = defaultEmployees,\n  onEmployeeSelect,\n  onColumnResize,\n  className = \"\",\n  theme = \"auto\",\n  enableAnimations = true\n}: ResizableTableProps = {}) {\n  const [selectedEmployees, setSelectedEmployees] = useState<string[]>([]);\n  const [mounted, setMounted] = useState(false);\n  const [currentPage, setCurrentPage] = useState(1);\n  const [sortField, setSortField] = useState<SortField | null>(null);\n  const [sortOrder, setSortOrder] = useState<SortOrder>(\"asc\");\n  const [showSortMenu, setShowSortMenu] = useState(false);\n  const [showExportMenu, setShowExportMenu] = useState(false);\n  \n  const shouldReduceMotion = useReducedMotion();\n  const { resolvedTheme } = useTheme();\n  const forcedTheme = theme === \"auto\" ? undefined : theme;\n  const isDark = useBjorkTableIsDark(resolvedTheme, forcedTheme);\n  const palette = getBjorkTablePalette(isDark);\n\n  // Column width state with default values\n  const [columnWidths, setColumnWidths] = useState<Record<string, number>>({\n    checkbox: 50,\n    name: 200,\n    email: 220,\n    department: 140,\n    position: 180,\n    salary: 120,\n    hireDate: 120,\n    status: 100\n  });\n\n  const ITEMS_PER_PAGE = 10;\n\n  useEffect(() => {\n    setMounted(true);\n  }, []);\n\n  const handleEmployeeSelect = (employeeId: string) => {\n    setSelectedEmployees(prev => {\n      if (prev.includes(employeeId)) {\n        return prev.filter(id => id !== employeeId);\n      } else {\n        return [...prev, employeeId];\n      }\n    });\n    if (onEmployeeSelect) {\n      onEmployeeSelect(employeeId);\n    }\n  };\n\n  const handleSelectAll = () => {\n    if (selectedEmployees.length === paginatedEmployees.length) {\n      setSelectedEmployees([]);\n    } else {\n      setSelectedEmployees(paginatedEmployees.map(e => e.id));\n    }\n  };\n\n  const handleSort = (field: SortField) => {\n    if (sortField === field) {\n      setSortOrder(sortOrder === \"asc\" ? \"desc\" : \"asc\");\n    } else {\n      setSortField(field);\n      setSortOrder(\"asc\");\n    }\n    setShowSortMenu(false);\n    setCurrentPage(1);\n  };\n\n  const sortedEmployees = useMemo(() => {\n    if (!sortField) {\n      return initialEmployees;\n    }\n\n    return [...initialEmployees].sort((a, b) => {\n      let aVal: string | number = a[sortField];\n      let bVal: string | number = b[sortField];\n\n      if (sortField === \"hireDate\") {\n        aVal = new Date(a.hireDate).getTime();\n        bVal = new Date(b.hireDate).getTime();\n      }\n\n      if (aVal < bVal) return sortOrder === \"asc\" ? -1 : 1;\n      if (aVal > bVal) return sortOrder === \"asc\" ? 1 : -1;\n      return 0;\n    });\n  }, [initialEmployees, sortField, sortOrder]);\n\n  const paginatedEmployees = useMemo(() => {\n    const startIdx = (currentPage - 1) * ITEMS_PER_PAGE;\n    return sortedEmployees.slice(startIdx, startIdx + ITEMS_PER_PAGE);\n  }, [sortedEmployees, currentPage]);\n\n  const totalPages = Math.ceil(sortedEmployees.length / ITEMS_PER_PAGE);\n\n  const getStatusColor = (status: string) => {\n    if (!mounted && theme === \"auto\") {\n      const statusMap: Record<string, { bgColor: string; borderColor: string; textColor: string; dotColor: string }> = {\n        \"active\": getBjorkSignalPalette(\"green\", true),\n        \"inactive\": getBjorkSignalPalette(\"red\", true),\n        \"on-leave\": getBjorkSignalPalette(\"orange\", true),\n      };\n      return statusMap[status];\n    }\n\n    const statusMap: Record<string, { bgColor: string; borderColor: string; textColor: string; dotColor: string }> = {\n      \"active\": getBjorkSignalPalette(\"green\", isDark),\n      \"inactive\": getBjorkSignalPalette(\"red\", isDark),\n      \"on-leave\": getBjorkSignalPalette(\"orange\", isDark),\n    };\n\n    return statusMap[status];\n  };\n\n  const formatCurrency = (amount: number) => {\n    return new Intl.NumberFormat('en-US', {\n      style: 'currency',\n      currency: 'USD',\n      minimumFractionDigits: 0,\n      maximumFractionDigits: 0,\n    }).format(amount);\n  };\n\n  const formatDate = (dateString: string) => {\n    return new Date(dateString).toLocaleDateString('en-US', {\n      year: 'numeric',\n      month: 'short',\n      day: 'numeric'\n    });\n  };\n\n  const handleResize = (columnKey: string, { size }: { size: { width: number } }) => {\n    const newWidth = Math.max(80, Math.min(400, size.width));\n    \n    setColumnWidths(prev => ({\n      ...prev,\n      [columnKey]: newWidth\n    }));\n    \n    if (onColumnResize) {\n      onColumnResize(columnKey, newWidth);\n    }\n  };\n\n  const exportToCSV = () => {\n    const headers = [\"Name\", \"Email\", \"Department\", \"Position\", \"Salary\", \"Hire Date\", \"Status\"];\n    const rows = sortedEmployees.map(employee => [\n      employee.name,\n      employee.email,\n      employee.department,\n      employee.position,\n      employee.salary,\n      employee.hireDate,\n      employee.status\n    ]);\n\n    const csvContent = [\n      headers.join(\",\"),\n      ...rows.map(row => row.map(cell => `\"${String(cell).replace(/\"/g, '\"\"')}\"`).join(\",\"))\n    ].join(\"\\n\");\n\n    const blob = new Blob([csvContent], { type: \"text/csv;charset=utf-8;\" });\n    const link = document.createElement(\"a\");\n    link.href = URL.createObjectURL(blob);\n    link.download = `employees-${new Date().toISOString().split('T')[0]}.csv`;\n    link.click();\n  };\n\n  const exportToJSON = () => {\n    const jsonContent = JSON.stringify(sortedEmployees, null, 2);\n    const blob = new Blob([jsonContent], { type: \"application/json;charset=utf-8;\" });\n    const link = document.createElement(\"a\");\n    link.href = URL.createObjectURL(blob);\n    link.download = `employees-${new Date().toISOString().split('T')[0]}.json`;\n    link.click();\n  };\n\n  const shouldAnimate = enableAnimations && !shouldReduceMotion;\n\n  const containerVariants = {\n    visible: {\n      transition: {\n        staggerChildren: 0.04,\n        delayChildren: 0.1,\n      },\n    }\n  };\n\n  const rowVariants = {\n    hidden: { \n      opacity: 0, \n      y: 20,\n      scale: 0.98,\n      filter: \"blur(4px)\" \n    },\n    visible: {\n      opacity: 1,\n      y: 0,\n      scale: 1,\n      filter: \"blur(0px)\",\n      transition: {\n        type: \"spring\",\n        stiffness: 400,\n        damping: 25,\n        mass: 0.7,\n      },\n    },\n    exit: {\n      opacity: 0,\n      y: -10,\n      transition: { duration: 0.2 }\n    }\n  };\n\n\n  return (\n    <div className={`mx-auto w-full max-w-[1040px] ${className}`}>\n      <div className=\"mb-4 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3\">\n        <div className=\"flex items-center gap-2\">\n        </div>\n        \n        <div className=\"flex items-center gap-2 flex-wrap\">\n          <div className=\"relative\">\n            <button \n              onClick={() => setShowSortMenu(!showSortMenu)}\n              className={`flex items-center gap-2 rounded-md border px-3 py-1.5 text-sm transition-colors ${palette.control}`}\n            >\n              <svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\">\n                <path d=\"M3 6L6 3L9 6M6 3V13M13 10L10 13L7 10M10 13V3\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\"/>\n              </svg>\n              Sort {sortField && <span className={`ml-1 rounded-sm px-1.5 py-0.5 text-xs ${palette.accentBg} ${palette.accent}`}>1</span>}\n              <ChevronDown size={14} className=\"opacity-50\" />\n            </button>\n            \n            {showSortMenu && (\n              <>\n                <div \n                  className=\"fixed inset-0 z-10\"\n                  onClick={() => setShowSortMenu(false)}\n                />\n                <div className={`absolute right-0 z-20 mt-1 w-48 rounded-md border py-1 ${palette.menu}`}>\n                  <button\n                    onClick={() => handleSort(\"name\")}\n                    className={`w-full px-3 py-2 text-left text-sm transition-colors ${palette.menuItem} ${sortField === \"name\" ? palette.menuActive : ''}`}\n                  >\n                    Name {sortField === \"name\" && `(${sortOrder === \"asc\" ? \"A-Z\" : \"Z-A\"})`}\n                  </button>\n                  <button\n                    onClick={() => handleSort(\"department\")}\n                    className={`w-full px-3 py-2 text-left text-sm transition-colors ${palette.menuItem} ${sortField === \"department\" ? palette.menuActive : ''}`}\n                  >\n                    Department {sortField === \"department\" && `(${sortOrder === \"asc\" ? \"↑\" : \"↓\"})`}\n                  </button>\n                  <button\n                    onClick={() => handleSort(\"salary\")}\n                    className={`w-full px-3 py-2 text-left text-sm transition-colors ${palette.menuItem} ${sortField === \"salary\" ? palette.menuActive : ''}`}\n                  >\n                    Salary {sortField === \"salary\" && `(${sortOrder === \"asc\" ? \"↑\" : \"↓\"})`}\n                  </button>\n                  <button\n                    onClick={() => handleSort(\"hireDate\")}\n                    className={`w-full px-3 py-2 text-left text-sm transition-colors ${palette.menuItem} ${sortField === \"hireDate\" ? palette.menuActive : ''}`}\n                  >\n                    Hire Date {sortField === \"hireDate\" && `(${sortOrder === \"asc\" ? \"↑\" : \"↓\"})`}\n                  </button>\n                </div>\n              </>\n            )}\n          </div>\n\n          <div className=\"relative\">\n            <button \n              onClick={() => setShowExportMenu(!showExportMenu)}\n              className={`flex items-center gap-2 rounded-md border px-3 py-1.5 text-sm transition-colors ${palette.control}`}\n            >\n              <Download size={14} />\n              Export\n              <ChevronDown size={14} className=\"opacity-50\" />\n            </button>\n            \n            {showExportMenu && (\n              <>\n                <div \n                  className=\"fixed inset-0 z-10\"\n                  onClick={() => setShowExportMenu(false)}\n                />\n                <div className={`absolute right-0 z-20 mt-1 w-32 rounded-md border ${palette.menu}`}>\n                  <button\n                    onClick={() => {\n                      exportToCSV();\n                      setShowExportMenu(false);\n                    }}\n                    className={`flex w-full items-center gap-2 px-3 py-2 text-left text-sm transition-colors ${palette.menuItem}`}\n                  >\n                    CSV\n                  </button>\n                  <button\n                    onClick={() => {\n                      exportToJSON();\n                      setShowExportMenu(false);\n                    }}\n                    className={`flex w-full items-center gap-2 border-t px-3 py-2 text-left text-sm transition-colors ${palette.divider} ${palette.menuItem}`}\n                  >\n                    JSON\n                  </button>\n                </div>\n              </>\n            )}\n          </div>\n        </div>\n      </div>\n\n      <div className={`relative overflow-hidden rounded-[18px] border ${palette.container}`}>\n        <div className=\"overflow-x-auto\">\n          <div className=\"min-w-[1200px]\">\n            <div className={`flex border-b py-3 text-xs font-medium uppercase tracking-wide ${palette.header}`}>\n              <div className={`flex items-center justify-center border-r pr-3 ${palette.divider}`} style={{ width: columnWidths.checkbox }}>\n                <input \n                  type=\"checkbox\" \n                  className=\"w-4 h-4 rounded border-border/40 cursor-pointer\"\n                  style={mounted ? {\n                    accentColor: palette.checkboxAccent\n                  } : {}}\n                  checked={paginatedEmployees.length > 0 && selectedEmployees.length === paginatedEmployees.length}\n                  onChange={handleSelectAll}\n                />\n              </div>\n              \n              <Resizable\n                width={columnWidths.name}\n                height={0}\n                onResize={(e, data) => handleResize('name', data)}\n                minConstraints={[80, 0]}\n                maxConstraints={[400, 0]}\n                handle={<div className=\"absolute bottom-0 right-0 top-0 w-1 cursor-col-resize bg-transparent transition-[background-color,border-color,color,opacity,transform] hover:w-1.5 hover:bg-[#ec5c13]/45\" />}\n              >\n                <div className={`relative flex items-center border-r px-3 ${palette.divider}`} style={{ width: columnWidths.name }}>\n                  <span>{title}</span>\n                </div>\n              </Resizable>\n\n              <Resizable\n                width={columnWidths.email}\n                height={0}\n                onResize={(e, data) => handleResize('email', data)}\n                minConstraints={[80, 0]}\n                maxConstraints={[400, 0]}\n                handle={<div className=\"absolute bottom-0 right-0 top-0 w-1 cursor-col-resize bg-transparent transition-[background-color,border-color,color,opacity,transform] hover:w-1.5 hover:bg-[#ec5c13]/45\" />}\n              >\n                <div className={`relative flex items-center border-r px-3 ${palette.divider}`} style={{ width: columnWidths.email }}>\n                  <span>Email</span>\n                </div>\n              </Resizable>\n\n              <Resizable\n                width={columnWidths.department}\n                height={0}\n                onResize={(e, data) => handleResize('department', data)}\n                minConstraints={[80, 0]}\n                maxConstraints={[400, 0]}\n                handle={<div className=\"absolute bottom-0 right-0 top-0 w-1 cursor-col-resize bg-transparent transition-[background-color,border-color,color,opacity,transform] hover:w-1.5 hover:bg-[#ec5c13]/45\" />}\n              >\n                <div className={`relative flex items-center border-r px-3 ${palette.divider}`} style={{ width: columnWidths.department }}>\n                  <span>Department</span>\n                </div>\n              </Resizable>\n\n              <Resizable\n                width={columnWidths.position}\n                height={0}\n                onResize={(e, data) => handleResize('position', data)}\n                minConstraints={[80, 0]}\n                maxConstraints={[400, 0]}\n                handle={<div className=\"absolute bottom-0 right-0 top-0 w-1 cursor-col-resize bg-transparent transition-[background-color,border-color,color,opacity,transform] hover:w-1.5 hover:bg-[#ec5c13]/45\" />}\n              >\n                <div className={`relative flex items-center border-r px-3 ${palette.divider}`} style={{ width: columnWidths.position }}>\n                  <span>Position</span>\n                </div>\n              </Resizable>\n\n              <Resizable\n                width={columnWidths.salary}\n                height={0}\n                onResize={(e, data) => handleResize('salary', data)}\n                minConstraints={[80, 0]}\n                maxConstraints={[400, 0]}\n                handle={<div className=\"absolute bottom-0 right-0 top-0 w-1 cursor-col-resize bg-transparent transition-[background-color,border-color,color,opacity,transform] hover:w-1.5 hover:bg-[#ec5c13]/45\" />}\n              >\n                <div className={`relative flex items-center border-r px-3 ${palette.divider}`} style={{ width: columnWidths.salary }}>\n                  <span>Salary</span>\n                </div>\n              </Resizable>\n\n              <Resizable\n                width={columnWidths.hireDate}\n                height={0}\n                onResize={(e, data) => handleResize('hireDate', data)}\n                minConstraints={[80, 0]}\n                maxConstraints={[400, 0]}\n                handle={<div className=\"absolute bottom-0 right-0 top-0 w-1 cursor-col-resize bg-transparent transition-[background-color,border-color,color,opacity,transform] hover:w-1.5 hover:bg-[#ec5c13]/45\" />}\n              >\n                <div className={`relative flex items-center border-r px-3 ${palette.divider}`} style={{ width: columnWidths.hireDate }}>\n                  <span>Hire Date</span>\n                </div>\n              </Resizable>\n\n              <div className=\"flex items-center px-3\" style={{ width: columnWidths.status }}>\n                <span>Status</span>\n              </div>\n\n\n            </div>\n\n            <AnimatePresence mode=\"wait\">\n              <motion.div\n                key={`page-${currentPage}`}\n                variants={shouldAnimate ? containerVariants : {}}\n                initial={shouldAnimate ? \"hidden\" : \"visible\"}\n                animate=\"visible\"\n              >\n                {paginatedEmployees.map((employee) => (\n                  <motion.div key={employee.id} variants={shouldAnimate ? rowVariants : {}}>\n                    <div\n                      className={`group relative flex border-b py-3.5 transition-[background-color,border-color,color,opacity,transform] duration-150 ${\n                        selectedEmployees.includes(employee.id)\n                          ? palette.selectedRow\n                          : palette.row\n                      }`}\n                    >\n                      <div className={`flex items-center justify-center border-r pr-3 ${palette.divider}`} style={{ width: columnWidths.checkbox }}>\n                        <input \n                          type=\"checkbox\" \n                          className=\"w-4 h-4 rounded border-border/40 cursor-pointer\"\n                          style={mounted ? {\n                            accentColor: palette.checkboxAccent\n                          } : {}}\n                          checked={selectedEmployees.includes(employee.id)}\n                          onChange={() => handleEmployeeSelect(employee.id)}\n                        />\n                      </div>\n\n                      <div className={`flex min-w-0 items-center border-r px-3 ${palette.divider}`} style={{ width: columnWidths.name }}>\n                        <span className={`truncate text-sm ${palette.primaryText}`}>{employee.name}</span>\n                      </div>\n\n                      <div className={`flex min-w-0 items-center border-r px-3 ${palette.divider}`} style={{ width: columnWidths.email }}>\n                        <a \n                          href={`mailto:${employee.email}`}\n                          className={`truncate text-sm ${palette.accent}`}\n                          onClick={(e) => e.stopPropagation()}\n                        >\n                          {employee.email}\n                        </a>\n                      </div>\n\n                      <div className={`flex items-center border-r px-3 ${palette.divider}`} style={{ width: columnWidths.department }}>\n                        <span className={`truncate text-sm ${palette.primaryText}`}>\n                          {employee.department}\n                        </span>\n                      </div>\n\n                      <div className={`flex min-w-0 items-center border-r px-3 ${palette.divider}`} style={{ width: columnWidths.position }}>\n                        <span className={`truncate text-sm ${palette.primaryText}`}>\n                          {employee.position}\n                        </span>\n                      </div>\n\n                      <div className={`flex items-center border-r px-3 ${palette.divider}`} style={{ width: columnWidths.salary }}>\n                        <span className={`text-sm font-semibold ${palette.primaryText}`}>\n                          {formatCurrency(employee.salary)}\n                        </span>\n                      </div>\n\n                      <div className={`flex items-center border-r px-3 ${palette.divider}`} style={{ width: columnWidths.hireDate }}>\n                        <span className={`text-sm ${palette.primaryText}`}>\n                          {formatDate(employee.hireDate)}\n                        </span>\n                      </div>\n\n                      <div className=\"flex items-center px-3\" style={{ width: columnWidths.status }}>\n                        {(() => {\n                          const { bgColor, borderColor, textColor, dotColor } = getStatusColor(employee.status);\n                          return (\n                            <div className={`inline-flex items-center gap-1.5 whitespace-nowrap rounded-md border px-2.5 py-1 text-xs font-medium ${bgColor} ${borderColor} ${textColor}`}>\n                              <div className={`w-1.5 h-1.5 rounded-full ${dotColor}`}></div>\n                              {employee.status.charAt(0).toUpperCase() + employee.status.slice(1).replace('-', ' ')}\n                            </div>\n                          );\n                        })()}\n                      </div>\n\n\n                    </div>\n                  </motion.div>\n                ))}\n              </motion.div>\n            </AnimatePresence>\n          </div>\n        </div>\n\n      </div>\n\n      {totalPages > 1 && (\n        <div className=\"mt-4 flex items-center justify-between px-2\">\n          <div className=\"text-xs tabular-nums text-muted-foreground/70\">\n            Page {currentPage} of {totalPages} • {sortedEmployees.length} employees\n          </div>\n          \n          <div className=\"flex gap-1.5\">\n            <button\n              onClick={() => setCurrentPage(prev => Math.max(1, prev - 1))}\n              disabled={currentPage === 1}\n              className={`rounded-md border px-3 py-1.5 text-xs transition-colors disabled:cursor-not-allowed disabled:opacity-40 ${palette.control}`}\n            >\n              Previous\n            </button>\n            <button\n              onClick={() => setCurrentPage(prev => Math.min(totalPages, prev + 1))}\n              disabled={currentPage === totalPages}\n              className={`rounded-md border px-3 py-1.5 text-xs transition-colors disabled:cursor-not-allowed disabled:opacity-40 ${palette.control}`}\n            >\n              Next\n            </button>\n          </div>\n        </div>\n      )}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/bjork-ui/tables/resizable-table.tsx"
    },
    {
      "path": "components/bjork-ui/tables/table-theme.ts",
      "content": "import { useEffect, useState } from \"react\";\n\nexport type BjorkTableThemeMode = \"light\" | \"dark\" | \"auto\";\n\nexport function useBjorkTableIsDark(\n  resolvedTheme?: string,\n  forcedTheme?: Exclude<BjorkTableThemeMode, \"auto\">\n) {\n  const [documentTheme, setDocumentTheme] = useState<\"light\" | \"dark\" | null>(null);\n  const [previewTheme, setPreviewTheme] = useState<\"light\" | \"dark\" | null>(null);\n  // Server and first client render must agree, otherwise React keeps the\n  // server-rendered (dark) classes when the real theme resolves to light.\n  const [mounted, setMounted] = useState(false);\n\n  useEffect(() => {\n    setMounted(true);\n    const syncDocumentTheme = () => {\n      const root = document.documentElement;\n      const searchParams = new URLSearchParams(window.location.search);\n      const theme = searchParams.get(\"theme\");\n\n      setPreviewTheme(theme === \"light\" || theme === \"dark\" ? theme : null);\n\n      setDocumentTheme(\n        root.classList.contains(\"light\")\n          ? \"light\"\n          : root.classList.contains(\"dark\")\n            ? \"dark\"\n            : null\n      );\n    };\n\n    syncDocumentTheme();\n\n    const observer = new MutationObserver(syncDocumentTheme);\n    observer.observe(document.documentElement, {\n      attributes: true,\n      attributeFilter: [\"class\"],\n    });\n\n    return () => observer.disconnect();\n  }, []);\n\n  const activeTheme =\n    forcedTheme ?? previewTheme ?? documentTheme ?? (mounted ? resolvedTheme : undefined);\n\n  return activeTheme !== \"light\";\n}\n\nexport function getBjorkTablePalette(isDark: boolean) {\n  return {\n    container: isDark\n      ? \"border-[#1c1c1c] bg-[#121212] shadow-[inset_0_7px_14px_rgba(255,255,255,0.03),inset_0_0.5px_0.5px_rgba(255,255,255,0.06),0_14px_20px_-6px_rgba(0,0,0,0.45)]\"\n      : \"border-[#f5ede2] bg-[#fffcf6] shadow-[inset_0_7px_14px_rgba(88,72,49,0.045),inset_0_0.5px_0.5px_rgba(255,255,255,0.92),inset_1px_0_0_rgba(88,72,49,0.026),inset_-1px_0_0_rgba(255,255,255,0.68),0_14px_22px_-9px_rgba(66,52,33,0.11)]\",\n    header: isDark\n      ? \"border-[#232323] bg-[#181818] text-[#ededed]/42\"\n      : \"border-[#f1e8dc] bg-[#fbf7ef] text-[#171717]/60\",\n    row: isDark\n      ? \"border-[#232323] hover:bg-[#181818]/70\"\n      : \"border-[#f1e8dc] hover:bg-[#f8f2e7]/72\",\n    selectedRow: isDark\n      ? \"border-[#2a2a2a] bg-[#181818]\"\n      : \"border-[#eadfce] bg-[#f8f2e7]\",\n    control: isDark\n      ? \"border-[#232323] bg-[#181818] text-[#ededed]/70 hover:bg-[#202020] shadow-[inset_0_1px_0_rgba(255,255,255,0.045)]\"\n      : \"border-[#eadfce] bg-[#fffcf6] text-[#171717]/68 shadow-[var(--bjork-shadow-soft)] hover:bg-[#f8f2e7]\",\n    menu: isDark\n      ? \"border-[#232323] bg-[#181818] text-[#ededed]/76 shadow-[inset_0_1px_0_rgba(255,255,255,0.055),inset_0_12px_24px_rgba(255,255,255,0.018),0_22px_42px_-22px_rgba(0,0,0,0.9)]\"\n      : \"border-[#eadfce] bg-[#fffcf6] text-[#171717]/76 shadow-[inset_0_1px_0_rgba(88,72,49,0.04),inset_0_12px_24px_rgba(88,72,49,0.022),0_22px_44px_-26px_rgba(66,52,33,0.22)]\",\n    menuItem: isDark ? \"hover:bg-[#232323]\" : \"hover:bg-[#f8f2e7]\",\n    menuActive: isDark ? \"bg-[#232323]\" : \"bg-[#f8f2e7]\",\n    divider: isDark ? \"border-[#232323]\" : \"border-[#f1e8dc]\",\n    primaryText: isDark ? \"text-[#ededed]/90\" : \"text-[#171717]/86\",\n    secondaryText: isDark ? \"text-[#ededed]/38\" : \"text-[#171717]/60\",\n    mutedSurface: isDark ? \"bg-[#181818]\" : \"bg-[#f8f2e7]\",\n    popover: isDark ? \"bg-[#111] text-[#ededed]\" : \"bg-[#fffcf6] text-[#171717]\",\n    accent: isDark ? \"text-[#d86a2c]\" : \"text-[#bd4514]\",\n    accentBg: isDark ? \"bg-[#ec5c13]/12\" : \"bg-[#ec5c13]/14\",\n    accentBorder: isDark ? \"border-[#ec5c13]/24\" : \"border-[#bd4514]/22\",\n    checkboxAccent: isDark ? \"#d86a2c\" : \"#bd4514\",\n  };\n}\n\nexport function getBjorkSignalPalette(\n  tone: \"green\" | \"red\" | \"orange\" | \"neutral\",\n  isDark: boolean\n) {\n  const palettes = {\n    green: {\n      bgColor: isDark ? \"bg-[#3e6f52]/18\" : \"bg-[#dce8dc]\",\n      borderColor: isDark ? \"border-[#5f9f78]/28\" : \"border-[#bad0bd]\",\n      textColor: isDark ? \"text-[#77b58b]\" : \"text-[#47785c]\",\n      dotColor: isDark ? \"bg-[#77b58b]\" : \"bg-[#47785c]\",\n      color: isDark ? \"#77b58b\" : \"#47785c\",\n    },\n    red: {\n      bgColor: isDark ? \"bg-[#87463f]/18\" : \"bg-[#eadad5]\",\n      borderColor: isDark ? \"border-[#c46a61]/28\" : \"border-[#d7bcb4]\",\n      textColor: isDark ? \"text-[#cf7c72]\" : \"text-[#9d5149]\",\n      dotColor: isDark ? \"bg-[#cf7c72]\" : \"bg-[#9d5149]\",\n      color: isDark ? \"#cf7c72\" : \"#9d5149\",\n    },\n    orange: {\n      bgColor: isDark ? \"bg-[#ec5c13]/14\" : \"bg-[#ead9cb]\",\n      borderColor: isDark ? \"border-[#ec5c13]/28\" : \"border-[#d5b59f]\",\n      textColor: isDark ? \"text-[#d86a2c]\" : \"text-[#9f4317]\",\n      dotColor: isDark ? \"bg-[#d86a2c]\" : \"bg-[#9f4317]\",\n      color: isDark ? \"#d86a2c\" : \"#9f4317\",\n    },\n    neutral: {\n      bgColor: isDark ? \"bg-[#ededed]/8\" : \"bg-[#ded7ca]\",\n      borderColor: isDark ? \"border-[#ededed]/14\" : \"border-[#cfc6b7]\",\n      textColor: isDark ? \"text-[#ededed]/58\" : \"text-[#171717]/60\",\n      dotColor: isDark ? \"bg-[#ededed]/45\" : \"bg-[#171717]/42\",\n      color: isDark ? \"#a3a3a3\" : \"#706b62\",\n    },\n  } as const;\n\n  return palettes[tone];\n}\n",
      "type": "registry:component",
      "target": "@components/bjork-ui/tables/table-theme.ts"
    }
  ],
  "cssVars": {
    "light": {
      "bjork-bg": "#f7f3ea",
      "bjork-surface": "#fffcf6",
      "bjork-surface-muted": "rgba(250, 246, 237, 0.94)",
      "bjork-surface-hover": "#f5efe3",
      "bjork-surface-active": "#efe7d8",
      "bjork-panel": "#f8f2e7",
      "bjork-menu": "rgba(255, 252, 246, 0.98)",
      "bjork-field": "rgba(255, 252, 246, 0.96)",
      "bjork-field-muted": "rgba(248, 242, 231, 0.82)",
      "bjork-field-inset": "rgba(239, 231, 216, 0.72)",
      "bjork-error-bg": "#fff0e8",
      "bjork-text": "#171717",
      "bjork-text-strong": "rgba(23, 23, 23, 0.9)",
      "bjork-text-medium": "rgba(23, 23, 23, 0.78)",
      "bjork-text-muted": "rgba(23, 23, 23, 0.68)",
      "bjork-text-soft": "rgba(23, 23, 23, 0.62)",
      "bjork-text-faint": "rgba(23, 23, 23, 0.22)",
      "bjork-inverted-text": "#fff8f0",
      "bjork-border": "#eee6db",
      "bjork-border-muted": "#f5ede2",
      "bjork-border-strong": "#e1d7c8",
      "bjork-thumb": "#a49b8e",
      "bjork-track": "#ded6ca",
      "bjork-accent": "#ec7d43",
      "bjork-accent-hover": "#f0935f",
      "bjork-accent-soft": "rgba(236, 125, 67, 0.1)",
      "bjork-accent-muted": "rgba(236, 125, 67, 0.2)",
      "bjork-accent-badge": "rgba(236, 125, 67, 0.16)",
      "bjork-accent-badge-foreground": "#8d421d",
      "bjork-accent-foreground": "#3f2112",
      "bjork-accent-fill": "#ec7d43",
      "bjork-accent-fill-hover": "#f0935f",
      "bjork-shadow-surface": "inset 0 7px 14px rgba(88, 72, 49, 0.045), inset 0 0.5px 0.5px rgba(255, 255, 255, 0.92), inset 1px 0 0 rgba(88, 72, 49, 0.026), inset -1px 0 0 rgba(255, 255, 255, 0.68), 0 14px 22px -9px rgba(66, 52, 33, 0.11)",
      "bjork-shadow-soft": "inset 0 1px 0 rgba(88, 72, 49, 0.045), inset 0 0.5px 0.5px rgba(255, 255, 255, 0.86)",
      "bjork-shadow-panel": "inset 0 1px 0 rgba(88, 72, 49, 0.04), inset 1px 0 0 rgba(88, 72, 49, 0.024), inset -1px 0 0 rgba(255, 255, 255, 0.64), 0 18px 38px -30px rgba(66, 52, 33, 0.2)",
      "bjork-shadow-menu": "inset 0 1px 0 rgba(88, 72, 49, 0.04), inset 0 12px 24px rgba(88, 72, 49, 0.022), inset 1px 0 0 rgba(88, 72, 49, 0.024), inset -1px 0 0 rgba(255, 255, 255, 0.62), 0 22px 44px -26px rgba(66, 52, 33, 0.22)",
      "bjork-shadow-inset": "inset 0 1px 10px rgba(88, 72, 49, 0.12), inset 0 0.5px 0.5px rgba(255, 255, 255, 0.72)",
      "bjork-ring-offset": "#f7f3ea"
    },
    "dark": {
      "bjork-bg": "#111111",
      "bjork-surface": "#121212",
      "bjork-surface-muted": "rgba(22, 22, 22, 0.92)",
      "bjork-surface-hover": "#161616",
      "bjork-surface-active": "#202020",
      "bjork-panel": "#0d0d0d",
      "bjork-menu": "rgba(18, 18, 18, 0.98)",
      "bjork-field": "rgba(18, 18, 18, 0.95)",
      "bjork-field-muted": "rgba(18, 18, 18, 0.55)",
      "bjork-field-inset": "rgba(9, 9, 9, 0.75)",
      "bjork-error-bg": "#130d0a",
      "bjork-text": "#ededed",
      "bjork-text-strong": "rgba(237, 237, 237, 0.9)",
      "bjork-text-medium": "rgba(237, 237, 237, 0.72)",
      "bjork-text-muted": "rgba(237, 237, 237, 0.6)",
      "bjork-text-soft": "rgba(237, 237, 237, 0.5)",
      "bjork-text-faint": "rgba(237, 237, 237, 0.22)",
      "bjork-inverted-text": "#080808",
      "bjork-border": "#232323",
      "bjork-border-muted": "#1c1c1c",
      "bjork-border-strong": "#343434",
      "bjork-thumb": "#626262",
      "bjork-track": "#090909",
      "bjork-accent": "#ec5c13",
      "bjork-accent-hover": "#f07832",
      "bjork-accent-soft": "rgba(236, 92, 19, 0.12)",
      "bjork-accent-muted": "rgba(236, 92, 19, 0.24)",
      "bjork-accent-badge": "#b84a12",
      "bjork-accent-badge-foreground": "#fff2ea",
      "bjork-accent-foreground": "#fff2ea",
      "bjork-accent-fill": "#b84a12",
      "bjork-accent-fill-hover": "#a84a14",
      "bjork-shadow-surface": "inset 0 7px 14px rgba(255, 255, 255, 0.03), inset 0 0.5px 0.5px rgba(255, 255, 255, 0.06), 0 14px 20px -6px rgba(0, 0, 0, 0.45)",
      "bjork-shadow-soft": "inset 0 1px 0 rgba(255, 255, 255, 0.045)",
      "bjork-shadow-panel": "inset 0 1px 0 rgba(255, 255, 255, 0.035), 0 18px 36px -28px rgba(0, 0, 0, 0.9)",
      "bjork-shadow-menu": "inset 0 1px 0 rgba(255, 255, 255, 0.055), inset 0 12px 24px rgba(255, 255, 255, 0.018), inset 0 -18px 26px rgba(0, 0, 0, 0.24), 0 22px 42px -22px rgba(0, 0, 0, 0.9)",
      "bjork-shadow-inset": "inset 0 1px 10px rgba(0, 0, 0, 0.45)",
      "bjork-ring-offset": "#080808"
    }
  },
  "css": {
    ".bjork-range": {
      "box-shadow": "inset 0 1px 1px rgba(255, 255, 255, 0.04), 0 0 14px rgba(189, 69, 20, 0.06)"
    },
    ".bjork-range::-webkit-slider-thumb": {
      "appearance": "none",
      "width": "16px",
      "height": "8px",
      "border": "0",
      "border-radius": "999px",
      "background": "var(--bjork-thumb)",
      "box-shadow": "inset 0 1px 0 rgba(255, 255, 255, 0.18), 0 0 0 1px rgba(0, 0, 0, 0.42)"
    },
    ".bjork-range::-moz-range-thumb": {
      "width": "16px",
      "height": "8px",
      "border": "0",
      "border-radius": "999px",
      "background": "var(--bjork-thumb)",
      "box-shadow": "inset 0 1px 0 rgba(255, 255, 255, 0.18), 0 0 0 1px rgba(0, 0, 0, 0.42)"
    },
    ".bjork-layered-button-accent, .bjork-layered-button-raised": {
      "position": "relative",
      "isolation": "isolate",
      "appearance": "none",
      "overflow": "hidden",
      "border": "0",
      "border-radius": "13px",
      "box-shadow": "inset 0 1px 0 #ffffff38, 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a",
      "transition": "filter 150ms ease-out, box-shadow 150ms ease-out"
    },
    ".bjork-layered-button-accent::after, .bjork-layered-button-raised::after": {
      "position": "absolute",
      "inset": "0",
      "pointer-events": "none",
      "content": "\"\"",
      "border": "1.5px solid transparent",
      "border-radius": "inherit",
      "background": "linear-gradient(#ffffffb8, #0000003d 41% 75%, #ffffff47) border-box",
      "mix-blend-mode": "overlay",
      "-webkit-mask-image": "linear-gradient(#000, #000), linear-gradient(#000, #000)",
      "mask-image": "linear-gradient(#000, #000), linear-gradient(#000, #000)",
      "-webkit-mask-clip": "padding-box, border-box",
      "mask-clip": "padding-box, border-box",
      "-webkit-mask-origin": "padding-box, border-box",
      "mask-origin": "padding-box, border-box",
      "-webkit-mask-composite": "xor",
      "mask-composite": "exclude"
    },
    ".bjork-layered-button-accent": {
      "color": "oklch(100% 0 0)",
      "text-shadow": "0 -1px #00000040",
      "background": "radial-gradient(ellipse at -20px top, rgba(255, 255, 255, 0.14), transparent), linear-gradient(180deg, #ec5c13 0%, #ec5c13 62%, color-mix(in oklab, #ec5c13 84%, black 16%) 100%)"
    },
    ".bjork-layered-button-raised": {
      "color": "oklch(100% 0 0)",
      "text-shadow": "0 -1px #00000026",
      "background": "radial-gradient(ellipse at -20px top, #ffffff38, #fff0), linear-gradient(180deg, oklch(0.1881 0.006 265), oklch(0.5493 0.0081 265))"
    },
    ".dark .bjork-layered-button-raised": {
      "color": "oklch(0.2574 0.0056 265)",
      "text-shadow": "0 -1px #00000026",
      "background": "radial-gradient(ellipse at -20px top, #ffffff38, #fff0), linear-gradient(180deg, color-mix(in oklab, oklch(0.9674 0.0013 265) 80%, oklch(0.2574 0.0056 265) 20%), color-mix(in oklab, oklch(0.669 0.0107 265) 80%, oklch(0.2574 0.0056 265) 20%))"
    },
    "@media (hover: hover)": {
      ".bjork-layered-button-accent:hover": {
        "filter": "brightness(1.08) saturate(1.04)"
      },
      ".bjork-layered-button-raised:hover": {
        "filter": "brightness(1.08) contrast(0.9)"
      }
    }
  },
  "meta": {
    "collection": "Tables",
    "registryUrl": "https://ui.isaiahbjork.com/resizable-table.json",
    "installCommand": "npx shadcn@latest add https://ui.isaiahbjork.com/resizable-table.json"
  },
  "type": "registry:component"
}