@dimaan/ui 0.0.27 → 0.0.28

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.cjs CHANGED
@@ -602,6 +602,7 @@ function SidebarHeader({ className, children, ...props }) {
602
602
  function SidebarNav({ className, children, ...props }) {
603
603
  return /* @__PURE__ */ jsxRuntime.jsx("nav", { className: cn("flex flex-1 flex-col gap-1 overflow-y-auto p-2", className), ...props, children });
604
604
  }
605
+ var SidebarNavGroupContext = react.createContext(null);
605
606
  function SidebarNavGroup({
606
607
  icon,
607
608
  label,
@@ -627,19 +628,39 @@ function SidebarNavGroup({
627
628
  },
628
629
  [isControlled, onOpenChange]
629
630
  );
631
+ const [activeChildIds, setActiveChildIds] = react.useState(() => /* @__PURE__ */ new Set());
632
+ const reportActive = react.useCallback((id, isItemActive) => {
633
+ setActiveChildIds((prev) => {
634
+ if (isItemActive === prev.has(id)) return prev;
635
+ const next = new Set(prev);
636
+ if (isItemActive) next.add(id);
637
+ else next.delete(id);
638
+ return next;
639
+ });
640
+ }, []);
641
+ const contextValue = react.useMemo(() => ({ reportActive }), [reportActive]);
642
+ const hasActiveChild = activeChildIds.size > 0;
643
+ const isActive = active || hasActiveChild;
644
+ const prevHasActiveChild = react.useRef(false);
645
+ react.useEffect(() => {
646
+ if (hasActiveChild && !prevHasActiveChild.current && !collapsed) {
647
+ setOpen(true);
648
+ }
649
+ prevHasActiveChild.current = hasActiveChild;
650
+ }, [hasActiveChild, collapsed, setOpen]);
630
651
  react.useEffect(() => {
631
652
  if (collapsed && open) setOpen(false);
632
653
  }, [collapsed, open, setOpen]);
633
654
  const titleAttr = collapsed && typeof label === "string" ? label : props.title ?? void 0;
634
655
  const showChildren = !collapsed;
635
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
656
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 flex-col", children: [
636
657
  /* @__PURE__ */ jsxRuntime.jsxs(
637
658
  "button",
638
659
  {
639
660
  type: "button",
640
661
  "aria-expanded": showChildren ? open : void 0,
641
662
  "aria-controls": showChildren ? submenuId : void 0,
642
- "data-active": active ? "true" : void 0,
663
+ "data-active": isActive ? "true" : void 0,
643
664
  title: titleAttr,
644
665
  onClick: (e) => {
645
666
  if (showChildren) setOpen(!open);
@@ -649,12 +670,19 @@ function SidebarNavGroup({
649
670
  "group relative flex h-9 w-full items-center gap-3 rounded-md px-3 text-sm font-medium outline-none transition-colors",
650
671
  "text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
651
672
  "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-sidebar",
652
- active && "bg-sidebar-accent text-sidebar-accent-foreground",
673
+ isActive && "bg-sidebar-accent text-sidebar-accent-foreground",
653
674
  collapsed && "justify-center px-0",
654
675
  className
655
676
  ),
656
677
  ...props,
657
678
  children: [
679
+ isActive ? /* @__PURE__ */ jsxRuntime.jsx(
680
+ "span",
681
+ {
682
+ "aria-hidden": "true",
683
+ className: "absolute inset-y-1.5 start-0 w-1 rounded-full bg-primary"
684
+ }
685
+ ) : null,
658
686
  icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: "flex h-5 w-5 shrink-0 items-center justify-center", children: icon }) : null,
659
687
  /* @__PURE__ */ jsxRuntime.jsx(
660
688
  "span",
@@ -680,7 +708,7 @@ function SidebarNavGroup({
680
708
  "grid transition-[grid-template-rows] duration-200 ease-out",
681
709
  showChildren && open ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
682
710
  ),
683
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-0.5 ps-7 pt-1", children }) })
711
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-0.5 ps-7 pt-1", children: /* @__PURE__ */ jsxRuntime.jsx(SidebarNavGroupContext.Provider, { value: contextValue, children }) }) })
684
712
  }
685
713
  )
686
714
  ] });
@@ -710,13 +738,20 @@ function SidebarNavItem({
710
738
  ...props
711
739
  }) {
712
740
  const { collapsed } = useDashboardLayout();
713
- const location = reactRouterDom.useLocation();
714
741
  const resolved = reactRouterDom.useResolvedPath(to);
715
- const isActive = forcedActive || (end ? location.pathname === resolved.pathname : location.pathname.startsWith(resolved.pathname));
742
+ const group = react.useContext(SidebarNavGroupContext);
743
+ const itemId = react.useId();
744
+ const routeMatch = reactRouterDom.useMatch({ path: resolved.pathname, end: end ?? false });
745
+ const isActive = forcedActive || routeMatch != null;
746
+ react.useEffect(() => {
747
+ if (!group) return;
748
+ group.reportActive(itemId, isActive);
749
+ return () => group.reportActive(itemId, false);
750
+ }, [group, itemId, isActive]);
716
751
  const labelContent = label ?? children;
717
752
  const titleAttr = collapsed && typeof labelContent === "string" ? labelContent : props.title;
718
753
  const getClassName = (active) => cn(
719
- "group relative flex h-9 items-center gap-3 rounded-md px-3 text-sm font-medium outline-none transition-colors",
754
+ "group relative flex h-9 shrink-0 items-center gap-3 rounded-md px-3 text-sm font-medium outline-none transition-colors",
720
755
  "text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
721
756
  "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-sidebar",
722
757
  active && "bg-sidebar-accent text-sidebar-accent-foreground",
@@ -724,6 +759,13 @@ function SidebarNavItem({
724
759
  className
725
760
  );
726
761
  const innerContent = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
762
+ isActive ? /* @__PURE__ */ jsxRuntime.jsx(
763
+ "span",
764
+ {
765
+ "aria-hidden": "true",
766
+ className: "absolute inset-y-1.5 start-0 w-1 rounded-full bg-primary"
767
+ }
768
+ ) : null,
727
769
  icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: "flex h-5 w-5 shrink-0 items-center justify-center", children: icon }) : null,
728
770
  /* @__PURE__ */ jsxRuntime.jsx(
729
771
  "span",
@@ -2621,6 +2663,7 @@ var selectItemClass = "relative flex w-full cursor-pointer select-none items-cen
2621
2663
  var selectItemIndicatorClass = "absolute start-2 inline-flex h-3.5 w-3.5 items-center justify-center [&_svg]:h-3.5 [&_svg]:w-3.5";
2622
2664
  var selectGroupLabelClass = "px-2 py-1.5 text-xs font-semibold text-muted-foreground";
2623
2665
  var selectSeparatorClass = "-mx-1 my-1 h-px bg-border";
2666
+ var selectStatusClass = "flex items-center justify-center gap-2 px-2 py-6 text-center text-sm text-muted-foreground";
2624
2667
 
2625
2668
  // src/components/multi-select/multiSelectVariants.ts
2626
2669
  var multiSelectTriggerSizeClass = {
@@ -2638,11 +2681,13 @@ var multiSelectOptionClass = "flex w-full cursor-pointer select-none items-cente
2638
2681
  var multiSelectEmptyClass = "px-2 py-6 text-center text-sm text-muted-foreground";
2639
2682
  var DEFAULT_LABELS_LTR4 = {
2640
2683
  search: "Search\u2026",
2641
- empty: "No results"
2684
+ empty: "No results",
2685
+ loading: "Loading\u2026"
2642
2686
  };
2643
2687
  var DEFAULT_LABELS_RTL4 = {
2644
2688
  search: "\u0628\u062D\u062B\u2026",
2645
- empty: "\u0644\u0627 \u0646\u062A\u0627\u0626\u062C"
2689
+ empty: "\u0644\u0627 \u0646\u062A\u0627\u0626\u062C",
2690
+ loading: "\u062C\u0627\u0631\u064D \u0627\u0644\u062A\u062D\u0645\u064A\u0644\u2026"
2646
2691
  };
2647
2692
  function toArray(value) {
2648
2693
  return Array.isArray(value) ? value : [];
@@ -2652,6 +2697,7 @@ var MultiSelect = react.forwardRef(function MultiSelect2({
2652
2697
  selectSize = "md",
2653
2698
  options,
2654
2699
  placeholder,
2700
+ loading = false,
2655
2701
  value,
2656
2702
  defaultValue,
2657
2703
  onValueChange,
@@ -2732,7 +2778,10 @@ var MultiSelect = react.forwardRef(function MultiSelect2({
2732
2778
  className
2733
2779
  ),
2734
2780
  children: [
2735
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: multiSelectValueRowClass, children: selected.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-muted-foreground", children: placeholder }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2781
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: multiSelectValueRowClass, children: loading ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2 text-muted-foreground", children: [
2782
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { "aria-hidden": "true", className: "size-4 shrink-0 animate-spin" }),
2783
+ labels.loading
2784
+ ] }) : selected.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-muted-foreground", children: placeholder }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2736
2785
  shownValues.map((v) => /* @__PURE__ */ jsxRuntime.jsxs(Badge, { variant: "default", size: "sm", className: multiSelectChipClass, children: [
2737
2786
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: labelByValue.get(v) ?? v }),
2738
2787
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -2767,7 +2816,7 @@ var MultiSelect = react.forwardRef(function MultiSelect2({
2767
2816
  ]
2768
2817
  }
2769
2818
  ) }),
2770
- /* @__PURE__ */ jsxRuntime.jsx(RadixPopover__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs(
2819
+ /* @__PURE__ */ jsxRuntime.jsx(RadixPopover__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
2771
2820
  RadixPopover__namespace.Content,
2772
2821
  {
2773
2822
  align: "start",
@@ -2777,7 +2826,10 @@ var MultiSelect = react.forwardRef(function MultiSelect2({
2777
2826
  onOpenAutoFocus: (event) => {
2778
2827
  if (!searchable) event.preventDefault();
2779
2828
  },
2780
- children: [
2829
+ children: loading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: multiSelectEmptyClass, children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center justify-center gap-2", children: [
2830
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { "aria-hidden": "true", className: "size-4 shrink-0 animate-spin" }),
2831
+ labels.loading
2832
+ ] }) }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2781
2833
  searchable ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: multiSelectSearchRowClass, children: /* @__PURE__ */ jsxRuntime.jsx(
2782
2834
  Input,
2783
2835
  {
@@ -2817,7 +2869,7 @@ var MultiSelect = react.forwardRef(function MultiSelect2({
2817
2869
  option.value
2818
2870
  );
2819
2871
  }) })
2820
- ]
2872
+ ] })
2821
2873
  }
2822
2874
  ) })
2823
2875
  ] });
@@ -2831,6 +2883,9 @@ var Select = react.forwardRef(function Select2({
2831
2883
  selectSize = "md",
2832
2884
  options,
2833
2885
  placeholder,
2886
+ loading = false,
2887
+ loadingText = "Loading\u2026",
2888
+ emptyText = "No options",
2834
2889
  value,
2835
2890
  defaultValue,
2836
2891
  onValueChange,
@@ -2889,7 +2944,7 @@ var Select = react.forwardRef(function Select2({
2889
2944
  className
2890
2945
  ),
2891
2946
  children: [
2892
- /* @__PURE__ */ jsxRuntime.jsx(RadixSelect__namespace.Value, { placeholder }),
2947
+ /* @__PURE__ */ jsxRuntime.jsx(RadixSelect__namespace.Value, { placeholder, children: loading ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex items-center gap-2 text-muted-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(SelectLoading, { text: loadingText }) }) : void 0 }),
2893
2948
  /* @__PURE__ */ jsxRuntime.jsx(RadixSelect__namespace.Icon, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "pointer-events-none absolute end-3 top-1/2 size-4 shrink-0 -translate-y-1/2 text-muted-foreground" }) })
2894
2949
  ]
2895
2950
  }
