@underverse-ui/underverse 0.2.68 → 0.2.70

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.d.cts CHANGED
@@ -1702,6 +1702,10 @@ interface DataTableProps<T> {
1702
1702
  className?: string;
1703
1703
  /** Key để lưu pageSize vào localStorage. Nếu không cung cấp, pageSize sẽ không được persist */
1704
1704
  storageKey?: string;
1705
+ /** Bật sticky header khi cuộn. Mặc định là false */
1706
+ stickyHeader?: boolean;
1707
+ /** Chiều cao tối đa của bảng khi bật stickyHeader (mặc định: 500px) */
1708
+ maxHeight?: number | string;
1705
1709
  labels?: {
1706
1710
  density?: string;
1707
1711
  columns?: string;
@@ -1715,7 +1719,7 @@ interface DataTableProps<T> {
1715
1719
  };
1716
1720
  }
1717
1721
  declare function DataTable<T extends Record<string, any>>({ columns, data, rowKey, loading, total, page, pageSize, pageSizeOptions, onQueryChange, caption, toolbar, enableColumnVisibilityToggle, enableDensityToggle, enableHeaderAlignToggle, striped, // Mặc định bật màu nền sẽn kẽ cho các dòng
1718
- columnDividers, className, storageKey, labels, }: DataTableProps<T>): react_jsx_runtime.JSX.Element;
1722
+ columnDividers, className, storageKey, stickyHeader, maxHeight, labels, }: DataTableProps<T>): react_jsx_runtime.JSX.Element;
1719
1723
 
1720
1724
  interface TableProps extends React__default.HTMLAttributes<HTMLTableElement> {
1721
1725
  containerClassName?: string;
package/dist/index.d.ts CHANGED
@@ -1702,6 +1702,10 @@ interface DataTableProps<T> {
1702
1702
  className?: string;
1703
1703
  /** Key để lưu pageSize vào localStorage. Nếu không cung cấp, pageSize sẽ không được persist */
1704
1704
  storageKey?: string;
1705
+ /** Bật sticky header khi cuộn. Mặc định là false */
1706
+ stickyHeader?: boolean;
1707
+ /** Chiều cao tối đa của bảng khi bật stickyHeader (mặc định: 500px) */
1708
+ maxHeight?: number | string;
1705
1709
  labels?: {
1706
1710
  density?: string;
1707
1711
  columns?: string;
@@ -1715,7 +1719,7 @@ interface DataTableProps<T> {
1715
1719
  };
1716
1720
  }
1717
1721
  declare function DataTable<T extends Record<string, any>>({ columns, data, rowKey, loading, total, page, pageSize, pageSizeOptions, onQueryChange, caption, toolbar, enableColumnVisibilityToggle, enableDensityToggle, enableHeaderAlignToggle, striped, // Mặc định bật màu nền sẽn kẽ cho các dòng
1718
- columnDividers, className, storageKey, labels, }: DataTableProps<T>): react_jsx_runtime.JSX.Element;
1722
+ columnDividers, className, storageKey, stickyHeader, maxHeight, labels, }: DataTableProps<T>): react_jsx_runtime.JSX.Element;
1719
1723
 
1720
1724
  interface TableProps extends React__default.HTMLAttributes<HTMLTableElement> {
1721
1725
  containerClassName?: string;
package/dist/index.js CHANGED
@@ -12838,7 +12838,7 @@ var Table = React47.forwardRef(({ className, containerClassName, ...props }, ref
12838
12838
  }
12839
12839
  ));
12840
12840
  Table.displayName = "Table";
