@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/dist/index.d.cts CHANGED
@@ -2116,7 +2116,7 @@ interface CategoryTreeSelectBaseProps {
2116
2116
  leafOnlySelect?: boolean;
2117
2117
  /** Virtualize the dropdown tree by rendering only visible rows. Inline/view-only trees keep recursive rendering. */
2118
2118
  virtualized?: boolean;
2119
- /** Estimated tree row height used by virtualized rendering. Default: `44`. */
2119
+ /** Estimated tree row height used by virtualized rendering. Default: `36`. */
2120
2120
  estimatedItemHeight?: number;
2121
2121
  /** Number of extra rows rendered above and below the visible range. Default: `8`. */
2122
2122
  overscan?: number;
@@ -2149,7 +2149,7 @@ interface CategoryTreeSelectBaseProps {
2149
2149
  */
2150
2150
  renderItemActions?: (category: Category) => React__default.ReactNode;
2151
2151
  /**
2152
- * Base left padding in rem applied to every node regardless of depth. Default: `0.75`.
2152
+ * Base left padding in rem applied to every node regardless of depth. Default: `0`.
2153
2153
  *
2154
2154
  * @remarks
2155
2155
  * Combined with `indentSize`, the final padding of a node at a given depth is:
@@ -2161,7 +2161,7 @@ interface CategoryTreeSelectBaseProps {
2161
2161
  */
2162
2162
  baseIndent?: number;
2163
2163
  /**
2164
- * Additional left padding in rem added per depth level. Default: `1`.
2164
+ * Additional left padding in rem added per depth level. Default: `0.75`.
2165
2165
  *
2166
2166
  * @remarks
2167
2167
  * Set to a smaller value (e.g. `0.75`) to reduce the visual gap between
@@ -2172,6 +2172,8 @@ interface CategoryTreeSelectBaseProps {
2172
2172
  * <CategoryTreeSelect baseIndent={0.25} indentSize={0.75} />
2173
2173
  */
2174
2174
  indentSize?: number;
2175
+ /** Show dashed vertical guide lines between parent and child levels. Default: `true`. */
2176
+ showTreeLines?: boolean;
2175
2177
  borderMode?: BorderMode;
2176
2178
  }
2177
2179
  /** Multi-select mode. This is the default when `singleSelect` is omitted. */
@@ -2784,7 +2786,10 @@ type DataTableColumn<T> = {
2784
2786
  };
2785
2787
  render?: (value: any, record: T, index: number) => React__default.ReactNode;
2786
2788
  width?: number | string;
2789
+ /** Body cell alignment for this column. Defaults to `"left"`. */
2787
2790
  align?: "left" | "center" | "right";
2791
+ /** Header alignment for this column. Defaults to the table header alignment (`"center"` initially). */
2792
+ headerAlign?: "left" | "center" | "right";
2788
2793
  /** Vị trí của icon sort/filter: "inline" (ngay sát text) hoặc "end" (cuối dòng). Mặc định là "end". */
2789
2794
  iconPosition?: "inline" | "end";
2790
2795
  visible?: boolean;
package/dist/index.d.ts CHANGED
@@ -2116,7 +2116,7 @@ interface CategoryTreeSelectBaseProps {
2116
2116
  leafOnlySelect?: boolean;
2117
2117
  /** Virtualize the dropdown tree by rendering only visible rows. Inline/view-only trees keep recursive rendering. */
2118
2118
  virtualized?: boolean;
2119
- /** Estimated tree row height used by virtualized rendering. Default: `44`. */
2119
+ /** Estimated tree row height used by virtualized rendering. Default: `36`. */
2120
2120
  estimatedItemHeight?: number;
2121
2121
  /** Number of extra rows rendered above and below the visible range. Default: `8`. */
2122
2122
  overscan?: number;
@@ -2149,7 +2149,7 @@ interface CategoryTreeSelectBaseProps {
2149
2149
  */
2150
2150
  renderItemActions?: (category: Category) => React__default.ReactNode;
2151
2151
  /**
2152
- * Base left padding in rem applied to every node regardless of depth. Default: `0.75`.
2152
+ * Base left padding in rem applied to every node regardless of depth. Default: `0`.
2153
2153
  *
2154
2154
  * @remarks
2155
2155
  * Combined with `indentSize`, the final padding of a node at a given depth is:
@@ -2161,7 +2161,7 @@ interface CategoryTreeSelectBaseProps {
2161
2161
  */
2162
2162
  baseIndent?: number;
2163
2163
  /**
2164
- * Additional left padding in rem added per depth level. Default: `1`.
2164
+ * Additional left padding in rem added per depth level. Default: `0.75`.
2165
2165
  *
2166
2166
  * @remarks
2167
2167
  * Set to a smaller value (e.g. `0.75`) to reduce the visual gap between
@@ -2172,6 +2172,8 @@ interface CategoryTreeSelectBaseProps {
2172
2172
  * <CategoryTreeSelect baseIndent={0.25} indentSize={0.75} />
2173
2173
  */
2174
2174
  indentSize?: number;
2175
+ /** Show dashed vertical guide lines between parent and child levels. Default: `true`. */
2176
+ showTreeLines?: boolean;
2175
2177
  borderMode?: BorderMode;
2176
2178
  }
2177
2179
  /** Multi-select mode. This is the default when `singleSelect` is omitted. */
@@ -2784,7 +2786,10 @@ type DataTableColumn<T> = {
2784
2786
  };
2785
2787
  render?: (value: any, record: T, index: number) => React__default.ReactNode;
2786
2788
  width?: number | string;
2789
+ /** Body cell alignment for this column. Defaults to `"left"`. */
2787
2790
  align?: "left" | "center" | "right";
2791
+ /** Header alignment for this column. Defaults to the table header alignment (`"center"` initially). */
2792
+ headerAlign?: "left" | "center" | "right";
2788
2793
  /** Vị trí của icon sort/filter: "inline" (ngay sát text) hoặc "end" (cuối dòng). Mặc định là "end". */
2789
2794
  iconPosition?: "inline" | "end";
2790
2795
  visible?: boolean;
package/dist/index.js CHANGED
@@ -16682,9 +16682,8 @@ var defaultLabels = {
16682
16682
  searchPlaceholder: "Search...",
16683
16683
  noResultsText: "No results found"
16684
16684
  };
16685
- var TREE_NODE_BASE_PADDING_REM = 0.75;
16686
- var TREE_NODE_INDENT_REM = 1;
16687
- var TREE_BRANCH_OFFSET_CLASS = "ml-1.5 pl-1.5";
16685
+ var TREE_NODE_BASE_PADDING_REM = 0;
16686
+ var TREE_NODE_INDENT_REM = 0.75;
16688
16687
  var TREE_NODE_GAP_CLASS = "gap-1.5";
16689
16688
  var TREE_EXPANDER_PLACEHOLDER_CLASS = "w-5";
16690
16689
  var CATEGORY_TREE_DROPDOWN_MAX_HEIGHT = 320;
@@ -16814,7 +16813,7 @@ function CategoryTreeSelect(props) {
16814
16813
  useOverlayScrollbar = false,
16815
16814
  leafOnlySelect = false,
16816
16815
  virtualized = false,
16817
- estimatedItemHeight = 44,
16816
+ estimatedItemHeight = 36,
16818
16817
  overscan = 8,
16819
16818
  maxInitialOptions,
16820
16819
  searchMode = "auto",
@@ -16824,6 +16823,7 @@ function CategoryTreeSelect(props) {
16824
16823
  showSearchPromptWhenEmptyQuery = false,
16825
16824
  baseIndent = TREE_NODE_BASE_PADDING_REM,
16826
16825
  indentSize = TREE_NODE_INDENT_REM,
16826
+ showTreeLines = true,
16827
16827
  renderItemActions,
16828
16828
  singleSelect = false,
16829
16829
  borderMode
@@ -17111,7 +17111,7 @@ function CategoryTreeSelect(props) {
17111
17111
  {
17112
17112
  ref: virtualItem ? treeVirtualizer.measureElement : void 0,
17113
17113
  "data-index": virtualItem?.index,
17114
- className: cn("min-w-0 [content-visibility:auto] [contain-intrinsic-size:44px]", !virtualItem && "animate-in fade-in-50 duration-200"),
17114
+ className: cn("min-w-0 [content-visibility:auto] [contain-intrinsic-size:36px]", !virtualItem && "animate-in fade-in-50 duration-200"),
17115
17115
  style: { animationDelay: virtualItem ? void 0 : `${level * 30}ms`, ...rowStyle },
17116
17116
  children: [
17117
17117
  /* @__PURE__ */ jsxs36(
@@ -17123,7 +17123,7 @@ function CategoryTreeSelect(props) {
17123
17123
  "aria-selected": viewOnly ? void 0 : isSelected,
17124
17124
  onClick: () => !viewOnly && handleSelect(category.id, category),
17125
17125
  className: cn(
17126
- "relative flex min-w-0 items-center px-3 py-2.5 min-h-11 transition-all duration-200 rounded-3xl",
17126
+ "relative flex min-h-9 min-w-0 items-center rounded-3xl transition-all duration-200",
17127
17127
  TREE_NODE_GAP_CLASS,
17128
17128
  !viewOnly && (isSelectable ? "cursor-pointer" : "cursor-default"),
17129
17129
  isSelectable && !isSelected && "hover:bg-accent/50",
@@ -17133,6 +17133,16 @@ function CategoryTreeSelect(props) {
17133
17133
  ),
17134
17134
  style: { paddingLeft: `${level * indentSize + baseIndent}rem` },
17135
17135
  children: [
17136
+ virtualItem && showTreeLines && Array.from({ length: level }, (_, ancestorLevel) => /* @__PURE__ */ jsx43(
17137
+ "span",
17138
+ {
17139
+ "data-tree-line": true,
17140
+ "aria-hidden": "true",
17141
+ className: "pointer-events-none absolute inset-y-0 w-px border-l border-dashed border-border/50",
17142
+ style: { left: `${ancestorLevel * indentSize + baseIndent + 0.5}rem` }
17143
+ },
17144
+ ancestorLevel
17145
+ )),
17136
17146
  hasChildren ? /* @__PURE__ */ jsx43(
17137
17147
  "button",
17138
17148
  {
@@ -17181,16 +17191,23 @@ function CategoryTreeSelect(props) {
17181
17191
  ]
17182
17192
  }
17183
17193
  ),
17184
- !virtualItem && hasChildren && isExpanded && /* @__PURE__ */ jsx43(
17194
+ !virtualItem && hasChildren && isExpanded && /* @__PURE__ */ jsxs36(
17185
17195
  "div",
17186
17196
  {
17187
17197
  role: "group",
17188
- className: cn(
17189
- TREE_BRANCH_OFFSET_CLASS,
17190
- "border-l-2 border-dashed border-border/50",
17191
- "animate-in slide-in-from-top-2 fade-in-50 duration-200"
17192
- ),
17193
- children: children.map((child) => renderCategoryRow(child, level + 1))
17198
+ className: "relative animate-in fade-in-50 slide-in-from-top-2 duration-200",
17199
+ children: [
17200
+ showTreeLines && /* @__PURE__ */ jsx43(
17201
+ "span",
17202
+ {
17203
+ "data-tree-line": true,
17204
+ "aria-hidden": "true",
17205
+ className: "pointer-events-none absolute inset-y-0 w-px border-l border-dashed border-border/50",
17206
+ style: { left: `${level * indentSize + baseIndent + 0.5}rem` }
17207
+ }
17208
+ ),
17209
+ children.map((child) => renderCategoryRow(child, level + 1))
17210
+ ]
17194
17211
  }
17195
17212
  )
17196
17213
  ]
@@ -21324,6 +21341,7 @@ function DataTableBodyRows({
21324
21341
  style: getStickyColumnStyle(col),
21325
21342
  className: cn(
21326
21343
  cellPadding,
21344
+ (!col.align || col.align === "left") && "text-left",
21327
21345
  col.align === "right" && "text-right",
21328
21346
  col.align === "center" && "text-center",
21329
21347
  showBorderLeft && "border-l border-border/60",
@@ -21525,74 +21543,97 @@ function DataTableHeader({
21525
21543
  className: cn(
21526
21544
  "flex items-center gap-1",
21527
21545
  headerMinHeightClass,
21528
- col.align === "right" && "justify-end",
21529
- col.align === "center" && "justify-center",
21530
- !col.align && "justify-start"
21546
+ (col.headerAlign ?? headerAlign) === "right" && "justify-end",
21547
+ (col.headerAlign ?? headerAlign) === "center" && "justify-center",
21548
+ (col.headerAlign ?? headerAlign) === "left" && "justify-start"
21531
21549
  ),
21532
21550
  children: /* @__PURE__ */ jsx58("span", { className: cn("font-medium whitespace-nowrap select-text", headerTitleClass), children: col.title })
21533
21551
  }
21534
21552
  );
21535
21553
  }
21536
- const isRightAlign = col.align === "right" || !col.align && headerAlign === "right";
21537
- const isCenterAlign = col.align === "center" || !col.align && headerAlign === "center";
21554
+ const resolvedHeaderAlign = col.headerAlign ?? headerAlign;
21555
+ const isRightAlign = resolvedHeaderAlign === "right";
21556
+ const isCenterAlign = resolvedHeaderAlign === "center";
21538
21557
  const columnLabel = getColumnLabel(col.title) || col.key;
21539
21558
  const sortByText = sortByLabel ?? gi18n.sortBy ?? "Sort by";
21540
21559
  const sortLabel = `${sortByText} ${columnLabel}`;
21541
21560
  const useEndIcon = (col.iconPosition ?? "end") === "end";
21542
- const titleContent = /* @__PURE__ */ jsxs50("div", { className: cn("flex items-center gap-1", useEndIcon && "flex-1 justify-between"), children: [
21543
- /* @__PURE__ */ jsx58("span", { className: cn("font-medium whitespace-nowrap select-text", headerTitleClass), children: col.title }),
21544
- col.sortable && /* @__PURE__ */ jsx58(
21545
- Tooltip,
21546
- {
21547
- placement: "top",
21548
- content: /* @__PURE__ */ jsx58("span", { className: "text-xs font-medium", children: sortLabel }),
21549
- children: /* @__PURE__ */ jsx58(
21550
- "button",
21561
+ const titleContent = /* @__PURE__ */ jsxs50(
21562
+ "div",
21563
+ {
21564
+ "data-underverse-datatable-header-content": col.key,
21565
+ className: cn(
21566
+ "items-center gap-1",
21567
+ useEndIcon && isCenterAlign ? "grid flex-1 grid-cols-[1fr_auto_1fr]" : "flex",
21568
+ useEndIcon && !isCenterAlign && "flex-1 justify-between"
21569
+ ),
21570
+ children: [
21571
+ /* @__PURE__ */ jsx58(
21572
+ "span",
21551
21573
  {
21552
- type: "button",
21553
- title: sortLabel,
21554
21574
  className: cn(
21555
- "p-1 rounded-lg transition-all duration-200 hover:bg-foreground/10",
21556
- sort?.key === col.key ? "opacity-100 bg-foreground/10" : "opacity-60 hover:opacity-100"
21575
+ "font-medium whitespace-nowrap select-text",
21576
+ useEndIcon && isCenterAlign && "col-start-2",
21577
+ headerTitleClass
21557
21578
  ),
21558
- onClick: () => {
21559
- setCurPage(1);
21560
- setSort((current) => {
21561
- if (!current || current.key !== col.key) return { key: col.key, order: "asc" };
21562
- if (current.order === "asc") return { key: col.key, order: "desc" };
21563
- return null;
21564
- });
21565
- },
21566
- "aria-label": sortLabel,
21567
- children: /* @__PURE__ */ jsxs50("svg", { viewBox: "0 0 20 20", fill: "none", className: cn("inline-block", sortIconClass), children: [
21568
- /* @__PURE__ */ jsx58(
21569
- "path",
21570
- {
21571
- d: "M5 7.5l5-5 5 5",
21572
- stroke: "currentColor",
21573
- strokeWidth: "1.5",
21574
- strokeLinecap: "round",
21575
- strokeLinejoin: "round",
21576
- opacity: sort?.key === col.key && sort.order === "asc" ? 1 : 0.4
21577
- }
21578
- ),
21579
- /* @__PURE__ */ jsx58(
21580
- "path",
21581
- {
21582
- d: "M5 12.5l5 5 5-5",
21583
- stroke: "currentColor",
21584
- strokeWidth: "1.5",
21585
- strokeLinecap: "round",
21586
- strokeLinejoin: "round",
21587
- opacity: sort?.key === col.key && sort.order === "desc" ? 1 : 0.4
21588
- }
21589
- )
21590
- ] })
21579
+ children: col.title
21580
+ }
21581
+ ),
21582
+ col.sortable && /* @__PURE__ */ jsx58(
21583
+ Tooltip,
21584
+ {
21585
+ placement: "top",
21586
+ content: /* @__PURE__ */ jsx58("span", { className: "text-xs font-medium", children: sortLabel }),
21587
+ children: /* @__PURE__ */ jsx58(
21588
+ "button",
21589
+ {
21590
+ type: "button",
21591
+ title: sortLabel,
21592
+ className: cn(
21593
+ "p-1 rounded-lg transition-all duration-200 hover:bg-foreground/10",
21594
+ useEndIcon && isCenterAlign && "col-start-3 justify-self-end",
21595
+ sort?.key === col.key ? "opacity-100 bg-foreground/10" : "opacity-60 hover:opacity-100"
21596
+ ),
21597
+ onClick: () => {
21598
+ setCurPage(1);
21599
+ setSort((current) => {
21600
+ if (!current || current.key !== col.key) return { key: col.key, order: "asc" };
21601
+ if (current.order === "asc") return { key: col.key, order: "desc" };
21602
+ return null;
21603
+ });
21604
+ },
21605
+ "aria-label": sortLabel,
21606
+ children: /* @__PURE__ */ jsxs50("svg", { viewBox: "0 0 20 20", fill: "none", className: cn("inline-block", sortIconClass), children: [
21607
+ /* @__PURE__ */ jsx58(
21608
+ "path",
21609
+ {
21610
+ d: "M5 7.5l5-5 5 5",
21611
+ stroke: "currentColor",
21612
+ strokeWidth: "1.5",
21613
+ strokeLinecap: "round",
21614
+ strokeLinejoin: "round",
21615
+ opacity: sort?.key === col.key && sort.order === "asc" ? 1 : 0.4
21616
+ }
21617
+ ),
21618
+ /* @__PURE__ */ jsx58(
21619
+ "path",
21620
+ {
21621
+ d: "M5 12.5l5 5 5-5",
21622
+ stroke: "currentColor",
21623
+ strokeWidth: "1.5",
21624
+ strokeLinecap: "round",
21625
+ strokeLinejoin: "round",
21626
+ opacity: sort?.key === col.key && sort.order === "desc" ? 1 : 0.4
21627
+ }
21628
+ )
21629
+ ] })
21630
+ }
21631
+ )
21591
21632
  }
21592
21633
  )
21593
- }
21594
- )
21595
- ] });
21634
+ ]
21635
+ }
21636
+ );
21596
21637
  const filterContent = col.filter ? /* @__PURE__ */ jsx58(
21597
21638
  Popover,
21598
21639
  {
@@ -21696,8 +21737,8 @@ function DataTableHeader({
21696
21737
  },
21697
21738
  className: cn(
21698
21739
  "relative",
21699
- (col.align === "right" || !col.align && headerAlign === "right") && "text-right",
21700
- (col.align === "center" || !col.align && headerAlign === "center") && "text-center",
21740
+ (col.headerAlign ?? headerAlign) === "right" && "text-right",
21741
+ (col.headerAlign ?? headerAlign) === "center" && "text-center",
21701
21742
  showBorderLeft && "border-l border-border/60",
21702
21743
  getStickyHeaderClass(col)
21703
21744
  ),
@@ -22217,7 +22258,7 @@ function useDataTableState({
22217
22258
  const allLeafColumns = React56.useMemo(() => getLeafColumns(columns), [columns]);
22218
22259
  const defaultVisibleLeafKeys = React56.useMemo(() => allLeafColumns.filter((column) => column.visible !== false).map((column) => column.key), [allLeafColumns]);
22219
22260
  const knownLeafKeysRef = React56.useRef(new Set(defaultVisibleLeafKeys));
22220
- const [headerAlign, setHeaderAlign] = React56.useState("left");
22261
+ const [headerAlign, setHeaderAlign] = React56.useState("center");
22221
22262
  const [visibleCols, setVisibleCols] = React56.useState(defaultVisibleLeafKeys);
22222
22263
  const [filters, setFilters] = React56.useState({});
22223
22264
  const [sort, setSort] = React56.useState(null);