periplo-ui 3.19.1 → 3.21.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 (26) hide show
  1. package/dist/components/Button/Button.d.ts +66 -4
  2. package/dist/components/Button/Button.js +11 -8
  3. package/dist/components/Button/Button.js.map +1 -1
  4. package/dist/components/Carousel/Carousel.d.ts +28 -2
  5. package/dist/components/DataTable/DataTable.d.ts +1 -30
  6. package/dist/components/DataTable/DataTable.js +41 -159
  7. package/dist/components/DataTable/DataTable.js.map +1 -1
  8. package/dist/components/DataTable/components/DataTableBody.d.ts +12 -0
  9. package/dist/components/DataTable/components/DataTableBody.js +62 -0
  10. package/dist/components/DataTable/components/DataTableBody.js.map +1 -0
  11. package/dist/components/DataTable/components/DataTableToolbar.d.ts +17 -0
  12. package/dist/components/DataTable/components/DataTableToolbar.js +83 -0
  13. package/dist/components/DataTable/components/DataTableToolbar.js.map +1 -0
  14. package/dist/components/DataTable/hooks/useTableSelection.d.ts +14 -0
  15. package/dist/components/DataTable/hooks/useTableSelection.js +55 -0
  16. package/dist/components/DataTable/hooks/useTableSelection.js.map +1 -0
  17. package/dist/components/DatePicker/DatePicker.d.ts +10 -6
  18. package/dist/components/DatePicker/DatePicker.js.map +1 -1
  19. package/dist/components/Pagination/Pagination.d.ts +4 -4
  20. package/dist/components/Pagination/Pagination.js +13 -13
  21. package/dist/components/Pagination/Pagination.js.map +1 -1
  22. package/dist/components/Sidebar/Sidebar.d.ts +14 -1
  23. package/dist/lib/dateUtils.d.ts +2 -2
  24. package/dist/lib/dateUtils.js +1 -1
  25. package/dist/lib/dateUtils.js.map +1 -1
  26. package/package.json +1 -1
@@ -1,11 +1,11 @@
1
+ import { Icon, IconProps as PhosphorIconProps } from '@phosphor-icons/react';
1
2
  import { VariantProps } from 'class-variance-authority';
2
- import { Icon } from '@phosphor-icons/react';
3
3
  import * as React from 'react';
4
4
  declare const buttonVariants: (props?: ({
5
5
  variant?: "primary" | "secondary" | "success" | "error" | "text" | "input" | "ghost" | "inverse-ghost" | null | undefined;
6
6
  size?: "sm" | "md" | "lg" | null | undefined;
7
7
  } & import('class-variance-authority/types').ClassProp) | undefined) => string;
8
- export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
8
+ export type ButtonProps = {
9
9
  /**
10
10
  * The visual style of the button.
11
11
  * @default primary
@@ -44,7 +44,17 @@ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElemen
44
44
  * Not displayed when iconOnly is true.
45
45
  */
46
46
  children?: React.ReactNode;
47
- }
47
+ /**
48
+ * When true, renders the button as a child component using Radix Slot.
49
+ * Useful for custom wrappers or links.
50
+ */
51
+ asChild?: boolean;
52
+ /**
53
+ * Custom props to pass to the icons (StartIcon, EndIcon, SpinnerGap).
54
+ * Allows overriding size, weight, color, etc.
55
+ */
56
+ iconProps?: Partial<PhosphorIconProps>;
57
+ } & React.ButtonHTMLAttributes<HTMLButtonElement> & VariantProps<typeof buttonVariants>;
48
58
  /**
49
59
  * A versatile button component that supports multiple variants, sizes, and icon placements.
50
60
  *
@@ -61,5 +71,57 @@ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElemen
61
71
  * // Loading state
62
72
  * <Button loading>Processing...</Button>
63
73
  */
64
- declare const Button: React.ForwardRefExoticComponent<ButtonProps & React.RefAttributes<HTMLButtonElement>>;
74
+ declare const Button: React.ForwardRefExoticComponent<{
75
+ /**
76
+ * The visual style of the button.
77
+ * @default primary
78
+ */
79
+ variant?: "primary" | "secondary" | "ghost" | "inverse-ghost" | "text" | "error" | "success";
80
+ /**
81
+ * The size of the button, affecting height, padding, and font size.
82
+ * @default md
83
+ */
84
+ size?: "sm" | "md" | "lg";
85
+ /**
86
+ * Icon component to display at the start of the button content.
87
+ * Use Phosphor icons for consistent styling.
88
+ * @example <Button StartIcon={House}>Home</Button>
89
+ */
90
+ StartIcon?: Icon;
91
+ /**
92
+ * Icon component to display at the end of the button content.
93
+ * Use Phosphor icons for consistent styling.
94
+ * @example <Button EndIcon={ArrowRight}>Next</Button>
95
+ */
96
+ EndIcon?: Icon;
97
+ /**
98
+ * When true, displays a loading spinner and disables the button.
99
+ * The spinner replaces the StartIcon if present.
100
+ * @default false
101
+ */
102
+ loading?: boolean;
103
+ /**
104
+ * When true, creates a circular button with only the StartIcon visible.
105
+ * @default false
106
+ */
107
+ iconOnly?: boolean;
108
+ /**
109
+ * The content to display inside the button.
110
+ * Not displayed when iconOnly is true.
111
+ */
112
+ children?: React.ReactNode;
113
+ /**
114
+ * When true, renders the button as a child component using Radix Slot.
115
+ * Useful for custom wrappers or links.
116
+ */
117
+ asChild?: boolean;
118
+ /**
119
+ * Custom props to pass to the icons (StartIcon, EndIcon, SpinnerGap).
120
+ * Allows overriding size, weight, color, etc.
121
+ */
122
+ iconProps?: Partial<PhosphorIconProps>;
123
+ } & React.ButtonHTMLAttributes<HTMLButtonElement> & VariantProps<(props?: ({
124
+ variant?: "primary" | "secondary" | "success" | "error" | "text" | "input" | "ghost" | "inverse-ghost" | null | undefined;
125
+ size?: "sm" | "md" | "lg" | null | undefined;
126
+ } & import('class-variance-authority/types').ClassProp) | undefined) => string> & React.RefAttributes<HTMLButtonElement>>;
65
127
  export { Button, buttonVariants };
@@ -1,9 +1,10 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { SpinnerGap } from '@phosphor-icons/react/dist/ssr';
2
3
  import { cva } from 'class-variance-authority';
3
4
  import * as React from 'react';
4
5
  import { cn } from '../../lib/utils.js';
5
- import { SpinnerGap } from '@phosphor-icons/react/dist/ssr';
6
6
  import { typographyVariants } from '../Typography/Typography.js';
7
+ import { Slot } from '@radix-ui/react-slot';
7
8
 
8
9
  const buttonVariants = cva(
9
10
  "inline-flex items-center justify-center gap-2 rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
@@ -45,8 +46,8 @@ const buttonVariants = cva(
45
46
  }
46
47
  );
