periplo-ui 3.16.0 → 3.18.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.
@@ -1,9 +1,10 @@
1
1
  import { VariantProps } from 'class-variance-authority';
2
2
  import * as React from 'react';
3
3
  declare const badgeVariants: (props?: ({
4
- intent?: "neutral" | "accent" | "success" | "warning" | "error" | null | undefined;
4
+ color?: "neutral" | "primary" | "accent" | "success" | "warning" | "error" | null | undefined;
5
5
  size?: "sm" | "md" | "lg" | null | undefined;
6
6
  position?: "top-right" | "top-left" | "bottom-right" | "bottom-left" | null | undefined;
7
+ numeric?: boolean | null | undefined;
7
8
  } & import('class-variance-authority/types').ClassProp) | undefined) => string;
8
9
  export interface BadgeProps extends React.HTMLAttributes<HTMLElement>, VariantProps<typeof badgeVariants> {
9
10
  /**
@@ -15,7 +16,7 @@ export interface BadgeProps extends React.HTMLAttributes<HTMLElement>, VariantPr
15
16
  * The color variant of the badge
16
17
  * @default 'accent'
17
18
  */
18
- intent?: 'neutral' | 'accent' | 'success' | 'warning' | 'error';
19
+ color?: 'neutral' | 'accent' | 'primary' | 'success' | 'warning' | 'error';
19
20
  /**
20
21
  * The size of the badge
21
22
  * @default 'md'
@@ -26,6 +27,10 @@ export interface BadgeProps extends React.HTMLAttributes<HTMLElement>, VariantPr
26
27
  * @default 'top-right'
27
28
  */
28
29
  position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';
30
+ /**
31
+ * The number to display in the badge)
32
+ */
33
+ count?: number;
29
34
  }
30
35
  /**
31
36
  * A status indicator component that can be used to show notifications,
@@ -9,17 +9,19 @@ const badgeVariants = cva("rounded-full", {
9
9
  * Controls the color scheme of the badge
10
10
  * @default 'accent'
11
11
  */
12
- intent: {
12
+ color: {
13
13
  /** Default gray appearance */
14
- neutral: "bg-neutral",
14
+ neutral: "bg-neutral text-neutral-foreground",
15
+ /** Accent brand color */
16
+ accent: "bg-accent text-accent-foreground",
15
17
  /** Primary brand color */
16
- accent: "bg-accent",
18
+ primary: "bg-primary text-primary-foreground",
17
19
  /** Indicates successful or positive state */
18
- success: "bg-success",
20
+ success: "bg-success text-success-foreground ",
19
21
  /** Indicates warning or cautionary state */
20
- warning: "bg-warning",
22
+ warning: "bg-warning text-warning-foreground",
21
23
  /** Indicates error or critical state */
22
- error: "bg-error"
24
+ error: "bg-error text-error-foreground"
23
25
  },
24
26
  /**
25
27
  * Controls the size of the badge
@@ -46,30 +48,58 @@ const badgeVariants = cva("rounded-full", {
46
48
  "bottom-right": "right-0 bottom-0",
47
49
  /** Places the badge in the bottom-left corner */
48
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: ""
49
59
  }
50
60
  },
51
61
  defaultVariants: {
52
- intent: "accent",
62
+ color: "primary",
53
63
  size: "md",
54
- position: "top-right"
64
+ position: "top-right",
65
+ numeric: false
55
66
  }
56
67
  });
57
68
  const Badge = React.forwardRef(
58
- ({ className, intent = "accent", size = "md", position = "top-right", ping = true, ...props }, ref) => {
69
+ ({ className, color = "accent", size = "md", position = "top-right", ping = true, count, ...props }, ref) => {
59
70
  const sizeClasses = {
60
71
  sm: "h-2 w-2",
61
72
  md: "h-3 w-3",
62
73
  lg: "h-4 w-4"
63
74
  }[size || "md"];
64
- return /* @__PURE__ */ jsxs("span", { className: cn("absolute flex", badgeVariants({ position }), sizeClasses, className), ref, ...props, children: [
65
- ping && /* @__PURE__ */ jsx(
66
- "span",
67
- {
68
- className: cn(badgeVariants({ intent }), "absolute inline-flex h-full w-full animate-ping opacity-75")
69
- }
70
- ),
71
- /* @__PURE__ */ jsx("span", { className: cn(badgeVariants({ intent }), "relative inline-flex h-full w-full") })
72
- ] });
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
+ );
73
103
  }
74
104
  );
