@underverse-ui/underverse 1.0.39 → 1.0.41

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.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "package": "@underverse-ui/underverse",
3
- "version": "1.0.39",
3
+ "version": "1.0.41",
4
4
  "sourceEntry": "src/index.ts",
5
- "totalExports": 232,
5
+ "totalExports": 238,
6
6
  "exports": [
7
7
  {
8
8
  "name": "*",
@@ -322,6 +322,13 @@
322
322
  "reexport": true,
323
323
  "local": false
324
324
  },
325
+ {
326
+ "name": "Category",
327
+ "kind": "type",
328
+ "source": "./components/CategoryTreeSelect",
329
+ "reexport": true,
330
+ "local": false
331
+ },
325
332
  {
326
333
  "name": "CategoryTreeSelect",
327
334
  "kind": "value",
@@ -329,6 +336,41 @@
329
336
  "reexport": true,
330
337
  "local": false
331
338
  },
339
+ {
340
+ "name": "CategoryTreeSelectBaseProps",
341
+ "kind": "type",
342
+ "source": "./components/CategoryTreeSelect",
343
+ "reexport": true,
344
+ "local": false
345
+ },
346
+ {
347
+ "name": "CategoryTreeSelectLabels",
348
+ "kind": "type",
349
+ "source": "./components/CategoryTreeSelect",
350
+ "reexport": true,
351
+ "local": false
352
+ },
353
+ {
354
+ "name": "CategoryTreeSelectMultiProps",
355
+ "kind": "type",
356
+ "source": "./components/CategoryTreeSelect",
357
+ "reexport": true,
358
+ "local": false
359
+ },
360
+ {
361
+ "name": "CategoryTreeSelectProps",
362
+ "kind": "type",
363
+ "source": "./components/CategoryTreeSelect",
364
+ "reexport": true,
365
+ "local": false
366
+ },
367
+ {
368
+ "name": "CategoryTreeSelectSingleProps",
369
+ "kind": "type",
370
+ "source": "./components/CategoryTreeSelect",
371
+ "reexport": true,
372
+ "local": false
373
+ },
332
374
  {
333
375
  "name": "Checkbox",
334
376
  "kind": "value",
package/dist/index.cjs CHANGED
@@ -14947,9 +14947,18 @@ var defaultLabels = {
14947
14947
  };
14948
14948
  function CategoryTreeSelect(props) {
14949
14949
  const {
14950
+ id,
14951
+ label,
14952
+ labelClassName,
14950
14953
  categories,
14951
14954
  placeholder = "Select category",
14952
14955
  disabled,
14956
+ required = false,
14957
+ size = "md",
14958
+ variant = "default",
14959
+ allowClear = false,
14960
+ error,
14961
+ helperText,
14953
14962
  viewOnly = false,
14954
14963
  defaultExpanded = false,
14955
14964
  enableSearch,
@@ -14966,6 +14975,12 @@ function CategoryTreeSelect(props) {
14966
14975
  const searchInputRef = (0, import_react19.useRef)(null);
14967
14976
  const dropdownViewportRef = (0, import_react19.useRef)(null);
14968
14977
  useOverlayScrollbarTarget(dropdownViewportRef, { enabled: useOverlayScrollbar });
14978
+ const autoId = (0, import_react19.useId)();
14979
+ const resolvedId = id ? String(id) : `category-tree-select-${autoId}`;
14980
+ const labelId = label ? `${resolvedId}-label` : void 0;
14981
+ const helperId = helperText && !error ? `${resolvedId}-helper` : void 0;
14982
+ const errorId = error ? `${resolvedId}-error` : void 0;
14983
+ const describedBy = errorId || helperId;
14969
14984
  const mergedLabels = { ...defaultLabels, ...labels };
14970
14985
  const valueArray = singleSelect ? props.value != null ? [props.value] : [] : props.value || [];
14971
14986
  const { parentCategories, childrenMap, byId } = (0, import_react19.useMemo)(() => {
@@ -15009,8 +15024,8 @@ function CategoryTreeSelect(props) {
15009
15024
  const stack = [rootId];
15010
15025
  let guard = 0;
15011
15026
  while (stack.length > 0 && guard++ < categories.length * 3) {
15012
- const id = stack.pop();
15013
- const children = childrenMap.get(id) ?? [];
15027
+ const id2 = stack.pop();
15028
+ const children = childrenMap.get(id2) ?? [];
15014
15029
  for (const child of children) {
15015
15030
  if (visible.has(child.id)) continue;
15016
15031
  visible.add(child.id);
@@ -15040,13 +15055,13 @@ function CategoryTreeSelect(props) {
15040
15055
  setExpandedNodes(new Set(allParentIds));
15041
15056
  }
15042
15057
  }, [viewOnly, inline, defaultExpanded, categories]);
15043
- const toggleExpand = (id) => {
15058
+ const toggleExpand = (id2) => {
15044
15059
  if (isSearchMode) return;
15045
15060
  const newExpanded = new Set(expandedNodes);
15046
- if (newExpanded.has(id)) {
15047
- newExpanded.delete(id);
15061
+ if (newExpanded.has(id2)) {
15062
+ newExpanded.delete(id2);
15048
15063
  } else {
15049
- newExpanded.add(id);
15064
+ newExpanded.add(id2);
15050
15065
  }
15051
15066
  setExpandedNodes(newExpanded);
15052
15067
  };
@@ -15190,29 +15205,112 @@ function CategoryTreeSelect(props) {
15190
15205
  /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "w-12 h-12 rounded-2xl bg-muted/50 flex items-center justify-center mb-3", children: isSearchMode ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_lucide_react25.SearchX, { className: "w-6 h-6 text-muted-foreground/50" }) : /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_lucide_react25.Layers, { className: "w-6 h-6 text-muted-foreground/50" }) }),
15191
15206
  /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { className: "text-sm text-muted-foreground", children: isSearchMode ? mergedLabels.noResultsText : mergedLabels.emptyText })
15192
15207
  ] }) : effectiveParentCategories.map((cat) => renderCategory(cat)) });
15208
+ const renderLabel = () => label ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
15209
+ Label,
15210
+ {
15211
+ id: labelId,
15212
+ htmlFor: resolvedId,
15213
+ className: cn(
15214
+ size === "sm" ? "text-xs" : size === "lg" ? "text-base" : "text-sm",
15215
+ disabled ? "text-muted-foreground" : "text-foreground",
15216
+ error && "text-destructive",
15217
+ labelClassName
15218
+ ),
15219
+ children: [
15220
+ label,
15221
+ required && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { className: "ml-1 text-destructive", children: "*" })
15222
+ ]
15223
+ }
15224
+ ) }) : null;
15225
+ const renderAssistiveText = () => error ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("p", { id: errorId, className: "text-sm text-destructive", children: error }) : helperText ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("p", { id: helperId, className: "text-sm text-muted-foreground", children: helperText }) : null;
15193
15226
  if (viewOnly) {
15194
- return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: cn("rounded-2xl border border-border/60 bg-card/50 backdrop-blur-sm p-3 shadow-sm", disabled && "opacity-50", className), children: [
15195
- renderSearch(),
15196
- renderTreeContent()
15227
+ return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: cn("w-full space-y-2", className), children: [
15228
+ renderLabel(),
15229
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
15230
+ "div",
15231
+ {
15232
+ id: resolvedId,
15233
+ "aria-labelledby": labelId,
15234
+ "aria-describedby": describedBy,
15235
+ className: cn("rounded-2xl border border-border/60 bg-card/50 backdrop-blur-sm p-3 shadow-sm", disabled && "opacity-50"),
15236
+ children: [
15237
+ renderSearch(),
15238
+ renderTreeContent()
15239
+ ]
15240
+ }
15241
+ ),
15242
+ renderAssistiveText()
15197
15243
  ] });
