@chesapeake/stack 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 (82) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/LICENSE +190 -0
  3. package/README.md +440 -0
  4. package/components/ui/avatar.tsx +87 -0
  5. package/components/ui/badge.tsx +49 -0
  6. package/components/ui/badgeVariants.ts +26 -0
  7. package/components/ui/button-group.tsx +67 -0
  8. package/components/ui/button.tsx +70 -0
  9. package/components/ui/buttonGroupVariants.ts +23 -0
  10. package/components/ui/buttonVariants.ts +38 -0
  11. package/components/ui/calendar.tsx +220 -0
  12. package/components/ui/card.tsx +162 -0
  13. package/components/ui/dialog.tsx +290 -0
  14. package/components/ui/drawer.tsx +227 -0
  15. package/components/ui/dropdown-menu.tsx +260 -0
  16. package/components/ui/index.ts +86 -0
  17. package/components/ui/input.tsx +59 -0
  18. package/components/ui/label.tsx +47 -0
  19. package/components/ui/location-input.md +194 -0
  20. package/components/ui/location-input.tsx +384 -0
  21. package/components/ui/popover.tsx +110 -0
  22. package/components/ui/select.tsx +306 -0
  23. package/components/ui/separator.tsx +50 -0
  24. package/components/ui/sonner-api.ts +8 -0
  25. package/components/ui/sonner.tsx +69 -0
  26. package/components/ui/textarea.tsx +53 -0
  27. package/components/ui/tooltip.tsx +125 -0
  28. package/components/ux/ActivityAnimation.tsx +135 -0
  29. package/components/ux/EmptyState.tsx +22 -0
  30. package/components/ux/FeedbackDrawer.tsx +141 -0
  31. package/components/ux/FireAnimation.tsx +189 -0
  32. package/components/ux/FriendList.tsx +111 -0
  33. package/components/ux/FriendRequestList.tsx +104 -0
  34. package/components/ux/GifSearch.tsx +264 -0
  35. package/components/ux/ImageUpload.tsx +829 -0
  36. package/components/ux/SelectedUsersList.tsx +91 -0
  37. package/components/ux/StandardLayout/PageLayout.tsx +107 -0
  38. package/components/ux/StandardLayout/ProfilePage.tsx +452 -0
  39. package/components/ux/StandardLayout/StandardLayout.tsx +345 -0
  40. package/components/ux/StandardLayout/index.ts +22 -0
  41. package/components/ux/TutorialCarousel.tsx +128 -0
  42. package/components/ux/UserSearch.tsx +324 -0
  43. package/components/ux/UserSelector.tsx +116 -0
  44. package/components/ux/auth/AuthSelector.tsx +154 -0
  45. package/components/ux/auth/MultiAuthProvider.tsx +196 -0
  46. package/components/ux/auth/README.md +317 -0
  47. package/components/ux/auth/context.ts +46 -0
  48. package/components/ux/auth/github/AuthCallback.tsx +177 -0
  49. package/components/ux/auth/github/AuthProvider.tsx +111 -0
  50. package/components/ux/auth/github/README.md +208 -0
  51. package/components/ux/auth/github/client.ts +127 -0
  52. package/components/ux/auth/github/config.ts +68 -0
  53. package/components/ux/auth/github/context.ts +34 -0
  54. package/components/ux/auth/github/default-client.ts +74 -0
  55. package/components/ux/auth/github/index.ts +43 -0
  56. package/components/ux/auth/github/types.ts +44 -0
  57. package/components/ux/auth/github/useGitHubAuth.ts +16 -0
  58. package/components/ux/auth/google/AuthCallback.tsx +170 -0
  59. package/components/ux/auth/google/AuthProvider.tsx +108 -0
  60. package/components/ux/auth/google/README.md +450 -0
  61. package/components/ux/auth/google/client.ts +109 -0
  62. package/components/ux/auth/google/config.ts +65 -0
  63. package/components/ux/auth/google/context.ts +34 -0
  64. package/components/ux/auth/google/default-client.ts +69 -0
  65. package/components/ux/auth/google/index.ts +43 -0
  66. package/components/ux/auth/google/types.ts +42 -0
  67. package/components/ux/auth/google/useGoogleAuth.ts +16 -0
  68. package/components/ux/auth/index.ts +72 -0
  69. package/components/ux/auth/useAuth.ts +16 -0
  70. package/components/ux/index.ts +46 -0
  71. package/components/ux/theme-provider-context.ts +20 -0
  72. package/components/ux/theme-provider.tsx +114 -0
  73. package/components/ux/useTheme.ts +42 -0
  74. package/index.ts +29 -0
  75. package/lib/utils.ts +57 -0
  76. package/package.json +170 -0
  77. package/styles/base.css +38 -0
  78. package/styles/components.css +54 -0
  79. package/styles/index.css +7 -0
  80. package/styles/tokens.css +189 -0
  81. package/tailwind.preset.ts +169 -0
  82. package/types/user.ts +69 -0