47
48
  const Button = React.forwardRef(
48
- ({ className, variant = "primary", size = "md", StartIcon, EndIcon, loading, iconOnly, ...props }, ref) => {
49
- const iconProps = () => {
49
+ ({ className, variant = "primary", size = "md", StartIcon, EndIcon, loading, iconOnly, iconProps = {}, asChild = false, ...props }, ref) => {
50
+ const defaultIconProps = () => {
50
51
  switch (size) {
51
52
  case "sm":
52
53
  return { size: 16, weight: "regular" };
@@ -56,6 +57,7 @@ const Button = React.forwardRef(
56
57
  return { size: 18, weight: "regular" };
57
58
  }
58
59
  };
60
+ const mergedIconProps = { ...defaultIconProps(), ...iconProps };
59
61
  const getIconOnlyClasses = () => {
60
62
  switch (size) {
61
63
  case "sm":
@@ -66,8 +68,9 @@ const Button = React.forwardRef(
66
68
  return "h-10 w-10";
67
69
  }
68
70
  };
71
+ const Comp = asChild ? Slot : "button";
69
72
  return /* @__PURE__ */ jsxs(
70
- "button",
73
+ Comp,
71
74
  {
72
75
  className: cn(
73
76
  buttonVariants({ variant, size, className }),
@@ -76,13 +79,13 @@ const Button = React.forwardRef(
76
79
  className
77
80
  ),
78
81
  ref,
79
- disabled: loading,
82
+ disabled: loading && !asChild,
80
83
  ...props,
81
84
  children: [
82
- loading && /* @__PURE__ */ jsx(SpinnerGap, { ...iconProps(), className: "animate-spin", "aria-label": "Loading" }),
83
- StartIcon && !loading && /* @__PURE__ */ jsx(StartIcon, { ...iconProps() }),
85
+ loading && /* @__PURE__ */ jsx(SpinnerGap, { ...defaultIconProps(), className: "animate-spin", "aria-label": "Loading" }),
86
+ StartIcon && !loading && /* @__PURE__ */ jsx(StartIcon, { ...mergedIconProps }),
84
87
  iconOnly ? /* @__PURE__ */ jsx("span", { className: "sr-only", children: props.children }) : props.children,
85
- EndIcon && !iconOnly && /* @__PURE__ */ jsx(EndIcon, { ...iconProps() })
88
+ EndIcon && !iconOnly && /* @__PURE__ */ jsx(EndIcon, { ...mergedIconProps })
86
89
  ]
87
90
  }
88
91
  );
@@ -1 +1 @@
1
- {"version":3,"file":"Button.js","sources":["../../../src/components/Button/Button.tsx"],"sourcesContent":["import { cva, type VariantProps } from 'class-variance-authority'\nimport * as React from 'react'\nimport { cn } from '../../lib/utils'\nimport { Icon, IconWeight } from '@phosphor-icons/react'\nimport { SpinnerGap } from '@phosphor-icons/react/dist/ssr'\nimport { typographyVariants } from '../Typography'\n\nconst buttonVariants = cva(\n 'inline-flex items-center justify-center gap-2 rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',\n {\n variants: {\n /**\n * Defines the visual style of the button\n */\n variant: {\n primary:\n 'bg-primary-500 text-primary-foreground hover:bg-primary-700 active:bg-primary-900 disabled:bg-background-200 dark:disabled:bg-background-200 dark:disabled:text-neutral',\n secondary:\n 'bg-neutral-950 text-neutral-foreground hover:bg-neutral-800 active:bg-neutral-700 disabled:bg-neutral-200 dark:bg-neutral-600 dark:hover:bg-neutral-500 dark:disable:bg-neutral-900 disabled:text-neutral-600 dark:disabled:text-neutral-300',\n ghost:\n 'border border-neutral-500 text-primary-foreground hover:bg-neutral-100/50 active:bg-neutral-200/50 dark:border-neutral dark:text-neutral-50 dark:active:bg-neutral-600/50 disabled:border-neutral-500 dark:hover:bg-neutral/50 disabled:text-neutral-500 dark:disabled:border-neutral-300 dark:disabled:text-neutral-300',\n 'inverse-ghost':\n 'border border-neutral-50 text-neutral-50 hover:bg-neutral/50 hover:border-neutral-50 active:bg-neutral-600/50 dark:border-neutral-500 dark:text-primary-foreground dark:hover:bg-neutral-100/50 dark:active:bg-neutral-200/50 disabled:border-neutral-300 disabled:text-neutral-300 dark:disabled:border-neutral-500 dark:disabled:text-neutral-500',\n text: 'hover:bg-neutral-50 active:bg-neutral-100 text-primary-foreground disabled:text-neutral-500 dark:disable:text-neutral-300 dark:text-neutral-50 dark:hover:bg-neutral dark:active:bg-neutral-600',\n error:\n 'bg-error text-white hover:bg-error-800 active:bg-error-900 disabled:bg-error-200 disabled:text-neutral-600',\n success:\n 'bg-success text-white hover:bg-success-800 active:bg-success-900 disabled:bg-success-200 disabled:text-neutral-600',\n input:\n 'border border-neutral-200 bg-white text-neutral-950 hover:bg-neutral-50 active:bg-neutral-100 focus-visible:border-neutral-950 disabled:bg-neutral-50 dark:border-neutral-500 dark:bg-neutral-950 dark:text-neutral-50 dark:hover:bg-neutral-800 dark:active:bg-neutral-700 dark:focus-visible:border-neutral-500 focus-visible:ring-0',\n },\n /**\n * Controls the size and padding of the button\n */\n size: {\n sm: 'h-8 px-3 text-sm',\n md: 'h-10 px-4 text-sm',\n lg: 'h-12 px-6 text-base',\n },\n },\n compoundVariants: [\n {\n variant: 'input',\n size: ['sm', 'md', 'lg'],\n className: typographyVariants({ variant: 'body-lg' }),\n },\n ],\n defaultVariants: {\n variant: 'primary',\n size: 'md',\n },\n },\n)\n\nexport interface ButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {\n /**\n * The visual style of the button.\n * @default primary\n */\n variant?: 'primary' | 'secondary' | 'ghost' | 'inverse-ghost' | 'text' | 'error' | 'success'\n\n /**\n * The size of the button, affecting height, padding, and font size.\n * @default md\n */\n size?: 'sm' | 'md' | 'lg'\n\n /**\n * Icon component to display at the start of the button content.\n * Use Phosphor icons for consistent styling.\n * @example <Button StartIcon={House}>Home</Button>\n */\n StartIcon?: Icon\n\n /**\n * Icon component to display at the end of the button content.\n * Use Phosphor icons for consistent styling.\n * @example <Button EndIcon={ArrowRight}>Next</Button>\n */\n EndIcon?: Icon\n\n /**\n * When true, displays a loading spinner and disables the button.\n * The spinner replaces the StartIcon if present.\n * @default false\n */\n loading?: boolean\n\n /**\n * When true, creates a circular button with only the StartIcon visible.\n * @default false\n */\n iconOnly?: boolean\n\n /**\n * The content to display inside the button.\n * Not displayed when iconOnly is true.\n */\n children?: React.ReactNode\n}\n\n/**\n * A versatile button component that supports multiple variants, sizes, and icon placements.\n *\n * @example\n * // Basic usage\n * <Button>Click me</Button>\n *\n * // With icon\n * <Button StartIcon={House}>Home</Button>\n *\n * // Icon only button\n * <Button StartIcon={Plus} iconOnly aria-label=\"Add item\" />\n *\n * // Loading state\n * <Button loading>Processing...</Button>\n */\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n ({ className, variant = 'primary', size = 'md', StartIcon, EndIcon, loading, iconOnly, ...props }, ref) => {\n const iconProps = (): { size: number; weight: IconWeight } => {\n switch (size) {\n case 'sm':\n return { size: 16, weight: 'regular' }\n case 'lg':\n return { size: 20, weight: 'regular' }\n default:\n return { size: 18, weight: 'regular' }\n }\n }\n\n const getIconOnlyClasses = () => {\n switch (size) {\n case 'sm':\n return 'h-8 w-8'\n case 'lg':\n return 'h-12 w-12'\n default:\n return 'h-10 w-10'\n }\n }\n\n return (\n <button\n className={cn(\n buttonVariants({ variant, size, className }),\n iconOnly && 'rounded-full p-0',\n iconOnly && getIconOnlyClasses(),\n className,\n )}\n ref={ref}\n disabled={loading}\n {...props}\n >\n {loading && <SpinnerGap {...iconProps()} className=\"animate-spin\" aria-label=\"Loading\" />}\n {StartIcon && !loading && <StartIcon {...iconProps()} />}\n {iconOnly ? <span className=\"sr-only\">{props.children}</span> : props.children}\n {EndIcon && !iconOnly && <EndIcon {...iconProps()} />}\n </button>\n )\n },\n)\nButton.displayName = 'Button'\n\nexport { Button, buttonVariants }\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Button.js","sources":["../../../src/components/Button/Button.tsx"],"sourcesContent":["import { Icon, IconWeight, IconProps as PhosphorIconProps } from '@phosphor-icons/react'\nimport { SpinnerGap } from '@phosphor-icons/react/dist/ssr'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport * as React from 'react'\n\nimport { cn } from '../../lib/utils'\nimport { typographyVariants } from '../Typography'\nimport { Slot } from '@radix-ui/react-slot'\n\nconst buttonVariants = cva(\n 'inline-flex items-center justify-center gap-2 rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',\n {\n variants: {\n /**\n * Defines the visual style of the button\n */\n variant: {\n primary:\n 'bg-primary-500 text-primary-foreground hover:bg-primary-700 active:bg-primary-900 disabled:bg-background-200 dark:disabled:bg-background-200 dark:disabled:text-neutral',\n secondary:\n 'bg-neutral-950 text-neutral-foreground hover:bg-neutral-800 active:bg-neutral-700 disabled:bg-neutral-200 dark:bg-neutral-600 dark:hover:bg-neutral-500 dark:disable:bg-neutral-900 disabled:text-neutral-600 dark:disabled:text-neutral-300',\n ghost:\n 'border border-neutral-500 text-primary-foreground hover:bg-neutral-100/50 active:bg-neutral-200/50 dark:border-neutral dark:text-neutral-50 dark:active:bg-neutral-600/50 disabled:border-neutral-500 dark:hover:bg-neutral/50 disabled:text-neutral-500 dark:disabled:border-neutral-300 dark:disabled:text-neutral-300',\n 'inverse-ghost':\n 'border border-neutral-50 text-neutral-50 hover:bg-neutral/50 hover:border-neutral-50 active:bg-neutral-600/50 dark:border-neutral-500 dark:text-primary-foreground dark:hover:bg-neutral-100/50 dark:active:bg-neutral-200/50 disabled:border-neutral-300 disabled:text-neutral-300 dark:disabled:border-neutral-500 dark:disabled:text-neutral-500',\n text: 'hover:bg-neutral-50 active:bg-neutral-100 text-primary-foreground disabled:text-neutral-500 dark:disable:text-neutral-300 dark:text-neutral-50 dark:hover:bg-neutral dark:active:bg-neutral-600',\n error:\n 'bg-error text-white hover:bg-error-800 active:bg-error-900 disabled:bg-error-200 disabled:text-neutral-600',\n success:\n 'bg-success text-white hover:bg-success-800 active:bg-success-900 disabled:bg-success-200 disabled:text-neutral-600',\n input:\n 'border border-neutral-200 bg-white text-neutral-950 hover:bg-neutral-50 active:bg-neutral-100 focus-visible:border-neutral-950 disabled:bg-neutral-50 dark:border-neutral-500 dark:bg-neutral-950 dark:text-neutral-50 dark:hover:bg-neutral-800 dark:active:bg-neutral-700 dark:focus-visible:border-neutral-500 focus-visible:ring-0',\n },\n /**\n * Controls the size and padding of the button\n */\n size: {\n sm: 'h-8 px-3 text-sm',\n md: 'h-10 px-4 text-sm',\n lg: 'h-12 px-6 text-base',\n },\n },\n compoundVariants: [\n {\n variant: 'input',\n size: ['sm', 'md', 'lg'],\n className: typographyVariants({ variant: 'body-lg' }),\n },\n ],\n defaultVariants: {\n variant: 'primary',\n size: 'md',\n },\n },\n)\n\nexport type ButtonProps = {\n /**\n * The visual style of the button.\n * @default primary\n */\n variant?: 'primary' | 'secondary' | 'ghost' | 'inverse-ghost' | 'text' | 'error' | 'success'\n\n /**\n * The size of the button, affecting height, padding, and font size.\n * @default md\n */\n size?: 'sm' | 'md' | 'lg'\n\n /**\n * Icon component to display at the start of the button content.\n * Use Phosphor icons for consistent styling.\n * @example <Button StartIcon={House}>Home</Button>\n */\n StartIcon?: Icon\n\n /**\n * Icon component to display at the end of the button content.\n * Use Phosphor icons for consistent styling.\n * @example <Button EndIcon={ArrowRight}>Next</Button>\n */\n EndIcon?: Icon\n\n /**\n * When true, displays a loading spinner and disables the button.\n * The spinner replaces the StartIcon if present.\n * @default false\n */\n loading?: boolean\n\n /**\n * When true, creates a circular button with only the StartIcon visible.\n * @default false\n */\n iconOnly?: boolean\n\n /**\n * The content to display inside the button.\n * Not displayed when iconOnly is true.\n */\n children?: React.ReactNode\n\n /**\n * When true, renders the button as a child component using Radix Slot.\n * Useful for custom wrappers or links.\n */\n asChild?: boolean\n\n /**\n * Custom props to pass to the icons (StartIcon, EndIcon, SpinnerGap).\n * Allows overriding size, weight, color, etc.\n */\n iconProps?: Partial<PhosphorIconProps>\n} & React.ButtonHTMLAttributes<HTMLButtonElement> &\n VariantProps<typeof buttonVariants>\n\n/**\n * A versatile button component that supports multiple variants, sizes, and icon placements.\n *\n * @example\n * // Basic usage\n * <Button>Click me</Button>\n *\n * // With icon\n * <Button StartIcon={House}>Home</Button>\n *\n * // Icon only button\n * <Button StartIcon={Plus} iconOnly aria-label=\"Add item\" />\n *\n * // Loading state\n * <Button loading>Processing...</Button>\n */\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n (\n { className, variant = 'primary', size = 'md', StartIcon, EndIcon, loading, iconOnly, iconProps = {}, asChild = false, ...props },\n ref,\n ) => {\n const defaultIconProps = (): { size: number; weight: IconWeight } => {\n switch (size) {\n case 'sm':\n return { size: 16, weight: 'regular' }\n case 'lg':\n return { size: 20, weight: 'regular' }\n default:\n return { size: 18, weight: 'regular' }\n }\n }\n\n const mergedIconProps = { ...defaultIconProps(), ...iconProps }\n\n const getIconOnlyClasses = () => {\n switch (size) {\n case 'sm':\n return 'h-8 w-8'\n case 'lg':\n return 'h-12 w-12'\n default:\n return 'h-10 w-10'\n }\n }\n\n const Comp = asChild ? Slot : 'button'\n\n return (\n <Comp\n className={cn(\n buttonVariants({ variant, size, className }),\n iconOnly && 'rounded-full p-0',\n iconOnly && getIconOnlyClasses(),\n className,\n )}\n ref={ref}\n disabled={loading && !asChild}\n {...props}\n >\n {loading && <SpinnerGap {...defaultIconProps()} className=\"animate-spin\" aria-label=\"Loading\" />}\n {StartIcon && !loading && <StartIcon {...mergedIconProps} />}\n {iconOnly ? <span className=\"sr-only\">{props.children}</span> : props.children}\n {EndIcon && !iconOnly && <EndIcon {...mergedIconProps} />}\n </Comp>\n )\n },\n)\nButton.displayName = 'Button'\n\nexport { Button, buttonVariants }\n"],"names":[],"mappings":"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"}
@@ -22,6 +22,32 @@ declare const CarouselContext: React.Context<CarouselContextProps | null>;
22
22
  declare const Carousel: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & CarouselProps & React.RefAttributes<HTMLDivElement>>;
23
23
  declare const CarouselContent: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
24
24
  declare const CarouselItem: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
25
- declare const CarouselPrevious: React.ForwardRefExoticComponent<Omit<import('../Button').ButtonProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
26
- declare const CarouselNext: React.ForwardRefExoticComponent<Omit<import('../Button').ButtonProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
25
+ declare const CarouselPrevious: React.ForwardRefExoticComponent<Omit<{
26
+ variant?: "primary" | "secondary" | "ghost" | "inverse-ghost" | "text" | "error" | "success";
27
+ size?: "sm" | "md" | "lg";
28
+ StartIcon?: import('@phosphor-icons/react').Icon;
29
+ EndIcon?: import('@phosphor-icons/react').Icon;
30
+ loading?: boolean;
31
+ iconOnly?: boolean;
32
+ children?: React.ReactNode;
33
+ asChild?: boolean;
34
+ iconProps?: Partial<import('@phosphor-icons/react').IconProps>;
35
+ } & React.ButtonHTMLAttributes<HTMLButtonElement> & import('class-variance-authority').VariantProps<(props?: ({
36
+ variant?: "primary" | "secondary" | "success" | "error" | "text" | "input" | "ghost" | "inverse-ghost" | null | undefined;
37
+ size?: "sm" | "md" | "lg" | null | undefined;
38
+ } & import('class-variance-authority/types').ClassProp) | undefined) => string> & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
39
+ declare const CarouselNext: React.ForwardRefExoticComponent<Omit<{
40
+ variant?: "primary" | "secondary" | "ghost" | "inverse-ghost" | "text" | "error" | "success";
41
+ size?: "sm" | "md" | "lg";
42
+ StartIcon?: import('@phosphor-icons/react').Icon;
43
+ EndIcon?: import('@phosphor-icons/react').Icon;
44
+ loading?: boolean;
45
+ iconOnly?: boolean;
46
+ children?: React.ReactNode;
47
+ asChild?: boolean;
48
+ iconProps?: Partial<import('@phosphor-icons/react').IconProps>;
49
+ } & React.ButtonHTMLAttributes<HTMLButtonElement> & import('class-variance-authority').VariantProps<(props?: ({
50
+ variant?: "primary" | "secondary" | "success" | "error" | "text" | "input" | "ghost" | "inverse-ghost" | null | undefined;
51
+ size?: "sm" | "md" | "lg" | null | undefined;
52
+ } & import('class-variance-authority/types').ClassProp) | undefined) => string> & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
27
53
  export { type CarouselApi, Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext, CarouselContext };
@@ -89,35 +89,6 @@ type RowIdentifierFn<T> = (row: T) => string;
89
89
  /**
90
90
  * A feature-rich data table component built on top of TanStack Table.
91
91
  * Provides sorting, filtering, pagination, and column visibility controls.
92
- *
93
- * @template TData The type of data being displayed in the table
94
- *
95
- * @example
96
- * ```tsx
97
- * type User = {
98
- * id: string;
99
- * name: string;
100
- * email: string;
101
- * };
102
- *
103
- * const columns: ColumnDef<User>[] = [
104
- * {
105
- * accessorKey: 'name',
106
- * header: 'Name',
107
- * },
108
- * {
109
- * accessorKey: 'email',
110
- * header: 'Email',
111
- * },
112
- * ];
113
- *
114
- * const data: User[] = [
115
- * { id: '1', name: 'John', email: 'john@example.com' },
116
- * { id: '2', name: 'Jane', email: 'jane@example.com' },
117
- * ];
118
- *
119
- * <DataTable columns={columns} data={data} />
120
- * ```
121
92
  */
122
- export declare function DataTable<TData extends object = any>({ columns: userColumns, data, getRowId, showColumnVisibilityControls, isLoading, pagination, primaryFilters, secondaryFilters, activePrimaryFiltersCount, activeSecondaryFiltersCount, labels, onSelectAll, onSelect, className, tableClassName, }: DataTableProps<TData>): import("react/jsx-runtime").JSX.Element;
93
+ export declare function DataTable<TData extends object>({ columns: userColumns, data, getRowId, showColumnVisibilityControls, isLoading, pagination, primaryFilters, secondaryFilters, activePrimaryFiltersCount, activeSecondaryFiltersCount, labels, onSelectAll, onSelect, className, tableClassName, }: DataTableProps<TData>): import("react/jsx-runtime").JSX.Element;
123
94
  export {};
@@ -2,23 +2,19 @@
2
2
  import { jsxs, jsx } from 'react/jsx-runtime';
3
3
  import { useReactTable, getCoreRowModel, getPaginationRowModel, flexRender } from '@tanstack/react-table';
4
4
  import * as React from 'react';
5
- import { Button } from '../Button/Button.js';
6
- import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuCheckboxItem, DropdownMenuSeparator } from '../DropdownMenu/DropdownMenu.js';
7
- import { Skeleton } from '../Skeleton/Skeleton.js';
8
- import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from '../Table/Table.js';
9
- import { DataTablePagination } from './DataTablePagination.js';
10
5
  import { Checkbox } from '../Checkbox/Checkbox.js';
11
- import { FunnelSimple, TextColumns } from '@phosphor-icons/react';
12
- import { cn } from '../../lib/utils.js';
6
+ import { Table, TableHeader, TableRow, TableHead } from '../Table/Table.js';
13
7
  import { Typography } from '../Typography/Typography.js';
14
- import { PopoverRoot, PopoverTrigger, PopoverContent } from '../Popover/Popover.js';
15
- import { useIsMobile } from '../../lib/useMobile.js';
16
- import { Badge } from '../Badge/Badge.js';
8
+ import { DataTableBody } from './components/DataTableBody.js';
9
+ import { DataTableToolbar } from './components/DataTableToolbar.js';
10
+ import { DataTablePagination } from './DataTablePagination.js';
11
+ import { useTableSelection } from './hooks/useTableSelection.js';
12
+ import { cn } from '../../lib/utils.js';
17
13
 
18
14
  function DataTable({
19
15
  columns: userColumns,
20
16
  data,
21
- getRowId = (row) => row?.id,
17
+ getRowId = (row) => row.id,
22
18
  showColumnVisibilityControls = true,
23
19
  isLoading = false,
24
20
  pagination,
@@ -36,59 +32,18 @@ function DataTable({
36
32
  className,
37
33
  tableClassName
38
34
  }) {
39
- const isMobile = useIsMobile();
40
35
  const [columnVisibility, setColumnVisibility] = React.useState({});
41
- const [isAllRowsSelected, setIsAllRowsSelected] = React.useState(false);
42
- const [deselectedRows, setDeselectedRows] = React.useState({});
43
- const [selectedRows, setSelectedRows] = React.useState({});
44
36
  const [pageIndex, setPageIndex] = React.useState(0);
45
37
  const isBackendPagination = pagination && "onPageChange" in pagination;
46
38
  const total = isBackendPagination ? pagination.total : data.length;
47
39
  const pageSize = pagination?.pageSize ?? data.length;
48
40
  const totalPages = Math.ceil(total / pageSize);
49
- const isSelectable = typeof onSelectAll === "function" || typeof onSelect === "function";
50
- const handleSelectAll = (checked) => {
51
- setIsAllRowsSelected(checked);
52
- setDeselectedRows({});
53
- setSelectedRows({});
54
- onSelectAll?.(checked);
55
- };
56
- const handleRowSelect = (checked, rowData) => {
57
- const rowId = getRowId(rowData);
58
- if (isAllRowsSelected) {
59
- setDeselectedRows((prev) => {
60
- const newDeselections = { ...prev };
61
- if (!checked) {
62
- newDeselections[rowId] = true;
63
- } else {
64
- delete newDeselections[rowId];
65
- }
66
- return newDeselections;
67
- });
68
- } else {
69
- setSelectedRows((prev) => {
70
- const newSelection = { ...prev };
71
- if (checked) {
72
- newSelection[rowId] = true;
73
- } else {
74
- delete newSelection[rowId];
75
- }
76
- return newSelection;
77
- });
78
- }
79
- onSelect?.(checked, rowData);
80
- };
81
- const rowSelection = React.useMemo(() => {
82
- if (isAllRowsSelected) {
83
- return Object.fromEntries(
84
- data.map((row) => [
85
- getRowId(row),
86
- !deselectedRows[getRowId(row)]
87
- ])
88
- );
89
- }
90
- return selectedRows;
91
- }, [data, isAllRowsSelected, deselectedRows, selectedRows, getRowId]);
41
+ const { isSelectable, isAllRowsSelected, rowSelection, handleSelectAll, handleRowSelect } = useTableSelection({
42
+ data,
43
+ getRowId,
44
+ onSelectAll,
45
+ onSelect
46
+ });
92
47
  const table = useReactTable({
93
48
  data,
94
49
  columns: userColumns,
@@ -113,103 +68,19 @@ function DataTable({
113
68
  }
114
69
  }
115
70
  });
116
- const renderTableBody = () => {
117
- if (isLoading) {
118
- return Array.from({ length: pageSize ?? 10 }).map((_, rowIndex) => /* @__PURE__ */ jsxs(TableRow, { children: [
119
- isSelectable && /* @__PURE__ */ jsx(TableCell, { className: "w-[50px]", children: /* @__PURE__ */ jsx(Checkbox, { checked: false, disabled: true }) }),
120
- table.getAllColumns().filter((column) => column.getIsVisible()).map((column) => /* @__PURE__ */ jsx(
121
- TableCell,
122
- {
123
- style: { width: column.columnDef.size },
124
- children: /* @__PURE__ */ jsx("div", { className: cn("flex items-center justify-center", column.id === "actions" && "justify-end"), children: /* @__PURE__ */ jsx(Skeleton, { className: cn(column.id === "actions" ? "h-8 w-8" : "h-[20px] w-full") }) })
125
- },
126
- `skeleton-cell-${rowIndex.toString()}-${column.id}`
127
- ))
128
- ] }, `skeleton-row-${rowIndex.toString()}`));
129
- }
130
- if (data.length === 0) {
131
- return /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(
132
- TableCell,
133
- {
134
- colSpan: isSelectable ? table.getAllColumns().length + 1 : table.getAllColumns().length,
135
- className: "h-[200px] text-center",
136
- children: /* @__PURE__ */ jsx(Typography, { color: "neutral", children: "No data available" })
137
- }
138
- ) });
139
- }
140
- return table.getRowModel().rows.map((row) => {
141
- const rowId = getRowId(row.original);
142
- const isSelected = rowSelection[rowId] ?? false;
143
- return /* @__PURE__ */ jsxs(TableRow, { "data-selected": isSelected, children: [
144
- isSelectable && /* @__PURE__ */ jsx(TableCell, { className: "w-[50px]", children: /* @__PURE__ */ jsx(
145
- Checkbox,
146
- {
147
- checked: isSelected,
148
- onCheckedChange: (checked) => {
149
- const isChecked = checked === true;
150
- handleRowSelect(isChecked, row.original);
151
- },
152
- "aria-label": `Select row ${rowId}`
153
- }
154
- ) }),
155
- row.getVisibleCells().map((cell) => /* @__PURE__ */ jsx(TableCell, { style: { width: cell.column.columnDef.size }, children: /* @__PURE__ */ jsx(Typography, { children: flexRender(cell.column.columnDef.cell, cell.getContext()) }) }, cell.id))
156
- ] }, rowId);
157
- });
158
- };
159
71
  return /* @__PURE__ */ jsxs("div", { className: cn("flex h-full min-h-0 w-full flex-1 flex-col gap-2 overflow-hidden", className), children: [
160
- (showColumnVisibilityControls || primaryFilters || secondaryFilters) && /* @__PURE__ */ jsxs("div", { className: "flex flex-shrink-0 items-end justify-between p-1", children: [
161
- !isMobile && (primaryFilters ? /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: primaryFilters }) : /* @__PURE__ */ jsx("div", {})),
162
- /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-2", isMobile ? "w-full justify-end" : ""), children: [
163
- (isMobile ? primaryFilters || secondaryFilters : secondaryFilters) && /* @__PURE__ */ jsxs(PopoverRoot, { children: [
164
- /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { variant: "text", size: "sm", StartIcon: FunnelSimple, className: "relative whitespace-nowrap", children: [
165
- (isMobile && activePrimaryFiltersCount + activeSecondaryFiltersCount > 0 || !isMobile && activeSecondaryFiltersCount > 0) && /* @__PURE__ */ jsx(
166
- Badge,
167
- {
168
- count: isMobile ? activePrimaryFiltersCount + activeSecondaryFiltersCount : activeSecondaryFiltersCount,
169
- color: "primary",
170
- ping: false,
171
- className: "absolute -right-1 -top-1"
172
- }
173
- ),
174
- !primaryFilters || isMobile ? labels.filters : labels.moreFilters
175
- ] }) }),
176
- /* @__PURE__ */ jsx(PopoverContent, { align: "center", className: "w-fit p-4", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
177
- isMobile && primaryFilters && primaryFilters,
178
- secondaryFilters
179
- ] }) })
180
- ] }),
181
- showColumnVisibilityControls && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
182
- /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "text", size: "sm", className: cn("whitespace-nowrap"), StartIcon: TextColumns, children: labels.columnVisibilityButton ?? "Hide columns" }) }),
183
- /* @__PURE__ */ jsxs(DropdownMenuContent, { children: [
184
- /* @__PURE__ */ jsx(
185
- DropdownMenuCheckboxItem,
186
- {
187
- className: "capitalize",
188
- checked: table.getAllColumns().every((column) => column.getIsVisible()),
189
- onSelect: (event) => event.preventDefault(),
190
- onCheckedChange: (value) => table.getAllColumns().forEach((column) => column.toggleVisibility(!!value)),
191
- children: "Select all"
192
- },
193
- "all-columns"
194
- ),
195
- /* @__PURE__ */ jsx(DropdownMenuSeparator, { className: "bg-neutral-100" }),
196
- table.getAllColumns().filter((column) => column.getCanHide()).map((column) => {
197
- return /* @__PURE__ */ jsx(
198
- DropdownMenuCheckboxItem,
199
- {
200
- className: "capitalize",
201
- checked: column.getIsVisible(),
202
- onSelect: (event) => event.preventDefault(),
203
- onCheckedChange: (value) => column.toggleVisibility(!!value),
204
- children: column.columnDef.header?.toString()
205
- },
206
- column.id
207
- );
208
- })
209
- ] })
210
- ] })
211
- ] })
212
- ] }),
72
+ /* @__PURE__ */ jsx(
73
+ DataTableToolbar,
74
+ {
75
+ table,
76
+ showColumnVisibilityControls,
77
+ primaryFilters,
78
+ secondaryFilters,
79
+ activePrimaryFiltersCount,
80
+ activeSecondaryFiltersCount,
81
+ labels
82
+ }
83
+ ),
213
84
  /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col rounded-md border bg-white", children: [
