@dimaan/ui 0.0.26 → 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.d.cts CHANGED
@@ -213,6 +213,23 @@ interface SidebarNavGroupProps extends Omit<ButtonHTMLAttributes<HTMLButtonEleme
213
213
  onOpenChange?: (open: boolean) => void;
214
214
  children: ReactNode;
215
215
  }
216
+ /**
217
+ * Collapsible group of nav links inside a `<SidebarNav>`.
218
+ *
219
+ * Route-aware out of the box: when one of its `<SidebarNavItem>` children
220
+ * matches the current route, the group auto-opens and highlights itself — no
221
+ * need to wire `active`/`open` by hand. It opens on navigation into a child but
222
+ * stays user-closable; pass `open`/`onOpenChange` for full control, or
223
+ * `defaultOpen` / `active` to override the initial/derived state.
224
+ *
225
+ * @example
226
+ * ```tsx
227
+ * <SidebarNavGroup label="Platform" icon={<Settings />}>
228
+ * <SidebarNavItem to="/platform/branches">Branches</SidebarNavItem>
229
+ * <SidebarNavItem to="/platform/warehouses">Warehouses</SidebarNavItem>
230
+ * </SidebarNavGroup>
231
+ * ```
232
+ */
216
233
  declare function SidebarNavGroup({ icon, label, endSlot, active, defaultOpen, open: openProp, onOpenChange, className, children, onClick, ...props }: SidebarNavGroupProps): react_jsx_runtime.JSX.Element;
217
234
 
218
235
  type SidebarNavItemRenderProps = {
@@ -1538,6 +1555,17 @@ interface SelectProps {
1538
1555
  options?: SelectOptions;
1539
1556
  /** Placeholder shown when no value is selected. */
1540
1557
  placeholder?: string;
1558
+ /**
1559
+ * Show a loading indicator — in the trigger (when a `value` is set but its
1560
+ * option hasn't arrived yet) and as a row inside the popup. Use it while the
1561
+ * `options` are still being fetched so the control reads as "loading" instead
1562
+ * of blank. Flip back to `false` once the data lands.
1563
+ */
1564
+ loading?: boolean;
1565
+ /** Message shown while `loading`. Defaults to `'Loading…'`. */
1566
+ loadingText?: ReactNode;
1567
+ /** Message shown when `options` is empty (and not loading). Defaults to `'No options'`. */
1568
+ emptyText?: ReactNode;
1541
1569
  /** Controlled value. */
1542
1570
  value?: string;
1543
1571
  /** Initial value for uncontrolled usage. */
@@ -1598,6 +1626,19 @@ interface SelectProps {
1598
1626
  * { label: 'Levant', options: [{ value: 'jo', label: 'Jordan' }] },
1599
1627
  * ]} />
1600
1628
  * ```
1629
+ *
1630
+ * @example Async options (edit page) — show loading, then the fetched value
1631
+ * ```tsx
1632
+ * <Select
1633
+ * options={categories} // [] until the request resolves
1634
+ * loading={isLoading} // spinner in the trigger + popup meanwhile
1635
+ * value={String(item.categoryId)} // ⚠️ value must be a string that matches an option's value
1636
+ * onValueChange={(v) => setCategoryId(Number(v))}
1637
+ * placeholder="Category"
1638
+ * loadingText="جارٍ التحميل…"
1639
+ * emptyText="لا توجد عناصر"
1640
+ * />
1641
+ * ```
1601
1642
  */
1602
1643
  declare const Select: react.ForwardRefExoticComponent<SelectProps & react.RefAttributes<HTMLButtonElement>>;
1603
1644
 
@@ -1811,6 +1852,8 @@ interface MultiSelectLabels {
1811
1852
  search?: string;
1812
1853
  /** Empty-state text when the search matches nothing. Default: `"No results"` / `"لا نتائج"`. */
1813
1854
  empty?: string;
1855
+ /** Loading text shown while `loading`. Default: `"Loading…"` / `"جارٍ التحميل…"`. */
1856
+ loading?: string;
1814
1857
  }
1815
1858
  interface MultiSelectProps {
1816
1859
  variant?: SelectVariant;
@@ -1820,6 +1863,12 @@ interface MultiSelectProps {
1820
1863
  options: SelectOption[];
1821
1864
  /** Placeholder shown when nothing is selected. */
1822
1865
  placeholder?: string;
1866
+ /**
1867
+ * Show a loading indicator — in the trigger and as a row inside the popup —
1868
+ * while the `options` are still being fetched. Flip back to `false` once the
1869
+ * data lands. The empty state (`labels.empty`) covers a resolved empty list.
1870
+ */
1871
+ loading?: boolean;
1823
1872
  /** Controlled value (array of selected option values). */
1824
1873
  value?: string[];
1825
1874
  /** Initial value for uncontrolled usage. */
@@ -1876,6 +1925,17 @@ interface MultiSelectProps {
1876
1925
  * aria-label="Roles filter"
1877
1926
  * />
1878
1927
  * ```
1928
+ *
1929
+ * @example Async options — show loading while fetching
1930
+ * ```tsx
1931
+ * <MultiSelect
1932
+ * options={roleOptions} // [] until the request resolves
1933
+ * loading={isLoading} // spinner in the trigger + popup meanwhile
1934
+ * value={roleIds.map(String)} // ⚠️ values must be strings matching option values
1935
+ * onValueChange={setRoleIds}
1936
+ * placeholder="Roles"
1937
+ * />
1938
+ * ```
1879
1939
  */
1880
1940
  declare const MultiSelect: react.ForwardRefExoticComponent<MultiSelectProps & react.RefAttributes<HTMLDivElement>>;
1881
1941
 
package/dist/index.d.ts CHANGED
@@ -213,6 +213,23 @@ interface SidebarNavGroupProps extends Omit<ButtonHTMLAttributes<HTMLButtonEleme
213
213
  onOpenChange?: (open: boolean) => void;
214
214
  children: ReactNode;
215
215
  }
