@dimaan/ui 0.0.21 → 0.0.22

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
@@ -1375,15 +1375,20 @@ var detailPageBaseClass = "flex w-full flex-col gap-6";
1375
1375
  var detailPageBodyClass = "flex flex-col gap-6";
1376
1376
  var detailPageSkeletonRowClass = "h-5 w-full animate-pulse rounded-md bg-muted";
1377
1377
  var detailPageEmptyClass = "rounded-md border border-border bg-card";
1378
- var DEFAULT_LABELS = {
1378
+ var DEFAULT_LABELS_LTR = {
1379
+ back: "Back",
1379
1380
  notFoundTitle: "Not found",
1380
1381
  notFoundDescription: "The record you\u2019re looking for does not exist or has been removed."
1381
1382
  };
1383
+ var DEFAULT_LABELS_RTL = {
1384
+ back: "\u0631\u062C\u0648\u0639",
1385
+ notFoundTitle: "\u063A\u064A\u0631 \u0645\u0648\u062C\u0648\u062F",
1386
+ notFoundDescription: "\u0627\u0644\u0633\u062C\u0644 \u0627\u0644\u0630\u064A \u062A\u0628\u062D\u062B \u0639\u0646\u0647 \u063A\u064A\u0631 \u0645\u0648\u062C\u0648\u062F \u0623\u0648 \u062A\u0645 \u062D\u0630\u0641\u0647."
1387
+ };
1382
1388
  var DEFAULT_SKELETON_ROW_COUNT = 6;
1383
1389
  function DetailPage({
1384
1390
  title,
1385
1391
  description,
1386
- back,
1387
1392
  actions,
1388
1393
  bordered = true,
1389
1394
  isLoading = false,
@@ -1395,7 +1400,10 @@ function DetailPage({
1395
1400
  className,
1396
1401
  bodyClassName
1397
1402
  }) {
1398
- const labels = { ...DEFAULT_LABELS, ...labelsProp };
1403
+ const navigate = reactRouterDom.useNavigate();
1404
+ const dir = useDirection();
1405
+ const defaults = dir === "rtl" ? DEFAULT_LABELS_RTL : DEFAULT_LABELS_LTR;
1406
+ const labels = { ...defaults, ...labelsProp };
1399
1407
  return /* @__PURE__ */ jsxRuntime.jsxs(
1400
1408
  "div",
1401
1409
  {
@@ -1409,7 +1417,7 @@ function DetailPage({
1409
1417
  {
1410
1418
  title,
1411
1419
  description,
1412
- back,
1420
+ back: { label: labels.back, onClick: () => navigate(-1) },
1413
1421
  actions,
1414
1422
  bordered
1415
1423
  }
@@ -1723,13 +1731,13 @@ var formPageBodyClass = "flex-1";
1723
1731
  var formPageActionsBarClass = "sticky bottom-0 -mx-6 -mb-6 mt-6 flex items-center justify-end gap-2 border-t border-border bg-background/95 px-6 py-3 backdrop-blur supports-[backdrop-filter]:bg-background/80";
1724
1732
  var formPageSkeletonRowClass = "h-10 w-full animate-pulse rounded-md bg-muted";
1725
1733
  var DEFAULT_SKELETON_ROW_COUNT2 = 6;
1726
- var DEFAULT_LABELS_LTR = {
1734
+ var DEFAULT_LABELS_LTR2 = {
1727
1735
  back: "Back",
1728
1736
  cancel: "Cancel",
1729
1737
  save: "Save",
1730
1738
  saving: "Saving\u2026"
1731
1739
  };
1732
- var DEFAULT_LABELS_RTL = {
1740
+ var DEFAULT_LABELS_RTL2 = {
1733
1741
  back: "\u0631\u062C\u0648\u0639",
1734
1742
  cancel: "\u0625\u0644\u063A\u0627\u0621",
1735
1743
  save: "\u062D\u0641\u0638",
@@ -1756,7 +1764,7 @@ function FormPage({
1756
1764
  const dir = useDirection();
1757
1765
  const formContext = reactHookForm.useFormContext();
1758
1766
  const submitting = isSubmitting ?? formContext?.formState?.isSubmitting ?? false;
1759
- const defaults = dir === "rtl" ? DEFAULT_LABELS_RTL : DEFAULT_LABELS_LTR;
1767
+ const defaults = dir === "rtl" ? DEFAULT_LABELS_RTL2 : DEFAULT_LABELS_LTR2;
1760
1768
  const labels = { ...defaults, ...labelsProp };
1761
1769
  const goBack = () => navigate(-1);
1762
1770
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-slot": "form-page", className: cn(formPageBaseClass, className), children: [
@@ -1924,132 +1932,6 @@ function LanguageSwitcher({
1924
1932
  }
1925
1933
  );
1926
1934
  }
1927
-
1928
- // src/components/select/selectVariants.ts
1929
- var selectVariantClass = {
1930
- default: "border border-input bg-background hover:border-ring",
1931
- filled: "border border-transparent bg-muted hover:bg-muted/80",
1932
- ghost: "border border-transparent bg-transparent hover:bg-accent"
1933
- };
1934
- var selectSizeClass = {
1935
- sm: "h-8 rounded-md ps-2.5 pe-8 text-sm",
1936
- md: "h-9 rounded-md ps-3 pe-9 text-sm",
1937
- lg: "h-11 rounded-md ps-4 pe-10 text-base"
1938
- };
1939
- var selectBaseClass = "group/select relative inline-flex w-full items-center text-foreground outline-none transition-[background-color,border-color,box-shadow] focus:ring-2 focus:ring-ring/40 focus:ring-offset-1 focus:ring-offset-background aria-[invalid=true]:border-destructive aria-[invalid=true]:focus:ring-destructive/40 disabled:pointer-events-none disabled:opacity-50 cursor-pointer data-[placeholder]:text-muted-foreground";
1940
- var selectContentClass = "z-50 max-h-(--radix-select-content-available-height) min-w-(--radix-select-trigger-width) overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95";
1941
- var selectViewportClass = "p-1";
1942
- var selectItemClass = "relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 ps-8 pe-2 text-sm outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50";
1943
- 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";
1944
- var selectGroupLabelClass = "px-2 py-1.5 text-xs font-semibold text-muted-foreground";
1945
- var selectSeparatorClass = "-mx-1 my-1 h-px bg-border";
1946
- function isGroupedOptions(options) {
1947
- const first = options[0];
1948
- return first !== void 0 && "options" in first;
1949
- }
1950
- var Select = react.forwardRef(function Select2({
1951
- variant = "default",
1952
- selectSize = "md",
1953
- options,
1954
- placeholder,
1955
- value,
1956
- defaultValue,
1957
- onValueChange,
1958
- onChange,
1959
- onBlur,
1960
- name,
1961
- disabled,
1962
- required,
1963
- id,
1964
- className,
1965
- "aria-invalid": ariaInvalid,
1966
- "aria-describedby": ariaDescribedBy,
1967
- "aria-label": ariaLabel,
1968
- children
1969
- }, ref) {
1970
- const generatedId = react.useId();
1971
- const triggerId = id ?? generatedId;
1972
- const handleValueChange = react.useCallback(
1973
- (next) => {
1974
- onValueChange?.(next);
1975
- if (onChange) {
1976
- const synthetic = {
1977
- target: { value: next, name },
1978
- currentTarget: { value: next, name },
1979
- type: "change"
1980
- };
1981
- onChange(synthetic);
1982
- }
1983
- },
1984
- [onValueChange, onChange, name]
1985
- );
1986
- return /* @__PURE__ */ jsxRuntime.jsxs(
1987
- RadixSelect__namespace.Root,
1988
- {
1989
- value,
1990
- defaultValue,
1991
- onValueChange: handleValueChange,
1992
- disabled,
1993
- required,
1994
- name,
1995
- children: [
1996
- /* @__PURE__ */ jsxRuntime.jsxs(
1997
- RadixSelect__namespace.Trigger,
1998
- {
1999
- ref,
2000
- id: triggerId,
2001
- "aria-label": ariaLabel,
2002
- "aria-invalid": ariaInvalid,
2003
- "aria-describedby": ariaDescribedBy,
2004
- onBlur,
2005
- "data-slot": "select-trigger",
2006
- className: cn(
2007
- selectBaseClass,
2008
- selectVariantClass[variant],
2009
- selectSizeClass[selectSize],
2010
- className
2011
- ),
2012
- children: [
2013
- /* @__PURE__ */ jsxRuntime.jsx(RadixSelect__namespace.Value, { placeholder }),
2014
- /* @__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" }) })
2015
- ]
2016
- }
2017
- ),
2018
- /* @__PURE__ */ jsxRuntime.jsx(RadixSelect__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs(
2019
- RadixSelect__namespace.Content,
2020
- {
2021
- position: "popper",
2022
- sideOffset: 4,
2023
- "data-slot": "select-content",
2024
- className: selectContentClass,
2025
- children: [
2026
- /* @__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" }) }),
2027
- /* @__PURE__ */ jsxRuntime.jsx(RadixSelect__namespace.Viewport, { className: selectViewportClass, children: children ?? (options ? renderOptions(options) : null) }),
2028
- /* @__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" }) })
2029
- ]
2030
- }
2031
- ) })
2032
- ]
2033
- }
2034
- );
2035
- });
2036
- function renderOptions(options) {
2037
- if (isGroupedOptions(options)) {
2038
- const lastIndex = options.length - 1;
2039
- return options.map((group, idx) => /* @__PURE__ */ jsxRuntime.jsxs(RadixSelect__namespace.Group, { children: [
2040
- /* @__PURE__ */ jsxRuntime.jsx(RadixSelect__namespace.Label, { className: selectGroupLabelClass, children: group.label }),
2041
- group.options.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: opt.value, disabled: opt.disabled, children: opt.label }, opt.value)),
2042
- idx < lastIndex && /* @__PURE__ */ jsxRuntime.jsx(RadixSelect__namespace.Separator, { className: selectSeparatorClass })
2043
- ] }, group.label));
2044
- }
2045
- return options.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: opt.value, disabled: opt.disabled, children: opt.label }, opt.value));
2046
- }
2047
- var SelectItem = react.forwardRef(function SelectItem2({ className, children, ...props }, ref) {
2048
- return /* @__PURE__ */ jsxRuntime.jsxs(RadixSelect__namespace.Item, { ref, className: cn(selectItemClass, className), ...props, children: [
2049
- /* @__PURE__ */ jsxRuntime.jsx(RadixSelect__namespace.ItemIndicator, { className: selectItemIndicatorClass, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, {}) }),
2050
- /* @__PURE__ */ jsxRuntime.jsx(RadixSelect__namespace.ItemText, { children })
2051
- ] });
2052
- });
2053
1935
  var EN_LABELS = {
2054
1936
  rowsPerPage: "Rows per page",
2055
1937
  pageRangeOf: "of",
@@ -2173,17 +2055,17 @@ var tableSizeClass = {
2173
2055
  sm: {
2174
2056
  row: "",
2175
2057
  cell: "px-3 py-1.5 text-xs",
2176
- head: "px-3 py-2 text-xs font-medium"
2058
+ head: "whitespace-nowrap px-3 py-2 text-xs font-medium"
2177
2059
  },
2178
2060
  md: {
2179
2061
  row: "",
2180
2062
  cell: "px-4 py-2.5 text-sm",
2181
- head: "px-4 py-2.5 text-xs font-medium uppercase tracking-wide"
2063
+ head: "whitespace-nowrap px-4 py-2.5 text-xs font-medium uppercase tracking-wide"
2182
2064
  },
2183
2065
  lg: {
2184
2066
  row: "",
2185
2067
  cell: "px-5 py-3.5 text-sm",
2186
- head: "px-5 py-3 text-sm font-medium"
2068
+ head: "whitespace-nowrap px-5 py-3 text-sm font-medium"
2187
2069
  }
2188
2070
  };
2189
2071
  var tableBaseClass = "w-full caption-bottom border-collapse";
@@ -2501,20 +2383,446 @@ function SortIndicator({ active, direction }) {
2501
2383
  if (!active) return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronsUpDown, { "aria-hidden": "true", className });
2502
2384
  return direction === "asc" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronUp, { "aria-hidden": "true", className }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { "aria-hidden": "true", className });
2503
2385
  }
2504
- var DEFAULT_LABELS2 = {
2505
- searchPlaceholder: "Search\u2026",
2506
- searchAriaLabel: "Search",
2386
+
2387
+ // src/components/select/selectVariants.ts
2388
+ var selectVariantClass = {
2389
+ default: "border border-input bg-background hover:border-ring",
2390
+ filled: "border border-transparent bg-muted hover:bg-muted/80",
2391
+ ghost: "border border-transparent bg-transparent hover:bg-accent"
2392
+ };
2393
+ var selectSizeClass = {
2394
+ sm: "h-8 rounded-md ps-2.5 pe-8 text-sm",
2395
+ md: "h-9 rounded-md ps-3 pe-9 text-sm",
2396
+ lg: "h-11 rounded-md ps-4 pe-10 text-base"
2397
+ };
2398
+ var selectBaseClass = "group/select relative inline-flex w-full items-center text-foreground outline-none transition-[background-color,border-color,box-shadow] focus:ring-2 focus:ring-ring/40 focus:ring-offset-1 focus:ring-offset-background aria-[invalid=true]:border-destructive aria-[invalid=true]:focus:ring-destructive/40 disabled:pointer-events-none disabled:opacity-50 cursor-pointer data-[placeholder]:text-muted-foreground";
2399
+ var selectContentClass = "z-50 max-h-(--radix-select-content-available-height) min-w-(--radix-select-trigger-width) overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95";
2400
+ var selectViewportClass = "p-1";
2401
+ var selectItemClass = "relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 ps-8 pe-2 text-sm outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50";
2402
+ 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";
2403
+ var selectGroupLabelClass = "px-2 py-1.5 text-xs font-semibold text-muted-foreground";
2404
+ var selectSeparatorClass = "-mx-1 my-1 h-px bg-border";
2405
+
2406
+ // src/components/multi-select/multiSelectVariants.ts
2407
+ var multiSelectTriggerSizeClass = {
2408
+ sm: "min-h-8 rounded-md ps-2.5 pe-8 py-1 text-sm",
2409
+ md: "min-h-9 rounded-md ps-3 pe-9 py-1 text-sm",
2410
+ lg: "min-h-11 rounded-md ps-4 pe-10 py-1.5 text-base"
2411
+ };
2412
+ var multiSelectValueRowClass = "flex min-w-0 flex-1 flex-wrap items-center gap-1";
2413
+ var multiSelectChipClass = "max-w-full gap-1 pe-1";
2414
+ var multiSelectChipRemoveClass = "inline-flex size-3.5 shrink-0 items-center justify-center rounded-sm hover:bg-foreground/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40";
2415
+ var multiSelectContentClass = "z-50 w-(--radix-popover-trigger-width) overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95";
2416
+ var multiSelectSearchRowClass = "border-b border-border p-1";
2417
+ var multiSelectListClass = "max-h-60 overflow-y-auto overflow-x-hidden p-1";
2418
+ var multiSelectOptionClass = "flex w-full cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground has-[:focus-visible]:bg-accent has-[:disabled]:pointer-events-none has-[:disabled]:opacity-50";
2419
+ var multiSelectEmptyClass = "px-2 py-6 text-center text-sm text-muted-foreground";
2420
+ var DEFAULT_LABELS_LTR3 = {
2421
+ search: "Search\u2026",
2422
+ empty: "No results"
2423
+ };
2424
+ var DEFAULT_LABELS_RTL3 = {
2425
+ search: "\u0628\u062D\u062B\u2026",
2426
+ empty: "\u0644\u0627 \u0646\u062A\u0627\u0626\u062C"
2427
+ };
2428
+ function toArray(value) {
2429
+ return Array.isArray(value) ? value : [];
2430
+ }
2431
+ var MultiSelect = react.forwardRef(function MultiSelect2({
2432
+ variant = "default",
2433
+ selectSize = "md",
2434
+ options,
2435
+ placeholder,
2436
+ value,
2437
+ defaultValue,
2438
+ onValueChange,
2439
+ onChange,
2440
+ onBlur,
2441
+ searchable = true,
2442
+ maxTagCount,
2443
+ labels: labelsProp,
2444
+ name,
2445
+ disabled = false,
2446
+ required,
2447
+ id,
2448
+ className,
2449
+ contentClassName,
2450
+ "aria-invalid": ariaInvalid,
2451
+ "aria-describedby": ariaDescribedBy,
2452
+ "aria-label": ariaLabel
2453
+ }, ref) {
2454
+ const dir = useDirection();
2455
+ const labels = { ...dir === "rtl" ? DEFAULT_LABELS_RTL3 : DEFAULT_LABELS_LTR3, ...labelsProp };
2456
+ const generatedId = react.useId();
2457
+ const triggerId = id ?? generatedId;
2458
+ const isControlled = value !== void 0;
2459
+ const [internal, setInternal] = react.useState(() => toArray(defaultValue));
2460
+ const selected = isControlled ? toArray(value) : internal;
2461
+ const [open, setOpen] = react.useState(false);
2462
+ const [query, setQuery] = react.useState("");
2463
+ const labelByValue = react.useMemo(() => new Map(options.map((o) => [o.value, o.label])), [options]);
2464
+ const filtered = react.useMemo(() => {
2465
+ const q = query.trim().toLowerCase();
2466
+ if (!searchable || q === "") return options;
2467
+ return options.filter((o) => o.label.toLowerCase().includes(q));
2468
+ }, [options, query, searchable]);
2469
+ const emit = (next) => {
2470
+ if (!isControlled) setInternal(next);
2471
+ onValueChange?.(next);
2472
+ onChange?.(next);
2473
+ };
2474
+ const toggle = (optionValue) => {
2475
+ emit(
2476
+ selected.includes(optionValue) ? selected.filter((v) => v !== optionValue) : [...selected, optionValue]
2477
+ );
2478
+ };
2479
+ const remove = (optionValue) => emit(selected.filter((v) => v !== optionValue));
2480
+ const openOnKeys = (event) => {
2481
+ if (disabled) return;
2482
+ if (event.key === "Enter" || event.key === " " || event.key === "ArrowDown") {
2483
+ event.preventDefault();
2484
+ setOpen(true);
2485
+ }
2486
+ };
2487
+ const shownValues = maxTagCount !== void 0 ? selected.slice(0, maxTagCount) : selected;
2488
+ const overflowCount = selected.length - shownValues.length;
2489
+ return /* @__PURE__ */ jsxRuntime.jsxs(RadixPopover__namespace.Root, { open, onOpenChange: setOpen, children: [
2490
+ /* @__PURE__ */ jsxRuntime.jsx(RadixPopover__namespace.Anchor, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
2491
+ "div",
2492
+ {
2493
+ ref,
2494
+ id: triggerId,
2495
+ role: "button",
2496
+ tabIndex: disabled ? -1 : 0,
2497
+ "aria-haspopup": "listbox",
2498
+ "aria-expanded": open,
2499
+ "aria-disabled": disabled || void 0,
2500
+ "aria-label": ariaLabel,
2501
+ "aria-invalid": ariaInvalid,
2502
+ "aria-describedby": ariaDescribedBy,
2503
+ "data-slot": "multi-select-trigger",
2504
+ "data-state": open ? "open" : "closed",
2505
+ "data-placeholder": selected.length === 0 ? "" : void 0,
2506
+ onClick: () => !disabled && setOpen(true),
2507
+ onKeyDown: openOnKeys,
2508
+ onBlur,
2509
+ className: cn(
2510
+ selectBaseClass,
2511
+ selectVariantClass[variant],
2512
+ multiSelectTriggerSizeClass[selectSize],
2513
+ className
2514
+ ),
2515
+ children: [
2516
+ /* @__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: [
2517
+ shownValues.map((v) => /* @__PURE__ */ jsxRuntime.jsxs(Badge, { variant: "default", size: "sm", className: multiSelectChipClass, children: [
2518
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: labelByValue.get(v) ?? v }),
2519
+ /* @__PURE__ */ jsxRuntime.jsx(
2520
+ "button",
2521
+ {
2522
+ type: "button",
2523
+ tabIndex: -1,
2524
+ "aria-label": `Remove ${labelByValue.get(v) ?? v}`,
2525
+ "data-slot": "multi-select-chip-remove",
2526
+ className: multiSelectChipRemoveClass,
2527
+ onClick: (event) => {
2528
+ event.stopPropagation();
2529
+ if (!disabled) remove(v);
2530
+ },
2531
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { "aria-hidden": "true", className: "size-3" })
2532
+ }
2533
+ )
2534
+ ] }, v)),
2535
+ overflowCount > 0 ? /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "default", size: "sm", children: `+${overflowCount}` }) : null
2536
+ ] }) }),
2537
+ /* @__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" }),
2538
+ name ? /* @__PURE__ */ jsxRuntime.jsx(
2539
+ "input",
2540
+ {
2541
+ type: "hidden",
2542
+ name,
2543
+ value: selected.join(","),
2544
+ required,
2545
+ readOnly: true
2546
+ }
2547
+ ) : null
2548
+ ]
2549
+ }
2550
+ ) }),
2551
+ /* @__PURE__ */ jsxRuntime.jsx(RadixPopover__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs(
2552
+ RadixPopover__namespace.Content,
2553
+ {
2554
+ align: "start",
2555
+ sideOffset: 4,
2556
+ "data-slot": "multi-select-content",
2557
+ className: cn(multiSelectContentClass, contentClassName),
2558
+ onOpenAutoFocus: (event) => {
2559
+ if (!searchable) event.preventDefault();
2560
+ },
2561
+ children: [
2562
+ searchable ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: multiSelectSearchRowClass, children: /* @__PURE__ */ jsxRuntime.jsx(
2563
+ Input,
2564
+ {
2565
+ type: "search",
2566
+ inputSize: "sm",
2567
+ value: query,
2568
+ onChange: (e) => setQuery(e.target.value),
2569
+ placeholder: labels.search,
2570
+ "aria-label": labels.search,
2571
+ leadingIcon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "size-4" })
2572
+ }
2573
+ ) }) : null,
2574
+ filtered.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: multiSelectEmptyClass, children: labels.empty }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: multiSelectListClass, children: filtered.map((option) => {
2575
+ const isSelected = selected.includes(option.value);
2576
+ const optionId = `${triggerId}-opt-${option.value}`;
2577
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2578
+ "label",
2579
+ {
2580
+ htmlFor: optionId,
2581
+ "data-slot": "multi-select-option",
2582
+ "data-selected": isSelected ? "true" : void 0,
2583
+ className: multiSelectOptionClass,
2584
+ children: [
2585
+ /* @__PURE__ */ jsxRuntime.jsx(
2586
+ Checkbox,
2587
+ {
2588
+ id: optionId,
2589
+ size: "sm",
2590
+ checked: isSelected,
2591
+ disabled: option.disabled,
2592
+ onCheckedChange: () => toggle(option.value)
2593
+ }
2594
+ ),
2595
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: option.label })
2596
+ ]
2597
+ },
2598
+ option.value
2599
+ );
2600
+ }) })
2601
+ ]
2602
+ }
2603
+ ) })
2604
+ ] });
2605
+ });
2606
+ function isGroupedOptions(options) {
2607
+ const first = options[0];
2608
+ return first !== void 0 && "options" in first;
2609
+ }
2610
+ var Select = react.forwardRef(function Select2({
2611
+ variant = "default",
2612
+ selectSize = "md",
2613
+ options,
2614
+ placeholder,
2615
+ value,
2616
+ defaultValue,
2617
+ onValueChange,
2618
+ onChange,
2619
+ onBlur,
2620
+ name,
2621
+ disabled,
2622
+ required,
2623
+ id,
2624
+ className,
2625
+ "aria-invalid": ariaInvalid,
2626
+ "aria-describedby": ariaDescribedBy,
2627
+ "aria-label": ariaLabel,
2628
+ children
2629
+ }, ref) {
2630
+ const generatedId = react.useId();
2631
+ const triggerId = id ?? generatedId;
2632
+ const handleValueChange = react.useCallback(
2633
+ (next) => {
2634
+ onValueChange?.(next);
2635
+ if (onChange) {
2636
+ const synthetic = {
2637
+ target: { value: next, name },
2638
+ currentTarget: { value: next, name },
2639
+ type: "change"
2640
+ };
2641
+ onChange(synthetic);
2642
+ }
2643
+ },
2644
+ [onValueChange, onChange, name]
2645
+ );
2646
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2647
+ RadixSelect__namespace.Root,
2648
+ {
2649
+ value,
2650
+ defaultValue,
2651
+ onValueChange: handleValueChange,
2652
+ disabled,
2653
+ required,
2654
+ name,
2655
+ children: [
2656
+ /* @__PURE__ */ jsxRuntime.jsxs(
2657
+ RadixSelect__namespace.Trigger,
2658
+ {
2659
+ ref,
2660
+ id: triggerId,
2661
+ "aria-label": ariaLabel,
2662
+ "aria-invalid": ariaInvalid,
2663
+ "aria-describedby": ariaDescribedBy,
2664
+ onBlur,
2665
+ "data-slot": "select-trigger",
2666
+ className: cn(
2667
+ selectBaseClass,
2668
+ selectVariantClass[variant],
2669
+ selectSizeClass[selectSize],
2670
+ className
2671
+ ),
2672
+ children: [
2673
+ /* @__PURE__ */ jsxRuntime.jsx(RadixSelect__namespace.Value, { placeholder }),
2674
+ /* @__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" }) })
2675
+ ]
2676
+ }
2677
+ ),
2678
+ /* @__PURE__ */ jsxRuntime.jsx(RadixSelect__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs(
2679
+ RadixSelect__namespace.Content,
2680
+ {
2681
+ position: "popper",
2682
+ sideOffset: 4,
2683
+ "data-slot": "select-content",
2684
+ className: selectContentClass,
2685
+ children: [
2686
+ /* @__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" }) }),
2687
+ /* @__PURE__ */ jsxRuntime.jsx(RadixSelect__namespace.Viewport, { className: selectViewportClass, children: children ?? (options ? renderOptions(options) : null) }),
2688
+ /* @__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" }) })
2689
+ ]
2690
+ }
2691
+ ) })
2692
+ ]
2693
+ }
2694
+ );
2695
+ });
2696
+ function renderOptions(options) {
2697
+ if (isGroupedOptions(options)) {
2698
+ const lastIndex = options.length - 1;
2699
+ return options.map((group, idx) => /* @__PURE__ */ jsxRuntime.jsxs(RadixSelect__namespace.Group, { children: [
2700
+ /* @__PURE__ */ jsxRuntime.jsx(RadixSelect__namespace.Label, { className: selectGroupLabelClass, children: group.label }),
2701
+ group.options.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: opt.value, disabled: opt.disabled, children: opt.label }, opt.value)),
2702
+ idx < lastIndex && /* @__PURE__ */ jsxRuntime.jsx(RadixSelect__namespace.Separator, { className: selectSeparatorClass })
2703
+ ] }, group.label));
2704
+ }
2705
+ return options.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: opt.value, disabled: opt.disabled, children: opt.label }, opt.value));
2706
+ }
2707
+ var SelectItem = react.forwardRef(function SelectItem2({ className, children, ...props }, ref) {
2708
+ return /* @__PURE__ */ jsxRuntime.jsxs(RadixSelect__namespace.Item, { ref, className: cn(selectItemClass, className), ...props, children: [
2709
+ /* @__PURE__ */ jsxRuntime.jsx(RadixSelect__namespace.ItemIndicator, { className: selectItemIndicatorClass, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, {}) }),
2710
+ /* @__PURE__ */ jsxRuntime.jsx(RadixSelect__namespace.ItemText, { children })
2711
+ ] });
2712
+ });
2713
+
2714
+ // src/components/list-page/listPageFilters.ts
2715
+ var FILTER_SPAN_CLASS = {
2716
+ narrow: "",
2717
+ default: "",
2718
+ wide: "sm:col-span-2"
2719
+ };
2720
+ function filterDefaultValue(filter) {
2721
+ return filter.type === "select" ? filter.options[0]?.value ?? "" : "";
2722
+ }
2723
+ function hasActiveFilters(filters, values) {
2724
+ for (const filter of filters ?? []) {
2725
+ const current = values?.[filter.key];
2726
+ if (current === void 0) continue;
2727
+ const value = filter.type === "text" ? current.trim() : current;
2728
+ if (value !== filterDefaultValue(filter)) return true;
2729
+ }
2730
+ return false;
2731
+ }
2732
+ function ListPageFilterBar({
2733
+ filters,
2734
+ values,
2735
+ onChange,
2736
+ disabled = false,
2737
+ labels
2738
+ }) {
2739
+ const active = hasActiveFilters(filters, values);
2740
+ const reset = () => {
2741
+ for (const filter of filters ?? []) {
2742
+ onChange?.(filter.key, filterDefaultValue(filter));
2743
+ }
2744
+ };
2745
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-slot": "list-page-filter-bar", className: "space-y-3", children: [
2746
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3", children: filters?.map((filter) => /* @__PURE__ */ jsxRuntime.jsx(
2747
+ FilterControl,
2748
+ {
2749
+ filter,
2750
+ value: values?.[filter.key],
2751
+ onChange,
2752
+ disabled
2753
+ },
2754
+ filter.key
2755
+ )) }),
2756
+ active && !disabled ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "ghost", size: "sm", onClick: reset, children: [
2757
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "size-4" }),
2758
+ labels.reset
2759
+ ] }) }) : null
2760
+ ] });
2761
+ }
2762
+ function FilterControl({ filter, value, onChange, disabled }) {
2763
+ const spanClass = FILTER_SPAN_CLASS[filter.width ?? "default"];
2764
+ const ariaLabel = typeof filter.label === "string" ? filter.label : filter.key;
2765
+ switch (filter.type) {
2766
+ case "select":
2767
+ return /* @__PURE__ */ jsxRuntime.jsx(
2768
+ Select,
2769
+ {
2770
+ "aria-label": ariaLabel,
2771
+ value: value ?? filterDefaultValue(filter),
2772
+ onValueChange: (v) => onChange?.(filter.key, v),
2773
+ options: filter.options,
2774
+ className: spanClass,
2775
+ disabled
2776
+ }
2777
+ );
2778
+ case "text":
2779
+ return /* @__PURE__ */ jsxRuntime.jsx(
2780
+ Input,
2781
+ {
2782
+ type: "search",
2783
+ "aria-label": ariaLabel,
2784
+ placeholder: filter.placeholder,
2785
+ value: value ?? "",
2786
+ onChange: (e) => onChange?.(filter.key, e.target.value),
2787
+ leadingIcon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "size-4" }),
2788
+ wrapperClassName: spanClass,
2789
+ disabled
2790
+ }
2791
+ );
2792
+ case "date":
2793
+ return /* @__PURE__ */ jsxRuntime.jsx(
2794
+ DatePicker,
2795
+ {
2796
+ "aria-label": ariaLabel,
2797
+ placeholder: filter.placeholder,
2798
+ value: value ?? "",
2799
+ onValueChange: (v) => onChange?.(filter.key, v),
2800
+ className: spanClass,
2801
+ disabled
2802
+ }
2803
+ );
2804
+ case "multiselect":
2805
+ return /* @__PURE__ */ jsxRuntime.jsx(
2806
+ MultiSelect,
2807
+ {
2808
+ "aria-label": ariaLabel,
2809
+ placeholder: filter.placeholder,
2810
+ options: filter.options,
2811
+ value: value ? value.split(",").filter(Boolean) : [],
2812
+ onValueChange: (values) => onChange?.(filter.key, values.join(",")),
2813
+ className: spanClass,
2814
+ disabled
2815
+ }
2816
+ );
2817
+ }
2818
+ }
2819
+ var DEFAULT_LABELS = {
2507
2820
  reset: "Reset filters",
2508
2821
  emptyTitle: "No results",
2509
2822
  emptyDescription: "Try clearing the search or adjusting the filters.",
2510
2823
  noDataTitle: "No data yet",
2511
2824
  noDataDescription: "Nothing has been added here so far."
2512
2825
  };
