@facter/ds-core 1.33.1 → 1.33.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.mjs CHANGED
@@ -17,6 +17,7 @@ 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 { createPortal } from 'react-dom';
20
21
  import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
21
22
  import * as AvatarPrimitive from '@radix-ui/react-avatar';
22
23
  import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
@@ -3487,8 +3488,11 @@ function CardSelect({
3487
3488
  const [search, setSearch] = React10.useState("");
3488
3489
  const selected = options.find((o) => o.value === value);
3489
3490
  const containerRef = React10.useRef(null);
3491
+ const dropdownRef = React10.useRef(null);
3490
3492
  const listRef = React10.useRef(null);
3491
3493
  const searchRef = React10.useRef(null);
3494
+ const [dropdownPos, setDropdownPos] = React10.useState({});
3495
+ const [listMaxHeight, setListMaxHeight] = React10.useState(300);
3492
3496
  const filteredOptions = React10.useMemo(() => {
3493
3497
  if (!searchable || onSearch || !search) return options;
3494
3498
  const q = search.toLowerCase();
@@ -3503,10 +3507,39 @@ function CardSelect({
3503
3507
  },
3504
3508
  [onSearch]
3505
3509
  );
3510
+ React10.useEffect(() => {
3511
+ if (!open || !containerRef.current) return;
3512
+ const updatePosition = () => {
3513
+ const rect = containerRef.current.getBoundingClientRect();
3514
+ const margin = 8;
3515
+ const searchBarHeight = searchable ? 41 : 0;
3516
+ const spaceBelow = window.innerHeight - rect.bottom - margin;
3517
+ const spaceAbove = rect.top - margin;
3518
+ const showAbove = spaceBelow < 150 && spaceAbove > spaceBelow;
3519
+ const availableSpace = showAbove ? spaceAbove : spaceBelow;
3520
+ const maxList = Math.max(Math.min(availableSpace - searchBarHeight - margin, 300), 100);
3521
+ setListMaxHeight(maxList);
3522
+ setDropdownPos({
3523
+ position: "fixed",
3524
+ left: rect.left,
3525
+ width: rect.width,
3526
+ zIndex: 9999,
3527
+ ...showAbove ? { bottom: window.innerHeight - rect.top + 4 } : { top: rect.bottom + 4 }
3528
+ });
3529
+ };
3530
+ updatePosition();
3531
+ window.addEventListener("scroll", updatePosition, true);
3532
+ window.addEventListener("resize", updatePosition);
3533
+ return () => {
3534
+ window.removeEventListener("scroll", updatePosition, true);
3535
+ window.removeEventListener("resize", updatePosition);
3536
+ };
3537
+ }, [open, searchable]);
3506
3538
  React10.useEffect(() => {
3507
3539
  if (!open) return;
3508
3540
  const handleClickOutside = (e) => {
3509
- if (containerRef.current && !containerRef.current.contains(e.target)) {
3541
+ const target = e.target;
3542
+ if (containerRef.current && !containerRef.current.contains(target) && (!dropdownRef.current || !dropdownRef.current.contains(target))) {
3510
3543
  setOpen(false);
3511
3544
  }
3512
3545
  };
@@ -3595,63 +3628,75 @@ function CardSelect({
3595
3628
  ]
3596
3629
  }
3597
3630
  ),
3598
- open && /* @__PURE__ */ jsxs("div", { className: "absolute left-0 top-full z-50 mt-1 w-full 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", 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" }),
3601
- /* @__PURE__ */ jsx(
3602
- "input",
3603
- {
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"
3610
- }
3611
- )
3612
- ] }),
3613
- /* @__PURE__ */ jsx(
3631
+ open && createPortal(
3632
+ /* @__PURE__ */ jsxs(
3614
3633
  "div",
3615
3634
  {
3616
- ref: listRef,
3617
- className: "overflow-y-auto overscroll-contain max-h-[300px]",
3618
- onScroll: handleScroll,
3619
- children: /* @__PURE__ */ jsxs("div", { className: "divide-y divide-border/50 p-2", children: [
3620
- filteredOptions.length === 0 && !loading ? /* @__PURE__ */ jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: emptyText }) : filteredOptions.map((option) => {
3621
- const isSelected = value === option.value;
3622
- const isDisabled = option.disabled || disabled;
3623
- return /* @__PURE__ */ jsxs(
3624
- "button",
3635
+ ref: dropdownRef,
3636
+ style: dropdownPos,
3637
+ 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",
3638
+ children: [
3639
+ searchable && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2 border-b border-border", children: [
3640
+ /* @__PURE__ */ jsx(Search, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
3641
+ /* @__PURE__ */ jsx(
3642
+ "input",
3625
3643
  {
3626
- type: "button",
3627
- disabled: isDisabled,
3628
- onClick: () => {
3629
- onChange(option.value);
3630
- setOpen(false);
3631
- },
3632
- className: cn(
3633
- "flex w-full items-center gap-3 p-3 text-left transition-all",
3634
- "cursor-pointer hover:bg-accent",
3635
- isSelected && "bg-primary/5",
3636
- isDisabled && "cursor-not-allowed opacity-50 hover:bg-transparent"
3637
- ),
3638
- children: [
3639
- 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" }) }),
3640
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
3641
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium leading-tight", children: option.label }),
3642
- option.description && /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs leading-tight text-muted-foreground", children: option.description })
3643
- ] }),
3644
- 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" }) })
3645
- ]
3646
- },
3647
- option.value
3648
- );
3649
- }),
3650
- 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" }) })
3651
- ] })
3644
+ ref: searchRef,
3645
+ type: "text",
3646
+ value: search,
3647
+ onChange: (e) => handleSearch(e.target.value),
3648
+ placeholder: searchPlaceholder,
3649
+ className: "flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
3650
+ }
3651
+ )
3652
+ ] }),
3653
+ /* @__PURE__ */ jsx(
3654
+ "div",
3655
+ {
3656
+ ref: listRef,
3657
+ className: "overflow-y-auto overscroll-contain",
3658
+ style: { maxHeight: listMaxHeight },
3659
+ onScroll: handleScroll,
3660
+ children: /* @__PURE__ */ jsxs("div", { className: "divide-y divide-border/50 p-2", children: [
3661
+ filteredOptions.length === 0 && !loading ? /* @__PURE__ */ jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: emptyText }) : filteredOptions.map((option) => {
3662
+ const isSelected = value === option.value;
3663
+ const isDisabled = option.disabled || disabled;
3664
+ return /* @__PURE__ */ jsxs(
3665
+ "button",
3666
+ {
3667
+ type: "button",
3668
+ disabled: isDisabled,
3669
+ onClick: () => {
3670
+ onChange(option.value);
3671
+ setOpen(false);
3672
+ },
3673
+ className: cn(
3674
+ "flex w-full items-center gap-3 p-3 text-left transition-all",
3675
+ "cursor-pointer hover:bg-accent",
3676
+ isSelected && "bg-primary/5",
3677
+ isDisabled && "cursor-not-allowed opacity-50 hover:bg-transparent"
3678
+ ),
3679
+ children: [
3680
+ 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" }) }),
3681
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
3682
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium leading-tight", children: option.label }),
3683
+ option.description && /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs leading-tight text-muted-foreground", children: option.description })
3684
+ ] }),
3685
+ 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" }) })
3686
+ ]
3687
+ },
3688
+ option.value
3689
+ );
3690
+ }),
3691
+ 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" }) })
3692
+ ] })
3693
+ }
3694
+ )
3695
+ ]
3652
3696
  }
3653
- )
3654
- ] })
3697
+ ),
3698
+ document.body
3699
+ )
3655
3700
  ] });
3656
3701
  }
3657
3702
  function FormTextarea({