@facter/ds-core 1.33.9 → 1.33.11

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.mjs CHANGED
@@ -17,10 +17,11 @@ import { toast as toast$1, Toaster as Toaster$1 } from 'sonner';
17
17
  import * as SwitchPrimitives from '@radix-ui/react-switch';
18
18
  import { FormProvider, useFormContext, Controller } from 'react-hook-form';
19
19
  export { FormProvider, useFormContext } from 'react-hook-form';
20
- import * as PopoverPrimitive from '@radix-ui/react-popover';
20
+ import { createPortal } from 'react-dom';
21
21
  import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
22
22
  import * as AvatarPrimitive from '@radix-ui/react-avatar';
23
23
  import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
24
+ import * as PopoverPrimitive from '@radix-ui/react-popover';
24
25
  import * as TooltipPrimitive from '@radix-ui/react-tooltip';
25
26
  import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
26
27
  import * as SeparatorPrimitive from '@radix-ui/react-separator';
@@ -3379,22 +3380,6 @@ function FormInput({
3379
3380
  ) });
3380
3381
  }
3381
3382
  FormInput.displayName = "Form.Input";
3382
- var Popover = PopoverPrimitive.Root;
3383
- var PopoverTrigger = PopoverPrimitive.Trigger;
3384
- var PopoverContent = React10.forwardRef(({ className, align = "center", sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx(
3385
- PopoverPrimitive.Content,
3386
- {
3387
- ref,
3388
- align,
3389
- sideOffset,
3390
- className: cn(
3391
- "z-50 w-auto rounded-md border bg-popover p-4 text-popover-foreground shadow-lg outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
3392
- className
3393
- ),
3394
- ...props
3395
- }
3396
- ) }));
3397
- PopoverContent.displayName = PopoverPrimitive.Content.displayName;
3398
3383
  function FormSelect({
3399
3384
  name,
3400
3385
  label,
@@ -3463,7 +3448,13 @@ function FormSelect({
3463
3448
  {
3464
3449
  value: option.value,
3465
3450
  disabled: option.disabled,
3466
- children: option.label
3451
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
3452
+ option.icon && /* @__PURE__ */ jsx(option.icon, { className: "h-4 w-4 text-muted-foreground" }),
3453
+ /* @__PURE__ */ jsxs("div", { children: [
3454
+ /* @__PURE__ */ jsx("span", { children: option.label }),
3455
+ option.description && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground ml-2", children: option.description })
3456
+ ] })
3457
+ ] })
3467
3458
  },
3468
3459
  option.value
3469
3460
  ))