214
85
  /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-auto", children: /* @__PURE__ */ jsx("div", { className: "h-full overflow-auto", children: /* @__PURE__ */ jsxs(Table, { className: "w-full", tableClassName: cn("table-fixed", tableClassName), children: [
215
86
  /* @__PURE__ */ jsx(TableHeader, { className: "sticky top-0 z-10 bg-neutral-50", children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsxs(TableRow, { children: [
@@ -232,7 +103,18 @@ function DataTable({
232
103
  header.id
233
104
  ))
234
105
  ] }, headerGroup.id)) }),
235
- /* @__PURE__ */ jsx(TableBody, { children: renderTableBody() })
106
+ /* @__PURE__ */ jsx(
107
+ DataTableBody,
108
+ {
109
+ table,
110
+ isLoading,
111
+ isSelectable,
112
+ pageSize,
113
+ getRowId,
114
+ rowSelection,
115
+ handleRowSelect
116
+ }
117
+ )
236
118
  ] }) }) }),
237
119
  !!pagination && /* @__PURE__ */ jsx("div", { className: "border-t px-4 py-2", children: /* @__PURE__ */ jsx(
238
120
  DataTablePagination,
@@ -240,11 +122,11 @@ function DataTable({
240
122
  table,
241
123
  total,
242
124
  pageSize,
243
- currentPage: isBackendPagination ? pagination?.currentPage : table.getState().pagination?.pageIndex + 1 || 1,
125
+ currentPage: isBackendPagination ? pagination.currentPage : table.getState().pagination.pageIndex + 1 || 1,
244
126
  totalPages,
245
- onPageChange: isBackendPagination ? pagination?.onPageChange : undefined,
246
- isLoading: pagination?.isLoading,
247
- labels: pagination?.labels
127
+ onPageChange: isBackendPagination ? pagination.onPageChange : undefined,
128
+ isLoading: pagination.isLoading,
129
+ labels: pagination.labels
248
130
  }
249
131
  ) })
250
132
  ] })
