@underverse-ui/underverse 0.2.48 → 0.2.51

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/README.md CHANGED
@@ -49,6 +49,51 @@ Components use color variables like `primary`, `secondary`, `destructive`, etc.
49
49
 
50
50
  ---
51
51
 
52
+ ## ⚡ Performance Optimization
53
+
54
+ ### Optimize Package Imports (Next.js)
55
+
56
+ For best performance, add `optimizePackageImports` to your Next.js config:
57
+
58
+ ```js
59
+ // next.config.js
60
+ module.exports = {
61
+ experimental: {
62
+ optimizePackageImports: ["lucide-react", "@underverse-ui/underverse"],
63
+ },
64
+ };
65
+ ```
66
+
67
+ This provides:
68
+
69
+ - ✅ 15-70% faster dev boot
70
+ - ✅ 28% faster builds
71
+ - ✅ 40% faster cold starts
72
+ - ✅ Automatic tree-shaking for barrel imports
73
+
74
+ ### Dynamic Imports for Heavy Components
75
+
76
+ For pages that conditionally show DataTable or DatePicker:
77
+
78
+ ```tsx
79
+ import dynamic from "next/dynamic";
80
+
81
+ const DataTable = dynamic(() => import("@underverse-ui/underverse").then((m) => m.DataTable), { ssr: false, loading: () => <Skeleton /> });
82
+ ```
83
+
84
+ ### Web Interface Guidelines Compliant
85
+
86
+ All components follow [Vercel Web Interface Guidelines](https://github.com/vercel-labs/web-interface-guidelines):
87
+
88
+ - ✅ `focus-visible` ring (not `:focus`)
89
+ - ✅ Label `htmlFor` attribute
90
+ - ✅ ARIA attributes for accessibility
91
+ - ✅ `overscroll-behavior: contain` for modals
92
+ - ✅ Proper ellipsis (`…`) typography
93
+ - ✅ Locale-aware date formatting with `Intl.DateTimeFormat`
94
+
95
+ ---
96
+
52
97
  ## 🚀 Quick Start
53
98
 
54
99
  ### Standalone React (Vite, CRA, etc.)
package/dist/index.cjs CHANGED
@@ -259,29 +259,32 @@ var Button = (0, import_react.forwardRef)(
259
259
  const SpinnerIcon = Spinner ?? import_lucide_react.Activity;
260
260
  const [locked, setLocked] = (0, import_react.useState)(false);
261
261
  const lockTimer = (0, import_react.useRef)(null);
262
- const handleClick = (0, import_react.useCallback)(async (e) => {
263
- if (disabled || loading2) return;
264
- if (preventDoubleClick) {
265
- if (locked) return;
266
- setLocked(true);
267
- try {
268
- const result = onClick?.(e);
269
- if (result && typeof result === "object" && typeof result.then === "function") {
270
- await result;
262
+ const handleClick = (0, import_react.useCallback)(
263
+ async (e) => {
264
+ if (disabled || loading2) return;
265
+ if (preventDoubleClick) {
266
+ if (locked) return;
267
+ setLocked(true);
268
+ try {
269
+ const result = onClick?.(e);
270
+ if (result && typeof result === "object" && typeof result.then === "function") {
271
+ await result;
272
+ setLocked(false);
273
+ } else {
274
+ const ms = lockMs ?? 600;
275
+ if (lockTimer.current) clearTimeout(lockTimer.current);
276
+ lockTimer.current = setTimeout(() => setLocked(false), ms);
277
+ }
278
+ } catch (err) {
271
279
  setLocked(false);
272
- } else {
273
- const ms = lockMs ?? 600;
274
- if (lockTimer.current) clearTimeout(lockTimer.current);
275
- lockTimer.current = setTimeout(() => setLocked(false), ms);
280
+ throw err;
276
281
  }
277
- } catch (err) {
278
- setLocked(false);
279
- throw err;
282
+ } else {
283
+ onClick?.(e);
280
284
  }
281
- } else {
282
- onClick?.(e);
283
- }
284
- }, [disabled, loading2, onClick, locked, preventDoubleClick, lockMs]);
285
+ },
286
+ [disabled, loading2, onClick, locked, preventDoubleClick, lockMs]
287
+ );
285
288
  const computedDisabled = disabled || loading2 || (preventDoubleClick ? locked : false);
286
289
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
287
290
  "button",
@@ -312,15 +315,15 @@ var Button = (0, import_react.forwardRef)(
312
315
  "aria-label": rest["aria-label"] || title,
313
316
  ...rest,
314
317
  children: [
315
- !noHoverOverlay && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "absolute inset-0 bg-linear-to-r from-primary-foreground/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-200 dark:hidden" }),
318
+ !noHoverOverlay ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "absolute inset-0 bg-linear-to-r from-primary-foreground/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-200 dark:hidden" }) : null,
316
319
  loading2 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
317
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SpinnerIcon, { className: "w-4 h-4 animate-spin" }),
318
- loadingText && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ml-2", "aria-live": "polite", children: loadingText }),
319
- preserveChildrenOnLoading && !loadingText && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ml-2 opacity-70", "aria-hidden": true, children })
320
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SpinnerIcon, { className: "w-4 h-4 animate-spin", "aria-hidden": "true" }),
321
+ loadingText ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ml-2", "aria-live": "polite", children: loadingText }) : null,
322
+ preserveChildrenOnLoading && !loadingText ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ml-2 opacity-70", "aria-hidden": true, children }) : null
320
323
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
321
- Icon && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { className: cn("transition-transform duration-200", iConClassName ? iConClassName : "w-5 h-5") }),
324
+ Icon ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { className: cn("transition-transform duration-200", iConClassName ? iConClassName : "w-5 h-5") }) : null,
322
325
  children,