75
105
  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'\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 intent: {\n /** Default gray appearance */\n neutral: 'bg-neutral',\n /** Primary brand color */\n accent: 'bg-accent',\n /** Indicates successful or positive state */\n success: 'bg-success',\n /** Indicates warning or cautionary state */\n warning: 'bg-warning',\n /** Indicates error or critical state */\n error: 'bg-error',\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 defaultVariants: {\n intent: 'accent',\n size: 'md',\n position: 'top-right',\n },\n})\n\nexport interface BadgeProps extends React.HTMLAttributes<HTMLElement>, VariantProps<typeof badgeVariants> {\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 intent?: 'neutral' | 'accent' | '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/**\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, intent = 'accent', size = 'md', position = 'top-right', ping = true, ...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 className={cn('absolute flex', badgeVariants({ position }), sizeClasses, className)} ref={ref} {...props}>\n {ping && (\n <span\n className={cn(badgeVariants({ intent }), 'absolute inline-flex h-full w-full animate-ping opacity-75')}\n />\n )}\n <span className={cn(badgeVariants({ intent }), 'relative inline-flex h-full w-full')} />\n </span>\n )\n },\n)\n\nBadge.displayName = 'Badge'\n\nexport { Badge, badgeVariants }\n"],"names":[],"mappings":";;;;;AAIM,MAAA,aAAA,GAAgB,IAAI,cAAgB,EAAA;AAAA,EACxC,QAAU,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAKR,MAAQ,EAAA;AAAA;AAAA,MAEN,OAAS,EAAA,YAAA;AAAA;AAAA,MAET,MAAQ,EAAA,WAAA;AAAA;AAAA,MAER,OAAS,EAAA,YAAA;AAAA;AAAA,MAET,OAAS,EAAA,YAAA;AAAA;AAAA,MAET,KAAO,EAAA;AAAA,KACT;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,IAAM,EAAA;AAAA;AAAA,MAEJ,EAAI,EAAA,SAAA;AAAA;AAAA,MAEJ,EAAI,EAAA,SAAA;AAAA;AAAA,MAEJ,EAAI,EAAA;AAAA,KACN;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,QAAU,EAAA;AAAA;AAAA,MAER,WAAa,EAAA,eAAA;AAAA;AAAA,MAEb,UAAY,EAAA,cAAA;AAAA;AAAA,MAEZ,cAAgB,EAAA,kBAAA;AAAA;AAAA,MAEhB,aAAe,EAAA;AAAA;AACjB,GACF;AAAA,EACA,eAAiB,EAAA;AAAA,IACf,MAAQ,EAAA,QAAA;AAAA,IACR,IAAM,EAAA,IAAA;AAAA,IACN,QAAU,EAAA;AAAA;AAEd,CAAC;AAoDD,MAAM,QAAQ,KAAM,CAAA,UAAA;AAAA,EAClB,CAAC,EAAE,SAAW,EAAA,MAAA,GAAS,UAAU,IAAO,GAAA,IAAA,EAAM,QAAW,GAAA,WAAA,EAAa,IAAO,GAAA,IAAA,EAAM,GAAG,KAAA,IAAS,GAAQ,KAAA;AACrG,IAAA,MAAM,WAAc,GAAA;AAAA,MAClB,EAAI,EAAA,SAAA;AAAA,MACJ,EAAI,EAAA,SAAA;AAAA,MACJ,EAAI,EAAA;AAAA,KACN,CAAE,QAAQ,IAAI,CAAA;AAEd,IAAA,uBACG,IAAA,CAAA,MAAA,EAAA,EAAK,SAAW,EAAA,EAAA,CAAG,iBAAiB,aAAc,CAAA,EAAE,QAAS,EAAC,GAAG,WAAa,EAAA,SAAS,CAAG,EAAA,GAAA,EAAW,GAAG,KACtG,EAAA,QAAA,EAAA;AAAA,MACC,IAAA,oBAAA,GAAA;AAAA,QAAC,MAAA;AAAA,QAAA;AAAA,UACC,WAAW,EAAG,CAAA,aAAA,CAAc,EAAE,MAAO,EAAC,GAAG,4DAA4D;AAAA;AAAA,OACvG;AAAA,sBAEF,GAAA,CAAC,MAAK,EAAA,EAAA,SAAA,EAAW,EAAG,CAAA,aAAA,CAAc,EAAE,MAAO,EAAC,CAAG,EAAA,oCAAoC,CAAG,EAAA;AAAA,KACxF,EAAA,CAAA;AAAA;AAGN;AAEA,KAAA,CAAM,WAAc,GAAA,OAAA;;;;"}
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'\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 interface BadgeProps extends React.HTMLAttributes<HTMLElement>, VariantProps<typeof badgeVariants> {\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}\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":";;;;;AAIM,MAAA,aAAA,GAAgB,IAAI,cAAgB,EAAA;AAAA,EACxC,QAAU,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAKR,KAAO,EAAA;AAAA;AAAA,MAEL,OAAS,EAAA,oCAAA;AAAA;AAAA,MAET,MAAQ,EAAA,kCAAA;AAAA;AAAA,MAER,OAAS,EAAA,oCAAA;AAAA;AAAA,MAET,OAAS,EAAA,sCAAA;AAAA;AAAA,MAET,OAAS,EAAA,oCAAA;AAAA;AAAA,MAET,KAAO,EAAA;AAAA,KACT;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,IAAM,EAAA;AAAA;AAAA,MAEJ,EAAI,EAAA,SAAA;AAAA;AAAA,MAEJ,EAAI,EAAA,SAAA;AAAA;AAAA,MAEJ,EAAI,EAAA;AAAA,KACN;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,QAAU,EAAA;AAAA;AAAA,MAER,WAAa,EAAA,eAAA;AAAA;AAAA,MAEb,UAAY,EAAA,cAAA;AAAA;AAAA,MAEZ,cAAgB,EAAA,kBAAA;AAAA;AAAA,MAEhB,aAAe,EAAA;AAAA,KACjB;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,OAAS,EAAA;AAAA,MACP,IAAM,EAAA,gFAAA;AAAA,MACN,KAAO,EAAA;AAAA;AACT,GACF;AAAA,EACA,eAAiB,EAAA;AAAA,IACf,KAAO,EAAA,SAAA;AAAA,IACP,IAAM,EAAA,IAAA;AAAA,IACN,QAAU,EAAA,WAAA;AAAA,IACV,OAAS,EAAA;AAAA;AAEb,CAAC;AAyDD,MAAM,QAAQ,KAAM,CAAA,UAAA;AAAA,EAClB,CAAC,EAAE,SAAW,EAAA,KAAA,GAAQ,UAAU,IAAO,GAAA,IAAA,EAAM,QAAW,GAAA,WAAA,EAAa,OAAO,IAAM,EAAA,KAAA,EAAO,GAAG,KAAA,IAAS,GAAQ,KAAA;AAC3G,IAAA,MAAM,WAAc,GAAA;AAAA,MAClB,EAAI,EAAA,SAAA;AAAA,MACJ,EAAI,EAAA,SAAA;AAAA,MACJ,EAAI,EAAA;AAAA,KACN,CAAE,QAAQ,IAAI,CAAA;AAEd,IACE,uBAAA,IAAA;AAAA,MAAC,MAAA;AAAA,MAAA;AAAA,QACC,SAAW,EAAA,EAAA;AAAA,UACT,eAAA;AAAA,UACA,aAAA,CAAc,EAAE,KAAO,EAAA,QAAA,EAAU,SAAS,CAAC,CAAC,OAAO,CAAA;AAAA,UACnD,CAAC,KAAS,IAAA,WAAA;AAAA,UACV;AAAA,SACF;AAAA,QACA,GAAA;AAAA,QACC,GAAG,KAAA;AAAA,QAEH,QAAA,EAAA;AAAA,UACC,IAAA,oBAAA,GAAA;AAAA,YAAC,MAAA;AAAA,YAAA;AAAA,cACC,WAAW,EAAG,CAAA,aAAA,CAAc,EAAE,KAAM,EAAC,GAAG,4DAA4D;AAAA;AAAA,WACtG;AAAA,0BAEF,GAAA;AAAA,YAAC,MAAA;AAAA,YAAA;AAAA,cACC,WAAW,EAAG,CAAA,aAAA,CAAc,EAAE,KAAM,EAAC,GAAG,gEAAgE,CAAA;AAAA,cAEvG,QAAA,EAAA;AAAA;AAAA;AACH;AAAA;AAAA,KACF;AAAA;AAGN;AAEA,KAAA,CAAM,WAAc,GAAA,OAAA;;;;"}
@@ -54,8 +54,12 @@ export type DataTableProps<TData> = {
54
54
  readonly primaryFilters?: React.ReactNode;
55
55
  /** Secondary filters that appear in the filters dropdown */
56
56
  readonly secondaryFilters?: React.ReactNode;
57
+ /** Number of active primary filters */
58
+ readonly activePrimaryFiltersCount?: number;
59
+ /** Number of active secondary filters */
60
+ readonly activeSecondaryFiltersCount?: number;
57
61
  /** Text customization for filters */
