@payglocal_ui/flux-ui 0.1.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 (72) hide show
  1. package/package.json +72 -0
  2. package/src/accordion.tsx +68 -0
  3. package/src/alert.tsx +107 -0
  4. package/src/avatar-group.tsx +96 -0
  5. package/src/avatar-tag.tsx +136 -0
  6. package/src/avatar.tsx +39 -0
  7. package/src/badge.tsx +98 -0
  8. package/src/blanket.tsx +61 -0
  9. package/src/breadcrumb.tsx +119 -0
  10. package/src/button-group.tsx +218 -0
  11. package/src/button.tsx +83 -0
  12. package/src/calendar.tsx +227 -0
  13. package/src/callout.tsx +68 -0
  14. package/src/card.tsx +103 -0
  15. package/src/chart-templates.tsx +587 -0
  16. package/src/chart.tsx +379 -0
  17. package/src/checkbox-select.tsx +239 -0
  18. package/src/checkbox.tsx +54 -0
  19. package/src/code.tsx +154 -0
  20. package/src/command.tsx +77 -0
  21. package/src/country-select.tsx +242 -0
  22. package/src/currency-amount-input.tsx +72 -0
  23. package/src/data-table.tsx +378 -0
  24. package/src/date-picker.tsx +317 -0
  25. package/src/dialog.tsx +81 -0
  26. package/src/drawer.tsx +91 -0
  27. package/src/dropdown-menu.tsx +174 -0
  28. package/src/empty-state.tsx +32 -0
  29. package/src/field.tsx +243 -0
  30. package/src/flag.tsx +265 -0
  31. package/src/form.tsx +168 -0
  32. package/src/grid-flex.tsx +241 -0
  33. package/src/heading.tsx +202 -0
  34. package/src/icon-button.tsx +93 -0
  35. package/src/index.ts +332 -0
  36. package/src/inline-dialog.tsx +153 -0
  37. package/src/inline-edit.tsx +212 -0
  38. package/src/input-group.tsx +151 -0
  39. package/src/input.tsx +28 -0
  40. package/src/label.tsx +21 -0
  41. package/src/layout.tsx +119 -0
  42. package/src/link.tsx +80 -0
  43. package/src/lozenge.tsx +61 -0
  44. package/src/menu.tsx +146 -0
  45. package/src/otp-input.tsx +117 -0
  46. package/src/page-header.tsx +28 -0
  47. package/src/pagination.tsx +185 -0
  48. package/src/password-input.tsx +34 -0
  49. package/src/popover.tsx +31 -0
  50. package/src/progress-indicator.tsx +94 -0
  51. package/src/progress.tsx +95 -0
  52. package/src/radio-group.tsx +46 -0
  53. package/src/responsive.tsx +276 -0
  54. package/src/scroll-area.tsx +39 -0
  55. package/src/section-message.tsx +119 -0
  56. package/src/select.tsx +144 -0
  57. package/src/separator.tsx +26 -0
  58. package/src/side-nav.tsx +264 -0
  59. package/src/skeleton.tsx +74 -0
  60. package/src/slider.tsx +25 -0
  61. package/src/sonner.tsx +32 -0
  62. package/src/spinner.tsx +54 -0
  63. package/src/spotlight.tsx +141 -0
  64. package/src/status-badge.tsx +86 -0
  65. package/src/switch.tsx +70 -0
  66. package/src/tabs.tsx +57 -0
  67. package/src/tag.tsx +52 -0
  68. package/src/textarea.tsx +25 -0
  69. package/src/time-picker.tsx +443 -0
  70. package/src/tooltip.tsx +29 -0
  71. package/src/utils.ts +6 -0
  72. package/src/visually-hidden.tsx +25 -0