216
+ /**
217
+ * Collapsible group of nav links inside a `<SidebarNav>`.
218
+ *
219
+ * Route-aware out of the box: when one of its `<SidebarNavItem>` children
220
+ * matches the current route, the group auto-opens and highlights itself — no
221
+ * need to wire `active`/`open` by hand. It opens on navigation into a child but
222
+ * stays user-closable; pass `open`/`onOpenChange` for full control, or
223
+ * `defaultOpen` / `active` to override the initial/derived state.
224
+ *
225
+ * @example
226
+ * ```tsx
227
+ * <SidebarNavGroup label="Platform" icon={<Settings />}>
228
+ * <SidebarNavItem to="/platform/branches">Branches</SidebarNavItem>
229
+ * <SidebarNavItem to="/platform/warehouses">Warehouses</SidebarNavItem>
230
+ * </SidebarNavGroup>
231
+ * ```
232
+ */
216
233
  declare function SidebarNavGroup({ icon, label, endSlot, active, defaultOpen, open: openProp, onOpenChange, className, children, onClick, ...props }: SidebarNavGroupProps): react_jsx_runtime.JSX.Element;
217
234
 
218
235
  type SidebarNavItemRenderProps = {
@@ -1538,6 +1555,17 @@ interface SelectProps {
1538
1555
  options?: SelectOptions;
1539
1556
  /** Placeholder shown when no value is selected. */
1540
1557
  placeholder?: string;
1558
+ /**
1559
+ * Show a loading indicator — in the trigger (when a `value` is set but its
1560
+ * option hasn't arrived yet) and as a row inside the popup. Use it while the
1561
+ * `options` are still being fetched so the control reads as "loading" instead
1562
+ * of blank. Flip back to `false` once the data lands.
1563
+ */
1564
+ loading?: boolean;
1565
+ /** Message shown while `loading`. Defaults to `'Loading…'`. */
1566
+ loadingText?: ReactNode;
1567
+ /** Message shown when `options` is empty (and not loading). Defaults to `'No options'`. */
1568
+ emptyText?: ReactNode;
1541
1569
  /** Controlled value. */
1542
1570
  value?: string;
1543
1571
  /** Initial value for uncontrolled usage. */
@@ -1598,6 +1626,19 @@ interface SelectProps {
1598
1626
  * { label: 'Levant', options: [{ value: 'jo', label: 'Jordan' }] },
1599
1627
  * ]} />
1600
1628
  * ```
1629
+ *
1630
+ * @example Async options (edit page) — show loading, then the fetched value
1631
+ * ```tsx
1632
+ * <Select
1633
+ * options={categories} // [] until the request resolves
1634
+ * loading={isLoading} // spinner in the trigger + popup meanwhile
1635
+ * value={String(item.categoryId)} // ⚠️ value must be a string that matches an option's value
1636
+ * onValueChange={(v) => setCategoryId(Number(v))}
1637
+ * placeholder="Category"
1638
+ * loadingText="جارٍ التحميل…"
1639
+ * emptyText="لا توجد عناصر"
1640
+ * />
1641
+ * ```
1601
1642
  */
1602
1643
  declare const Select: react.ForwardRefExoticComponent<SelectProps & react.RefAttributes<HTMLButtonElement>>;
1603
1644
 
@@ -1811,6 +1852,8 @@ interface MultiSelectLabels {
1811
1852
  search?: string;
1812
1853
  /** Empty-state text when the search matches nothing. Default: `"No results"` / `"لا نتائج"`. */
1813
1854
  empty?: string;
1855
+ /** Loading text shown while `loading`. Default: `"Loading…"` / `"جارٍ التحميل…"`. */
1856
+ loading?: string;
1814
1857
  }
1815
1858
  interface MultiSelectProps {
1816
1859
  variant?: SelectVariant;
@@ -1820,6 +1863,12 @@ interface MultiSelectProps {
1820
1863
  options: SelectOption[];
1821
1864
  /** Placeholder shown when nothing is selected. */
1822
1865
  placeholder?: string;
1866
+ /**
1867
+ * Show a loading indicator — in the trigger and as a row inside the popup —
1868
+ * while the `options` are still being fetched. Flip back to `false` once the
1869
+ * data lands. The empty state (`labels.empty`) covers a resolved empty list.
1870
+ */
1871
+ loading?: boolean;
1823
1872
  /** Controlled value (array of selected option values). */
1824
1873
  value?: string[];
1825
1874
  /** Initial value for uncontrolled usage. */
@@ -1876,6 +1925,17 @@ interface MultiSelectProps {
1876
1925
  * aria-label="Roles filter"
1877
1926
  * />
1878
1927
  * ```
1928
+ *
1929
+ * @example Async options — show loading while fetching
1930
+ * ```tsx
1931
+ * <MultiSelect
1932
+ * options={roleOptions} // [] until the request resolves
1933
+ * loading={isLoading} // spinner in the trigger + popup meanwhile
1934
+ * value={roleIds.map(String)} // ⚠️ values must be strings matching option values
1935
+ * onValueChange={setRoleIds}
1936
+ * placeholder="Roles"
1937
+ * />
1938
+ * ```
1879
1939
  */
1880
1940
  declare const MultiSelect: react.ForwardRefExoticComponent<MultiSelectProps & react.RefAttributes<HTMLDivElement>>;
1881
1941
 
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { twMerge } from 'tailwind-merge';
5
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
6
  import { Loader2, Check, Minus, Calendar, X, ChevronLeft, ChevronRight, Upload, File as File$1, ChevronDown, Search, ChevronUp, ArrowLeft, Menu, FileQuestion, ChevronsUpDown, Inbox, RefreshCw, SearchX, Trash2, Pencil, Eye } from 'lucide-react';
7
7
  import { DirectionProvider } from '@radix-ui/react-direction';
8
- import { Link, useLocation, useResolvedPath, useNavigate } from 'react-router-dom';
8
+ import { Link, useResolvedPath, useMatch, useNavigate } from 'react-router-dom';
9
9
  import * as RadixPopover from '@radix-ui/react-popover';
10
10
  import { DayPicker } from 'react-day-picker';
11
11
  import * as RadixDialog from '@radix-ui/react-dialog';
@@ -574,6 +574,7 @@ function SidebarHeader({ className, children, ...props }) {
574
574
  function SidebarNav({ className, children, ...props }) {
575
575
  return /* @__PURE__ */ jsx("nav", { className: cn("flex flex-1 flex-col gap-1 overflow-y-auto p-2", className), ...props, children });
576
576
  }
577
+ var SidebarNavGroupContext = createContext(null);
577
578
  function SidebarNavGroup({
578
579
  icon,
579
580
  label,
@@ -599,19 +600,39 @@ function SidebarNavGroup({
599
600
  },
600
601
  [isControlled, onOpenChange]
601
602
  );
603
+ const [activeChildIds, setActiveChildIds] = useState(() => /* @__PURE__ */ new Set());
604
+ const reportActive = useCallback((id, isItemActive) => {
605
+ setActiveChildIds((prev) => {
606
+ if (isItemActive === prev.has(id)) return prev;
607
+ const next = new Set(prev);
608
+ if (isItemActive) next.add(id);
609
+ else next.delete(id);
610
+ return next;
611
+ });
612
+ }, []);
613
+ const contextValue = useMemo(() => ({ reportActive }), [reportActive]);
614
+ const hasActiveChild = activeChildIds.size > 0;
615
+ const isActive = active || hasActiveChild;
616
+ const prevHasActiveChild = useRef(false);
617
+ useEffect(() => {
618
+ if (hasActiveChild && !prevHasActiveChild.current && !collapsed) {
619
+ setOpen(true);
620
+ }
621
+ prevHasActiveChild.current = hasActiveChild;
622
+ }, [hasActiveChild, collapsed, setOpen]);
602
623
  useEffect(() => {
603
624
  if (collapsed && open) setOpen(false);
604
625
  }, [collapsed, open, setOpen]);
605
626
  const titleAttr = collapsed && typeof label === "string" ? label : props.title ?? void 0;
606
627
  const showChildren = !collapsed;
607
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
628
+ return /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-col", children: [
608
629
  /* @__PURE__ */ jsxs(
609
630
  "button",
610
631
  {
611
632
  type: "button",
612
633
  "aria-expanded": showChildren ? open : void 0,
613
634
  "aria-controls": showChildren ? submenuId : void 0,
614
- "data-active": active ? "true" : void 0,
635
+ "data-active": isActive ? "true" : void 0,
615
636
  title: titleAttr,
616
637
  onClick: (e) => {
617
638
  if (showChildren) setOpen(!open);
@@ -621,12 +642,19 @@ function SidebarNavGroup({
621
642
  "group relative flex h-9 w-full items-center gap-3 rounded-md px-3 text-sm font-medium outline-none transition-colors",
622
643
  "text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
623
644
  "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-sidebar",
624
- active && "bg-sidebar-accent text-sidebar-accent-foreground",
645
+ isActive && "bg-sidebar-accent text-sidebar-accent-foreground",
625
646
  collapsed && "justify-center px-0",
626
647
  className
627
648
  ),
628
649
  ...props,
629
650
  children: [
651
+ isActive ? /* @__PURE__ */ jsx(
652
+ "span",
653
+ {
654
+ "aria-hidden": "true",
655
+ className: "absolute inset-y-1.5 start-0 w-1 rounded-full bg-primary"
656
+ }
657
+ ) : null,
630
658
  icon ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "flex h-5 w-5 shrink-0 items-center justify-center", children: icon }) : null,
631
659
  /* @__PURE__ */ jsx(
632
660
  "span",
@@ -652,7 +680,7 @@ function SidebarNavGroup({
652
680
  "grid transition-[grid-template-rows] duration-200 ease-out",
653
681
  showChildren && open ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
654
682
  ),
655
- children: /* @__PURE__ */ jsx("div", { className: "overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5 ps-7 pt-1", children }) })
683
+ children: /* @__PURE__ */ jsx("div", { className: "overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5 ps-7 pt-1", children: /* @__PURE__ */ jsx(SidebarNavGroupContext.Provider, { value: contextValue, children }) }) })
656
684
  }
657
685
  )
658
686
  ] });