15198
15244
  }
15199
15245
  if (inline) {
15200
- return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
15201
- "div",
15202
- {
15203
- className: cn(
15204
- "rounded-2xl border border-border/60 bg-card/50 backdrop-blur-sm p-3 shadow-sm",
15205
- disabled && "opacity-50 pointer-events-none",
15206
- className
15207
- ),
15208
- children: [
15209
- renderSearch(),
15210
- renderTreeContent()
15211
- ]
15212
- }
15213
- );
15246
+ return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: cn("w-full space-y-2", className), children: [
15247
+ renderLabel(),
15248
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
15249
+ "div",
15250
+ {
15251
+ id: resolvedId,
15252
+ "aria-labelledby": labelId,
15253
+ "aria-describedby": describedBy,
15254
+ className: cn("rounded-2xl border border-border/60 bg-card/50 backdrop-blur-sm p-3 shadow-sm", disabled && "opacity-50 pointer-events-none"),
15255
+ children: [
15256
+ renderSearch(),
15257
+ renderTreeContent()
15258
+ ]
15259
+ }
15260
+ ),
15261
+ renderAssistiveText()
15262
+ ] });
15214
15263
  }
15215
15264
  const selectedCount = valueArray.length;
15265
+ const triggerSizeStyles = {
15266
+ sm: {
15267
+ button: "h-8 px-3 py-1.5 text-sm md:h-7 md:text-xs",
15268
+ iconWrap: "p-1",
15269
+ icon: "w-3.5 h-3.5 md:w-3 md:h-3",
15270
+ text: "text-xs md:text-[11px]",
15271
+ badge: "px-1.5 py-0.5 text-[10px]",
15272
+ actionIcon: "w-3.5 h-3.5",
15273
+ clearIcon: "h-3 w-3"
15274
+ },
15275
+ md: {
15276
+ button: "h-10 px-3 py-2.5 text-sm",
15277
+ iconWrap: "p-1.5",
15278
+ icon: "w-4 h-4",
15279
+ text: "text-sm",
15280
+ badge: "px-2 py-0.5 text-xs",
15281
+ actionIcon: "w-4 h-4",
15282
+ clearIcon: "h-3.5 w-3.5"
15283
+ },
15284
+ lg: {
15285
+ button: "h-12 px-4 py-3 text-base",
15286
+ iconWrap: "p-1.5",
15287
+ icon: "w-5 h-5",
15288
+ text: "text-base",
15289
+ badge: "px-2.5 py-1 text-sm",
15290
+ actionIcon: "w-5 h-5",
15291
+ clearIcon: "h-4 w-4"
15292
+ }
15293
+ };
15294
+ const triggerVariantStyles = {
15295
+ default: "border border-input bg-background shadow-sm hover:border-primary/50",
15296
+ outline: "border-2 border-input bg-transparent hover:border-primary",
15297
+ ghost: "border border-transparent bg-muted/50 hover:bg-muted",
15298
+ filled: "border border-transparent bg-muted/70 hover:bg-muted"
15299
+ };
15300
+ const clearSelection = () => {
15301
+ if (!props.onChange) return;
15302
+ if (singleSelect) {
15303
+ props.onChange(null);
15304
+ return;
15305
+ }
15306
+ props.onChange([]);
15307
+ };
15308
+ const handleClear = (event) => {
15309
+ event.preventDefault();
15310
+ event.stopPropagation();
15311
+ clearSelection();
15312
+ setIsOpen(false);
15313
+ };
15216
15314
  let displayText;