@@ -2903,7 +2958,7 @@ var Select = react.forwardRef(function Select2({
2903
2958
  className: selectContentClass,
2904
2959
  children: [
2905
2960
  /* @__PURE__ */ jsxRuntime.jsx(RadixSelect__namespace.ScrollUpButton, { className: "flex h-6 cursor-default items-center justify-center bg-popover text-muted-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronUp, { className: "size-4" }) }),
2906
- /* @__PURE__ */ jsxRuntime.jsx(RadixSelect__namespace.Viewport, { className: selectViewportClass, children: children ?? (options ? renderOptions(options) : null) }),
2961
+ /* @__PURE__ */ jsxRuntime.jsx(RadixSelect__namespace.Viewport, { className: selectViewportClass, children: children ?? renderViewportContent({ loading, options, loadingText, emptyText }) }),
2907
2962
  /* @__PURE__ */ jsxRuntime.jsx(RadixSelect__namespace.ScrollDownButton, { className: "flex h-6 cursor-default items-center justify-center bg-popover text-muted-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "size-4" }) })
2908
2963
  ]
2909
2964
  }
@@ -2912,6 +2967,26 @@ var Select = react.forwardRef(function Select2({
2912
2967
  }
2913
2968
  );
2914
2969
  });
2970
+ function SelectLoading({ text }) {
2971
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2972
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { "aria-hidden": "true", className: "size-4 shrink-0 animate-spin" }),
2973
+ text
2974
+ ] });
2975
+ }
2976
+ function renderViewportContent({
2977
+ loading,
2978
+ options,
2979
+ loadingText,
2980
+ emptyText
2981
+ }) {
2982
+ if (loading) {
2983
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: selectStatusClass, role: "presentation", children: /* @__PURE__ */ jsxRuntime.jsx(SelectLoading, { text: loadingText }) });
2984
+ }
2985
+ if (!options || options.length === 0) {
2986
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: selectStatusClass, role: "presentation", children: emptyText });
2987
+ }
2988
+ return renderOptions(options);
2989
+ }
2915
2990
  function renderOptions(options) {
2916
2991
  if (isGroupedOptions(options)) {
2917
2992
  const lastIndex = options.length - 1;
@@ -2952,6 +3027,7 @@ function DebouncedFilterInput({
2952
3027
  value,
2953
3028
  onChange,
2954
3029
  debounceMs,
3030
+ id,
2955
3031
  ariaLabel,
2956
3032
  placeholder,
2957
3033
  wrapperClassName,
@@ -2989,6 +3065,7 @@ function DebouncedFilterInput({
2989
3065
  return /* @__PURE__ */ jsxRuntime.jsx(
2990
3066
  Input,
2991
3067
  {
3068
+ id,
2992
3069
  type: "search",
2993
3070
  "aria-label": ariaLabel,
2994
3071
  placeholder,
@@ -3073,6 +3150,16 @@ function ListPageFilterBar({
3073
3150
  }
3074
3151
  function FilterControl({ filter, value, onChange, disabled, mode }) {
3075
3152
  const spanClass = FILTER_SPAN_CLASS[filter.width ?? "default"];
3153
+ const label = filter.label ?? filter.key;
3154
+ return /* @__PURE__ */ jsxRuntime.jsx(Field, { label, className: spanClass, children: renderFilterControl({ filter, value, onChange, disabled, mode }) });
3155
+ }
3156
+ function renderFilterControl({
3157
+ filter,
3158
+ value,
3159
+ onChange,
3160
+ disabled,
3161
+ mode
3162
+ }) {
3076
3163
  const ariaLabel = typeof filter.label === "string" ? filter.label : filter.key;
3077
3164
  switch (filter.type) {
3078
3165
  case "select":
@@ -3083,7 +3170,6 @@ function FilterControl({ filter, value, onChange, disabled, mode }) {
3083
3170
  value: value ?? filterDefaultValue(filter),
3084
3171
  onValueChange: (v) => onChange?.(filter.key, v),
3085
3172
  options: filter.options,
3086
- className: spanClass,
3087
3173
  disabled
3088
3174
  }
3089
3175
  );
@@ -3096,7 +3182,6 @@ function FilterControl({ filter, value, onChange, disabled, mode }) {
3096
3182
  debounceMs: mode === "live" ? filter.debounceMs ?? DEFAULT_TEXT_DEBOUNCE_MS : 0,
3097
3183
  ariaLabel,
3098
3184
  placeholder: filter.placeholder,
3099
- wrapperClassName: spanClass,
3100
3185
  disabled
3101
3186
  }
3102
3187
  );
@@ -3108,7 +3193,6 @@ function FilterControl({ filter, value, onChange, disabled, mode }) {
3108
3193
  placeholder: filter.placeholder,
3109
3194
  value: value ?? "",
3110
3195
  onValueChange: (v) => onChange?.(filter.key, v),
3111
- className: spanClass,
3112
3196
  disabled
3113
3197
  }
3114
3198
  );
@@ -3121,7 +3205,6 @@ function FilterControl({ filter, value, onChange, disabled, mode }) {
3121
3205
  options: filter.options,
3122
3206
  value: value ? value.split(",").filter(Boolean) : [],
3123
3207
  onValueChange: (values) => onChange?.(filter.key, values.join(",")),
3124
- className: spanClass,
3125
3208
  disabled
3126
3209
  }
3127
3210
  );