eidos-ui 1.0.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/Combobox.types-B7ozlnA9.d.cts +35 -0
  2. package/dist/Combobox.types-DkigkEh2.d.ts +35 -0
  3. package/dist/SegmentedControl.types-CK8EiShd.d.ts +35 -0
  4. package/dist/SegmentedControl.types-Dm4SnwCK.d.cts +35 -0
  5. package/dist/Select.types-B4kDpkGB.d.ts +42 -0
  6. package/dist/Select.types-CAkT2ACt.d.cts +42 -0
  7. package/dist/{chunk-NFIK3KMT.js → chunk-2KO5ZCZC.js} +2 -2
  8. package/dist/{chunk-IZBJW37N.js → chunk-6DBCT5MK.js} +9 -6
  9. package/dist/chunk-6DBCT5MK.js.map +1 -0
  10. package/dist/{chunk-CBLWP43O.cjs → chunk-6XB3QTHO.cjs} +212 -86
  11. package/dist/chunk-6XB3QTHO.cjs.map +1 -0
  12. package/dist/{chunk-W3X3V72Q.js → chunk-DW2YDWGN.js} +3 -3
  13. package/dist/chunk-DW2YDWGN.js.map +1 -0
  14. package/dist/{chunk-YRK56AOP.cjs → chunk-HNUTV3HR.cjs} +3 -3
  15. package/dist/chunk-HNUTV3HR.cjs.map +1 -0
  16. package/dist/{chunk-IC2M4N7R.cjs → chunk-NUK4NT5Q.cjs} +11 -11
  17. package/dist/{chunk-IC2M4N7R.cjs.map → chunk-NUK4NT5Q.cjs.map} +1 -1
  18. package/dist/{chunk-S2EICHZK.js → chunk-P2ZHWFK4.js} +202 -76
  19. package/dist/chunk-P2ZHWFK4.js.map +1 -0
  20. package/dist/{chunk-PA4IT2XQ.cjs → chunk-TKLWQWMT.cjs} +9 -6
  21. package/dist/chunk-TKLWQWMT.cjs.map +1 -0
  22. package/dist/combobox/index.cjs +2 -2
  23. package/dist/combobox/index.d.cts +4 -33
  24. package/dist/combobox/index.d.ts +4 -33
  25. package/dist/combobox/index.js +1 -1
  26. package/dist/data-grid/index.cjs +6 -3
  27. package/dist/data-grid/index.cjs.map +1 -1
  28. package/dist/data-grid/index.d.cts +100 -3
  29. package/dist/data-grid/index.d.ts +100 -3
  30. package/dist/data-grid/index.js +5 -2
  31. package/dist/index.cjs +10 -10
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.css +62 -34
  34. package/dist/index.d.cts +7 -4
  35. package/dist/index.d.ts +7 -4
  36. package/dist/index.js +9 -9
  37. package/dist/segmented-control/index.d.cts +5 -34
  38. package/dist/segmented-control/index.d.ts +5 -34
  39. package/dist/select/index.d.cts +5 -41
  40. package/dist/select/index.d.ts +5 -41
  41. package/dist/table/index.cjs +4 -3
  42. package/dist/table/index.cjs.map +1 -1
  43. package/dist/table/index.js +3 -2
  44. package/package.json +4 -7
  45. package/dist/chunk-CBLWP43O.cjs.map +0 -1
  46. package/dist/chunk-IZBJW37N.js.map +0 -1
  47. package/dist/chunk-PA4IT2XQ.cjs.map +0 -1
  48. package/dist/chunk-S2EICHZK.js.map +0 -1
  49. package/dist/chunk-W3X3V72Q.js.map +0 -1
  50. package/dist/chunk-YRK56AOP.cjs.map +0 -1
  51. /package/dist/{chunk-NFIK3KMT.js.map → chunk-2KO5ZCZC.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/eidos-ui/eidos-ui/dist/chunk-IC2M4N7R.cjs","../src/components/Table/Table.component.tsx"],"names":[],"mappings":"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","file":"/home/runner/work/eidos-ui/eidos-ui/dist/chunk-IC2M4N7R.cjs","sourcesContent":[null,"import { useState, useMemo, useLayoutEffect, useRef } from 'react';\nimport React from 'react';\nimport {\n ChevronsUpDown,\n ArrowUp,\n ArrowDown,\n X,\n Check,\n Download,\n Columns3,\n AlignJustify,\n GripVertical,\n FolderOpen,\n SearchX,\n} from 'lucide-react';\nimport { DndContext, PointerSensor, KeyboardSensor, useSensor, useSensors } from '@dnd-kit/core';\nimport type { DragEndEvent } from '@dnd-kit/core';\nimport {\n SortableContext,\n horizontalListSortingStrategy,\n useSortable,\n sortableKeyboardCoordinates,\n arrayMove,\n} from '@dnd-kit/sortable';\nimport { CSS } from '@dnd-kit/utilities';\nimport type { TableColumn, TableProps, TableFilters, FilterValue } from './Table.types';\n\nexport type { TableColumn, TableProps, TableFilters, FilterValue };\nimport { Button } from '../Button';\nimport { Checkbox } from '../Checkbox';\nimport { Dropdown } from '../Dropdown';\nimport { EmptyState } from '../EmptyState';\nimport { Pagination } from '../Pagination';\nimport { SplitButton } from '../SplitButton';\nimport { Spinner } from '../Spinner';\nimport { TableFiltersDropdown } from './TableFiltersDropdown.component';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\ntype Density = 'compact' | 'comfortable' | 'spacious';\n\nconst DENSITY_OPTIONS: { value: Density; label: string }[] = [\n { value: 'compact', label: 'Compact' },\n { value: 'comfortable', label: 'Comfortable' },\n { value: 'spacious', label: 'Spacious' },\n];\n\n// ── CSV helpers ───────────────────────────────────────────────────────────────\n\nfunction escapeCsvValue(value: string): string {\n if (value.includes(',') || value.includes('\\n') || value.includes('\"')) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n }\n return value;\n}\n\n// ── SortableColumnHeader ──────────────────────────────────────────────────────\n// Defined as a standalone component so it can legally call useSortable().\n// Only rendered when draggableColumns=true (inside a DndContext + SortableContext).\n\ninterface SortableThProps {\n id: string;\n column: { label: string };\n isSortable: boolean;\n isCurrentlySorted: boolean;\n sortDirection: 'asc' | 'desc' | null;\n alignment: string;\n columnType: string;\n columnWidth: string | undefined;\n pinnedClass: string;\n stickyStyle: React.CSSProperties;\n onSortClick: () => void;\n}\n\nfunction SortableColumnHeader({\n id,\n column,\n isSortable,\n isCurrentlySorted,\n sortDirection,\n alignment,\n columnType,\n columnWidth,\n pinnedClass,\n stickyStyle,\n onSortClick,\n}: SortableThProps) {\n const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({\n id,\n });\n\n const transformStr = CSS.Transform.toString(transform);\n\n const thClassName = [\n 'eidos-table-header-cell',\n `eidos-table-header-cell-${columnType}`,\n isSortable ? 'eidos-table-sortable' : '',\n isCurrentlySorted ? 'eidos-table-sorted' : '',\n `eidos-table-align-${alignment}`,\n pinnedClass,\n ]\n .filter(Boolean)\n .join(' ');\n\n return (\n <th\n ref={setNodeRef as React.LegacyRef<HTMLTableCellElement>}\n className={thClassName}\n style={{\n width: columnWidth,\n ...stickyStyle,\n ...(transformStr ? { transform: transformStr } : {}),\n transition: transition ?? undefined,\n opacity: isDragging ? 0.5 : 1,\n }}\n data-col-key={id}\n onClick={() => isSortable && onSortClick()}\n {...attributes}\n >\n {/* Grip - absolutely positioned in the left padding zone so it never\n displaces the column label or causes layout shift on hover. */}\n <span\n className=\"eidos-table-drag-handle\"\n {...listeners}\n onClick={(e) => e.stopPropagation()}\n title=\"Drag to reorder\"\n >\n <GripVertical size={12} />\n </span>\n <div className={`eidos-table-header-content eidos-table-align-${alignment}`}>\n <span className=\"eidos-table-header-label\">{column.label}</span>\n {isSortable && (\n <div className=\"eidos-table-sort-indicator\">\n {sortDirection === 'asc' && <ArrowUp size={14} />}\n {sortDirection === 'desc' && <ArrowDown size={14} />}\n {!sortDirection && <ChevronsUpDown size={14} />}\n </div>\n )}\n </div>\n </th>\n );\n}\n\n// ── Table ─────────────────────────────────────────────────────────────────────\n\nexport const Table = <T extends Record<string, unknown>>({\n data,\n columns,\n loading = false,\n emptyMessage = 'No data available',\n onRowClick,\n className,\n showFooter = false,\n totalItems,\n showPagination = false,\n pageSize = 25,\n pageSizeOptions = [10, 25, 50, 100],\n onSortChange,\n currentSort,\n filters = {},\n onFiltersChange,\n defaultFilters,\n showFilters = false,\n // Selection\n selectable = false,\n rowKey,\n selectedRows: controlledSelected,\n defaultSelectedRows = [],\n onSelectionChange,\n bulkActions = [],\n // New features\n density = 'comfortable',\n showDensity = false,\n showColumnVisibility = false,\n showExport = false,\n draggableColumns = false,\n onColumnReorder,\n}: TableProps<T>) => {\n // ── Column ordering (drag-reorder) ────────────────────────────────────────\n const [columnOrder, setColumnOrder] = useState<string[]>(() => columns.map((c) => String(c.key)));\n\n // Derived-state reset: when the set of column keys changes, reset the order.\n // Calling setState during render is the React-idiomatic way to do this.\n const columnKeysSignature = columns.map((c) => String(c.key)).join('\\0');\n const prevKeysRef = useRef(columnKeysSignature);\n if (prevKeysRef.current !== columnKeysSignature) {\n prevKeysRef.current = columnKeysSignature;\n setColumnOrder(columns.map((c) => String(c.key)));\n }\n\n const orderedColumns = useMemo(() => {\n const orderMap = new Map(columnOrder.map((key, idx) => [key, idx]));\n return [...columns].sort((a, b) => {\n const ai = orderMap.get(String(a.key)) ?? Infinity;\n const bi = orderMap.get(String(b.key)) ?? Infinity;\n return ai - bi;\n });\n }, [columns, columnOrder]);\n\n // ── Density (internal state when showDensity=true) ────────────────────────\n const [internalDensity, setInternalDensity] = useState<Density>(density as Density);\n // When showDensity is true the table owns density; otherwise respect the prop.\n const effectiveDensity: Density = showDensity ? internalDensity : (density as Density);\n\n // ── Column visibility ─────────────────────────────────────────────────────\n const [hiddenColumnKeys, setHiddenColumnKeys] = useState<Set<string>>(() => new Set<string>());\n\n const visibleColumns = useMemo(\n () => orderedColumns.filter((c) => !hiddenColumnKeys.has(String(c.key))),\n [orderedColumns, hiddenColumnKeys],\n );\n\n // ── Pinned / sticky columns ────────────────────────────────────────────────\n const theadRef = useRef<HTMLTableSectionElement>(null);\n const [pinnedOffsets, setPinnedOffsets] = useState<\n Map<string, { side: 'left' | 'right'; offset: number }>\n >(new Map());\n\n useLayoutEffect(() => {\n const hasPinned = visibleColumns.some((c) => c.pin);\n if (!hasPinned || !theadRef.current) {\n setPinnedOffsets(new Map());\n return;\n }\n\n const headerRow = theadRef.current.querySelector('tr');\n if (!headerRow) return;\n\n const cells = Array.from(headerRow.querySelectorAll<HTMLTableCellElement>('th[data-col-key]'));\n\n const newOffsets = new Map<string, { side: 'left' | 'right'; offset: number }>();\n\n // Left-pinned: scan left-to-right.\n // Checkbox (when selectable) counts as a left-pinned element.\n let leftAccum = 0;\n for (const cell of cells) {\n const key = cell.dataset.colKey!;\n if (key === '__checkbox__') {\n newOffsets.set(key, { side: 'left', offset: leftAccum });\n leftAccum += cell.offsetWidth;\n } else {\n const col = visibleColumns.find((c) => String(c.key) === key);\n if (col?.pin === 'left') {\n newOffsets.set(key, { side: 'left', offset: leftAccum });\n leftAccum += cell.offsetWidth;\n }\n }\n }\n\n // Right-pinned: scan right-to-left.\n let rightAccum = 0;\n for (let i = cells.length - 1; i >= 0; i--) {\n const cell = cells[i];\n const key = cell.dataset.colKey!;\n const col = visibleColumns.find((c) => String(c.key) === key);\n if (col?.pin === 'right') {\n newOffsets.set(key, { side: 'right', offset: rightAccum });\n rightAccum += cell.offsetWidth;\n }\n }\n\n setPinnedOffsets(newOffsets);\n }, [visibleColumns, selectable]);\n\n // ── Selection state (controlled / uncontrolled) ──────────────────────────\n const isControlledSelection = controlledSelected !== undefined;\n const [internalSelected, setInternalSelected] = useState<string[]>(defaultSelectedRows);\n const selectedSet = useMemo(\n () => new Set(isControlledSelection ? controlledSelected : internalSelected),\n [isControlledSelection, controlledSelected, internalSelected],\n );\n\n /** Stable string key for a row - uses `rowKey` prop, then \"id\", then index. */\n const getRowKey = (item: T, index: number): string => {\n if (rowKey) return String(item[rowKey]);\n if ('id' in item) return String(item.id);\n return String(index);\n };\n\n const commitSelection = (keys: string[]) => {\n if (!isControlledSelection) setInternalSelected(keys);\n const rows = data.filter((item, idx) => keys.includes(getRowKey(item, idx)));\n onSelectionChange?.(keys, rows);\n };\n\n const toggleRow = (key: string) => {\n const next = selectedSet.has(key)\n ? [...selectedSet].filter((k) => k !== key)\n : [...selectedSet, key];\n commitSelection(next);\n };\n\n const allKeys = useMemo(\n () => data.map((item, idx) => getRowKey(item, idx)),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [data, rowKey],\n );\n\n const allSelected = allKeys.length > 0 && allKeys.every((k) => selectedSet.has(k));\n const someSelected = !allSelected && allKeys.some((k) => selectedSet.has(k));\n\n const toggleAll = () => {\n commitSelection(allSelected ? [] : allKeys);\n };\n\n const clearSelection = () => commitSelection([]);\n\n // ── Pagination ────────────────────────────────────────────────────────────\n const [currentPage, setCurrentPage] = useState(1);\n const [currentPageSize, setCurrentPageSize] = useState(pageSize);\n\n const displayData = useMemo(() => {\n if (!showPagination) return data;\n const startIndex = (currentPage - 1) * currentPageSize;\n return data.slice(startIndex, startIndex + currentPageSize);\n }, [data, showPagination, currentPage, currentPageSize]);\n\n const totalPages = Math.ceil(data.length / currentPageSize);\n\n const handleSort = (key: string) => {\n const newDirection =\n currentSort?.key === key && currentSort?.direction === 'asc' ? 'desc' : 'asc';\n onSortChange?.(key, newDirection);\n };\n\n const handlePageChange = (page: number) => setCurrentPage(page);\n const handlePageSizeChange = (newPageSize: number) => {\n setCurrentPageSize(newPageSize);\n setCurrentPage(1);\n };\n\n // ── Derived UI state ──────────────────────────────────────────────────────\n const hasSelection = selectedSet.size > 0;\n const showToolbar =\n (showFilters && !!onFiltersChange) ||\n showColumnVisibility ||\n showExport ||\n showDensity ||\n (selectable && hasSelection);\n\n const selectedItems = useMemo(\n () => data.filter((item, idx) => selectedSet.has(getRowKey(item, idx))),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [data, selectedSet, rowKey],\n );\n\n // ── CSV Export ────────────────────────────────────────────────────────────\n const getRawValue = (item: T, column: TableColumn<T>): string => {\n const value =\n typeof column.key === 'string' && column.key.includes('.')\n ? (column.key as string)\n .split('.')\n .reduce(\n (obj: unknown, k: string) => (obj as Record<string, unknown>)?.[k],\n item as unknown,\n )\n : item[column.key as keyof T];\n return String(value ?? '');\n };\n\n const handleExportCsv = () => {\n const headers = visibleColumns.map((c) => escapeCsvValue(c.label)).join(',');\n const rows = data.map((item) =>\n visibleColumns.map((col) => escapeCsvValue(getRawValue(item, col))).join(','),\n );\n const csv = [headers, ...rows].join('\\n');\n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = 'export.csv';\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n };\n\n // ── DnD setup ─────────────────────────────────────────────────────────────\n const sensors = useSensors(\n useSensor(PointerSensor),\n useSensor(KeyboardSensor, {\n coordinateGetter: sortableKeyboardCoordinates,\n }),\n );\n\n const handleDragEnd = (event: DragEndEvent) => {\n const { active, over } = event;\n if (!over || active.id === over.id) return;\n\n const activeId = String(active.id);\n const overId = String(over.id);\n const oldIndex = columnOrder.indexOf(activeId);\n const newIndex = columnOrder.indexOf(overId);\n if (oldIndex === -1 || newIndex === -1) return;\n\n const newOrder = arrayMove(columnOrder, oldIndex, newIndex);\n setColumnOrder(newOrder);\n\n if (onColumnReorder) {\n const orderMap = new Map(newOrder.map((key, idx) => [key, idx]));\n const reordered = [...columns].sort((a, b) => {\n const ai = orderMap.get(String(a.key)) ?? Infinity;\n const bi = orderMap.get(String(b.key)) ?? Infinity;\n return ai - bi;\n });\n onColumnReorder(reordered);\n }\n };\n\n // ── Helpers ───────────────────────────────────────────────────────────────\n\n /** Returns sticky style + className for body <td> cells (zIndex: 1). */\n const getCellPinnedProps = (\n colKey: string,\n ): { style: React.CSSProperties; className: string } => {\n const pinInfo = pinnedOffsets.get(colKey);\n if (!pinInfo) return { style: {}, className: '' };\n return {\n style: {\n position: 'sticky',\n [pinInfo.side]: pinInfo.offset,\n zIndex: 1,\n },\n className:\n pinInfo.side === 'left'\n ? 'eidos-table-cell--pinned-left'\n : 'eidos-table-cell--pinned-right',\n };\n };\n\n /** Returns sticky style + className for header <th> cells (zIndex: 3). */\n const getHeaderPinnedProps = (\n colKey: string,\n ): { style: React.CSSProperties; className: string } => {\n const pinInfo = pinnedOffsets.get(colKey);\n if (!pinInfo) return { style: {}, className: '' };\n return {\n style: {\n position: 'sticky',\n [pinInfo.side]: pinInfo.offset,\n zIndex: 3,\n },\n className:\n pinInfo.side === 'left'\n ? 'eidos-table-header-cell--pinned-left'\n : 'eidos-table-header-cell--pinned-right',\n };\n };\n\n // ── Density class ─────────────────────────────────────────────────────────\n const densityClass = effectiveDensity !== 'comfortable' ? `eidos-table--${effectiveDensity}` : '';\n\n // ── Loading ───────────────────────────────────────────────────────────────\n if (loading) {\n return (\n <div className={`eidos-table-container ${className || ''}`}>\n <div className=\"eidos-table-loading\">\n <Spinner size=\"md\" />\n <p>Loading...</p>\n </div>\n </div>\n );\n }\n\n // ── Density panel (Dropdown content) ──────────────────────────────────────\n const densityPanel = (\n <div className=\"eidos-table-menu-panel\">\n {DENSITY_OPTIONS.map((option) => (\n <button\n key={option.value}\n className={[\n 'eidos-table-menu-item',\n effectiveDensity === option.value ? 'eidos-table-menu-item--active' : '',\n ]\n .filter(Boolean)\n .join(' ')}\n onClick={() => setInternalDensity(option.value)}\n >\n <span className=\"eidos-table-menu-item-check\">\n {effectiveDensity === option.value && <Check size={14} />}\n </span>\n <span>{option.label}</span>\n </button>\n ))}\n </div>\n );\n\n // ── Column visibility panel (Dropdown content) ────────────────────────────\n const columnVisibilityPanel = (\n <div className=\"eidos-table-menu-panel\">\n {/* Use orderedColumns so list order mirrors current column order */}\n {orderedColumns.map((col) => {\n const key = String(col.key);\n const isVisible = !hiddenColumnKeys.has(key);\n const isLastVisible = visibleColumns.length === 1 && isVisible;\n return (\n <button\n key={key}\n className={['eidos-table-menu-item', isVisible ? 'eidos-table-menu-item--active' : '']\n .filter(Boolean)\n .join(' ')}\n disabled={isLastVisible}\n onClick={() => {\n setHiddenColumnKeys((prev) => {\n const next = new Set(prev);\n if (next.has(key)) next.delete(key);\n else next.add(key);\n return next;\n });\n }}\n >\n <Checkbox\n checked={isVisible}\n disabled={isLastVisible}\n size=\"sm\"\n onChange={() => {\n /* click handled by parent button */\n }}\n />\n <span>{col.label}</span>\n </button>\n );\n })}\n </div>\n );\n\n // ── SortableContext items: visible column keys only ───────────────────────\n const sortableItems = visibleColumns.map((c) => String(c.key));\n\n // ── Main JSX ──────────────────────────────────────────────────────────────\n const tableContent = (\n <div className={`eidos-table-container ${className || ''}`}>\n {/* ── Toolbar ───────────────────────────────────────────────────────── */}\n {showToolbar && (\n <div className=\"eidos-table-toolbar\">\n {/* Left - bulk actions / selection info */}\n <div className=\"eidos-table-toolbar-left\">\n {selectable && hasSelection && (\n <>\n <span className=\"eidos-table-selection-count\">{selectedSet.size} selected</span>\n\n {bulkActions.length > 0 && (\n <div className=\"eidos-table-bulk-actions\">\n {bulkActions.map((action) => {\n const isDisabled =\n typeof action.disabled === 'function'\n ? action.disabled(selectedItems)\n : (action.disabled ?? false);\n\n if (action.type === 'split-button') {\n return (\n <SplitButton\n key={action.id}\n size=\"sm\"\n variant={action.variant ?? 'outlined'}\n color={action.color ?? 'secondary'}\n preIcon={action.icon}\n disabled={isDisabled}\n label={action.label}\n onClick={() => action.onClick(selectedItems)}\n options={action.options.map((option) => ({\n id: option.id,\n label: option.label,\n icon: option.icon,\n disabled: option.disabled,\n onClick: () => option.onClick(selectedItems),\n }))}\n />\n );\n }\n\n return action.label ? (\n <Button\n key={action.id}\n size=\"sm\"\n variant={action.variant ?? 'outlined'}\n color={action.color ?? 'secondary'}\n preIcon={action.icon}\n posIcon={action.posIcon}\n disabled={isDisabled}\n onClick={() => action.onClick(selectedItems)}\n >\n {action.label}\n </Button>\n ) : action.icon ? (\n <Button\n key={action.id}\n size=\"sm\"\n variant={action.variant ?? 'outlined'}\n color={action.color ?? 'secondary'}\n icon={action.icon}\n disabled={isDisabled}\n onClick={() => action.onClick(selectedItems)}\n />\n ) : null;\n })}\n </div>\n )}\n\n <button\n className=\"eidos-table-clear-selection\"\n onClick={clearSelection}\n aria-label=\"Clear selection\"\n >\n <X size={14} />\n </button>\n </>\n )}\n </div>\n\n {/* Right - density, column visibility, export, filter */}\n <div className=\"eidos-table-toolbar-right\">\n {showDensity && (\n <Dropdown\n placement=\"bottom\"\n align=\"end\"\n autoWidth={false}\n trigger={\n <Button variant=\"text\" size=\"sm\" preIcon={AlignJustify}>\n Density\n </Button>\n }\n content={densityPanel}\n />\n )}\n\n {showColumnVisibility && (\n <Dropdown\n placement=\"bottom\"\n align=\"end\"\n autoWidth={false}\n trigger={\n <Button variant=\"text\" size=\"sm\" preIcon={Columns3}>\n Columns\n </Button>\n }\n content={columnVisibilityPanel}\n />\n )}\n\n {showExport && (\n <Button\n variant=\"text\"\n size=\"sm\"\n icon={Download}\n onClick={handleExportCsv}\n tooltip=\"Export CSV\"\n />\n )}\n\n {showFilters && onFiltersChange && (\n <TableFiltersDropdown\n columns={columns}\n filters={filters}\n onFiltersChange={onFiltersChange}\n defaultFilters={defaultFilters}\n />\n )}\n </div>\n </div>\n )}\n\n {/* ── Table (wrapped for horizontal scroll) ─────────────────────────── */}\n <div className=\"eidos-table-scroll\">\n <table\n className={[\n 'eidos-table',\n densityClass,\n draggableColumns ? 'eidos-table--draggable-columns' : '',\n ]\n .filter(Boolean)\n .join(' ')}\n >\n <thead ref={theadRef}>\n <tr>\n {selectable && (\n <th\n className={[\n 'eidos-table-header-cell',\n 'eidos-table-checkbox-cell',\n getHeaderPinnedProps('__checkbox__').className,\n ]\n .filter(Boolean)\n .join(' ')}\n style={getHeaderPinnedProps('__checkbox__').style}\n data-col-key=\"__checkbox__\"\n >\n <Checkbox\n size=\"sm\"\n checked={allSelected}\n indeterminate={someSelected}\n onChange={toggleAll}\n aria-label=\"Select all rows\"\n />\n </th>\n )}\n\n {/* ── Headers: draggable or plain ──────────────────────────── */}\n {draggableColumns\n ? visibleColumns.map((column) => {\n const colKey = String(column.key);\n const columnType = column.type || 'data';\n const columnWidth =\n column.type === 'icon' ? 'var(--component-size-lg)' : column.width;\n const isSortable = !!column.sortable;\n const isCurrentlySorted = currentSort?.key === colKey;\n const sortDirection = isCurrentlySorted\n ? (currentSort!.direction as 'asc' | 'desc')\n : null;\n const alignment = column.align || 'left';\n const { style: pinnedStyle, className: pinnedClass } =\n getHeaderPinnedProps(colKey);\n\n return (\n <SortableColumnHeader\n key={colKey}\n id={colKey}\n column={column}\n isSortable={isSortable}\n isCurrentlySorted={!!isCurrentlySorted}\n sortDirection={sortDirection}\n alignment={alignment}\n columnType={columnType}\n columnWidth={columnWidth}\n pinnedClass={pinnedClass}\n stickyStyle={pinnedStyle}\n onSortClick={() => handleSort(colKey)}\n />\n );\n })\n : visibleColumns.map((column, index) => {\n const colKey = String(column.key);\n const columnType = column.type || 'data';\n const columnWidth =\n column.type === 'icon' ? 'var(--component-size-lg)' : column.width;\n const isSortable = column.sortable;\n const isCurrentlySorted = currentSort?.key === colKey;\n const sortDirection = isCurrentlySorted ? currentSort!.direction : null;\n const alignment = column.align || 'left';\n const alignmentClass = `eidos-table-align-${alignment}`;\n const { style: pinnedStyle, className: pinnedClass } =\n getHeaderPinnedProps(colKey);\n\n return (\n <th\n key={index}\n className={[\n 'eidos-table-header-cell',\n `eidos-table-header-cell-${columnType}`,\n isSortable ? 'eidos-table-sortable' : '',\n isCurrentlySorted ? 'eidos-table-sorted' : '',\n alignmentClass,\n pinnedClass,\n ]\n .filter(Boolean)\n .join(' ')}\n style={{ width: columnWidth, ...pinnedStyle }}\n data-col-key={colKey}\n onClick={() => isSortable && handleSort(colKey)}\n >\n <div className={`eidos-table-header-content ${alignmentClass}`}>\n <span>{column.label}</span>\n {isSortable && (\n <div className=\"eidos-table-sort-indicator\">\n {sortDirection === 'asc' && <ArrowUp size={14} />}\n {sortDirection === 'desc' && <ArrowDown size={14} />}\n {!sortDirection && <ChevronsUpDown size={14} />}\n </div>\n )}\n </div>\n </th>\n );\n })}\n </tr>\n </thead>\n <tbody>\n {displayData.length > 0 ? (\n displayData.map((item, rowIndex) => {\n const globalIndex = showPagination\n ? (currentPage - 1) * currentPageSize + rowIndex\n : rowIndex;\n const key = getRowKey(item, globalIndex);\n const isSelected = selectedSet.has(key);\n\n return (\n <tr\n key={key}\n className={[\n 'eidos-table-row',\n onRowClick ? 'eidos-table-clickable' : '',\n isSelected ? 'eidos-table-row--selected' : '',\n ]\n .filter(Boolean)\n .join(' ')}\n onClick={() => onRowClick?.(item)}\n >\n {selectable && (\n <td\n className={[\n 'eidos-table-cell',\n 'eidos-table-checkbox-cell',\n getCellPinnedProps('__checkbox__').className,\n ]\n .filter(Boolean)\n .join(' ')}\n style={getCellPinnedProps('__checkbox__').style}\n onClick={(e) => {\n e.stopPropagation();\n toggleRow(key);\n }}\n >\n {/* Wrap in a span so Checkbox click-events don't\n bubble to the <td> and cause a double-toggle.\n The <td> onClick handles toggling when the user\n clicks the cell margin outside the checkbox. */}\n <span onClick={(e) => e.stopPropagation()}>\n <Checkbox\n size=\"sm\"\n checked={isSelected}\n onChange={() => toggleRow(key)}\n aria-label={`Select row ${globalIndex + 1}`}\n />\n </span>\n </td>\n )}\n {visibleColumns.map((column, colIndex) => {\n const colKey = String(column.key);\n const value =\n typeof column.key === 'string' && column.key.includes('.')\n ? (column.key as string)\n .split('.')\n .reduce(\n (obj: unknown, k: string) => (obj as Record<string, unknown>)?.[k],\n item as unknown,\n )\n : item[column.key as keyof T];\n\n const columnType = column.type || 'data';\n const columnWidth =\n column.type === 'icon' ? 'var(--component-size-lg)' : column.width;\n const alignment = column.align || 'left';\n const alignmentClass = `eidos-table-align-${alignment}`;\n const { style: pinnedStyle, className: pinnedClass } =\n getCellPinnedProps(colKey);\n\n return (\n <td\n key={colIndex}\n className={[\n 'eidos-table-cell',\n `eidos-table-cell-${columnType}`,\n alignmentClass,\n pinnedClass,\n ]\n .filter(Boolean)\n .join(' ')}\n style={{ width: columnWidth, ...pinnedStyle }}\n // `type: 'action'` cells exist specifically to hold\n // interactive controls (a row-actions Menu/button,\n // typically) - without this, clicking them bubbles\n // straight into `onRowClick` on the <tr> below,\n // e.g. opening a details drawer at the same time as\n // (or instead of) the action itself. Mirrors the\n // selectable checkbox cell's own stopPropagation\n // above, for the same reason.\n onClick={columnType === 'action' ? (e) => e.stopPropagation() : undefined}\n >\n {column.render ? column.render(value, item) : String(value ?? '')}\n </td>\n );\n })}\n </tr>\n );\n })\n ) : (\n <tr className=\"eidos-table-empty-row\">\n <td\n colSpan={selectable ? visibleColumns.length + 1 : visibleColumns.length}\n className=\"eidos-table-cell eidos-table-empty-cell\"\n >\n {Object.keys(filters).length > 0 ? (\n <EmptyState\n icon={<SearchX />}\n title=\"No results found\"\n description=\"Try adjusting your filters or search terms.\"\n action={\n onFiltersChange ? (\n <Button\n size=\"sm\"\n variant=\"outlined\"\n color=\"primary\"\n onClick={() => onFiltersChange({})}\n >\n Clear filters\n </Button>\n ) : undefined\n }\n size=\"sm\"\n />\n ) : (\n <EmptyState\n icon={<FolderOpen />}\n title={emptyMessage}\n description=\"There are no records to display.\"\n size=\"sm\"\n />\n )}\n </td>\n </tr>\n )}\n </tbody>\n </table>\n </div>\n\n {/* ── Footer / Pagination ───────────────────────────────────────────── */}\n {showFooter && (\n <div className=\"eidos-table-footer\">\n <div className=\"eidos-table-footer-content\">\n {showPagination && data.length > 0 ? (\n <Pagination\n page={currentPage}\n totalPages={totalPages}\n onChange={handlePageChange}\n totalItems={totalItems ?? data.length}\n pageSize={currentPageSize}\n onPageSizeChange={handlePageSizeChange}\n pageSizeOptions={pageSizeOptions}\n size=\"sm\"\n />\n ) : (\n <span className=\"eidos-table-results-info\">\n Showing {data.length} of {totalItems ?? data.length} results\n </span>\n )}\n </div>\n </div>\n )}\n </div>\n );\n\n // ── Wrap with DnD context when draggableColumns=true ─────────────────────\n if (draggableColumns) {\n return (\n <DndContext sensors={sensors} onDragEnd={handleDragEnd}>\n <SortableContext items={sortableItems} strategy={horizontalListSortingStrategy}>\n {tableContent}\n </SortableContext>\n </DndContext>\n );\n }\n\n return tableContent;\n};\n"]}
1
+ {"version":3,"sources":["/home/runner/work/eidos-ui/eidos-ui/dist/chunk-NUK4NT5Q.cjs","../src/components/Table/Table.component.tsx"],"names":[],"mappings":"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","file":"/home/runner/work/eidos-ui/eidos-ui/dist/chunk-NUK4NT5Q.cjs","sourcesContent":[null,"import { useState, useMemo, useLayoutEffect, useRef } from 'react';\nimport React from 'react';\nimport {\n ChevronsUpDown,\n ArrowUp,\n ArrowDown,\n X,\n Check,\n Download,\n Columns3,\n AlignJustify,\n GripVertical,\n FolderOpen,\n SearchX,\n} from 'lucide-react';\nimport { DndContext, PointerSensor, KeyboardSensor, useSensor, useSensors } from '@dnd-kit/core';\nimport type { DragEndEvent } from '@dnd-kit/core';\nimport {\n SortableContext,\n horizontalListSortingStrategy,\n useSortable,\n sortableKeyboardCoordinates,\n arrayMove,\n} from '@dnd-kit/sortable';\nimport { CSS } from '@dnd-kit/utilities';\nimport type { TableColumn, TableProps, TableFilters, FilterValue } from './Table.types';\n\nexport type { TableColumn, TableProps, TableFilters, FilterValue };\nimport { Button } from '../Button';\nimport { Checkbox } from '../Checkbox';\nimport { Dropdown } from '../Dropdown';\nimport { EmptyState } from '../EmptyState';\nimport { Pagination } from '../Pagination';\nimport { SplitButton } from '../SplitButton';\nimport { Spinner } from '../Spinner';\nimport { TableFiltersDropdown } from './TableFiltersDropdown.component';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\ntype Density = 'compact' | 'comfortable' | 'spacious';\n\nconst DENSITY_OPTIONS: { value: Density; label: string }[] = [\n { value: 'compact', label: 'Compact' },\n { value: 'comfortable', label: 'Comfortable' },\n { value: 'spacious', label: 'Spacious' },\n];\n\n// ── CSV helpers ───────────────────────────────────────────────────────────────\n\nfunction escapeCsvValue(value: string): string {\n if (value.includes(',') || value.includes('\\n') || value.includes('\"')) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n }\n return value;\n}\n\n// ── SortableColumnHeader ──────────────────────────────────────────────────────\n// Defined as a standalone component so it can legally call useSortable().\n// Only rendered when draggableColumns=true (inside a DndContext + SortableContext).\n\ninterface SortableThProps {\n id: string;\n column: { label: string };\n isSortable: boolean;\n isCurrentlySorted: boolean;\n sortDirection: 'asc' | 'desc' | null;\n alignment: string;\n columnType: string;\n columnWidth: string | undefined;\n pinnedClass: string;\n stickyStyle: React.CSSProperties;\n onSortClick: () => void;\n}\n\nfunction SortableColumnHeader({\n id,\n column,\n isSortable,\n isCurrentlySorted,\n sortDirection,\n alignment,\n columnType,\n columnWidth,\n pinnedClass,\n stickyStyle,\n onSortClick,\n}: SortableThProps) {\n const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({\n id,\n });\n\n const transformStr = CSS.Transform.toString(transform);\n\n const thClassName = [\n 'eidos-table-header-cell',\n `eidos-table-header-cell-${columnType}`,\n isSortable ? 'eidos-table-sortable' : '',\n isCurrentlySorted ? 'eidos-table-sorted' : '',\n `eidos-table-align-${alignment}`,\n pinnedClass,\n ]\n .filter(Boolean)\n .join(' ');\n\n return (\n <th\n ref={setNodeRef as React.LegacyRef<HTMLTableCellElement>}\n className={thClassName}\n style={{\n width: columnWidth,\n ...stickyStyle,\n ...(transformStr ? { transform: transformStr } : {}),\n transition: transition ?? undefined,\n opacity: isDragging ? 0.5 : 1,\n }}\n data-col-key={id}\n onClick={() => isSortable && onSortClick()}\n {...attributes}\n >\n {/* Grip - absolutely positioned in the left padding zone so it never\n displaces the column label or causes layout shift on hover. */}\n <span\n className=\"eidos-table-drag-handle\"\n {...listeners}\n onClick={(e) => e.stopPropagation()}\n title=\"Drag to reorder\"\n >\n <GripVertical size={12} />\n </span>\n <div className={`eidos-table-header-content eidos-table-align-${alignment}`}>\n <span className=\"eidos-table-header-label\">{column.label}</span>\n {isSortable && (\n <div className=\"eidos-table-sort-indicator\">\n {sortDirection === 'asc' && <ArrowUp size={14} />}\n {sortDirection === 'desc' && <ArrowDown size={14} />}\n {!sortDirection && <ChevronsUpDown size={14} />}\n </div>\n )}\n </div>\n </th>\n );\n}\n\n// ── Table ─────────────────────────────────────────────────────────────────────\n\nexport const Table = <T extends Record<string, unknown>>({\n data,\n columns,\n loading = false,\n emptyMessage = 'No data available',\n onRowClick,\n className,\n showFooter = false,\n totalItems,\n showPagination = false,\n pageSize = 25,\n pageSizeOptions = [10, 25, 50, 100],\n onSortChange,\n currentSort,\n filters = {},\n onFiltersChange,\n defaultFilters,\n showFilters = false,\n // Selection\n selectable = false,\n rowKey,\n selectedRows: controlledSelected,\n defaultSelectedRows = [],\n onSelectionChange,\n bulkActions = [],\n // New features\n density = 'comfortable',\n showDensity = false,\n showColumnVisibility = false,\n showExport = false,\n draggableColumns = false,\n onColumnReorder,\n}: TableProps<T>) => {\n // ── Column ordering (drag-reorder) ────────────────────────────────────────\n const [columnOrder, setColumnOrder] = useState<string[]>(() => columns.map((c) => String(c.key)));\n\n // Derived-state reset: when the set of column keys changes, reset the order.\n // Calling setState during render is the React-idiomatic way to do this.\n const columnKeysSignature = columns.map((c) => String(c.key)).join('\\0');\n const prevKeysRef = useRef(columnKeysSignature);\n if (prevKeysRef.current !== columnKeysSignature) {\n prevKeysRef.current = columnKeysSignature;\n setColumnOrder(columns.map((c) => String(c.key)));\n }\n\n const orderedColumns = useMemo(() => {\n const orderMap = new Map(columnOrder.map((key, idx) => [key, idx]));\n return [...columns].sort((a, b) => {\n const ai = orderMap.get(String(a.key)) ?? Infinity;\n const bi = orderMap.get(String(b.key)) ?? Infinity;\n return ai - bi;\n });\n }, [columns, columnOrder]);\n\n // ── Density (internal state when showDensity=true) ────────────────────────\n const [internalDensity, setInternalDensity] = useState<Density>(density as Density);\n // When showDensity is true the table owns density; otherwise respect the prop.\n const effectiveDensity: Density = showDensity ? internalDensity : (density as Density);\n\n // ── Column visibility ─────────────────────────────────────────────────────\n const [hiddenColumnKeys, setHiddenColumnKeys] = useState<Set<string>>(() => new Set<string>());\n\n const visibleColumns = useMemo(\n () => orderedColumns.filter((c) => !hiddenColumnKeys.has(String(c.key))),\n [orderedColumns, hiddenColumnKeys],\n );\n\n // ── Pinned / sticky columns ────────────────────────────────────────────────\n const theadRef = useRef<HTMLTableSectionElement>(null);\n const [pinnedOffsets, setPinnedOffsets] = useState<\n Map<string, { side: 'left' | 'right'; offset: number }>\n >(new Map());\n\n useLayoutEffect(() => {\n const hasPinned = visibleColumns.some((c) => c.pin);\n if (!hasPinned || !theadRef.current) {\n setPinnedOffsets(new Map());\n return;\n }\n\n const headerRow = theadRef.current.querySelector('tr');\n if (!headerRow) return;\n\n const cells = Array.from(headerRow.querySelectorAll<HTMLTableCellElement>('th[data-col-key]'));\n\n const newOffsets = new Map<string, { side: 'left' | 'right'; offset: number }>();\n\n // Left-pinned: scan left-to-right.\n // Checkbox (when selectable) counts as a left-pinned element.\n let leftAccum = 0;\n for (const cell of cells) {\n const key = cell.dataset.colKey!;\n if (key === '__checkbox__') {\n newOffsets.set(key, { side: 'left', offset: leftAccum });\n leftAccum += cell.offsetWidth;\n } else {\n const col = visibleColumns.find((c) => String(c.key) === key);\n if (col?.pin === 'left') {\n newOffsets.set(key, { side: 'left', offset: leftAccum });\n leftAccum += cell.offsetWidth;\n }\n }\n }\n\n // Right-pinned: scan right-to-left.\n let rightAccum = 0;\n for (let i = cells.length - 1; i >= 0; i--) {\n const cell = cells[i];\n const key = cell.dataset.colKey!;\n const col = visibleColumns.find((c) => String(c.key) === key);\n if (col?.pin === 'right') {\n newOffsets.set(key, { side: 'right', offset: rightAccum });\n rightAccum += cell.offsetWidth;\n }\n }\n\n setPinnedOffsets(newOffsets);\n }, [visibleColumns, selectable]);\n\n // ── Selection state (controlled / uncontrolled) ──────────────────────────\n const isControlledSelection = controlledSelected !== undefined;\n const [internalSelected, setInternalSelected] = useState<string[]>(defaultSelectedRows);\n const selectedSet = useMemo(\n () => new Set(isControlledSelection ? controlledSelected : internalSelected),\n [isControlledSelection, controlledSelected, internalSelected],\n );\n\n /** Stable string key for a row - uses `rowKey` prop, then \"id\", then index. */\n const getRowKey = (item: T, index: number): string => {\n if (rowKey) return String(item[rowKey]);\n if ('id' in item) return String(item.id);\n return String(index);\n };\n\n const commitSelection = (keys: string[]) => {\n if (!isControlledSelection) setInternalSelected(keys);\n const rows = data.filter((item, idx) => keys.includes(getRowKey(item, idx)));\n onSelectionChange?.(keys, rows);\n };\n\n const toggleRow = (key: string) => {\n const next = selectedSet.has(key)\n ? [...selectedSet].filter((k) => k !== key)\n : [...selectedSet, key];\n commitSelection(next);\n };\n\n const allKeys = useMemo(\n () => data.map((item, idx) => getRowKey(item, idx)),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [data, rowKey],\n );\n\n const allSelected = allKeys.length > 0 && allKeys.every((k) => selectedSet.has(k));\n const someSelected = !allSelected && allKeys.some((k) => selectedSet.has(k));\n\n const toggleAll = () => {\n commitSelection(allSelected ? [] : allKeys);\n };\n\n const clearSelection = () => commitSelection([]);\n\n // ── Pagination ────────────────────────────────────────────────────────────\n const [currentPage, setCurrentPage] = useState(1);\n const [currentPageSize, setCurrentPageSize] = useState(pageSize);\n\n const displayData = useMemo(() => {\n if (!showPagination) return data;\n const startIndex = (currentPage - 1) * currentPageSize;\n return data.slice(startIndex, startIndex + currentPageSize);\n }, [data, showPagination, currentPage, currentPageSize]);\n\n const totalPages = Math.ceil(data.length / currentPageSize);\n\n const handleSort = (key: string) => {\n const newDirection =\n currentSort?.key === key && currentSort?.direction === 'asc' ? 'desc' : 'asc';\n onSortChange?.(key, newDirection);\n };\n\n const handlePageChange = (page: number) => setCurrentPage(page);\n const handlePageSizeChange = (newPageSize: number) => {\n setCurrentPageSize(newPageSize);\n setCurrentPage(1);\n };\n\n // ── Derived UI state ──────────────────────────────────────────────────────\n const hasSelection = selectedSet.size > 0;\n const showToolbar =\n (showFilters && !!onFiltersChange) ||\n showColumnVisibility ||\n showExport ||\n showDensity ||\n (selectable && hasSelection);\n\n const selectedItems = useMemo(\n () => data.filter((item, idx) => selectedSet.has(getRowKey(item, idx))),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [data, selectedSet, rowKey],\n );\n\n // ── CSV Export ────────────────────────────────────────────────────────────\n const getRawValue = (item: T, column: TableColumn<T>): string => {\n const value =\n typeof column.key === 'string' && column.key.includes('.')\n ? (column.key as string)\n .split('.')\n .reduce(\n (obj: unknown, k: string) => (obj as Record<string, unknown>)?.[k],\n item as unknown,\n )\n : item[column.key as keyof T];\n return String(value ?? '');\n };\n\n const handleExportCsv = () => {\n const headers = visibleColumns.map((c) => escapeCsvValue(c.label)).join(',');\n const rows = data.map((item) =>\n visibleColumns.map((col) => escapeCsvValue(getRawValue(item, col))).join(','),\n );\n const csv = [headers, ...rows].join('\\n');\n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = 'export.csv';\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n };\n\n // ── DnD setup ─────────────────────────────────────────────────────────────\n const sensors = useSensors(\n useSensor(PointerSensor),\n useSensor(KeyboardSensor, {\n coordinateGetter: sortableKeyboardCoordinates,\n }),\n );\n\n const handleDragEnd = (event: DragEndEvent) => {\n const { active, over } = event;\n if (!over || active.id === over.id) return;\n\n const activeId = String(active.id);\n const overId = String(over.id);\n const oldIndex = columnOrder.indexOf(activeId);\n const newIndex = columnOrder.indexOf(overId);\n if (oldIndex === -1 || newIndex === -1) return;\n\n const newOrder = arrayMove(columnOrder, oldIndex, newIndex);\n setColumnOrder(newOrder);\n\n if (onColumnReorder) {\n const orderMap = new Map(newOrder.map((key, idx) => [key, idx]));\n const reordered = [...columns].sort((a, b) => {\n const ai = orderMap.get(String(a.key)) ?? Infinity;\n const bi = orderMap.get(String(b.key)) ?? Infinity;\n return ai - bi;\n });\n onColumnReorder(reordered);\n }\n };\n\n // ── Helpers ───────────────────────────────────────────────────────────────\n\n /** Returns sticky style + className for body <td> cells (zIndex: 1). */\n const getCellPinnedProps = (\n colKey: string,\n ): { style: React.CSSProperties; className: string } => {\n const pinInfo = pinnedOffsets.get(colKey);\n if (!pinInfo) return { style: {}, className: '' };\n return {\n style: {\n position: 'sticky',\n [pinInfo.side]: pinInfo.offset,\n zIndex: 1,\n },\n className:\n pinInfo.side === 'left'\n ? 'eidos-table-cell--pinned-left'\n : 'eidos-table-cell--pinned-right',\n };\n };\n\n /** Returns sticky style + className for header <th> cells (zIndex: 3). */\n const getHeaderPinnedProps = (\n colKey: string,\n ): { style: React.CSSProperties; className: string } => {\n const pinInfo = pinnedOffsets.get(colKey);\n if (!pinInfo) return { style: {}, className: '' };\n return {\n style: {\n position: 'sticky',\n [pinInfo.side]: pinInfo.offset,\n zIndex: 3,\n },\n className:\n pinInfo.side === 'left'\n ? 'eidos-table-header-cell--pinned-left'\n : 'eidos-table-header-cell--pinned-right',\n };\n };\n\n // ── Density class ─────────────────────────────────────────────────────────\n const densityClass = effectiveDensity !== 'comfortable' ? `eidos-table--${effectiveDensity}` : '';\n\n // ── Loading ───────────────────────────────────────────────────────────────\n if (loading) {\n return (\n <div className={`eidos-table-container ${className || ''}`}>\n <div className=\"eidos-table-loading\">\n <Spinner size=\"md\" />\n <p>Loading...</p>\n </div>\n </div>\n );\n }\n\n // ── Density panel (Dropdown content) ──────────────────────────────────────\n const densityPanel = (\n <div className=\"eidos-table-menu-panel\">\n {DENSITY_OPTIONS.map((option) => (\n <button\n key={option.value}\n className={[\n 'eidos-table-menu-item',\n effectiveDensity === option.value ? 'eidos-table-menu-item--active' : '',\n ]\n .filter(Boolean)\n .join(' ')}\n onClick={() => setInternalDensity(option.value)}\n >\n <span className=\"eidos-table-menu-item-check\">\n {effectiveDensity === option.value && <Check size={14} />}\n </span>\n <span>{option.label}</span>\n </button>\n ))}\n </div>\n );\n\n // ── Column visibility panel (Dropdown content) ────────────────────────────\n const columnVisibilityPanel = (\n <div className=\"eidos-table-menu-panel\">\n {/* Use orderedColumns so list order mirrors current column order */}\n {orderedColumns.map((col) => {\n const key = String(col.key);\n const isVisible = !hiddenColumnKeys.has(key);\n const isLastVisible = visibleColumns.length === 1 && isVisible;\n return (\n <button\n key={key}\n className={['eidos-table-menu-item', isVisible ? 'eidos-table-menu-item--active' : '']\n .filter(Boolean)\n .join(' ')}\n disabled={isLastVisible}\n onClick={() => {\n setHiddenColumnKeys((prev) => {\n const next = new Set(prev);\n if (next.has(key)) next.delete(key);\n else next.add(key);\n return next;\n });\n }}\n >\n <Checkbox\n checked={isVisible}\n disabled={isLastVisible}\n size=\"sm\"\n onChange={() => {\n /* click handled by parent button */\n }}\n />\n <span>{col.label}</span>\n </button>\n );\n })}\n </div>\n );\n\n // ── SortableContext items: visible column keys only ───────────────────────\n const sortableItems = visibleColumns.map((c) => String(c.key));\n\n // ── Main JSX ──────────────────────────────────────────────────────────────\n const tableContent = (\n <div className={`eidos-table-container ${className || ''}`}>\n {/* ── Toolbar ───────────────────────────────────────────────────────── */}\n {showToolbar && (\n <div className=\"eidos-table-toolbar\">\n {/* Left - bulk actions / selection info */}\n <div className=\"eidos-table-toolbar-left\">\n {selectable && hasSelection && (\n <>\n <span className=\"eidos-table-selection-count\">{selectedSet.size} selected</span>\n\n {bulkActions.length > 0 && (\n <div className=\"eidos-table-bulk-actions\">\n {bulkActions.map((action) => {\n const isDisabled =\n typeof action.disabled === 'function'\n ? action.disabled(selectedItems)\n : (action.disabled ?? false);\n\n if (action.type === 'split-button') {\n return (\n <SplitButton\n key={action.id}\n size=\"sm\"\n variant={action.variant ?? 'outlined'}\n color={action.color ?? 'secondary'}\n preIcon={action.icon}\n disabled={isDisabled}\n label={action.label}\n onClick={() => action.onClick(selectedItems)}\n options={action.options.map((option) => ({\n id: option.id,\n label: option.label,\n icon: option.icon,\n disabled: option.disabled,\n onClick: () => option.onClick(selectedItems),\n }))}\n />\n );\n }\n\n return action.label ? (\n <Button\n key={action.id}\n size=\"sm\"\n variant={action.variant ?? 'outlined'}\n color={action.color ?? 'secondary'}\n preIcon={action.icon}\n posIcon={action.posIcon}\n disabled={isDisabled}\n onClick={() => action.onClick(selectedItems)}\n >\n {action.label}\n </Button>\n ) : action.icon ? (\n <Button\n key={action.id}\n size=\"sm\"\n variant={action.variant ?? 'outlined'}\n color={action.color ?? 'secondary'}\n icon={action.icon}\n disabled={isDisabled}\n onClick={() => action.onClick(selectedItems)}\n />\n ) : null;\n })}\n </div>\n )}\n\n <button\n className=\"eidos-table-clear-selection\"\n onClick={clearSelection}\n aria-label=\"Clear selection\"\n >\n <X size={14} />\n </button>\n </>\n )}\n </div>\n\n {/* Right - density, column visibility, export, filter */}\n <div className=\"eidos-table-toolbar-right\">\n {showDensity && (\n <Dropdown\n placement=\"bottom\"\n align=\"end\"\n autoWidth={false}\n trigger={\n <Button variant=\"text\" size=\"sm\" preIcon={AlignJustify}>\n Density\n </Button>\n }\n content={densityPanel}\n />\n )}\n\n {showColumnVisibility && (\n <Dropdown\n placement=\"bottom\"\n align=\"end\"\n autoWidth={false}\n trigger={\n <Button variant=\"text\" size=\"sm\" preIcon={Columns3}>\n Columns\n </Button>\n }\n content={columnVisibilityPanel}\n />\n )}\n\n {showExport && (\n <Button\n variant=\"text\"\n size=\"sm\"\n icon={Download}\n onClick={handleExportCsv}\n tooltip=\"Export CSV\"\n />\n )}\n\n {showFilters && onFiltersChange && (\n <TableFiltersDropdown\n columns={columns}\n filters={filters}\n onFiltersChange={onFiltersChange}\n defaultFilters={defaultFilters}\n />\n )}\n </div>\n </div>\n )}\n\n {/* ── Table (wrapped for horizontal scroll) ─────────────────────────── */}\n <div className=\"eidos-table-scroll\">\n <table\n className={[\n 'eidos-table',\n densityClass,\n draggableColumns ? 'eidos-table--draggable-columns' : '',\n ]\n .filter(Boolean)\n .join(' ')}\n >\n <thead ref={theadRef}>\n <tr>\n {selectable && (\n <th\n className={[\n 'eidos-table-header-cell',\n 'eidos-table-checkbox-cell',\n getHeaderPinnedProps('__checkbox__').className,\n ]\n .filter(Boolean)\n .join(' ')}\n style={getHeaderPinnedProps('__checkbox__').style}\n data-col-key=\"__checkbox__\"\n >\n <Checkbox\n size=\"sm\"\n checked={allSelected}\n indeterminate={someSelected}\n onChange={toggleAll}\n aria-label=\"Select all rows\"\n />\n </th>\n )}\n\n {/* ── Headers: draggable or plain ──────────────────────────── */}\n {draggableColumns\n ? visibleColumns.map((column) => {\n const colKey = String(column.key);\n const columnType = column.type || 'data';\n const columnWidth =\n column.type === 'icon' ? 'var(--component-size-lg)' : column.width;\n const isSortable = !!column.sortable;\n const isCurrentlySorted = currentSort?.key === colKey;\n const sortDirection = isCurrentlySorted\n ? (currentSort!.direction as 'asc' | 'desc')\n : null;\n const alignment = column.align || 'left';\n const { style: pinnedStyle, className: pinnedClass } =\n getHeaderPinnedProps(colKey);\n\n return (\n <SortableColumnHeader\n key={colKey}\n id={colKey}\n column={column}\n isSortable={isSortable}\n isCurrentlySorted={!!isCurrentlySorted}\n sortDirection={sortDirection}\n alignment={alignment}\n columnType={columnType}\n columnWidth={columnWidth}\n pinnedClass={pinnedClass}\n stickyStyle={pinnedStyle}\n onSortClick={() => handleSort(colKey)}\n />\n );\n })\n : visibleColumns.map((column, index) => {\n const colKey = String(column.key);\n const columnType = column.type || 'data';\n const columnWidth =\n column.type === 'icon' ? 'var(--component-size-lg)' : column.width;\n const isSortable = column.sortable;\n const isCurrentlySorted = currentSort?.key === colKey;\n const sortDirection = isCurrentlySorted ? currentSort!.direction : null;\n const alignment = column.align || 'left';\n const alignmentClass = `eidos-table-align-${alignment}`;\n const { style: pinnedStyle, className: pinnedClass } =\n getHeaderPinnedProps(colKey);\n\n return (\n <th\n key={index}\n className={[\n 'eidos-table-header-cell',\n `eidos-table-header-cell-${columnType}`,\n isSortable ? 'eidos-table-sortable' : '',\n isCurrentlySorted ? 'eidos-table-sorted' : '',\n alignmentClass,\n pinnedClass,\n ]\n .filter(Boolean)\n .join(' ')}\n style={{ width: columnWidth, ...pinnedStyle }}\n data-col-key={colKey}\n onClick={() => isSortable && handleSort(colKey)}\n >\n <div className={`eidos-table-header-content ${alignmentClass}`}>\n <span>{column.label}</span>\n {isSortable && (\n <div className=\"eidos-table-sort-indicator\">\n {sortDirection === 'asc' && <ArrowUp size={14} />}\n {sortDirection === 'desc' && <ArrowDown size={14} />}\n {!sortDirection && <ChevronsUpDown size={14} />}\n </div>\n )}\n </div>\n </th>\n );\n })}\n </tr>\n </thead>\n <tbody>\n {displayData.length > 0 ? (\n displayData.map((item, rowIndex) => {\n const globalIndex = showPagination\n ? (currentPage - 1) * currentPageSize + rowIndex\n : rowIndex;\n const key = getRowKey(item, globalIndex);\n const isSelected = selectedSet.has(key);\n\n return (\n <tr\n key={key}\n className={[\n 'eidos-table-row',\n onRowClick ? 'eidos-table-clickable' : '',\n isSelected ? 'eidos-table-row--selected' : '',\n ]\n .filter(Boolean)\n .join(' ')}\n onClick={() => onRowClick?.(item)}\n >\n {selectable && (\n <td\n className={[\n 'eidos-table-cell',\n 'eidos-table-checkbox-cell',\n getCellPinnedProps('__checkbox__').className,\n ]\n .filter(Boolean)\n .join(' ')}\n style={getCellPinnedProps('__checkbox__').style}\n onClick={(e) => {\n e.stopPropagation();\n toggleRow(key);\n }}\n >\n {/* Wrap in a span so Checkbox click-events don't\n bubble to the <td> and cause a double-toggle.\n The <td> onClick handles toggling when the user\n clicks the cell margin outside the checkbox. */}\n <span onClick={(e) => e.stopPropagation()}>\n <Checkbox\n size=\"sm\"\n checked={isSelected}\n onChange={() => toggleRow(key)}\n aria-label={`Select row ${globalIndex + 1}`}\n />\n </span>\n </td>\n )}\n {visibleColumns.map((column, colIndex) => {\n const colKey = String(column.key);\n const value =\n typeof column.key === 'string' && column.key.includes('.')\n ? (column.key as string)\n .split('.')\n .reduce(\n (obj: unknown, k: string) => (obj as Record<string, unknown>)?.[k],\n item as unknown,\n )\n : item[column.key as keyof T];\n\n const columnType = column.type || 'data';\n const columnWidth =\n column.type === 'icon' ? 'var(--component-size-lg)' : column.width;\n const alignment = column.align || 'left';\n const alignmentClass = `eidos-table-align-${alignment}`;\n const { style: pinnedStyle, className: pinnedClass } =\n getCellPinnedProps(colKey);\n\n return (\n <td\n key={colIndex}\n className={[\n 'eidos-table-cell',\n `eidos-table-cell-${columnType}`,\n alignmentClass,\n pinnedClass,\n ]\n .filter(Boolean)\n .join(' ')}\n style={{ width: columnWidth, ...pinnedStyle }}\n // `type: 'action'` cells exist specifically to hold\n // interactive controls (a row-actions Menu/button,\n // typically) - without this, clicking them bubbles\n // straight into `onRowClick` on the <tr> below,\n // e.g. opening a details drawer at the same time as\n // (or instead of) the action itself. Mirrors the\n // selectable checkbox cell's own stopPropagation\n // above, for the same reason.\n onClick={columnType === 'action' ? (e) => e.stopPropagation() : undefined}\n >\n {column.render ? column.render(value, item) : String(value ?? '')}\n </td>\n );\n })}\n </tr>\n );\n })\n ) : (\n <tr className=\"eidos-table-empty-row\">\n <td\n colSpan={selectable ? visibleColumns.length + 1 : visibleColumns.length}\n className=\"eidos-table-cell eidos-table-empty-cell\"\n >\n {Object.keys(filters).length > 0 ? (\n <EmptyState\n icon={<SearchX />}\n title=\"No results found\"\n description=\"Try adjusting your filters or search terms.\"\n action={\n onFiltersChange ? (\n <Button\n size=\"sm\"\n variant=\"outlined\"\n color=\"primary\"\n onClick={() => onFiltersChange({})}\n >\n Clear filters\n </Button>\n ) : undefined\n }\n size=\"sm\"\n />\n ) : (\n <EmptyState\n icon={<FolderOpen />}\n title={emptyMessage}\n description=\"There are no records to display.\"\n size=\"sm\"\n />\n )}\n </td>\n </tr>\n )}\n </tbody>\n </table>\n </div>\n\n {/* ── Footer / Pagination ───────────────────────────────────────────── */}\n {showFooter && (\n <div className=\"eidos-table-footer\">\n <div className=\"eidos-table-footer-content\">\n {showPagination && data.length > 0 ? (\n <Pagination\n page={currentPage}\n totalPages={totalPages}\n onChange={handlePageChange}\n totalItems={totalItems ?? data.length}\n pageSize={currentPageSize}\n onPageSizeChange={handlePageSizeChange}\n pageSizeOptions={pageSizeOptions}\n size=\"sm\"\n />\n ) : (\n <span className=\"eidos-table-results-info\">\n Showing {data.length} of {totalItems ?? data.length} results\n </span>\n )}\n </div>\n </div>\n )}\n </div>\n );\n\n // ── Wrap with DnD context when draggableColumns=true ─────────────────────\n if (draggableColumns) {\n return (\n <DndContext sensors={sensors} onDragEnd={handleDragEnd}>\n <SortableContext items={sortableItems} strategy={horizontalListSortingStrategy}>\n {tableContent}\n </SortableContext>\n </DndContext>\n );\n }\n\n return tableContent;\n};\n"]}