@underverse-ui/underverse 2.0.1 → 2.0.3

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/CHANGELOG.md CHANGED
@@ -4,6 +4,22 @@ All notable changes to `@underverse-ui/underverse` are documented in this file.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ### Fixed
8
+
9
+ - `DataTable` sortable headers now keep their titles visually centered while the sort control remains aligned to the trailing edge.
10
+
11
+ ## [2.0.2] - 2026-07-28
12
+
13
+ ### Added
14
+
15
+ - `CategoryTreeSelect` now supports `showTreeLines` to show or hide dashed hierarchy guide lines in recursive and virtualized trees.
16
+ - `DataTable` columns now support `headerAlign` for per-column header alignment.
17
+
18
+ ### Changed
19
+
20
+ - `DataTable` headers now default to centered alignment.
21
+ - `DataTable` body cells now default to left alignment, with per-column overrides through `align`.
22
+
7
23
  ## [2.0.0] - 2026-07-26
8
24
 
9
25
  ### Highlights
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "package": "@underverse-ui/underverse",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
4
4
  "sourceEntry": "src/index.ts",
5
5
  "totalExports": 256,
6
6
  "exports": [
package/dist/index.cjs CHANGED
@@ -26270,9 +26270,8 @@ var defaultLabels = {
26270
26270
  searchPlaceholder: "Search...",
26271
26271
  noResultsText: "No results found"
26272
26272
  };
26273
- var TREE_NODE_BASE_PADDING_REM = 0.75;
26274
- var TREE_NODE_INDENT_REM = 1;
26275
- var TREE_BRANCH_OFFSET_CLASS = "ml-1.5 pl-1.5";
26273
+ var TREE_NODE_BASE_PADDING_REM = 0;
26274
+ var TREE_NODE_INDENT_REM = 0.75;
26276
26275
  var TREE_NODE_GAP_CLASS = "gap-1.5";
26277
26276
  var TREE_EXPANDER_PLACEHOLDER_CLASS = "w-5";
26278
26277
  var CATEGORY_TREE_DROPDOWN_MAX_HEIGHT = 320;
@@ -26402,7 +26401,7 @@ function CategoryTreeSelect(props) {
26402
26401
  useOverlayScrollbar = false,
26403
26402
  leafOnlySelect = false,
26404
26403
  virtualized = false,
26405
- estimatedItemHeight = 44,
26404
+ estimatedItemHeight = 36,
26406
26405
  overscan = 8,
26407
26406
  maxInitialOptions,
26408
26407
  searchMode = "auto",
@@ -26412,6 +26411,7 @@ function CategoryTreeSelect(props) {
26412
26411
  showSearchPromptWhenEmptyQuery = false,
26413
26412
  baseIndent = TREE_NODE_BASE_PADDING_REM,
26414
26413
  indentSize = TREE_NODE_INDENT_REM,
26414
+ showTreeLines = true,
26415
26415
  renderItemActions,
26416
26416
  singleSelect = false,
26417
26417
  borderMode
@@ -26699,7 +26699,7 @@ function CategoryTreeSelect(props) {
26699
26699
  {
26700
26700
  ref: virtualItem ? treeVirtualizer.measureElement : void 0,
26701
26701
  "data-index": virtualItem?.index,
26702
- className: cn("min-w-0 [content-visibility:auto] [contain-intrinsic-size:44px]", !virtualItem && "animate-in fade-in-50 duration-200"),
26702
+ className: cn("min-w-0 [content-visibility:auto] [contain-intrinsic-size:36px]", !virtualItem && "animate-in fade-in-50 duration-200"),
26703
26703
  style: { animationDelay: virtualItem ? void 0 : `${level * 30}ms`, ...rowStyle },
26704
26704
  children: [
26705
26705
  /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(
@@ -26711,7 +26711,7 @@ function CategoryTreeSelect(props) {
26711
26711
  "aria-selected": viewOnly ? void 0 : isSelected,
26712
26712
  onClick: () => !viewOnly && handleSelect(category.id, category),
26713
26713
  className: cn(
26714
- "relative flex min-w-0 items-center px-3 py-2.5 min-h-11 transition-all duration-200 rounded-3xl",
26714
+ "relative flex min-h-9 min-w-0 items-center rounded-3xl transition-all duration-200",
26715
26715
  TREE_NODE_GAP_CLASS,
26716
26716
  !viewOnly && (isSelectable ? "cursor-pointer" : "cursor-default"),
26717
26717
  isSelectable && !isSelected && "hover:bg-accent/50",
@@ -26721,6 +26721,16 @@ function CategoryTreeSelect(props) {
26721
26721
  ),
26722
26722
  style: { paddingLeft: `${level * indentSize + baseIndent}rem` },
26723
26723
  children: [
26724
+ virtualItem && showTreeLines && Array.from({ length: level }, (_, ancestorLevel) => /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
26725
+ "span",
26726
+ {
26727
+ "data-tree-line": true,
26728
+ "aria-hidden": "true",
26729
+ className: "pointer-events-none absolute inset-y-0 w-px border-l border-dashed border-border/50",
26730
+ style: { left: `${ancestorLevel * indentSize + baseIndent + 0.5}rem` }
26731
+ },
26732
+ ancestorLevel
26733
+ )),
26724
26734
  hasChildren ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
26725
26735
  "button",
26726
26736
  {
@@ -26769,16 +26779,23 @@ function CategoryTreeSelect(props) {
26769
26779
  ]
26770
26780
  }
26771
26781
  ),
26772
- !virtualItem && hasChildren && isExpanded && /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
26782
+ !virtualItem && hasChildren && isExpanded && /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(
26773
26783
  "div",
26774
26784
  {
26775
26785
  role: "group",
26776
- className: cn(
26777
- TREE_BRANCH_OFFSET_CLASS,
26778
- "border-l-2 border-dashed border-border/50",
26779
- "animate-in slide-in-from-top-2 fade-in-50 duration-200"
26780
- ),
26781
- children: children.map((child) => renderCategoryRow(child, level + 1))
26786
+ className: "relative animate-in fade-in-50 slide-in-from-top-2 duration-200",
26787
+ children: [
26788
+ showTreeLines && /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
26789
+ "span",
26790
+ {
26791
+ "data-tree-line": true,
26792
+ "aria-hidden": "true",
26793
+ className: "pointer-events-none absolute inset-y-0 w-px border-l border-dashed border-border/50",
26794
+ style: { left: `${level * indentSize + baseIndent + 0.5}rem` }
26795
+ }
26796
+ ),
26797
+ children.map((child) => renderCategoryRow(child, level + 1))
26798
+ ]
26782
26799
  }
26783
26800
  )
26784
26801
  ]
@@ -30925,6 +30942,7 @@ function DataTableBodyRows({
30925
30942
  style: getStickyColumnStyle(col),
30926
30943
  className: cn(
30927
30944
  cellPadding,
30945
+ (!col.align || col.align === "left") && "text-left",
30928
30946
  col.align === "right" && "text-right",
30929
30947
  col.align === "center" && "text-center",
30930
30948
  showBorderLeft && "border-l border-border/60",
@@ -31130,74 +31148,97 @@ function DataTableHeader({
31130
31148
  className: cn(
31131
31149
  "flex items-center gap-1",
31132
31150
  headerMinHeightClass,
31133
- col.align === "right" && "justify-end",
31134
- col.align === "center" && "justify-center",
31135
- !col.align && "justify-start"
31151
+ (col.headerAlign ?? headerAlign) === "right" && "justify-end",
31152
+ (col.headerAlign ?? headerAlign) === "center" && "justify-center",
31153
+ (col.headerAlign ?? headerAlign) === "left" && "justify-start"
31136
31154
  ),
31137
31155
  children: /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("span", { className: cn("font-medium whitespace-nowrap select-text", headerTitleClass), children: col.title })
31138
31156
  }
31139
31157
  );
31140
31158
  }
31141
- const isRightAlign = col.align === "right" || !col.align && headerAlign === "right";
31142
- const isCenterAlign = col.align === "center" || !col.align && headerAlign === "center";
31159
+ const resolvedHeaderAlign = col.headerAlign ?? headerAlign;
31160
+ const isRightAlign = resolvedHeaderAlign === "right";
31161
+ const isCenterAlign = resolvedHeaderAlign === "center";
31143
31162
  const columnLabel = getColumnLabel(col.title) || col.key;
31144
31163
  const sortByText = sortByLabel ?? gi18n.sortBy ?? "Sort by";
31145
31164
  const sortLabel = `${sortByText} ${columnLabel}`;
31146
31165
  const useEndIcon = (col.iconPosition ?? "end") === "end";
31147
- const titleContent = /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("div", { className: cn("flex items-center gap-1", useEndIcon && "flex-1 justify-between"), children: [
31148
- /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("span", { className: cn("font-medium whitespace-nowrap select-text", headerTitleClass), children: col.title }),
31149
- col.sortable && /* @__PURE__ */ (0, import_jsx_runtime69.jsx)(
31150
- Tooltip,
31151
- {
31152
- placement: "top",
31153
- content: /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("span", { className: "text-xs font-medium", children: sortLabel }),
31154
- children: /* @__PURE__ */ (0, import_jsx_runtime69.jsx)(
31155
- "button",
31166
+ const titleContent = /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)(
31167
+ "div",
31168
+ {
31169
+ "data-underverse-datatable-header-content": col.key,
31170
+ className: cn(
31171
+ "items-center gap-1",
31172
+ useEndIcon && isCenterAlign ? "grid flex-1 grid-cols-[1fr_auto_1fr]" : "flex",
31173
+ useEndIcon && !isCenterAlign && "flex-1 justify-between"
31174
+ ),
31175
+ children: [
31176
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)(
31177
+ "span",
31156
31178
  {
31157
- type: "button",
31158
- title: sortLabel,
31159
31179
  className: cn(
31160
- "p-1 rounded-lg transition-all duration-200 hover:bg-foreground/10",
31161
- sort?.key === col.key ? "opacity-100 bg-foreground/10" : "opacity-60 hover:opacity-100"
31180
+ "font-medium whitespace-nowrap select-text",
31181
+ useEndIcon && isCenterAlign && "col-start-2",
31182
+ headerTitleClass
31162
31183
  ),
31163
- onClick: () => {
31164
- setCurPage(1);
31165
- setSort((current) => {
31166
- if (!current || current.key !== col.key) return { key: col.key, order: "asc" };
31167
- if (current.order === "asc") return { key: col.key, order: "desc" };
31168
- return null;
31169
- });
31170
- },
31171
- "aria-label": sortLabel,
31172
- children: /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("svg", { viewBox: "0 0 20 20", fill: "none", className: cn("inline-block", sortIconClass), children: [
31173
- /* @__PURE__ */ (0, import_jsx_runtime69.jsx)(
31174
- "path",
31175
- {
31176
- d: "M5 7.5l5-5 5 5",
31177
- stroke: "currentColor",
31178
- strokeWidth: "1.5",
31179
- strokeLinecap: "round",
31180
- strokeLinejoin: "round",
31181
- opacity: sort?.key === col.key && sort.order === "asc" ? 1 : 0.4
31182
- }
31183
- ),
31184
- /* @__PURE__ */ (0, import_jsx_runtime69.jsx)(
31185
- "path",
31186
- {
31187
- d: "M5 12.5l5 5 5-5",
31188
- stroke: "currentColor",
31189
- strokeWidth: "1.5",
31190
- strokeLinecap: "round",
31191
- strokeLinejoin: "round",
31192
- opacity: sort?.key === col.key && sort.order === "desc" ? 1 : 0.4
31193
- }
31194
- )
31195
- ] })
31184
+ children: col.title
31185
+ }
31186
+ ),
31187
+ col.sortable && /* @__PURE__ */ (0, import_jsx_runtime69.jsx)(
31188
+ Tooltip,
31189
+ {
31190
+ placement: "top",
31191
+ content: /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("span", { className: "text-xs font-medium", children: sortLabel }),
31192
+ children: /* @__PURE__ */ (0, import_jsx_runtime69.jsx)(
31193
+ "button",
31194
+ {
31195
+ type: "button",
31196
+ title: sortLabel,
31197
+ className: cn(
31198
+ "p-1 rounded-lg transition-all duration-200 hover:bg-foreground/10",
31199
+ useEndIcon && isCenterAlign && "col-start-3 justify-self-end",
31200
+ sort?.key === col.key ? "opacity-100 bg-foreground/10" : "opacity-60 hover:opacity-100"
31201
+ ),
31202
+ onClick: () => {
31203
+ setCurPage(1);
31204
+ setSort((current) => {
31205
+ if (!current || current.key !== col.key) return { key: col.key, order: "asc" };
31206
+ if (current.order === "asc") return { key: col.key, order: "desc" };
31207
+ return null;
31208
+ });
31209
+ },
31210
+ "aria-label": sortLabel,
31211
+ children: /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("svg", { viewBox: "0 0 20 20", fill: "none", className: cn("inline-block", sortIconClass), children: [
31212
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)(
31213
+ "path",
31214
+ {
31215
+ d: "M5 7.5l5-5 5 5",
31216
+ stroke: "currentColor",
31217
+ strokeWidth: "1.5",
31218
+ strokeLinecap: "round",
31219
+ strokeLinejoin: "round",
31220
+ opacity: sort?.key === col.key && sort.order === "asc" ? 1 : 0.4
31221
+ }
31222
+ ),
31223
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)(
31224
+ "path",
31225
+ {
31226
+ d: "M5 12.5l5 5 5-5",
31227
+ stroke: "currentColor",
31228
+ strokeWidth: "1.5",
31229
+ strokeLinecap: "round",
31230
+ strokeLinejoin: "round",
31231
+ opacity: sort?.key === col.key && sort.order === "desc" ? 1 : 0.4
31232
+ }
31233
+ )
31234
+ ] })
31235
+ }
31236
+ )
31196
31237
  }
31197
31238
  )
31198
- }
31199
- )
31200
- ] });
31239
+ ]
31240
+ }
31241
+ );
31201
31242
  const filterContent = col.filter ? /* @__PURE__ */ (0, import_jsx_runtime69.jsx)(
31202
31243
  Popover,
31203
31244
  {
@@ -31301,8 +31342,8 @@ function DataTableHeader({
31301
31342
  },
31302
31343
  className: cn(
31303
31344
  "relative",
31304
- (col.align === "right" || !col.align && headerAlign === "right") && "text-right",
31305
- (col.align === "center" || !col.align && headerAlign === "center") && "text-center",
31345
+ (col.headerAlign ?? headerAlign) === "right" && "text-right",
31346
+ (col.headerAlign ?? headerAlign) === "center" && "text-center",
31306
31347
  showBorderLeft && "border-l border-border/60",
31307
31348
  getStickyHeaderClass(col)
31308
31349
  ),
@@ -31824,7 +31865,7 @@ function useDataTableState({
31824
31865
  const allLeafColumns = import_react40.default.useMemo(() => getLeafColumns(columns), [columns]);
31825
31866
  const defaultVisibleLeafKeys = import_react40.default.useMemo(() => allLeafColumns.filter((column) => column.visible !== false).map((column) => column.key), [allLeafColumns]);
31826
31867
  const knownLeafKeysRef = import_react40.default.useRef(new Set(defaultVisibleLeafKeys));
31827
- const [headerAlign, setHeaderAlign] = import_react40.default.useState("left");
31868
+ const [headerAlign, setHeaderAlign] = import_react40.default.useState("center");
31828
31869
  const [visibleCols, setVisibleCols] = import_react40.default.useState(defaultVisibleLeafKeys);
31829
31870
  const [filters, setFilters] = import_react40.default.useState({});
31830
31871
  const [sort, setSort] = import_react40.default.useState(null);