@motor-hero/ui-kit 0.23.1 → 0.24.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.
@@ -19,16 +19,32 @@ export interface ComboboxProps {
19
19
  className?: string;
20
20
  "aria-invalid"?: boolean | "true" | "false";
21
21
  "aria-describedby"?: string;
22
+ /** Modo servidor: chamado a cada tecla com o texto bruto da busca. Sua
23
+ * presença ativa o modo servidor (filtro do cmdk desligado). */
24
+ onSearchChange?: (search: string) => void;
25
+ /** Modo servidor: chamado ao rolar até o fim da lista. */
26
+ onLoadMore?: () => void;
27
+ /** Modo servidor: mostra uma linha de carregamento no fim da lista. */
28
+ loading?: boolean;
29
+ /** Modo servidor: habilita o disparo de `onLoadMore`. */
30
+ hasMore?: boolean;
31
+ /** Modo servidor: rótulo do valor já selecionado que pode não estar na
32
+ * página carregada (ex.: formulário de edição). */
33
+ selectedOption?: ComboboxOption;
22
34
  }
23
35
  /**
24
36
  * Select com busca (combobox) acessível e responsivo.
25
37
  *
26
- * - Desktop: Popover (modal) + cmdk — o dropdown acompanha a largura do gatilho.
38
+ * - Desktop: Popover não-modal + cmdk — o dropdown acompanha a largura do
39
+ * gatilho. Não-modal (com `pointer-events-auto` no conteúdo) de propósito:
40
+ * dentro de um Dialog, um Popover modal trava `body { pointer-events: none }`
41
+ * e prende o foco, então a busca não recebe clique/digitação (mesmo motivo do
42
+ * MultiCombobox).
27
43
  * - Mobile: Drawer (bottom sheet) + cmdk — evita os problemas de foco/clique de
28
44
  * Popover dentro de modal no touch, então a busca funciona no celular.
29
45
  *
30
46
  * Aceita `aria-invalid`/`aria-describedby` (encaminhados pelo FormField em
31
47
  * estado de erro) e fica com a borda vermelha quando inválido.
32
48
  */
33
- export declare function Combobox({ options, value, onChange, placeholder, searchPlaceholder, emptyMessage, disabled, id, className, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedby, }: ComboboxProps): React.JSX.Element;
49
+ export declare function Combobox({ options, value, onChange, placeholder, searchPlaceholder, emptyMessage, disabled, id, className, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedby, onSearchChange, onLoadMore, loading, hasMore, selectedOption, }: ComboboxProps): React.JSX.Element;
34
50
  //# sourceMappingURL=combobox.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"combobox.d.ts","sourceRoot":"","sources":["../../src/components/combobox.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAK9B,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,cAAc,EAAE,CAAA;IACzB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,oCAAoC;IACpC,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,cAAc,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAA;IAC3C,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,EACvB,OAAO,EACP,KAAK,EACL,QAAQ,EACR,WAA4B,EAC5B,iBAA+B,EAC/B,YAA6C,EAC7C,QAAQ,EACR,EAAE,EACF,SAAS,EACT,cAAc,EAAE,WAAW,EAC3B,kBAAkB,EAAE,eAAe,GACpC,EAAE,aAAa,qBAgGf"}
