svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function AlertDescription({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+export { Alert, AlertTitle, AlertDescription, AlertAction }
diff --git a/services/frontend/src/components/ui/aspect-ratio.tsx b/services/frontend/src/components/ui/aspect-ratio.tsx
new file mode 100644
index 0000000..4c0ebaa
--- /dev/null
+++ b/services/frontend/src/components/ui/aspect-ratio.tsx
@@ -0,0 +1,22 @@
+import { cn } from "@/lib/utils"
+
+function AspectRatio({
+ ratio,
+ className,
+ ...props
+}: React.ComponentProps<"div"> & { ratio: number }) {
+ return (
+
+ )
+}
+
+export { AspectRatio }
diff --git a/services/frontend/src/components/ui/attachment.tsx b/services/frontend/src/components/ui/attachment.tsx
new file mode 100644
index 0000000..20609f0
--- /dev/null
+++ b/services/frontend/src/components/ui/attachment.tsx
@@ -0,0 +1,207 @@
+import * as React from "react"
+import { mergeProps } from "@base-ui/react/merge-props"
+import { useRender } from "@base-ui/react/use-render"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+
+const attachmentVariants = cva(
+ "group/attachment relative flex w-fit max-w-full min-w-0 shrink-0 flex-wrap rounded-xl border bg-card text-card-foreground transition-colors focus-within:ring-1 focus-within:ring-ring/50 has-[>a,>button]:hover:bg-muted/50 data-[state=error]:border-destructive/30 data-[state=idle]:border-dashed",
+ {
+ variants: {
+ size: {
+ default:
+ "gap-2 text-sm has-data-[slot=attachment-content]:px-2.5 has-data-[slot=attachment-content]:py-2 has-data-[slot=attachment-media]:p-2",
+ sm: "gap-2.5 text-xs has-data-[slot=attachment-content]:px-2 has-data-[slot=attachment-content]:py-1.5 has-data-[slot=attachment-media]:p-1.5",
+ xs: "gap-1.5 rounded-lg text-xs has-data-[slot=attachment-content]:px-1.5 has-data-[slot=attachment-content]:py-1 has-data-[slot=attachment-media]:p-1",
+ },
+ orientation: {
+ horizontal: "min-w-40 items-center",
+ vertical: "w-24 flex-col has-data-[slot=attachment-content]:w-30",
+ },
+ },
+ }
+)
+
+function Attachment({
+ className,
+ state = "done",
+ size = "default",
+ orientation = "horizontal",
+ ...props
+}: React.ComponentProps<"div"> &
+ VariantProps
& {
+ state?: "idle" | "uploading" | "processing" | "error" | "done"
+ }) {
+ return (
+
+ )
+}
+
+const attachmentMediaVariants = cva(
+ "relative flex aspect-square w-10 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-muted text-foreground group-data-[orientation=vertical]/attachment:w-full group-data-[size=sm]/attachment:w-8 group-data-[size=xs]/attachment:w-7 group-data-[size=xs]/attachment:rounded-md group-data-[state=error]/attachment:bg-destructive/10 group-data-[state=error]/attachment:text-destructive group-data-[orientation=vertical]/attachment:*:data-[slot=spinner]:size-6! [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 group-data-[orientation=vertical]/attachment:[&_svg:not([class*='size-'])]:size-6 group-data-[size=xs]/attachment:[&_svg:not([class*='size-'])]:size-3.5",
+ {
+ variants: {
+ variant: {
+ icon: "",
+ image:
+ "opacity-60 group-data-[state=done]/attachment:opacity-100 group-data-[state=idle]/attachment:opacity-100 *:[img]:aspect-square *:[img]:w-full *:[img]:object-cover",
+ },
+ },
+ defaultVariants: {
+ variant: "icon",
+ },
+ }
+)
+
+function AttachmentMedia({
+ className,
+ variant = "icon",
+ ...props
+}: React.ComponentProps<"div"> & VariantProps) {
+ return (
+
+ )
+}
+
+function AttachmentContent({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function AttachmentTitle({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
+ return (
+
+ )
+}
+
+function AttachmentDescription({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
+ return (
+
+ )
+}
+
+function AttachmentActions({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function AttachmentAction({
+ className,
+ variant,
+ size = "icon-xs",
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AttachmentTrigger({
+ className,
+ render,
+ type,
+ ...props
+}: useRender.ComponentProps<"button">) {
+ return useRender({
+ defaultTagName: "button",
+ props: mergeProps<"button">(
+ {
+ type: render ? type : (type ?? "button"),
+ className: cn("absolute inset-0 z-10 outline-none", className),
+ },
+ props
+ ),
+ render,
+ state: {
+ slot: "attachment-trigger",
+ },
+ })
+}
+
+function AttachmentGroup({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+export {
+ Attachment,
+ AttachmentGroup,
+ AttachmentMedia,
+ AttachmentContent,
+ AttachmentTitle,
+ AttachmentDescription,
+ AttachmentActions,
+ AttachmentAction,
+ AttachmentTrigger,
+}
diff --git a/services/frontend/src/components/ui/avatar.tsx b/services/frontend/src/components/ui/avatar.tsx
index a153389..e4fed86 100644
--- a/services/frontend/src/components/ui/avatar.tsx
+++ b/services/frontend/src/components/ui/avatar.tsx
@@ -1,16 +1,16 @@
-"use client";
+"use client"
-import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar";
-import type * as React from "react";
+import * as React from "react"
+import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar"
-import { cn } from "@/lib/utils";
+import { cn } from "@/lib/utils"
function Avatar({
className,
size = "default",
...props
}: AvatarPrimitive.Root.Props & {
- size?: "default" | "sm" | "lg";
+ size?: "default" | "sm" | "lg"
}) {
return (
- );
+ )
}
function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {
@@ -31,13 +31,11 @@ function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {
data-slot="avatar-image"
className={cn(
"aspect-square size-full rounded-full object-cover",
- className,
+ className
)}
- loading="lazy"
- decoding="async"
{...props}
/>
- );
+ )
}
function AvatarFallback({
@@ -49,11 +47,11 @@ function AvatarFallback({
data-slot="avatar-fallback"
className={cn(
"flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground group-data-[size=sm]/avatar:text-xs",
- className,
+ className
)}
{...props}
/>
- );
+ )
}
function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
@@ -65,11 +63,11 @@ function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
- className,
+ className
)}
{...props}
/>
- );
+ )
}
function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
@@ -78,11 +76,11 @@ function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
data-slot="avatar-group"
className={cn(
"group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background",
- className,
+ className
)}
{...props}
/>
- );
+ )
}
function AvatarGroupCount({
@@ -94,18 +92,18 @@ function AvatarGroupCount({
data-slot="avatar-group-count"
className={cn(
"relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
- className,
+ className
)}
{...props}
/>
- );
+ )
}
export {
Avatar,
- AvatarBadge,
+ AvatarImage,
AvatarFallback,
AvatarGroup,
AvatarGroupCount,
- AvatarImage,
-};
+ AvatarBadge,
+}
diff --git a/services/frontend/src/components/ui/badge.tsx b/services/frontend/src/components/ui/badge.tsx
index a8cbbac..b20959d 100644
--- a/services/frontend/src/components/ui/badge.tsx
+++ b/services/frontend/src/components/ui/badge.tsx
@@ -1,8 +1,8 @@
-import { mergeProps } from "@base-ui/react/merge-props";
-import { useRender } from "@base-ui/react/use-render";
-import { cva, type VariantProps } from "class-variance-authority";
+import { mergeProps } from "@base-ui/react/merge-props"
+import { useRender } from "@base-ui/react/use-render"
+import { cva, type VariantProps } from "class-variance-authority"
-import { cn } from "@/lib/utils";
+import { cn } from "@/lib/utils"
const badgeVariants = cva(
"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
@@ -24,8 +24,8 @@ const badgeVariants = cva(
defaultVariants: {
variant: "default",
},
- },
-);
+ }
+)
function Badge({
className,
@@ -39,14 +39,14 @@ function Badge({
{
className: cn(badgeVariants({ variant }), className),
},
- props,
+ props
),
render,
state: {
slot: "badge",
variant,
},
- });
+ })
}
-export { Badge, badgeVariants };
+export { Badge, badgeVariants }
diff --git a/services/frontend/src/components/ui/breadcrumb.tsx b/services/frontend/src/components/ui/breadcrumb.tsx
new file mode 100644
index 0000000..3d85c18
--- /dev/null
+++ b/services/frontend/src/components/ui/breadcrumb.tsx
@@ -0,0 +1,125 @@
+import * as React from "react"
+import { mergeProps } from "@base-ui/react/merge-props"
+import { useRender } from "@base-ui/react/use-render"
+
+import { cn } from "@/lib/utils"
+import { ChevronRightIcon, MoreHorizontalIcon } from "lucide-react"
+
+function Breadcrumb({ className, ...props }: React.ComponentProps<"nav">) {
+ return (
+
+ )
+}
+
+function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
+ return (
+
+ )
+}
+
+function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
+ return (
+
+ )
+}
+
+function BreadcrumbLink({
+ className,
+ render,
+ ...props
+}: useRender.ComponentProps<"a">) {
+ return useRender({
+ defaultTagName: "a",
+ props: mergeProps<"a">(
+ {
+ className: cn("transition-colors hover:text-foreground", className),
+ },
+ props
+ ),
+ render,
+ state: {
+ slot: "breadcrumb-link",
+ },
+ })
+}
+
+function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
+ return (
+
+ )
+}
+
+function BreadcrumbSeparator({
+ children,
+ className,
+ ...props
+}: React.ComponentProps<"li">) {
+ return (
+ svg]:size-3.5", className)}
+ {...props}
+ >
+ {children ?? (
+
+ )}
+
+ )
+}
+
+function BreadcrumbEllipsis({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
+ return (
+ svg]:size-4",
+ className
+ )}
+ {...props}
+ >
+
+ More
+
+ )
+}
+
+export {
+ Breadcrumb,
+ BreadcrumbList,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ BreadcrumbPage,
+ BreadcrumbSeparator,
+ BreadcrumbEllipsis,
+}
diff --git a/services/frontend/src/components/ui/bubble.tsx b/services/frontend/src/components/ui/bubble.tsx
new file mode 100644
index 0000000..0b4bfe2
--- /dev/null
+++ b/services/frontend/src/components/ui/bubble.tsx
@@ -0,0 +1,128 @@
+import * as React from "react"
+import { mergeProps } from "@base-ui/react/merge-props"
+import { useRender } from "@base-ui/react/use-render"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+function BubbleGroup({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+const bubbleVariants = cva(
+ "group/bubble relative flex w-fit max-w-[80%] min-w-0 flex-col gap-1 group-data-[align=end]/message:self-end data-[align=end]:self-end data-[variant=ghost]:max-w-full",
+ {
+ variants: {
+ variant: {
+ default:
+ "*:data-[slot=bubble-content]:bg-primary *:data-[slot=bubble-content]:text-primary-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-primary/80",
+ secondary:
+ "*:data-[slot=bubble-content]:bg-secondary *:data-[slot=bubble-content]:text-secondary-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]",
+ muted:
+ "*:data-[slot=bubble-content]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[color-mix(in_oklch,var(--muted),var(--foreground)_5%)]",
+ tinted:
+ "*:data-[slot=bubble-content]:bg-[oklch(from_var(--primary)_0.93_calc(c*0.4)_h)] *:data-[slot=bubble-content]:text-foreground dark:*:data-[slot=bubble-content]:bg-[oklch(from_var(--primary)_0.3_calc(c*0.4)_h)] [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[oklch(from_var(--primary)_0.88_calc(c*0.5)_h)] dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-[oklch(from_var(--primary)_0.35_calc(c*0.5)_h)]",
+ outline:
+ "*:data-[slot=bubble-content]:border-border *:data-[slot=bubble-content]:bg-background [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:text-foreground dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-input/30",
+ ghost:
+ "border-none *:data-[slot=bubble-content]:rounded-none *:data-[slot=bubble-content]:bg-transparent *:data-[slot=bubble-content]:p-0 [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:text-foreground dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted/50",
+ destructive:
+ "*:data-[slot=bubble-content]:bg-destructive/10 *:data-[slot=bubble-content]:text-destructive dark:*:data-[slot=bubble-content]:bg-destructive/20 [&>[data-slot=bubble-content]:is(button,a):hover]:bg-destructive/20 dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-destructive/30",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+function Bubble({
+ variant = "default",
+ align = "start",
+ className,
+ ...props
+}: React.ComponentProps<"div"> &
+ VariantProps & {
+ align?: "start" | "end"
+ }) {
+ return (
+
+ )
+}
+
+function BubbleContent({
+ className,
+ render,
+ ...props
+}: useRender.ComponentProps<"div">) {
+ return useRender({
+ defaultTagName: "div",
+ props: mergeProps<"div">(
+ {
+ className: cn(
+ "w-fit max-w-full min-w-0 overflow-hidden rounded-xl border border-transparent px-3 py-2 text-sm leading-relaxed wrap-break-word group-data-[align=end]/bubble:self-end [button]:text-left [button,a]:transition-colors [button,a]:outline-none [button,a]:focus-visible:border-ring [button,a]:focus-visible:ring-3 [button,a]:focus-visible:ring-ring/50",
+ className
+ ),
+ },
+ props
+ ),
+ render,
+ state: {
+ slot: "bubble-content",
+ },
+ })
+}
+
+const bubbleReactionsVariants = cva(
+ "absolute z-10 flex w-fit shrink-0 items-center justify-center gap-1 rounded-full bg-muted px-1.5 py-0.5 text-sm ring-3 ring-card has-[button]:p-0",
+ {
+ variants: {
+ side: {
+ top: "top-0 -translate-y-3/4",
+ bottom: "bottom-0 translate-y-3/4",
+ },
+ align: {
+ start: "left-3",
+ end: "right-3",
+ },
+ },
+ defaultVariants: {
+ side: "bottom",
+ align: "end",
+ },
+ }
+)
+
+function BubbleReactions({
+ side = "bottom",
+ align = "end",
+ className,
+ ...props
+}: React.ComponentProps<"div"> & {
+ align?: "start" | "end"
+ side?: "top" | "bottom"
+}) {
+ return (
+
+ )
+}
+
+export { BubbleGroup, Bubble, BubbleContent, BubbleReactions }
diff --git a/services/frontend/src/components/ui/button-group.tsx b/services/frontend/src/components/ui/button-group.tsx
new file mode 100644
index 0000000..3c97c26
--- /dev/null
+++ b/services/frontend/src/components/ui/button-group.tsx
@@ -0,0 +1,87 @@
+import { mergeProps } from "@base-ui/react/merge-props"
+import { useRender } from "@base-ui/react/use-render"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+import { Separator } from "@/components/ui/separator"
+
+const buttonGroupVariants = cva(
+ "flex w-fit items-stretch *:focus-visible:relative *:focus-visible:z-10 has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-lg [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
+ {
+ variants: {
+ orientation: {
+ horizontal:
+ "*:data-slot:rounded-r-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-lg! [&>[data-slot]~[data-slot]]:rounded-l-none [&>[data-slot]~[data-slot]]:border-l-0",
+ vertical:
+ "flex-col *:data-slot:rounded-b-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-lg! [&>[data-slot]~[data-slot]]:rounded-t-none [&>[data-slot]~[data-slot]]:border-t-0",
+ },
+ },
+ defaultVariants: {
+ orientation: "horizontal",
+ },
+ }
+)
+
+function ButtonGroup({
+ className,
+ orientation,
+ ...props
+}: React.ComponentProps<"div"> & VariantProps) {
+ return (
+
+ )
+}
+
+function ButtonGroupText({
+ className,
+ render,
+ ...props
+}: useRender.ComponentProps<"div">) {
+ return useRender({
+ defaultTagName: "div",
+ props: mergeProps<"div">(
+ {
+ className: cn(
+ "flex items-center gap-2 rounded-lg border bg-muted px-2.5 text-sm font-medium [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
+ className
+ ),
+ },
+ props
+ ),
+ render,
+ state: {
+ slot: "button-group-text",
+ },
+ })
+}
+
+function ButtonGroupSeparator({
+ className,
+ orientation = "vertical",
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ ButtonGroup,
+ ButtonGroupSeparator,
+ ButtonGroupText,
+ buttonGroupVariants,
+}
diff --git a/services/frontend/src/components/ui/button.tsx b/services/frontend/src/components/ui/button.tsx
index e2ee793..b033601 100644
--- a/services/frontend/src/components/ui/button.tsx
+++ b/services/frontend/src/components/ui/button.tsx
@@ -1,7 +1,7 @@
-import { Button as ButtonPrimitive } from "@base-ui/react/button";
-import { cva, type VariantProps } from "class-variance-authority";
+import { Button as ButtonPrimitive } from "@base-ui/react/button"
+import { cva, type VariantProps } from "class-variance-authority"
-import { cn } from "@/lib/utils";
+import { cn } from "@/lib/utils"
const buttonVariants = cva(
"group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
@@ -37,8 +37,8 @@ const buttonVariants = cva(
variant: "default",
size: "default",
},
- },
-);
+ }
+)
function Button({
className,
@@ -52,7 +52,7 @@ function Button({
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
- );
+ )
}
-export { Button, buttonVariants };
+export { Button, buttonVariants }
diff --git a/services/frontend/src/components/ui/calendar.tsx b/services/frontend/src/components/ui/calendar.tsx
new file mode 100644
index 0000000..0f8950a
--- /dev/null
+++ b/services/frontend/src/components/ui/calendar.tsx
@@ -0,0 +1,221 @@
+"use client"
+
+import * as React from "react"
+import {
+ DayPicker,
+ getDefaultClassNames,
+ type DayButton,
+ type Locale,
+} from "react-day-picker"
+
+import { cn } from "@/lib/utils"
+import { Button, buttonVariants } from "@/components/ui/button"
+import { ChevronLeftIcon, ChevronRightIcon, ChevronDownIcon } from "lucide-react"
+
+function Calendar({
+ className,
+ classNames,
+ showOutsideDays = true,
+ captionLayout = "label",
+ buttonVariant = "ghost",
+ locale,
+ formatters,
+ components,
+ ...props
+}: React.ComponentProps & {
+ buttonVariant?: React.ComponentProps["variant"]
+}) {
+ const defaultClassNames = getDefaultClassNames()
+
+ return (
+ svg]:rotate-180`,
+ String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
+ className
+ )}
+ captionLayout={captionLayout}
+ locale={locale}
+ formatters={{
+ formatMonthDropdown: (date) =>
+ date.toLocaleString(locale?.code, { month: "short" }),
+ ...formatters,
+ }}
+ classNames={{
+ root: cn("w-fit", defaultClassNames.root),
+ months: cn(
+ "relative flex flex-col gap-4 md:flex-row",
+ defaultClassNames.months
+ ),
+ month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
+ nav: cn(
+ "absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
+ defaultClassNames.nav
+ ),
+ button_previous: cn(
+ buttonVariants({ variant: buttonVariant }),
+ "size-(--cell-size) p-0 select-none aria-disabled:opacity-50",
+ defaultClassNames.button_previous
+ ),
+ button_next: cn(
+ buttonVariants({ variant: buttonVariant }),
+ "size-(--cell-size) p-0 select-none aria-disabled:opacity-50",
+ defaultClassNames.button_next
+ ),
+ month_caption: cn(
+ "flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)",
+ defaultClassNames.month_caption
+ ),
+ dropdowns: cn(
+ "flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium",
+ defaultClassNames.dropdowns
+ ),
+ dropdown_root: cn(
+ "relative rounded-(--cell-radius)",
+ defaultClassNames.dropdown_root
+ ),
+ dropdown: cn(
+ "absolute inset-0 bg-popover opacity-0",
+ defaultClassNames.dropdown
+ ),
+ caption_label: cn(
+ "font-medium select-none",
+ captionLayout === "label"
+ ? "text-sm"
+ : "flex items-center gap-1 rounded-(--cell-radius) text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground",
+ defaultClassNames.caption_label
+ ),
+ month_grid: cn("w-full border-collapse", defaultClassNames.month_grid),
+ weekdays: cn("flex", defaultClassNames.weekdays),
+ weekday: cn(
+ "flex-1 rounded-(--cell-radius) text-[0.8rem] font-normal text-muted-foreground select-none",
+ defaultClassNames.weekday
+ ),
+ week: cn("mt-2 flex w-full", defaultClassNames.week),
+ week_number_header: cn(
+ "w-(--cell-size) select-none",
+ defaultClassNames.week_number_header
+ ),
+ week_number: cn(
+ "text-[0.8rem] text-muted-foreground select-none",
+ defaultClassNames.week_number
+ ),
+ day: cn(
+ "group/day relative aspect-square h-full w-full rounded-(--cell-radius) p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius)",
+ props.showWeekNumber
+ ? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-(--cell-radius)"
+ : "[&:first-child[data-selected=true]_button]:rounded-l-(--cell-radius)",
+ defaultClassNames.day
+ ),
+ range_start: cn(
+ "relative isolate z-0 rounded-l-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:right-0 after:w-4 after:bg-muted",
+ defaultClassNames.range_start
+ ),
+ range_middle: cn("rounded-none", defaultClassNames.range_middle),
+ range_end: cn(
+ "relative isolate z-0 rounded-r-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:left-0 after:w-4 after:bg-muted",
+ defaultClassNames.range_end
+ ),
+ today: cn(
+ "rounded-(--cell-radius) bg-muted text-foreground data-[selected=true]:rounded-none",
+ defaultClassNames.today
+ ),
+ outside: cn(
+ "text-muted-foreground aria-selected:text-muted-foreground",
+ defaultClassNames.outside
+ ),
+ disabled: cn(
+ "text-muted-foreground opacity-50",
+ defaultClassNames.disabled
+ ),
+ hidden: cn("invisible", defaultClassNames.hidden),
+ ...classNames,
+ }}
+ components={{
+ Root: ({ className, rootRef, ...props }) => {
+ return (
+
+ )
+ },
+ Chevron: ({ className, orientation, ...props }) => {
+ if (orientation === "left") {
+ return (
+
+ )
+ }
+
+ if (orientation === "right") {
+ return (
+
+ )
+ }
+
+ return (
+
+ )
+ },
+ DayButton: ({ ...props }) => (
+
+ ),
+ WeekNumber: ({ children, ...props }) => {
+ return (
+
+
+ {children}
+
+ |
+ )
+ },
+ ...components,
+ }}
+ {...props}
+ />
+ )
+}
+
+function CalendarDayButton({
+ className,
+ day,
+ modifiers,
+ locale,
+ ...props
+}: React.ComponentProps & { locale?: Partial }) {
+ const defaultClassNames = getDefaultClassNames()
+
+ const ref = React.useRef(null)
+ React.useEffect(() => {
+ if (modifiers.focused) ref.current?.focus()
+ }, [modifiers.focused])
+
+ return (
+