@motor-hero/ui-kit 0.23.2 → 0.25.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,6 +19,24 @@ 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;
34
+ /** Permite confirmar o texto digitado como valor, mesmo fora da lista
35
+ * (ex.: unidade de medida com texto livre). Mostra uma linha "criar" no
36
+ * topo e exibe o valor personalizado no gatilho. */
37
+ creatable?: boolean;
38
+ /** Rótulo da linha de criação. Padrão: `Usar "<texto>"`. */
39
+ formatCreateLabel?: (search: string) => string;
22
40
  }
23
41
  /**
24
42
  * Select com busca (combobox) acessível e responsivo.
@@ -34,5 +52,5 @@ export interface ComboboxProps {
34
52
  * Aceita `aria-invalid`/`aria-describedby` (encaminhados pelo FormField em
35
53
  * estado de erro) e fica com a borda vermelha quando inválido.
36
54
  */
37
- export declare function Combobox({ options, value, onChange, placeholder, searchPlaceholder, emptyMessage, disabled, id, className, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedby, }: ComboboxProps): React.JSX.Element;
55
+ export declare function Combobox({ options, value, onChange, placeholder, searchPlaceholder, emptyMessage, disabled, id, className, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedby, onSearchChange, onLoadMore, loading, hasMore, selectedOption, creatable, formatCreateLabel, }: ComboboxProps): React.JSX.Element;
38
56
  //# 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;;;;;;;;;;;;;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,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;IAC/B;;yDAEqD;IACrD,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,4DAA4D;IAC5D,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAA;CAC/C;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,EACd,SAAS,EACT,iBAAiB,GAClB,EAAE,aAAa,qBAwKf"}