@@ -3496,8 +3487,11 @@ function CardSelect({
3496
3487
  const [open, setOpen] = React10.useState(false);
3497
3488
  const [search, setSearch] = React10.useState("");
3498
3489
  const selected = options.find((o) => o.value === value);
3490
+ const containerRef = React10.useRef(null);
3491
+ const dropdownRef = React10.useRef(null);
3499
3492
  const listRef = React10.useRef(null);
3500
3493
  const searchRef = React10.useRef(null);
3494
+ const [dropdownPos, setDropdownPos] = React10.useState({});
3501
3495
  const filteredOptions = React10.useMemo(() => {
3502
3496
  if (!searchable || onSearch || !search) return options;
3503
3497
  const q = search.toLowerCase();
@@ -3512,25 +3506,72 @@ function CardSelect({
3512
3506
  },
3513
3507
  [onSearch]
3514
3508
  );
3515
- const handleOpenChange = React10.useCallback(
3516
- (nextOpen) => {
3517
- setOpen(nextOpen);
3518
- if (!nextOpen) {
3519
- setSearch("");
3520
- if (onSearch) onSearch("");
3509
+ React10.useEffect(() => {
3510
+ if (!open || !containerRef.current) return;
3511
+ const updatePosition = () => {
3512
+ const rect = containerRef.current.getBoundingClientRect();
3513
+ const spaceBelow = window.innerHeight - rect.bottom;
3514
+ const estimatedHeight = 340;
3515
+ const showAbove = spaceBelow < estimatedHeight && rect.top > spaceBelow;
3516
+ setDropdownPos({
3517
+ position: "fixed",
3518
+ left: rect.left,
3519
+ width: rect.width,
3520
+ zIndex: 9999,
3521
+ ...showAbove ? { bottom: window.innerHeight - rect.top + 4 } : { top: rect.bottom + 4 }
3522
+ });
3523
+ };
3524
+ updatePosition();
3525
+ window.addEventListener("scroll", updatePosition, true);
3526
+ window.addEventListener("resize", updatePosition);
3527
+ return () => {
3528
+ window.removeEventListener("scroll", updatePosition, true);
3529
+ window.removeEventListener("resize", updatePosition);
3530
+ };
3531
+ }, [open]);
3532
+ React10.useEffect(() => {
3533
+ if (!open) return;
3534
+ const handleClickOutside = (e) => {
3535
+ const target = e.target;
3536
+ if (containerRef.current && !containerRef.current.contains(target) && (!dropdownRef.current || !dropdownRef.current.contains(target))) {
3537
+ setOpen(false);
3521
3538
  }
3522
- },
3523
- [onSearch]
3524
- );
3539
+ };
3540
+ document.addEventListener("mousedown", handleClickOutside);
3541
+ return () => document.removeEventListener("mousedown", handleClickOutside);
3542
+ }, [open]);
3543
+ React10.useEffect(() => {
3544
+ if (!open) return;
3545
+ const handleEscape = (e) => {
3546
+ if (e.key === "Escape") setOpen(false);
3547
+ };
3548
+ document.addEventListener("keydown", handleEscape);
3549
+ return () => document.removeEventListener("keydown", handleEscape);
3550
+ }, [open]);
3551
+ React10.useEffect(() => {
3552
+ if (!open) {
3553
+ setSearch("");
3554
+ if (onSearch) onSearch("");
3555
+ }
3556
+ }, [open, onSearch]);
3557
+ React10.useEffect(() => {
3558
+ if (open && searchable) {
3559
+ setTimeout(() => searchRef.current?.focus(), 0);
3560
+ }
3561
+ }, [open, searchable]);
3525
3562
  React10.useEffect(() => {
3526
3563
  const el = listRef.current;
3527
3564
  if (!el || !open) return;
3528
3565
  const handleWheel = (e) => {
3529
3566
  const { scrollTop, scrollHeight, clientHeight } = el;
3530
- if (scrollHeight <= clientHeight) return;
3567
+ const isScrollable = scrollHeight > clientHeight;
3568
+ if (!isScrollable) return;
3531
3569
  e.preventDefault();
3532
3570
  e.stopPropagation();
3533
- el.scrollTop = Math.max(0, Math.min(scrollTop + e.deltaY, scrollHeight - clientHeight));
3571
+ el.scrollTop = Math.max(
3572
+ 0,
3573
+ Math.min(scrollTop + e.deltaY, scrollHeight - clientHeight)
3574
+ );
3534
3575
  };
3535
3576
  el.addEventListener("wheel", handleWheel, { passive: false, capture: true });
3536
3577
  return () => el.removeEventListener("wheel", handleWheel, { capture: true });
@@ -3544,116 +3585,110 @@ function CardSelect({
3544
3585
  onLoadMore();
3545
3586
  }
3546
3587
  }, [onLoadMore, hasMore, loading]);
3547
- return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: handleOpenChange, children: [
3548
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
3549
- /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
3550
- "button",
3551
- {
3552
- type: "button",
3553
- disabled,
3554
- className: cn(
3555
- "flex w-full items-center justify-between rounded-md border-2 bg-background px-3 text-sm transition-colors",
3556
- "focus:outline-none focus:border-primary",
3557
- "disabled:cursor-not-allowed disabled:opacity-50",
3558
- label ? "h-12 pt-4 pb-2" : "h-9 py-2",
3559
- error ? "border-red-500" : "border-border",
3560
- open && !error && "border-primary"
3561
- ),
3562
- children: [
3563
- selected ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 truncate", children: [
3564
- selected.icon && /* @__PURE__ */ jsx(selected.icon, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
3565
- /* @__PURE__ */ jsx("span", { className: "truncate", children: selected.label })
3566
- ] }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: placeholder }),
3567
- /* @__PURE__ */ jsx(ChevronDown, { className: cn("h-4 w-4 shrink-0 opacity-50 transition-transform", open && "rotate-180") })
3568
- ]
3569
- }
3570
- ) }),
3571
- label && /* @__PURE__ */ jsxs(
3572
- "label",
3573
- {
3574
- className: cn(
3575
- "absolute left-3 top-[-6px] text-xs font-medium bg-background px-1 pointer-events-none",
3576
- error ? "text-red-500" : "text-foreground"
3577
- ),
3578
- children: [
3579
- label,
3580
- required && /* @__PURE__ */ jsx("span", { className: "text-red-500 ml-0.5", children: "*" })
3581
- ]
3582
- }
3583
- )
3584
- ] }),
3588
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "relative", children: [
3585
3589
  /* @__PURE__ */ jsxs(
3586
- PopoverContent,
3590
+ "button",
3587
3591
  {
3588
- align: "start",
3589
- sideOffset: 4,
3590
- className: "p-0 overflow-hidden",
3591
- style: { width: "var(--radix-popover-trigger-width)" },
3592
- onOpenAutoFocus: (e) => {
3593
- e.preventDefault();
3594
- if (searchable) {
3595
- searchRef.current?.focus();
3596
- }
3597
- },
3592
+ type: "button",
3593
+ disabled,
3594
+ onClick: () => setOpen((prev) => !prev),
3595
+ className: cn(
3596
+ "flex w-full items-center justify-between rounded-md border-2 bg-background px-3 text-sm transition-colors",
3597
+ "focus:outline-none focus:border-primary",
3598
+ "disabled:cursor-not-allowed disabled:opacity-50",
3599
+ label ? "h-12 pt-4 pb-2" : "h-9 py-2",
3600
+ error ? "border-red-500" : "border-border",
3601
+ open && !error && "border-primary"
3602
+ ),
3603
+ children: [
3604
+ selected ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 truncate", children: [
3605
+ selected.icon && /* @__PURE__ */ jsx(selected.icon, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
3606
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: selected.label })
3607
+ ] }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: placeholder }),
3608
+ /* @__PURE__ */ jsx(ChevronDown, { className: cn("h-4 w-4 shrink-0 opacity-50 transition-transform", open && "rotate-180") })
3609
+ ]
3610
+ }
3611
+ ),
3612
+ label && /* @__PURE__ */ jsxs(
3613
+ "label",
3614
+ {
3615
+ className: cn(
3616
+ "absolute left-3 top-[-6px] text-xs font-medium bg-background px-1 pointer-events-none",
3617
+ error ? "text-red-500" : "text-foreground"
3618
+ ),
3598
3619
  children: [
3599
- searchable && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2 border-b border-border", children: [
3600
- /* @__PURE__ */ jsx(Search, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
3620
+ label,
3621
+ required && /* @__PURE__ */ jsx("span", { className: "text-red-500 ml-0.5", children: "*" })
3622
+ ]
3623
+ }
3624
+ ),
3625
+ open && createPortal(
3626
+ /* @__PURE__ */ jsxs(
3627
+ "div",
3628
+ {
3629
+ ref: dropdownRef,
3630
+ style: dropdownPos,
3631
+ className: "rounded-md border border-border bg-popover shadow-md overflow-hidden animate-in fade-in-0 zoom-in-95 slide-in-from-top-2 duration-150",
3632
+ children: [
3633
+ searchable && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2 border-b border-border", children: [
3634
+ /* @__PURE__ */ jsx(Search, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
3635
+ /* @__PURE__ */ jsx(
3636
+ "input",
3637
+ {
3638
+ ref: searchRef,
3639
+ type: "text",
3640
+ value: search,
3641
+ onChange: (e) => handleSearch(e.target.value),
3642
+ placeholder: searchPlaceholder,
3643
+ className: "flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
3644
+ }
3645
+ )
3646
+ ] }),
3601
3647
  /* @__PURE__ */ jsx(
3602
- "input",
3648
+ "div",
3603
3649
  {
3604
- ref: searchRef,
3605
- type: "text",
3606
- value: search,
3607
- onChange: (e) => handleSearch(e.target.value),
3608
- placeholder: searchPlaceholder,
3609
- className: "flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
3650
+ ref: listRef,
3651
+ className: "overflow-y-auto overscroll-contain max-h-[300px]",
3652
+ onScroll: handleScroll,
3653
+ children: /* @__PURE__ */ jsxs("div", { className: "divide-y divide-border/50 p-2", children: [
3654
+ filteredOptions.length === 0 && !loading ? /* @__PURE__ */ jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: emptyText }) : filteredOptions.map((option) => {
3655
+ const isSelected = value === option.value;
3656
+ const isDisabled = option.disabled || disabled;
3657
+ return /* @__PURE__ */ jsxs(
3658
+ "button",
3659
+ {
3660
+ type: "button",
3661
+ disabled: isDisabled,
3662
+ onClick: () => {
3663
+ onChange(option.value);
3664
+ setOpen(false);
3665
+ },
3666
+ className: cn(
3667
+ "flex w-full items-center gap-3 p-3 text-left transition-all",
3668
+ "cursor-pointer hover:bg-accent",
3669
+ isSelected && "bg-primary/5",
3670
+ isDisabled && "cursor-not-allowed opacity-50 hover:bg-transparent"
3671
+ ),
3672
+ children: [
3673
+ option.icon && /* @__PURE__ */ jsx("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-primary/10", children: /* @__PURE__ */ jsx(option.icon, { className: "h-3.5 w-3.5 text-primary" }) }),
3674
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
3675
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium leading-tight", children: option.label }),
3676
+ option.description && /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs leading-tight text-muted-foreground", children: option.description })
3677
+ ] }),
3678
+ isSelected && /* @__PURE__ */ jsx("span", { className: "flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground", children: /* @__PURE__ */ jsx(Check, { className: "h-3 w-3" }) })
3679
+ ]
3680
+ },
3681
+ option.value
3682
+ );
3683
+ }),
3684
+ loading && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-3", children: /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" }) })
3685
+ ] })
3610
3686
  }