323
- IconRight && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconRight, { className: "w-4 h-4 transition-transform duration-200" })
326
+ IconRight ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconRight, { className: "w-4 h-4 transition-transform duration-200" }) : null
324
327
  ] })
325
328
  ]
326
329
  }
@@ -589,7 +592,7 @@ var Card = ({
589
592
  "div",
590
593
  {
591
594
  className: cn(
592
- "rounded-lg md:rounded-xl bg-card text-card-foreground transition-all duration-300 ease-soft",
595
+ "rounded-lg md:rounded-xl bg-card text-card-foreground transition-[transform,box-shadow,border-color,background-color] duration-300 ease-soft",
593
596
  "shadow-sm md:hover:shadow-md mx-2 md:mx-0 border border-border",
594
597
  hoverable && "md:hover:-translate-y-0.5 md:hover:border-primary/15",
595
598
  clickable && "cursor-pointer active:translate-y-px md:hover:bg-accent/5",
@@ -1162,6 +1165,7 @@ var Input = (0, import_react3.forwardRef)(
1162
1165
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1163
1166
  "label",
1164
1167
  {
1168
+ htmlFor: resolvedId,
1165
1169
  className: cn(
1166
1170
  // Label size follows input size
1167
1171
  size === "sm" ? "text-xs" : size === "lg" ? "text-base" : "text-sm",
@@ -1299,10 +1303,10 @@ var Input = (0, import_react3.forwardRef)(
1299
1303
  }
1300
1304
  )
1301
1305
  ] }),
1302
- errMsg && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { id: errorId, className: "flex items-center gap-2 text-sm text-destructive animate-in slide-in-from-top-1 duration-200", children: [
1306
+ errMsg ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { id: errorId, className: "flex items-center gap-2 text-sm text-destructive animate-in slide-in-from-top-1 duration-200", children: [
1303
1307
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.AlertCircle, { className: "w-4 h-4 shrink-0" }),
1304
1308
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: errMsg })
1305
- ] }),
1309
+ ] }) : null,
1306
1310
  (description || hint) && !errMsg && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1307
1311
  "p",
