@underverse-ui/underverse 0.1.20 → 0.1.21

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.cjs CHANGED
@@ -3962,7 +3962,11 @@ var Combobox = ({
3962
3962
  const autoId = (0, import_react10.useId)();
3963
3963
  const resolvedId = id ? String(id) : `combobox-${autoId}`;
3964
3964
  const labelId = label ? `${resolvedId}-label` : void 0;
3965
- const filteredOptions = React18.useMemo(() => options.filter((o) => getOptionLabel(o).toLowerCase().includes(query.toLowerCase())), [options, query]);
3965
+ const enableSearch = options.length > 10;
3966
+ const filteredOptions = React18.useMemo(
3967
+ () => enableSearch ? options.filter((o) => getOptionLabel(o).toLowerCase().includes(query.toLowerCase())) : options,
3968
+ [options, query, enableSearch]
3969
+ );
3966
3970
  const [dropdownPosition, setDropdownPosition] = React18.useState(null);
3967
3971
  const triggerRef = React18.useRef(null);
3968
3972
  const calculatePosition = React18.useCallback(() => {
@@ -4029,12 +4033,12 @@ var Combobox = ({
4029
4033
  if (!open) {
4030
4034
  setQuery("");
4031
4035
  setActiveIndex(null);
4032
- } else {
4036
+ } else if (enableSearch) {
4033
4037
  setTimeout(() => {
4034
4038
  inputRef.current?.focus();
4035
4039
  }, 100);
4036
4040
  }
4037
- }, [open]);
4041
+ }, [open, enableSearch]);
4038
4042
  const selectedOption = findOptionByValue(options, value);
4039
4043
  const displayValue = selectedOption ? getOptionLabel(selectedOption) : "";
4040
4044
  const dropdownContent = /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
@@ -4057,7 +4061,7 @@ var Combobox = ({
4057
4061
  "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95"
4058
4062
  ),
4059
4063
  children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: cn("rounded-md border bg-popover text-popover-foreground shadow-md", "backdrop-blur-sm bg-popover/95 border-border/60"), children: [
4060
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "relative p-3 border-b border-border/50 bg-muted/20", children: [
4064
+ enableSearch && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "relative p-3 border-b border-border/50 bg-muted/20", children: [
4061
4065
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react12.Search, { className: "absolute left-6 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground transition-colors" }),
4062
4066
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
4063
4067
  "input",
@@ -4186,11 +4190,12 @@ var Combobox = ({
4186
4190
  setOpen(next);
4187
4191
  },
4188
4192
  className: cn(
4189
- "flex w-full items-center justify-between border border-input bg-background px-3 vanh",
4193
+ "flex w-full items-center justify-between border border-input bg-background px-3",
4190
4194
  radiusClass,
4191
4195
  sizeStyles8[size],
4192
- "ring-offset-background placeholder:text-muted-foreground outline-none focus:outline-none focus-visible:outline-none focus:ring-0 focus-visible:ring-0 focus:ring-offset-0",
4196
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
4193
4197
  "disabled:cursor-not-allowed disabled:opacity-50",
4198
+ "hover:bg-accent/5 transition-colors hover:border-primary/40 focus:border-primary",
4194
4199
  className
4195
4200
  ),
4196
4201
  children: [
@@ -5191,7 +5196,9 @@ var MultiCombobox = ({
5191
5196
  disabled = false,
5192
5197
  size = "md",
5193
5198
  label,
5194
- required
5199
+ title,
5200
+ required,
5201
+ displayFormat = (option) => option.label
5195
5202
  }) => {
5196
5203
  const [query, setQuery] = React22.useState("");
5197
5204
  const [open, setOpen] = React22.useState(false);
@@ -5248,14 +5255,22 @@ var MultiCombobox = ({
5248
5255
  document.removeEventListener("keydown", handleEscape);
5249
5256
  };
5250
5257
  }, [open]);
5251
- const filtered = options.filter((o) => o.toLowerCase().includes(query.toLowerCase()));
5252
- const toggleSelect = (val) => {
5253
- if (disabledOptions.includes(val)) return;
5254
- if (value.includes(val)) {
5255
- onChange(value.filter((v) => v !== val));
5258
+ const normalizedOptions = React22.useMemo(
5259
+ () => options.map((o) => typeof o === "string" ? { value: o, label: o } : { value: o.value, label: o.label }),
5260
+ [options]
5261
+ );
5262
+ const enableSearch = normalizedOptions.length > 10;
5263
+ const filtered = React22.useMemo(
5264
+ () => enableSearch ? normalizedOptions.filter((opt) => opt.label.toLowerCase().includes(query.toLowerCase())) : normalizedOptions,
5265
+ [normalizedOptions, query, enableSearch]
5266
+ );
5267
+ const toggleSelect = (optionValue) => {
5268
+ if (disabledOptions.includes(optionValue)) return;
5269
+ if (value.includes(optionValue)) {
5270
+ onChange(value.filter((v) => v !== optionValue));
5256
5271
  } else {
5257
5272
  if (!maxSelected || value.length < maxSelected) {
5258
- onChange([...value, val]);
5273
+ onChange([...value, optionValue]);
5259
5274
  }
5260
5275
  }
5261
5276
  };
@@ -5267,7 +5282,7 @@ var MultiCombobox = ({
5267
5282
  if (e.key === "Enter") {
5268
5283
  e.preventDefault();
5269
5284
  if (activeIndex !== null && filtered[activeIndex]) {
5270
- toggleSelect(filtered[activeIndex]);
5285
+ toggleSelect(filtered[activeIndex].value);
5271
5286
  }
5272
5287
  }
5273
5288
  };
@@ -5275,12 +5290,12 @@ var MultiCombobox = ({
5275
5290
  onChange([]);
5276
5291
  };
5277
5292
  React22.useEffect(() => {
5278
- if (open) {
5293
+ if (open && enableSearch) {
5279
5294
  setTimeout(() => {
5280
5295
  inputRef.current?.focus();
5281
5296
  }, 100);
5282
5297
  }
5283
- }, [open]);
5298
+ }, [open, enableSearch]);
5284
5299
  const sizeStyles8 = {
5285
5300
  sm: {
5286
5301
  trigger: "h-8 px-3 py-1.5 text-sm md:h-7 md:text-xs",
@@ -5309,6 +5324,20 @@ var MultiCombobox = ({
5309
5324
  const labelId = label ? `${resolvedId}-label` : void 0;
5310
5325
  const labelSize = size === "sm" ? "text-xs" : size === "lg" ? "text-base" : "text-sm";
5311
5326
  return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: cn("w-full space-y-2 group", className), children: [
5327
+ title && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
5328
+ "label",
5329
+ {
5330
+ className: cn(
5331
+ size === "sm" ? "text-xs" : size === "lg" ? "text-base" : "text-sm",
5332
+ "font-medium transition-colors duration-200",
5333
+ disabled ? "text-muted-foreground" : "text-foreground group-focus-within:text-primary"
5334
+ ),
5335
+ children: [
5336
+ title,
5337
+ required && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "text-destructive ml-1", children: "*" })
5338
+ ]
5339
+ }
5340
+ ) }),
5312
5341
  label && /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
5313
5342
  "label",
5314
5343
  {
@@ -5325,148 +5354,152 @@ var MultiCombobox = ({
5325
5354
  ]
5326
5355
  }
5327
5356
  ),
5328
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "relative w-full", children: [
5329
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex flex-wrap gap-1", children: [
5330
- showTags && value.map((item) => /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { className: cn("flex items-center gap-1 rounded bg-accent text-accent-foreground", sizeStyles8[size].tag), children: [
5331
- item,
5332
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
5333
- "button",
5334
- {
5335
- type: "button",
5336
- onClick: (e) => {
5337
- e.preventDefault();
5338
- e.stopPropagation();
5339
- handleRemove(item);
5340
- },
5341
- className: "text-xs hover:text-destructive",
5342
- children: "\xD7"
5343
- }
5344
- )
5345
- ] }, item)),
5346
- showClear && value.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
5347
- "button",
5348
- {
5349
- type: "button",
5350
- onClick: (e) => {
5351
- e.preventDefault();
5352
- e.stopPropagation();
5353
- handleClearAll();
5354
- },
5355
- className: "ml-auto text-xs text-muted-foreground hover:underline",
5356
- children: "Clear all"
5357
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "relative w-full" }),
5358
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
5359
+ "button",
5360
+ {
5361
+ ref: triggerRef,
5362
+ type: "button",
5363
+ disabled,
5364
+ id: resolvedId,
5365
+ "aria-labelledby": labelId,
5366
+ onClick: () => {
5367
+ const next = !open;
5368
+ if (next) {
5369
+ const pos = calculatePosition();
5370
+ if (pos) setDropdownPosition(pos);
5357
5371
  }
5358
- )
5359
- ] }),
5360
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
5361
- "button",
5372
+ setOpen(next);
5373
+ },
5374
+ className: cn(
5375
+ "flex w-full items-center gap-2 rounded-lg border border-input bg-background shadow-sm min-h-[2.5rem]",
5376
+ "px-3 py-2",
5377
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
5378
+ "disabled:cursor-not-allowed disabled:opacity-50"
5379
+ ),
5380
+ children: [
5381
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "flex items-center gap-1 flex-wrap min-h-[1.5rem] flex-1", children: value.length > 0 ? showTags ? value.map((itemValue) => {
5382
+ const option = normalizedOptions.find((o) => o.value === itemValue);
5383
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { className: "inline-flex items-center gap-1 bg-accent text-accent-foreground rounded px-2 py-1 text-xs", children: [
5384
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "truncate max-w-[120px]", children: option ? displayFormat(option) : itemValue }),
5385
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
5386
+ "button",
5387
+ {
5388
+ type: "button",
5389
+ onClick: (e) => {
5390
+ e.preventDefault();
5391
+ e.stopPropagation();
5392
+ handleRemove(itemValue);
5393
+ },
5394
+ className: "hover:text-destructive transition-colors cursor-pointer",
5395
+ children: "\xD7"
5396
+ }
5397
+ )
5398
+ ] }, itemValue);
5399
+ }) : /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { className: "truncate text-sm", children: [
5400
+ value.length,
5401
+ " selected"
5402
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "text-muted-foreground", children: placeholder || "Select..." }) }),
5403
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_lucide_react15.ChevronDown, { className: cn("opacity-50 transition-transform", sizeStyles8[size].icon, open && "rotate-180") })
5404
+ ]
5405
+ }
5406
+ ),
5407
+ open && dropdownPosition && typeof window !== "undefined" ? (0, import_react_dom8.createPortal)(
5408
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
5409
+ "div",
5362
5410
  {
5363
- ref: triggerRef,
5364
- type: "button",
5365
- disabled,
5366
- id: resolvedId,
5367
- "aria-labelledby": labelId,
5368
- onClick: () => {
5369
- const next = !open;
5370
- if (next) {
5371
- const pos = calculatePosition();
5372
- if (pos) setDropdownPosition(pos);
5373
- }
5374
- setOpen(next);
5411
+ "data-dropdown": "multicombobox",
5412
+ style: {
5413
+ position: "absolute",
5414
+ top: dropdownPosition?.top || 0,
5415
+ left: dropdownPosition?.left || 0,
5416
+ width: dropdownPosition?.width || 200,
5417
+ zIndex: 9999
5375
5418
  },
5419
+ "data-state": open ? "open" : "closed",
5376
5420
  className: cn(
5377
- "flex w-full items-center justify-between rounded-lg border border-input bg-background shadow-sm",
5378
- sizeStyles8[size].trigger,
5379
- "outline-none focus:outline-none focus-visible:outline-none focus:ring-0 focus-visible:ring-0 focus:ring-offset-0",
5380
- "disabled:cursor-not-allowed disabled:opacity-50"
5421
+ "z-[9999]",
5422
+ "data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
5423
+ "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95"
5381
5424
  ),
5382
- children: [
5383
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "truncate", children: value.length ? `${value.length} selected` : placeholder || "Select..." }),
5384
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_lucide_react15.ChevronDown, { className: cn("opacity-50 transition-transform", sizeStyles8[size].icon, open && "rotate-180") })
5385
- ]
5425
+ children: /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
5426
+ "div",
5427
+ {
5428
+ className: cn(
5429
+ "rounded-md border bg-popover text-popover-foreground shadow-md",
5430
+ "backdrop-blur-sm bg-popover/95 border-border/60"
5431
+ ),
5432
+ children: [
5433
+ showClear && value.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "px-3 py-2 border-b border-border/60 flex justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
5434
+ "button",
5435
+ {
5436
+ type: "button",
5437
+ onClick: (e) => {
5438
+ e.preventDefault();
5439
+ e.stopPropagation();
5440
+ handleClearAll();
5441
+ },
5442
+ className: "text-xs text-muted-foreground hover:underline cursor-pointer",
5443
+ children: "Clear all"
5444
+ }
5445
+ ) }),
5446
+ enableSearch && /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "relative border-b border-border/60", children: [
5447
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_lucide_react15.Search, { className: cn("absolute left-2 top-2.5 text-muted-foreground", sizeStyles8[size].icon) }),
5448
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
5449
+ "input",
5450
+ {
5451
+ ref: inputRef,
5452
+ value: query,
5453
+ onChange: (e) => {
5454
+ setQuery(e.target.value);
5455
+ setActiveIndex(null);
5456
+ },
5457
+ onKeyDown: handleKeyDown,
5458
+ placeholder,
5459
+ className: cn("w-full rounded-t-md bg-transparent focus:outline-none cursor-text", sizeStyles8[size].search)
5460
+ }
5461
+ )
5462
+ ] }),
5463
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("ul", { className: cn("max-h-60 overflow-y-auto p-1", size === "lg" ? "text-base" : size === "sm" ? "text-xs" : "text-sm"), children: filtered.length ? filtered.map((item, index) => {
5464
+ const isSelected = value.includes(item.value);
5465
+ const isDisabled = disabledOptions.includes(item.value);
5466
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
5467
+ "li",
5468
+ {
5469
+ ref: (node) => {
5470
+ listRef.current[index] = node;
5471
+ },
5472
+ onClick: (e) => {
5473
+ e.preventDefault();
5474
+ e.stopPropagation();
5475
+ toggleSelect(item.value);
5476
+ inputRef.current?.focus();
5477
+ },
5478
+ style: {
5479
+ animationDelay: open ? `${index * 25}ms` : "0ms"
5480
+ },
5481
+ className: cn(
5482
+ "dropdown-item flex cursor-pointer items-center justify-between rounded-sm transition-colors",
5483
+ sizeStyles8[size].item,
5484
+ "hover:bg-accent hover:text-accent-foreground",
5485
+ index === activeIndex && "bg-accent text-accent-foreground",
5486
+ isDisabled && "opacity-50 cursor-not-allowed pointer-events-none"
5487
+ ),
5488
+ children: [
5489
+ item.label,
5490
+ isSelected && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_lucide_react15.Check, { className: sizeStyles8[size].icon })
5491
+ ]
5492
+ },
5493
+ item.value
5494
+ );
5495
+ }) : /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("li", { className: cn("px-3 py-2 text-muted-foreground", size === "lg" ? "text-base" : size === "sm" ? "text-xs" : "text-sm"), children: "No result." }) })
5496
+ ]
5497
+ }
5498
+ )
5386
5499
  }
