@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.
package/dist/index.js CHANGED
@@ -615,18 +615,52 @@ PasswordInput.displayName = "PasswordInput";
615
615
  // src/components/combobox.tsx
616
616
  import * as PopoverPrimitive from "@radix-ui/react-popover";
617
617
  import { Command as CommandPrimitive } from "cmdk";
618
- import { Check, ChevronsUpDown, Search } from "lucide-react";
619
- import * as React5 from "react";
618
+ import { Check, ChevronsUpDown, Loader2, Plus, Search } from "lucide-react";
619
+ import * as React6 from "react";
620
620
  import { Drawer as DrawerPrimitive } from "vaul";
621
621
 
622
- // src/hooks/use-is-desktop.ts
622
+ // src/hooks/use-infinite-scroll-sentinel.ts
623
623
  import * as React4 from "react";
624
+ function useInfiniteScrollSentinel({
625
+ enabled,
626
+ loading,
627
+ onLoadMore
628
+ }) {
629
+ const listRef = React4.useRef(null);
630
+ const sentinelRef = React4.useRef(null);
631
+ const loadingRef = React4.useRef(loading);
632
+ const onLoadMoreRef = React4.useRef(onLoadMore);
633
+ React4.useEffect(() => {
634
+ loadingRef.current = loading;
635
+ onLoadMoreRef.current = onLoadMore;
636
+ });
637
+ React4.useEffect(() => {
638
+ if (!enabled) return;
639
+ const root = listRef.current;
640
+ const sentinel = sentinelRef.current;
641
+ if (!root || !sentinel) return;
642
+ const observer = new IntersectionObserver(
643
+ (entries) => {
644
+ if (entries[0]?.isIntersecting && !loadingRef.current) {
645
+ onLoadMoreRef.current?.();
646
+ }
647
+ },
648
+ { root, rootMargin: "0px 0px 100px 0px" }
649
+ );
650
+ observer.observe(sentinel);
651
+ return () => observer.disconnect();
652
+ }, [enabled]);
653
+ return { listRef, sentinelRef };
654
+ }
655
+
656
+ // src/hooks/use-is-desktop.ts
657
+ import * as React5 from "react";
624
658
  var DESKTOP_QUERY = "(min-width: 640px)";
