periplo-ui 4.9.2 → 4.10.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.
Files changed (57) hide show
  1. package/dist/components/Badge/Badge.d.ts +22 -66
  2. package/dist/components/Badge/Badge.js +15 -88
  3. package/dist/components/Badge/Badge.js.map +1 -1
  4. package/dist/components/Combobox/Combobox.d.ts +2 -0
  5. package/dist/components/Combobox/Combobox.js +2 -1
  6. package/dist/components/Combobox/Combobox.js.map +1 -1
  7. package/dist/components/Combobox/VirtualizedComboboxList.d.ts +1 -1
  8. package/dist/components/Combobox/VirtualizedComboboxList.js +7 -6
  9. package/dist/components/Combobox/VirtualizedComboboxList.js.map +1 -1
  10. package/dist/components/Combobox/types.d.ts +1 -0
  11. package/dist/components/DataTable/DataTable.js +31 -23
  12. package/dist/components/DataTable/DataTable.js.map +1 -1
  13. package/dist/components/DataTable/components/DataTableBody.js +2 -2
  14. package/dist/components/DataTable/components/DataTableBody.js.map +1 -1
  15. package/dist/components/DataTable/components/DataTableToolbar.js +2 -2
  16. package/dist/components/DataTable/components/DataTableToolbar.js.map +1 -1
  17. package/dist/components/Form/Form.d.ts +1 -1
  18. package/dist/components/NotificationBadge/NotificationBadge.d.ts +84 -0
  19. package/dist/components/NotificationBadge/NotificationBadge.js +85 -0
  20. package/dist/components/NotificationBadge/NotificationBadge.js.map +1 -0
  21. package/dist/components/NotificationBadge/index.d.ts +1 -0
  22. package/dist/components/NotificationBadge/index.js +2 -0
  23. package/dist/components/NotificationBadge/index.js.map +1 -0
  24. package/dist/components/PhoneInput/PhoneInput.d.ts +24 -40
  25. package/dist/components/PhoneInput/PhoneInput.js +139 -116
  26. package/dist/components/PhoneInput/PhoneInput.js.map +1 -1
  27. package/dist/components/PhoneInput/hooks/index.d.ts +1 -0
  28. package/dist/components/PhoneInput/hooks/index.js +2 -0
  29. package/dist/components/PhoneInput/hooks/index.js.map +1 -0
  30. package/dist/components/PhoneInput/hooks/usePhoneField.d.ts +22 -0
  31. package/dist/components/PhoneInput/hooks/usePhoneField.js +152 -0
  32. package/dist/components/PhoneInput/hooks/usePhoneField.js.map +1 -0
  33. package/dist/components/PhoneInput/index.d.ts +2 -1
  34. package/dist/components/PhoneInput/index.js +1 -1
  35. package/dist/components/PhoneInput/phone.d.ts +47 -0
  36. package/dist/components/PhoneInput/phone.js +129 -0
  37. package/dist/components/PhoneInput/phone.js.map +1 -0
  38. package/dist/components/PhoneInput/types.d.ts +25 -0
  39. package/dist/components/PhoneInput/types.js +2 -0
  40. package/dist/components/PhoneInput/types.js.map +1 -0
  41. package/dist/components/RadioGroup/RadioGroup.d.ts +23 -1
  42. package/dist/components/RadioGroup/RadioGroup.js +63 -15
  43. package/dist/components/RadioGroup/RadioGroup.js.map +1 -1
  44. package/dist/components/Tooltip/Tooltip.d.ts +8 -2
  45. package/dist/components/Tooltip/Tooltip.js +18 -6
  46. package/dist/components/Tooltip/Tooltip.js.map +1 -1
  47. package/dist/components/Typography/Typography.d.ts +2 -2
  48. package/dist/index.d.ts +1 -0
  49. package/dist/index.js +2 -1
  50. package/dist/index.js.map +1 -1
  51. package/dist/lib/useTouchDevice.d.ts +1 -0
  52. package/dist/lib/useTouchDevice.js +18 -0
  53. package/dist/lib/useTouchDevice.js.map +1 -0
  54. package/package.json +6 -1
  55. package/dist/components/PhoneInput/countries.d.ts +0 -16
  56. package/dist/components/PhoneInput/countries.js +0 -254
  57. package/dist/components/PhoneInput/countries.js.map +0 -1
@@ -1,90 +1,46 @@
1
- import { VariantProps } from 'class-variance-authority';
2
1
  import * as React from 'react';
3
2
  declare const badgeVariants: (props?: ({
4
- color?: "primary" | "accent" | "success" | "warning" | "error" | "neutral" | null | undefined;
5
- size?: "sm" | "md" | "lg" | null | undefined;
6
- position?: "top-right" | "top-left" | "bottom-right" | "bottom-left" | null | undefined;
7
- numeric?: boolean | null | undefined;
3
+ color?: "accent" | "success" | "warning" | "error" | "neutral" | "info" | null | undefined;
4
+ size?: "big" | "small" | "medium" | null | undefined;
8
5
  } & import('class-variance-authority/types').ClassProp) | undefined) => string;
9
6
  export type BadgeProps = {
10
7
  /**
11
- * Controls the visibility of the pulsing animation effect
12
- * @default true
8
+ * The color of the badge.
9
+ * @default 'neutral'
13
10
  */
14
- ping?: boolean;
11
+ color?: 'neutral' | 'accent' | 'info' | 'success' | 'warning' | 'error';
15
12
  /**
16
- * The color variant of the badge
17
- * @default 'accent'
13
+ * The size of the badge.
14
+ * @default 'medium'
18
15
  */
19
- color?: 'neutral' | 'accent' | 'primary' | 'success' | 'warning' | 'error';
16
+ size?: 'small' | 'medium' | 'big';
20
17
  /**
21
- * The size of the badge
22
- * @default 'md'
18
+ * The label to display inside the badge.
23
19
  */
24
- size?: 'sm' | 'md' | 'lg';
25
- /**
26
- * The position of the badge relative to its container
27
- * @default 'top-right'
28
- */
29
- position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';
30
- /**
31
- * The number to display in the badge)
32
- */
33
- count?: number;
34
- } & React.HTMLAttributes<HTMLElement> & VariantProps<typeof badgeVariants>;
20
+ children?: React.ReactNode;
21
+ } & React.HTMLAttributes<HTMLElement>;
35
22
  /**
36
- * A status indicator component that can be used to show notifications,
37
- * status, or draw attention to an element.
23
+ * An inline status label used to represent a state (e.g. "Active", "Pending").
38
24
  *
39
25
  * @example
40
26
  * ```tsx
41
- * // Simple notification badge
42
- * <div className="relative">
43
- * <Bell className="h-6 w-6" />
44
- * <Badge intent="error" />
45
- * </div>
46
- *
47
- * // Custom size and position
48
- * <div className="relative">
49
- * <Avatar />
50
- * <Badge
51
- * intent="success"
52
- * size="sm"
53
- * position="bottom-right"
54
- * ping={false}
55
- * />
56
- * </div>
27
+ * <Badge color="success" size="small">Active</Badge>
57
28
  * ```
58
29
  */
59
30
  declare const Badge: React.ForwardRefExoticComponent<{
60
31
  /**
61
- * Controls the visibility of the pulsing animation effect
62
- * @default true
63
- */
64
- ping?: boolean;
65
- /**
66
- * The color variant of the badge
67
- * @default 'accent'
68
- */
69
- color?: "neutral" | "accent" | "primary" | "success" | "warning" | "error";
70
- /**
71
- * The size of the badge
72
- * @default 'md'
32
+ * The color of the badge.
33
+ * @default 'neutral'
73
34
  */
74
- size?: "sm" | "md" | "lg";
35
+ color?: "neutral" | "accent" | "info" | "success" | "warning" | "error";
75
36
  /**
76
- * The position of the badge relative to its container
77
- * @default 'top-right'
37
+ * The size of the badge.
38
+ * @default 'medium'
78
39
  */
79
- position?: "top-right" | "top-left" | "bottom-right" | "bottom-left";
40
+ size?: "small" | "medium" | "big";
80
41
  /**
81
- * The number to display in the badge)
42
+ * The label to display inside the badge.
82
43
  */
83
- count?: number;
84
- } & React.HTMLAttributes<HTMLElement> & VariantProps<(props?: ({
85
- color?: "primary" | "accent" | "success" | "warning" | "error" | "neutral" | null | undefined;
86
- size?: "sm" | "md" | "lg" | null | undefined;
87
- position?: "top-right" | "top-left" | "bottom-right" | "bottom-left" | null | undefined;
88
- numeric?: boolean | null | undefined;
89
- } & import('class-variance-authority/types').ClassProp) | undefined) => string> & React.RefAttributes<HTMLSpanElement>>;
44
+ children?: React.ReactNode;
45
+ } & React.HTMLAttributes<HTMLElement> & React.RefAttributes<HTMLSpanElement>>;
90
46
  export { Badge, badgeVariants };
@@ -1,105 +1,32 @@
1
- import { jsxs, jsx } from 'react/jsx-runtime';
1
+ import { jsx } from 'react/jsx-runtime';
2
2
  import { cva } from 'class-variance-authority';
3
3
  import * as React from 'react';
4
4
  import { cn } from '../../lib/utils.js';
5
5
 