58
- readonly customLabels?: {
62
+ readonly labels?: {
59
63
  /** Text for the column visibility button (default: "Hide columns") */
60
64
  columnVisibilityButton?: string;
61
65
  /** Text for the filters button when only secondary filters are present (default: "Filters") */
@@ -115,5 +119,5 @@ type RowIdentifierFn<T> = (row: T) => string;
115
119
  * <DataTable columns={columns} data={data} />
116
120
  * ```
117
121
  */
118
- export declare function DataTable<TData extends object = any>({ columns: userColumns, data, getRowId, showColumnVisibilityControls, isLoading, pagination, primaryFilters, secondaryFilters, customLabels, onSelectAll, onSelect, className, tableClassName, }: DataTableProps<TData>): import("react/jsx-runtime").JSX.Element;
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;
119
123
  export {};
@@ -13,6 +13,7 @@ import { cn } from '../../lib/utils.js';
13
13
  import { Typography } from '../Typography/Typography.js';
14
14
  import { PopoverRoot, PopoverTrigger, PopoverContent } from '../Popover/Popover.js';
15
15
  import { useIsMobile } from '../../lib/useMobile.js';
16
+ import { Badge } from '../Badge/Badge.js';
16
17
 
17
18
  function DataTable({
18
19
  columns: userColumns,
@@ -23,7 +24,9 @@ function DataTable({
23
24
  pagination,
24
25
  primaryFilters,
25
26
  secondaryFilters,
26
- customLabels = {
27
+ activePrimaryFiltersCount = 0,
28
+ activeSecondaryFiltersCount = 0,
29
+ labels = {
27
30
  columnVisibilityButton: "Hide columns",
28
31
  filters: "Filters",
29
32
  moreFilters: "More filters"
@@ -158,14 +161,25 @@ function DataTable({
158
161
  !isMobile && (primaryFilters ? /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: primaryFilters }) : /* @__PURE__ */ jsx("div", {})),
159
162
  /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-2", isMobile ? "w-full justify-end" : ""), children: [
160
163
  (isMobile ? primaryFilters || secondaryFilters : secondaryFilters) && /* @__PURE__ */ jsxs(PopoverRoot, { children: [
161
- /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "text", size: "sm", StartIcon: FunnelSimple, children: !primaryFilters || isMobile ? customLabels.filters : customLabels.moreFilters }) }),
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
+ ] }) }),
162
176
  /* @__PURE__ */ jsx(PopoverContent, { align: "center", className: "w-fit p-4", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
163
177
  isMobile && primaryFilters && primaryFilters,
164
178
  secondaryFilters
165
179
  ] }) })
166
180
  ] }),
167
181
  showColumnVisibilityControls && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
168
- /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "text", size: "sm", className: cn("whitespace-nowrap"), StartIcon: TextColumns, children: customLabels.columnVisibilityButton ?? "Hide columns" }) }),
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" }) }),
169
183
  /* @__PURE__ */ jsxs(DropdownMenuContent, { children: [
170
184
  /* @__PURE__ */ jsx(
171
185
  DropdownMenuCheckboxItem,
@@ -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'\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 /** Text customization for filters */\n readonly customLabels?: {\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 customLabels = {\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}>\n {!primaryFilters || isMobile ? customLabels.filters : customLabels.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 {customLabels.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":";;;;;;;;;;;;;;;;AAsJO;AAA+C;AAC3C;AACT;AACoD;AACrB;AACnB;AACZ;AACA;AACA;AACe;AACW;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;AAIA;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":["'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,39 +1,80 @@
1
+ import { Locale } from 'date-fns';
1
2
  import { DateRange, PropsBase } from 'react-day-picker';
2
- type BaseDatePickerProps = {
3
+ import { ValueFormat } from '../../lib/dateUtils';
4
+ type SupportedLocaleString = 'enUS' | 'es' | 'pt' | 'enGB' | 'de' | 'it' | 'fr';
5
+ type DatePickerOutput<Variant extends 'single' | 'range', Format extends ValueFormat> = Variant extends 'single' ? Format extends 'date' ? Date | undefined : string | undefined : Format extends 'date' ? DateRange | undefined : {
6
+ from?: string;
7
+ to?: string;
8
+ } | undefined;
9
+ type DatePickerInput<V extends 'single' | 'range'> = V extends 'single' ? Date | string | undefined : {
10
+ from?: Date | string;
11
+ to?: Date | string;
12
+ } | undefined;
13
+ export interface DatePickerProps<V extends 'single' | 'range' = 'single', F extends ValueFormat = 'iso'> extends Omit<PropsBase, 'mode' | 'selected' | 'onSelect' | 'locale'> {
3
14
  /**
4
15
  * Placeholder text displayed when no date is selected
5
- * @default 'Pick a date'
16
+ * @default 'Pick a date' for single mode, 'Pick a date range' for range mode
6
17
  */
7
- placeholder?: string;
18
+ readonly placeholder?: string;
8
19
  /**
9
- * Format string to use when displaying the selected date
20
+ * Format string to use when displaying the selected date in the button
10
21
  * @default 'MMM d, yyyy'
11
22
  */
12
- format?: string;
23
+ readonly displayFormat?: string;
13
24
  /**
14
25
  * ClassName for the button
15
26
  */
16
- buttonClassName?: string;
17
- } & Omit<PropsBase, 'mode' | 'selected' | 'onSelect'>;
18
- type SingleDatePickerProps = {
19
- variant?: 'single';
20
- value?: Date;
21
- initialValue?: Date;
22
- onChange?: (date: Date | undefined) => void;
23
- closeOnSelect?: boolean;
24
- showYearSwitcher?: boolean;
25
- } & BaseDatePickerProps;
26
- type RangeDatePickerProps = {
27
- variant: 'range';
28
- value?: DateRange;
29
- initialValue?: DateRange;
30
- onChange?: (date: DateRange | undefined) => void;
31
- closeOnSelect?: never;
32
- showYearSwitcher?: never;
33
- } & BaseDatePickerProps;
34
- export type DatePickerProps = SingleDatePickerProps | RangeDatePickerProps;
35
- declare function DatePicker(props: DatePickerProps): import("react/jsx-runtime").JSX.Element;
27
+ readonly buttonClassName?: string;
28
+ /**
29
+ * Whether to allow the user to show the year switcher menu
30
+ * @default true for single mode, false for range mode
31
+ */
32
+ readonly showYearSwitcher?: boolean;
33
+ /**
34
+ * Initial value for the date picker
35
+ * Accepts both Date objects and ISO strings regardless of valueFormat setting
36
+ */
37
+ readonly initialValue?: DatePickerInput<V>;
38
+ /**
39
+ * Current value for the date picker
40
+ * Accepts both Date objects and ISO strings regardless of valueFormat setting
41
+ */
42
+ readonly value?: DatePickerInput<V>;
43
+ /**
44
+ * Determines if the picker should close after a selection
45
+ * @default true for single mode, false for range mode
46
+ */
47
+ readonly closeOnSelect?: boolean;
48
+ /**
49
+ * Determines the format of the value provided to the onChange callback
50
+ * - 'iso' (default): onChange receives ISO string(s) ('yyyy-MM-dd')
51
+ * - 'date': onChange receives JavaScript Date object(s)
52
+ *
53
+ * Note: The component accepts both Date objects and ISO strings for value/initialValue
54
+ * regardless of this setting.
55
+ * @default 'iso'
56
+ */
57
+ readonly valueFormat?: F;
58
+ /**
59
+ * Callback when date or date range changes
60
+ */
61
+ readonly onChange?: (value: DatePickerOutput<V, F>) => void;
62
+ /**
63
+ * DatePicker mode - single date or date range
64
+ * @default 'single'
65
+ */
66
+ readonly variant?: V;
67
+ /**
68
+ * The locale to use for formatting dates and determining the start of the week.
69
+ * Can be a string identifier for supported locales ('enUS', 'es', 'pt', 'enGB', 'de', 'it', 'fr')
70
+ * or a Locale object from date-fns/locale for other languages.
71
+ * @default 'enUS'
72
+ */
73
+ readonly locale?: SupportedLocaleString | Locale;
74
+ }
75
+ declare function DatePicker<V extends 'single' | 'range' = 'single', F extends ValueFormat = 'iso'>({ variant, placeholder, valueFormat, initialValue: initialValueProp, value: valueProp, onChange, buttonClassName, displayFormat, closeOnSelect, showYearSwitcher, locale: localeProp, ...rest }: DatePickerProps<V, F>): import("react/jsx-runtime").JSX.Element;
36
76
  declare namespace DatePicker {
37
77
  var displayName: string;
38
78
  }
39
79
  export { DatePicker };
80
+ export type { DateRange };
@@ -1,64 +1,112 @@
1
1
  "use client";
2
2
  import { jsxs, jsx } from 'react/jsx-runtime';
3
- import * as React from 'react';
3
+ import { CalendarBlank } from '@phosphor-icons/react';
4
4
  import { format } from 'date-fns';
5
- import { cn } from '../../lib/utils.js';
6
- import { PopoverRoot, PopoverTrigger, PopoverContent } from '../Popover/Popover.js';
5
+ import { enUS, es, pt, enGB, de, it, fr } from 'date-fns/locale';
6
+ import * as React from 'react';
7
7
  import { Button, buttonVariants } from '../Button/Button.js';
8
8
  import { Calendar } from '../Calendar/Calendar.js';
9
- import { CalendarBlank } from '@phosphor-icons/react';
9
+ import { PopoverRoot, PopoverTrigger, PopoverContent } from '../Popover/Popover.js';
10
+ import { parseInputDate, parseInputRange, formatOutputDate, formatOutputRange } from '../../lib/dateUtils.js';
11
+ import { cn } from '../../lib/utils.js';
10
12
 
11
- function isDateRange(value) {
12
- return typeof value === "object" && value !== null && "from" in value;
13
- }
14
- function DatePicker(props) {
15
- const {
16
- variant = "single",
17
- placeholder = variant === "single" ? "Pick a date" : "Pick a date range",
18
- initialValue,
19
- value,
20
- onChange,
21
- buttonClassName,
22
- closeOnSelect = variant === "single",
23
- showYearSwitcher = variant === "single",
24
- ...rest
25
- } = props;
13
+ const localeMap = {
14
+ enUS,
15
+ es,
16
+ pt,
17
+ enGB,
18
+ de,
19
+ it,
20
+ fr
21
+ };
22
+ function DatePicker({
23
+ variant = "single",
24
+ placeholder = variant === "single" ? "Pick a date" : "Pick a date range",
25
+ valueFormat = "iso",
26
+ initialValue: initialValueProp,
27
+ value: valueProp,
28
+ onChange,
29
+ buttonClassName,
30
+ displayFormat = "MMM d, yyyy",
31
+ closeOnSelect = variant === "single",
32
+ showYearSwitcher = variant === "single",
33
+ locale: localeProp = "enUS",
34
+ ...rest
35
+ }) {
26
36
  const [isOpen, setIsOpen] = React.useState(false);
27
- const [internalSingleDate, setInternalSingleDate] = React.useState(
28
- variant === "single" && initialValue instanceof Date ? initialValue : undefined
29
- );
30
- const [internalDateRange, setInternalDateRange] = React.useState(
31
- variant === "range" && isDateRange(initialValue) ? initialValue : undefined
32
- );
33
- const singleDate = variant === "single" && value instanceof Date ? value : internalSingleDate;
34
- const dateRange = variant === "range" && isDateRange(value) ? value : internalDateRange;
37
+ const resolvedLocale = React.useMemo(() => {
38
+ if (typeof localeProp === "string") {
39
+ return localeMap[localeProp];
40
+ }
41
+ return localeProp;
42
+ }, [localeProp]);
43
+ const [internalSingleDate, setInternalSingleDate] = React.useState(() => {
44
+ if (variant === "single") {
45
+ return parseInputDate(initialValueProp);
46
+ }
47
+ return undefined;
48
+ });
49
+ const [internalDateRange, setInternalDateRange] = React.useState(() => {
50
+ if (variant === "range") {
51
+ return parseInputRange(initialValueProp);
52
+ }
53
+ return undefined;
54
+ });
55
+ React.useEffect(() => {
56
+ if (variant === "single") {
57
+ const parsedValue = parseInputDate(valueProp);
58
+ if (parsedValue?.getTime() !== internalSingleDate?.getTime()) {
59
+ setInternalSingleDate(parsedValue);
60
+ }
61
+ } else {
62
+ const parsedValue = parseInputRange(valueProp);
63
+ if (parsedValue?.from?.getTime() !== internalDateRange?.from?.getTime() || parsedValue?.to?.getTime() !== internalDateRange?.to?.getTime()) {
64
+ setInternalDateRange(parsedValue);
65
+ }
66
+ }
67
+ }, [valueProp, variant]);
68
+ const singleDate = internalSingleDate;
69
+ const dateRange = internalDateRange;
35
70
  const handleSelect = React.useCallback(
36
71
  (selectedDate) => {
37
72
  if (variant === "single") {
38
73
  const date = selectedDate;
39
74
  setInternalSingleDate(date);
40
- if (onChange && typeof onChange === "function") onChange(date);
75
+ if (onChange) {
76
+ const output = valueFormat === "date" ? date : formatOutputDate(date, "iso");
77
+ onChange(output);
78
+ }
41
79
  if (closeOnSelect) {
42
80
  setIsOpen(false);
43
81
  }
44
82
  } else {
45
83
  const range = selectedDate;
46
84
  setInternalDateRange(range);
47
- if (onChange && typeof onChange === "function") onChange(range);
85
+ if (onChange) {
86
+ const output = valueFormat === "date" ? range : formatOutputRange(range, "iso");
87
+ onChange(output);
88
+ }
48
89
  }
49
90
  },
50
- [variant, onChange, closeOnSelect]
91
+ [variant, valueFormat, onChange, closeOnSelect]
51
92
  );
52
- const formatDate = () => {
93
+ const formatForDisplay = () => {
94
+ const formatOptions = { locale: resolvedLocale };
53
95
  if (variant === "single") {
54
- return singleDate ? format(singleDate, rest.format ?? "MMM d, yyyy") : placeholder;
96
+ return singleDate ? format(singleDate, displayFormat, formatOptions) : placeholder;
55
97
  }
56
98
  if (!dateRange) return placeholder;
57
- return `${dateRange.from ? format(dateRange.from, rest.format ?? "MMM d, yyyy") : ""} - ${dateRange.to ? format(dateRange.to, rest.format ?? "MMM d, yyyy") : ""}`;
99
+ const fromStr = dateRange.from ? format(dateRange.from, displayFormat, formatOptions) : "...";
100
+ const toStr = dateRange.to ? format(dateRange.to, displayFormat, formatOptions) : "...";
101
+ if (!dateRange.from && !dateRange.to) return placeholder;
102
+ if (!dateRange.from) return `... - ${toStr}`;
103
+ if (!dateRange.to) return `${fromStr} - ...`;
104
+ return `${fromStr} - ${toStr}`;
58
105
  };
59
106
  const calendarProps = React.useMemo(() => {
60
107
  const baseProps = {
61
108
  ...rest,
109
+ locale: resolvedLocale,
62
110
  initialFocus: true
63
111
  };
64
112
  if (variant === "single") {
@@ -78,7 +126,7 @@ function DatePicker(props) {
78
126
  defaultMonth: dateRange?.from ?? /* @__PURE__ */ new Date(),
79
127
  numberOfMonths: rest.numberOfMonths ?? 2
80
128
  };
81
- }, [variant, rest, singleDate, dateRange, handleSelect]);
129
+ }, [variant, rest, singleDate, dateRange, handleSelect, resolvedLocale]);
82
130
  return /* @__PURE__ */ jsxs(PopoverRoot, { open: isOpen, onOpenChange: setIsOpen, children: [
83
131
  /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
84
132
  Button,
@@ -86,15 +134,15 @@ function DatePicker(props) {
86
134
  id: rest.id,
87
135
  variant: "ghost",
88
136
  className: cn(
89
- "w-fit justify-start text-left font-normal",
137
+ "relative flex w-fit items-center justify-start text-left font-normal",
90
138
  !(variant === "single" ? singleDate : dateRange) && "text-muted-foreground",
91
139
  buttonVariants({ variant: "input" }),
92
140
  buttonClassName
93
141
  ),
94
142
  disabled: typeof rest.disabled === "boolean" ? rest.disabled : false,
95
143
  children: [
96
- /* @__PURE__ */ jsx(CalendarBlank, { className: "mr-2 h-4 w-4 shrink-0" }),
97
- /* @__PURE__ */ jsx("span", { children: formatDate() })
144
+ /* @__PURE__ */ jsx(CalendarBlank, { className: "absolute left-4 h-4 w-4 shrink-0" }),
145
+ /* @__PURE__ */ jsx("span", { className: "w-full pl-7 text-center", children: formatForDisplay() })
98
146
  ]
99
147
  }
100
148
  ) }),
@@ -1 +1 @@
1
- {"version":3,"file":"DatePicker.js","sources":["../../../src/components/DatePicker/DatePicker.tsx"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\nimport { format } from 'date-fns'\nimport { DateRange, PropsBase } from 'react-day-picker'\n\nimport { cn } from '@/lib/utils'\nimport { PopoverContent, PopoverRoot, PopoverTrigger } from '../Popover'\nimport { Button, buttonVariants } from '../Button'\nimport { Calendar } from '../Calendar'\nimport { CalendarBlank } from '@phosphor-icons/react'\n\ntype BaseDatePickerProps = {\n /**\n * Placeholder text displayed when no date is selected\n * @default 'Pick a date'\n */\n placeholder?: string\n /**\n * Format string to use when displaying the selected date\n * @default 'MMM d, yyyy'\n */\n format?: string\n /**\n * ClassName for the button\n */\n buttonClassName?: string\n} & Omit<PropsBase, 'mode' | 'selected' | 'onSelect'>\n\ntype SingleDatePickerProps = {\n variant?: 'single'\n value?: Date\n initialValue?: Date\n onChange?: (date: Date | undefined) => void\n closeOnSelect?: boolean\n showYearSwitcher?: boolean\n} & BaseDatePickerProps\n\ntype RangeDatePickerProps = {\n variant: 'range'\n value?: DateRange\n initialValue?: DateRange\n onChange?: (date: DateRange | undefined) => void\n closeOnSelect?: never\n showYearSwitcher?: never\n} & BaseDatePickerProps\n\nexport type DatePickerProps = SingleDatePickerProps | RangeDatePickerProps\n\nfunction isDateRange(value: any): value is DateRange {\n return typeof value === 'object' && value !== null && 'from' in value\n}\n\nfunction DatePicker(props: DatePickerProps) {\n const {\n variant = 'single',\n placeholder = variant === 'single' ? 'Pick a date' : 'Pick a date range',\n initialValue,\n value,\n onChange,\n buttonClassName,\n closeOnSelect = variant === 'single',\n showYearSwitcher = variant === 'single',\n ...rest\n } = props\n\n const [isOpen, setIsOpen] = React.useState(false)\n\n const [internalSingleDate, setInternalSingleDate] = React.useState<Date | undefined>(\n variant === 'single' && initialValue instanceof Date ? initialValue : undefined,\n )\n const [internalDateRange, setInternalDateRange] = React.useState<DateRange | undefined>(\n variant === 'range' && isDateRange(initialValue) ? initialValue : undefined,\n )\n\n const singleDate = variant === 'single' && value instanceof Date ? value : internalSingleDate\n const dateRange = variant === 'range' && isDateRange(value) ? value : internalDateRange\n\n const handleSelect = React.useCallback(\n (selectedDate: Date | DateRange | undefined) => {\n if (variant === 'single') {\n const date = selectedDate as Date | undefined\n setInternalSingleDate(date)\n if (onChange && typeof onChange === 'function') (onChange as (date: Date | undefined) => void)(date)\n if (closeOnSelect) {\n setIsOpen(false)\n }\n } else {\n const range = selectedDate as DateRange | undefined\n setInternalDateRange(range)\n if (onChange && typeof onChange === 'function') (onChange as (range: DateRange | undefined) => void)(range)\n }\n },\n [variant, onChange, closeOnSelect],\n )\n\n const formatDate = () => {\n if (variant === 'single') {\n return singleDate ? format(singleDate, rest.format ?? 'MMM d, yyyy') : placeholder\n }\n\n if (!dateRange) return placeholder\n return `${dateRange.from ? format(dateRange.from, rest.format ?? 'MMM d, yyyy') : ''} - ${\n dateRange.to ? format(dateRange.to, rest.format ?? 'MMM d, yyyy') : ''\n }`\n }\n\n const calendarProps = React.useMemo(() => {\n const baseProps = {\n ...rest,\n initialFocus: true,\n }\n\n if (variant === 'single') {\n return {\n ...baseProps,\n mode: 'single' as const,\n selected: singleDate,\n onSelect: (date: Date | undefined) => handleSelect(date),\n defaultMonth: singleDate ?? new Date(),\n }\n }\n\n return {\n ...baseProps,\n mode: 'range' as const,\n selected: dateRange,\n onSelect: (range: DateRange | undefined) => handleSelect(range),\n defaultMonth: dateRange?.from ?? new Date(),\n numberOfMonths: rest.numberOfMonths ?? 2,\n }\n }, [variant, rest, singleDate, dateRange, handleSelect])\n\n return (\n <PopoverRoot open={isOpen} onOpenChange={setIsOpen}>\n <PopoverTrigger asChild>\n <Button\n id={rest.id}\n variant=\"ghost\"\n className={cn(\n 'w-fit justify-start text-left font-normal',\n !(variant === 'single' ? singleDate : dateRange) && 'text-muted-foreground',\n buttonVariants({ variant: 'input' }),\n buttonClassName,\n )}\n disabled={typeof rest.disabled === 'boolean' ? rest.disabled : false}\n >\n <CalendarBlank className=\"mr-2 h-4 w-4 shrink-0\" />\n <span>{formatDate()}</span>\n </Button>\n </PopoverTrigger>\n <PopoverContent className=\"w-auto p-0\" align=\"center\">\n <Calendar\n {...calendarProps}\n className=\"border-0\"\n showYearSwitcher={variant === 'single' ? showYearSwitcher : false}\n />\n </PopoverContent>\n </PopoverRoot>\n )\n}\n\nDatePicker.displayName = 'DatePicker'\n\nexport { DatePicker }\n"],"names":[],"mappings":";;;;;;;;;;AAiDA;AACE;AACF;AAEA;AACE;AAAM;AACM;AAC2C;AACrD;AACA;AACA;AACA;AAC4B;AACG;AAC5B;AAGL;AAEA;AAA0D;AACc;AAExE;AAAwD;AACY;AAGpE;AACA;AAEA;AAA2B;AAEvB;AACE;AACA;AACA;AACA;AACE;AAAe;AACjB;AAEA;AACA;AACA;AAA0G;AAC5G;AACF;AACiC;AAGnC;AACE;AACE;AAAuE;AAGzE;AACA;AAEA;AAGF;AACE;AAAkB;AACb;AACW;AAGhB;AACE;AAAO;AACF;AACG;AACI;AAC6C;AAClB;AACvC;AAGF;AAAO;AACF;AACG;AACI;AACoD;AACpB;AACH;AACzC;AAGF;AAEI;AACE;AAAC;AAAA;AACU;AACD;AACG;AACT;AACoD;AACjB;AACnC;AACF;AAC+D;AAE/D;AAAiD;AAC7B;AAAA;AAAA;AAExB;AAEE;AAAC;AAAA;AACK;AACM;AACkD;AAAA;AAEhE;AAGN;AAEA;;"}
1
+ {"version":3,"file":"DatePicker.js","sources":["../../../src/components/DatePicker/DatePicker.tsx"],"sourcesContent":["'use client'\n\nimport { CalendarBlank } from '@phosphor-icons/react'\nimport { format as formatFn, Locale } from 'date-fns'\nimport { enUS, es, pt, enGB, de, it, fr } from 'date-fns/locale'\nimport * as React from 'react'\nimport { DateRange, PropsBase } from 'react-day-picker'\n\nimport { Button, buttonVariants } from '../Button'\nimport { Calendar } from '../Calendar'\nimport { PopoverContent, PopoverRoot, PopoverTrigger } from '../Popover'\n\nimport {\n formatOutputDate,\n formatOutputRange,\n parseInputDate,\n parseInputRange,\n InputDate,\n InputRange,\n ValueFormat,\n} from '@/lib/dateUtils'\nimport { cn } from '@/lib/utils'\n\ntype SupportedLocaleString = 'enUS' | 'es' | 'pt' | 'enGB' | 'de' | 'it' | 'fr'\n\nconst localeMap: Record<SupportedLocaleString, Locale> = {\n enUS,\n es,\n pt,\n enGB,\n de,\n it,\n fr,\n}\n\ntype DatePickerOutput<Variant extends 'single' | 'range', Format extends ValueFormat> = Variant extends 'single'\n ? Format extends 'date'\n ? Date | undefined\n : string | undefined\n : Format extends 'date'\n ? DateRange | undefined\n : { from?: string; to?: string } | undefined\n\ntype DatePickerInput<V extends 'single' | 'range'> = V extends 'single'\n ? Date | string | undefined\n : { from?: Date | string; to?: Date | string } | undefined\n\nexport interface DatePickerProps<V extends 'single' | 'range' = 'single', F extends ValueFormat = 'iso'>\n extends Omit<PropsBase, 'mode' | 'selected' | 'onSelect' | 'locale'> {\n /**\n * Placeholder text displayed when no date is selected\n * @default 'Pick a date' for single mode, 'Pick a date range' for range mode\n */\n readonly placeholder?: string\n\n /**\n * Format string to use when displaying the selected date in the button\n * @default 'MMM d, yyyy'\n */\n readonly displayFormat?: string\n\n /**\n * ClassName for the button\n */\n readonly buttonClassName?: string\n\n /**\n * Whether to allow the user to show the year switcher menu\n * @default true for single mode, false for range mode\n */\n readonly showYearSwitcher?: boolean\n\n /**\n * Initial value for the date picker\n * Accepts both Date objects and ISO strings regardless of valueFormat setting\n */\n readonly initialValue?: DatePickerInput<V>\n\n /**\n * Current value for the date picker\n * Accepts both Date objects and ISO strings regardless of valueFormat setting\n */\n readonly value?: DatePickerInput<V>\n\n /**\n * Determines if the picker should close after a selection\n * @default true for single mode, false for range mode\n */\n readonly closeOnSelect?: boolean\n\n /**\n * Determines the format of the value provided to the onChange callback\n * - 'iso' (default): onChange receives ISO string(s) ('yyyy-MM-dd')\n * - 'date': onChange receives JavaScript Date object(s)\n *\n * Note: The component accepts both Date objects and ISO strings for value/initialValue\n * regardless of this setting.\n * @default 'iso'\n */\n readonly valueFormat?: F\n\n /**\n * Callback when date or date range changes\n */\n readonly onChange?: (value: DatePickerOutput<V, F>) => void\n\n /**\n * DatePicker mode - single date or date range\n * @default 'single'\n */\n readonly variant?: V\n\n /**\n * The locale to use for formatting dates and determining the start of the week.\n * Can be a string identifier for supported locales ('enUS', 'es', 'pt', 'enGB', 'de', 'it', 'fr')\n * or a Locale object from date-fns/locale for other languages.\n * @default 'enUS'\n */\n readonly locale?: SupportedLocaleString | Locale\n}\n\nfunction DatePicker<V extends 'single' | 'range' = 'single', F extends ValueFormat = 'iso'>({\n variant = 'single' as V,\n placeholder = variant === 'single' ? 'Pick a date' : 'Pick a date range',\n valueFormat = 'iso' as F,\n initialValue: initialValueProp,\n value: valueProp,\n onChange,\n buttonClassName,\n displayFormat = 'MMM d, yyyy',\n closeOnSelect = variant === 'single',\n showYearSwitcher = variant === 'single',\n locale: localeProp = 'enUS',\n ...rest\n}: DatePickerProps<V, F>) {\n const [isOpen, setIsOpen] = React.useState(false)\n\n const resolvedLocale = React.useMemo(() => {\n if (typeof localeProp === 'string') {\n return localeMap[localeProp]\n }\n return localeProp\n }, [localeProp])\n\n const [internalSingleDate, setInternalSingleDate] = React.useState<Date | undefined>(() => {\n if (variant === 'single') {\n return parseInputDate(initialValueProp as InputDate)\n }\n return undefined\n })\n\n const [internalDateRange, setInternalDateRange] = React.useState<DateRange | undefined>(() => {\n if (variant === 'range') {\n return parseInputRange(initialValueProp as InputRange)\n }\n return undefined\n })\n\n React.useEffect(() => {\n if (variant === 'single') {\n const parsedValue = parseInputDate(valueProp as InputDate)\n if (parsedValue?.getTime() !== internalSingleDate?.getTime()) {\n setInternalSingleDate(parsedValue)\n }\n } else {\n const parsedValue = parseInputRange(valueProp as InputRange)\n if (\n parsedValue?.from?.getTime() !== internalDateRange?.from?.getTime() ||\n parsedValue?.to?.getTime() !== internalDateRange?.to?.getTime()\n ) {\n setInternalDateRange(parsedValue)\n }\n }\n }, [valueProp, variant])\n\n const singleDate = internalSingleDate\n const dateRange = internalDateRange\n\n const handleSelect = React.useCallback(\n (selectedDate: Date | DateRange | undefined) => {\n if (variant === 'single') {\n const date = selectedDate as Date | undefined\n setInternalSingleDate(date)\n if (onChange) {\n const output = valueFormat === 'date' ? date : formatOutputDate(date, 'iso')\n onChange(output as DatePickerOutput<V, F>)\n }\n if (closeOnSelect) {\n setIsOpen(false)\n }\n } else {\n const range = selectedDate as DateRange | undefined\n setInternalDateRange(range)\n if (onChange) {\n const output = valueFormat === 'date' ? range : formatOutputRange(range, 'iso')\n onChange(output as DatePickerOutput<V, F>)\n }\n }\n },\n [variant, valueFormat, onChange, closeOnSelect],\n )\n\n const formatForDisplay = () => {\n const formatOptions = { locale: resolvedLocale }\n if (variant === 'single') {\n return singleDate ? formatFn(singleDate, displayFormat, formatOptions) : placeholder\n }\n\n if (!dateRange) return placeholder\n const fromStr = dateRange.from ? formatFn(dateRange.from, displayFormat, formatOptions) : '...'\n const toStr = dateRange.to ? formatFn(dateRange.to, displayFormat, formatOptions) : '...'\n if (!dateRange.from && !dateRange.to) return placeholder\n if (!dateRange.from) return `... - ${toStr}`\n if (!dateRange.to) return `${fromStr} - ...`\n return `${fromStr} - ${toStr}`\n }\n\n const calendarProps = React.useMemo(() => {\n const baseProps = {\n ...rest,\n locale: resolvedLocale,\n initialFocus: true,\n }\n\n if (variant === 'single') {\n return {\n ...baseProps,\n mode: 'single' as const,\n selected: singleDate,\n onSelect: (date: Date | undefined) => handleSelect(date),\n defaultMonth: singleDate ?? new Date(),\n }\n }\n\n return {\n ...baseProps,\n mode: 'range' as const,\n selected: dateRange,\n onSelect: (range: DateRange | undefined) => handleSelect(range),\n defaultMonth: dateRange?.from ?? new Date(),\n numberOfMonths: rest.numberOfMonths ?? 2,\n }\n }, [variant, rest, singleDate, dateRange, handleSelect, resolvedLocale])\n\n return (\n <PopoverRoot open={isOpen} onOpenChange={setIsOpen}>\n <PopoverTrigger asChild>\n <Button\n id={rest.id}\n variant=\"ghost\"\n className={cn(\n 'relative flex w-fit items-center justify-start text-left font-normal',\n !(variant === 'single' ? singleDate : dateRange) && 'text-muted-foreground',\n buttonVariants({ variant: 'input' }),\n buttonClassName,\n )}\n disabled={typeof rest.disabled === 'boolean' ? rest.disabled : false}\n >\n <CalendarBlank className=\"absolute left-4 h-4 w-4 shrink-0\" />\n <span className=\"w-full pl-7 text-center\">{formatForDisplay()}</span>\n </Button>\n </PopoverTrigger>\n <PopoverContent className=\"w-auto p-0\" align=\"center\">\n <Calendar\n {...calendarProps}\n className=\"border-0\"\n showYearSwitcher={variant === 'single' ? showYearSwitcher : false}\n />\n </PopoverContent>\n </PopoverRoot>\n )\n}\n\nDatePicker.displayName = 'DatePicker'\n\nexport { DatePicker }\nexport type { DateRange }\n"],"names":[],"mappings":";;;;;;;;;;;;AAyBA;AAAyD;AACvD;AACA;AACA;AACA;AACA;AACA;AAEF;AAwFA;AAA4F;AAChF;AAC2C;AACvC;AACA;AACP;AACP;AACA;AACgB;AACY;AACG;AACV;AAEvB;AACE;AAEA;AACE;AACE;AAA2B;AAE7B;AAAO;AAGT;AACE;AACE;AAAmD;AAErD;AAAO;AAGT;AACE;AACE;AAAqD;AAEvD;AAAO;AAGT;AACE;AACE;AACA;AACE;AAAiC;AACnC;AAEA;AACA;AAIE;AAAgC;AAClC;AACF;AAGF;AACA;AAEA;AAA2B;AAEvB;AACE;AACA;AACA;AACE;AACA;AAAyC;AAE3C;AACE;AAAe;AACjB;AAEA;AACA;AACA;AACE;AACA;AAAyC;AAC3C;AACF;AACF;AAC8C;AAGhD;AACE;AACA;AACE;AAAyE;AAG3E;AACA;AACA;AACA;AACA;AACA;AACA;AAA4B;AAG9B;AACE;AAAkB;AACb;AACK;AACM;AAGhB;AACE;AAAO;AACF;AACG;AACI;AAC6C;AAClB;AACvC;AAGF;AAAO;AACF;AACG;AACI;AACoD;AACpB;AACH;AACzC;AAGF;AAEI;AACE;AAAC;AAAA;AACU;AACD;AACG;AACT;AACoD;AACjB;AACnC;AACF;AAC+D;AAE/D;AAA4D;AACE;AAAA;AAAA;AAElE;AAEE;AAAC;AAAA;AACK;AACM;AACkD;AAAA;AAEhE;AAGN;AAEA;;"}
@@ -0,0 +1,41 @@
1
+ import { DateRange } from 'react-day-picker';
2
+ /**
3
+ * Format options for date values returned by onChange callbacks
4
+ */
5
+ export type ValueFormat = 'iso' | 'date';
6
+ /**
7
+ * Type for single date input that can be a Date object, ISO string, or undefined
8
+ */
9
+ export type InputDate = Date | string | undefined;
10
+ /**
11
+ * Type for date range input that can be a DateRange object, an object with from/to properties, or undefined
12
+ */
13
+ export type InputRange = DateRange | {
14
+ from?: Date | string;
15
+ to?: Date | string;
16
+ } | undefined;
17
+ export type OutputDate<TFormat extends ValueFormat> = TFormat extends 'iso' ? string | undefined : Date | undefined;
18
+ export type OutputRange<TFormat extends ValueFormat> = TFormat extends 'iso' ? {
19
+ from?: string;
20
+ to?: string;
21
+ } | undefined : DateRange | undefined;
22
+ /**
23
+ * Parses an input value (Date or ISO string) into a valid Date object.
24
+ * Returns undefined if the input is invalid or cannot be parsed.
25
+ */
26
+ export declare const parseInputDate: (input: InputDate) => Date | undefined;
27
+ /**
28
+ * Formats a Date object into the specified output format (Date or ISO string).
29
+ * Returns undefined if the input date is undefined or invalid.
30
+ */
31
+ export declare const formatOutputDate: <TFormat extends "iso" | "date">(date: Date | undefined, format: TFormat) => OutputDate<TFormat>;
32
+ /**
33
+ * Parses an input range value (containing Dates or ISO strings) into a DateRange object.
34
+ * Returns undefined if the input is invalid.
35
+ */
36
+ export declare const parseInputRange: (input: InputRange) => DateRange | undefined;
37
+ /**
38
+ * Formats a DateRange object into the specified output format (DateRange or object with ISO strings).
39
+ * Returns undefined if the input range is undefined.
40
+ */
41
+ export declare const formatOutputRange: <TFormat extends "iso" | "date">(range: DateRange | undefined, format: TFormat) => OutputRange<TFormat>;
@@ -0,0 +1,50 @@
1
+ import { isValid, parseISO, format } from 'date-fns';
2
+
3
+ const parseInputDate = (input) => {
4
+ if (input === undefined || input === null) return undefined;
5
+ if (input instanceof Date && isValid(input)) {
6
+ return input;
7
+ }
8
+ if (typeof input === "string") {
9
+ try {
10
+ const parsedDate = parseISO(input);
11
+ return isValid(parsedDate) ? parsedDate : void 0;
12
+ } catch {
13
+ return undefined;
14
+ }
15
+ }
16
+ return undefined;
17
+ };
18
+ const formatOutputDate = (date, format$1) => {
19
+ if (date === undefined || !isValid(date)) return undefined;
20
+ if (format$1 === "date") {
21
+ return date;
22
+ }
23
+ try {
24
+ return format(date, "yyyy-MM-dd");
25
+ } catch {
26
+ return undefined;
27
+ }
28
+ };
29
+ const parseInputRange = (input) => {
30
+ if (!input) return undefined;
31
+ const fromDate = parseInputDate(input.from);
32
+ const toDate = parseInputDate(input.to);
33
+ if (fromDate === undefined && toDate === undefined) return undefined;
34
+ return { from: fromDate, to: toDate };
35
+ };
36
+ const formatOutputRange = (range, format) => {
37
+ if (!range) return undefined;
38
+ if (format === "date") {
39
+ return range;
40
+ }
41
+ const fromString = formatOutputDate(range.from, "iso");
42
+ const toString = formatOutputDate(range.to, "iso");
43
+ return {
44
+ from: fromString,
45
+ to: toString
46
+ };
47
+ };
48
+
49
+ export { formatOutputDate, formatOutputRange, parseInputDate, parseInputRange };
50
+ //# sourceMappingURL=dateUtils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dateUtils.js","sources":["../../src/lib/dateUtils.ts"],"sourcesContent":["import { format as formatFn, isValid, parseISO } from 'date-fns' // Used for parsing, formatting, and validating dates\nimport { DateRange } from 'react-day-picker' // Type used for date ranges\n\n/**\n * Format options for date values returned by onChange callbacks\n */\nexport type ValueFormat = 'iso' | 'date'\n\n/**\n * Type for single date input that can be a Date object, ISO string, or undefined\n */\nexport type InputDate = Date | string | undefined\n\n/**\n * Type for date range input that can be a DateRange object, an object with from/to properties, or undefined\n */\nexport type InputRange = DateRange | { from?: Date | string; to?: Date | string } | undefined\n\nexport type OutputDate<TFormat extends ValueFormat> = TFormat extends 'iso' ? string | undefined : Date | undefined\nexport type OutputRange<TFormat extends ValueFormat> = TFormat extends 'iso'\n ? { from?: string; to?: string } | undefined\n : DateRange | undefined\n\n/**\n * Parses an input value (Date or ISO string) into a valid Date object.\n * Returns undefined if the input is invalid or cannot be parsed.\n */\nexport const parseInputDate = (input: InputDate): Date | undefined => {\n if (input === undefined || input === null) return undefined\n\n if (input instanceof Date && isValid(input)) {\n return input\n }\n\n if (typeof input === 'string') {\n try {\n const parsedDate = parseISO(input)\n return isValid(parsedDate) ? parsedDate : undefined\n } catch {\n return undefined\n }\n }\n\n return undefined\n}\n\n/**\n * Formats a Date object into the specified output format (Date or ISO string).\n * Returns undefined if the input date is undefined or invalid.\n */\nexport const formatOutputDate = <TFormat extends 'iso' | 'date'>(\n date: Date | undefined,\n format: TFormat,\n): OutputDate<TFormat> => {\n if (date === undefined || !isValid(date)) return undefined\n\n if (format === 'date') {\n return date as OutputDate<TFormat>\n }\n\n try {\n return formatFn(date, 'yyyy-MM-dd') as OutputDate<TFormat>\n } catch {\n return undefined\n }\n}\n\n/**\n * Parses an input range value (containing Dates or ISO strings) into a DateRange object.\n * Returns undefined if the input is invalid.\n */\nexport const parseInputRange = (input: InputRange): DateRange | undefined => {\n if (!input) return undefined\n\n const fromDate = parseInputDate(input.from)\n const toDate = parseInputDate(input.to)\n\n if (fromDate === undefined && toDate === undefined) return undefined\n\n return { from: fromDate, to: toDate }\n}\n\n/**\n * Formats a DateRange object into the specified output format (DateRange or object with ISO strings).\n * Returns undefined if the input range is undefined.\n */\nexport const formatOutputRange = <TFormat extends 'iso' | 'date'>(\n range: DateRange | undefined,\n format: TFormat,\n): OutputRange<TFormat> => {\n if (!range) return undefined\n\n if (format === 'date') {\n return range as OutputRange<TFormat>\n }\n\n const fromString = formatOutputDate(range.from, 'iso')\n const toString = formatOutputDate(range.to, 'iso')\n\n return {\n from: fromString,\n to: toString,\n } as OutputRange<TFormat>\n}\n"],"names":["format","formatFn"],"mappings":";;AA2Ba,MAAA,cAAA,GAAiB,CAAC,KAAuC,KAAA;AACpE,EAAA,IAAI,KAAU,KAAA,SAAA,IAAa,KAAU,KAAA,IAAA,EAAa,OAAA,SAAA;AAElD,EAAA,IAAI,KAAiB,YAAA,IAAA,IAAQ,OAAQ,CAAA,KAAK,CAAG,EAAA;AAC3C,IAAO,OAAA,KAAA;AAAA;AAGT,EAAI,IAAA,OAAO,UAAU,QAAU,EAAA;AAC7B,IAAI,IAAA;AACF,MAAM,MAAA,UAAA,GAAa,SAAS,KAAK,CAAA;AACjC,MAAO,OAAA,OAAA,CAAQ,UAAU,CAAA,GAAI,UAAa,GAAA,KAAA,CAAA;AAAA,KACpC,CAAA,MAAA;AACN,MAAO,OAAA,SAAA;AAAA;AACT;AAGF,EAAO,OAAA,SAAA;AACT;AAMa,MAAA,gBAAA,GAAmB,CAC9B,IAAA,EACAA,QACwB,KAAA;AACxB,EAAA,IAAI,SAAS,SAAa,IAAA,CAAC,OAAQ,CAAA,IAAI,GAAU,OAAA,SAAA;AAEjD,EAAA,IAAIA,aAAW,MAAQ,EAAA;AACrB,IAAO,OAAA,IAAA;AAAA;AAGT,EAAI,IAAA;AACF,IAAO,OAAAC,MAAA,CAAS,MAAM,YAAY,CAAA;AAAA,GAC5B,CAAA,MAAA;AACN,IAAO,OAAA,SAAA;AAAA;AAEX;AAMa,MAAA,eAAA,GAAkB,CAAC,KAA6C,KAAA;AAC3E,EAAI,IAAA,CAAC,OAAc,OAAA,SAAA;AAEnB,EAAM,MAAA,QAAA,GAAW,cAAe,CAAA,KAAA,CAAM,IAAI,CAAA;AAC1C,EAAM,MAAA,MAAA,GAAS,cAAe,CAAA,KAAA,CAAM,EAAE,CAAA;AAEtC,EAAA,IAAI,QAAa,KAAA,SAAA,IAAa,MAAW,KAAA,SAAA,EAAkB,OAAA,SAAA;AAE3D,EAAA,OAAO,EAAE,IAAA,EAAM,QAAU,EAAA,EAAA,EAAI,MAAO,EAAA;AACtC;AAMa,MAAA,iBAAA,GAAoB,CAC/B,KAAA,EACA,MACyB,KAAA;AACzB,EAAI,IAAA,CAAC,OAAc,OAAA,SAAA;AAEnB,EAAA,IAAI,WAAW,MAAQ,EAAA;AACrB,IAAO,OAAA,KAAA;AAAA;AAGT,EAAA,MAAM,UAAa,GAAA,gBAAA,CAAiB,KAAM,CAAA,IAAA,EAAM,KAAK,CAAA;AACrD,EAAA,MAAM,QAAW,GAAA,gBAAA,CAAiB,KAAM,CAAA,EAAA,EAAI,KAAK,CAAA;AAEjD,EAAO,OAAA;AAAA,IACL,IAAM,EAAA,UAAA;AAAA,IACN,EAAI,EAAA;AAAA,GACN;AACF;;;;"}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "periplo-ui",
3
3
  "description": "IATI UI library",
4
4
  "private": false,
5
- "version": "3.16.0",
5
+ "version": "3.18.0",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
8
8
  "types": "dist/index.d.ts",
@@ -67,7 +67,7 @@
67
67
  "eslint-plugin-react-hooks": "4.6.2",
68
68
  "eslint-plugin-storybook": "0.11.2",
69
69
  "eslint-plugin-testing-library": "6.5.0",
70
- "eslint-plugin-vitest": "0.5.4",
70
+ "eslint-plugin-vitest": "0.4.1",
71
71
  "husky": "9.1.7",
72
72
  "jsdom": "24.1.3",
73
73
  "lint-staged": "15.3.0",