625
659
  function useIsDesktop() {
626
- const [isDesktop, setIsDesktop] = React4.useState(
660
+ const [isDesktop, setIsDesktop] = React5.useState(
627
661
  () => typeof window !== "undefined" ? window.matchMedia(DESKTOP_QUERY).matches : true
628
662
  );
629
- React4.useEffect(() => {
663
+ React5.useEffect(() => {
630
664
  const mql = window.matchMedia(DESKTOP_QUERY);
631
665
  const onChange = () => setIsDesktop(mql.matches);
632
666
  onChange();
@@ -649,11 +683,41 @@ function Combobox({
649
683
  id,
650
684
  className,
651
685
  "aria-invalid": ariaInvalid,
652
- "aria-describedby": ariaDescribedby
686
+ "aria-describedby": ariaDescribedby,
687
+ onSearchChange,
688
+ onLoadMore,
689
+ loading,
690
+ hasMore,
691
+ selectedOption,
692
+ creatable,
693
+ formatCreateLabel
653
694
  }) {
654
- const [open, setOpen] = React5.useState(false);
695
+ const [open, setOpen] = React6.useState(false);
696
+ const [search, setSearch] = React6.useState("");
655
697
  const isDesktop = useIsDesktop();
656
- const selected = options.find((option) => option.value === value);
698
+ const isServer = onSearchChange !== void 0;
699
+ const selected = selectedOption?.value === value ? selectedOption : options.find((option) => option.value === value) ?? (creatable && value ? { value, label: value } : void 0);
700
+ const handleOpenChange = (next) => {
701
+ setOpen(next);
702
+ if (!next) setSearch("");
703
+ };
704
+ const handleSelect = (next) => {
705
+ onChange(next);
706
+ handleOpenChange(false);
707
+ };
708
+ const handleSearchChange = (next) => {
709
+ setSearch(next);
710
+ onSearchChange?.(next);
711
+ };
712
+ const trimmedSearch = search.trim();
713
+ const showCreate = !!creatable && trimmedSearch.length > 0 && !options.some(
714
+ (option) => option.value.toLowerCase() === trimmedSearch.toLowerCase()
715
+ );
716
+ const { listRef, sentinelRef } = useInfiniteScrollSentinel({
717
+ enabled: isServer && !!hasMore,
718
+ loading,
719
+ onLoadMore
720
+ });
657
721
  const trigger = /* @__PURE__ */ jsxs14(
658
722
  "button",
659
723
  {
@@ -674,48 +738,77 @@ function Combobox({
674
738
  ]
675
739
  }
676
740
  );
677
- const command = /* @__PURE__ */ jsxs14(CommandPrimitive, { className: "flex h-full w-full flex-col overflow-hidden", children: [
678
- /* @__PURE__ */ jsxs14("div", { className: "flex items-center border-b px-3", children: [
679
- /* @__PURE__ */ jsx16(Search, { className: "mr-2 h-4 w-4 shrink-0 opacity-50" }),
680
- /* @__PURE__ */ jsx16(
681
- CommandPrimitive.Input,
682
- {
683
- placeholder: searchPlaceholder,
684
- className: "flex h-11 w-full bg-transparent py-2 text-base outline-none placeholder:text-muted-foreground sm:h-9 sm:text-sm"
685
- }
686
- )
687
- ] }),
688
- /* @__PURE__ */ jsxs14(CommandPrimitive.List, { className: "max-h-[50vh] overflow-y-auto overflow-x-hidden p-1 sm:max-h-60", children: [
689
- /* @__PURE__ */ jsx16(CommandPrimitive.Empty, { className: "py-6 text-center text-sm text-muted-foreground", children: emptyMessage }),
690
- options.map((option) => /* @__PURE__ */ jsxs14(
691
- CommandPrimitive.Item,
692
- {
693
- value: option.label,
694
- disabled: option.disabled,
695
- onSelect: () => {
696
- onChange(option.value);
697
- setOpen(false);
698
- },
699
- 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",
700
- children: [
701
- /* @__PURE__ */ jsx16(
702
- Check,
703
- {
704
- className: cn(
705
- "h-4 w-4 shrink-0",
706
- value === option.value ? "opacity-100" : "opacity-0"
707
- )
708
- }
709
- ),
710
- /* @__PURE__ */ jsx16("span", { className: "truncate", children: option.label })
711
- ]
712
- },
713
- option.value
714
- ))
715
- ] })
716
- ] });
741
+ const command = /* @__PURE__ */ jsxs14(
742
+ CommandPrimitive,
743
+ {
744
+ shouldFilter: !isServer,
745
+ className: "flex h-full w-full flex-col overflow-hidden",
746
+ children: [
747
+ /* @__PURE__ */ jsxs14("div", { className: "flex items-center border-b px-3", children: [
748
+ /* @__PURE__ */ jsx16(Search, { className: "mr-2 h-4 w-4 shrink-0 opacity-50" }),
749
+ /* @__PURE__ */ jsx16(
750
+ CommandPrimitive.Input,
751
+ {
752
+ placeholder: searchPlaceholder,
753
+ onValueChange: isServer || creatable ? handleSearchChange : void 0,
754
+ className: "flex h-11 w-full bg-transparent py-2 text-base outline-none placeholder:text-muted-foreground sm:h-9 sm:text-sm"
755
+ }
756
+ )
757
+ ] }),
758
+ /* @__PURE__ */ jsxs14(
759
+ CommandPrimitive.List,
760
+ {
761
+ ref: listRef,
762
+ className: "max-h-[50vh] overflow-y-auto overflow-x-hidden p-1 sm:max-h-60",
763
+ children: [
764
+ !isServer && !showCreate && /* @__PURE__ */ jsx16(CommandPrimitive.Empty, { className: "py-6 text-center text-sm text-muted-foreground", children: emptyMessage }),
765
+ showCreate && /* @__PURE__ */ jsxs14(
766
+ CommandPrimitive.Item,
767
+ {
768
+ value: trimmedSearch,
769
+ onSelect: () => handleSelect(trimmedSearch),
770
+ 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",
771
+ children: [
772
+ /* @__PURE__ */ jsx16(Plus, { className: "h-4 w-4 shrink-0" }),
773
+ /* @__PURE__ */ jsx16("span", { className: "truncate", children: (formatCreateLabel ?? ((text) => `Usar "${text}"`))(
774
+ trimmedSearch
775
+ ) })
776
+ ]
777
+ }
778
+ ),
779
+ options.map((option) => /* @__PURE__ */ jsxs14(
780
+ CommandPrimitive.Item,
781
+ {
782
+ value: isServer ? option.value : option.label,
783
+ disabled: option.disabled,
784
+ onSelect: () => handleSelect(option.value),
785
+ 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",
786
+ children: [
787
+ /* @__PURE__ */ jsx16(
788
+ Check,
789
+ {
790
+ className: cn(
791
+ "h-4 w-4 shrink-0",
792
+ value === option.value ? "opacity-100" : "opacity-0"
793
+ )
794
+ }
795
+ ),
796
+ /* @__PURE__ */ jsx16("span", { className: "truncate", children: option.label })
797
+ ]
798
+ },
799
+ option.value
800
+ )),
801
+ isServer && !loading && options.length === 0 && /* @__PURE__ */ jsx16("div", { className: "py-6 text-center text-sm text-muted-foreground", children: emptyMessage }),
802
+ isServer && loading && /* @__PURE__ */ jsx16("div", { className: "flex items-center justify-center py-6", children: /* @__PURE__ */ jsx16(Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" }) }),
803
+ isServer && hasMore && /* @__PURE__ */ jsx16("div", { ref: sentinelRef, "aria-hidden": true, className: "h-1" })
804
+ ]
805
+ }
806
+ )
807
+ ]
808
+ }
809
+ );
717
810
  if (isDesktop) {
718
- return /* @__PURE__ */ jsxs14(PopoverPrimitive.Root, { open, onOpenChange: setOpen, children: [
811
+ return /* @__PURE__ */ jsxs14(PopoverPrimitive.Root, { open, onOpenChange: handleOpenChange, children: [
719
812
  /* @__PURE__ */ jsx16(PopoverPrimitive.Trigger, { asChild: true, children: trigger }),
720
813
  /* @__PURE__ */ jsx16(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx16(
721
814
  PopoverPrimitive.Content,
@@ -729,7 +822,7 @@ function Combobox({
729
822
  ) })
730
823
  ] });
731
824
  }
732
- return /* @__PURE__ */ jsxs14(DrawerPrimitive.Root, { open, onOpenChange: setOpen, children: [
825
+ return /* @__PURE__ */ jsxs14(DrawerPrimitive.Root, { open, onOpenChange: handleOpenChange, children: [
733
826
  /* @__PURE__ */ jsx16(DrawerPrimitive.Trigger, { asChild: true, children: trigger }),
734
827
  /* @__PURE__ */ jsxs14(DrawerPrimitive.Portal, { children: [
735
828
  /* @__PURE__ */ jsx16(DrawerPrimitive.Overlay, { className: "fixed inset-0 z-50 bg-black/80" }),
@@ -745,8 +838,8 @@ function Combobox({
745
838
  // src/components/multi-combobox.tsx
746
839
  import * as PopoverPrimitive2 from "@radix-ui/react-popover";
747
840
  import { Command as CommandPrimitive2 } from "cmdk";
748
- import { Check as Check2, ChevronsUpDown as ChevronsUpDown2, Search as Search2, X as X2 } from "lucide-react";
749
- import * as React6 from "react";
841
+ import { Check as Check2, ChevronsUpDown as ChevronsUpDown2, Loader2 as Loader22, Search as Search2, X as X2 } from "lucide-react";
842
+ import * as React7 from "react";
750
843
  import { Drawer as DrawerPrimitive2 } from "vaul";
751
844
  import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
752
845
  function MultiCombobox({
@@ -760,11 +853,27 @@ function MultiCombobox({
760
853
  id,
761
854
  className,
762
855
  "aria-invalid": ariaInvalid,
763
- "aria-describedby": ariaDescribedby
856
+ "aria-describedby": ariaDescribedby,
857
+ onSearchChange,
858
+ onLoadMore,
859
+ loading,
860
+ hasMore,
861
+ selectedOptions
764
862
  }) {
765
- const [open, setOpen] = React6.useState(false);
863
+ const [open, setOpen] = React7.useState(false);
766
864
  const isDesktop = useIsDesktop();
767
- const selected = options.filter((option) => value.includes(option.value));
865
+ const isServer = onSearchChange !== void 0;
866
+ const seen = /* @__PURE__ */ new Set();
867
+ const selected = [...selectedOptions ?? [], ...options].filter((option) => {
868
+ if (!value.includes(option.value) || seen.has(option.value)) return false;
869
+ seen.add(option.value);
870
+ return true;
871
+ });
872
+ const { listRef, sentinelRef } = useInfiniteScrollSentinel({
873
+ enabled: isServer && !!hasMore,
874
+ loading,
875
+ onLoadMore
876
+ });
768
877
  const toggleValue = (optionValue) => {
769
878
  onChange(
770
879
  value.includes(optionValue) ? value.filter((v) => v !== optionValue) : [...value, optionValue]
@@ -824,43 +933,61 @@ function MultiCombobox({
824
933
  ]
825
934
  }
826
935
  );
827
- const command = /* @__PURE__ */ jsxs15(CommandPrimitive2, { className: "flex h-full w-full flex-col overflow-hidden", children: [
828
- /* @__PURE__ */ jsxs15("div", { className: "flex items-center border-b px-3", children: [
829
- /* @__PURE__ */ jsx17(Search2, { className: "mr-2 h-4 w-4 shrink-0 opacity-50" }),
830
- /* @__PURE__ */ jsx17(
831
- CommandPrimitive2.Input,
832
- {
833
- placeholder: searchPlaceholder,
834
- className: "flex h-11 w-full bg-transparent py-2 text-base outline-none placeholder:text-muted-foreground sm:h-9 sm:text-sm"
835
- }
836
- )
837
- ] }),
838
- /* @__PURE__ */ jsxs15(CommandPrimitive2.List, { className: "max-h-[50vh] overflow-y-auto overflow-x-hidden p-1 sm:max-h-60", children: [
839
- /* @__PURE__ */ jsx17(CommandPrimitive2.Empty, { className: "py-6 text-center text-sm text-muted-foreground", children: emptyMessage }),
840
- options.map((option) => /* @__PURE__ */ jsxs15(
841
- CommandPrimitive2.Item,
842
- {
843
- value: option.label,
844
- disabled: option.disabled,
845
- onSelect: () => toggleValue(option.value),
846
- 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",
847
- children: [
848
- /* @__PURE__ */ jsx17(
849
- Check2,
850
- {
851
- className: cn(
852
- "h-4 w-4 shrink-0",
853
- value.includes(option.value) ? "opacity-100" : "opacity-0"
854
- )
855
- }
856
- ),
857
- /* @__PURE__ */ jsx17("span", { className: "truncate", children: option.label })
858
- ]
859
- },
860
- option.value
861
- ))
862
- ] })
863
- ] });
936
+ const command = /* @__PURE__ */ jsxs15(
937
+ CommandPrimitive2,
938
+ {
939
+ shouldFilter: !isServer,
940
+ className: "flex h-full w-full flex-col overflow-hidden",
941
+ children: [
942
+ /* @__PURE__ */ jsxs15("div", { className: "flex items-center border-b px-3", children: [
943
+ /* @__PURE__ */ jsx17(Search2, { className: "mr-2 h-4 w-4 shrink-0 opacity-50" }),
944
+ /* @__PURE__ */ jsx17(
945
+ CommandPrimitive2.Input,
946
+ {
947
+ placeholder: searchPlaceholder,
948
+ onValueChange: isServer ? onSearchChange : void 0,
949
+ className: "flex h-11 w-full bg-transparent py-2 text-base outline-none placeholder:text-muted-foreground sm:h-9 sm:text-sm"
950
+ }
951
+ )
952
+ ] }),
953
+ /* @__PURE__ */ jsxs15(
954
+ CommandPrimitive2.List,
955
+ {
956
+ ref: listRef,
957
+ className: "max-h-[50vh] overflow-y-auto overflow-x-hidden p-1 sm:max-h-60",
958
+ children: [
959
+ !isServer && /* @__PURE__ */ jsx17(CommandPrimitive2.Empty, { className: "py-6 text-center text-sm text-muted-foreground", children: emptyMessage }),
960
+ options.map((option) => /* @__PURE__ */ jsxs15(
961
+ CommandPrimitive2.Item,
962
+ {
963
+ value: isServer ? option.value : option.label,
964
+ disabled: option.disabled,
965
+ onSelect: () => toggleValue(option.value),
966
+ 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",
967
+ children: [
968
+ /* @__PURE__ */ jsx17(
969
+ Check2,
970
+ {
971
+ className: cn(
972
+ "h-4 w-4 shrink-0",
973
+ value.includes(option.value) ? "opacity-100" : "opacity-0"
974
+ )
975
+ }
976
+ ),
977
+ /* @__PURE__ */ jsx17("span", { className: "truncate", children: option.label })
978
+ ]
979
+ },
980
+ option.value
981
+ )),
982
+ isServer && !loading && options.length === 0 && /* @__PURE__ */ jsx17("div", { className: "py-6 text-center text-sm text-muted-foreground", children: emptyMessage }),
983
+ isServer && loading && /* @__PURE__ */ jsx17("div", { className: "flex items-center justify-center py-6", children: /* @__PURE__ */ jsx17(Loader22, { className: "h-4 w-4 animate-spin text-muted-foreground" }) }),
984
+ isServer && hasMore && /* @__PURE__ */ jsx17("div", { ref: sentinelRef, "aria-hidden": true, className: "h-1" })
985
+ ]
986
+ }
987
+ )
988
+ ]
989
+ }
990
+ );
864
991
  if (isDesktop) {
865
992
  return /* @__PURE__ */ jsxs15(PopoverPrimitive2.Root, { open, onOpenChange: setOpen, children: [
866
993
  /* @__PURE__ */ jsx17(PopoverPrimitive2.Trigger, { asChild: true, disabled, children: trigger }),
@@ -892,7 +1019,7 @@ function MultiCombobox({
892
1019
  // src/components/color-picker.tsx
893
1020
  import * as PopoverPrimitive3 from "@radix-ui/react-popover";
894
1021
  import { ChevronsUpDown as ChevronsUpDown3 } from "lucide-react";
895
- import * as React7 from "react";
1022
+ import * as React8 from "react";
896
1023
  import { HexColorInput, HexColorPicker } from "react-colorful";
897
1024
  import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
898
1025
  function ColorPicker({
@@ -905,7 +1032,7 @@ function ColorPicker({
905
1032
  "aria-invalid": ariaInvalid,
906
1033
  "aria-describedby": ariaDescribedby
907
1034
  }) {
908
- const [open, setOpen] = React7.useState(false);
1035
+ const [open, setOpen] = React8.useState(false);
909
1036
  const color = value || "";
910
1037
  return /* @__PURE__ */ jsxs16(PopoverPrimitive3.Root, { open, onOpenChange: setOpen, modal: true, children: [
911
1038
  /* @__PURE__ */ jsx18(PopoverPrimitive3.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs16(
@@ -1041,7 +1168,7 @@ function DataTableWrapper({
1041
1168
  }
1042
1169
 
1043
1170
  // src/components/mobile-card-list.tsx
1044
- import { useRef } from "react";
1171
+ import { useRef as useRef2 } from "react";
1045
1172
  import { jsx as jsx21, jsxs as jsxs19 } from "react/jsx-runtime";
1046
1173
  var SCROLL_THRESHOLD = 8;
1047
1174
  function MobileCardList({
@@ -1052,8 +1179,8 @@ function MobileCardList({
1052
1179
  loadingCount = 5,
1053
1180
  className
1054
1181
  }) {
1055
- const start = useRef(null);
1056
- const scrolled = useRef(false);
1182
+ const start = useRef2(null);
1183
+ const scrolled = useRef2(false);
1057
1184
  const onTouchStart = (e) => {
1058
1185
  const t = e.touches[0];
1059
1186
  start.current = { x: t.clientX, y: t.clientY };