5387
5500
  ),
5388
- open && dropdownPosition && typeof window !== "undefined" && (0, import_react_dom8.createPortal)(
5389
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
5390
- "div",
5391
- {
5392
- "data-dropdown": "multicombobox",
5393
- style: {
5394
- position: "absolute",
5395
- top: dropdownPosition?.top || 0,
5396
- left: dropdownPosition?.left || 0,
5397
- width: dropdownPosition?.width || 200,
5398
- zIndex: 9999
5399
- },
5400
- "data-state": open ? "open" : "closed",
5401
- className: cn(
5402
- "z-[9999]",
5403
- "data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
5404
- "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95"
5405
- ),
5406
- children: /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
5407
- "div",
5408
- {
5409
- className: cn(
5410
- "rounded-md border bg-popover text-popover-foreground shadow-md",
5411
- "backdrop-blur-sm bg-popover/95 border-border/60"
5412
- ),
5413
- children: [
5414
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "relative border-b border-border/60", children: [
5415
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_lucide_react15.Search, { className: cn("absolute left-2 top-2.5 text-muted-foreground", sizeStyles8[size].icon) }),
5416
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
5417
- "input",
5418
- {
5419
- ref: inputRef,
5420
- value: query,
5421
- onChange: (e) => {
5422
- setQuery(e.target.value);
5423
- setActiveIndex(null);
5424
- },
5425
- onKeyDown: handleKeyDown,
5426
- placeholder,
5427
- className: cn("w-full rounded-t-md bg-transparent focus:outline-none", sizeStyles8[size].search)
5428
- }
5429
- )
5430
- ] }),
5431
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("ul", { className: cn("max-h-60 overflow-y-auto p-1", size === "lg" ? "text-base" : size === "sm" ? "text-xs" : "text-sm"), children: filtered.length ? filtered.map((item, index) => {
5432
- const isSelected = value.includes(item);
5433
- const isDisabled = disabledOptions.includes(item);
5434
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
5435
- "li",
5436
- {
5437
- ref: (node) => {
5438
- listRef.current[index] = node;
5439
- },
5440
- onClick: () => {
5441
- toggleSelect(item);
5442
- inputRef.current?.focus();
5443
- },
5444
- style: {
5445
- animationDelay: open ? `${index * 25}ms` : "0ms"
5446
- },
5447
- className: cn(
5448
- "dropdown-item flex cursor-pointer items-center justify-between rounded-sm transition-colors",
5449
- sizeStyles8[size].item,
5450
- "hover:bg-accent hover:text-accent-foreground",
5451
- index === activeIndex && "bg-accent text-accent-foreground",
5452
- isDisabled && "opacity-50 cursor-not-allowed pointer-events-none"
5453
- ),
5454
- children: [
5455
- item,
5456
- isSelected && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_lucide_react15.Check, { className: sizeStyles8[size].icon })
5457
- ]
5458
- },
5459
- item
5460
- );
5461
- }) : /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("li", { className: cn("px-3 py-2 text-muted-foreground", size === "lg" ? "text-base" : size === "sm" ? "text-xs" : "text-sm"), children: "No result." }) })
5462
- ]
5463
- }
5464
- )
5465
- }
5466
- ),
5467
- document.body
5468
- )
5469
- ] })
5501
+ document.body
5502
+ ) : null
5470
5503
  ] });
5471
5504
  };
5472
5505