@@ -0,0 +1,119 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { ChevronRight, MoreHorizontal } from "lucide-react";
5
+ import { cn } from "./utils";
6
+
7
+ const Breadcrumb = React.forwardRef<
8
+ HTMLElement,
9
+ React.ComponentPropsWithoutRef<"nav"> & {
10
+ separator?: React.ReactNode;
11
+ }
12
+ >(({ className, ...props }, ref) => (
13
+ <nav
14
+ ref={ref}
15
+ aria-label="breadcrumb"
16
+ className={cn("", className)}
17
+ {...props}
18
+ />
19
+ ));
20
+ Breadcrumb.displayName = "Breadcrumb";
21
+
22
+ const BreadcrumbList = React.forwardRef<
23
+ HTMLOListElement,
24
+ React.ComponentPropsWithoutRef<"ol">
25
+ >(({ className, ...props }, ref) => (
26
+ <ol
27
+ ref={ref}
28
+ className={cn(
29
+ "flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2",
30
+ className
31
+ )}
32
+ {...props}
33
+ />
34
+ ));
35
+ BreadcrumbList.displayName = "BreadcrumbList";
36
+
37
+ const BreadcrumbItem = React.forwardRef<
38
+ HTMLLIElement,
39
+ React.ComponentPropsWithoutRef<"li">
40
+ >(({ className, ...props }, ref) => (
41
+ <li
42
+ ref={ref}
43
+ className={cn("inline-flex items-center gap-1.5", className)}
44
+ {...props}
45
+ />
46
+ ));
47
+ BreadcrumbItem.displayName = "BreadcrumbItem";
48
+
49
+ const BreadcrumbLink = React.forwardRef<
50
+ HTMLAnchorElement,
51
+ React.ComponentPropsWithoutRef<"a">
52
+ >(({ className, ...props }, ref) => (
53
+ <a
54
+ ref={ref}
55
+ className={cn(
56
+ "text-sm text-muted-foreground transition-colors duration-pg-fast ease-pg-standard hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/35 rounded-sm",
57
+ className
58
+ )}
59
+ {...props}
60
+ />
61
+ ));
62
+ BreadcrumbLink.displayName = "BreadcrumbLink";
63
+
64
+ const BreadcrumbPage = React.forwardRef<
65
+ HTMLSpanElement,
66
+ React.ComponentPropsWithoutRef<"span">
67
+ >(({ className, ...props }, ref) => (
68
+ <span
69
+ ref={ref}
70
+ role="link"
71
+ aria-current="page"
72
+ aria-disabled="true"
73
+ className={cn("text-sm font-medium text-foreground", className)}
74
+ {...props}
75
+ />
76
+ ));
77
+ BreadcrumbPage.displayName = "BreadcrumbPage";
78
+
79
+ const BreadcrumbSeparator = ({
80
+ children,
81
+ className,
82
+ ...props
83
+ }: React.ComponentProps<"li">) => (
84
+ <li
85
+ role="presentation"
86
+ aria-hidden="true"
87
+ className={cn("[&>svg]:size-3.5", className)}
88
+ {...props}
89
+ >
90
+ {children ?? <ChevronRight className="text-muted-foreground" />}
91
+ </li>
92
+ );
93
+ BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
94
+
95
+ const BreadcrumbEllipsis = ({
96
+ className,
97
+ ...props
98
+ }: React.ComponentProps<"span">) => (
99
+ <span
100
+ role="presentation"
101
+ aria-hidden="true"
102
+ className={cn("flex size-9 items-center justify-center", className)}
103
+ {...props}
104
+ >
105
+ <MoreHorizontal className="size-4 text-muted-foreground" />
106
+ <span className="sr-only">More</span>
107
+ </span>
108
+ );
109
+ BreadcrumbEllipsis.displayName = "BreadcrumbEllipsis";
110
+
111
+ export {
112
+ Breadcrumb,
113
+ BreadcrumbList,
114
+ BreadcrumbItem,
115
+ BreadcrumbLink,
116
+ BreadcrumbPage,
117
+ BreadcrumbSeparator,
118
+ BreadcrumbEllipsis,
119
+ };
@@ -0,0 +1,218 @@
1
+ "use client";
2
+
3
+ import { cn } from "./utils";
4
+ import { ChevronDown } from "lucide-react";
5
+ import {
6
+ forwardRef,
7
+ useState,
8
+ useRef,
9
+ useEffect,
10
+ type HTMLAttributes,
11
+ type ButtonHTMLAttributes,
12
+ type ReactNode,
13
+ } from "react";
14
+ import { Button } from "./button";
15
+ import type { ButtonProps } from "./button";
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // ButtonGroup
19
+ // ---------------------------------------------------------------------------
20
+
21
+ export interface ButtonGroupProps extends HTMLAttributes<HTMLDivElement> {
22
+ orientation?: "horizontal" | "vertical";
23
+ variant?: ButtonProps["variant"];
24
+ }
25
+
26
+ export const ButtonGroup = forwardRef<HTMLDivElement, ButtonGroupProps>(
27
+ ({ orientation = "horizontal", variant, className, children, ...props }, ref) => {
28
+ return (
29
+ <div
30
+ ref={ref}
31
+ role="group"
32
+ data-variant={variant}
33
+ className={cn(
34
+ "inline-flex",
35
+ orientation === "vertical" ? "flex-col" : "flex-row",
36
+ // Horizontal connected borders
37
+ orientation === "horizontal" && [
38
+ "[&>*:not(:first-child)]:rounded-l-none",
39
+ "[&>*:not(:last-child)]:rounded-r-none",
40
+ "[&>*:not(:first-child)]:-ml-px",
41
+ ],
42
+ // Vertical connected borders
43
+ orientation === "vertical" && [
44
+ "[&>*:not(:first-child)]:rounded-t-none",
45
+ "[&>*:not(:last-child)]:rounded-b-none",
46
+ "[&>*:not(:first-child)]:-mt-px",
47
+ ],
48
+ className
49
+ )}
50
+ {...props}
51
+ >
52
+ {children}
53
+ </div>
54
+ );
55
+ }
56
+ );
57
+ ButtonGroup.displayName = "ButtonGroup";
58
+
59
+ // ---------------------------------------------------------------------------
60
+ // SplitButton
61
+ // ---------------------------------------------------------------------------
62
+
63
+ export interface SplitButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "onClick"> {
64
+ label: ReactNode;
65
+ onClick?: ButtonHTMLAttributes<HTMLButtonElement>["onClick"];
66
+ children?: ReactNode;
67
+ variant?: ButtonProps["variant"];
68
+ size?: ButtonProps["size"];
69
+ isLoading?: boolean;
70
+ }
71
+
72
+ export const SplitButton = forwardRef<HTMLDivElement, SplitButtonProps>(
73
+ (
74
+ {
75
+ label,
76
+ onClick,
77
+ children,
78
+ variant = "primary",
79
+ size = "md",
80
+ isLoading = false,
81
+ className,
82
+ disabled,
83
+ ...props
84
+ },
85
+ ref
86
+ ) => {
87
+ const [open, setOpen] = useState(false);
88
+ const containerRef = useRef<HTMLDivElement>(null);
89
+
90
+ // Merge forwarded ref with internal ref
91
+ function setRefs(el: HTMLDivElement | null) {
92
+ (containerRef as React.MutableRefObject<HTMLDivElement | null>).current = el;
93
+ if (typeof ref === "function") {
94
+ ref(el);
95
+ } else if (ref) {
96
+ (ref as React.MutableRefObject<HTMLDivElement | null>).current = el;
97
+ }
98
+ }
99
+
100
+ // Close dropdown when clicking outside
101
+ useEffect(() => {
102
+ if (!open) return;
103
+ function handleOutside(e: MouseEvent) {
104
+ if (
105
+ containerRef.current &&
106
+ !containerRef.current.contains(e.target as Node)
107
+ ) {
108
+ setOpen(false);
109
+ }
110
+ }
111
+ document.addEventListener("mousedown", handleOutside);
112
+ return () => document.removeEventListener("mousedown", handleOutside);
113
+ }, [open]);
114
+
115
+ return (
116
+ <div
117
+ ref={setRefs}
118
+ className={cn("relative inline-flex", className)}
119
+ {...(props as HTMLAttributes<HTMLDivElement>)}
120
+ >
121
+ {/* Primary action button */}
122
+ <Button
123
+ variant={variant}
124
+ size={size}
125
+ isLoading={isLoading}
126
+ disabled={disabled}
127
+ onClick={onClick}
128
+ className="rounded-r-none"
129
+ >
130
+ {label}
131
+ </Button>
132
+
133
+ {/* Dropdown trigger */}
134
+ <button
135
+ type="button"
136
+ disabled={disabled || isLoading}
137
+ aria-haspopup="menu"
138
+ aria-expanded={open}
139
+ onClick={() => setOpen((v) => !v)}
140
+ className={cn(
141
+ "inline-flex items-center justify-center px-2 rounded-l-none -ml-px",
142
+ "transition-colors duration-pg-fast ease-pg-standard",
143
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/35",
144
+ "disabled:cursor-not-allowed disabled:opacity-50",
145
+ // Match variant styling
146
+ variant === "primary" &&
147
+ "bg-primary text-primary-foreground border border-primary shadow-sm hover:bg-[var(--primary-hover)] border-l border-l-primary-foreground/20",
148
+ variant === "secondary" &&
149
+ "bg-muted text-foreground border border-border shadow-sm hover:bg-muted/85 dark:bg-muted/35 dark:hover:bg-muted/55 border-l-0",
150
+ variant === "outline" &&
151
+ "bg-card text-foreground border border-border shadow-sm hover:bg-muted border-l-0",
152
+ variant === "ghost" &&
153
+ "bg-transparent text-foreground border border-transparent hover:bg-muted border-l-0",
154
+ variant === "danger" &&
155
+ "bg-red-600 text-white border border-red-600 shadow-sm hover:bg-red-700 border-l border-l-white/20",
156
+ // Size heights to match Button
157
+ size === "sm" && "h-9 min-h-9 rounded-lg",
158
+ size === "md" && "h-10 min-h-10 rounded-lg",
159
+ size === "lg" && "h-[3.25rem] min-h-[3.25rem] rounded-xl"
160
+ )}
161
+ >
162
+ <ChevronDown
163
+ className={cn(
164
+ "transition-transform duration-pg-fast ease-pg-standard",
165
+ open && "rotate-180",
166
+ size === "sm" ? "size-3.5" : size === "lg" ? "size-[1.125rem]" : "size-4"
167
+ )}
168
+ />
169
+ </button>
170
+
171
+ {/* Dropdown menu */}
172
+ {open && children && (
173
+ <div
174
+ role="menu"
175
+ className={cn(
176
+ "absolute right-0 top-full mt-1 z-50",
177
+ "bg-card border border-border rounded-lg shadow-lg py-1 min-w-32"
178
+ )}
179
+ >
180
+ {children}
181
+ </div>
182
+ )}
183
+ </div>
184
+ );
185
+ }
186
+ );
187
+ SplitButton.displayName = "SplitButton";
188
+
189
+ // ---------------------------------------------------------------------------
190
+ // SplitButtonItem — convenience wrapper for dropdown items
191
+ // ---------------------------------------------------------------------------
192
+
193
+ export interface SplitButtonItemProps extends ButtonHTMLAttributes<HTMLButtonElement> {
194
+ children: ReactNode;
195
+ }
196
+
197
+ export const SplitButtonItem = forwardRef<HTMLButtonElement, SplitButtonItemProps>(
198
+ ({ children, className, ...props }, ref) => {
199
+ return (
200
+ <button
201
+ ref={ref}
202
+ role="menuitem"
203
+ type="button"
204
+ className={cn(
205
+ "block w-full text-left px-3 py-2 text-sm text-foreground",
206
+ "hover:bg-muted transition-colors duration-pg-fast ease-pg-standard",
207
+ "disabled:cursor-not-allowed disabled:opacity-50",
208
+ "focus-visible:outline-none focus-visible:bg-muted",
209
+ className
210
+ )}
211
+ {...props}
212
+ >
213
+ {children}
214
+ </button>
215
+ );
216
+ }
217
+ );
218
+ SplitButtonItem.displayName = "SplitButtonItem";
package/src/button.tsx ADDED
@@ -0,0 +1,83 @@
1
+ "use client";
2
+
3
+ import { cn } from "./utils";
4
+ import { Loader2 } from "lucide-react";
5
+ import { type ButtonHTMLAttributes, forwardRef } from "react";
6
+
7
+ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
8
+ variant?: "primary" | "secondary" | "ghost" | "danger" | "outline" | "link";
9
+ size?: "sm" | "md" | "lg";
10
+ isLoading?: boolean;
11
+ leftIcon?: React.ReactNode;
12
+ rightIcon?: React.ReactNode;
13
+ }
14
+
15
+ const variantClasses: Record<NonNullable<ButtonProps["variant"]>, string> = {
16
+ primary:
17
+ "bg-primary text-primary-foreground border border-primary shadow-sm hover:bg-[var(--primary-hover)]",
18
+ secondary:
19
+ "bg-muted text-foreground border border-border shadow-sm hover:bg-muted/85 dark:bg-muted/35 dark:text-foreground dark:border-border dark:hover:bg-muted/55",
20
+ ghost:
21
+ "bg-transparent text-foreground border border-transparent hover:bg-muted focus-visible:bg-muted/80 active:bg-muted/90",
22
+ danger:
23
+ "bg-red-600 text-white border border-red-600 shadow-sm hover:bg-red-700",
24
+ outline:
25
+ "bg-card text-foreground border border-border shadow-sm hover:bg-muted",
26
+ link:
27
+ "h-auto min-h-0 rounded-md border border-transparent bg-transparent px-2 py-2 text-[15px] font-medium text-primary shadow-none underline-offset-4 hover:bg-primary/5 hover:underline focus-visible:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/35 disabled:pointer-events-none disabled:opacity-50 disabled:hover:bg-transparent disabled:hover:no-underline",
28
+ };
29
+
30
+ /** Heights and padding match dashboard primary actions (~44–48px) and comfortable tap targets. */
31
+ const sizes = {
32
+ sm: "h-9 min-h-9 px-3.5 text-xs gap-1.5 rounded-lg",
33
+ md: "h-10 min-h-10 px-5 text-sm gap-2 rounded-lg",
34
+ lg: "h-[3.25rem] min-h-[3.25rem] px-10 text-[15px] gap-2.5 rounded-xl",
35
+ };
36
+
37
+ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
38
+ (
39
+ {
40
+ variant = "primary",
41
+ size = "md",
42
+ isLoading = false,
43
+ leftIcon,
44
+ rightIcon,
45
+ children,
46
+ className,
47
+ disabled,
48
+ ...props
49
+ },
50
+ ref
51
+ ) => {
52
+ return (
53
+ <button
54
+ ref={ref}
55
+ disabled={disabled || isLoading}
56
+ className={cn(
57
+ "inline-flex items-center justify-center font-medium transition-colors duration-pg-fast ease-pg-standard",
58
+ variant !== "link" && "disabled:cursor-not-allowed disabled:opacity-50",
59
+ variant === "link" && "justify-center",
60
+ variant !== "link" && "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/35",
61
+ variantClasses[variant],
62
+ variant !== "link" && sizes[size],
63
+ className
64
+ )}
65
+ {...props}
66
+ >
67
+ {isLoading ? (
68
+ <Loader2
69
+ className={cn(
70
+ "animate-spin",
71
+ size === "sm" ? "size-3.5" : size === "lg" ? "size-[1.125rem]" : "size-3.5"
72
+ )}
73
+ />
74
+ ) : (
75
+ leftIcon
76
+ )}
77
+ <span className={cn(isLoading && "opacity-70")}>{children}</span>
78
+ {!isLoading && rightIcon}
79
+ </button>
80
+ );
81
+ }
82
+ );
83
+ Button.displayName = "Button";
@@ -0,0 +1,227 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { ChevronDown, ChevronLeft, ChevronRight } from "lucide-react";
5
+ import {
6
+ DayPicker,
7
+ getDefaultClassNames,
8
+ type DayButtonProps,
9
+ type DayPickerProps,
10
+ } from "react-day-picker";
11
+
12
+ import { Button } from "./button";
13
+ import type { ButtonProps } from "./button";
14
+ import { cn } from "./utils";
15
+
16
+ const cellSize = "[--cell-size:2rem]";
17
+
18
+ function navButtonClasses(variant: NonNullable<ButtonProps["variant"]>): string {
19
+ const base =
20
+ "inline-flex items-center justify-center rounded-lg border font-medium transition-colors duration-pg-fast ease-pg-standard focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/35 disabled:pointer-events-none disabled:opacity-50";
21
+ switch (variant) {
22
+ case "outline":
23
+ return cn(
24
+ base,
25
+ "border-border bg-card text-foreground shadow-sm hover:bg-muted"
26
+ );
27
+ case "ghost":
28
+ default:
29
+ return cn(
30
+ base,
31
+ "border-transparent bg-transparent text-foreground hover:bg-muted focus-visible:bg-muted/80 active:bg-muted/90"
32
+ );
33
+ }
34
+ }
35
+
36
+ export type CalendarProps = DayPickerProps & {
37
+ buttonVariant?: ButtonProps["variant"];
38
+ };
39
+
40
+ function Calendar({
41
+ className,
42
+ classNames,
43
+ showOutsideDays = true,
44
+ captionLayout = "label",
45
+ buttonVariant = "ghost",
46
+ locale,
47
+ formatters,
48
+ components,
49
+ showWeekNumber,
50
+ ...props
51
+ }: CalendarProps) {
52
+ const defaultClassNames = getDefaultClassNames();
53
+
54
+ return (
55
+ <DayPicker
56
+ showOutsideDays={showOutsideDays}
57
+ showWeekNumber={showWeekNumber}
58
+ locale={locale}
59
+ className={cn(
60
+ "group/calendar bg-background p-3",
61
+ cellSize,
62
+ "[[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
63
+ String.raw`rtl:**:[.rdp-button_next>svg]:rotate-180`,
64
+ String.raw`rtl:**:[.rdp-button_previous>svg]:rotate-180`,
65
+ className
66
+ )}
67
+ captionLayout={captionLayout}
68
+ formatters={{
69
+ formatMonthDropdown: (date) =>
70
+ date.toLocaleString(locale?.code ?? "default", { month: "short" }),
71
+ ...formatters,
72
+ }}
73
+ classNames={{
74
+ root: cn("w-fit", defaultClassNames.root),
75
+ months: cn(
76
+ "relative flex flex-col gap-4 md:flex-row",
77
+ defaultClassNames.months
78
+ ),
79
+ month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
80
+ nav: cn(
81
+ "absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
82
+ defaultClassNames.nav
83
+ ),
84
+ button_previous: cn(
85
+ navButtonClasses(buttonVariant),
86
+ "h-[var(--cell-size)] w-[var(--cell-size)] min-h-0 min-w-0 shrink-0 p-0 select-none",
87
+ defaultClassNames.button_previous
88
+ ),
89
+ button_next: cn(
90
+ navButtonClasses(buttonVariant),
91
+ "h-[var(--cell-size)] w-[var(--cell-size)] min-h-0 min-w-0 shrink-0 p-0 select-none",
92
+ defaultClassNames.button_next
93
+ ),
94
+ month_caption: cn(
95
+ "flex h-[var(--cell-size)] w-full items-center justify-center px-[var(--cell-size)]",
96
+ defaultClassNames.month_caption
97
+ ),
98
+ dropdowns: cn(
99
+ "flex h-[var(--cell-size)] w-full items-center justify-center gap-1.5 text-sm font-medium",
100
+ defaultClassNames.dropdowns
101
+ ),
102
+ dropdown_root: cn(
103
+ "relative rounded-md border border-border bg-card shadow-sm focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/35",
104
+ defaultClassNames.dropdown_root
105
+ ),
106
+ dropdown: cn("absolute inset-0 cursor-pointer opacity-0", defaultClassNames.dropdown),
107
+ caption_label: cn(
108
+ "font-medium select-none",
109
+ captionLayout === "label"
110
+ ? "text-sm"
111
+ : "flex h-8 items-center gap-1 rounded-md pl-2 pr-1 text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground",
112
+ defaultClassNames.caption_label
113
+ ),
114
+ month_grid: cn("w-full border-collapse", defaultClassNames.month_grid),
115
+ weekdays: cn("flex", defaultClassNames.weekdays),
116
+ weekday: cn(
117
+ "flex-1 rounded-md text-[0.8rem] font-normal text-muted-foreground select-none",
118
+ defaultClassNames.weekday
119
+ ),
120
+ week: cn("mt-2 flex w-full", defaultClassNames.week),
121
+ week_number_header: cn(
122
+ "w-[var(--cell-size)] select-none",
123
+ defaultClassNames.week_number_header
124
+ ),
125
+ week_number: cn(
126
+ "text-[0.8rem] text-muted-foreground select-none",
127
+ defaultClassNames.week_number
128
+ ),
129
+ day: cn(
130
+ "group/day relative aspect-square h-full w-full p-0 text-center select-none",
131
+ "[&:last-child[data-selected=true]_button]:rounded-r-md",
132
+ showWeekNumber
133
+ ? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-md"
134
+ : "[&:first-child[data-selected=true]_button]:rounded-l-md",
135
+ defaultClassNames.day
136
+ ),
137
+ range_start: cn(
138
+ "rounded-l-md bg-accent",
139
+ defaultClassNames.range_start
140
+ ),
141
+ range_middle: cn("rounded-none bg-accent/60", defaultClassNames.range_middle),
142
+ range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end),
143
+ today: cn(
144
+ "rounded-md bg-muted text-foreground data-[selected=true]:rounded-none",
145
+ defaultClassNames.today
146
+ ),
147
+ outside: cn(
148
+ "text-muted-foreground aria-selected:text-muted-foreground",
149
+ defaultClassNames.outside
150
+ ),
151
+ disabled: cn("text-muted-foreground opacity-50", defaultClassNames.disabled),
152
+ hidden: cn("invisible", defaultClassNames.hidden),
153
+ ...classNames,
154
+ }}
155
+ components={{
156
+ Root: ({ className: rootClass, rootRef, ...rootProps }) => (
157
+ <div
158
+ data-slot="calendar"
159
+ ref={rootRef}
160
+ className={cn(rootClass)}
161
+ {...rootProps}
162
+ />
163
+ ),
164
+ Chevron: ({ className: chClass, orientation, ...chProps }) => {
165
+ if (orientation === "left") {
166
+ return <ChevronLeft className={cn("size-4", chClass)} {...chProps} />;
167
+ }
168
+ if (orientation === "right") {
169
+ return <ChevronRight className={cn("size-4", chClass)} {...chProps} />;
170
+ }
171
+ return <ChevronDown className={cn("size-4", chClass)} {...chProps} />;
172
+ },
173
+ DayButton: CalendarDayButton,
174
+ WeekNumber: ({ children, ...weekProps }) => (
175
+ <th {...weekProps}>
176
+ <div className="flex h-[var(--cell-size)] w-[var(--cell-size)] min-h-[var(--cell-size)] min-w-[var(--cell-size)] items-center justify-center text-center text-xs tabular-nums">
177
+ {children}
178
+ </div>
179
+ </th>
180
+ ),
181
+ ...components,
182
+ }}
183
+ {...props}
184
+ />
185
+ );
186
+ }
187
+
188
+ function CalendarDayButton({
189
+ className,
190
+ day,
191
+ modifiers,
192
+ ...props
193
+ }: DayButtonProps) {
194
+ const defaultClassNames = getDefaultClassNames();
195
+ const ref = React.useRef<HTMLButtonElement>(null);
196
+ React.useEffect(() => {
197
+ if (modifiers.focused) ref.current?.focus();
198
+ }, [modifiers.focused]);
199
+
200
+ return (
201
+ <Button
202
+ ref={ref}
203
+ type="button"
204
+ variant="ghost"
205
+ {...props}
206
+ disabled={Boolean(modifiers.disabled) || props.disabled}
207
+ data-day={day.date.toLocaleDateString()}
208
+ data-selected-single={
209
+ modifiers.selected &&
210
+ !modifiers.range_start &&
211
+ !modifiers.range_end &&
212
+ !modifiers.range_middle
213
+ }
214
+ data-range-start={modifiers.range_start}
215
+ data-range-end={modifiers.range_end}
216
+ data-range-middle={modifiers.range_middle}
217
+ className={cn(
218
+ "flex aspect-square h-auto min-h-[var(--cell-size)] w-full min-w-[var(--cell-size)] flex-col gap-1 rounded-md p-0 font-normal leading-none data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:rounded-none data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground hover:bg-muted/80 dark:hover:bg-muted/50 [&>span]:text-xs [&>span]:opacity-70",
219
+ "group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-[3px] group-data-[focused=true]/day:ring-ring/35",
220
+ defaultClassNames.day,
221
+ className
222
+ )}
223
+ />
224
+ );
225
+ }
226
+
227
+ export { Calendar, CalendarDayButton };