2513
- var FILTER_WIDTH_CLASS = {
2514
- narrow: "w-32",
2515
- default: "w-44",
2516
- wide: "w-56"
2517
- };
2518
2826
  function ListPage({
2519
2827
  title,
2520
2828
  description,
@@ -2525,8 +2833,6 @@ function ListPage({
2525
2833
  getRowId,
2526
2834
  isLoading = false,
2527
2835
  loadingRowCount,
2528
- searchValue,
2529
- onSearchChange,
2530
2836
  filters,
2531
2837
  filterValues,
2532
2838
  onFilterChange,
@@ -2541,59 +2847,30 @@ function ListPage({
2541
2847
  labels: labelsProp,
2542
2848
  className
2543
2849
  }) {
2544
- const labels = { ...DEFAULT_LABELS2, ...labelsProp };
2545
- const showSearch = onSearchChange !== void 0;
2546
- const showFilterBar = showSearch || Boolean(filters?.length);
2547
- const hasActiveQuery = react.useMemo(() => {
2548
- if ((searchValue ?? "").trim() !== "") return true;
2549
- for (const f of filters ?? []) {
2550
- const current = filterValues?.[f.key];
2551
- const def = f.options[0]?.value ?? "";
2552
- if (current !== void 0 && current !== def) return true;
2553
- }
2554
- return false;
2555
- }, [searchValue, filters, filterValues]);
2850
+ const labels = { ...DEFAULT_LABELS, ...labelsProp };
2851
+ const showFilterBar = Boolean(filters?.length);
2852
+ const hasActiveQuery = react.useMemo(
2853
+ () => hasActiveFilters(filters, filterValues),
2854
+ [filters, filterValues]
2855
+ );
2556
2856
  const reset = () => {
2557
- onSearchChange?.("");
2558
2857
  for (const f of filters ?? []) {
2559
- const def = f.options[0]?.value ?? "";
2560
- onFilterChange?.(f.key, def);
2858
+ onFilterChange?.(f.key, filterDefaultValue(f));
2561
2859
  }
2562
2860
  };
2563
2861
  const tableMode = isLoading ? "loading" : data.length === 0 && !hasActiveQuery ? "no-data" : data.length === 0 && hasActiveQuery ? "no-results" : "rows";
2564
2862
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-slot": "list-page", className: cn("space-y-6", className), children: [
2565
2863
  /* @__PURE__ */ jsxRuntime.jsx(PageHeader, { title, description, bordered, actions }),
2566
- showFilterBar ? /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-slot": "list-page-filter-bar", className: "flex flex-wrap items-center gap-3", children: [
2567
- showSearch ? /* @__PURE__ */ jsxRuntime.jsx(
2568
- Input,
2569
- {
2570
- type: "search",
2571
- placeholder: labels.searchPlaceholder,
2572
- "aria-label": labels.searchAriaLabel || labels.searchPlaceholder,
2573
- value: searchValue ?? "",
2574
- onChange: (e) => onSearchChange?.(e.target.value),
2575
- leadingIcon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, {}),
2576
- wrapperClassName: "sm:max-w-xs",
2577
- disabled: isLoading
2578
- }
2579
- ) : null,
2580
- filters?.map((f) => /* @__PURE__ */ jsxRuntime.jsx(
2581
- Select,
2582
- {
2583
- "aria-label": typeof f.label === "string" ? f.label : f.key,
2584
- value: filterValues?.[f.key] ?? f.options[0]?.value ?? "",
2585
- onValueChange: (v) => onFilterChange?.(f.key, v),
2586
- options: f.options,
2587
- className: FILTER_WIDTH_CLASS[f.width ?? "default"],
2588
- disabled: isLoading
2589
- },
2590
- f.key
2591
- )),
2592
- hasActiveQuery && !isLoading ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "ghost", onClick: reset, children: [
2593
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, {}),
2594
- labels.reset
2595
- ] }) : null
2596
- ] }) : null,
2864
+ showFilterBar ? /* @__PURE__ */ jsxRuntime.jsx(
2865
+ ListPageFilterBar,
2866
+ {
2867
+ filters,
2868
+ values: filterValues,
2869
+ onChange: onFilterChange,
2870
+ disabled: isLoading,
2871
+ labels: { reset: labels.reset }
2872
+ }
2873
+ ) : null,
2597
2874
  tableMode === "loading" || tableMode === "rows" ? /* @__PURE__ */ jsxRuntime.jsx(
2598
2875
  Table,
2599
2876
  {
@@ -3070,6 +3347,7 @@ exports.HeaderTitle = HeaderTitle;
3070
3347
  exports.Input = Input;
3071
3348
  exports.LanguageSwitcher = LanguageSwitcher;
3072
3349
  exports.ListPage = ListPage;
3350
+ exports.MultiSelect = MultiSelect;
3073
3351
  exports.PageHeader = PageHeader;
3074
3352
  exports.RadioGroup = RadioGroup;
3075
3353
  exports.RadioGroupItem = RadioGroupItem;
@@ -3149,6 +3427,15 @@ exports.formPageSkeletonRowClass = formPageSkeletonRowClass;
3149
3427
  exports.inputBaseClass = inputBaseClass;
3150
3428
  exports.inputSizeClass = inputSizeClass;
3151
3429
  exports.inputVariantClass = inputVariantClass;
3430
+ exports.multiSelectChipClass = multiSelectChipClass;
3431
+ exports.multiSelectChipRemoveClass = multiSelectChipRemoveClass;
3432
+ exports.multiSelectContentClass = multiSelectContentClass;
3433
+ exports.multiSelectEmptyClass = multiSelectEmptyClass;
3434
+ exports.multiSelectListClass = multiSelectListClass;
3435
+ exports.multiSelectOptionClass = multiSelectOptionClass;
3436
+ exports.multiSelectSearchRowClass = multiSelectSearchRowClass;
3437
+ exports.multiSelectTriggerSizeClass = multiSelectTriggerSizeClass;
3438
+ exports.multiSelectValueRowClass = multiSelectValueRowClass;
3152
3439
  exports.pageHeaderActionsClass = pageHeaderActionsClass;
3153
3440
  exports.pageHeaderBackClass = pageHeaderBackClass;
3154
3441
  exports.pageHeaderBackIconClass = pageHeaderBackIconClass;