@@ -1 +1 @@
1
- {"version":3,"file":"DataTable.js","sources":["../../../src/components/DataTable/DataTable.tsx"],"sourcesContent":["'use client'\n\nimport {\n ColumnDef,\n flexRender,\n getCoreRowModel,\n getPaginationRowModel,\n useReactTable,\n VisibilityState,\n} from '@tanstack/react-table'\nimport * as React from 'react'\n\nimport { Button } from '../Button'\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from '../DropdownMenu'\nimport { Skeleton } from '../Skeleton'\nimport { Table as TableComponent, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../Table'\nimport { DataTablePagination } from './DataTablePagination'\nimport { Checkbox } from '../Checkbox'\nimport { TextColumns, FunnelSimple } from '@phosphor-icons/react'\nimport { cn } from '@/lib/utils'\nimport { Typography } from '../Typography'\nimport { PopoverContent, PopoverRoot, PopoverTrigger } from '../Popover'\nimport { useIsMobile } from '@/lib/useMobile'\nimport { Badge } from '../Badge'\n\ntype BasePaginationProps = {\n /** Number of rows per page */\n readonly pageSize: number\n /** Whether the pagination is in a loading state */\n readonly isLoading?: boolean\n /** Text customization for pagination */\n readonly labels?: {\n /** Text shown before the page size number (default: \"Showing\") */\n showing?: string\n /** Text shown before the total number (default: \"of\") */\n of?: string\n /** Text shown after the total number (default: \"results\") */\n results?: string\n /** Aria label for previous page button (default: \"Previous page\") */\n previousPage?: string\n /** Aria label for next page button (default: \"Next page\") */\n nextPage?: string\n /** Aria label for page number (default: \"Page {number}\") */\n pageLabel?: string\n }\n}\n\ntype BackendPaginationProps = BasePaginationProps & {\n /** Current page */\n readonly currentPage: number\n /** Total number of items */\n readonly total: number\n /** Callback when page changes */\n readonly onPageChange: (page: number) => void\n}\n\n/**\n * Type helper to check if a type has an 'id' property\n */\ntype HasId<T> = T extends { id: string | number } ? true : false\n\n/**\n * Props for the DataTable component\n * @template TData The type of data being displayed in the table\n */\nexport type DataTableProps<TData> = {\n /** Array of column definitions that describe the table structure */\n readonly columns: Array<ColumnDef<TData>>\n /** Array of data items to be displayed in the table */\n readonly data: Array<TData>\n /** Whether to show the column visibility toggle menu */\n readonly showColumnVisibilityControls?: boolean\n /** Whether the table is in a loading state */\n readonly isLoading?: boolean\n /** Pagination configuration. If not provided, pagination is disabled */\n readonly pagination?: BasePaginationProps | BackendPaginationProps\n /** Primary filters that appear directly above the table */\n readonly primaryFilters?: React.ReactNode\n /** Secondary filters that appear in the filters dropdown */\n readonly secondaryFilters?: React.ReactNode\n /** Number of active primary filters */\n readonly activePrimaryFiltersCount?: number\n /** Number of active secondary filters */\n readonly activeSecondaryFiltersCount?: number\n /** Text customization for filters */\n readonly labels?: {\n /** Text for the column visibility button (default: \"Hide columns\") */\n columnVisibilityButton?: string\n /** Text for the filters button when only secondary filters are present (default: \"Filters\") */\n filters?: string\n /** Text for the more filters button when both primary and secondary filters are present (default: \"More filters\") */\n moreFilters?: string\n }\n /** Callback when all rows are selected */\n readonly onSelectAll?: (selected: boolean) => void\n /** Callback when a row is selected */\n readonly onSelect?: (selected: boolean, row: TData) => void\n /** Optional className for the table container */\n readonly className?: string\n /** Optional className for the table */\n readonly tableClassName?: string\n} & (HasId<TData> extends true\n ? {\n /** Function to get unique identifier from a row. Not needed when data has 'id' property */\n readonly getRowId?: never\n }\n : {\n /** Function to get unique identifier from a row. Required when data doesn't have 'id' property */\n readonly getRowId: RowIdentifierFn<TData>\n })\n\n/**\n * Function to get a unique identifier from a row\n */\ntype RowIdentifierFn<T> = (row: T) => string\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 *\n * @template TData The type of data being displayed in the table\n *\n * @example\n * ```tsx\n * type User = {\n * id: string;\n * name: string;\n * email: string;\n * };\n *\n * const columns: ColumnDef<User>[] = [\n * {\n * accessorKey: 'name',\n * header: 'Name',\n * },\n * {\n * accessorKey: 'email',\n * header: 'Email',\n * },\n * ];\n *\n * const data: User[] = [\n * { id: '1', name: 'John', email: 'john@example.com' },\n * { id: '2', name: 'Jane', email: 'jane@example.com' },\n * ];\n *\n * <DataTable columns={columns} data={data} />\n * ```\n */\nexport function DataTable<TData extends object = any>({\n columns: userColumns,\n data,\n getRowId = (row: TData) => (row as { id: string })?.id,\n showColumnVisibilityControls = true,\n isLoading = false,\n pagination,\n primaryFilters,\n secondaryFilters,\n activePrimaryFiltersCount = 0,\n activeSecondaryFiltersCount = 0,\n labels = {\n columnVisibilityButton: 'Hide columns',\n filters: 'Filters',\n moreFilters: 'More filters',\n },\n onSelectAll,\n onSelect,\n className,\n tableClassName,\n}: DataTableProps<TData>) {\n const isMobile = useIsMobile()\n const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({})\n const [isAllRowsSelected, setIsAllRowsSelected] = React.useState(false)\n const [deselectedRows, setDeselectedRows] = React.useState<Record<string, boolean>>({})\n const [selectedRows, setSelectedRows] = React.useState<Record<string, boolean>>({})\n const [pageIndex, setPageIndex] = React.useState(0)\n\n const isBackendPagination = pagination && 'onPageChange' in pagination\n const total = isBackendPagination ? pagination.total : data.length\n const pageSize = pagination?.pageSize ?? data.length\n const totalPages = Math.ceil(total / pageSize)\n\n const isSelectable = typeof onSelectAll === 'function' || typeof onSelect === 'function'\n\n const handleSelectAll = (checked: boolean) => {\n setIsAllRowsSelected(checked)\n setDeselectedRows({})\n setSelectedRows({})\n onSelectAll?.(checked)\n }\n\n const handleRowSelect = (checked: boolean, rowData: TData) => {\n const rowId = (getRowId as (row: TData) => string)(rowData)\n\n if (isAllRowsSelected) {\n setDeselectedRows((prev) => {\n const newDeselections = { ...prev }\n if (!checked) {\n newDeselections[rowId] = true\n } else {\n delete newDeselections[rowId]\n }\n return newDeselections\n })\n } else {\n setSelectedRows((prev) => {\n const newSelection = { ...prev }\n if (checked) {\n newSelection[rowId] = true\n } else {\n delete newSelection[rowId]\n }\n return newSelection\n })\n }\n\n onSelect?.(checked, rowData)\n }\n\n const rowSelection = React.useMemo(() => {\n if (isAllRowsSelected) {\n return Object.fromEntries(\n data.map((row) => [\n (getRowId as (row: TData) => string)(row),\n !deselectedRows[(getRowId as (row: TData) => string)(row)],\n ]),\n )\n }\n return selectedRows\n }, [data, isAllRowsSelected, deselectedRows, selectedRows, getRowId])\n\n const table = useReactTable({\n data,\n columns: userColumns,\n getCoreRowModel: getCoreRowModel(),\n getPaginationRowModel: pagination && !isBackendPagination ? getPaginationRowModel() : undefined,\n onColumnVisibilityChange: setColumnVisibility,\n enableRowSelection: isSelectable,\n getRowId: getRowId,\n state: {\n columnVisibility,\n rowSelection,\n pagination: pagination\n ? {\n pageIndex: isBackendPagination ? pagination.currentPage - 1 : pageIndex,\n pageSize,\n }\n : undefined,\n },\n manualPagination: isBackendPagination,\n onPaginationChange: isBackendPagination\n ? undefined\n : (updater) => {\n if (typeof updater === 'function') {\n const newState = updater({ pageIndex, pageSize })\n setPageIndex(newState.pageIndex)\n }\n },\n })\n\n const renderTableBody = () => {\n if (isLoading) {\n return Array.from({ length: pageSize ?? 10 }).map((_, rowIndex) => (\n <TableRow key={`skeleton-row-${rowIndex.toString()}`}>\n {isSelectable && (\n <TableCell className=\"w-[50px]\">\n <Checkbox checked={false} disabled />\n </TableCell>\n )}\n {table\n .getAllColumns()\n .filter((column) => column.getIsVisible())\n .map((column) => (\n <TableCell\n key={`skeleton-cell-${rowIndex.toString()}-${column.id}`}\n style={{ width: column.columnDef.size }}\n >\n <div className={cn('flex items-center justify-center', column.id === 'actions' && 'justify-end')}>\n <Skeleton className={cn(column.id === 'actions' ? 'h-8 w-8' : 'h-[20px] w-full')} />\n </div>\n </TableCell>\n ))}\n </TableRow>\n ))\n }\n\n if (data.length === 0) {\n return (\n <TableRow>\n <TableCell\n colSpan={isSelectable ? table.getAllColumns().length + 1 : table.getAllColumns().length}\n className=\"h-[200px] text-center\"\n >\n <Typography color=\"neutral\">No data available</Typography>\n </TableCell>\n </TableRow>\n )\n }\n\n return table.getRowModel().rows.map((row) => {\n const rowId = (getRowId as (row: TData) => string)(row.original)\n const isSelected = rowSelection[rowId] ?? false\n\n return (\n <TableRow key={rowId} data-selected={isSelected}>\n {isSelectable && (\n <TableCell className=\"w-[50px]\">\n <Checkbox\n checked={isSelected}\n onCheckedChange={(checked) => {\n const isChecked = checked === true\n handleRowSelect(isChecked, row.original)\n }}\n aria-label={`Select row ${rowId}`}\n />\n </TableCell>\n )}\n {row.getVisibleCells().map((cell) => (\n <TableCell key={cell.id} style={{ width: cell.column.columnDef.size }}>\n <Typography>{flexRender(cell.column.columnDef.cell, cell.getContext())}</Typography>\n </TableCell>\n ))}\n </TableRow>\n )\n })\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 {(showColumnVisibilityControls || primaryFilters || secondaryFilters) && (\n <div className=\"flex flex-shrink-0 items-end justify-between p-1\">\n {!isMobile && (primaryFilters ? <div className=\"flex items-center gap-2\">{primaryFilters}</div> : <div />)}\n <div className={cn('flex items-center gap-2', isMobile ? 'w-full justify-end' : '')}>\n {(isMobile ? primaryFilters || secondaryFilters : secondaryFilters) && (\n <PopoverRoot>\n <PopoverTrigger asChild>\n <Button variant=\"text\" size=\"sm\" StartIcon={FunnelSimple} className=\"relative whitespace-nowrap\">\n {((isMobile && activePrimaryFiltersCount + activeSecondaryFiltersCount > 0) ||\n (!isMobile && activeSecondaryFiltersCount > 0)) && (\n <Badge\n count={\n isMobile\n ? activePrimaryFiltersCount + activeSecondaryFiltersCount\n : activeSecondaryFiltersCount\n }\n color=\"primary\"\n ping={false}\n className=\"absolute -right-1 -top-1\"\n />\n )}\n {!primaryFilters || isMobile ? labels.filters : labels.moreFilters}\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"center\" className=\"w-fit p-4\">\n <div className=\"flex flex-col gap-4\">\n {isMobile && primaryFilters && primaryFilters}\n {secondaryFilters}\n </div>\n </PopoverContent>\n </PopoverRoot>\n )}\n {showColumnVisibilityControls && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"text\" size=\"sm\" className={cn('whitespace-nowrap')} StartIcon={TextColumns}>\n {labels.columnVisibilityButton ?? 'Hide columns'}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent>\n <DropdownMenuCheckboxItem\n key={'all-columns'}\n className=\"capitalize\"\n checked={table.getAllColumns().every((column) => column.getIsVisible())}\n onSelect={(event) => event.preventDefault()}\n onCheckedChange={(value) =>\n table.getAllColumns().forEach((column) => column.toggleVisibility(!!value))\n }\n >\n Select all\n </DropdownMenuCheckboxItem>\n <DropdownMenuSeparator className=\"bg-neutral-100\" />\n {table\n .getAllColumns()\n .filter((column) => column.getCanHide())\n .map((column) => {\n return (\n <DropdownMenuCheckboxItem\n key={column.id}\n className=\"capitalize\"\n checked={column.getIsVisible()}\n onSelect={(event) => event.preventDefault()}\n onCheckedChange={(value) => column.toggleVisibility(!!value)}\n >\n {column.columnDef.header?.toString()}\n </DropdownMenuCheckboxItem>\n )\n })}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </div>\n </div>\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\">\n <div className=\"h-full overflow-auto\">\n <TableComponent className=\"w-full\" tableClassName={cn('table-fixed', tableClassName)}>\n <TableHeader className=\"sticky top-0 z-10 bg-neutral-50\">\n {table.getHeaderGroups().map((headerGroup) => (\n <TableRow key={headerGroup.id}>\n {isSelectable && (\n <TableHead className=\"w-[50px]\">\n <Checkbox\n checked={isAllRowsSelected}\n onCheckedChange={handleSelectAll}\n disabled={isLoading || !data.length}\n aria-label=\"Select all rows\"\n />\n </TableHead>\n )}\n {headerGroup.headers.map((header) => (\n <TableHead\n key={header.id}\n className=\"whitespace-normal\"\n style={{ width: header.column.columnDef.size }}\n >\n <Typography weight=\"medium\">\n {header.isPlaceholder\n ? null\n : flexRender(header.column.columnDef.header, header.getContext())}\n </Typography>\n </TableHead>\n ))}\n </TableRow>\n ))}\n </TableHeader>\n <TableBody>{renderTableBody()}</TableBody>\n </TableComponent>\n </div>\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={\n isBackendPagination ? pagination?.currentPage : table.getState().pagination?.pageIndex + 1 || 1\n }\n totalPages={totalPages}\n onPageChange={isBackendPagination ? pagination?.onPageChange : undefined}\n isLoading={pagination?.isLoading}\n labels={pagination?.labels}\n />\n </div>\n )}\n </div>\n </div>\n )\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AA2JO;AAA+C;AAC3C;AACT;AACoD;AACrB;AACnB;AACZ;AACA;AACA;AAC4B;AACE;AACrB;AACiB;AACf;AACI;AACf;AACA;AACA;AACA;AAEF;AACE;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACE;AACA;AACA;AACA;AAAqB;AAGvB;AACE;AAEA;AACE;AACE;AACA;AACE;AAAyB;AAEzB;AAA4B;AAE9B;AAAO;AACR;AAED;AACE;AACA;AACE;AAAsB;AAEtB;AAAyB;AAE3B;AAAO;AACR;AAGH;AAA2B;AAG7B;AACE;AACE;AAAc;AACM;AACwB;AACiB;AAC1D;AACH;AAEF;AAAO;AAGT;AAA4B;AAC1B;AACS;AACwB;AACqD;AAC5D;AACN;AACpB;AACO;AACL;AACA;AAEI;AACgE;AAC9D;AAEF;AACN;AACkB;AAIZ;AACE;AACA;AAA+B;AACjC;AACF;AAGN;AACE;AACE;AAEK;AAGC;AAME;AAAC;AAAA;AAEuC;AAItC;AAAA;AALsD;AAOzD;AAEN;AAGH;AACE;AAEI;AAAC;AAAA;AACkF;AACvE;AAEmC;AAAA;AAEjD;AAIJ;AACE;AACA;AAEA;AAEK;AAEG;AAAC;AAAA;AACU;AAEP;AACA;AAAuC;AACzC;AAC+B;AAAA;AAEnC;AAMD;AACH;AAEH;AAGH;AAEM;AAEG;AAAsG;AAEnG;AAEE;AAEO;AAED;AAAC;AAAA;AAIO;AAEA;AACA;AACI;AAAA;AACZ;AAEqD;AAE3D;AAGK;AAA8B;AAC9B;AAEL;AACF;AAIE;AAIA;AAEE;AAAA;AAAC;AAAA;AAEW;AAC4D;AAC5B;AAEkC;AAE7E;AAAA;AAPM;AASP;AACkD;AAK9C;AACE;AAAC;AAAA;AAEW;AACmB;AACa;AACiB;AAExB;AAAA;AANvB;AAOd;AAEH;AACL;AACF;AAEJ;AACF;AAIA;AAGM;AAGO;AAEG;AAAC;AAAA;AACU;AACQ;AACY;AAClB;AAAA;AAEf;AAGA;AAAC;AAAA;AAEW;AACmC;AAM7C;AAAA;AARY;AAUf;AAGP;AAC8B;AAGpC;AAGI;AAAC;AAAA;AACC;AACA;AACA;AAEgG;AAEhG;AAC+D;AACxC;AACH;AAAA;AAExB;AAEJ;AAGN;;"}
1
+ {"version":3,"file":"DataTable.js","sources":["../../../src/components/DataTable/DataTable.tsx"],"sourcesContent":["/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */\n/* eslint-disable @typescript-eslint/no-dynamic-delete */\n'use client'\n\nimport {\n ColumnDef,\n flexRender,\n getCoreRowModel,\n getPaginationRowModel,\n useReactTable,\n VisibilityState,\n} from '@tanstack/react-table'\nimport * as React from 'react'\n\nimport { Checkbox } from '../Checkbox'\nimport { Table as TableComponent, TableHeader, TableHead, TableRow } from '../Table'\nimport { Typography } from '../Typography'\n\nimport { DataTableBody } from './components/DataTableBody'\nimport { DataTableToolbar } from './components/DataTableToolbar'\nimport { DataTablePagination } from './DataTablePagination'\nimport { useTableSelection } from './hooks/useTableSelection'\n\nimport { cn } from '@/lib/utils'\n\ntype BasePaginationProps = {\n /** Number of rows per page */\n readonly pageSize: number\n /** Whether the pagination is in a loading state */\n readonly isLoading?: boolean\n /** Text customization for pagination */\n readonly labels?: {\n /** Text shown before the page size number (default: \"Showing\") */\n showing?: string\n /** Text shown before the total number (default: \"of\") */\n of?: string\n /** Text shown after the total number (default: \"results\") */\n results?: string\n /** Aria label for previous page button (default: \"Previous page\") */\n previousPage?: string\n /** Aria label for next page button (default: \"Next page\") */\n nextPage?: string\n /** Aria label for page number (default: \"Page {number}\") */\n pageLabel?: string\n }\n}\n\ntype BackendPaginationProps = BasePaginationProps & {\n /** Current page */\n readonly currentPage: number\n /** Total number of items */\n readonly total: number\n /** Callback when page changes */\n readonly onPageChange: (page: number) => void\n}\n\n/**\n * Type helper to check if a type has an 'id' property\n */\ntype HasId<T> = T extends { id: string | number } ? true : false\n\n/**\n * Props for the DataTable component\n * @template TData The type of data being displayed in the table\n */\nexport type DataTableProps<TData> = {\n /** Array of column definitions that describe the table structure */\n readonly columns: Array<ColumnDef<TData>>\n /** Array of data items to be displayed in the table */\n readonly data: Array<TData>\n /** Whether to show the column visibility toggle menu */\n readonly showColumnVisibilityControls?: boolean\n /** Whether the table is in a loading state */\n readonly isLoading?: boolean\n /** Pagination configuration. If not provided, pagination is disabled */\n readonly pagination?: BasePaginationProps | BackendPaginationProps\n /** Primary filters that appear directly above the table */\n readonly primaryFilters?: React.ReactNode\n /** Secondary filters that appear in the filters dropdown */\n readonly secondaryFilters?: React.ReactNode\n /** Number of active primary filters */\n readonly activePrimaryFiltersCount?: number\n /** Number of active secondary filters */\n readonly activeSecondaryFiltersCount?: number\n /** Text customization for filters */\n readonly labels?: {\n /** Text for the column visibility button (default: \"Hide columns\") */\n columnVisibilityButton?: string\n /** Text for the filters button when only secondary filters are present (default: \"Filters\") */\n filters?: string\n /** Text for the more filters button when both primary and secondary filters are present (default: \"More filters\") */\n moreFilters?: string\n }\n /** Callback when all rows are selected */\n readonly onSelectAll?: (selected: boolean) => void\n /** Callback when a row is selected */\n readonly onSelect?: (selected: boolean, row: TData) => void\n /** Optional className for the table container */\n readonly className?: string\n /** Optional className for the table */\n readonly tableClassName?: string\n} & (HasId<TData> extends true\n ? {\n /** Function to get unique identifier from a row. Not needed when data has 'id' property */\n readonly getRowId?: never\n }\n : {\n /** Function to get unique identifier from a row. Required when data doesn't have 'id' property */\n readonly getRowId: RowIdentifierFn<TData>\n })\n\n/**\n * Function to get a unique identifier from a row\n */\ntype RowIdentifierFn<T> = (row: T) => string\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 showColumnVisibilityControls = true,\n isLoading = false,\n pagination,\n primaryFilters,\n secondaryFilters,\n activePrimaryFiltersCount = 0,\n activeSecondaryFiltersCount = 0,\n labels = {\n columnVisibilityButton: 'Hide columns',\n filters: 'Filters',\n moreFilters: 'More filters',\n },\n onSelectAll,\n onSelect,\n className,\n tableClassName,\n}: DataTableProps<TData>) {\n const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({})\n const [pageIndex, setPageIndex] = React.useState(0)\n\n const isBackendPagination = pagination && 'onPageChange' in pagination\n const total = isBackendPagination ? pagination.total : data.length\n const pageSize = pagination?.pageSize ?? data.length\n const totalPages = Math.ceil(total / pageSize)\n\n const { isSelectable, isAllRowsSelected, rowSelection, handleSelectAll, handleRowSelect } = useTableSelection({\n data,\n getRowId,\n onSelectAll,\n onSelect,\n })\n\n const table = useReactTable({\n data,\n columns: userColumns,\n getCoreRowModel: getCoreRowModel(),\n getPaginationRowModel: pagination && !isBackendPagination ? getPaginationRowModel() : undefined,\n onColumnVisibilityChange: setColumnVisibility,\n enableRowSelection: isSelectable,\n getRowId: getRowId,\n state: {\n columnVisibility,\n rowSelection,\n pagination: pagination\n ? {\n pageIndex: isBackendPagination ? pagination.currentPage - 1 : pageIndex,\n pageSize,\n }\n : undefined,\n },\n manualPagination: isBackendPagination,\n onPaginationChange: isBackendPagination\n ? undefined\n : (updater) => {\n if (typeof updater === 'function') {\n const newState = updater({ pageIndex, pageSize })\n setPageIndex(newState.pageIndex)\n }\n },\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 showColumnVisibilityControls={showColumnVisibilityControls}\n primaryFilters={primaryFilters}\n secondaryFilters={secondaryFilters}\n activePrimaryFiltersCount={activePrimaryFiltersCount}\n activeSecondaryFiltersCount={activeSecondaryFiltersCount}\n labels={labels}\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\">\n <div className=\"h-full overflow-auto\">\n <TableComponent className=\"w-full\" tableClassName={cn('table-fixed', tableClassName)}>\n <TableHeader className=\"sticky top-0 z-10 bg-neutral-50\">\n {table.getHeaderGroups().map((headerGroup) => (\n <TableRow key={headerGroup.id}>\n {isSelectable && (\n <TableHead className=\"w-[50px]\">\n <Checkbox\n checked={isAllRowsSelected}\n onCheckedChange={handleSelectAll}\n disabled={isLoading || !data.length}\n aria-label=\"Select all rows\"\n />\n </TableHead>\n )}\n {headerGroup.headers.map((header) => (\n <TableHead\n key={header.id}\n className=\"whitespace-normal\"\n style={{ width: header.column.columnDef.size }}\n >\n <Typography weight=\"medium\">\n {header.isPlaceholder\n ? null\n : flexRender(header.column.columnDef.header, header.getContext())}\n </Typography>\n </TableHead>\n ))}\n </TableRow>\n ))}\n </TableHeader>\n <DataTableBody\n table={table}\n isLoading={isLoading}\n isSelectable={isSelectable}\n pageSize={pageSize}\n getRowId={getRowId}\n rowSelection={rowSelection}\n handleRowSelect={handleRowSelect}\n />\n </TableComponent>\n </div>\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={\n isBackendPagination ? pagination.currentPage : table.getState().pagination.pageIndex + 1 || 1\n }\n totalPages={totalPages}\n onPageChange={isBackendPagination ? pagination.onPageChange : undefined}\n isLoading={pagination.isLoading}\n labels={pagination.labels}\n />\n </div>\n )}\n </div>\n </div>\n )\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;AAwHO;AAAyC;AACrC;AACT;AACmD;AACpB;AACnB;AACZ;AACA;AACA;AAC4B;AACE;AACrB;AACiB;AACf;AACI;AACf;AACA;AACA;AACA;AAEF;AACE;AACA;AAEA;AACA;AACA;AACA;AAEA;AAA8G;AAC5G;AACA;AACA;AACA;AAGF;AAA4B;AAC1B;AACS;AACwB;AACqD;AAC5D;AACN;AACpB;AACO;AACL;AACA;AAEI;AACgE;AAC9D;AAEF;AACN;AACkB;AAIZ;AACE;AACA;AAA+B;AACjC;AACF;AAGN;AAEI;AAAA;AAAC;AAAA;AACC;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACF;AAGE;AAGM;AAGO;AAEG;AAAC;AAAA;AACU;AACQ;AACY;AAClB;AAAA;AAEf;AAGA;AAAC;AAAA;AAEW;AACmC;AAM7C;AAAA;AARY;AAUf;AAGP;AACA;AAAC;AAAA;AACC;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACF;AAGN;AAGI;AAAC;AAAA;AACC;AACA;AACA;AAE8F;AAE9F;AAC8D;AACxC;AACH;AAAA;AAEvB;AAEJ;AAGN;;"}
@@ -0,0 +1,12 @@
1
+ import { Table } from '@tanstack/react-table';
2
+ type DataTableBodyProps<TData> = {
3
+ readonly table: Table<TData>;
4
+ readonly isLoading: boolean;
5
+ readonly isSelectable: boolean;
6
+ readonly pageSize: number;
7
+ readonly getRowId: (row: TData) => string;
8
+ readonly rowSelection: Record<string, boolean>;
9
+ readonly handleRowSelect: (checked: boolean, rowData: TData) => void;
10
+ };
11
+ export declare function DataTableBody<TData>({ table, isLoading, isSelectable, pageSize, getRowId, rowSelection, handleRowSelect, }: DataTableBodyProps<TData>): import("react/jsx-runtime").JSX.Element;
12
+ export {};