@@ -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,41 @@ 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,
766
+ creatable,
767
+ formatCreateLabel
727
768
  }) {
728
- const [open, setOpen] = React5.useState(false);
769
+ const [open, setOpen] = React6.useState(false);
770
+ const [search, setSearch] = React6.useState("");
729
771
  const isDesktop = useIsDesktop();
730
- const selected = options.find((option) => option.value === value);
772
+ const isServer = onSearchChange !== void 0;
773
+ const selected = selectedOption?.value === value ? selectedOption : options.find((option) => option.value === value) ?? (creatable && value ? { value, label: value } : void 0);
774
+ const handleOpenChange = (next) => {
775
+ setOpen(next);
776
+ if (!next) setSearch("");
777
+ };
778
+ const handleSelect = (next) => {
779
+ onChange(next);
780
+ handleOpenChange(false);
781
+ };
782
+ const handleSearchChange = (next) => {
783
+ setSearch(next);
784
+ onSearchChange?.(next);
785
+ };
786
+ const trimmedSearch = search.trim();
787
+ const showCreate = !!creatable && trimmedSearch.length > 0 && !options.some(
788
+ (option) => option.value.toLowerCase() === trimmedSearch.toLowerCase()
789
+ );
790
+ const { listRef, sentinelRef } = useInfiniteScrollSentinel({
791
+ enabled: isServer && !!hasMore,
792
+ loading,
793
+ onLoadMore
794
+ });
731
795
  const trigger = /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
732
796
  "button",
733
797
  {
@@ -748,48 +812,77 @@ function Combobox({
748
812
  ]
749
813
  }
750
814
  );
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
- ] });
815
+ const command = /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
816
+ import_cmdk.Command,
817
+ {
818
+ shouldFilter: !isServer,
819
+ className: "flex h-full w-full flex-col overflow-hidden",
820
+ children: [
821
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center border-b px-3", children: [
822
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react4.Search, { className: "mr-2 h-4 w-4 shrink-0 opacity-50" }),
823
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
824
+ import_cmdk.Command.Input,
825
+ {
826
+ placeholder: searchPlaceholder,
827
+ onValueChange: isServer || creatable ? handleSearchChange : void 0,
828
+ className: "flex h-11 w-full bg-transparent py-2 text-base outline-none placeholder:text-muted-foreground sm:h-9 sm:text-sm"
829
+ }
830
+ )
831
+ ] }),
832
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
833
+ import_cmdk.Command.List,
834
+ {
835
+ ref: listRef,
836
+ className: "max-h-[50vh] overflow-y-auto overflow-x-hidden p-1 sm:max-h-60",
837
+ children: [
838
+ !isServer && !showCreate && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_cmdk.Command.Empty, { className: "py-6 text-center text-sm text-muted-foreground", children: emptyMessage }),
839
+ showCreate && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
840
+ import_cmdk.Command.Item,
841
+ {
842
+ value: trimmedSearch,
843
+ onSelect: () => handleSelect(trimmedSearch),
844
+ 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 sm:py-1.5",
845
+ children: [
846
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react4.Plus, { className: "h-4 w-4 shrink-0" }),
847
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "truncate", children: (formatCreateLabel ?? ((text) => `Usar "${text}"`))(
848
+ trimmedSearch
849
+ ) })
850
+ ]
851
+ }
852
+ ),
853
+ options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
854
+ import_cmdk.Command.Item,
855
+ {
856
+ value: isServer ? option.value : option.label,
857
+ disabled: option.disabled,
858
+ onSelect: () => handleSelect(option.value),
859
+ 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",
860
+ children: [
861
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
862
+ import_lucide_react4.Check,
863
+ {
864
+ className: cn(
865
+ "h-4 w-4 shrink-0",
866
+ value === option.value ? "opacity-100" : "opacity-0"
867
+ )
868
+ }
869
+ ),
870
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "truncate", children: option.label })
871
+ ]
872
+ },
873
+ option.value
874
+ )),
875
+ isServer && !loading && options.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "py-6 text-center text-sm text-muted-foreground", children: emptyMessage }),
876
+ 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" }) }),
877
+ isServer && hasMore && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { ref: sentinelRef, "aria-hidden": true, className: "h-1" })
878
+ ]
879
+ }
880
+ )
881
+ ]
882
+ }
883
+ );
791
884
  if (isDesktop) {
792
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(PopoverPrimitive.Root, { open, onOpenChange: setOpen, children: [
885
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(PopoverPrimitive.Root, { open, onOpenChange: handleOpenChange, children: [
793
886
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(PopoverPrimitive.Trigger, { asChild: true, children: trigger }),
794
887
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(PopoverPrimitive.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
795
888
  PopoverPrimitive.Content,
@@ -803,7 +896,7 @@ function Combobox({
803
896
  ) })
804
897
  ] });
805
898
  }
806
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_vaul.Drawer.Root, { open, onOpenChange: setOpen, children: [
899
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_vaul.Drawer.Root, { open, onOpenChange: handleOpenChange, children: [
807
900
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_vaul.Drawer.Trigger, { asChild: true, children: trigger }),
808
901
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_vaul.Drawer.Portal, { children: [
809
902
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_vaul.Drawer.Overlay, { className: "fixed inset-0 z-50 bg-black/80" }),
@@ -820,7 +913,7 @@ function Combobox({
820
913
  var PopoverPrimitive2 = __toESM(require("@radix-ui/react-popover"), 1);
821
914
  var import_cmdk2 = require("cmdk");
822
915
  var import_lucide_react5 = require("lucide-react");
823
- var React6 = __toESM(require("react"), 1);
916
+ var React7 = __toESM(require("react"), 1);
824
917
  var import_vaul2 = require("vaul");
825
918
  var import_jsx_runtime17 = require("react/jsx-runtime");
826
919
  function MultiCombobox({
@@ -834,11 +927,27 @@ function MultiCombobox({
834
927
  id,
835
928
  className,
836
929
  "aria-invalid": ariaInvalid,
837
- "aria-describedby": ariaDescribedby
930
+ "aria-describedby": ariaDescribedby,
931
+ onSearchChange,
932
+ onLoadMore,
933
+ loading,
934
+ hasMore,
935
+ selectedOptions
838
936
  }) {
839
- const [open, setOpen] = React6.useState(false);
937
+ const [open, setOpen] = React7.useState(false);
840
938
  const isDesktop = useIsDesktop();
841
- const selected = options.filter((option) => value.includes(option.value));
939
+ const isServer = onSearchChange !== void 0;
940
+ const seen = /* @__PURE__ */ new Set();
941
+ const selected = [...selectedOptions ?? [], ...options].filter((option) => {
942
+ if (!value.includes(option.value) || seen.has(option.value)) return false;
943
+ seen.add(option.value);
944
+ return true;
945
+ });
946
+ const { listRef, sentinelRef } = useInfiniteScrollSentinel({
947
+ enabled: isServer && !!hasMore,
948
+ loading,
949
+ onLoadMore
950
+ });
842
951
  const toggleValue = (optionValue) => {
843
952
  onChange(
844
953
  value.includes(optionValue) ? value.filter((v) => v !== optionValue) : [...value, optionValue]
@@ -898,43 +1007,61 @@ function MultiCombobox({
898
1007
  ]
899
1008
  }
900
1009
  );
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
- ] });
1010
+ const command = /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
1011
+ import_cmdk2.Command,
1012
+ {
1013
+ shouldFilter: !isServer,
1014
+ className: "flex h-full w-full flex-col overflow-hidden",
1015
+ children: [
1016
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center border-b px-3", children: [
1017
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.Search, { className: "mr-2 h-4 w-4 shrink-0 opacity-50" }),
1018
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1019
+ import_cmdk2.Command.Input,
1020
+ {
1021
+ placeholder: searchPlaceholder,
1022
+ onValueChange: isServer ? onSearchChange : void 0,
1023
+ className: "flex h-11 w-full bg-transparent py-2 text-base outline-none placeholder:text-muted-foreground sm:h-9 sm:text-sm"
1024
+ }
1025
+ )
1026
+ ] }),
1027
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
1028
+ import_cmdk2.Command.List,
1029
+ {
1030
+ ref: listRef,
1031
+ className: "max-h-[50vh] overflow-y-auto overflow-x-hidden p-1 sm:max-h-60",
1032
+ children: [
1033
+ !isServer && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_cmdk2.Command.Empty, { className: "py-6 text-center text-sm text-muted-foreground", children: emptyMessage }),
1034
+ options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
1035
+ import_cmdk2.Command.Item,
1036
+ {
1037
+ value: isServer ? option.value : option.label,
1038
+ disabled: option.disabled,
1039
+ onSelect: () => toggleValue(option.value),
1040
+ 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",
1041
+ children: [
1042
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1043
+ import_lucide_react5.Check,
1044
+ {
1045
+ className: cn(
1046
+ "h-4 w-4 shrink-0",
1047
+ value.includes(option.value) ? "opacity-100" : "opacity-0"
1048
+ )
1049
+ }
1050
+ ),
1051
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "truncate", children: option.label })
1052
+ ]
1053
+ },
1054
+ option.value
1055
+ )),
1056
+ isServer && !loading && options.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "py-6 text-center text-sm text-muted-foreground", children: emptyMessage }),
1057
+ 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" }) }),
1058
+ isServer && hasMore && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { ref: sentinelRef, "aria-hidden": true, className: "h-1" })
1059
+ ]
1060
+ }
1061
+ )
1062
+ ]
1063
+ }
1064
+ );
938
1065
  if (isDesktop) {
939
1066
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(PopoverPrimitive2.Root, { open, onOpenChange: setOpen, children: [
940
1067
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(PopoverPrimitive2.Trigger, { asChild: true, disabled, children: trigger }),
@@ -966,7 +1093,7 @@ function MultiCombobox({
966
1093
  // src/components/color-picker.tsx
967
1094
  var PopoverPrimitive3 = __toESM(require("@radix-ui/react-popover"), 1);
968
1095
  var import_lucide_react6 = require("lucide-react");
969
- var React7 = __toESM(require("react"), 1);
1096
+ var React8 = __toESM(require("react"), 1);
970
1097
  var import_react_colorful = require("react-colorful");
971
1098
  var import_jsx_runtime18 = require("react/jsx-runtime");
972
1099
  function ColorPicker({
@@ -979,7 +1106,7 @@ function ColorPicker({
979
1106
  "aria-invalid": ariaInvalid,
980
1107
  "aria-describedby": ariaDescribedby
981
1108
  }) {
982
- const [open, setOpen] = React7.useState(false);
1109
+ const [open, setOpen] = React8.useState(false);
983
1110
  const color = value || "";
984
1111
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(PopoverPrimitive3.Root, { open, onOpenChange: setOpen, modal: true, children: [
985
1112
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(PopoverPrimitive3.Trigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(