@@ -682,13 +710,20 @@ function SidebarNavItem({
682
710
  ...props
683
711
  }) {
684
712
  const { collapsed } = useDashboardLayout();
685
- const location = useLocation();
686
713
  const resolved = useResolvedPath(to);
687
- const isActive = forcedActive || (end ? location.pathname === resolved.pathname : location.pathname.startsWith(resolved.pathname));
714
+ const group = useContext(SidebarNavGroupContext);
715
+ const itemId = useId();
716
+ const routeMatch = useMatch({ path: resolved.pathname, end: end ?? false });
717
+ const isActive = forcedActive || routeMatch != null;
718
+ useEffect(() => {
719
+ if (!group) return;
720
+ group.reportActive(itemId, isActive);
721
+ return () => group.reportActive(itemId, false);
722
+ }, [group, itemId, isActive]);
688
723
  const labelContent = label ?? children;
689
724
  const titleAttr = collapsed && typeof labelContent === "string" ? labelContent : props.title;
690
725
  const getClassName = (active) => cn(
691
- "group relative flex h-9 items-center gap-3 rounded-md px-3 text-sm font-medium outline-none transition-colors",
726
+ "group relative flex h-9 shrink-0 items-center gap-3 rounded-md px-3 text-sm font-medium outline-none transition-colors",
692
727
  "text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
693
728
  "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-sidebar",
694
729
  active && "bg-sidebar-accent text-sidebar-accent-foreground",
@@ -696,6 +731,13 @@ function SidebarNavItem({
696
731
  className
697
732
  );
698
733
  const innerContent = /* @__PURE__ */ jsxs(Fragment, { children: [
734
+ isActive ? /* @__PURE__ */ jsx(
735
+ "span",
736
+ {
737
+ "aria-hidden": "true",
738
+ className: "absolute inset-y-1.5 start-0 w-1 rounded-full bg-primary"
739
+ }
740
+ ) : null,
699
741
  icon ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "flex h-5 w-5 shrink-0 items-center justify-center", children: icon }) : null,
700
742
  /* @__PURE__ */ jsx(
701
743
  "span",
@@ -2593,6 +2635,7 @@ var selectItemClass = "relative flex w-full cursor-pointer select-none items-cen
2593
2635
  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";
2594
2636
  var selectGroupLabelClass = "px-2 py-1.5 text-xs font-semibold text-muted-foreground";
2595
2637
  var selectSeparatorClass = "-mx-1 my-1 h-px bg-border";
2638
+ var selectStatusClass = "flex items-center justify-center gap-2 px-2 py-6 text-center text-sm text-muted-foreground";
2596
2639
 
2597
2640
  // src/components/multi-select/multiSelectVariants.ts
2598
2641
  var multiSelectTriggerSizeClass = {
@@ -2610,11 +2653,13 @@ var multiSelectOptionClass = "flex w-full cursor-pointer select-none items-cente
2610
2653
  var multiSelectEmptyClass = "px-2 py-6 text-center text-sm text-muted-foreground";
2611
2654
  var DEFAULT_LABELS_LTR4 = {
2612
2655
  search: "Search\u2026",
2613
- empty: "No results"
2656
+ empty: "No results",
2657
+ loading: "Loading\u2026"
2614
2658
  };
2615
2659
  var DEFAULT_LABELS_RTL4 = {
2616
2660
  search: "\u0628\u062D\u062B\u2026",
2617
- empty: "\u0644\u0627 \u0646\u062A\u0627\u0626\u062C"
2661
+ empty: "\u0644\u0627 \u0646\u062A\u0627\u0626\u062C",
2662
+ loading: "\u062C\u0627\u0631\u064D \u0627\u0644\u062A\u062D\u0645\u064A\u0644\u2026"
2618
2663
  };
2619
2664
  function toArray(value) {
2620
2665
  return Array.isArray(value) ? value : [];
@@ -2624,6 +2669,7 @@ var MultiSelect = forwardRef(function MultiSelect2({
2624
2669
  selectSize = "md",
2625
2670
  options,
2626
2671
  placeholder,
2672
+ loading = false,
2627
2673
  value,
2628
2674
  defaultValue,
2629
2675
  onValueChange,
@@ -2704,7 +2750,10 @@ var MultiSelect = forwardRef(function MultiSelect2({
2704
2750
  className
2705
2751
  ),
2706
2752
  children: [
2707
- /* @__PURE__ */ jsx("span", { className: multiSelectValueRowClass, children: selected.length === 0 ? /* @__PURE__ */ jsx("span", { className: "truncate text-muted-foreground", children: placeholder }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2753
+ /* @__PURE__ */ jsx("span", { className: multiSelectValueRowClass, children: loading ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2 text-muted-foreground", children: [
2754
+ /* @__PURE__ */ jsx(Loader2, { "aria-hidden": "true", className: "size-4 shrink-0 animate-spin" }),
2755
+ labels.loading
2756
+ ] }) : selected.length === 0 ? /* @__PURE__ */ jsx("span", { className: "truncate text-muted-foreground", children: placeholder }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2708
2757
  shownValues.map((v) => /* @__PURE__ */ jsxs(Badge, { variant: "default", size: "sm", className: multiSelectChipClass, children: [
2709
2758
  /* @__PURE__ */ jsx("span", { className: "truncate", children: labelByValue.get(v) ?? v }),
2710
2759
  /* @__PURE__ */ jsx(
@@ -2739,7 +2788,7 @@ var MultiSelect = forwardRef(function MultiSelect2({
2739
2788
  ]
2740
2789
  }
2741
2790
  ) }),
2742
- /* @__PURE__ */ jsx(RadixPopover.Portal, { children: /* @__PURE__ */ jsxs(
2791
+ /* @__PURE__ */ jsx(RadixPopover.Portal, { children: /* @__PURE__ */ jsx(
2743
2792
  RadixPopover.Content,
2744
2793
  {
2745
2794
  align: "start",
@@ -2749,7 +2798,10 @@ var MultiSelect = forwardRef(function MultiSelect2({
2749
2798
  onOpenAutoFocus: (event) => {
2750
2799
  if (!searchable) event.preventDefault();
2751
2800
  },
2752
- children: [
2801
+ children: loading ? /* @__PURE__ */ jsx("div", { className: multiSelectEmptyClass, children: /* @__PURE__ */ jsxs("span", { className: "flex items-center justify-center gap-2", children: [
2802
+ /* @__PURE__ */ jsx(Loader2, { "aria-hidden": "true", className: "size-4 shrink-0 animate-spin" }),
2803
+ labels.loading
2804
+ ] }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2753
2805
  searchable ? /* @__PURE__ */ jsx("div", { className: multiSelectSearchRowClass, children: /* @__PURE__ */ jsx(
2754
2806
  Input,
2755
2807
  {
@@ -2789,7 +2841,7 @@ var MultiSelect = forwardRef(function MultiSelect2({
2789
2841
  option.value
2790
2842
  );
2791
2843
  }) })
2792
- ]
2844
+ ] })
2793
2845
  }
2794
2846
  ) })
2795
2847
  ] });
@@ -2803,6 +2855,9 @@ var Select = forwardRef(function Select2({
2803
2855
  selectSize = "md",
2804
2856
  options,
2805
2857
  placeholder,
2858
+ loading = false,
2859
+ loadingText = "Loading\u2026",
2860
+ emptyText = "No options",
2806
2861
  value,
2807
2862
  defaultValue,
2808
2863
  onValueChange,
@@ -2861,7 +2916,7 @@ var Select = forwardRef(function Select2({
2861
2916
  className
2862
2917
  ),
2863
2918
  children: [
2864
- /* @__PURE__ */ jsx(RadixSelect.Value, { placeholder }),
2919
+ /* @__PURE__ */ jsx(RadixSelect.Value, { placeholder, children: loading ? /* @__PURE__ */ jsx("span", { className: "flex items-center gap-2 text-muted-foreground", children: /* @__PURE__ */ jsx(SelectLoading, { text: loadingText }) }) : void 0 }),
2865
2920
  /* @__PURE__ */ jsx(RadixSelect.Icon, { asChild: true, children: /* @__PURE__ */ jsx(ChevronDown, { className: "pointer-events-none absolute end-3 top-1/2 size-4 shrink-0 -translate-y-1/2 text-muted-foreground" }) })
2866
2921
  ]
2867
2922
  }
@@ -2875,7 +2930,7 @@ var Select = forwardRef(function Select2({
2875
2930
  className: selectContentClass,
2876
2931
  children: [
2877
2932
  /* @__PURE__ */ jsx(RadixSelect.ScrollUpButton, { className: "flex h-6 cursor-default items-center justify-center bg-popover text-muted-foreground", children: /* @__PURE__ */ jsx(ChevronUp, { className: "size-4" }) }),
2878
- /* @__PURE__ */ jsx(RadixSelect.Viewport, { className: selectViewportClass, children: children ?? (options ? renderOptions(options) : null) }),
2933
+ /* @__PURE__ */ jsx(RadixSelect.Viewport, { className: selectViewportClass, children: children ?? renderViewportContent({ loading, options, loadingText, emptyText }) }),
2879
2934
  /* @__PURE__ */ jsx(RadixSelect.ScrollDownButton, { className: "flex h-6 cursor-default items-center justify-center bg-popover text-muted-foreground", children: /* @__PURE__ */ jsx(ChevronDown, { className: "size-4" }) })
2880
2935
  ]
2881
2936
  }
@@ -2884,6 +2939,26 @@ var Select = forwardRef(function Select2({
2884
2939
  }
2885
2940
  );
2886
2941
  });
2942
+ function SelectLoading({ text }) {
2943
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2944
+ /* @__PURE__ */ jsx(Loader2, { "aria-hidden": "true", className: "size-4 shrink-0 animate-spin" }),
2945
+ text
2946
+ ] });
2947
+ }
2948
+ function renderViewportContent({
2949
+ loading,
2950
+ options,
2951
+ loadingText,
2952
+ emptyText
2953
+ }) {
2954
+ if (loading) {
2955
+ return /* @__PURE__ */ jsx("div", { className: selectStatusClass, role: "presentation", children: /* @__PURE__ */ jsx(SelectLoading, { text: loadingText }) });
2956
+ }
2957
+ if (!options || options.length === 0) {
2958
+ return /* @__PURE__ */ jsx("div", { className: selectStatusClass, role: "presentation", children: emptyText });
2959
+ }
2960
+ return renderOptions(options);
2961
+ }
2887
2962
  function renderOptions(options) {
2888
2963
  if (isGroupedOptions(options)) {
2889
2964
  const lastIndex = options.length - 1;
@@ -2924,6 +2999,7 @@ function DebouncedFilterInput({
2924
2999
  value,
2925
3000
  onChange,
2926
3001
  debounceMs,
3002
+ id,
2927
3003
  ariaLabel,
2928
3004
  placeholder,
2929
3005
  wrapperClassName,
@@ -2961,6 +3037,7 @@ function DebouncedFilterInput({
2961
3037
  return /* @__PURE__ */ jsx(
2962
3038
  Input,
2963
3039
  {
3040
+ id,
2964
3041
  type: "search",
2965
3042
  "aria-label": ariaLabel,
2966
3043
  placeholder,
@@ -3045,6 +3122,16 @@ function ListPageFilterBar({
3045
3122
  }
3046
3123
  function FilterControl({ filter, value, onChange, disabled, mode }) {
3047
3124
  const spanClass = FILTER_SPAN_CLASS[filter.width ?? "default"];
3125
+ const label = filter.label ?? filter.key;
3126
+ return /* @__PURE__ */ jsx(Field, { label, className: spanClass, children: renderFilterControl({ filter, value, onChange, disabled, mode }) });
3127
+ }
3128
+ function renderFilterControl({
3129
+ filter,
3130
+ value,
3131
+ onChange,
3132
+ disabled,
3133
+ mode
3134
+ }) {
3048
3135
  const ariaLabel = typeof filter.label === "string" ? filter.label : filter.key;
3049
3136
  switch (filter.type) {
3050
3137
  case "select":
@@ -3055,7 +3142,6 @@ function FilterControl({ filter, value, onChange, disabled, mode }) {
3055
3142
  value: value ?? filterDefaultValue(filter),
3056
3143
  onValueChange: (v) => onChange?.(filter.key, v),
3057
3144
  options: filter.options,
3058
- className: spanClass,
3059
3145
  disabled
3060
3146
  }
3061
3147
  );
@@ -3068,7 +3154,6 @@ function FilterControl({ filter, value, onChange, disabled, mode }) {
3068
3154
  debounceMs: mode === "live" ? filter.debounceMs ?? DEFAULT_TEXT_DEBOUNCE_MS : 0,
3069
3155
  ariaLabel,
3070
3156
  placeholder: filter.placeholder,
3071
- wrapperClassName: spanClass,
3072
3157
  disabled
3073
3158
  }
3074
3159
  );
@@ -3080,7 +3165,6 @@ function FilterControl({ filter, value, onChange, disabled, mode }) {
3080
3165
  placeholder: filter.placeholder,
3081
3166
  value: value ?? "",
3082
3167
  onValueChange: (v) => onChange?.(filter.key, v),
3083
- className: spanClass,
3084
3168
  disabled
3085
3169
  }
3086
3170
  );
@@ -3093,7 +3177,6 @@ function FilterControl({ filter, value, onChange, disabled, mode }) {
3093
3177
  options: filter.options,
3094
3178
  value: value ? value.split(",").filter(Boolean) : [],
3095
3179
  onValueChange: (values) => onChange?.(filter.key, values.join(",")),
3096
- className: spanClass,
3097
3180
  disabled
3098
3181
  }
3099
3182
  );