1308
1312
  {
@@ -1320,7 +1324,7 @@ var Input = (0, import_react3.forwardRef)(
1320
1324
  );
1321
1325
  Input.displayName = "Input";
1322
1326
  var SearchInput = (0, import_react3.forwardRef)(
1323
- ({ onSearch, searchDelay = 300, placeholder = "Search...", ...props }, ref) => {
1327
+ ({ onSearch, searchDelay = 300, placeholder = "Search\u2026", ...props }, ref) => {
1324
1328
  const [searchValue, setSearchValue] = (0, import_react3.useState)(props.value || "");
1325
1329
  import_react3.default.useEffect(() => {
1326
1330
  if (!onSearch) return;
@@ -1567,7 +1571,7 @@ var Textarea = (0, import_react3.forwardRef)(
1567
1571
  onBlur: () => setIsFocused(false),
1568
1572
  className: cn(
1569
1573
  "w-full rounded-lg px-4 py-3 text-sm text-foreground transition-all duration-200",
1570
- "placeholder:text-muted-foreground focus:outline-none min-h-[80px]",
1574
+ "placeholder:text-muted-foreground focus:outline-none min-h-20",
1571
1575
  "disabled:cursor-not-allowed disabled:opacity-50",
1572
1576
  variantStyles6[variant],
1573
1577
  // DÒNG NÀY ĐÃ ĐƯỢC CẬP NHẬT:
@@ -1894,7 +1898,7 @@ var Textarea2 = (0, import_react5.forwardRef)(
1894
1898
  md: "px-4 py-3 text-sm min-h-[100px]",
1895
1899
  lg: "px-5 py-4 text-base min-h-[120px]"
1896
1900
  };
1897
- const variantClasses = {
1901
+ const variantClasses2 = {
1898
1902
  default: cn(
1899
1903
  "border border-input bg-background",
1900
1904
  "hover:border-accent-foreground/20",
@@ -1938,7 +1942,7 @@ var Textarea2 = (0, import_react5.forwardRef)(
1938
1942
  "focus:outline-none shadow-sm focus:shadow-md",
1939
1943
  "backdrop-blur-sm",
1940
1944
  sizeClasses2[size],
1941
- error ? "border-destructive focus:ring-destructive/20 focus:border-destructive" : variantClasses[variant],
1945
+ error ? "border-destructive focus:ring-destructive/20 focus:border-destructive" : variantClasses2[variant],
1942
1946
  "disabled:cursor-not-allowed disabled:opacity-50",
1943
1947
  className
1944
1948
  ),
@@ -1993,7 +1997,7 @@ var Switch = ({
1993
1997
  translate: "translate-x-6"
1994
1998
  }
1995
1999
  };
1996
- const variantClasses = {
2000
+ const variantClasses2 = {
1997
2001
  default: {
1998
2002
  active: "bg-primary border-primary",
1999
2003
  inactive: "bg-input border-input"
@@ -2047,7 +2051,7 @@ var Switch = ({
2047
2051
  {
2048
2052
  className: cn(
2049
2053
  "block w-full h-full rounded-full transition-colors duration-200 ease-out border",
2050
- checked ? variantClasses[variant].active : variantClasses[variant].inactive
2054
+ checked ? variantClasses2[variant].active : variantClasses2[variant].inactive
2051
2055
  )
2052
2056
  }
2053
2057
  ),
@@ -2276,7 +2280,7 @@ var Skeleton = ({
2276
2280
  animation = "pulse",
2277
2281
  lines = 1
2278
2282
  }) => {
2279
- const variantClasses = {
2283
+ const variantClasses2 = {
2280
2284
  rectangular: "rounded-md",
2281
2285
  circular: "rounded-full",
2282
2286
  rounded: "rounded-lg",
@@ -2293,7 +2297,7 @@ var Skeleton = ({
2293
2297
  {
2294
2298
  className: cn(
2295
2299
  "h-4 bg-muted",
2296
- variantClasses[variant],
2300
+ variantClasses2[variant],
2297
2301
  animationClasses[animation],
2298
2302
  index === lines - 1 && "w-3/4"
2299
2303
  // Last line is shorter
@@ -2311,7 +2315,7 @@ var Skeleton = ({
2311
2315
  {
2312
2316
  className: cn(
2313
2317
  "bg-muted",
2314
- variantClasses[variant],
2318
+ variantClasses2[variant],
2315
2319
  animationClasses[animation],
2316
2320
  className
2317
2321
  ),
@@ -3025,6 +3029,7 @@ var Modal = ({
3025
3029
  "div",
3026
3030
  {
3027
3031
  className: cn("fixed inset-0 z-9999 flex items-center justify-center", overlayClassName),
3032
+ style: { overscrollBehavior: "contain" },
3028
3033
  onMouseDown: handleOverlayMouseDown,
3029
3034
  onMouseUp: handleOverlayMouseUp,
3030
3035
  children: [
@@ -3067,12 +3072,13 @@ var Modal = ({
3067
3072
  "button",
3068
3073
  {
3069
3074
  onClick: onClose,
3075
+ "aria-label": "Close modal",
3070
3076
  className: cn(
3071
3077
  "rounded-sm opacity-70 ring-offset-background transition-opacity",
3072
3078
  "hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
3073
3079
  "disabled:pointer-events-none "
3074
3080
  ),
3075
- children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react6.X, { className: "h-4 w-4 cursor-pointer" })
3081
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react6.X, { className: "h-4 w-4 cursor-pointer", "aria-hidden": "true" })
3076
3082
  }
3077
3083
  )
3078
3084
  ] }),
@@ -3366,6 +3372,7 @@ var Tooltip = ({
3366
3372
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3367
3373
  "div",
3368
3374
  {
3375
+ role: "tooltip",
3369
3376
  style: {
3370
3377
  position: "fixed",
3371
3378
  top: position.top,
@@ -4824,8 +4831,8 @@ var Combobox = ({
4824
4831
  options,
4825
4832
  value,
4826
4833
  onChange,
4827
- placeholder = "Select...",
4828
- searchPlaceholder = "Search...",
4834
+ placeholder = "Select\u2026",
4835
+ searchPlaceholder = "Search\u2026",
4829
4836
  emptyText = "No results found",
4830
4837
  className,
4831
4838
  disabled = false,
@@ -4987,7 +4994,7 @@ var Combobox = ({
4987
4994
  ] }),
4988
4995
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "max-h-64 overflow-y-auto overscroll-contain", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("ul", { className: "p-1 space-y-1", children: loading2 ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("li", { className: "px-3 py-8 text-center", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex flex-col items-center gap-2 animate-in fade-in-0 zoom-in-95 duration-300", children: [
4989
4996
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react12.Loader2, { className: "h-6 w-6 animate-spin text-primary" }),
4990
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "text-sm text-muted-foreground", children: loadingText || "Loading..." })
4997
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "text-sm text-muted-foreground", children: loadingText || "Loading\u2026" })
4991
4998
  ] }) }) : filteredOptions.length > 0 ? filteredOptions.map((item, index) => {
4992
4999
  const itemValue = getOptionValue(item);
4993
5000
  const itemLabel = getOptionLabel(item);
@@ -5025,7 +5032,7 @@ var Combobox = ({
5025
5032
  }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("li", { className: "px-3 py-8 text-center text-muted-foreground text-sm", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex flex-col items-center gap-2 animate-in fade-in-0 zoom-in-95 duration-300", children: [
5026
5033
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react12.SearchX, { className: "h-8 w-8 opacity-40 text-muted-foreground" }),
5027
5034
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "text-sm", children: emptyText }),
5028
- query && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("button", { type: "button", onClick: () => setQuery(""), className: "text-xs text-primary hover:underline", children: "Clear search" })
5035
+ query && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("button", { type: "button", onClick: () => setQuery(""), className: "text-xs text-primary hover:underline", children: "Clear" })
5029
5036
  ] }) }) }) })
5030
5037
  ] })
5031
5038
  }
@@ -5044,6 +5051,7 @@ var Combobox = ({
5044
5051
  "label",
5045
5052
  {
5046
5053
  id: labelId,
5054
+ htmlFor: resolvedId,
5047
5055
  onClick: () => triggerRef.current?.focus(),
5048
5056
  className: cn(
5049
5057
  labelSize,
@@ -5467,7 +5475,7 @@ var Section = import_react13.default.forwardRef(
5467
5475
  gradientDirection = "to-br",
5468
5476
  ...props
5469
5477
  }, ref) => {
5470
- const variantClasses = {
5478
+ const variantClasses2 = {
5471
5479
  default: "bg-background",
5472
5480
  muted: "bg-muted/30",
5473
5481
  primary: "bg-primary/5",
@@ -5483,7 +5491,7 @@ var Section = import_react13.default.forwardRef(
5483
5491
  {
5484
5492
  ref,
5485
5493
  className: cn(
5486
- variant === "gradient" ? getGradientClasses() : variantClasses[variant],
5494
+ variant === "gradient" ? getGradientClasses() : variantClasses2[variant],
5487
5495
  spacingClasses[spacing],
5488
5496
  paddingXClasses[paddingX],
5489
5497
  outlined && "rounded-lg border border-border/60",
@@ -5502,9 +5510,43 @@ var Section_default = Section;
5502
5510
  // ../../components/ui/ScrollArea.tsx
5503
5511
  var import_react14 = require("react");
5504
5512
  var import_jsx_runtime28 = require("react/jsx-runtime");
5505
- var ScrollArea = (0, import_react14.forwardRef)(({ className, children, ...props }, ref) => {
5506
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { ref, className: cn("relative overflow-hidden bg-background", className), ...props, children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: cn("h-full w-full overflow-y-auto scroll-area-viewport"), children }) });
5507
- });
5513
+ var variantClasses = {
5514
+ default: "bg-background",
5515
+ muted: "bg-muted/30",
5516
+ primary: "bg-primary/5",
5517
+ accent: "bg-accent/10"
5518
+ };
5519
+ var spacingClasses2 = {
5520
+ none: "",
5521
+ sm: "p-2",
5522
+ md: "p-4",
5523
+ lg: "p-6",
5524
+ xl: "p-8"
5525
+ };
5526
+ var ScrollArea = (0, import_react14.forwardRef)(
5527
+ ({ className, contentClassName, children, variant = "default", spacing = "none", fullWidth = true, outlined = false, ...props }, ref) => {
5528
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
5529
+ "div",
5530
+ {
5531
+ ref,
5532
+ className: cn("relative overflow-hidden", variantClasses[variant], outlined && "rounded-lg border border-border/60", className),
5533
+ ...props,
5534
+ children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
5535
+ "div",
5536
+ {
5537
+ className: cn(
5538
+ "h-full w-full overflow-y-auto scroll-area-viewport",
5539
+ spacingClasses2[spacing],
5540
+ !fullWidth && "container mx-auto px-4 md:px-6",
5541
+ contentClassName
5542
+ ),
5543
+ children
5544
+ }
5545
+ )
5546
+ }
5547
+ );
5548
+ }
5549
+ );
5508
5550
  ScrollArea.displayName = "ScrollArea";
5509
5551
 
5510
5552
  // ../../components/ui/DatePicker.tsx
@@ -5745,6 +5787,7 @@ var DatePicker = ({
5745
5787
  "label",
5746
5788
  {
5747
5789
  id: labelId,
5790
+ htmlFor: resolvedId,
5748
5791
  onClick: () => triggerRef.current?.focus(),
5749
5792
  className: cn(
5750
5793
  labelSize,
@@ -9182,7 +9225,7 @@ var ListRoot = React33.forwardRef(
9182
9225
  const Comp = ordered ? "ol" : as;
9183
9226
  const childCount = React33.Children.count(children);
9184
9227
  const hasChildren = childCount > 0;
9185
- const variantClasses = {
9228
+ const variantClasses2 = {
9186
9229
  plain: "",
9187
9230
  outlined: "rounded-lg md:rounded-xl bg-card text-card-foreground border border-border shadow-sm",
9188
9231
  soft: "rounded-lg bg-muted/40 border border-border/60",
@@ -9192,10 +9235,10 @@ var ListRoot = React33.forwardRef(
9192
9235
  striped: "rounded-lg border border-border overflow-hidden"
9193
9236
  };
9194
9237
  if (loading2) {
9195
- return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(Comp, { ref, className: cn("group/list", variantClasses[variant], inset && "p-1.5 md:p-2", divided && "divide-y divide-border/60", className), ...rest, children: Array.from({ length: loadingCount }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(ListItemSkeleton, { size }, i)) });
9238
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(Comp, { ref, className: cn("group/list", variantClasses2[variant], inset && "p-1.5 md:p-2", divided && "divide-y divide-border/60", className), ...rest, children: Array.from({ length: loadingCount }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(ListItemSkeleton, { size }, i)) });
9196
9239
  }
9197
9240
  if (!hasChildren && emptyText) {
9198
- return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(Comp, { ref, className: cn("group/list", variantClasses[variant], inset && "p-1.5 md:p-2", className), ...rest, children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "text-center py-8 text-muted-foreground text-sm", children: emptyText }) });
9241
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(Comp, { ref, className: cn("group/list", variantClasses2[variant], inset && "p-1.5 md:p-2", className), ...rest, children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "text-center py-8 text-muted-foreground text-sm", children: emptyText }) });
9199
9242
  }
9200
9243
  return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
9201
9244
  Comp,
@@ -9203,7 +9246,7 @@ var ListRoot = React33.forwardRef(
9203
9246
  ref,
9204
9247
  className: cn(
9205
9248
  "group/list",
9206
- variantClasses[variant],
9249
+ variantClasses2[variant],
9207
9250
  inset && "p-1.5 md:p-2",
9208
9251
  divided && "divide-y divide-border/60",
9209
9252
  variant === "striped" && "[&>*:nth-child(even)]:bg-muted/30",
@@ -9770,7 +9813,7 @@ var TimelineItem = React35.forwardRef(
9770
9813
  }
9771
9814
  };
9772
9815
  const padding = ctx.dense ? sz.densePadY : sz.padY;
9773
- const variantClasses = {
9816
+ const variantClasses2 = {
9774
9817
  default: "",
9775
9818
  outlined: "rounded-lg border border-border bg-card shadow-sm px-4 py-3",
9776
9819
  card: "rounded-xl border border-border bg-card shadow-md px-5 py-4",
@@ -9778,7 +9821,7 @@ var TimelineItem = React35.forwardRef(
9778
9821
  modern: "rounded-lg bg-linear-to-r from-card to-muted/20 border border-border/50 px-5 py-4 backdrop-blur-sm",
9779
9822
  gradient: "rounded-xl bg-linear-to-br from-primary/10 via-card to-accent/10 border border-primary/20 px-5 py-4 shadow-lg"
9780
9823
  };
9781
- const contentBox = /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: cn("min-w-0 flex-1", variantClasses[ctx.variant]), children: [
9824
+ const contentBox = /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: cn("min-w-0 flex-1", variantClasses2[ctx.variant]), children: [
9782
9825
  /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex items-start justify-between gap-2", children: [
9783
9826
  /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex-1 min-w-0", children: [
9784
9827
  title && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex items-center gap-2", children: [
@@ -12099,7 +12142,7 @@ function DataTable({
12099
12142
  const [curPage, setCurPage] = import_react32.default.useState(page);
12100
12143
  const hasMounted = import_react32.default.useRef(false);
12101
12144
  const loadedFromStorage = import_react32.default.useRef(false);
12102
- const getInitialPageSize = import_react32.default.useCallback(() => {
12145
+ const [curPageSize, setCurPageSize] = import_react32.default.useState(() => {
12103
12146
  if (typeof window === "undefined" || !storageKey) return pageSize;
12104
12147
  try {
12105
12148
  const saved = localStorage.getItem(`datatable_${storageKey}_pageSize`);
@@ -12113,8 +12156,7 @@ function DataTable({
12113
12156
  } catch {
12114
12157
  }
12115
12158
  return pageSize;
12116
- }, [storageKey, pageSize]);
12117
- const [curPageSize, setCurPageSize] = import_react32.default.useState(getInitialPageSize);
12159
+ });
12118
12160
  import_react32.default.useEffect(() => {
12119
12161
  if (typeof window === "undefined" || !storageKey) return;
12120
12162
  if (!hasMounted.current) return;
@@ -12150,7 +12192,8 @@ function DataTable({
12150
12192
  }, [debouncedFilters, sort, curPage, curPageSize]);
12151
12193
  const densityRowClass = density === "compact" ? "h-9" : density === "comfortable" ? "h-14" : "h-12";
12152
12194
  const cellPadding = density === "compact" ? "py-1.5 px-3" : density === "comfortable" ? "py-3 px-4" : "py-2.5 px-4";
12153
- const visibleColumns = columns.filter((c) => visibleCols.includes(c.key));
12195
+ const visibleColsSet = import_react32.default.useMemo(() => new Set(visibleCols), [visibleCols]);
12196
+ const visibleColumns = columns.filter((c) => visibleColsSet.has(c.key));
12154
12197
  const getRowKey = (row, idx) => {
12155
12198
  if (!rowKey) return String(idx);
12156
12199
  if (typeof rowKey === "function") return String(rowKey(row));
@@ -12302,7 +12345,7 @@ function DataTable({
12302
12345
  });
12303
12346
  },
12304
12347
  className: "text-xs text-destructive hover:underline",
12305
- children: "Clear filter"
12348
+ children: t("clearFilter")
12306
12349
  }
12307
12350
  )
12308
12351
  ] })
@@ -12453,27 +12496,42 @@ function DataTable({
12453
12496
  }
12454
12497
  )
12455
12498
  ] }),
12456
- /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("span", { className: "text-sm", children: "Loading..." })
12457
- ] }) }) }) : !displayedData || displayedData.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(TableRow, { children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(TableCell, { colSpan: visibleColumns.length, className: "text-center py-6 text-muted-foreground", children: "No data" }) }) : displayedData.map((row, idx) => {
12499
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("span", { className: "text-sm", children: [
12500
+ t("loading"),
12501
+ "\u2026"
12502
+ ] })
12503
+ ] }) }) }) : !displayedData || displayedData.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(TableRow, { children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(TableCell, { colSpan: visibleColumns.length, className: "text-center py-6 text-muted-foreground", children: t("noData") }) }) : displayedData.map((row, idx) => {
12458
12504
  const isLastRow = idx === displayedData.length - 1;
12459
- return /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(TableRow, { className: cn(densityRowClass, striped && idx % 2 === 0 && "bg-muted/50"), children: visibleColumns.map((col, colIdx) => {
12460
- const value = col.dataIndex ? row[col.dataIndex] : void 0;
12461
- return /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
12462
- TableCell,
12463
- {
12464
- className: cn(
12465
- cellPadding,
12466
- col.align === "right" && "text-right",
12467
- col.align === "center" && "text-center",
12468
- columnDividers && colIdx > 0 && "border-l border-border/60",
12469
- isLastRow && col === visibleColumns[0] && "rounded-bl-md",
12470
- isLastRow && col === visibleColumns[visibleColumns.length - 1] && "rounded-br-md"
12471
- ),
12472
- children: col.render ? col.render(value, row, idx) : String(value ?? "")
12505
+ return /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
12506
+ TableRow,
12507
+ {
12508
+ className: cn(densityRowClass, striped && idx % 2 === 0 && "bg-muted/50"),
12509
+ style: {
12510
+ // content-visibility: auto for rendering performance (skip off-screen rows)
12511
+ contentVisibility: "auto",
12512
+ containIntrinsicSize: density === "compact" ? "0 36px" : density === "comfortable" ? "0 56px" : "0 48px"
12473
12513
  },
12474
- col.key
12475
- );
12476
- }) }, getRowKey(row, idx));
12514
+ children: visibleColumns.map((col, colIdx) => {
12515
+ const value = col.dataIndex ? row[col.dataIndex] : void 0;
12516
+ return /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
12517
+ TableCell,
12518
+ {
12519
+ className: cn(
12520
+ cellPadding,
12521
+ col.align === "right" && "text-right",
12522
+ col.align === "center" && "text-center",
12523
+ columnDividers && colIdx > 0 && "border-l border-border/60",
12524
+ isLastRow && col === visibleColumns[0] && "rounded-bl-md",
12525
+ isLastRow && col === visibleColumns[visibleColumns.length - 1] && "rounded-br-md"
12526
+ ),
12527
+ children: col.render ? col.render(value, row, idx) : String(value ?? "")
12528
+ },
12529
+ col.key
12530
+ );
12531
+ })
12532
+ },
12533
+ getRowKey(row, idx)
12534
+ );
12477
12535
  }) })
12478
12536
  ]
12479
12537
  }
@@ -13486,7 +13544,14 @@ var en_default = {
13486
13544
  compact: "Compact",
13487
13545
  normal: "Normal",
13488
13546
  comfortable: "Comfortable",
13489
- columns: "Columns"
13547
+ columns: "Columns",
13548
+ loading: "Loading",
13549
+ noData: "No data",
13550
+ clearFilter: "Clear filter",
13551
+ headerAlign: "Header alignment",
13552
+ alignLeft: "Align left",
13553
+ alignCenter: "Align center",
13554
+ alignRight: "Align right"
13490
13555
  },
13491
13556
  ValidationInput: {
13492
13557
  required: "This field is required",
@@ -13549,7 +13614,14 @@ var vi_default = {
13549
13614
  compact: "G\u1ECDn",
13550
13615
  normal: "Th\u01B0\u1EDDng",
13551
13616
  comfortable: "Tho\u1EA3i m\xE1i",
13552
- columns: "C\u1ED9t"
13617
+ columns: "C\u1ED9t",
13618
+ loading: "\u0110ang t\u1EA3i",
13619
+ noData: "Kh\xF4ng c\xF3 d\u1EEF li\u1EC7u",
13620
+ clearFilter: "X\xF3a b\u1ED9 l\u1ECDc",
13621
+ headerAlign: "C\u0103n ch\u1EC9nh ti\xEAu \u0111\u1EC1",
13622
+ alignLeft: "C\u0103n tr\xE1i",
13623
+ alignCenter: "C\u0103n gi\u1EEFa",
13624
+ alignRight: "C\u0103n ph\u1EA3i"
13553
13625
  },
13554
13626
  ValidationInput: {
13555
13627
  required: "Tr\u01B0\u1EDDng n\xE0y l\xE0 b\u1EAFt bu\u1ED9c",
@@ -13612,7 +13684,14 @@ var ko_default = {
13612
13684
  compact: "\uCEF4\uD329\uD2B8",
13613
13685
  normal: "\uBCF4\uD1B5",
13614
13686
  comfortable: "\uC5EC\uC720",
13615
- columns: "\uC5F4"
13687
+ columns: "\uC5F4",
13688
+ loading: "\uB85C\uB529 \uC911",
13689
+ noData: "\uB370\uC774\uD130 \uC5C6\uC74C",
13690
+ clearFilter: "\uD544\uD130 \uC9C0\uC6B0\uAE30",
13691
+ headerAlign: "\uD5E4\uB354 \uC815\uB82C",
13692
+ alignLeft: "\uC67C\uCABD \uC815\uB82C",
13693
+ alignCenter: "\uAC00\uC6B4\uB370 \uC815\uB82C",
13694
+ alignRight: "\uC624\uB978\uCABD \uC815\uB82C"
13616
13695
  },
13617
13696
  ValidationInput: {
13618
13697
  required: "\uD544\uC218 \uC785\uB825 \uD56D\uBAA9\uC785\uB2C8\uB2E4",
@@ -13675,7 +13754,14 @@ var ja_default = {
13675
13754
  compact: "\u30B3\u30F3\u30D1\u30AF\u30C8",
13676
13755
  normal: "\u901A\u5E38",
13677
13756
  comfortable: "\u3086\u3063\u305F\u308A",
13678
- columns: "\u5217"
13757
+ columns: "\u5217",
13758
+ loading: "\u8AAD\u307F\u8FBC\u307F\u4E2D",
13759
+ noData: "\u30C7\u30FC\u30BF\u304C\u3042\u308A\u307E\u305B\u3093",
13760
+ clearFilter: "\u30D5\u30A3\u30EB\u30BF\u30FC\u3092\u30AF\u30EA\u30A2",
13761
+ headerAlign: "\u30D8\u30C3\u30C0\u30FC\u914D\u7F6E",
13762
+ alignLeft: "\u5DE6\u63C3\u3048",
13763
+ alignCenter: "\u4E2D\u592E\u63C3\u3048",
13764
+ alignRight: "\u53F3\u63C3\u3048"
13679
13765
  },
13680
13766
  ValidationInput: {
13681
13767
  required: "\u3053\u306E\u9805\u76EE\u306F\u5FC5\u9808\u3067\u3059",