15217
15315
  if (singleSelect) {
15218
15316
  displayText = selectedCount > 0 ? categories.find((c) => c.id === valueArray[0])?.name || placeholder : placeholder;
@@ -15220,69 +15318,111 @@ function CategoryTreeSelect(props) {
15220
15318
  if (selectedCount === 0) {
15221
15319
  displayText = placeholder;
15222
15320
  } else if (selectedCount <= 3) {
15223
- const selectedNames = valueArray.map((id) => categories.find((c) => c.id === id)?.name).filter(Boolean).join(", ");
15321
+ const selectedNames = valueArray.map((id2) => categories.find((c) => c.id === id2)?.name).filter(Boolean).join(", ");
15224
15322
  displayText = selectedNames || placeholder;
15225
15323
  } else {
15226
15324
  displayText = mergedLabels.selectedText(selectedCount);
15227
15325
  }
15228
15326
  }
15229
- return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: cn("relative", className), children: [
15230
- /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
15231
- "button",
15232
- {
15233
- type: "button",
15234
- onClick: () => !disabled && setIsOpen(!isOpen),
15235
- disabled,
15236
- className: cn(
15237
- // Modern trigger button styling
15238
- "group flex w-full items-center justify-between px-3 py-2.5",
15239
- "bg-background/80 backdrop-blur-sm border border-border/60",
15240
- "rounded-full h-11 text-sm",
15241
- "hover:bg-accent/10 hover:border-primary/40 hover:shadow-lg hover:shadow-primary/5 hover:-translate-y-0.5",
15242
- "transition-all duration-300 ease-out",
15243
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background",
15244
- disabled && "opacity-50 cursor-not-allowed hover:transform-none hover:shadow-none",
15245
- isOpen && "ring-2 ring-primary/30 border-primary/50 shadow-lg shadow-primary/10"
15246
- ),
15247
- children: [
15248
- /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "flex items-center gap-2.5", children: [
15249
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
15250
- "div",
15251
- {
15252
- className: cn(
15253
- "flex items-center justify-center rounded-lg p-1.5 transition-all duration-300",
15254
- isOpen ? "bg-primary/15 text-primary" : "bg-muted/50 text-muted-foreground group-hover:bg-primary/10 group-hover:text-primary"
15255
- ),
15256
- children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_lucide_react25.FolderTree, { className: cn("w-4 h-4 transition-transform duration-300", isOpen && "scale-110") })
15257
- }
15258
- ),
15259
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { className: cn("font-medium transition-colors duration-200", selectedCount === 0 ? "text-muted-foreground" : "text-foreground"), children: displayText }),
15260
- selectedCount > 0 && !singleSelect && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { className: "ml-1 px-2 py-0.5 text-xs font-bold rounded-full bg-primary/15 text-primary", children: selectedCount })
15261
- ] }),
15262
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { className: cn("transition-all duration-300 text-muted-foreground group-hover:text-foreground", isOpen && "rotate-180 text-primary"), children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_lucide_react25.ChevronDown, { className: "w-4 h-4" }) })
15263
- ]
15264
- }
15265
- ),
15266
- isOpen && !disabled && /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(import_jsx_runtime46.Fragment, { children: [
15267
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "fixed inset-0 z-10", onClick: () => setIsOpen(false) }),
15327
+ return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: cn("w-full space-y-2", className), children: [
15328
+ renderLabel(),
15329
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "relative", children: [
15268
15330
  /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
15269
- "div",
15331
+ "button",
15270
15332
  {
15271
- ref: dropdownViewportRef,
15333
+ id: resolvedId,
15334
+ type: "button",
15335
+ onClick: () => !disabled && setIsOpen(!isOpen),
15336
+ disabled,
15337
+ role: "combobox",
15338
+ "aria-haspopup": "tree",
15339
+ "aria-expanded": isOpen,
15340
+ "aria-controls": `${resolvedId}-tree`,
15341
+ "aria-labelledby": labelId,
15342
+ "aria-describedby": describedBy,
15343
+ "aria-invalid": !!error,
15272
15344
  className: cn(
15273
- "absolute z-20 mt-2 w-full max-h-80 overflow-auto",
15274
- "rounded-2xl md:rounded-3xl border border-border/40 bg-popover/95 text-popover-foreground",
15275
- "shadow-2xl backdrop-blur-xl",
15276
- "p-2",
15277
- "animate-in fade-in-0 zoom-in-95 slide-in-from-top-2 duration-300"
15345
+ "group flex w-full items-center justify-between rounded-full transition-all duration-200",
15346
+ "backdrop-blur-sm",
15347
+ triggerSizeStyles[size].button,
15348
+ triggerVariantStyles[variant],
15349
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background",
15350
+ disabled && "opacity-50 cursor-not-allowed hover:transform-none hover:shadow-none",
15351
+ isOpen && "ring-2 ring-primary/30 border-primary/50 shadow-lg shadow-primary/10",
15352
+ error && "border-destructive focus-visible:ring-destructive/30"
15278
15353
  ),
15279
15354
  children: [
15280
- renderSearch(),
15281
- renderTreeContent()
15355
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "flex min-w-0 flex-1 items-center gap-2.5 text-left", children: [
15356
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
15357
+ "div",
15358
+ {
15359
+ className: cn(
15360
+ "shrink-0 flex items-center justify-center rounded-lg transition-all duration-300",
15361
+ triggerSizeStyles[size].iconWrap,
15362
+ isOpen ? "bg-primary/15 text-primary" : "bg-muted/50 text-muted-foreground group-hover:bg-primary/10 group-hover:text-primary"
15363
+ ),
15364
+ children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_lucide_react25.FolderTree, { className: cn(triggerSizeStyles[size].icon, "transition-transform duration-300", isOpen && "scale-110") })
15365
+ }
15366
+ ),
15367
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
15368
+ "span",
15369
+ {
15370
+ className: cn(
15371
+ "min-w-0 flex-1 truncate font-medium transition-colors duration-200",
15372
+ triggerSizeStyles[size].text,
15373
+ selectedCount === 0 ? "text-muted-foreground" : "text-foreground"
15374
+ ),
15375
+ title: displayText,
15376
+ children: displayText
15377
+ }
15378
+ )
15379
+ ] }),
15380
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "ml-2 flex shrink-0 items-center gap-1.5", children: [
15381
+ allowClear && selectedCount > 0 && !disabled && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
15382
+ "div",
15383
+ {
15384
+ role: "button",
15385
+ tabIndex: 0,
15386
+ "aria-label": "Clear selection",
15387
+ onClick: handleClear,
15388
+ onKeyDown: (event) => (event.key === "Enter" || event.key === " ") && handleClear(event),
15389
+ className: cn(
15390
+ "opacity-0 group-hover:opacity-100 transition-all duration-200",
15391
+ "p-1 rounded-lg hover:bg-destructive/10 text-muted-foreground hover:text-destructive",
15392
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/30"
15393
+ ),
15394
+ children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_lucide_react25.X, { className: triggerSizeStyles[size].clearIcon })
15395
+ }
15396
+ ),
15397
+ selectedCount > 0 && !singleSelect && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { className: cn("rounded-full bg-primary/15 font-bold text-primary", triggerSizeStyles[size].badge), children: selectedCount }),
15398
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { className: cn("transition-all duration-300 text-muted-foreground group-hover:text-foreground", isOpen && "rotate-180 text-primary"), children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_lucide_react25.ChevronDown, { className: triggerSizeStyles[size].actionIcon }) })
15399
+ ] })
15282
15400
  ]
15283
15401
  }
15284
- )
15285
- ] })
15402
+ ),
15403
+ isOpen && !disabled && /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(import_jsx_runtime46.Fragment, { children: [
15404
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "fixed inset-0 z-10", onClick: () => setIsOpen(false) }),
15405
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
15406
+ "div",
15407
+ {
15408
+ ref: dropdownViewportRef,
15409
+ id: `${resolvedId}-tree`,
15410
+ className: cn(
15411
+ "absolute z-20 mt-2 w-full max-h-80 overflow-auto",
15412
+ "rounded-2xl md:rounded-3xl border border-border/40 bg-popover/95 text-popover-foreground",
15413
+ "shadow-2xl backdrop-blur-xl",
15414
+ "p-2",
15415
+ "animate-in fade-in-0 zoom-in-95 slide-in-from-top-2 duration-300"
15416
+ ),
15417
+ children: [
15418
+ renderSearch(),
15419
+ renderTreeContent()
15420
+ ]
15421
+ }
15422
+ )
15423
+ ] })
15424
+ ] }),
15425
+ renderAssistiveText()
15286
15426
  ] });
15287
15427
  }
15288
15428