@@ -0,0 +1,87 @@
1
+ // Copyright 2026 Chesapeake Computing
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import * as React from "react";
5
+ import * as AvatarPrimitive from "@radix-ui/react-avatar";
6
+
7
+ import { cn } from "../../lib/utils";
8
+
9
+ /**
10
+ * Container component for displaying user avatars.
11
+ * Provides consistent circular styling and overflow handling.
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * <Avatar>
16
+ * <AvatarImage src="https://example.com/avatar.jpg" alt="User" />
17
+ * <AvatarFallback>JD</AvatarFallback>
18
+ * </Avatar>
19
+ * ```
20
+ */
21
+ function Avatar({
22
+ className,
23
+ ...props
24
+ }: React.ComponentProps<typeof AvatarPrimitive.Root>) {
25
+ return (
26
+ <AvatarPrimitive.Root
27
+ data-slot="avatar"
28
+ className={cn(
29
+ "relative flex size-8 shrink-0 overflow-hidden rounded-full",
30
+ className
31
+ )}
32
+ {...props}
33
+ />
34
+ );
35
+ }
36
+
37
+ /**
38
+ * Image component for displaying the avatar image.
39
+ * Renders the actual avatar image within the Avatar container.
40
+ * Falls back to AvatarFallback if image fails to load.
41
+ *
42
+ * @example
43
+ * ```tsx
44
+ * <AvatarImage src="https://example.com/avatar.jpg" alt="User Name" />
45
+ * ```
46
+ */
47
+ function AvatarImage({
48
+ className,
49
+ ...props
50
+ }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
51
+ return (
52
+ <AvatarPrimitive.Image
53
+ data-slot="avatar-image"
54
+ className={cn("aspect-square size-full", className)}
55
+ {...props}
56
+ />
57
+ );
58
+ }
59
+
60
+ /**
61
+ * Fallback component displayed when the avatar image fails to load.
62
+ * Typically shows initials or a placeholder icon.
63
+ *
64
+ * @example
65
+ * ```tsx
66
+ * <AvatarFallback>JD</AvatarFallback>
67
+ * // or with icon
68
+ * <AvatarFallback><UserIcon /></AvatarFallback>
69
+ * ```
70
+ */
71
+ function AvatarFallback({
72
+ className,
73
+ ...props
74
+ }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
75
+ return (
76
+ <AvatarPrimitive.Fallback
77
+ data-slot="avatar-fallback"
78
+ className={cn(
79
+ "bg-muted flex size-full items-center justify-center rounded-full",
80
+ className
81
+ )}
82
+ {...props}
83
+ />
84
+ );
85
+ }
86
+
87
+ export { Avatar, AvatarFallback, AvatarImage };
@@ -0,0 +1,49 @@
1
+ // Copyright 2026 Chesapeake Computing
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import * as React from "react";
5
+
6
+ import { cn } from "../../lib/utils";
7
+ import { badgeVariants, type BadgeVariantsProps } from "./badgeVariants.ts";
8
+
9
+ /**
10
+ * Props for the Badge component
11
+ * Extends HTML div props and adds variant options
12
+ */
13
+ export interface BadgeProps
14
+ extends React.HTMLAttributes<HTMLDivElement>, BadgeVariantsProps {}
15
+
16
+ /**
17
+ * A small label component for displaying status, categories, or counts.
18
+ *
19
+ * Available variants: default, secondary, destructive, outline
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * // Default badge
24
+ * <Badge>New</Badge>
25
+ *
26
+ * // Secondary variant
27
+ * <Badge variant="secondary">Draft</Badge>
28
+ *
29
+ * // Destructive variant
30
+ * <Badge variant="destructive">Error</Badge>
31
+ *
32
+ * // Outline variant
33
+ * <Badge variant="outline">Pending</Badge>
34
+ *
35
+ * // With count
36
+ * <Badge>42</Badge>
37
+ * ```
38
+ */
39
+ function Badge({ className, variant, ...props }: BadgeProps) {
40
+ return (
41
+ <div
42
+ data-slot="badge"
43
+ className={cn(badgeVariants({ variant }), className)}
44
+ {...props}
45
+ />
46
+ );
47
+ }
48
+
49
+ export { Badge };
@@ -0,0 +1,26 @@
1
+ // Copyright 2026 Chesapeake Computing
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { cva, type VariantProps } from "class-variance-authority";
5
+
6
+ export const badgeVariants = cva(
7
+ "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default:
12
+ "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
13
+ secondary:
14
+ "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15
+ destructive:
16
+ "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
17
+ outline: "text-foreground",
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ variant: "default",
22
+ },
23
+ }
24
+ );
25
+
26
+ export type BadgeVariantsProps = VariantProps<typeof badgeVariants>;
@@ -0,0 +1,67 @@
1
+ // Copyright 2026 Chesapeake Computing
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { Slot } from "@radix-ui/react-slot";
5
+
6
+ import { cn } from "../../lib/utils";
7
+ import {
8
+ buttonGroupVariants,
9
+ type ButtonGroupVariantsProps,
10
+ } from "./buttonGroupVariants.ts";
11
+ import { Separator } from "./separator";
12
+
13
+ function ButtonGroup({
14
+ className,
15
+ orientation,
16
+ ...props
17
+ }: React.ComponentPropsWithoutRef<"div"> & ButtonGroupVariantsProps) {
18
+ return (
19
+ <div
20
+ role="group"
21
+ data-slot="button-group"
22
+ data-orientation={orientation}
23
+ className={cn(buttonGroupVariants({ orientation }), className)}
24
+ {...props}
25
+ />
26
+ );
27
+ }
28
+
29
+ function ButtonGroupText({
30
+ className,
31
+ asChild,
32
+ ...props
33
+ }: React.ComponentPropsWithoutRef<"div"> & {
34
+ asChild?: boolean;
35
+ }) {
36
+ const Comp = asChild ? Slot : "div";
37
+
38
+ return (
39
+ <Comp
40
+ className={cn(
41
+ "bg-muted flex items-center gap-2 rounded-md border px-4 text-sm font-medium shadow-xs [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
42
+ className
43
+ )}
44
+ {...props}
45
+ />
46
+ );
47
+ }
48
+
49
+ function ButtonGroupSeparator({
50
+ className,
51
+ orientation,
52
+ ...props
53
+ }: React.ComponentProps<typeof Separator>) {
54
+ return (
55
+ <Separator
56
+ data-slot="button-group-separator"
57
+ orientation={orientation}
58
+ className={cn(
59
+ "bg-input relative !m-0 self-stretch data-[orientation=vertical]:h-auto",
60
+ className
61
+ )}
62
+ {...props}
63
+ />
64
+ );
65
+ }
66
+
67
+ export { ButtonGroup, ButtonGroupSeparator, ButtonGroupText };
@@ -0,0 +1,70 @@
1
+ // Copyright 2026 Chesapeake Computing
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import React from "react";
5
+ import { Slot } from "@radix-ui/react-slot";
6
+
7
+ import { cn } from "../../lib/utils";
8
+ import { buttonVariants, type ButtonVariantsProps } from "./buttonVariants.ts";
9
+
10
+ /**
11
+ * Props for the Button component
12
+ * Extends HTML button props and adds variant/size options from buttonVariants
13
+ */
14
+ interface ButtonProps
15
+ extends
16
+ Omit<React.ComponentPropsWithoutRef<"button">, "ref">,
17
+ ButtonVariantsProps {
18
+ /**
19
+ * When true, the button will pass all props to its child element.
20
+ * Useful for rendering buttons as links or other elements.
21
+ * @default false
22
+ */
23
+ asChild?: boolean;
24
+ }
25
+
26
+ /**
27
+ * A versatile button component with multiple variants and sizes.
28
+ *
29
+ * Supports various styles including default, destructive, outline, secondary, ghost, and link.
30
+ * Can be rendered as a different element using the asChild prop.
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * // Default button
35
+ * <Button>Click me</Button>
36
+ *
37
+ * // Destructive variant
38
+ * <Button variant="destructive">Delete</Button>
39
+ *
40
+ * // Different sizes
41
+ * <Button size="sm">Small</Button>
42
+ * <Button size="lg">Large</Button>
43
+ *
44
+ * // As a link
45
+ * <Button asChild>
46
+ * <a href="/path">Link Button</a>
47
+ * </Button>
48
+ *
49
+ * // Outline style
50
+ * <Button variant="outline">Outline</Button>
51
+ * ```
52
+ */
53
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
54
+ ({ className, variant, size, asChild, ...props }, ref) => {
55
+ const Comp = asChild ? Slot : "button";
56
+
57
+ return (
58
+ <Comp
59
+ ref={ref}
60
+ data-slot="button"
61
+ className={cn(buttonVariants({ variant, size, className }))}
62
+ {...props}
63
+ />
64
+ );
65
+ }
66
+ );
67
+ Button.displayName = "Button";
68
+
69
+ export { Button };
70
+ export type { ButtonProps };
@@ -0,0 +1,23 @@
1
+ // Copyright 2026 Chesapeake Computing
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { cva, type VariantProps } from "class-variance-authority";
5
+
6
+ export const buttonGroupVariants = cva(
7
+ "flex w-fit items-stretch [&>*]:focus-visible:z-10 [&>*]:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md has-[>[data-slot=button-group]]:gap-2",
8
+ {
9
+ variants: {
10
+ orientation: {
11
+ horizontal:
12
+ "[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none",
13
+ vertical:
14
+ "flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none",
15
+ },
16
+ },
17
+ defaultVariants: {
18
+ orientation: "horizontal",
19
+ },
20
+ }
21
+ );
22
+
23
+ export type ButtonGroupVariantsProps = VariantProps<typeof buttonGroupVariants>;
@@ -0,0 +1,38 @@
1
+ // Copyright 2026 Chesapeake Computing
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { cva, type VariantProps } from "class-variance-authority";
5
+
6
+ export const buttonVariants = cva(
7
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
12
+ destructive:
13
+ "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
14
+ outline:
15
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
16
+ secondary:
17
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
18
+ ghost:
19
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
20
+ link: "text-primary underline-offset-4 hover:underline",
21
+ },
22
+ size: {
23
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
24
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
25
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
26
+ icon: "size-9",
27
+ "icon-sm": "size-8",
28
+ "icon-lg": "size-10",
29
+ },
30
+ },
31
+ defaultVariants: {
32
+ variant: "default",
33
+ size: "default",
34
+ },
35
+ }
36
+ );
37
+
38
+ export type ButtonVariantsProps = VariantProps<typeof buttonVariants>;
@@ -0,0 +1,220 @@
1
+ // Copyright 2026 Chesapeake Computing
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ "use client";
5
+
6
+ import * as React from "react";
7
+ import {
8
+ ChevronDownIcon,
9
+ ChevronLeftIcon,
10
+ ChevronRightIcon,
11
+ } from "lucide-react";
12
+ import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker";
13
+
14
+ import { cn } from "../../lib/utils";
15
+ import { Button } from "./button";
16
+ import { buttonVariants } from "./buttonVariants.ts";
17
+
18
+ function Calendar({
19
+ className,
20
+ classNames,
21
+ showOutsideDays,
22
+ captionLayout,
23
+ buttonVariant,
24
+ formatters,
25
+ components,
26
+ ...props
27
+ }: React.ComponentProps<typeof DayPicker> & {
28
+ buttonVariant?: React.ComponentProps<typeof Button>["variant"];
29
+ }) {
30
+ const defaultClassNames = getDefaultClassNames();
31
+
32
+ return (
33
+ <DayPicker
34
+ showOutsideDays={showOutsideDays}
35
+ className={cn(
36
+ "bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
37
+ String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
38
+ String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
39
+ className
40
+ )}
41
+ captionLayout={captionLayout}
42
+ formatters={{
43
+ formatMonthDropdown: (date) =>
44
+ date.toLocaleString("default", { month: "short" }),
45
+ ...formatters,
46
+ }}
47
+ classNames={{
48
+ root: cn("w-fit", defaultClassNames.root),
49
+ months: cn(
50
+ "flex gap-4 flex-col md:flex-row relative",
51
+ defaultClassNames.months
52
+ ),
53
+ month: cn("flex flex-col w-full gap-4", defaultClassNames.month),
54
+ nav: cn(
55
+ "flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between",
56
+ defaultClassNames.nav
57
+ ),
58
+ button_previous: cn(
59
+ buttonVariants({ variant: buttonVariant }),
60
+ "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
61
+ defaultClassNames.button_previous
62
+ ),
63
+ button_next: cn(
64
+ buttonVariants({ variant: buttonVariant }),
65
+ "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
66
+ defaultClassNames.button_next
67
+ ),
68
+ month_caption: cn(
69
+ "flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)",
70
+ defaultClassNames.month_caption
71
+ ),
72
+ dropdowns: cn(
73
+ "w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5",
74
+ defaultClassNames.dropdowns
75
+ ),
76
+ dropdown_root: cn(
77
+ "relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md",
78
+ defaultClassNames.dropdown_root
79
+ ),
80
+ dropdown: cn(
81
+ "absolute bg-popover inset-0 opacity-0",
82
+ defaultClassNames.dropdown
83
+ ),
84
+ caption_label: cn(
85
+ "select-none font-medium",
86
+ captionLayout === "label"
87
+ ? "text-sm"
88
+ : "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5",
89
+ defaultClassNames.caption_label
90
+ ),
91
+ table: "w-full border-collapse",
92
+ weekdays: cn("flex", defaultClassNames.weekdays),
93
+ weekday: cn(
94
+ "text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none",
95
+ defaultClassNames.weekday
96
+ ),
97
+ week: cn("flex w-full mt-2", defaultClassNames.week),
98
+ week_number_header: cn(
99
+ "select-none w-(--cell-size)",
100
+ defaultClassNames.week_number_header
101
+ ),
102
+ week_number: cn(
103
+ "text-[0.8rem] select-none text-muted-foreground",
104
+ defaultClassNames.week_number
105
+ ),
106
+ day: cn(
107
+ "relative w-full h-full p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none",
108
+ props.showWeekNumber
109
+ ? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-md"
110
+ : "[&:first-child[data-selected=true]_button]:rounded-l-md",
111
+ defaultClassNames.day
112
+ ),
113
+ range_start: cn(
114
+ "rounded-l-md bg-accent",
115
+ defaultClassNames.range_start
116
+ ),
117
+ range_middle: cn("rounded-none", defaultClassNames.range_middle),
118
+ range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end),
119
+ today: cn(
120
+ "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
121
+ defaultClassNames.today
122
+ ),
123
+ outside: cn(
124
+ "text-muted-foreground aria-selected:text-muted-foreground",
125
+ defaultClassNames.outside
126
+ ),
127
+ disabled: cn(
128
+ "text-muted-foreground opacity-50",
129
+ defaultClassNames.disabled
130
+ ),
131
+ hidden: cn("invisible", defaultClassNames.hidden),
132
+ ...classNames,
133
+ }}
134
+ components={{
135
+ Root: ({ className, rootRef, ...props }) => {
136
+ return (
137
+ <div
138
+ data-slot="calendar"
139
+ ref={rootRef}
140
+ className={cn(className)}
141
+ {...props}
142
+ />
143
+ );
144
+ },
145
+ Chevron: ({ className, orientation, ...props }) => {
146
+ if (orientation === "left") {
147
+ return (
148
+ <ChevronLeftIcon className={cn("size-4", className)} {...props} />
149
+ );
150
+ }
151
+
152
+ if (orientation === "right") {
153
+ return (
154
+ <ChevronRightIcon
155
+ className={cn("size-4", className)}
156
+ {...props}
157
+ />
158
+ );
159
+ }
160
+
161
+ return (
162
+ <ChevronDownIcon className={cn("size-4", className)} {...props} />
163
+ );
164
+ },
165
+ DayButton: CalendarDayButton,
166
+ WeekNumber: ({ children, ...props }) => {
167
+ return (
168
+ <td {...props}>
169
+ <div className="flex size-(--cell-size) items-center justify-center text-center">
170
+ {children}
171
+ </div>
172
+ </td>
173
+ );
174
+ },
175
+ ...components,
176
+ }}
177
+ {...props}
178
+ />
179
+ );
180
+ }
181
+
182
+ function CalendarDayButton({
183
+ className,
184
+ day,
185
+ modifiers,
186
+ ...props
187
+ }: React.ComponentProps<typeof DayButton>) {
188
+ const defaultClassNames = getDefaultClassNames();
189
+
190
+ const ref = React.useRef<HTMLButtonElement>(null);
191
+ React.useEffect(() => {
192
+ if (modifiers.focused) ref.current?.focus();
193
+ }, [modifiers.focused]);
194
+
195
+ return (
196
+ <Button
197
+ ref={ref}
198
+ variant="ghost"
199
+ size="icon"
200
+ data-day={day.date.toLocaleDateString()}
201
+ data-selected-single={
202
+ modifiers.selected &&
203
+ !modifiers.range_start &&
204
+ !modifiers.range_end &&
205
+ !modifiers.range_middle
206
+ }
207
+ data-range-start={modifiers.range_start}
208
+ data-range-end={modifiers.range_end}
209
+ data-range-middle={modifiers.range_middle}
210
+ className={cn(
211
+ "data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 dark:hover:text-accent-foreground flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-70",
212
+ defaultClassNames.day,
213
+ className
214
+ )}
215
+ {...props}
216
+ />
217
+ );
218
+ }
219
+
220
+ export { Calendar, CalendarDayButton };