6
- const badgeVariants = cva("rounded-full", {
6
+ const badgeVariants = cva("inline-flex items-center rounded-sm border border-transparent font-medium", {
7
7
  variants: {
8
- /**
9
- * Controls the color scheme of the badge
10
- * @default 'accent'
11
- */
12
8
  color: {
13
- /** Default gray appearance */
14
- neutral: "bg-neutral text-neutral-foreground",
15
- /** Accent brand color */
16
- accent: "bg-accent text-accent-foreground",
17
- /** Primary brand color */
18
- primary: "bg-primary text-primary-foreground",
19
- /** Indicates successful or positive state */
20
- success: "bg-success text-success-foreground ",
21
- /** Indicates warning or cautionary state */
22
- warning: "bg-warning text-warning-foreground",
23
- /** Indicates error or critical state */
24
- error: "bg-error text-error-foreground"
9
+ neutral: "bg-neutral-50 text-neutral-950",
10
+ accent: "bg-accent-100 text-neutral-950",
11
+ info: "bg-information-100 text-information-800",
12
+ success: "bg-success-100 text-success-800",
13
+ warning: "bg-background-300 text-accent-900",
14
+ error: "bg-error-100 text-error-800"
25
15
  },
26
- /**
27
- * Controls the size of the badge
28
- * @default 'md'
29
- */
30
16
  size: {
31
- /** Small size - 8px */
32
- sm: "h-2 w-2",
33
- /** Medium size - 12px */
34
- md: "h-3 w-3",
35
- /** Large size - 16px */
36
- lg: "h-4 w-4"
37
- },
38
- /**
39
- * Controls the position of the badge relative to its container
40
- * @default 'top-right'
41
- */
42
- position: {
43
- /** Places the badge in the top-right corner */
44
- "top-right": "right-0 top-0",
45
- /** Places the badge in the top-left corner */
46
- "top-left": "left-0 top-0",
47
- /** Places the badge in the bottom-right corner */
48
- "bottom-right": "right-0 bottom-0",
49
- /** Places the badge in the bottom-left corner */
50
- "bottom-left": "left-0 bottom-0"
51
- },
52
- /**
53
- * Controls whether the badge displays a number
54
- * @default false
55
- */
56
- numeric: {
57
- true: "min-w-[16px] h-4 px-1 flex items-center justify-center text-[10px] font-medium",
58
- false: ""
17
+ small: "px-2 py-0.5 text-xs",
18
+ medium: "px-2.5 py-0.5 text-sm",
19
+ big: "px-3 py-1 text-base"
59
20
  }
60
21
  },
61
22
  defaultVariants: {
62
- color: "primary",
63
- size: "md",
64
- position: "top-right",
65
- numeric: false
23
+ color: "neutral",
24
+ size: "medium"
66
25
  }
67
26
  });
68
27
  const Badge = React.forwardRef(
69
- ({ className, color = "accent", size = "md", position = "top-right", ping = true, count, ...props }, ref) => {
70
- const sizeClasses = {
71
- sm: "h-2 w-2",
72
- md: "h-3 w-3",
73
- lg: "h-4 w-4"
74
- }[size || "md"];
75
- return /* @__PURE__ */ jsxs(
76
- "span",
77
- {
78
- className: cn(
79
- "absolute flex",
80
- badgeVariants({ color, position, numeric: !!count }),
81
- !count && sizeClasses,
82
- className
83
- ),
84
- ref,
85
- ...props,
86
- children: [
87
- ping && /* @__PURE__ */ jsx(
88
- "span",
89
- {
90
- className: cn(badgeVariants({ color }), "absolute inline-flex h-full w-full animate-ping opacity-75")
91
- }
92
- ),
93
- /* @__PURE__ */ jsx(
94
- "span",
95
- {
96
- className: cn(badgeVariants({ color }), "relative inline-flex h-full w-full items-center justify-center"),
97
- children: count
98
- }
99
- )
100
- ]
101
- }
102
- );
28
+ ({ className, color = "neutral", size = "medium", children, ...props }, ref) => {
29
+ return /* @__PURE__ */ jsx("span", { className: cn(badgeVariants({ color, size }), className), ref, ...props, children });
103
30
  }
104
31
  );
105
32
  Badge.displayName = "Badge";
@@ -1 +1 @@
1
- {"version":3,"file":"Badge.js","sources":["../../../src/components/Badge/Badge.tsx"],"sourcesContent":["import { cva, type VariantProps } from 'class-variance-authority'\nimport * as React from 'react'\n\nimport { cn } from '../../lib/utils'\n\nconst badgeVariants = cva('rounded-full', {\n variants: {\n /**\n * Controls the color scheme of the badge\n * @default 'accent'\n */\n color: {\n /** Default gray appearance */\n neutral: 'bg-neutral text-neutral-foreground',\n /** Accent brand color */\n accent: 'bg-accent text-accent-foreground',\n /** Primary brand color */\n primary: 'bg-primary text-primary-foreground',\n /** Indicates successful or positive state */\n success: 'bg-success text-success-foreground ',\n /** Indicates warning or cautionary state */\n warning: 'bg-warning text-warning-foreground',\n /** Indicates error or critical state */\n error: 'bg-error text-error-foreground',\n },\n /**\n * Controls the size of the badge\n * @default 'md'\n */\n size: {\n /** Small size - 8px */\n sm: 'h-2 w-2',\n /** Medium size - 12px */\n md: 'h-3 w-3',\n /** Large size - 16px */\n lg: 'h-4 w-4',\n },\n /**\n * Controls the position of the badge relative to its container\n * @default 'top-right'\n */\n position: {\n /** Places the badge in the top-right corner */\n 'top-right': 'right-0 top-0',\n /** Places the badge in the top-left corner */\n 'top-left': 'left-0 top-0',\n /** Places the badge in the bottom-right corner */\n 'bottom-right': 'right-0 bottom-0',\n /** Places the badge in the bottom-left corner */\n 'bottom-left': 'left-0 bottom-0',\n },\n /**\n * Controls whether the badge displays a number\n * @default false\n */\n numeric: {\n true: 'min-w-[16px] h-4 px-1 flex items-center justify-center text-[10px] font-medium',\n false: '',\n },\n },\n defaultVariants: {\n color: 'primary',\n size: 'md',\n position: 'top-right',\n numeric: false,\n },\n})\n\nexport type BadgeProps = {\n /**\n * Controls the visibility of the pulsing animation effect\n * @default true\n */\n ping?: boolean\n\n /**\n * The color variant of the badge\n * @default 'accent'\n */\n color?: 'neutral' | 'accent' | 'primary' | 'success' | 'warning' | 'error'\n\n /**\n * The size of the badge\n * @default 'md'\n */\n size?: 'sm' | 'md' | 'lg'\n\n /**\n * The position of the badge relative to its container\n * @default 'top-right'\n */\n position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'\n\n /**\n * The number to display in the badge)\n */\n count?: number\n} & React.HTMLAttributes<HTMLElement> &\n VariantProps<typeof badgeVariants>\n\n/**\n * A status indicator component that can be used to show notifications,\n * status, or draw attention to an element.\n *\n * @example\n * ```tsx\n * // Simple notification badge\n * <div className=\"relative\">\n * <Bell className=\"h-6 w-6\" />\n * <Badge intent=\"error\" />\n * </div>\n *\n * // Custom size and position\n * <div className=\"relative\">\n * <Avatar />\n * <Badge\n * intent=\"success\"\n * size=\"sm\"\n * position=\"bottom-right\"\n * ping={false}\n * />\n * </div>\n * ```\n */\nconst Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(\n ({ className, color = 'accent', size = 'md', position = 'top-right', ping = true, count, ...props }, ref) => {\n const sizeClasses = {\n sm: 'h-2 w-2',\n md: 'h-3 w-3',\n lg: 'h-4 w-4',\n }[size || 'md']\n\n return (\n <span\n className={cn(\n 'absolute flex',\n badgeVariants({ color, position, numeric: !!count }),\n !count && sizeClasses,\n className,\n )}\n ref={ref}\n {...props}\n >\n {ping && (\n <span\n className={cn(badgeVariants({ color }), 'absolute inline-flex h-full w-full animate-ping opacity-75')}\n />\n )}\n <span\n className={cn(badgeVariants({ color }), 'relative inline-flex h-full w-full items-center justify-center')}\n >\n {count}\n </span>\n </span>\n )\n },\n)\n\nBadge.displayName = 'Badge'\n\nexport { Badge, badgeVariants }\n"],"names":[],"mappings":";;;;;AAKA,MAAM,aAAA,GAAgB,IAAI,cAAA,EAAgB;AAAA,EACxC,QAAA,EAAU;AAAA;AAAA;AAAA;AAAA;AAAA,IAKR,KAAA,EAAO;AAAA;AAAA,MAEL,OAAA,EAAS,oCAAA;AAAA;AAAA,MAET,MAAA,EAAQ,kCAAA;AAAA;AAAA,MAER,OAAA,EAAS,oCAAA;AAAA;AAAA,MAET,OAAA,EAAS,sCAAA;AAAA;AAAA,MAET,OAAA,EAAS,oCAAA;AAAA;AAAA,MAET,KAAA,EAAO;AAAA,KACT;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,IAAA,EAAM;AAAA;AAAA,MAEJ,EAAA,EAAI,SAAA;AAAA;AAAA,MAEJ,EAAA,EAAI,SAAA;AAAA;AAAA,MAEJ,EAAA,EAAI;AAAA,KACN;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,QAAA,EAAU;AAAA;AAAA,MAER,WAAA,EAAa,eAAA;AAAA;AAAA,MAEb,UAAA,EAAY,cAAA;AAAA;AAAA,MAEZ,cAAA,EAAgB,kBAAA;AAAA;AAAA,MAEhB,aAAA,EAAe;AAAA,KACjB;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,OAAA,EAAS;AAAA,MACP,IAAA,EAAM,gFAAA;AAAA,MACN,KAAA,EAAO;AAAA;AACT,GACF;AAAA,EACA,eAAA,EAAiB;AAAA,IACf,KAAA,EAAO,SAAA;AAAA,IACP,IAAA,EAAM,IAAA;AAAA,IACN,QAAA,EAAU,WAAA;AAAA,IACV,OAAA,EAAS;AAAA;AAEb,CAAC;AA0DD,MAAM,QAAQ,KAAA,CAAM,UAAA;AAAA,EAClB,CAAC,EAAE,SAAA,EAAW,KAAA,GAAQ,UAAU,IAAA,GAAO,IAAA,EAAM,QAAA,GAAW,WAAA,EAAa,OAAO,IAAA,EAAM,KAAA,EAAO,GAAG,KAAA,IAAS,GAAA,KAAQ;AAC3G,IAAA,MAAM,WAAA,GAAc;AAAA,MAClB,EAAA,EAAI,SAAA;AAAA,MACJ,EAAA,EAAI,SAAA;AAAA,MACJ,EAAA,EAAI;AAAA,KACN,CAAE,QAAQ,IAAI,CAAA;AAEd,IAAA,uBACE,IAAA;AAAA,MAAC,MAAA;AAAA,MAAA;AAAA,QACC,SAAA,EAAW,EAAA;AAAA,UACT,eAAA;AAAA,UACA,aAAA,CAAc,EAAE,KAAA,EAAO,QAAA,EAAU,SAAS,CAAC,CAAC,OAAO,CAAA;AAAA,UACnD,CAAC,KAAA,IAAS,WAAA;AAAA,UACV;AAAA,SACF;AAAA,QACA,GAAA;AAAA,QACC,GAAG,KAAA;AAAA,QAEH,QAAA,EAAA;AAAA,UAAA,IAAA,oBACC,GAAA;AAAA,YAAC,MAAA;AAAA,YAAA;AAAA,cACC,WAAW,EAAA,CAAG,aAAA,CAAc,EAAE,KAAA,EAAO,GAAG,4DAA4D;AAAA;AAAA,WACtG;AAAA,0BAEF,GAAA;AAAA,YAAC,MAAA;AAAA,YAAA;AAAA,cACC,WAAW,EAAA,CAAG,aAAA,CAAc,EAAE,KAAA,EAAO,GAAG,gEAAgE,CAAA;AAAA,cAEvG,QAAA,EAAA;AAAA;AAAA;AACH;AAAA;AAAA,KACF;AAAA,EAEJ;AACF;AAEA,KAAA,CAAM,WAAA,GAAc,OAAA;;;;"}
1
+ {"version":3,"file":"Badge.js","sources":["../../../src/components/Badge/Badge.tsx"],"sourcesContent":["import { cva } from 'class-variance-authority'\nimport * as React from 'react'\n\nimport { cn } from '../../lib/utils'\n\nconst badgeVariants = cva('inline-flex items-center rounded-sm border border-transparent font-medium', {\n variants: {\n color: {\n neutral: 'bg-neutral-50 text-neutral-950',\n accent: 'bg-accent-100 text-neutral-950',\n info: 'bg-information-100 text-information-800',\n success: 'bg-success-100 text-success-800',\n warning: 'bg-background-300 text-accent-900',\n error: 'bg-error-100 text-error-800',\n },\n size: {\n small: 'px-2 py-0.5 text-xs',\n medium: 'px-2.5 py-0.5 text-sm',\n big: 'px-3 py-1 text-base',\n },\n },\n defaultVariants: {\n color: 'neutral',\n size: 'medium',\n },\n})\n\nexport type BadgeProps = {\n /**\n * The color of the badge.\n * @default 'neutral'\n */\n color?: 'neutral' | 'accent' | 'info' | 'success' | 'warning' | 'error'\n\n /**\n * The size of the badge.\n * @default 'medium'\n */\n size?: 'small' | 'medium' | 'big'\n\n /**\n * The label to display inside the badge.\n */\n children?: React.ReactNode\n} & React.HTMLAttributes<HTMLElement>\n\n/**\n * An inline status label used to represent a state (e.g. \"Active\", \"Pending\").\n *\n * @example\n * ```tsx\n * <Badge color=\"success\" size=\"small\">Active</Badge>\n * ```\n */\nconst Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(\n ({ className, color = 'neutral', size = 'medium', children, ...props }, ref) => {\n return (\n <span className={cn(badgeVariants({ color, size }), className)} ref={ref} {...props}>\n {children}\n </span>\n )\n },\n)\n\nBadge.displayName = 'Badge'\n\nexport { Badge, badgeVariants }\n"],"names":[],"mappings":";;;;;AAKA,MAAM,aAAA,GAAgB,IAAI,2EAAA,EAA6E;AAAA,EACrG,QAAA,EAAU;AAAA,IACR,KAAA,EAAO;AAAA,MACL,OAAA,EAAS,gCAAA;AAAA,MACT,MAAA,EAAQ,gCAAA;AAAA,MACR,IAAA,EAAM,yCAAA;AAAA,MACN,OAAA,EAAS,iCAAA;AAAA,MACT,OAAA,EAAS,mCAAA;AAAA,MACT,KAAA,EAAO;AAAA,KACT;AAAA,IACA,IAAA,EAAM;AAAA,MACJ,KAAA,EAAO,qBAAA;AAAA,MACP,MAAA,EAAQ,uBAAA;AAAA,MACR,GAAA,EAAK;AAAA;AACP,GACF;AAAA,EACA,eAAA,EAAiB;AAAA,IACf,KAAA,EAAO,SAAA;AAAA,IACP,IAAA,EAAM;AAAA;AAEV,CAAC;AA6BD,MAAM,QAAQ,KAAA,CAAM,UAAA;AAAA,EAClB,CAAC,EAAE,SAAA,EAAW,KAAA,GAAQ,SAAA,EAAW,IAAA,GAAO,QAAA,EAAU,QAAA,EAAU,GAAG,KAAA,EAAM,EAAG,GAAA,KAAQ;AAC9E,IAAA,uBACE,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAW,EAAA,CAAG,cAAc,EAAE,KAAA,EAAO,IAAA,EAAM,GAAG,SAAS,CAAA,EAAG,GAAA,EAAW,GAAG,OAC3E,QAAA,EACH,CAAA;AAAA,EAEJ;AACF;AAEA,KAAA,CAAM,WAAA,GAAc,OAAA;;;;"}
@@ -89,6 +89,8 @@ export type ComboboxVirtualizedProps<T> = ComboboxBaseProps<T> & {
89
89
  onLoadMore: () => void;
90
90
  /** Emitted (debounced) when the search term changes. The consumer refetches. */
91
91
  onSearchChange: (search: string) => void;
92
+ /** Estimated row height (px), default: 40. */
93
+ estimateSize?: number;
92
94
  };
93
95
  export type ComboboxProps<T> = ComboboxSingleProps<T> | ComboboxMultipleProps<T> | ComboboxVirtualizedProps<T>;
94
96
  /**
@@ -152,7 +152,8 @@ const Combobox = (props) => {
152
152
  maxHeight: boundedListMaxHeight,
153
153
  hasNextPage,
154
154
  loadingMore,
155
- onLoadMore: loadNextPage
155
+ onLoadMore: loadNextPage,
156
+ estimateSize: "estimateSize" in props ? props.estimateSize : void 0
156
157
  }
157
158
  ) : /* @__PURE__ */ jsx(
158
159
  StaticComboboxList,
@@ -1 +1 @@
1
- {"version":3,"file":"Combobox.js","sources":["../../../src/components/Combobox/Combobox.tsx"],"sourcesContent":["import { X } from '@phosphor-icons/react/dist/ssr/X'\nimport { useEffect, useRef } from 'react'\n\nimport { cn } from '../../lib/utils'\nimport { Command, CommandInput } from '../Command'\nimport { PopoverContent, PopoverRoot, PopoverTrigger } from '../Popover'\n\nimport { ComboboxTrigger } from './ComboboxTrigger'\nimport { StaticComboboxList } from './StaticComboboxList'\nimport { useCombobox } from './useCombobox'\nimport { VirtualizedComboboxList } from './VirtualizedComboboxList'\n\ntype ComboboxBaseProps<T> = {\n /** Unique identifier for the combobox */\n id?: string\n /** Array of options to display in the combobox (static mode). */\n options?: Array<T>\n /** Function to get the unique identifier from an option. */\n getOptionValue: (option: T) => string\n /** Function to get the display text from an option. */\n getOptionLabel: (option: T) => string\n /** Custom render function for options. If not provided, defaults to showing a checkmark and label */\n renderOption?: (option: T, isSelected: boolean) => React.ReactNode\n /** Placeholder text shown when no option is selected */\n placeholder?: string\n /** Placeholder text for the search input field */\n searchPlaceholder?: string\n /** Message shown when no options match the search query */\n emptyMessage?: string\n /** Additional CSS classes to apply to the combobox trigger */\n className?: string\n /** Additional CSS classes to apply to the popover content */\n contentClassName?: string\n /** Whether the combobox is disabled */\n disabled?: boolean\n /** Maximum height of the options list. Can be any valid CSS height value */\n maxHeight?: string | number\n /** Whether to close the dropdown when an option is selected. Defaults to `true` for single select and `false` for multi-select. */\n closeOnSelect?: boolean\n /** Whether the combobox is in a loading state */\n loading?: boolean\n /** Message to show when in loading state */\n loadingPlaceholder?: string\n /** Whether the combobox has an error */\n error?: boolean | string\n /** Custom function to filter options based on search term (static mode only) */\n filterOptions?: (options: Array<T>, searchTerm: string) => Array<T>\n /** Callback function executed when the clear button is clicked. When provided, an X button will appear on hover to clear the selection. */\n onClear?: boolean | (() => void)\n /** Whether the selection can be cleared */\n clearable?: boolean\n /** Whether the combobox is inside a modal */\n modal?: boolean\n /** Placeholder text for the selected multiple options */\n selectedMultiplePlaceholder?: string\n /** Placeholder text for the multiple options */\n multipleOptionsPlaceholder?: string\n /** Special options that appear at the top with their own title. When selected, the combobox switches to single-select mode. */\n specialOptions?: Array<T>\n /** Title for the special options group */\n specialOptionsTitle?: string\n /** Container element to position the combobox relative to. */\n container?: HTMLElement\n /** Alignment of the dropdown relative to the trigger. Defaults to 'center'. */\n align?: 'start' | 'center' | 'end'\n /** Auto-focus the search input when the dropdown opens. */\n onOpenAutoFocus?: boolean\n}\n\nexport type ComboboxSingleProps<T> = ComboboxBaseProps<T> & {\n virtualized?: false\n multiple?: false\n value?: string\n onChange: (value: string) => void\n /** Custom render function for the selected value display. */\n renderLabel?: (selectedOption: T) => React.ReactNode\n}\n\nexport type ComboboxMultipleProps<T> = ComboboxBaseProps<T> & {\n virtualized?: false\n multiple: true\n value?: Array<string>\n onChange: (value: Array<string>) => void\n /** Custom render function for the selected value(s) display. */\n renderLabel?: (selectedOptions: Array<T>, onRemove: (value: string) => void) => React.ReactNode\n}\n\nexport type ComboboxVirtualizedProps<T> = ComboboxBaseProps<T> & {\n virtualized: true\n multiple?: false\n value?: string\n onChange: (value: string) => void\n /** Custom render function for the selected value display. */\n renderLabel?: (selectedOption: T) => React.ReactNode\n /** The accumulated, flattened list of loaded items. */\n items: Array<T>\n /** The resolved selected item, fetched independently of the list. Used for the trigger label. */\n selectedItem?: T\n /** Whether more pages can be loaded. */\n hasNextPage: boolean\n /** Whether the next page is currently being fetched. */\n isFetchingNextPage: boolean\n /** Request the next page (triggered near the end of the list). */\n onLoadMore: () => void\n /** Emitted (debounced) when the search term changes. The consumer refetches. */\n onSearchChange: (search: string) => void\n}\n\nexport type ComboboxProps<T> = ComboboxSingleProps<T> | ComboboxMultipleProps<T> | ComboboxVirtualizedProps<T>\n\n/**\n * A searchable combobox component with support for custom rendering, keyboard navigation, and search filtering.\n *\n * @example Basic usage\n * ```tsx\n * <Combobox<User>\n * options={users}\n * value={selectedUserId}\n * onChange={setSelectedUserId}\n * getOptionValue={(user) => user.id}\n * getOptionLabel={(user) => user.name}\n * />\n * ```\n */\nexport const Combobox = <T extends object>(props: ComboboxProps<T>) => {\n const {\n id,\n options = [],\n getOptionValue,\n getOptionLabel,\n searchPlaceholder = 'Search...',\n emptyMessage = 'No results found.',\n className = 'w-60',\n contentClassName,\n disabled = false,\n maxHeight = '300px',\n renderOption,\n loading = false,\n loadingPlaceholder = 'Cargando...',\n error = false,\n multiple,\n onClear,\n modal = false,\n selectedMultiplePlaceholder = 'Selected',\n multipleOptionsPlaceholder = 'Options',\n specialOptions,\n specialOptionsTitle,\n container,\n align,\n onOpenAutoFocus = false,\n } = props\n\n const searchInputRef = useRef<HTMLInputElement>(null)\n const normalizedMaxHeight = typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight\n const boundedListMaxHeight = `min(${normalizedMaxHeight}, max(120px, calc(var(--radix-popover-content-available-height, 100dvh) - 68px)))`\n\n const {\n open,\n setOpen,\n searchTerm,\n setSearchTerm,\n setIsHovered,\n hasNextPage,\n loadingMore,\n isVirtualized,\n filteredOptions,\n displayValue,\n hasValue,\n showClearButton,\n handleSelect,\n handleClear,\n loadNextPage,\n } = useCombobox(props)\n\n useEffect(() => {\n if (!open || !onOpenAutoFocus) return\n const timeoutId = globalThis.setTimeout(() => searchInputRef.current?.focus(), 0)\n return () => globalThis.clearTimeout(timeoutId)\n }, [open, onOpenAutoFocus])\n\n const hasMultipleRenderLabel = !!(multiple && 'renderLabel' in props && props.renderLabel)\n\n return (\n <div className=\"flex w-full flex-col gap-1\">\n <PopoverRoot open={open} onOpenChange={setOpen} modal={modal}>\n <div\n className={cn('relative', className)}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n <PopoverTrigger\n asChild\n onPointerDown={(event) => {\n event.stopPropagation()\n if (open) {\n setOpen(false)\n event.preventDefault()\n } else {\n setOpen(true)\n }\n }}\n onClick={(event) => {\n event.preventDefault()\n event.stopPropagation()\n }}\n >\n <ComboboxTrigger\n id={id}\n disabled={disabled}\n open={open}\n error={error}\n hasValue={hasValue}\n showClearButton={showClearButton}\n isMultilineLabel={hasMultipleRenderLabel}\n displayValue={displayValue}\n className={cn('w-full', className)}\n />\n </PopoverTrigger>\n {!!onClear && hasValue && (\n <X\n data-testid=\"clear-button\"\n className={cn(\n 'absolute top-1/2 right-4 z-10 h-4 w-4 -translate-y-1/2 cursor-pointer transition-opacity duration-150',\n showClearButton ? 'opacity-100 hover:opacity-70' : 'opacity-0',\n )}\n onPointerDown={(event) => event.stopPropagation()}\n onClick={handleClear}\n />\n )}\n </div>\n <PopoverContent\n className={cn('overflow-hidden p-0', contentClassName)}\n container={container}\n side=\"bottom\"\n align={align}\n onOpenAutoFocus={onOpenAutoFocus ? (event) => event.preventDefault() : undefined}\n >\n <Command shouldFilter={false}>\n <CommandInput\n ref={searchInputRef}\n placeholder={searchPlaceholder}\n value={searchTerm}\n onValueChange={setSearchTerm}\n />\n {isVirtualized ? (\n <VirtualizedComboboxList\n localOptions={filteredOptions}\n loading={loading}\n loadingPlaceholder={loadingPlaceholder}\n emptyMessage={emptyMessage}\n value={props.value as string}\n getOptionValue={getOptionValue}\n getOptionLabel={getOptionLabel}\n handleSelect={handleSelect}\n renderOption={renderOption}\n maxHeight={boundedListMaxHeight}\n hasNextPage={hasNextPage}\n loadingMore={loadingMore}\n onLoadMore={loadNextPage}\n />\n ) : (\n <StaticComboboxList\n filteredOptions={filteredOptions}\n loading={loading}\n loadingPlaceholder={loadingPlaceholder}\n emptyMessage={emptyMessage}\n multiple={!!multiple}\n value={props.value}\n getOptionValue={getOptionValue}\n getOptionLabel={getOptionLabel}\n handleSelect={handleSelect}\n renderOption={renderOption}\n maxHeight={boundedListMaxHeight}\n selectedMultiplePlaceholder={selectedMultiplePlaceholder}\n multipleOptionsPlaceholder={multipleOptionsPlaceholder}\n options={options}\n specialOptions={specialOptions}\n specialOptionsTitle={specialOptionsTitle}\n />\n )}\n </Command>\n </PopoverContent>\n </PopoverRoot>\n {typeof error === 'string' && <span className=\"text-error-500 text-sm\">{error}</span>}\n </div>\n )\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Combobox.js","sources":["../../../src/components/Combobox/Combobox.tsx"],"sourcesContent":["import { X } from '@phosphor-icons/react/dist/ssr/X'\nimport { useEffect, useRef } from 'react'\n\nimport { cn } from '../../lib/utils'\nimport { Command, CommandInput } from '../Command'\nimport { PopoverContent, PopoverRoot, PopoverTrigger } from '../Popover'\n\nimport { ComboboxTrigger } from './ComboboxTrigger'\nimport { StaticComboboxList } from './StaticComboboxList'\nimport { useCombobox } from './useCombobox'\nimport { VirtualizedComboboxList } from './VirtualizedComboboxList'\n\ntype ComboboxBaseProps<T> = {\n /** Unique identifier for the combobox */\n id?: string\n /** Array of options to display in the combobox (static mode). */\n options?: Array<T>\n /** Function to get the unique identifier from an option. */\n getOptionValue: (option: T) => string\n /** Function to get the display text from an option. */\n getOptionLabel: (option: T) => string\n /** Custom render function for options. If not provided, defaults to showing a checkmark and label */\n renderOption?: (option: T, isSelected: boolean) => React.ReactNode\n /** Placeholder text shown when no option is selected */\n placeholder?: string\n /** Placeholder text for the search input field */\n searchPlaceholder?: string\n /** Message shown when no options match the search query */\n emptyMessage?: string\n /** Additional CSS classes to apply to the combobox trigger */\n className?: string\n /** Additional CSS classes to apply to the popover content */\n contentClassName?: string\n /** Whether the combobox is disabled */\n disabled?: boolean\n /** Maximum height of the options list. Can be any valid CSS height value */\n maxHeight?: string | number\n /** Whether to close the dropdown when an option is selected. Defaults to `true` for single select and `false` for multi-select. */\n closeOnSelect?: boolean\n /** Whether the combobox is in a loading state */\n loading?: boolean\n /** Message to show when in loading state */\n loadingPlaceholder?: string\n /** Whether the combobox has an error */\n error?: boolean | string\n /** Custom function to filter options based on search term (static mode only) */\n filterOptions?: (options: Array<T>, searchTerm: string) => Array<T>\n /** Callback function executed when the clear button is clicked. When provided, an X button will appear on hover to clear the selection. */\n onClear?: boolean | (() => void)\n /** Whether the selection can be cleared */\n clearable?: boolean\n /** Whether the combobox is inside a modal */\n modal?: boolean\n /** Placeholder text for the selected multiple options */\n selectedMultiplePlaceholder?: string\n /** Placeholder text for the multiple options */\n multipleOptionsPlaceholder?: string\n /** Special options that appear at the top with their own title. When selected, the combobox switches to single-select mode. */\n specialOptions?: Array<T>\n /** Title for the special options group */\n specialOptionsTitle?: string\n /** Container element to position the combobox relative to. */\n container?: HTMLElement\n /** Alignment of the dropdown relative to the trigger. Defaults to 'center'. */\n align?: 'start' | 'center' | 'end'\n /** Auto-focus the search input when the dropdown opens. */\n onOpenAutoFocus?: boolean\n}\n\nexport type ComboboxSingleProps<T> = ComboboxBaseProps<T> & {\n virtualized?: false\n multiple?: false\n value?: string\n onChange: (value: string) => void\n /** Custom render function for the selected value display. */\n renderLabel?: (selectedOption: T) => React.ReactNode\n}\n\nexport type ComboboxMultipleProps<T> = ComboboxBaseProps<T> & {\n virtualized?: false\n multiple: true\n value?: Array<string>\n onChange: (value: Array<string>) => void\n /** Custom render function for the selected value(s) display. */\n renderLabel?: (selectedOptions: Array<T>, onRemove: (value: string) => void) => React.ReactNode\n}\n\nexport type ComboboxVirtualizedProps<T> = ComboboxBaseProps<T> & {\n virtualized: true\n multiple?: false\n value?: string\n onChange: (value: string) => void\n /** Custom render function for the selected value display. */\n renderLabel?: (selectedOption: T) => React.ReactNode\n /** The accumulated, flattened list of loaded items. */\n items: Array<T>\n /** The resolved selected item, fetched independently of the list. Used for the trigger label. */\n selectedItem?: T\n /** Whether more pages can be loaded. */\n hasNextPage: boolean\n /** Whether the next page is currently being fetched. */\n isFetchingNextPage: boolean\n /** Request the next page (triggered near the end of the list). */\n onLoadMore: () => void\n /** Emitted (debounced) when the search term changes. The consumer refetches. */\n onSearchChange: (search: string) => void\n /** Estimated row height (px), default: 40. */\n estimateSize?: number\n}\n\nexport type ComboboxProps<T> = ComboboxSingleProps<T> | ComboboxMultipleProps<T> | ComboboxVirtualizedProps<T>\n\n/**\n * A searchable combobox component with support for custom rendering, keyboard navigation, and search filtering.\n *\n * @example Basic usage\n * ```tsx\n * <Combobox<User>\n * options={users}\n * value={selectedUserId}\n * onChange={setSelectedUserId}\n * getOptionValue={(user) => user.id}\n * getOptionLabel={(user) => user.name}\n * />\n * ```\n */\nexport const Combobox = <T extends object>(props: ComboboxProps<T>) => {\n const {\n id,\n options = [],\n getOptionValue,\n getOptionLabel,\n searchPlaceholder = 'Search...',\n emptyMessage = 'No results found.',\n className = 'w-60',\n contentClassName,\n disabled = false,\n maxHeight = '300px',\n renderOption,\n loading = false,\n loadingPlaceholder = 'Cargando...',\n error = false,\n multiple,\n onClear,\n modal = false,\n selectedMultiplePlaceholder = 'Selected',\n multipleOptionsPlaceholder = 'Options',\n specialOptions,\n specialOptionsTitle,\n container,\n align,\n onOpenAutoFocus = false,\n } = props\n\n const searchInputRef = useRef<HTMLInputElement>(null)\n const normalizedMaxHeight = typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight\n const boundedListMaxHeight = `min(${normalizedMaxHeight}, max(120px, calc(var(--radix-popover-content-available-height, 100dvh) - 68px)))`\n\n const {\n open,\n setOpen,\n searchTerm,\n setSearchTerm,\n setIsHovered,\n hasNextPage,\n loadingMore,\n isVirtualized,\n filteredOptions,\n displayValue,\n hasValue,\n showClearButton,\n handleSelect,\n handleClear,\n loadNextPage,\n } = useCombobox(props)\n\n useEffect(() => {\n if (!open || !onOpenAutoFocus) return\n const timeoutId = globalThis.setTimeout(() => searchInputRef.current?.focus(), 0)\n return () => globalThis.clearTimeout(timeoutId)\n }, [open, onOpenAutoFocus])\n\n const hasMultipleRenderLabel = !!(multiple && 'renderLabel' in props && props.renderLabel)\n\n return (\n <div className=\"flex w-full flex-col gap-1\">\n <PopoverRoot open={open} onOpenChange={setOpen} modal={modal}>\n <div\n className={cn('relative', className)}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n <PopoverTrigger\n asChild\n onPointerDown={(event) => {\n event.stopPropagation()\n if (open) {\n setOpen(false)\n event.preventDefault()\n } else {\n setOpen(true)\n }\n }}\n onClick={(event) => {\n event.preventDefault()\n event.stopPropagation()\n }}\n >\n <ComboboxTrigger\n id={id}\n disabled={disabled}\n open={open}\n error={error}\n hasValue={hasValue}\n showClearButton={showClearButton}\n isMultilineLabel={hasMultipleRenderLabel}\n displayValue={displayValue}\n className={cn('w-full', className)}\n />\n </PopoverTrigger>\n {!!onClear && hasValue && (\n <X\n data-testid=\"clear-button\"\n className={cn(\n 'absolute top-1/2 right-4 z-10 h-4 w-4 -translate-y-1/2 cursor-pointer transition-opacity duration-150',\n showClearButton ? 'opacity-100 hover:opacity-70' : 'opacity-0',\n )}\n onPointerDown={(event) => event.stopPropagation()}\n onClick={handleClear}\n />\n )}\n </div>\n <PopoverContent\n className={cn('overflow-hidden p-0', contentClassName)}\n container={container}\n side=\"bottom\"\n align={align}\n onOpenAutoFocus={onOpenAutoFocus ? (event) => event.preventDefault() : undefined}\n >\n <Command shouldFilter={false}>\n <CommandInput\n ref={searchInputRef}\n placeholder={searchPlaceholder}\n value={searchTerm}\n onValueChange={setSearchTerm}\n />\n {isVirtualized ? (\n <VirtualizedComboboxList\n localOptions={filteredOptions}\n loading={loading}\n loadingPlaceholder={loadingPlaceholder}\n emptyMessage={emptyMessage}\n value={props.value as string}\n getOptionValue={getOptionValue}\n getOptionLabel={getOptionLabel}\n handleSelect={handleSelect}\n renderOption={renderOption}\n maxHeight={boundedListMaxHeight}\n hasNextPage={hasNextPage}\n loadingMore={loadingMore}\n onLoadMore={loadNextPage}\n estimateSize={'estimateSize' in props ? props.estimateSize : undefined}\n />\n ) : (\n <StaticComboboxList\n filteredOptions={filteredOptions}\n loading={loading}\n loadingPlaceholder={loadingPlaceholder}\n emptyMessage={emptyMessage}\n multiple={!!multiple}\n value={props.value}\n getOptionValue={getOptionValue}\n getOptionLabel={getOptionLabel}\n handleSelect={handleSelect}\n renderOption={renderOption}\n maxHeight={boundedListMaxHeight}\n selectedMultiplePlaceholder={selectedMultiplePlaceholder}\n multipleOptionsPlaceholder={multipleOptionsPlaceholder}\n options={options}\n specialOptions={specialOptions}\n specialOptionsTitle={specialOptionsTitle}\n />\n )}\n </Command>\n </PopoverContent>\n </PopoverRoot>\n {typeof error === 'string' && <span className=\"text-error-500 text-sm\">{error}</span>}\n </div>\n )\n}\n"],"names":[],"mappings":"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"}
@@ -1,2 +1,2 @@
1
1
  import { VirtualizedComboboxListProps } from './types';
2
- export declare const VirtualizedComboboxList: <T extends object>({ localOptions, loading, loadingPlaceholder, emptyMessage, value, getOptionValue, getOptionLabel, handleSelect, renderOption, maxHeight, hasNextPage, loadingMore, onLoadMore, }: VirtualizedComboboxListProps<T>) => import("react/jsx-runtime").JSX.Element;
2
+ export declare const VirtualizedComboboxList: <T extends object>({ localOptions, loading, loadingPlaceholder, emptyMessage, value, getOptionValue, getOptionLabel, handleSelect, renderOption, maxHeight, hasNextPage, loadingMore, onLoadMore, estimateSize, }: VirtualizedComboboxListProps<T>) => import("react/jsx-runtime").JSX.Element;
@@ -5,6 +5,7 @@ import { useRef, useEffect } from 'react';
5
5
  import { CommandEmpty, CommandItem } from '../Command/Command.js';
6
6
  import { ComboboxOptionWithTooltip } from './ComboboxOptionWithTooltip.js';
7
7
 
8
+ const DEFAULT_ESTIMATED_ROW_HEIGHT = 40;
8
9
  const VirtualizedComboboxList = ({
9
10
  localOptions,
10
11
  loading,
@@ -18,7 +19,8 @@ const VirtualizedComboboxList = ({
18
19
  maxHeight,
19
20
  hasNextPage,
20
21
  loadingMore,
21
- onLoadMore
22
+ onLoadMore,
23
+ estimateSize = DEFAULT_ESTIMATED_ROW_HEIGHT
22
24
  }) => {
23
25
  const parentRef = useRef(null);
24
26
  const hasTriggeredRef = useRef(false);
@@ -27,8 +29,9 @@ const VirtualizedComboboxList = ({
27
29
  const rowVirtualizer = useVirtualizer({
28
30
  count: totalCount,
29
31
  getScrollElement: () => parentRef.current,
30
- estimateSize: () => 40,
31
- overscan: 10
32
+ estimateSize: () => estimateSize,
33
+ overscan: 10,
34
+ useAnimationFrameWithResizeObserver: true
32
35
  });
33
36
  useEffect(() => {
34
37
  hasTriggeredRef.current = false;
@@ -71,9 +74,7 @@ const VirtualizedComboboxList = ({
71
74
  "div",
72
75
  {
73
76
  "data-index": virtualRow.index,
74
- ref: (el) => {
75
- if (el) rowVirtualizer.measureElement(el);
76
- },
77
+ ref: (el) => rowVirtualizer.measureElement(el),
77
78
  style: {
78
79
  position: "absolute",
79
80
  top: 0,
@@ -1 +1 @@
1
- {"version":3,"file":"VirtualizedComboboxList.js","sources":["../../../src/components/Combobox/VirtualizedComboboxList.tsx"],"sourcesContent":["import { Check } from '@phosphor-icons/react/dist/ssr/Check'\nimport { useVirtualizer } from '@tanstack/react-virtual'\nimport { useRef, useEffect } from 'react'\n\nimport { CommandEmpty, CommandItem } from '../Command'\n\nimport { ComboboxOptionWithTooltip } from './ComboboxOptionWithTooltip'\nimport type { VirtualizedComboboxListProps } from './types'\n\nexport const VirtualizedComboboxList = <T extends object>({\n localOptions,\n loading,\n loadingPlaceholder,\n emptyMessage,\n value,\n getOptionValue,\n getOptionLabel,\n handleSelect,\n renderOption,\n maxHeight,\n hasNextPage,\n loadingMore,\n onLoadMore,\n}: VirtualizedComboboxListProps<T>) => {\n const parentRef = useRef<HTMLDivElement>(null)\n const hasTriggeredRef = useRef(false)\n\n const totalCount = localOptions.length\n const PREFETCH_THRESHOLD = 25\n\n const rowVirtualizer = useVirtualizer({\n count: totalCount,\n getScrollElement: () => parentRef.current,\n estimateSize: () => 40,\n overscan: 10,\n })\n\n useEffect(() => {\n hasTriggeredRef.current = false\n }, [totalCount, loadingMore])\n\n const virtualItems = rowVirtualizer.getVirtualItems()\n\n useEffect(() => {\n if (virtualItems.length === 0 || !hasNextPage || loadingMore || loading || hasTriggeredRef.current) {\n return\n }\n\n const lastVisibleItem = virtualItems.at(-1)\n const itemsFromEnd = totalCount - (lastVisibleItem?.index ?? 0)\n\n if (itemsFromEnd <= PREFETCH_THRESHOLD) {\n hasTriggeredRef.current = true\n onLoadMore()\n }\n }, [virtualItems, totalCount, hasNextPage, loadingMore, loading, onLoadMore])\n\n if (loading && localOptions.length === 0) {\n return (\n <div className=\"flex items-center justify-center py-6 text-sm text-neutral-400\">{loadingPlaceholder}</div>\n )\n }\n\n if (localOptions.length === 0 && !loading && !loadingMore) {\n return <CommandEmpty>{emptyMessage}</CommandEmpty>\n }\n\n const getItemAtIndex = (index: number) => {\n const option = localOptions[index]\n const isSelected = value === getOptionValue(option)\n return { option, isSelected }\n }\n\n return (\n <div\n ref={parentRef}\n className=\"overflow-auto overscroll-contain\"\n style={{ maxHeight }}\n onWheel={(event) => event.stopPropagation()}\n >\n <div style={{ height: `${rowVirtualizer.getTotalSize()}px`, position: 'relative' }}>\n {rowVirtualizer.getVirtualItems().map((virtualRow) => {\n const { option, isSelected } = getItemAtIndex(virtualRow.index)\n const optionValue = getOptionValue(option)\n\n return (\n <div\n key={optionValue}\n data-index={virtualRow.index}\n ref={(el) => {\n if (el) rowVirtualizer.measureElement(el)\n }}\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n width: '100%',\n transform: `translateY(${virtualRow.start}px)`,\n }}\n >\n <CommandItem value={optionValue} onSelect={handleSelect}>\n {renderOption ? (\n renderOption(option, isSelected)\n ) : (\n <>\n <Check className={`mr-2 h-4 w-4 ${isSelected ? 'opacity-100' : 'opacity-0'}`} />\n <ComboboxOptionWithTooltip label={getOptionLabel(option)} />\n </>\n )}\n </CommandItem>\n </div>\n )\n })}\n </div>\n {loadingMore && (\n <div className=\"flex items-center justify-center py-2 text-sm text-neutral-500\">{loadingPlaceholder}</div>\n )}\n </div>\n )\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"VirtualizedComboboxList.js","sources":["../../../src/components/Combobox/VirtualizedComboboxList.tsx"],"sourcesContent":["import { Check } from '@phosphor-icons/react/dist/ssr/Check'\nimport { useVirtualizer } from '@tanstack/react-virtual'\nimport { useRef, useEffect } from 'react'\n\nimport { CommandEmpty, CommandItem } from '../Command'\n\nimport { ComboboxOptionWithTooltip } from './ComboboxOptionWithTooltip'\nimport type { VirtualizedComboboxListProps } from './types'\n\nconst DEFAULT_ESTIMATED_ROW_HEIGHT = 40\n\nexport const VirtualizedComboboxList = <T extends object>({\n localOptions,\n loading,\n loadingPlaceholder,\n emptyMessage,\n value,\n getOptionValue,\n getOptionLabel,\n handleSelect,\n renderOption,\n maxHeight,\n hasNextPage,\n loadingMore,\n onLoadMore,\n estimateSize = DEFAULT_ESTIMATED_ROW_HEIGHT,\n}: VirtualizedComboboxListProps<T>) => {\n const parentRef = useRef<HTMLDivElement>(null)\n const hasTriggeredRef = useRef(false)\n\n const totalCount = localOptions.length\n const PREFETCH_THRESHOLD = 25\n\n const rowVirtualizer = useVirtualizer({\n count: totalCount,\n getScrollElement: () => parentRef.current,\n estimateSize: () => estimateSize,\n overscan: 10,\n useAnimationFrameWithResizeObserver: true,\n })\n\n useEffect(() => {\n hasTriggeredRef.current = false\n }, [totalCount, loadingMore])\n\n const virtualItems = rowVirtualizer.getVirtualItems()\n\n useEffect(() => {\n if (virtualItems.length === 0 || !hasNextPage || loadingMore || loading || hasTriggeredRef.current) {\n return\n }\n\n const lastVisibleItem = virtualItems.at(-1)\n const itemsFromEnd = totalCount - (lastVisibleItem?.index ?? 0)\n\n if (itemsFromEnd <= PREFETCH_THRESHOLD) {\n hasTriggeredRef.current = true\n onLoadMore()\n }\n }, [virtualItems, totalCount, hasNextPage, loadingMore, loading, onLoadMore])\n\n if (loading && localOptions.length === 0) {\n return <div className=\"flex items-center justify-center py-6 text-sm text-neutral-400\">{loadingPlaceholder}</div>\n }\n\n if (localOptions.length === 0 && !loading && !loadingMore) {\n return <CommandEmpty>{emptyMessage}</CommandEmpty>\n }\n\n const getItemAtIndex = (index: number) => {\n const option = localOptions[index]\n const isSelected = value === getOptionValue(option)\n return { option, isSelected }\n }\n\n return (\n <div\n ref={parentRef}\n className=\"overflow-auto overscroll-contain\"\n style={{ maxHeight }}\n onWheel={(event) => event.stopPropagation()}\n >\n <div style={{ height: `${rowVirtualizer.getTotalSize()}px`, position: 'relative' }}>\n {rowVirtualizer.getVirtualItems().map((virtualRow) => {\n const { option, isSelected } = getItemAtIndex(virtualRow.index)\n const optionValue = getOptionValue(option)\n\n return (\n <div\n key={optionValue}\n data-index={virtualRow.index}\n ref={(el) => rowVirtualizer.measureElement(el)}\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n width: '100%',\n transform: `translateY(${virtualRow.start}px)`,\n }}\n >\n <CommandItem value={optionValue} onSelect={handleSelect}>\n {renderOption ? (\n renderOption(option, isSelected)\n ) : (\n <>\n <Check className={`mr-2 h-4 w-4 ${isSelected ? 'opacity-100' : 'opacity-0'}`} />\n <ComboboxOptionWithTooltip label={getOptionLabel(option)} />\n </>\n )}\n </CommandItem>\n </div>\n )\n })}\n </div>\n {loadingMore && (\n <div className=\"flex items-center justify-center py-2 text-sm text-neutral-500\">{loadingPlaceholder}</div>\n )}\n </div>\n )\n}\n"],"names":[],"mappings":"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"}
@@ -14,6 +14,7 @@ export type VirtualizedComboboxListProps<T> = ComboboxListBaseProps<T> & {
14
14
  hasNextPage: boolean;
15
15
  loadingMore: boolean;
16
16
  onLoadMore: () => void;
17
+ estimateSize?: number;
17
18
  };
18
19
  export type StaticComboboxListProps<T> = ComboboxListBaseProps<T> & {
19
20
  filteredOptions: Array<T>;
@@ -138,6 +138,7 @@ function DataTable({
138
138
  manualPagination: isBackendPagination,
139
139
  onPaginationChange: isBackendPagination ? void 0 : tableOnPaginationChange
140
140
  });
141
+ const isEmptyState = !isLoading && table.getRowModel().rows.length === 0;
141
142
  return /* @__PURE__ */ jsxs("div", { className: cn("flex h-full min-h-0 w-full flex-1 flex-col gap-2 overflow-hidden", className), children: [
142
143
  /* @__PURE__ */ jsx(
143
144
  DataTableToolbar,
@@ -153,29 +154,36 @@ function DataTable({
153
154
  }
154
155
  ),
155
156
  /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col rounded-md border bg-white", children: [
156
- /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-auto overscroll-x-none overscroll-y-auto", children: /* @__PURE__ */ jsxs(Table, { className: "w-full", tableClassName: cn("min-w-max", tableClassName), children: [
157
- /* @__PURE__ */ jsx(TableHeader, { className: "sticky top-0 z-10 bg-neutral-50", children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsx(TableRow, { children: headerGroup.headers.map((header) => /* @__PURE__ */ jsx(
158
- TableHead,
159
- {
160
- style: { width: header.column.columnDef.size },
161
- className: "h-auto p-0 text-neutral-900",
162
- children: /* @__PURE__ */ jsx("div", { className: getDataTableCellClassName({ columnId: header.column.id, kind: "header" }), children: header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext()) })
163
- },
164
- header.id
165
- )) }, headerGroup.id)) }),
166
- /* @__PURE__ */ jsx(
167
- DataTableBody,
168
- {
169
- table,
170
- isLoading,
171
- pageSize,
172
- getRowId,
173
- rowSelection,
174
- emptyState,
175
- group: groupConfig
176
- }
177
- )
178
- ] }) }),
157
+ /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-auto overscroll-x-none overscroll-y-auto", children: /* @__PURE__ */ jsxs(
158
+ Table,
159
+ {
160
+ className: cn("w-full", isEmptyState && "h-full"),
161
+ tableClassName: cn("min-w-max", tableClassName),
162
+ children: [
163
+ /* @__PURE__ */ jsx(TableHeader, { className: "sticky top-0 z-10 bg-neutral-50", children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsx(TableRow, { children: headerGroup.headers.map((header) => /* @__PURE__ */ jsx(
164
+ TableHead,
165
+ {
166
+ style: { width: header.column.columnDef.size },
167
+ className: "h-auto p-0 text-neutral-900",
168
+ children: /* @__PURE__ */ jsx("div", { className: getDataTableCellClassName({ columnId: header.column.id, kind: "header" }), children: header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext()) })
169
+ },
170
+ header.id
171
+ )) }, headerGroup.id)) }),
172
+ /* @__PURE__ */ jsx(
173
+ DataTableBody,
174
+ {
175
+ table,
176
+ isLoading,
177
+ pageSize,
178
+ getRowId,
179
+ rowSelection,
180
+ emptyState,
181
+ group: groupConfig
182
+ }
183
+ )
184
+ ]
185
+ }
186
+ ) }),
179
187
  !!pagination && /* @__PURE__ */ jsx("div", { className: "border-t px-4 py-2", children: /* @__PURE__ */ jsx(
180
188
  DataTablePagination,
181
189
  {
@@ -1 +1 @@
1
- {"version":3,"file":"DataTable.js","sources":["../../../src/components/DataTable/DataTable.tsx"],"sourcesContent":["'use client'\n\nimport {\n flexRender,\n getCoreRowModel,\n getExpandedRowModel,\n getPaginationRowModel,\n useReactTable,\n VisibilityState,\n PaginationState,\n Updater,\n} from '@tanstack/react-table'\nimport * as React from 'react'\n\nimport { Table as TableComponent, TableHeader, TableHead, TableRow } from '../Table'\n\nimport { DataTableBody } from './components/DataTableBody'\nimport { getDataTableCellClassName } from './components/dataTableCellStyles'\nimport { DataTablePagination } from './components/DataTablePagination'\nimport { DataTableToolbar } from './components/DataTableToolbar'\nimport { useGroupExpansion } from './hooks/useGroupExpansion'\nimport { useSelectColumn } from './hooks/useSelectColumn'\nimport { useSelection } from './hooks/useSelection'\nimport { DataTableProps } from './types'\nimport { flattenLeaves } from './utils'\n\nimport { cn } from '@/lib/utils'\n\n/**\n * A feature-rich data table component built on top of TanStack Table.\n * Provides sorting, filtering, pagination, and column visibility controls.\n */\nexport function DataTable<TData extends object>({\n columns: userColumns,\n data,\n getRowId = (row: TData) => (row as { id: string }).id,\n columnVisibility: columnVisibilityConfig,\n isLoading = false,\n pagination,\n grouping,\n primaryFilters,\n secondaryFilters,\n activePrimaryFiltersCount = 0,\n activeSecondaryFiltersCount = 0,\n selection,\n labels = {\n filters: 'Filters',\n moreFilters: 'More filters',\n },\n emptyState = {\n title: 'No data available',\n description: 'Try adjusting your filters or search criteria',\n },\n className,\n tableClassName,\n}: DataTableProps<TData>) {\n const [internalColumnVisibility, setInternalColumnVisibility] = React.useState<VisibilityState>({})\n const [pageIndex, setPageIndex] = React.useState(0)\n\n const columnVisibilityState = columnVisibilityConfig?.state ?? internalColumnVisibility\n const setColumnVisibilityState = columnVisibilityConfig?.onChange ?? setInternalColumnVisibility\n\n const { isGroupOpen, toggleGroup } = useGroupExpansion({ grouping, data, getRowId })\n\n const selectionData = React.useMemo(\n () => (grouping ? flattenLeaves(data, grouping.getSubRows) : data),\n [grouping, data],\n )\n\n const isBackendPagination = pagination !== undefined && 'onPageChange' in pagination\n\n const total = isBackendPagination ? pagination.total : data.length\n const pageSize = pagination?.pageSize ?? (isLoading ? 10 : data.length)\n const totalPages = Math.ceil(total / pageSize)\n\n const { selectedIds, handleRowSelect, handleSelectAll } = useSelection({\n data: selectionData,\n getRowId,\n selection,\n })\n\n const rowSelection = React.useMemo(\n () => Object.fromEntries(Array.from(selectedIds).map((id) => [id, true])),\n [selectedIds],\n )\n\n const selectColumn = useSelectColumn({\n selection,\n selectedIds,\n selectionData,\n getRowId,\n handleRowSelect,\n handleSelectAll,\n isBackendPagination,\n isLoading,\n })\n\n const tableColumns = React.useMemo(() => {\n if (!selectColumn) return userColumns\n return [selectColumn.column, ...userColumns]\n }, [userColumns, selectColumn])\n\n const toolbarSelectionProps = React.useMemo(() => {\n if (isBackendPagination && selection) {\n return {\n isSelectionStarted: selection.isSelectionStarted,\n onSelectionStart: selection.onSelectionStart,\n onSelectionCancel: selection.onSelectionCancel,\n selectionSummary: selection.labels?.selectionSummary,\n isSelectionDisabled: selection.isSelectionDisabled,\n labels: selection.labels,\n }\n }\n return undefined\n }, [isBackendPagination, selection])\n\n const currentPage = isBackendPagination ? pagination.currentPage : pageIndex + 1\n const handlePageChange = isBackendPagination ? pagination.onPageChange : (page: number) => setPageIndex(page - 1)\n\n const tablePaginationState = React.useMemo(() => {\n if (!pagination) {\n return undefined\n }\n return {\n pageIndex: isBackendPagination ? pagination.currentPage - 1 : pageIndex,\n pageSize,\n }\n }, [pagination, isBackendPagination, pageIndex, pageSize])\n\n const tableGetPaginationRowModel = React.useMemo(() => {\n return pagination && !isBackendPagination ? getPaginationRowModel() : undefined\n }, [pagination, isBackendPagination])\n\n const tableOnPaginationChange = React.useCallback(\n (updater: Updater<PaginationState>) => {\n if (typeof updater === 'function') {\n const newState = updater({ pageIndex, pageSize })\n setPageIndex(newState.pageIndex)\n }\n },\n [pageIndex, pageSize],\n )\n\n const groupConfig = React.useMemo(() => {\n if (!grouping) return undefined\n\n return {\n renderGroupRow: grouping.renderGroupRow,\n renderGroupSelector: selectColumn?.GroupSelectCell,\n isGroupOpen,\n toggleGroup,\n getRowId,\n }\n }, [grouping, selectColumn, isGroupOpen, toggleGroup, getRowId])\n\n const table = useReactTable({\n data,\n columns: tableColumns,\n getCoreRowModel: getCoreRowModel(),\n getPaginationRowModel: tableGetPaginationRowModel,\n getSubRows: grouping?.getSubRows,\n getExpandedRowModel: grouping ? getExpandedRowModel() : undefined,\n onColumnVisibilityChange: setColumnVisibilityState,\n enableRowSelection: !!selection,\n getRowId,\n state: {\n columnVisibility: columnVisibilityState,\n rowSelection,\n pagination: tablePaginationState,\n expanded: grouping ? true : undefined,\n },\n manualPagination: isBackendPagination,\n onPaginationChange: isBackendPagination ? undefined : tableOnPaginationChange,\n })\n\n return (\n <div className={cn('flex h-full min-h-0 w-full flex-1 flex-col gap-2 overflow-hidden', className)}>\n <DataTableToolbar\n table={table}\n columnVisibility={columnVisibilityConfig}\n primaryFilters={primaryFilters}\n secondaryFilters={secondaryFilters}\n activePrimaryFiltersCount={activePrimaryFiltersCount}\n activeSecondaryFiltersCount={activeSecondaryFiltersCount}\n labels={labels}\n selection={toolbarSelectionProps}\n />\n\n <div className=\"flex min-h-0 flex-1 flex-col rounded-md border bg-white\">\n <div className=\"min-h-0 flex-1 overflow-auto overscroll-x-none overscroll-y-auto\">\n <TableComponent className=\"w-full\" tableClassName={cn('min-w-max', tableClassName)}>\n <TableHeader className=\"sticky top-0 z-10 bg-neutral-50\">\n {table.getHeaderGroups().map((headerGroup) => (\n <TableRow key={headerGroup.id}>\n {headerGroup.headers.map((header) => (\n <TableHead\n key={header.id}\n style={{ width: header.column.columnDef.size }}\n className=\"h-auto p-0 text-neutral-900\"\n >\n <div className={getDataTableCellClassName({ columnId: header.column.id, kind: 'header' })}>\n {header.isPlaceholder\n ? null\n : flexRender(header.column.columnDef.header, header.getContext())}\n </div>\n </TableHead>\n ))}\n </TableRow>\n ))}\n </TableHeader>\n <DataTableBody\n table={table}\n isLoading={isLoading}\n pageSize={pageSize}\n getRowId={getRowId}\n rowSelection={rowSelection}\n emptyState={emptyState}\n group={groupConfig}\n />\n </TableComponent>\n </div>\n {!!pagination && (\n <div className=\"border-t px-4 py-2\">\n <DataTablePagination\n table={table}\n total={total}\n pageSize={pageSize}\n currentPage={currentPage}\n totalPages={totalPages}\n onPageChange={handlePageChange}\n isLoading={pagination.isLoading}\n labels={pagination.labels}\n />\n </div>\n )}\n </div>\n </div>\n )\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;AAgCO;AAAyC;AACrC;AACT;AACmD;AACjC;AACN;AACZ;AACA;AACA;AACA;AAC4B;AACE;AAC9B;AACS;AACE;AACI;AACf;AACa;AACJ;AACM;AACf;AACA;AAEF;AACE;AACA;AAEA;AACA;AAEA;AAEA;AAA4B;AACmC;AAC9C;AAGjB;AAEA;AACA;AACA;AAEA;AAAuE;AAC/D;AACN;AACA;AAGF;AAA2B;AAC+C;AAC5D;AAGd;AAAqC;AACnC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGF;AACE;AACA;AAA2C;AAG7C;AACE;AACE;AAAO;AACyB;AACF;AACC;AACO;AACL;AACb;AACpB;AAEF;AAAO;AAGT;AACA;AAEA;AACE;AACE;AAAO;AAET;AAAO;AACyD;AAC9D;AACF;AAGF;AACE;AAAsE;AAGxE;AAAsC;AAElC;AACE;AACA;AAA+B;AACjC;AACF;AACoB;AAGtB;AACE;AAEA;AAAO;AACoB;AACU;AACnC;AACA;AACA;AACF;AAGF;AAA4B;AAC1B;AACS;AACwB;AACV;AACD;AACkC;AAC9B;AACJ;AACtB;AACO;AACa;AAClB;AACY;AACgB;AAC9B;AACkB;AACoC;AAGxD;AAEI;AAAA;AAAC;AAAA;AACC;AACkB;AAClB;AACA;AACA;AACA;AACA;AACW;AAAA;AACb;AAGE;AAEI;AAIQ;AAAC;AAAA;AAE8C;AACnC;AAMV;AAAA;AARY;AAatB;AACA;AAAC;AAAA;AACC;AACA;AACA;AACA;AACA;AACA;AACO;AAAA;AACT;AAEJ;AAGI;AAAC;AAAA;AACC;AACA;AACA;AACA;AACA;AACc;AACQ;AACH;AAAA;AAEvB;AAEJ;AAGN;;"}
1
+ {"version":3,"file":"DataTable.js","sources":["../../../src/components/DataTable/DataTable.tsx"],"sourcesContent":["'use client'\n\nimport {\n flexRender,\n getCoreRowModel,\n getExpandedRowModel,\n getPaginationRowModel,\n useReactTable,\n VisibilityState,\n PaginationState,\n Updater,\n} from '@tanstack/react-table'\nimport * as React from 'react'\n\nimport { Table as TableComponent, TableHeader, TableHead, TableRow } from '../Table'\n\nimport { DataTableBody } from './components/DataTableBody'\nimport { getDataTableCellClassName } from './components/dataTableCellStyles'\nimport { DataTablePagination } from './components/DataTablePagination'\nimport { DataTableToolbar } from './components/DataTableToolbar'\nimport { useGroupExpansion } from './hooks/useGroupExpansion'\nimport { useSelectColumn } from './hooks/useSelectColumn'\nimport { useSelection } from './hooks/useSelection'\nimport { DataTableProps } from './types'\nimport { flattenLeaves } from './utils'\n\nimport { cn } from '@/lib/utils'\n\n/**\n * A feature-rich data table component built on top of TanStack Table.\n * Provides sorting, filtering, pagination, and column visibility controls.\n */\nexport function DataTable<TData extends object>({\n columns: userColumns,\n data,\n getRowId = (row: TData) => (row as { id: string }).id,\n columnVisibility: columnVisibilityConfig,\n isLoading = false,\n pagination,\n grouping,\n primaryFilters,\n secondaryFilters,\n activePrimaryFiltersCount = 0,\n activeSecondaryFiltersCount = 0,\n selection,\n labels = {\n filters: 'Filters',\n moreFilters: 'More filters',\n },\n emptyState = {\n title: 'No data available',\n description: 'Try adjusting your filters or search criteria',\n },\n className,\n tableClassName,\n}: DataTableProps<TData>) {\n const [internalColumnVisibility, setInternalColumnVisibility] = React.useState<VisibilityState>({})\n const [pageIndex, setPageIndex] = React.useState(0)\n\n const columnVisibilityState = columnVisibilityConfig?.state ?? internalColumnVisibility\n const setColumnVisibilityState = columnVisibilityConfig?.onChange ?? setInternalColumnVisibility\n\n const { isGroupOpen, toggleGroup } = useGroupExpansion({ grouping, data, getRowId })\n\n const selectionData = React.useMemo(\n () => (grouping ? flattenLeaves(data, grouping.getSubRows) : data),\n [grouping, data],\n )\n\n const isBackendPagination = pagination !== undefined && 'onPageChange' in pagination\n\n const total = isBackendPagination ? pagination.total : data.length\n const pageSize = pagination?.pageSize ?? (isLoading ? 10 : data.length)\n const totalPages = Math.ceil(total / pageSize)\n\n const { selectedIds, handleRowSelect, handleSelectAll } = useSelection({\n data: selectionData,\n getRowId,\n selection,\n })\n\n const rowSelection = React.useMemo(\n () => Object.fromEntries(Array.from(selectedIds).map((id) => [id, true])),\n [selectedIds],\n )\n\n const selectColumn = useSelectColumn({\n selection,\n selectedIds,\n selectionData,\n getRowId,\n handleRowSelect,\n handleSelectAll,\n isBackendPagination,\n isLoading,\n })\n\n const tableColumns = React.useMemo(() => {\n if (!selectColumn) return userColumns\n return [selectColumn.column, ...userColumns]\n }, [userColumns, selectColumn])\n\n const toolbarSelectionProps = React.useMemo(() => {\n if (isBackendPagination && selection) {\n return {\n isSelectionStarted: selection.isSelectionStarted,\n onSelectionStart: selection.onSelectionStart,\n onSelectionCancel: selection.onSelectionCancel,\n selectionSummary: selection.labels?.selectionSummary,\n isSelectionDisabled: selection.isSelectionDisabled,\n labels: selection.labels,\n }\n }\n return undefined\n }, [isBackendPagination, selection])\n\n const currentPage = isBackendPagination ? pagination.currentPage : pageIndex + 1\n const handlePageChange = isBackendPagination ? pagination.onPageChange : (page: number) => setPageIndex(page - 1)\n\n const tablePaginationState = React.useMemo(() => {\n if (!pagination) {\n return undefined\n }\n return {\n pageIndex: isBackendPagination ? pagination.currentPage - 1 : pageIndex,\n pageSize,\n }\n }, [pagination, isBackendPagination, pageIndex, pageSize])\n\n const tableGetPaginationRowModel = React.useMemo(() => {\n return pagination && !isBackendPagination ? getPaginationRowModel() : undefined\n }, [pagination, isBackendPagination])\n\n const tableOnPaginationChange = React.useCallback(\n (updater: Updater<PaginationState>) => {\n if (typeof updater === 'function') {\n const newState = updater({ pageIndex, pageSize })\n setPageIndex(newState.pageIndex)\n }\n },\n [pageIndex, pageSize],\n )\n\n const groupConfig = React.useMemo(() => {\n if (!grouping) return undefined\n\n return {\n renderGroupRow: grouping.renderGroupRow,\n renderGroupSelector: selectColumn?.GroupSelectCell,\n isGroupOpen,\n toggleGroup,\n getRowId,\n }\n }, [grouping, selectColumn, isGroupOpen, toggleGroup, getRowId])\n\n const table = useReactTable({\n data,\n columns: tableColumns,\n getCoreRowModel: getCoreRowModel(),\n getPaginationRowModel: tableGetPaginationRowModel,\n getSubRows: grouping?.getSubRows,\n getExpandedRowModel: grouping ? getExpandedRowModel() : undefined,\n onColumnVisibilityChange: setColumnVisibilityState,\n enableRowSelection: !!selection,\n getRowId,\n state: {\n columnVisibility: columnVisibilityState,\n rowSelection,\n pagination: tablePaginationState,\n expanded: grouping ? true : undefined,\n },\n manualPagination: isBackendPagination,\n onPaginationChange: isBackendPagination ? undefined : tableOnPaginationChange,\n })\n\n const isEmptyState = !isLoading && table.getRowModel().rows.length === 0\n\n return (\n <div className={cn('flex h-full min-h-0 w-full flex-1 flex-col gap-2 overflow-hidden', className)}>\n <DataTableToolbar\n table={table}\n columnVisibility={columnVisibilityConfig}\n primaryFilters={primaryFilters}\n secondaryFilters={secondaryFilters}\n activePrimaryFiltersCount={activePrimaryFiltersCount}\n activeSecondaryFiltersCount={activeSecondaryFiltersCount}\n labels={labels}\n selection={toolbarSelectionProps}\n />\n\n <div className=\"flex min-h-0 flex-1 flex-col rounded-md border bg-white\">\n <div className=\"min-h-0 flex-1 overflow-auto overscroll-x-none overscroll-y-auto\">\n <TableComponent\n className={cn('w-full', isEmptyState && 'h-full')}\n tableClassName={cn('min-w-max', tableClassName)}\n >\n <TableHeader className=\"sticky top-0 z-10 bg-neutral-50\">\n {table.getHeaderGroups().map((headerGroup) => (\n <TableRow key={headerGroup.id}>\n {headerGroup.headers.map((header) => (\n <TableHead\n key={header.id}\n style={{ width: header.column.columnDef.size }}\n className=\"h-auto p-0 text-neutral-900\"\n >\n <div className={getDataTableCellClassName({ columnId: header.column.id, kind: 'header' })}>\n {header.isPlaceholder\n ? null\n : flexRender(header.column.columnDef.header, header.getContext())}\n </div>\n </TableHead>\n ))}\n </TableRow>\n ))}\n </TableHeader>\n <DataTableBody\n table={table}\n isLoading={isLoading}\n pageSize={pageSize}\n getRowId={getRowId}\n rowSelection={rowSelection}\n emptyState={emptyState}\n group={groupConfig}\n />\n </TableComponent>\n </div>\n {!!pagination && (\n <div className=\"border-t px-4 py-2\">\n <DataTablePagination\n table={table}\n total={total}\n pageSize={pageSize}\n currentPage={currentPage}\n totalPages={totalPages}\n onPageChange={handlePageChange}\n isLoading={pagination.isLoading}\n labels={pagination.labels}\n />\n </div>\n )}\n </div>\n </div>\n )\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;AAgCO;AAAyC;AACrC;AACT;AACmD;AACjC;AACN;AACZ;AACA;AACA;AACA;AAC4B;AACE;AAC9B;AACS;AACE;AACI;AACf;AACa;AACJ;AACM;AACf;AACA;AAEF;AACE;AACA;AAEA;AACA;AAEA;AAEA;AAA4B;AACmC;AAC9C;AAGjB;AAEA;AACA;AACA;AAEA;AAAuE;AAC/D;AACN;AACA;AAGF;AAA2B;AAC+C;AAC5D;AAGd;AAAqC;AACnC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGF;AACE;AACA;AAA2C;AAG7C;AACE;AACE;AAAO;AACyB;AACF;AACC;AACO;AACL;AACb;AACpB;AAEF;AAAO;AAGT;AACA;AAEA;AACE;AACE;AAAO;AAET;AAAO;AACyD;AAC9D;AACF;AAGF;AACE;AAAsE;AAGxE;AAAsC;AAElC;AACE;AACA;AAA+B;AACjC;AACF;AACoB;AAGtB;AACE;AAEA;AAAO;AACoB;AACU;AACnC;AACA;AACA;AACF;AAGF;AAA4B;AAC1B;AACS;AACwB;AACV;AACD;AACkC;AAC9B;AACJ;AACtB;AACO;AACa;AAClB;AACY;AACgB;AAC9B;AACkB;AACoC;AAGxD;AAEA;AAEI;AAAA;AAAC;AAAA;AACC;AACkB;AAClB;AACA;AACA;AACA;AACA;AACW;AAAA;AACb;AAGE;AACE;AAAC;AAAA;AACiD;AACF;AAE9C;AAIQ;AAAC;AAAA;AAE8C;AACnC;AAMV;AAAA;AARY;AAatB;AACA;AAAC;AAAA;AACC;AACA;AACA;AACA;AACA;AACA;AACO;AAAA;AACT;AAAA;AAAA;AAEJ;AAGI;AAAC;AAAA;AACC;AACA;AACA;AACA;AACA;AACc;AACQ;AACH;AAAA;AAEvB;AAEJ;AAGN;;"}
@@ -60,11 +60,11 @@ function DataTableBody({
60
60
  const rows = table.getRowModel().rows;
61
61
  if (!rows.length) {
62
62
  const visibleColumns = table.getAllColumns().filter((col) => col.getIsVisible());
63
- return /* @__PURE__ */ jsx(TableBody, { children: /* @__PURE__ */ jsx(TableRow, { className: "h-full", children: /* @__PURE__ */ jsx(TableCell, { colSpan: visibleColumns.length, className: "h-full p-0", children: /* @__PURE__ */ jsxs("div", { className: "mt-20 flex h-full w-full flex-col items-center justify-center", children: [
63
+ return /* @__PURE__ */ jsx(TableBody, { children: /* @__PURE__ */ jsx(TableRow, { className: "h-full", children: /* @__PURE__ */ jsx(TableCell, { colSpan: visibleColumns.length, className: "h-full p-0", children: /* @__PURE__ */ jsxs("div", { className: "flex h-full w-full flex-col items-center justify-center", children: [
64
64
  emptyState.imageRoute && /* @__PURE__ */ jsx(
65
65
  "img",
66
66
  {
67
- className: cn("mb-6 w-[200px] md:w-[300px]", emptyState.imageClassName),
67
+ className: cn("mb-6 w-50 md:w-75", emptyState.imageClassName),
68
68
  src: emptyState.imageRoute,
69
69
  alt: emptyState.imageAlt ?? "No data available"
70
70
  }