@particle-academy/react-fancy 2.3.4 → 2.4.0

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
@@ -3292,10 +3292,14 @@ var NativeSelect = react.forwardRef(
3292
3292
  suffixPosition,
3293
3293
  onValueChange,
3294
3294
  onChange,
3295
+ value,
3296
+ defaultValue,
3295
3297
  ...props
3296
3298
  }, ref) => {
3297
3299
  const autoId = react.useId();
3298
3300
  const selectId = id ?? autoId;
3301
+ const isControlled = value !== void 0;
3302
+ const resolvedDefault = !isControlled && defaultValue === void 0 && placeholder ? "" : defaultValue;
3299
3303
  const select = /* @__PURE__ */ jsxRuntime.jsx(
3300
3304
  InputWrapper,
3301
3305
  {
@@ -3327,6 +3331,7 @@ var NativeSelect = react.forwardRef(
3327
3331
  onValueChange?.(e.target.value);
3328
3332
  },
3329
3333
  ...props,
3334
+ ...isControlled ? { value } : { defaultValue: resolvedDefault },
3330
3335
  children: [
3331
3336
  placeholder && /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", disabled: true, children: placeholder }),
3332
3337
  list.map(
@@ -3376,6 +3381,9 @@ var ListboxSelect = react.forwardRef(
3376
3381
  onValueChange,
3377
3382
  onValuesChange,
3378
3383
  searchable = false,
3384
+ creatable = false,
3385
+ onCreate,
3386
+ createLabel = "Create",
3379
3387
  selectedSuffix = "selected",
3380
3388
  indicator = "check",
3381
3389
  value: controlledSingleValue,
@@ -3383,9 +3391,11 @@ var ListboxSelect = react.forwardRef(
3383
3391
  }, _ref) => {
3384
3392
  const autoId = react.useId();
3385
3393
  const selectId = id ?? autoId;
3394
+ const textInputEnabled = searchable || creatable;
3386
3395
  const [open, setOpen] = react.useState(false);
3387
3396
  const [search2, setSearch] = react.useState("");
3388
3397
  const [activeIndex, setActiveIndex] = react.useState(-1);
3398
+ const [createdOptions, setCreatedOptions] = react.useState([]);
3389
3399
  const [singleValue, setSingleValue] = react.useState(
3390
3400
  controlledSingleValue ?? defaultSingleValue ?? ""
3391
3401
  );
@@ -3402,7 +3412,6 @@ var ListboxSelect = react.forwardRef(
3402
3412
  }, [controlledSingleValue]);
3403
3413
  const anchorRef = react.useRef(null);
3404
3414
  const listRef = react.useRef(null);
3405
- const wrapperRef = react.useRef(null);
3406
3415
  const searchRef = react.useRef(null);
3407
3416
  const position = useFloatingPosition(anchorRef, listRef, {
3408
3417
  placement: "bottom-start",
@@ -3414,18 +3423,24 @@ var ListboxSelect = react.forwardRef(
3414
3423
  setSearch("");
3415
3424
  setActiveIndex(-1);
3416
3425
  }, []);
3417
- useOutsideClick(wrapperRef, close, open);
3426
+ useOutsideClick(listRef, close, open, anchorRef);
3418
3427
  useEscapeKey(close, open);
3419
3428
  react.useEffect(() => {
3420
- if (open && searchable) {
3429
+ if (open && textInputEnabled) {
3421
3430
  requestAnimationFrame(() => searchRef.current?.focus());
3422
3431
  }
3423
- }, [open, searchable]);
3424
- const allOptions = flattenOptions(list);
3425
- const resolvedOptions = allOptions.map(resolveOption);
3432
+ }, [open, textInputEnabled]);
3433
+ const resolvedOptions = react.useMemo(() => {
3434
+ const base = flattenOptions(list).map(resolveOption);
3435
+ const created = createdOptions.map(resolveOption);
3436
+ return [...base, ...created];
3437
+ }, [list, createdOptions]);
3426
3438
  const filtered = search2 ? resolvedOptions.filter(
3427
3439
  (o) => o.label.toLowerCase().includes(search2.toLowerCase())
3428
3440
  ) : resolvedOptions;
3441
+ const canCreate = creatable && search2.trim().length > 0 && !resolvedOptions.some(
3442
+ (o) => o.label.toLowerCase() === search2.trim().toLowerCase() || o.value === search2.trim()
3443
+ );
3429
3444
  const isSelected = (value) => {
3430
3445
  if (multiple) return currentMulti.includes(value);
3431
3446
  return currentSingle === value;
@@ -3444,6 +3459,25 @@ var ListboxSelect = react.forwardRef(
3444
3459
  },
3445
3460
  [multiple, currentMulti, onValuesChange, onValueChange, close]
3446
3461
  );
3462
+ const handleCreate = react.useCallback(() => {
3463
+ const label2 = search2.trim();
3464
+ if (!label2) return;
3465
+ const value = label2;
3466
+ const newOption = { value, label: label2 };
3467
+ setCreatedOptions((prev) => [...prev, newOption]);
3468
+ onCreate?.(label2);
3469
+ setSearch("");
3470
+ if (multiple) {
3471
+ const next = [...currentMulti, value];
3472
+ setMultiValues(next);
3473
+ onValuesChange?.(next);
3474
+ requestAnimationFrame(() => searchRef.current?.focus());
3475
+ } else {
3476
+ setSingleValue(value);
3477
+ onValueChange?.(value);
3478
+ close();
3479
+ }
3480
+ }, [search2, multiple, currentMulti, onCreate, onValuesChange, onValueChange, close]);
3447
3481
  const getDisplayText = () => {
3448
3482
  if (multiple) {
3449
3483
  if (currentMulti.length === 0) return placeholder;
@@ -3472,10 +3506,15 @@ var ListboxSelect = react.forwardRef(
3472
3506
  } else if (e.key === "ArrowUp") {
3473
3507
  e.preventDefault();
3474
3508
  setActiveIndex((i) => Math.max(i - 1, 0));
3475
- } else if (e.key === "Enter" && activeIndex >= 0) {
3476
- e.preventDefault();
3477
- const item = filtered[activeIndex];
3478
- if (item && !item.disabled) toggleOption(item.value);
3509
+ } else if (e.key === "Enter") {
3510
+ if (activeIndex >= 0) {
3511
+ e.preventDefault();
3512
+ const item = filtered[activeIndex];
3513
+ if (item && !item.disabled) toggleOption(item.value);
3514
+ } else if (canCreate) {
3515
+ e.preventDefault();
3516
+ handleCreate();
3517
+ }
3479
3518
  }
3480
3519
  };
3481
3520
  const trigger = /* @__PURE__ */ jsxRuntime.jsxs(
@@ -3535,7 +3574,7 @@ var ListboxSelect = react.forwardRef(
3535
3574
  width: anchorRef.current?.offsetWidth
3536
3575
  },
3537
3576
  children: [
3538
- searchable && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 pb-1", children: /* @__PURE__ */ jsxRuntime.jsx(
3577
+ textInputEnabled && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 pb-1 pt-1", children: /* @__PURE__ */ jsxRuntime.jsx(
3539
3578
  "input",
3540
3579
  {
3541
3580
  ref: searchRef,
@@ -3546,11 +3585,28 @@ var ListboxSelect = react.forwardRef(
3546
3585
  setActiveIndex(-1);
3547
3586
  },
3548
3587
  onKeyDown: handleKeyDown,
3549
- placeholder: "Search...",
3588
+ placeholder: creatable && !searchable ? "Type to add\u2026" : "Search\u2026",
3550
3589
  className: "w-full rounded-md border-0 bg-zinc-100 px-2.5 py-1.5 text-sm text-zinc-900 placeholder:text-zinc-400 outline-none dark:bg-zinc-800 dark:text-zinc-100 dark:placeholder:text-zinc-500"
3551
3590
  }
3552
3591
  ) }),
3553
- filtered.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-2 text-sm text-zinc-400", children: "No results found" }) : filtered.map((option, i) => {
3592
+ canCreate && /* @__PURE__ */ jsxRuntime.jsxs(
3593
+ "button",
3594
+ {
3595
+ type: "button",
3596
+ onClick: handleCreate,
3597
+ className: "flex w-full items-center gap-2 rounded-lg px-3 py-2 text-left text-sm text-blue-600 hover:bg-blue-50 dark:text-blue-400 dark:hover:bg-blue-500/10",
3598
+ children: [
3599
+ /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "h-4 w-4 shrink-0", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M10 3a.75.75 0 01.75.75v5.5h5.5a.75.75 0 010 1.5h-5.5v5.5a.75.75 0 01-1.5 0v-5.5h-5.5a.75.75 0 010-1.5h5.5v-5.5A.75.75 0 0110 3z", clipRule: "evenodd" }) }),
3600
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "truncate", children: [
3601
+ createLabel,
3602
+ ' "',
3603
+ search2.trim(),
3604
+ '"'
3605
+ ] })
3606
+ ]
3607
+ }
3608
+ ),
3609
+ filtered.length === 0 && !canCreate ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-2 text-sm text-zinc-400", children: "No results found" }) : filtered.length === 0 ? null : filtered.map((option, i) => {
3554
3610
  const selected = isSelected(option.value);
3555
3611
  return /* @__PURE__ */ jsxRuntime.jsxs(
3556
3612
  "button",
@@ -3589,7 +3645,7 @@ var ListboxSelect = react.forwardRef(
3589
3645
  ]
3590
3646
  }
3591
3647
  ) });
3592
- const content = /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: wrapperRef, className: "relative", children: [
3648
+ const content = /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
3593
3649
  trigger,
3594
3650
  dropdown
3595
3651
  ] });