12841
- var TableHeader = React47.forwardRef(({ className, children, filterRow, ...props }, ref) => /* @__PURE__ */ jsxs50("thead", { ref, className: cn("[&_tr]:border-b [&_tr]:border-border", "bg-muted/50", className), ...props, children: [
12841
+ var TableHeader = React47.forwardRef(({ className, children, filterRow, ...props }, ref) => /* @__PURE__ */ jsxs50("thead", { ref, className: cn("[&_tr]:border-b [&_tr]:border-border", "bg-muted", className), ...props, children: [
12842
12842
  children,
12843
12843
  filterRow
12844
12844
  ] }));
@@ -12907,6 +12907,8 @@ function DataTable({
12907
12907
  columnDividers = false,
12908
12908
  className,
12909
12909
  storageKey,
12910
+ stickyHeader = true,
12911
+ maxHeight = 500,
12910
12912
  labels
12911
12913
  }) {
12912
12914
  const t = useTranslations("Common");
@@ -12970,6 +12972,12 @@ function DataTable({
12970
12972
  const cellPadding = density === "compact" ? "py-1.5 px-3" : density === "comfortable" ? "py-3 px-4" : "py-2.5 px-4";
12971
12973
  const visibleColsSet = React48.useMemo(() => new Set(visibleCols), [visibleCols]);
12972
12974
  const visibleColumns = columns.filter((c) => visibleColsSet.has(c.key));
12975
+ const totalColumnsWidth = React48.useMemo(() => {
12976
+ return visibleColumns.reduce((sum, col) => {
12977
+ const colWidth = typeof col.width === "number" ? col.width : parseInt(String(col.width) || "150", 10);
12978
+ return sum + colWidth;
12979
+ }, 0);
12980
+ }, [visibleColumns]);
12973
12981
  const stickyPositions = React48.useMemo(() => {
12974
12982
  const positions = {};
12975
12983
  let leftOffset = 0;
@@ -12994,11 +13002,11 @@ function DataTable({
12994
13002
  const getStickyColumnClass = (col, isHeader = false) => {
12995
13003
  if (!col.fixed) return "";
12996
13004
  return cn(
12997
- "sticky z-10 bg-background",
12998
- col.fixed === "left" && "shadow-[2px_0_5px_-2px_rgba(0,0,0,0.1)]",
12999
- col.fixed === "right" && "shadow-[-2px_0_5px_-2px_rgba(0,0,0,0.1)]",
13000
- isHeader && "z-20"
13001
- // Header cần z-index cao hơn
13005
+ "sticky",
13006
+ col.fixed === "left" && "left-0 shadow-[2px_0_5px_-2px_rgba(0,0,0,0.15)]",
13007
+ col.fixed === "right" && "right-0 shadow-[-2px_0_5px_-2px_rgba(0,0,0,0.15)]",
13008
+ // Header fixed column cần z-index cao hơn thead (z-30) để không bị che
13009
+ isHeader ? "z-50 bg-muted!" : "z-10 bg-card!"
13002
13010
  );
13003
13011
  };
13004
13012
  const getStickyColumnStyle = (col) => {
@@ -13066,129 +13074,144 @@ function DataTable({
13066
13074
  }
13067
13075
  return null;
13068
13076
  };
13069
- const renderHeader = /* @__PURE__ */ jsx57(TableRow, { children: visibleColumns.map((col, colIdx) => /* @__PURE__ */ jsx57(
13070
- TableHead,
13071
- {
13072
- style: { width: col.width, ...getStickyColumnStyle(col) },
13073
- className: cn(
13074
- // Use column-specific align if defined, otherwise use global headerAlign
13075
- (col.align === "right" || !col.align && headerAlign === "right") && "text-right",
13076
- (col.align === "center" || !col.align && headerAlign === "center") && "text-center",
13077
- columnDividers && colIdx > 0 && "border-l border-border/60",
13078
- getStickyColumnClass(col, true)
13079
- ),
13080
- children: (() => {
13081
- const isRightAlign = col.align === "right" || !col.align && headerAlign === "right";
13082
- const isCenterAlign = col.align === "center" || !col.align && headerAlign === "center";
13083
- const titleContent = /* @__PURE__ */ jsxs51("div", { className: "flex items-center gap-1 min-w-0 shrink", children: [
13084
- /* @__PURE__ */ jsx57("span", { className: "truncate font-medium text-sm", children: col.title }),
13085
- col.sortable && /* @__PURE__ */ jsx57(
13086
- "button",
13077
+ const renderHeader = /* @__PURE__ */ jsx57(TableRow, { children: visibleColumns.map((col, colIdx) => {
13078
+ const prevCol = colIdx > 0 ? visibleColumns[colIdx - 1] : null;
13079
+ const isAfterFixedLeft = prevCol?.fixed === "left";
13080
+ const showBorderLeft = columnDividers && colIdx > 0 && !isAfterFixedLeft && !col.fixed;
13081
+ return /* @__PURE__ */ jsx57(
13082
+ TableHead,
13083
+ {
13084
+ style: { width: col.width, ...getStickyColumnStyle(col) },
13085
+ className: cn(
13086
+ // Use column-specific align if defined, otherwise use global headerAlign
13087
+ (col.align === "right" || !col.align && headerAlign === "right") && "text-right",
13088
+ (col.align === "center" || !col.align && headerAlign === "center") && "text-center",
13089
+ showBorderLeft && "border-l border-border/60",
13090
+ getStickyColumnClass(col, true)
13091
+ ),
13092
+ children: (() => {
13093
+ const isRightAlign = col.align === "right" || !col.align && headerAlign === "right";
13094
+ const isCenterAlign = col.align === "center" || !col.align && headerAlign === "center";
13095
+ const titleContent = /* @__PURE__ */ jsxs51(
13096
+ "div",
13087
13097
  {
13088
13098
  className: cn(
13089
- "p-1 rounded-lg transition-all duration-200 hover:bg-accent",
13090
- sort?.key === col.key ? "opacity-100 bg-accent" : "opacity-60 hover:opacity-100"
13099
+ "flex items-center gap-1",
13100
+ // Cột fixed không cần shrink đã có width cố định
13101
+ !col.fixed && "min-w-0 shrink"
13091
13102
  ),
13092
- onClick: () => {
13093
- setCurPage(1);
13094
- setSort((s) => {
13095
- if (!s || s.key !== col.key) return { key: col.key, order: "asc" };
13096
- if (s.order === "asc") return { key: col.key, order: "desc" };
13097
- return null;
13098
- });
13099
- },
13100
- "aria-label": "Sort",
13101
- title: `Sort by ${String(col.title)}`,
13102
- children: /* @__PURE__ */ jsxs51("svg", { width: "14", height: "14", viewBox: "0 0 20 20", fill: "none", className: "inline-block", children: [
13103
- /* @__PURE__ */ jsx57(
13104
- "path",
13105
- {
13106
- d: "M7 8l3-3 3 3",
13107
- stroke: "currentColor",
13108
- strokeWidth: "1.5",
13109
- strokeLinecap: "round",
13110
- strokeLinejoin: "round",
13111
- opacity: sort?.key === col.key && sort.order === "asc" ? 1 : 0.4
13112
- }
13113
- ),
13114
- /* @__PURE__ */ jsx57(
13115
- "path",
13103
+ children: [
13104
+ /* @__PURE__ */ jsx57("span", { className: cn("font-medium text-sm", !col.fixed && "truncate"), children: col.title }),
13105
+ col.sortable && /* @__PURE__ */ jsx57(
13106
+ "button",
13116
13107
  {
13117
- d: "M7 12l3 3 3-3",
13118
- stroke: "currentColor",
13119
- strokeWidth: "1.5",
13120
- strokeLinecap: "round",
13121
- strokeLinejoin: "round",
13122
- opacity: sort?.key === col.key && sort.order === "desc" ? 1 : 0.4
13108
+ className: cn(
13109
+ "p-1 rounded-lg transition-all duration-200 hover:bg-accent",
13110
+ sort?.key === col.key ? "opacity-100 bg-accent" : "opacity-60 hover:opacity-100"
13111
+ ),
13112
+ onClick: () => {
13113
+ setCurPage(1);
13114
+ setSort((s) => {
13115
+ if (!s || s.key !== col.key) return { key: col.key, order: "asc" };
13116
+ if (s.order === "asc") return { key: col.key, order: "desc" };
13117
+ return null;
13118
+ });
13119
+ },
13120
+ "aria-label": "Sort",
13121
+ title: `Sort by ${String(col.title)}`,
13122
+ children: /* @__PURE__ */ jsxs51("svg", { width: "14", height: "14", viewBox: "0 0 20 20", fill: "none", className: "inline-block", children: [
13123
+ /* @__PURE__ */ jsx57(
13124
+ "path",
13125
+ {
13126
+ d: "M7 8l3-3 3 3",
13127
+ stroke: "currentColor",
13128
+ strokeWidth: "1.5",
13129
+ strokeLinecap: "round",
13130
+ strokeLinejoin: "round",
13131
+ opacity: sort?.key === col.key && sort.order === "asc" ? 1 : 0.4
13132
+ }
13133
+ ),
13134
+ /* @__PURE__ */ jsx57(
13135
+ "path",
13136
+ {
13137
+ d: "M7 12l3 3 3-3",
13138
+ stroke: "currentColor",
13139
+ strokeWidth: "1.5",
13140
+ strokeLinecap: "round",
13141
+ strokeLinejoin: "round",
13142
+ opacity: sort?.key === col.key && sort.order === "desc" ? 1 : 0.4
13143
+ }
13144
+ )
13145
+ ] })
13123
13146
  }
13124
13147
  )
13125
- ] })
13148
+ ]
13126
13149
  }
13127
- )
13128
- ] });
13129
- const filterContent = col.filter && /* @__PURE__ */ jsx57(
13130
- Popover,
13131
- {
13132
- placement: isRightAlign ? "bottom-end" : "bottom-start",
13133
- trigger: /* @__PURE__ */ jsx57(
13134
- "button",
13135
- {
13136
- className: cn(
13137
- "p-1.5 rounded-lg hover:bg-accent text-muted-foreground hover:text-foreground transition-colors",
13138
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
13139
- filters[col.key] && "bg-accent text-foreground"
13140
- ),
13141
- "aria-label": "Filter",
13142
- title: "Filter",
13143
- children: /* @__PURE__ */ jsx57(FilterIcon, { className: "h-4 w-4" })
13144
- }
13145
- ),
13146
- children: /* @__PURE__ */ jsxs51("div", { className: "w-48 p-2 space-y-2", children: [
13147
- /* @__PURE__ */ jsxs51("div", { className: "text-xs font-medium text-muted-foreground mb-2", children: [
13148
- "Filter ",
13149
- col.title
13150
- ] }),
13151
- renderFilterControl(col),
13152
- filters[col.key] && /* @__PURE__ */ jsx57(
13150
+ );
13151
+ const filterContent = col.filter && /* @__PURE__ */ jsx57(
13152
+ Popover,
13153
+ {
13154
+ placement: isRightAlign ? "bottom-end" : "bottom-start",
13155
+ trigger: /* @__PURE__ */ jsx57(
13153
13156
  "button",
13154
13157
  {
13155
- onClick: () => {
13156
- setCurPage(1);
13157
- setFilters((f) => {
13158
- const newFilters = { ...f };
13159
- delete newFilters[col.key];
13160
- return newFilters;
13161
- });
13162
- },
13163
- className: "text-xs text-destructive hover:underline",
13164
- children: t("clearFilter")
13158
+ className: cn(
13159
+ "p-1.5 rounded-lg hover:bg-accent text-muted-foreground hover:text-foreground transition-colors",
13160
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
13161
+ filters[col.key] && "bg-accent text-foreground"
13162
+ ),
13163
+ "aria-label": "Filter",
13164
+ title: "Filter",
13165
+ children: /* @__PURE__ */ jsx57(FilterIcon, { className: "h-4 w-4" })
13165
13166
  }
13166
- )
13167
- ] })
13168
- }
13169
- );
13170
- return /* @__PURE__ */ jsx57(
13171
- "div",
13172
- {
13173
- className: cn(
13174
- "flex items-center gap-2 select-none min-h-10",
13175
- isRightAlign && "justify-end",
13176
- isCenterAlign && "justify-center",
13177
- !isRightAlign && !isCenterAlign && "justify-start"
13178
- ),
13179
- children: isRightAlign ? /* @__PURE__ */ jsxs51(Fragment22, { children: [
13180
- filterContent,
13181
- titleContent
13182
- ] }) : /* @__PURE__ */ jsxs51(Fragment22, { children: [
13183
- titleContent,
13184
- filterContent
13185
- ] })
13186
- }
13187
- );
13188
- })()
13189
- },
13190
- col.key
13191
- )) });
13167
+ ),
13168
+ children: /* @__PURE__ */ jsxs51("div", { className: "w-48 p-2 space-y-2", children: [
13169
+ /* @__PURE__ */ jsxs51("div", { className: "text-xs font-medium text-muted-foreground mb-2", children: [
13170
+ "Filter ",
13171
+ col.title
13172
+ ] }),
13173
+ renderFilterControl(col),
13174
+ filters[col.key] && /* @__PURE__ */ jsx57(
13175
+ "button",
13176
+ {
13177
+ onClick: () => {
13178
+ setCurPage(1);
13179
+ setFilters((f) => {
13180
+ const newFilters = { ...f };
13181
+ delete newFilters[col.key];
13182
+ return newFilters;
13183
+ });
13184
+ },
13185
+ className: "text-xs text-destructive hover:underline",
13186
+ children: t("clearFilter")
13187
+ }
13188
+ )
13189
+ ] })
13190
+ }
13191
+ );
13192
+ return /* @__PURE__ */ jsx57(
13193
+ "div",
13194
+ {
13195
+ className: cn(
13196
+ "flex items-center gap-2 select-none min-h-10",
13197
+ isRightAlign && "justify-end",
13198
+ isCenterAlign && "justify-center",
13199
+ !isRightAlign && !isCenterAlign && "justify-start"
13200
+ ),
13201
+ children: isRightAlign ? /* @__PURE__ */ jsxs51(Fragment22, { children: [
13202
+ filterContent,
13203
+ titleContent
13204
+ ] }) : /* @__PURE__ */ jsxs51(Fragment22, { children: [
13205
+ titleContent,
13206
+ filterContent
13207
+ ] })
13208
+ }
13209
+ );
13210
+ })()
13211
+ },
13212
+ col.key
13213
+ );
13214
+ }) });
13192
13215
  const isServerMode = Boolean(onQueryChange);
13193
13216
  const processedData = React48.useMemo(() => {
13194
13217
  if (isServerMode) return data;
@@ -13293,69 +13316,92 @@ function DataTable({
13293
13316
  toolbar
13294
13317
  ] })
13295
13318
  ] }),
13296
- /* @__PURE__ */ jsx57("div", { className: cn("relative rounded-2xl md:rounded-3xl border border-border/50 overflow-hidden", loading2 && "opacity-60 pointer-events-none"), children: /* @__PURE__ */ jsxs51(
13297
- Table,
13319
+ /* @__PURE__ */ jsx57(
13320
+ "div",
13298
13321
  {
13299
- containerClassName: "border-0 md:border-0 rounded-none md:rounded-none shadow-none bg-transparent",
13300
- className: "[&_thead]:sticky [&_thead]:top-0 [&_thead]:z-5 [&_thead]:bg-background [&_thead]:backdrop-blur-sm",
13301
- children: [
13302
- /* @__PURE__ */ jsx57(TableHeader, { children: renderHeader }),
13303
- /* @__PURE__ */ jsx57(TableBody, { children: loading2 ? /* @__PURE__ */ jsx57(TableRow, { children: /* @__PURE__ */ jsx57(TableCell, { colSpan: visibleColumns.length, className: "text-center py-8", children: /* @__PURE__ */ jsxs51("div", { className: "flex items-center justify-center gap-2 text-muted-foreground", children: [
13304
- /* @__PURE__ */ jsxs51("svg", { className: "animate-spin h-4 w-4", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [
13305
- /* @__PURE__ */ jsx57("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
13306
- /* @__PURE__ */ jsx57(
13307
- "path",
13308
- {
13309
- className: "opacity-75",
13310
- fill: "currentColor",
13311
- d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
13312
- }
13313
- )
13314
- ] }),
13315
- /* @__PURE__ */ jsxs51("span", { className: "text-sm", children: [
13316
- t("loading"),
13317
- "\u2026"
13318
- ] })
13319
- ] }) }) }) : !displayedData || displayedData.length === 0 ? /* @__PURE__ */ jsx57(TableRow, { children: /* @__PURE__ */ jsx57(TableCell, { colSpan: visibleColumns.length, className: "text-center py-6 text-muted-foreground", children: t("noData") }) }) : displayedData.map((row, idx) => {
13320
- const isLastRow = idx === displayedData.length - 1;
13321
- return /* @__PURE__ */ jsx57(
13322
- TableRow,
13323
- {
13324
- className: cn(densityRowClass, striped && idx % 2 === 0 && "bg-muted/50"),
13325
- style: {
13326
- // content-visibility: auto for rendering performance (skip off-screen rows)
13327
- contentVisibility: "auto",
13328
- containIntrinsicSize: density === "compact" ? "0 36px" : density === "comfortable" ? "0 56px" : "0 48px"
13329
- },
13330
- children: visibleColumns.map((col, colIdx) => {
13331
- const value = col.dataIndex ? row[col.dataIndex] : void 0;
13332
- return /* @__PURE__ */ jsx57(
13333
- TableCell,
13322
+ className: cn("relative rounded-2xl md:rounded-3xl border border-border/50", loading2 && "opacity-60 pointer-events-none"),
13323
+ style: stickyHeader ? {
13324
+ maxHeight: typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight,
13325
+ overflowY: "auto",
13326
+ overflowX: "auto"
13327
+ } : { overflowX: "auto" },
13328
+ children: /* @__PURE__ */ jsxs51(
13329
+ Table,
13330
+ {
13331
+ containerClassName: cn("border-0 md:border-0 rounded-none md:rounded-none shadow-none bg-transparent", "overflow-visible"),
13332
+ className: cn(
13333
+ "table-fixed",
13334
+ stickyHeader && ["[&_thead]:sticky", "[&_thead]:top-0", "[&_thead]:z-20", "[&_thead]:shadow-[0_1px_3px_rgba(0,0,0,0.1)]"]
13335
+ ),
13336
+ style: { minWidth: totalColumnsWidth > 0 ? `${totalColumnsWidth}px` : void 0 },
13337
+ children: [
13338
+ /* @__PURE__ */ jsx57(TableHeader, { children: renderHeader }),
13339
+ /* @__PURE__ */ jsx57(TableBody, { children: loading2 ? /* @__PURE__ */ jsx57(TableRow, { children: /* @__PURE__ */ jsx57(TableCell, { colSpan: visibleColumns.length, className: "text-center py-8", children: /* @__PURE__ */ jsxs51("div", { className: "flex items-center justify-center gap-2 text-muted-foreground", children: [
13340
+ /* @__PURE__ */ jsxs51("svg", { className: "animate-spin h-4 w-4", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [
13341
+ /* @__PURE__ */ jsx57("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
13342
+ /* @__PURE__ */ jsx57(
13343
+ "path",
13334
13344
  {
13335
- style: getStickyColumnStyle(col),
13336
- className: cn(
13337
- cellPadding,
13338
- col.align === "right" && "text-right",
13339
- col.align === "center" && "text-center",
13340
- columnDividers && colIdx > 0 && "border-l border-border/60",
13341
- isLastRow && col === visibleColumns[0] && "rounded-bl-2xl md:rounded-bl-3xl",
13342
- isLastRow && col === visibleColumns[visibleColumns.length - 1] && "rounded-br-2xl md:rounded-br-3xl",
13343
- getStickyColumnClass(col),
13344
- // Giữ màu nền striped cho cột cố định
13345
- col.fixed && striped && idx % 2 === 0 && "bg-muted/50!"
13346
- ),
13347
- children: col.render ? col.render(value, row, idx) : String(value ?? "")
13345
+ className: "opacity-75",
13346
+ fill: "currentColor",
13347
+ d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
13348
+ }
13349
+ )
13350
+ ] }),
13351
+ /* @__PURE__ */ jsxs51("span", { className: "text-sm", children: [
13352
+ t("loading"),
13353
+ "\u2026"
13354
+ ] })
13355
+ ] }) }) }) : !displayedData || displayedData.length === 0 ? /* @__PURE__ */ jsx57(TableRow, { children: /* @__PURE__ */ jsx57(TableCell, { colSpan: visibleColumns.length, className: "text-center py-6 text-muted-foreground", children: t("noData") }) }) : displayedData.map((row, idx) => {
13356
+ const isLastRow = idx === displayedData.length - 1;
13357
+ return /* @__PURE__ */ jsx57(
13358
+ TableRow,
13359
+ {
13360
+ className: cn(densityRowClass),
13361
+ style: {
13362
+ // content-visibility: auto for rendering performance (skip off-screen rows)
13363
+ contentVisibility: "auto",
13364
+ containIntrinsicSize: density === "compact" ? "0 36px" : density === "comfortable" ? "0 56px" : "0 48px"
13348
13365
  },
13349
- col.key
13350
- );
13351
- })
13352
- },
13353
- getRowKey(row, idx)
13354
- );
13355
- }) })
13356
- ]
13366
+ children: visibleColumns.map((col, colIdx) => {
13367
+ const value = col.dataIndex ? row[col.dataIndex] : void 0;
13368
+ const isStripedRow = striped && idx % 2 === 0;
13369
+ const prevCol = colIdx > 0 ? visibleColumns[colIdx - 1] : null;
13370
+ const isAfterFixedLeft = prevCol?.fixed === "left";
13371
+ const showBorderLeft = columnDividers && colIdx > 0 && !isAfterFixedLeft && !col.fixed;
13372
+ return /* @__PURE__ */ jsx57(
13373
+ TableCell,
13374
+ {
13375
+ style: getStickyColumnStyle(col),
13376
+ className: cn(
13377
+ cellPadding,
13378
+ col.align === "right" && "text-right",
13379
+ col.align === "center" && "text-center",
13380
+ showBorderLeft && "border-l border-border/60",
13381
+ isLastRow && col === visibleColumns[0] && "rounded-bl-2xl md:rounded-bl-3xl",
13382
+ isLastRow && col === visibleColumns[visibleColumns.length - 1] && "rounded-br-2xl md:rounded-br-3xl",
13383
+ // Cột cố định cần solid background
13384
+ col.fixed ? cn(
13385
+ "sticky z-10",
13386
+ col.fixed === "left" && "left-0 shadow-[2px_0_5px_-2px_rgba(0,0,0,0.15)]",
13387
+ col.fixed === "right" && "right-0 shadow-[-2px_0_5px_-2px_rgba(0,0,0,0.15)]",
13388
+ isStripedRow ? "bg-muted!" : "bg-card!"
13389
+ ) : isStripedRow && "bg-muted/50"
13390
+ ),
13391
+ children: col.render ? col.render(value, row, idx) : String(value ?? "")
13392
+ },
13393
+ col.key
13394
+ );
13395
+ })
13396
+ },
13397
+ getRowKey(row, idx)
13398
+ );
13399
+ }) })
13400
+ ]
13401
+ }
13402
+ )
13357
13403
  }
13358
- ) }),
13404
+ ),
13359
13405
  totalItems > 0 && Math.ceil(totalItems / curPageSize) > 1 && /* @__PURE__ */ jsxs51("div", { className: "flex items-center justify-between gap-2 px-1 pt-3 text-xs text-muted-foreground", children: [
13360
13406
  /* @__PURE__ */ jsxs51("div", { className: "tabular-nums", children: [
13361
13407
  (curPage - 1) * curPageSize + 1,