3611
3687
  )
3612
- ] }),
3613
- /* @__PURE__ */ jsx(
3614
- "div",
3615
- {
3616
- ref: listRef,
3617
- className: "overflow-y-auto overscroll-contain",
3618
- style: { maxHeight: "min(300px, var(--radix-popover-content-available-height, 300px))" },
3619
- onScroll: handleScroll,
3620
- children: /* @__PURE__ */ jsxs("div", { className: "divide-y divide-border/50 p-2", children: [
3621
- filteredOptions.length === 0 && !loading ? /* @__PURE__ */ jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: emptyText }) : filteredOptions.map((option) => {
3622
- const isSelected = value === option.value;
3623
- const isDisabled = option.disabled || disabled;
3624
- return /* @__PURE__ */ jsxs(
3625
- "button",
3626
- {
3627
- type: "button",
3628
- disabled: isDisabled,
3629
- onClick: () => {
3630
- onChange(option.value);
3631
- handleOpenChange(false);
3632
- },
3633
- className: cn(
3634
- "flex w-full items-center gap-3 p-3 text-left transition-all",
3635
- "cursor-pointer hover:bg-accent",
3636
- isSelected && "bg-primary/5",
3637
- isDisabled && "cursor-not-allowed opacity-50 hover:bg-transparent"
3638
- ),
3639
- children: [
3640
- option.icon && /* @__PURE__ */ jsx("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-primary/10", children: /* @__PURE__ */ jsx(option.icon, { className: "h-3.5 w-3.5 text-primary" }) }),
3641
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
3642
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium leading-tight", children: option.label }),
3643
- option.description && /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs leading-tight text-muted-foreground", children: option.description })
3644
- ] }),
3645
- isSelected && /* @__PURE__ */ jsx("span", { className: "flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground", children: /* @__PURE__ */ jsx(Check, { className: "h-3 w-3" }) })
3646
- ]
3647
- },
3648
- option.value
3649
- );
3650
- }),
3651
- loading && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-3", children: /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" }) })
3652
- ] })
3653
- }
3654
- )
3655
- ]
3656
- }
3688
+ ]
3689
+ }
3690
+ ),
3691
+ document.body
3657
3692
  )
3658
3693
  ] });
3659
3694
  }
@@ -4218,6 +4253,22 @@ var DropdownMenuShortcut = ({
4218
4253
  );
4219
4254
  };
4220
4255
  DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
4256
+ var Popover = PopoverPrimitive.Root;
4257
+ var PopoverTrigger = PopoverPrimitive.Trigger;
4258
+ var PopoverContent = React10.forwardRef(({ className, align = "center", sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx(
4259
+ PopoverPrimitive.Content,
4260
+ {
4261
+ ref,
4262
+ align,
4263
+ sideOffset,
4264
+ className: cn(
4265
+ "z-50 w-auto rounded-md border bg-popover p-4 text-popover-foreground shadow-lg outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
4266
+ className
4267
+ ),
4268
+ ...props
4269
+ }
4270
+ ) }));
4271
+ PopoverContent.displayName = PopoverPrimitive.Content.displayName;
4221
4272
  var TooltipProvider = TooltipPrimitive.Provider;
4222
4273
  var TooltipRoot = TooltipPrimitive.Root;
4223
4274
  var TooltipTrigger = TooltipPrimitive.Trigger;