1
+ {"version":3,"file":"combobox.d.ts","sourceRoot":"","sources":["../../src/components/combobox.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAM9B,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,cAAc,EAAE,CAAA;IACzB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,oCAAoC;IACpC,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,cAAc,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAA;IAC3C,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B;qEACiE;IACjE,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IACzC,0DAA0D;IAC1D,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;IACvB,uEAAuE;IACvE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,yDAAyD;IACzD,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;wDACoD;IACpD,cAAc,CAAC,EAAE,cAAc,CAAA;CAChC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,QAAQ,CAAC,EACvB,OAAO,EACP,KAAK,EACL,QAAQ,EACR,WAA4B,EAC5B,iBAA+B,EAC/B,YAA6C,EAC7C,QAAQ,EACR,EAAE,EACF,SAAS,EACT,cAAc,EAAE,WAAW,EAC3B,kBAAkB,EAAE,eAAe,EACnC,cAAc,EACd,UAAU,EACV,OAAO,EACP,OAAO,EACP,cAAc,GACf,EAAE,aAAa,qBAiIf"}
@@ -15,6 +15,18 @@ export interface MultiComboboxProps {
15
15
  className?: string;
16
16
  "aria-invalid"?: boolean | "true" | "false";
17
17
  "aria-describedby"?: string;
18
+ /** Modo servidor: chamado a cada tecla com o texto bruto da busca. Sua
19
+ * presença ativa o modo servidor (filtro do cmdk desligado). */
20
+ onSearchChange?: (search: string) => void;
21
+ /** Modo servidor: chamado ao rolar até o fim da lista. */
22
+ onLoadMore?: () => void;
23
+ /** Modo servidor: mostra uma linha de carregamento no fim da lista. */
24
+ loading?: boolean;
25
+ /** Modo servidor: habilita o disparo de `onLoadMore`. */
26
+ hasMore?: boolean;
27
+ /** Modo servidor: rótulos dos valores já selecionados que podem não estar
28
+ * na página carregada (chips do formulário de edição). */
29
+ selectedOptions?: ComboboxOption[];
18
30
  }
19
31
  /**
20
32
  * Combobox de múltipla seleção com chips, acessível e responsivo.
@@ -31,5 +43,5 @@ export interface MultiComboboxProps {
31
43
  * chip remove a opção. Aceita `aria-invalid`/`aria-describedby` (encaminhados
32
44
  * pelo FormField em estado de erro) e fica com a borda vermelha quando inválido.
33
45
  */
34
- export declare function MultiCombobox({ options, value, onChange, placeholder, searchPlaceholder, emptyMessage, disabled, id, className, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedby, }: MultiComboboxProps): React.JSX.Element;
46
+ export declare function MultiCombobox({ options, value, onChange, placeholder, searchPlaceholder, emptyMessage, disabled, id, className, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedby, onSearchChange, onLoadMore, loading, hasMore, selectedOptions, }: MultiComboboxProps): React.JSX.Element;
35
47
  //# sourceMappingURL=multi-combobox.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"multi-combobox.d.ts","sourceRoot":"","sources":["../../src/components/multi-combobox.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAEhD,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,cAAc,EAAE,CAAA;IACzB,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;IACnC,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,oCAAoC;IACpC,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,cAAc,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAA;IAC3C,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,KAAK,EACL,QAAQ,EACR,WAA4B,EAC5B,iBAA+B,EAC/B,YAA6C,EAC7C,QAAQ,EACR,EAAE,EACF,SAAS,EACT,cAAc,EAAE,WAAW,EAC3B,kBAAkB,EAAE,eAAe,GACpC,EAAE,kBAAkB,qBA0IpB"}
1
+ {"version":3,"file":"multi-combobox.d.ts","sourceRoot":"","sources":["../../src/components/multi-combobox.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAK9B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAEhD,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,cAAc,EAAE,CAAA;IACzB,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;IACnC,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,oCAAoC;IACpC,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,cAAc,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAA;IAC3C,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B;qEACiE;IACjE,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IACzC,0DAA0D;IAC1D,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;IACvB,uEAAuE;IACvE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,yDAAyD;IACzD,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;+DAC2D;IAC3D,eAAe,CAAC,EAAE,cAAc,EAAE,CAAA;CACnC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,KAAK,EACL,QAAQ,EACR,WAA4B,EAC5B,iBAA+B,EAC/B,YAA6C,EAC7C,QAAQ,EACR,EAAE,EACF,SAAS,EACT,cAAc,EAAE,WAAW,EAC3B,kBAAkB,EAAE,eAAe,EACnC,cAAc,EACd,UAAU,EACV,OAAO,EACP,OAAO,EACP,eAAe,GAChB,EAAE,kBAAkB,qBA6KpB"}
@@ -0,0 +1,16 @@
1
+ import * as React from "react";
2
+ /**
3
+ * Dispara `onLoadMore` quando o `sentinelRef` entra na viewport do `listRef`
4
+ * (raiz = a lista rolável, não a janela — a lista rola por dentro do Popover/
5
+ * Drawer). Usa refs "latest" para `loading`/`onLoadMore` e evita reassinar o
6
+ * observer a cada render.
7
+ */
8
+ export declare function useInfiniteScrollSentinel({ enabled, loading, onLoadMore, }: {
9
+ enabled: boolean;
10
+ loading?: boolean;
11
+ onLoadMore?: () => void;
12
+ }): {
13
+ listRef: React.RefObject<HTMLDivElement | null>;
14
+ sentinelRef: React.RefObject<HTMLDivElement | null>;
15
+ };
16
+ //# sourceMappingURL=use-infinite-scroll-sentinel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-infinite-scroll-sentinel.d.ts","sourceRoot":"","sources":["../../src/hooks/use-infinite-scroll-sentinel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,EACxC,OAAO,EACP,OAAO,EACP,UAAU,GACX,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;CACxB;;;EA8BA"}
package/dist/index.cjs CHANGED
@@ -690,17 +690,51 @@ PasswordInput.displayName = "PasswordInput";
690
690
  var PopoverPrimitive = __toESM(require("@radix-ui/react-popover"), 1);
691
691
  var import_cmdk = require("cmdk");
692
692
  var import_lucide_react4 = require("lucide-react");
693
- var React5 = __toESM(require("react"), 1);
693
+ var React6 = __toESM(require("react"), 1);
694
694
  var import_vaul = require("vaul");
695
695
 
696
- // src/hooks/use-is-desktop.ts
696
+ // src/hooks/use-infinite-scroll-sentinel.ts
697
697
  var React4 = __toESM(require("react"), 1);
698
+ function useInfiniteScrollSentinel({
699
+ enabled,
700
+ loading,
701
+ onLoadMore
702
+ }) {
703
+ const listRef = React4.useRef(null);
704
+ const sentinelRef = React4.useRef(null);
705
+ const loadingRef = React4.useRef(loading);
706
+ const onLoadMoreRef = React4.useRef(onLoadMore);
707
+ React4.useEffect(() => {
708
+ loadingRef.current = loading;
709
+ onLoadMoreRef.current = onLoadMore;
710
+ });
711
+ React4.useEffect(() => {
712
+ if (!enabled) return;
713
+ const root = listRef.current;
714
+ const sentinel = sentinelRef.current;
715
+ if (!root || !sentinel) return;
716
+ const observer = new IntersectionObserver(
717
+ (entries) => {
718
+ if (entries[0]?.isIntersecting && !loadingRef.current) {
719
+ onLoadMoreRef.current?.();
720
+ }
721
+ },
722
+ { root, rootMargin: "0px 0px 100px 0px" }
723
+ );
724
+ observer.observe(sentinel);
725
+ return () => observer.disconnect();
726
+ }, [enabled]);
727
+ return { listRef, sentinelRef };
728
+ }
729
+
730
+ // src/hooks/use-is-desktop.ts
731
+ var React5 = __toESM(require("react"), 1);
698
732
  var DESKTOP_QUERY = "(min-width: 640px)";
699
733
  function useIsDesktop() {
700
- const [isDesktop, setIsDesktop] = React4.useState(
734
+ const [isDesktop, setIsDesktop] = React5.useState(
701
735
  () => typeof window !== "undefined" ? window.matchMedia(DESKTOP_QUERY).matches : true
702
736
  );
703
- React4.useEffect(() => {
737
+ React5.useEffect(() => {
704
738
  const mql = window.matchMedia(DESKTOP_QUERY);
705
739
  const onChange = () => setIsDesktop(mql.matches);
706
740
  onChange();
@@ -723,11 +757,22 @@ function Combobox({
723
757
  id,
724
758
  className,
725
759
  "aria-invalid": ariaInvalid,
726
- "aria-describedby": ariaDescribedby
760
+ "aria-describedby": ariaDescribedby,
761
+ onSearchChange,
762
+ onLoadMore,
763
+ loading,
764
+ hasMore,
765
+ selectedOption
727
766
  }) {
728
- const [open, setOpen] = React5.useState(false);
767
+ const [open, setOpen] = React6.useState(false);
729
768
  const isDesktop = useIsDesktop();
730
- const selected = options.find((option) => option.value === value);
769
+ const isServer = onSearchChange !== void 0;
770
+ const selected = selectedOption?.value === value ? selectedOption : options.find((option) => option.value === value);
771
+ const { listRef, sentinelRef } = useInfiniteScrollSentinel({
772
+ enabled: isServer && !!hasMore,
773
+ loading,
774
+ onLoadMore
775
+ });
731
776
  const trigger = /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
732
777
  "button",
733
778
  {
@@ -748,55 +793,73 @@ function Combobox({
748
793
  ]
749
794
  }
750
795
  );
751
- const command = /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_cmdk.Command, { className: "flex h-full w-full flex-col overflow-hidden", children: [
752
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center border-b px-3", children: [
753
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react4.Search, { className: "mr-2 h-4 w-4 shrink-0 opacity-50" }),
754
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
755
- import_cmdk.Command.Input,
756
- {
757
- placeholder: searchPlaceholder,
758
- className: "flex h-11 w-full bg-transparent py-2 text-base outline-none placeholder:text-muted-foreground sm:h-9 sm:text-sm"
759
- }
760
- )
761
- ] }),
762
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_cmdk.Command.List, { className: "max-h-[50vh] overflow-y-auto overflow-x-hidden p-1 sm:max-h-60", children: [
763
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_cmdk.Command.Empty, { className: "py-6 text-center text-sm text-muted-foreground", children: emptyMessage }),
764
- options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
765
- import_cmdk.Command.Item,
766
- {
767
- value: option.label,
768
- disabled: option.disabled,
769
- onSelect: () => {
770
- onChange(option.value);
771
- setOpen(false);
772
- },
773
- className: "relative flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-2.5 text-sm outline-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 sm:py-1.5",
774
- children: [
775
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
776
- import_lucide_react4.Check,
777
- {
778
- className: cn(
779
- "h-4 w-4 shrink-0",
780
- value === option.value ? "opacity-100" : "opacity-0"
781
- )
782
- }
783
- ),
784
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "truncate", children: option.label })
785
- ]
786
- },
787
- option.value
788
- ))
789
- ] })
790
- ] });
796
+ const command = /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
797
+ import_cmdk.Command,
798
+ {
799
+ shouldFilter: !isServer,
800
+ className: "flex h-full w-full flex-col overflow-hidden",
801
+ children: [
802
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center border-b px-3", children: [
803
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react4.Search, { className: "mr-2 h-4 w-4 shrink-0 opacity-50" }),
804
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
805
+ import_cmdk.Command.Input,
806
+ {
807
+ placeholder: searchPlaceholder,
808
+ onValueChange: isServer ? onSearchChange : void 0,
809
+ className: "flex h-11 w-full bg-transparent py-2 text-base outline-none placeholder:text-muted-foreground sm:h-9 sm:text-sm"
810
+ }
811
+ )
812
+ ] }),
813
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
814
+ import_cmdk.Command.List,
815
+ {
816
+ ref: listRef,
817
+ className: "max-h-[50vh] overflow-y-auto overflow-x-hidden p-1 sm:max-h-60",
818
+ children: [
819
+ !isServer && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_cmdk.Command.Empty, { className: "py-6 text-center text-sm text-muted-foreground", children: emptyMessage }),
820
+ options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
821
+ import_cmdk.Command.Item,
822
+ {
823
+ value: isServer ? option.value : option.label,
824
+ disabled: option.disabled,
825
+ onSelect: () => {
826
+ onChange(option.value);
827
+ setOpen(false);
828
+ },
829
+ className: "relative flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-2.5 text-sm outline-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 sm:py-1.5",
830
+ children: [
831
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
832
+ import_lucide_react4.Check,
833
+ {
834
+ className: cn(
835
+ "h-4 w-4 shrink-0",
836
+ value === option.value ? "opacity-100" : "opacity-0"
837
+ )
838
+ }
839
+ ),
840
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "truncate", children: option.label })
841
+ ]
842
+ },
843
+ option.value
844
+ )),
845
+ isServer && !loading && options.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "py-6 text-center text-sm text-muted-foreground", children: emptyMessage }),
846
+ isServer && loading && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex items-center justify-center py-6", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react4.Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" }) }),
847
+ isServer && hasMore && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { ref: sentinelRef, "aria-hidden": true, className: "h-1" })
848
+ ]
849
+ }
850
+ )
851
+ ]
852
+ }
853
+ );
791
854
  if (isDesktop) {
792
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(PopoverPrimitive.Root, { open, onOpenChange: setOpen, modal: true, children: [
855
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(PopoverPrimitive.Root, { open, onOpenChange: setOpen, children: [
793
856
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(PopoverPrimitive.Trigger, { asChild: true, children: trigger }),
794
857
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(PopoverPrimitive.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
795
858
  PopoverPrimitive.Content,
796
859
  {
797
860
  align: "start",
798
861
  sideOffset: 4,
799
- className: "z-50 overflow-hidden rounded-md border bg-popover p-0 text-popover-foreground shadow-md",
862
+ className: "pointer-events-auto z-50 overflow-hidden rounded-md border bg-popover p-0 text-popover-foreground shadow-md",
800
863
  style: { width: "var(--radix-popover-trigger-width)" },
801
864
  children: command
802
865
  }
@@ -820,7 +883,7 @@ function Combobox({
820
883
  var PopoverPrimitive2 = __toESM(require("@radix-ui/react-popover"), 1);
821
884
  var import_cmdk2 = require("cmdk");
822
885
  var import_lucide_react5 = require("lucide-react");
823
- var React6 = __toESM(require("react"), 1);
886
+ var React7 = __toESM(require("react"), 1);
824
887
  var import_vaul2 = require("vaul");
825
888
  var import_jsx_runtime17 = require("react/jsx-runtime");
826
889
  function MultiCombobox({
@@ -834,11 +897,27 @@ function MultiCombobox({
834
897
  id,
835
898
  className,
836
899
  "aria-invalid": ariaInvalid,
837
- "aria-describedby": ariaDescribedby
900
+ "aria-describedby": ariaDescribedby,
901
+ onSearchChange,
902
+ onLoadMore,
903
+ loading,
904
+ hasMore,
905
+ selectedOptions
838
906
  }) {
839
- const [open, setOpen] = React6.useState(false);
907
+ const [open, setOpen] = React7.useState(false);
840
908
  const isDesktop = useIsDesktop();
841
- const selected = options.filter((option) => value.includes(option.value));
909
+ const isServer = onSearchChange !== void 0;
910
+ const seen = /* @__PURE__ */ new Set();
911
+ const selected = [...selectedOptions ?? [], ...options].filter((option) => {
912
+ if (!value.includes(option.value) || seen.has(option.value)) return false;
913
+ seen.add(option.value);
914
+ return true;
915
+ });
916
+ const { listRef, sentinelRef } = useInfiniteScrollSentinel({
917
+ enabled: isServer && !!hasMore,
918
+ loading,
919
+ onLoadMore
920
+ });
842
921
  const toggleValue = (optionValue) => {
843
922
  onChange(
844
923
  value.includes(optionValue) ? value.filter((v) => v !== optionValue) : [...value, optionValue]
@@ -898,43 +977,61 @@ function MultiCombobox({
898
977
  ]
899
978
  }
900
979
  );
901
- const command = /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_cmdk2.Command, { className: "flex h-full w-full flex-col overflow-hidden", children: [
902
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center border-b px-3", children: [
903
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.Search, { className: "mr-2 h-4 w-4 shrink-0 opacity-50" }),
904
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
905
- import_cmdk2.Command.Input,
906
- {
907
- placeholder: searchPlaceholder,
908
- className: "flex h-11 w-full bg-transparent py-2 text-base outline-none placeholder:text-muted-foreground sm:h-9 sm:text-sm"
909
- }
910
- )
911
- ] }),
912
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_cmdk2.Command.List, { className: "max-h-[50vh] overflow-y-auto overflow-x-hidden p-1 sm:max-h-60", children: [
913
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_cmdk2.Command.Empty, { className: "py-6 text-center text-sm text-muted-foreground", children: emptyMessage }),
914
- options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
915
- import_cmdk2.Command.Item,
916
- {
917
- value: option.label,
918
- disabled: option.disabled,
919
- onSelect: () => toggleValue(option.value),
920
- className: "relative flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-2.5 text-sm outline-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 sm:py-1.5",
921
- children: [
922
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
923
- import_lucide_react5.Check,
924
- {
925
- className: cn(
926
- "h-4 w-4 shrink-0",
927
- value.includes(option.value) ? "opacity-100" : "opacity-0"
928
- )
929
- }
930
- ),
931
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "truncate", children: option.label })
932
- ]
933
- },
934
- option.value
935
- ))
936
- ] })
937
- ] });
980
+ const command = /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
981
+ import_cmdk2.Command,
982
+ {
983
+ shouldFilter: !isServer,
984
+ className: "flex h-full w-full flex-col overflow-hidden",
985
+ children: [
986
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center border-b px-3", children: [
987
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.Search, { className: "mr-2 h-4 w-4 shrink-0 opacity-50" }),
988
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
989
+ import_cmdk2.Command.Input,
990
+ {
991
+ placeholder: searchPlaceholder,
992
+ onValueChange: isServer ? onSearchChange : void 0,
993
+ className: "flex h-11 w-full bg-transparent py-2 text-base outline-none placeholder:text-muted-foreground sm:h-9 sm:text-sm"
994
+ }
995
+ )
996
+ ] }),
997
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
998
+ import_cmdk2.Command.List,
999
+ {
1000
+ ref: listRef,
1001
+ className: "max-h-[50vh] overflow-y-auto overflow-x-hidden p-1 sm:max-h-60",
1002
+ children: [
1003
+ !isServer && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_cmdk2.Command.Empty, { className: "py-6 text-center text-sm text-muted-foreground", children: emptyMessage }),
1004
+ options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
1005
+ import_cmdk2.Command.Item,
1006
+ {
1007
+ value: isServer ? option.value : option.label,
1008
+ disabled: option.disabled,
1009
+ onSelect: () => toggleValue(option.value),
1010
+ className: "relative flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-2.5 text-sm outline-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 sm:py-1.5",
1011
+ children: [
1012
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1013
+ import_lucide_react5.Check,
1014
+ {
1015
+ className: cn(
1016
+ "h-4 w-4 shrink-0",
1017
+ value.includes(option.value) ? "opacity-100" : "opacity-0"
1018
+ )
1019
+ }
1020
+ ),
1021
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "truncate", children: option.label })
1022
+ ]
1023
+ },
1024
+ option.value
1025
+ )),
1026
+ isServer && !loading && options.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "py-6 text-center text-sm text-muted-foreground", children: emptyMessage }),
1027
+ isServer && loading && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex items-center justify-center py-6", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" }) }),
1028
+ isServer && hasMore && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { ref: sentinelRef, "aria-hidden": true, className: "h-1" })
1029
+ ]
1030
+ }
1031
+ )
1032
+ ]
1033
+ }
1034
+ );
938
1035
  if (isDesktop) {
939
1036
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(PopoverPrimitive2.Root, { open, onOpenChange: setOpen, children: [
940
1037
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(PopoverPrimitive2.Trigger, { asChild: true, disabled, children: trigger }),
@@ -966,7 +1063,7 @@ function MultiCombobox({
966
1063
  // src/components/color-picker.tsx
967
1064
  var PopoverPrimitive3 = __toESM(require("@radix-ui/react-popover"), 1);
968
1065
  var import_lucide_react6 = require("lucide-react");
969
- var React7 = __toESM(require("react"), 1);
1066
+ var React8 = __toESM(require("react"), 1);
970
1067
  var import_react_colorful = require("react-colorful");
971
1068
  var import_jsx_runtime18 = require("react/jsx-runtime");
972
1069
  function ColorPicker({
@@ -979,7 +1076,7 @@ function ColorPicker({
979
1076
  "aria-invalid": ariaInvalid,
980
1077
  "aria-describedby": ariaDescribedby
981
1078
  }) {
982
- const [open, setOpen] = React7.useState(false);
1079
+ const [open, setOpen] = React8.useState(false);
983
1080
  const color = value || "";
984
1081
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(PopoverPrimitive3.Root, { open, onOpenChange: setOpen, modal: true, children: [
985
1082
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(PopoverPrimitive3.Trigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(