@underverse-ui/underverse 0.2.74 → 0.2.76

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.
package/dist/index.js CHANGED
@@ -421,7 +421,7 @@ var Card = ({
421
421
  {
422
422
  className: cn(
423
423
  "rounded-2xl md:rounded-3xl bg-card text-card-foreground transition-[transform,box-shadow,border-color,background-color] duration-300 ease-soft",
424
- "shadow-sm md:hover:shadow-md mx-2 md:mx-0 border border-border",
424
+ "shadow-sm md:hover:shadow-md border border-border",
425
425
  hoverable && "md:hover:-translate-y-0.5 md:hover:border-primary/15",
426
426
  clickable && "cursor-pointer active:translate-y-px md:hover:bg-accent/5",
427
427
  "backdrop-blur-sm",
@@ -12970,6 +12970,103 @@ function DataTablePagination({
12970
12970
  ] });
12971
12971
  }
12972
12972
 
12973
+ // ../../components/ui/DataTable/utils/headers.ts
12974
+ function isLeafColumn(col) {
12975
+ return !col.children || col.children.length === 0;
12976
+ }
12977
+ function getLeafColumns(columns) {
12978
+ const leaves = [];
12979
+ function traverse(cols) {
12980
+ for (const col of cols) {
12981
+ if (isLeafColumn(col)) {
12982
+ leaves.push(col);
12983
+ } else if (col.children) {
12984
+ traverse(col.children);
12985
+ }
12986
+ }
12987
+ }
12988
+ traverse(columns);
12989
+ return leaves;
12990
+ }
12991
+ function getHeaderDepth(columns) {
12992
+ function getDepth(cols) {
12993
+ if (!cols || cols.length === 0) return 0;
12994
+ let maxDepth = 1;
12995
+ for (const col of cols) {
12996
+ if (col.children && col.children.length > 0) {
12997
+ maxDepth = Math.max(maxDepth, 1 + getDepth(col.children));
12998
+ }
12999
+ }
13000
+ return maxDepth;
13001
+ }
13002
+ return getDepth(columns);
13003
+ }
13004
+ function getColSpan(col) {
13005
+ if (col.colSpan !== void 0) return col.colSpan;
13006
+ if (isLeafColumn(col)) return 1;
13007
+ return col.children.reduce((sum, child) => sum + getColSpan(child), 0);
13008
+ }
13009
+ function getRowSpan(col, maxDepth, currentDepth) {
13010
+ if (col.rowSpan !== void 0) return col.rowSpan;
13011
+ if (isLeafColumn(col)) {
13012
+ return maxDepth - currentDepth + 1;
13013
+ }
13014
+ return 1;
13015
+ }
13016
+ function buildHeaderRows(columns) {
13017
+ const maxDepth = getHeaderDepth(columns);
13018
+ const rows = Array.from({ length: maxDepth }, () => []);
13019
+ function buildCell(col, rowIndex, colIndex) {
13020
+ const colSpan = getColSpan(col);
13021
+ const rowSpan = getRowSpan(col, maxDepth, rowIndex + 1);
13022
+ const isLeaf = isLeafColumn(col);
13023
+ rows[rowIndex].push({
13024
+ column: col,
13025
+ colSpan,
13026
+ rowSpan,
13027
+ isLeaf,
13028
+ rowIndex,
13029
+ colIndex
13030
+ });
13031
+ if (col.children && col.children.length > 0) {
13032
+ let childColIndex = colIndex;
13033
+ for (const child of col.children) {
13034
+ childColIndex = buildCell(child, rowIndex + 1, childColIndex);
13035
+ }
13036
+ }
13037
+ return colIndex + colSpan;
13038
+ }
13039
+ let currentColIndex = 0;
13040
+ for (const col of columns) {
13041
+ currentColIndex = buildCell(col, 0, currentColIndex);
13042
+ }
13043
+ return rows;
13044
+ }
13045
+ function filterVisibleColumns(columns, visibleKeys) {
13046
+ return columns.map((col) => {
13047
+ if (col.children) {
13048
+ const visibleChildren = filterVisibleColumns(col.children, visibleKeys);
13049
+ if (visibleChildren.length > 0) {
13050
+ return { ...col, children: visibleChildren };
13051
+ }
13052
+ return null;
13053
+ }
13054
+ return visibleKeys.has(col.key) ? col : null;
13055
+ }).filter((col) => col !== null);
13056
+ }
13057
+ function getLeafColumnsWithFixedInheritance(columns, inheritedFixed) {
13058
+ const leaves = [];
13059
+ for (const col of columns) {
13060
+ const effectiveFixed = col.fixed ?? inheritedFixed;
13061
+ if (isLeafColumn(col)) {
13062
+ leaves.push({ ...col, fixed: effectiveFixed });
13063
+ } else if (col.children) {
13064
+ leaves.push(...getLeafColumnsWithFixedInheritance(col.children, effectiveFixed));
13065
+ }
13066
+ }
13067
+ return leaves;
13068
+ }
13069
+
12973
13070
  // ../../components/ui/DataTable/components/Toolbar.tsx
12974
13071
  import { jsx as jsx58, jsxs as jsxs52 } from "react/jsx-runtime";
12975
13072
  function DataTableToolbar({
@@ -13004,36 +13101,39 @@ function DataTableToolbar({
13004
13101
  ]
13005
13102
  }
13006
13103
  ),
13007
- enableColumnVisibilityToggle && /* @__PURE__ */ jsx58(
13008
- DropdownMenu_default,
13009
- {
13010
- trigger: /* @__PURE__ */ jsxs52(Button_default, { variant: "ghost", size: "sm", className: "h-8 px-2", children: [
13011
- /* @__PURE__ */ jsx58("svg", { className: "w-4 h-4 mr-1", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx58(
13012
- "path",
13104
+ enableColumnVisibilityToggle && (() => {
13105
+ const leafCols = getLeafColumns(columns);
13106
+ return /* @__PURE__ */ jsx58(
13107
+ DropdownMenu_default,
13108
+ {
13109
+ trigger: /* @__PURE__ */ jsxs52(Button_default, { variant: "ghost", size: "sm", className: "h-8 px-2", children: [
13110
+ /* @__PURE__ */ jsx58("svg", { className: "w-4 h-4 mr-1", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx58(
13111
+ "path",
13112
+ {
13113
+ strokeLinecap: "round",
13114
+ strokeLinejoin: "round",
13115
+ strokeWidth: 2,
13116
+ d: "M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2"
13117
+ }
13118
+ ) }),
13119
+ labels?.columns || t("columns")
13120
+ ] }),
13121
+ children: leafCols.map((c) => /* @__PURE__ */ jsxs52(
13122
+ DropdownMenuItem,
13013
13123
  {
13014
- strokeLinecap: "round",
13015
- strokeLinejoin: "round",
13016
- strokeWidth: 2,
13017
- d: "M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2"
13018
- }
13019
- ) }),
13020
- labels?.columns || t("columns")
13021
- ] }),
13022
- children: columns.map((c) => /* @__PURE__ */ jsxs52(
13023
- DropdownMenuItem,
13024
- {
13025
- onClick: () => {
13026
- setVisibleCols((prev) => prev.includes(c.key) ? prev.filter((k) => k !== c.key) : [...prev, c.key]);
13124
+ onClick: () => {
13125
+ setVisibleCols((prev) => prev.includes(c.key) ? prev.filter((k) => k !== c.key) : [...prev, c.key]);
13126
+ },
13127
+ children: [
13128
+ /* @__PURE__ */ jsx58("input", { type: "checkbox", className: "mr-2 rounded-md border-border", readOnly: true, checked: visibleCols.includes(c.key) }),
13129
+ /* @__PURE__ */ jsx58("span", { className: "truncate", children: c.title })
13130
+ ]
13027
13131
  },
13028
- children: [
13029
- /* @__PURE__ */ jsx58("input", { type: "checkbox", className: "mr-2 rounded-md border-border", readOnly: true, checked: visibleCols.includes(c.key) }),
13030
- /* @__PURE__ */ jsx58("span", { className: "truncate", children: c.title })
13031
- ]
13032
- },
13033
- c.key
13034
- ))
13035
- }
13036
- ),
13132
+ c.key
13133
+ ))
13134
+ }
13135
+ );
13136
+ })(),
13037
13137
  enableHeaderAlignToggle && /* @__PURE__ */ jsx58(
13038
13138
  DropdownMenu_default,
13039
13139
  {
@@ -13108,32 +13208,40 @@ import React50 from "react";
13108
13208
  // ../../components/ui/DataTable/utils/columns.ts
13109
13209
  function getColumnWidth(col, fallback = 150) {
13110
13210
  if (typeof col.width === "number") return col.width;
13111
- const raw = col.width ? String(col.width) : String(fallback);
13112
- const parsed = parseInt(raw, 10);
13113
- return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
13211
+ if (col.width) {
13212
+ const raw = String(col.width);
13213
+ const parsed = parseInt(raw, 10);
13214
+ if (Number.isFinite(parsed) && parsed > 0) return parsed;
13215
+ }
13216
+ if (col.children && col.children.length > 0) {
13217
+ return col.children.reduce((sum, child) => sum + getColumnWidth(child, fallback), 0);
13218
+ }
13219
+ return fallback;
13114
13220
  }
13115
13221
 
13116
13222
  // ../../components/ui/DataTable/hooks/useStickyColumns.ts
13117
- function useStickyColumns(visibleColumns) {
13223
+ function useStickyColumns(columns, visibleKeys) {
13224
+ const visibleColumns = React50.useMemo(() => filterVisibleColumns(columns, visibleKeys), [columns, visibleKeys]);
13225
+ const leafColumns = React50.useMemo(() => getLeafColumnsWithFixedInheritance(visibleColumns), [visibleColumns]);
13118
13226
  const stickyPositions = React50.useMemo(() => {
13119
13227
  const positions = {};
13120
13228
  let leftOffset = 0;
13121
- for (const col of visibleColumns) {
13229
+ for (const col of leafColumns) {
13122
13230
  if (col.fixed === "left") {
13123
13231
  positions[col.key] = { left: leftOffset };
13124
13232
  leftOffset += getColumnWidth(col);
13125
13233
  }
13126
13234
  }
13127
13235
  let rightOffset = 0;
13128
- for (let i = visibleColumns.length - 1; i >= 0; i--) {
13129
- const col = visibleColumns[i];
13236
+ for (let i = leafColumns.length - 1; i >= 0; i--) {
13237
+ const col = leafColumns[i];
13130
13238
  if (col.fixed === "right") {
13131
13239
  positions[col.key] = { right: rightOffset };
13132
13240
  rightOffset += getColumnWidth(col);
13133
13241
  }
13134
13242
  }
13135
13243
  return positions;
13136
- }, [visibleColumns]);
13244
+ }, [leafColumns]);
13137
13245
  const getStickyColumnStyle = React50.useCallback(
13138
13246
  (col) => {
13139
13247
  if (!col.fixed) return {};
@@ -13163,7 +13271,109 @@ function useStickyColumns(visibleColumns) {
13163
13271
  isStripedRow ? "bg-muted!" : "bg-card!"
13164
13272
  );
13165
13273
  }, []);
13166
- return { getStickyColumnStyle, getStickyHeaderClass, getStickyCellClass };
13274
+ const getStickyHeaderCellStyle = React50.useCallback(
13275
+ (headerCell) => {
13276
+ const col = headerCell.column;
13277
+ if (headerCell.isLeaf) {
13278
+ return getStickyColumnStyle(col);
13279
+ }
13280
+ const descendants = getLeafColumns([col]);
13281
+ const stickyDescendants = descendants.filter((d) => d.fixed);
13282
+ if (stickyDescendants.length === 0) return {};
13283
+ const firstSticky = stickyDescendants[0];
13284
+ const lastSticky = stickyDescendants[stickyDescendants.length - 1];
13285
+ if (firstSticky.fixed === "left") {
13286
+ const pos = stickyPositions[firstSticky.key];
13287
+ return pos?.left !== void 0 ? { left: pos.left } : {};
13288
+ }
13289
+ if (lastSticky.fixed === "right") {
13290
+ const pos = stickyPositions[lastSticky.key];
13291
+ return pos?.right !== void 0 ? { right: pos.right } : {};
13292
+ }
13293
+ return {};
13294
+ },
13295
+ [stickyPositions, getStickyColumnStyle]
13296
+ );
13297
+ return {
13298
+ getStickyColumnStyle,
13299
+ getStickyHeaderClass,
13300
+ getStickyCellClass,
13301
+ getStickyHeaderCellStyle
13302
+ };
13303
+ }
13304
+
13305
+ // ../../components/ui/DataTable/utils/validation.ts
13306
+ function validateColumns(columns) {
13307
+ const warnings = [];
13308
+ const keys = /* @__PURE__ */ new Set();
13309
+ function validate(cols, path = "") {
13310
+ for (const col of cols) {
13311
+ const fullPath = path ? `${path}.${col.key}` : col.key;
13312
+ if (keys.has(col.key)) {
13313
+ warnings.push(`Duplicate key "${col.key}" at ${fullPath}`);
13314
+ }
13315
+ keys.add(col.key);
13316
+ const isGroup = col.children && col.children.length > 0;
13317
+ if (isGroup) {
13318
+ if (col.dataIndex) {
13319
+ warnings.push(`Group column "${fullPath}" has dataIndex (will be ignored)`);
13320
+ }
13321
+ if (col.sortable) {
13322
+ warnings.push(`Group column "${fullPath}" has sortable (will be ignored)`);
13323
+ }
13324
+ if (col.filter) {
13325
+ warnings.push(`Group column "${fullPath}" has filter (will be ignored)`);
13326
+ }
13327
+ if (col.render) {
13328
+ warnings.push(`Group column "${fullPath}" has render function (will be ignored)`);
13329
+ }
13330
+ if (col.colSpan !== void 0) {
13331
+ const actualColSpan = getColSpan(col);
13332
+ if (col.colSpan !== actualColSpan) {
13333
+ warnings.push(
13334
+ `Column "${fullPath}" has colSpan=${col.colSpan} but structure suggests ${actualColSpan} (based on ${col.children.length} children)`
13335
+ );
13336
+ }
13337
+ }
13338
+ if (col.fixed) {
13339
+ const conflictingChildren = col.children.filter((c) => c.fixed && c.fixed !== col.fixed);
13340
+ if (conflictingChildren.length > 0) {
13341
+ warnings.push(
13342
+ `Group column "${fullPath}" has fixed="${col.fixed}" but children have different fixed values: ${conflictingChildren.map((c) => c.key).join(", ")}`
13343
+ );
13344
+ }
13345
+ }
13346
+ validate(col.children, fullPath);
13347
+ } else {
13348
+ if (col.children !== void 0) {
13349
+ warnings.push(`Leaf column "${fullPath}" has children property (should be omitted for leaf columns)`);
13350
+ }
13351
+ }
13352
+ }
13353
+ }
13354
+ validate(columns);
13355
+ const depth = getHeaderDepth(columns);
13356
+ if (depth > 4) {
13357
+ warnings.push(`Header depth is ${depth} rows. Consider simplifying - too many header rows may impact user experience.`);
13358
+ }
13359
+ function checkMixedSticky(cols, parentPath = "") {
13360
+ for (const col of cols) {
13361
+ if (col.children && col.children.length > 0) {
13362
+ const childrenFixed = col.children.map((c) => c.fixed);
13363
+ const hasStickyChild = childrenFixed.some((f) => f !== void 0);
13364
+ const hasNonStickyChild = childrenFixed.some((f) => f === void 0);
13365
+ if (hasStickyChild && hasNonStickyChild) {
13366
+ const fullPath = parentPath ? `${parentPath}.${col.key}` : col.key;
13367
+ warnings.push(
13368
+ `Group column "${fullPath}" has mixed sticky children (some fixed, some not). This may cause visual separation when scrolling.`
13369
+ );
13370
+ }
13371
+ checkMixedSticky(col.children, parentPath ? `${parentPath}.${col.key}` : col.key);
13372
+ }
13373
+ }
13374
+ }
13375
+ checkMixedSticky(columns);
13376
+ return warnings;
13167
13377
  }
13168
13378
 
13169
13379
  // ../../components/ui/DataTable/DataTable.tsx
@@ -13199,6 +13409,12 @@ function DataTable({
13199
13409
  const [density, setDensity] = React51.useState("normal");
13200
13410
  const [curPage, setCurPage] = React51.useState(page);
13201
13411
  const { curPageSize, setCurPageSize } = usePageSizeStorage({ pageSize, storageKey });
13412
+ React51.useEffect(() => {
13413
+ if (process.env.NODE_ENV === "development") {
13414
+ const warnings = validateColumns(columns);
13415
+ warnings.forEach((w) => console.warn(`[DataTable] ${w}`));
13416
+ }
13417
+ }, [columns]);
13202
13418
  React51.useEffect(() => {
13203
13419
  const newColKeys = columns.filter((c) => c.visible !== false).map((c) => c.key);
13204
13420
  setVisibleCols((prev) => {
@@ -13223,11 +13439,19 @@ function DataTable({
13223
13439
  const densityRowClass = density === "compact" ? "h-9" : density === "comfortable" ? "h-14" : "h-12";
13224
13440
  const cellPadding = density === "compact" ? "py-1.5 px-3" : density === "comfortable" ? "py-3 px-4" : "py-2.5 px-4";
13225
13441
  const visibleColsSet = React51.useMemo(() => new Set(visibleCols), [visibleCols]);
13226
- const visibleColumns = columns.filter((c) => visibleColsSet.has(c.key));
13227
- const totalColumnsWidth = React51.useMemo(() => {
13228
- return visibleColumns.reduce((sum, col) => sum + getColumnWidth(col), 0);
13442
+ const visibleColumns = React51.useMemo(() => {
13443
+ return filterVisibleColumns(columns, visibleColsSet);
13444
+ }, [columns, visibleColsSet]);
13445
+ const leafColumns = React51.useMemo(() => {
13446
+ return getLeafColumns(visibleColumns);
13229
13447
  }, [visibleColumns]);
13230
- const { getStickyCellClass, getStickyColumnStyle, getStickyHeaderClass } = useStickyColumns(visibleColumns);
13448
+ const totalColumnsWidth = React51.useMemo(() => {
13449
+ return leafColumns.reduce((sum, col) => sum + getColumnWidth(col), 0);
13450
+ }, [leafColumns]);
13451
+ const { getStickyCellClass, getStickyColumnStyle, getStickyHeaderClass, getStickyHeaderCellStyle } = useStickyColumns(
13452
+ columns,
13453
+ visibleColsSet
13454
+ );
13231
13455
  const getRowKey = (row, idx) => {
13232
13456
  if (!rowKey) return String(idx);
13233
13457
  if (typeof rowKey === "function") return String(rowKey(row));
@@ -13283,127 +13507,150 @@ function DataTable({
13283
13507
  }
13284
13508
  return null;
13285
13509
  };
13286
- const renderHeader = /* @__PURE__ */ jsx59(TableRow, { children: visibleColumns.map((col, colIdx) => {
13287
- const prevCol = colIdx > 0 ? visibleColumns[colIdx - 1] : null;
13510
+ const headerRows = React51.useMemo(() => buildHeaderRows(visibleColumns), [visibleColumns]);
13511
+ const renderHeaderContent = (col, isLeaf) => {
13512
+ if (!isLeaf) {
13513
+ return /* @__PURE__ */ jsx59(
13514
+ "div",
13515
+ {
13516
+ className: cn(
13517
+ "flex items-center gap-1 min-h-10",
13518
+ col.align === "right" && "justify-end",
13519
+ col.align === "center" && "justify-center",
13520
+ !col.align && "justify-start"
13521
+ ),
13522
+ children: /* @__PURE__ */ jsx59("span", { className: "font-medium text-sm whitespace-nowrap", children: col.title })
13523
+ }
13524
+ );
13525
+ }
13526
+ const isRightAlign = col.align === "right" || !col.align && headerAlign === "right";
13527
+ const isCenterAlign = col.align === "center" || !col.align && headerAlign === "center";
13528
+ const titleContent = /* @__PURE__ */ jsxs53("div", { className: "flex items-center gap-1", children: [
13529
+ /* @__PURE__ */ jsx59("span", { className: "font-medium text-sm whitespace-nowrap", children: col.title }),
13530
+ col.sortable && /* @__PURE__ */ jsx59(
13531
+ "button",
13532
+ {
13533
+ className: cn(
13534
+ "p-1 rounded-lg transition-all duration-200 hover:bg-accent",
13535
+ sort?.key === col.key ? "opacity-100 bg-accent" : "opacity-60 hover:opacity-100"
13536
+ ),
13537
+ onClick: () => {
13538
+ setCurPage(1);
13539
+ setSort((s) => {
13540
+ if (!s || s.key !== col.key) return { key: col.key, order: "asc" };
13541
+ if (s.order === "asc") return { key: col.key, order: "desc" };
13542
+ return null;
13543
+ });
13544
+ },
13545
+ "aria-label": "Sort",
13546
+ title: `Sort by ${String(col.title)}`,
13547
+ children: /* @__PURE__ */ jsxs53("svg", { width: "14", height: "14", viewBox: "0 0 20 20", fill: "none", className: "inline-block", children: [
13548
+ /* @__PURE__ */ jsx59(
13549
+ "path",
13550
+ {
13551
+ d: "M7 8l3-3 3 3",
13552
+ stroke: "currentColor",
13553
+ strokeWidth: "1.5",
13554
+ strokeLinecap: "round",
13555
+ strokeLinejoin: "round",
13556
+ opacity: sort?.key === col.key && sort.order === "asc" ? 1 : 0.4
13557
+ }
13558
+ ),
13559
+ /* @__PURE__ */ jsx59(
13560
+ "path",
13561
+ {
13562
+ d: "M7 12l3 3 3-3",
13563
+ stroke: "currentColor",
13564
+ strokeWidth: "1.5",
13565
+ strokeLinecap: "round",
13566
+ strokeLinejoin: "round",
13567
+ opacity: sort?.key === col.key && sort.order === "desc" ? 1 : 0.4
13568
+ }
13569
+ )
13570
+ ] })
13571
+ }
13572
+ )
13573
+ ] });
13574
+ const filterContent = col.filter ? /* @__PURE__ */ jsx59(
13575
+ Popover,
13576
+ {
13577
+ placement: "bottom-start",
13578
+ trigger: /* @__PURE__ */ jsx59(
13579
+ "button",
13580
+ {
13581
+ className: cn(
13582
+ "p-1.5 rounded-lg transition-all duration-200 hover:bg-accent",
13583
+ filters[col.key] ? "bg-accent text-primary" : "text-muted-foreground"
13584
+ ),
13585
+ "aria-label": "Filter",
13586
+ title: `Filter by ${String(col.title)}`,
13587
+ children: /* @__PURE__ */ jsx59(FilterIcon, { className: "w-4 h-4" })
13588
+ }
13589
+ ),
13590
+ children: /* @__PURE__ */ jsxs53("div", { className: "p-3 w-64 space-y-3", children: [
13591
+ /* @__PURE__ */ jsxs53("div", { className: "flex items-center justify-between", children: [
13592
+ /* @__PURE__ */ jsx59("div", { className: "text-sm font-medium", children: col.title }),
13593
+ filters[col.key] !== void 0 && filters[col.key] !== null && filters[col.key] !== "" && /* @__PURE__ */ jsx59(
13594
+ "button",
13595
+ {
13596
+ onClick: () => {
13597
+ setCurPage(1);
13598
+ setFilters((f) => ({ ...f, [col.key]: void 0 }));
13599
+ },
13600
+ className: "text-xs text-destructive hover:underline",
13601
+ children: t("clearFilter")
13602
+ }
13603
+ )
13604
+ ] }),
13605
+ renderFilterControl(col)
13606
+ ] })
13607
+ }
13608
+ ) : null;
13609
+ return /* @__PURE__ */ jsx59(
13610
+ "div",
13611
+ {
13612
+ className: cn(
13613
+ "flex items-center gap-2 select-none min-h-10",
13614
+ isRightAlign && "justify-end",
13615
+ isCenterAlign && "justify-center",
13616
+ !isRightAlign && !isCenterAlign && "justify-start"
13617
+ ),
13618
+ children: isRightAlign ? /* @__PURE__ */ jsxs53(Fragment22, { children: [
13619
+ filterContent,
13620
+ titleContent
13621
+ ] }) : /* @__PURE__ */ jsxs53(Fragment22, { children: [
13622
+ titleContent,
13623
+ filterContent
13624
+ ] })
13625
+ }
13626
+ );
13627
+ };
13628
+ const renderHeader = /* @__PURE__ */ jsx59(Fragment22, { children: headerRows.map((row, rowIndex) => /* @__PURE__ */ jsx59(TableRow, { children: row.map((headerCell, cellIndex) => {
13629
+ const { column: col, colSpan, rowSpan, isLeaf } = headerCell;
13630
+ const prevCell = cellIndex > 0 ? row[cellIndex - 1] : null;
13631
+ const prevCol = prevCell?.column;
13288
13632
  const isAfterFixedLeft = prevCol?.fixed === "left";
13289
- const showBorderLeft = columnDividers && colIdx > 0 && !isAfterFixedLeft && !col.fixed;
13633
+ const showBorderLeft = columnDividers && cellIndex > 0 && !isAfterFixedLeft && !col.fixed;
13290
13634
  return /* @__PURE__ */ jsx59(
13291
13635
  TableHead,
13292
13636
  {
13293
- style: { width: col.width, ...getStickyColumnStyle(col) },
13637
+ colSpan,
13638
+ rowSpan,
13639
+ style: {
13640
+ width: col.width,
13641
+ ...getStickyHeaderCellStyle(headerCell)
13642
+ },
13294
13643
  className: cn(
13295
13644
  (col.align === "right" || !col.align && headerAlign === "right") && "text-right",
13296
13645
  (col.align === "center" || !col.align && headerAlign === "center") && "text-center",
13297
13646
  showBorderLeft && "border-l border-border/60",
13298
13647
  getStickyHeaderClass(col)
13299
13648
  ),
13300
- children: (() => {
13301
- const isRightAlign = col.align === "right" || !col.align && headerAlign === "right";
13302
- const isCenterAlign = col.align === "center" || !col.align && headerAlign === "center";
13303
- const titleContent = /* @__PURE__ */ jsxs53("div", { className: cn("flex items-center gap-1", !col.fixed && "min-w-0 shrink"), children: [
13304
- /* @__PURE__ */ jsx59("span", { className: cn("font-medium text-sm", !col.fixed && "truncate"), children: col.title }),
13305
- col.sortable && /* @__PURE__ */ jsx59(
13306
- "button",
13307
- {
13308
- className: cn(
13309
- "p-1 rounded-lg transition-all duration-200 hover:bg-accent",
13310
- sort?.key === col.key ? "opacity-100 bg-accent" : "opacity-60 hover:opacity-100"
13311
- ),
13312
- onClick: () => {
13313
- setCurPage(1);
13314
- setSort((s) => {
13315
- if (!s || s.key !== col.key) return { key: col.key, order: "asc" };
13316
- if (s.order === "asc") return { key: col.key, order: "desc" };
13317
- return null;
13318
- });
13319
- },
13320
- "aria-label": "Sort",
13321
- title: `Sort by ${String(col.title)}`,
13322
- children: /* @__PURE__ */ jsxs53("svg", { width: "14", height: "14", viewBox: "0 0 20 20", fill: "none", className: "inline-block", children: [
13323
- /* @__PURE__ */ jsx59(
13324
- "path",
13325
- {
13326
- d: "M7 8l3-3 3 3",
13327
- stroke: "currentColor",
13328
- strokeWidth: "1.5",
13329
- strokeLinecap: "round",
13330
- strokeLinejoin: "round",
13331
- opacity: sort?.key === col.key && sort.order === "asc" ? 1 : 0.4
13332
- }
13333
- ),
13334
- /* @__PURE__ */ jsx59(
13335
- "path",
13336
- {
13337
- d: "M7 12l3 3 3-3",
13338
- stroke: "currentColor",
13339
- strokeWidth: "1.5",
13340
- strokeLinecap: "round",
13341
- strokeLinejoin: "round",
13342
- opacity: sort?.key === col.key && sort.order === "desc" ? 1 : 0.4
13343
- }
13344
- )
13345
- ] })
13346
- }
13347
- )
13348
- ] });
13349
- const filterContent = col.filter ? /* @__PURE__ */ jsx59(
13350
- Popover,
13351
- {
13352
- placement: "bottom-start",
13353
- trigger: /* @__PURE__ */ jsx59(
13354
- "button",
13355
- {
13356
- className: cn(
13357
- "p-1.5 rounded-lg transition-all duration-200 hover:bg-accent",
13358
- filters[col.key] ? "bg-accent text-primary" : "text-muted-foreground"
13359
- ),
13360
- "aria-label": "Filter",
13361
- title: `Filter by ${String(col.title)}`,
13362
- children: /* @__PURE__ */ jsx59(FilterIcon, { className: "w-4 h-4" })
13363
- }
13364
- ),
13365
- children: /* @__PURE__ */ jsxs53("div", { className: "p-3 w-64 space-y-3", children: [
13366
- /* @__PURE__ */ jsxs53("div", { className: "flex items-center justify-between", children: [
13367
- /* @__PURE__ */ jsx59("div", { className: "text-sm font-medium", children: col.title }),
13368
- filters[col.key] !== void 0 && filters[col.key] !== null && filters[col.key] !== "" && /* @__PURE__ */ jsx59(
13369
- "button",
13370
- {
13371
- onClick: () => {
13372
- setCurPage(1);
13373
- setFilters((f) => ({ ...f, [col.key]: void 0 }));
13374
- },
13375
- className: "text-xs text-destructive hover:underline",
13376
- children: t("clearFilter")
13377
- }
13378
- )
13379
- ] }),
13380
- renderFilterControl(col)
13381
- ] })
13382
- }
13383
- ) : null;
13384
- return /* @__PURE__ */ jsx59(
13385
- "div",
13386
- {
13387
- className: cn(
13388
- "flex items-center gap-2 select-none min-h-10",
13389
- isRightAlign && "justify-end",
13390
- isCenterAlign && "justify-center",
13391
- !isRightAlign && !isCenterAlign && "justify-start"
13392
- ),
13393
- children: isRightAlign ? /* @__PURE__ */ jsxs53(Fragment22, { children: [
13394
- filterContent,
13395
- titleContent
13396
- ] }) : /* @__PURE__ */ jsxs53(Fragment22, { children: [
13397
- titleContent,
13398
- filterContent
13399
- ] })
13400
- }
13401
- );
13402
- })()
13649
+ children: renderHeaderContent(col, isLeaf)
13403
13650
  },
13404
13651
  col.key
13405
13652
  );
13406
- }) });
13653
+ }) }, `header-row-${rowIndex}`)) });
13407
13654
  const processedData = React51.useMemo(() => {
13408
13655
  if (isServerMode) return data;
13409
13656
  let result = [...data];
@@ -13479,7 +13726,7 @@ function DataTable({
13479
13726
  style: { minWidth: totalColumnsWidth > 0 ? `${totalColumnsWidth}px` : void 0 },
13480
13727
  children: [
13481
13728
  /* @__PURE__ */ jsx59(TableHeader, { children: renderHeader }),
13482
- /* @__PURE__ */ jsx59(TableBody, { children: loading2 ? /* @__PURE__ */ jsx59(TableRow, { children: /* @__PURE__ */ jsx59(TableCell, { colSpan: visibleColumns.length, className: "text-center py-8", children: /* @__PURE__ */ jsxs53("div", { className: "flex items-center justify-center gap-2 text-muted-foreground", children: [
13729
+ /* @__PURE__ */ jsx59(TableBody, { children: loading2 ? /* @__PURE__ */ jsx59(TableRow, { children: /* @__PURE__ */ jsx59(TableCell, { colSpan: leafColumns.length, className: "text-center py-8", children: /* @__PURE__ */ jsxs53("div", { className: "flex items-center justify-center gap-2 text-muted-foreground", children: [
13483
13730
  /* @__PURE__ */ jsxs53("svg", { className: "animate-spin h-4 w-4", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [
13484
13731
  /* @__PURE__ */ jsx59("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
13485
13732
  /* @__PURE__ */ jsx59(
@@ -13495,7 +13742,7 @@ function DataTable({
13495
13742
  t("loading"),
13496
13743
  "\u2026"
13497
13744
  ] })
13498
- ] }) }) }) : !displayedData || displayedData.length === 0 ? /* @__PURE__ */ jsx59(TableRow, { children: /* @__PURE__ */ jsx59(TableCell, { colSpan: visibleColumns.length, className: "text-center py-6 text-muted-foreground", children: t("noData") }) }) : displayedData.map((row, idx) => {
13745
+ ] }) }) }) : !displayedData || displayedData.length === 0 ? /* @__PURE__ */ jsx59(TableRow, { children: /* @__PURE__ */ jsx59(TableCell, { colSpan: leafColumns.length, className: "text-center py-6 text-muted-foreground", children: t("noData") }) }) : displayedData.map((row, idx) => {
13499
13746
  const isLastRow = idx === displayedData.length - 1;
13500
13747
  return /* @__PURE__ */ jsx59(
13501
13748
  TableRow,
@@ -13505,10 +13752,10 @@ function DataTable({
13505
13752
  contentVisibility: "auto",
13506
13753
  containIntrinsicSize: density === "compact" ? "0 36px" : density === "comfortable" ? "0 56px" : "0 48px"
13507
13754
  },
13508
- children: visibleColumns.map((col, colIdx) => {
13755
+ children: leafColumns.map((col, colIdx) => {
13509
13756
  const value = col.dataIndex ? row[col.dataIndex] : void 0;
13510
13757
  const isStripedRow = striped && idx % 2 === 0;
13511
- const prevCol = colIdx > 0 ? visibleColumns[colIdx - 1] : null;
13758
+ const prevCol = colIdx > 0 ? leafColumns[colIdx - 1] : null;
13512
13759
  const isAfterFixedLeft = prevCol?.fixed === "left";
13513
13760
  const showBorderLeft = columnDividers && colIdx > 0 && !isAfterFixedLeft && !col.fixed;
13514
13761
  return /* @__PURE__ */ jsx59(
@@ -13520,8 +13767,8 @@ function DataTable({
13520
13767
  col.align === "right" && "text-right",
13521
13768
  col.align === "center" && "text-center",
13522
13769
  showBorderLeft && "border-l border-border/60",
13523
- isLastRow && col === visibleColumns[0] && "rounded-bl-2xl md:rounded-bl-3xl",
13524
- isLastRow && col === visibleColumns[visibleColumns.length - 1] && "rounded-br-2xl md:rounded-br-3xl",
13770
+ isLastRow && col === leafColumns[0] && "rounded-bl-2xl md:rounded-bl-3xl",
13771
+ isLastRow && col === leafColumns[leafColumns.length - 1] && "rounded-br-2xl md:rounded-br-3xl",
13525
13772
  getStickyCellClass(col, isStripedRow),
13526
13773
  !col.fixed && isStripedRow && "bg-muted/50"
13527
13774
  ),