@moloodev/ui 0.0.0-stage → 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 (93) hide show
  1. package/package.json +139 -3
  2. package/registry/components.ts +135 -0
  3. package/registry/fonts.css +81 -0
  4. package/registry/suit-OFL.txt +94 -0
  5. package/registry/wanted-sans-OFL.txt +93 -0
  6. package/src/components/ui/accordion.tsx +74 -0
  7. package/src/components/ui/alert-dialog.tsx +187 -0
  8. package/src/components/ui/alert.tsx +83 -0
  9. package/src/components/ui/aspect-ratio.tsx +22 -0
  10. package/src/components/ui/attachment.tsx +207 -0
  11. package/src/components/ui/avatar.tsx +109 -0
  12. package/src/components/ui/badge.tsx +54 -0
  13. package/src/components/ui/breadcrumb.tsx +126 -0
  14. package/src/components/ui/bubble.tsx +128 -0
  15. package/src/components/ui/button-group.tsx +103 -0
  16. package/src/components/ui/button.tsx +68 -0
  17. package/src/components/ui/calendar.tsx +222 -0
  18. package/src/components/ui/card.tsx +103 -0
  19. package/src/components/ui/carousel.tsx +242 -0
  20. package/src/components/ui/chart.tsx +398 -0
  21. package/src/components/ui/checkbox.tsx +28 -0
  22. package/src/components/ui/collapsible.tsx +40 -0
  23. package/src/components/ui/combobox.tsx +299 -0
  24. package/src/components/ui/command.tsx +218 -0
  25. package/src/components/ui/context-menu.tsx +271 -0
  26. package/src/components/ui/data-table.tsx +919 -0
  27. package/src/components/ui/date-picker.tsx +65 -0
  28. package/src/components/ui/dialog.tsx +224 -0
  29. package/src/components/ui/drawer.tsx +231 -0
  30. package/src/components/ui/dropdown-menu.tsx +268 -0
  31. package/src/components/ui/dropzone.tsx +186 -0
  32. package/src/components/ui/empty.tsx +101 -0
  33. package/src/components/ui/field.tsx +238 -0
  34. package/src/components/ui/form.tsx +83 -0
  35. package/src/components/ui/highlighted-source.tsx +200 -0
  36. package/src/components/ui/hover-card.tsx +51 -0
  37. package/src/components/ui/image-field.tsx +796 -0
  38. package/src/components/ui/input-group.tsx +149 -0
  39. package/src/components/ui/input-otp.tsx +86 -0
  40. package/src/components/ui/input-step.ts +19 -0
  41. package/src/components/ui/input.tsx +100 -0
  42. package/src/components/ui/item.tsx +201 -0
  43. package/src/components/ui/kbd.tsx +91 -0
  44. package/src/components/ui/kind-icon-glob.ts +24 -0
  45. package/src/components/ui/kind-icon-load.ts +52 -0
  46. package/src/components/ui/kind-icon-static.ts +46 -0
  47. package/src/components/ui/kind-icon.tsx +84 -0
  48. package/src/components/ui/kind-mark.tsx +65 -0
  49. package/src/components/ui/label.tsx +29 -0
  50. package/src/components/ui/marker.tsx +71 -0
  51. package/src/components/ui/menubar.tsx +277 -0
  52. package/src/components/ui/message-scroller.tsx +137 -0
  53. package/src/components/ui/message.tsx +92 -0
  54. package/src/components/ui/moloo-logo.tsx +45 -0
  55. package/src/components/ui/native-select.tsx +61 -0
  56. package/src/components/ui/navigation-menu.tsx +168 -0
  57. package/src/components/ui/outline-list.tsx +73 -0
  58. package/src/components/ui/pagination.tsx +130 -0
  59. package/src/components/ui/panel.tsx +108 -0
  60. package/src/components/ui/popover.tsx +90 -0
  61. package/src/components/ui/progress.tsx +117 -0
  62. package/src/components/ui/radio-group.tsx +38 -0
  63. package/src/components/ui/resizable.tsx +52 -0
  64. package/src/components/ui/scroll-area.tsx +54 -0
  65. package/src/components/ui/select.tsx +205 -0
  66. package/src/components/ui/separator.tsx +25 -0
  67. package/src/components/ui/sheet.tsx +140 -0
  68. package/src/components/ui/sidebar.tsx +746 -0
  69. package/src/components/ui/skeleton.tsx +13 -0
  70. package/src/components/ui/slider.tsx +52 -0
  71. package/src/components/ui/spinner.tsx +18 -0
  72. package/src/components/ui/switch.tsx +32 -0
  73. package/src/components/ui/table.tsx +116 -0
  74. package/src/components/ui/tabs.tsx +83 -0
  75. package/src/components/ui/tag-list-input.tsx +194 -0
  76. package/src/components/ui/textarea.tsx +18 -0
  77. package/src/components/ui/timeline.tsx +265 -0
  78. package/src/components/ui/toast.tsx +237 -0
  79. package/src/components/ui/toggle-group.tsx +90 -0
  80. package/src/components/ui/toggle.tsx +45 -0
  81. package/src/components/ui/tooltip.tsx +66 -0
  82. package/src/css.d.ts +1 -0
  83. package/src/hooks/use-clipboard.ts +82 -0
  84. package/src/hooks/use-mobile.ts +21 -0
  85. package/src/index.ts +106 -0
  86. package/src/kind-icon.ts +4 -0
  87. package/src/lib/format-relative-time.ts +41 -0
  88. package/src/lib/list-selection.ts +61 -0
  89. package/src/lib/utils.ts +6 -0
  90. package/src/moloo.css +360 -0
  91. package/src/styles.css +36 -0
  92. package/src/typeset.css +518 -0
  93. package/README.md +0 -3
@@ -0,0 +1,126 @@
1
+ import * as React from "react"
2
+ import { mergeProps } from "@base-ui/react/merge-props"
3
+ import { useRender } from "@base-ui/react/use-render"
4
+
5
+ import { cn } from "../../lib/utils"
6
+ import { IconDots } from "@tabler/icons-react"
7
+
8
+ function Breadcrumb({ className, ...props }: React.ComponentProps<"nav">) {
9
+ return (
10
+ <nav
11
+ aria-label="breadcrumb"
12
+ data-slot="breadcrumb"
13
+ className={cn(className)}
14
+ {...props}
15
+ />
16
+ )
17
+ }
18
+
19
+ function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
20
+ return (
21
+ <ol
22
+ data-slot="breadcrumb-list"
23
+ className={cn(
24
+ "flex flex-wrap items-center gap-1.5 text-sm font-semibold wrap-break-word text-muted-foreground",
25
+ className
26
+ )}
27
+ {...props}
28
+ />
29
+ )
30
+ }
31
+
32
+ function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
33
+ return (
34
+ <li
35
+ data-slot="breadcrumb-item"
36
+ className={cn("inline-flex items-center gap-1", className)}
37
+ {...props}
38
+ />
39
+ )
40
+ }
41
+
42
+ function BreadcrumbLink({
43
+ className,
44
+ render,
45
+ ...props
46
+ }: useRender.ComponentProps<"a">) {
47
+ return useRender({
48
+ defaultTagName: "a",
49
+ props: mergeProps<"a">(
50
+ {
51
+ className: cn(
52
+ "moloo-focus transition-colors outline-none hover:text-foreground",
53
+ className
54
+ ),
55
+ },
56
+ props
57
+ ),
58
+ render,
59
+ state: {
60
+ slot: "breadcrumb-link",
61
+ },
62
+ })
63
+ }
64
+
65
+ function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
66
+ return (
67
+ <span
68
+ data-slot="breadcrumb-page"
69
+ role="link"
70
+ aria-disabled="true"
71
+ aria-current="page"
72
+ className={cn("font-semibold text-foreground", className)}
73
+ {...props}
74
+ />
75
+ )
76
+ }
77
+
78
+ function BreadcrumbSeparator({
79
+ children,
80
+ className,
81
+ ...props
82
+ }: React.ComponentProps<"li">) {
83
+ return (
84
+ <li
85
+ data-slot="breadcrumb-separator"
86
+ role="presentation"
87
+ aria-hidden="true"
88
+ className={cn("px-0.5 [&>svg]:size-3.5", className)}
89
+ {...props}
90
+ >
91
+ {children ?? "/"}
92
+ </li>
93
+ )
94
+ }
95
+
96
+ function BreadcrumbEllipsis({
97
+ className,
98
+ ...props
99
+ }: React.ComponentProps<"span">) {
100
+ return (
101
+ <span
102
+ data-slot="breadcrumb-ellipsis"
103
+ role="presentation"
104
+ aria-hidden="true"
105
+ className={cn(
106
+ "flex size-5 items-center justify-center [&>svg]:size-4",
107
+ className
108
+ )}
109
+ {...props}
110
+ >
111
+ <IconDots
112
+ />
113
+ <span className="sr-only">More</span>
114
+ </span>
115
+ )
116
+ }
117
+
118
+ export {
119
+ Breadcrumb,
120
+ BreadcrumbList,
121
+ BreadcrumbItem,
122
+ BreadcrumbLink,
123
+ BreadcrumbPage,
124
+ BreadcrumbSeparator,
125
+ BreadcrumbEllipsis,
126
+ }
@@ -0,0 +1,128 @@
1
+ import * as React from "react"
2
+ import { mergeProps } from "@base-ui/react/merge-props"
3
+ import { useRender } from "@base-ui/react/use-render"
4
+ import { cva, type VariantProps } from "class-variance-authority"
5
+
6
+ import { cn } from "../../lib/utils"
7
+
8
+ function BubbleGroup({ className, ...props }: React.ComponentProps<"div">) {
9
+ return (
10
+ <div
11
+ data-slot="bubble-group"
12
+ className={cn("flex min-w-0 flex-col gap-2", className)}
13
+ {...props}
14
+ />
15
+ )
16
+ }
17
+
18
+ const bubbleVariants = cva(
19
+ "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",
20
+ {
21
+ variants: {
22
+ variant: {
23
+ default:
24
+ "*:data-[slot=bubble-content]:bg-primary *:data-[slot=bubble-content]:text-primary-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-primary/80",
25
+ secondary:
26
+ "*: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%)]",
27
+ muted:
28
+ "*:data-[slot=bubble-content]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[color-mix(in_oklch,var(--muted),var(--foreground)_5%)]",
29
+ tinted:
30
+ "*: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)]",
31
+ outline:
32
+ "*: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",
33
+ ghost:
34
+ "border-none *:data-[slot=bubble-content]:rounded-md *: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",
35
+ destructive:
36
+ "*: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",
37
+ },
38
+ },
39
+ defaultVariants: {
40
+ variant: "default",
41
+ },
42
+ }
43
+ )
44
+
45
+ function Bubble({
46
+ variant = "default",
47
+ align = "start",
48
+ className,
49
+ ...props
50
+ }: React.ComponentProps<"div"> &
51
+ VariantProps<typeof bubbleVariants> & {
52
+ align?: "start" | "end"
53
+ }) {
54
+ return (
55
+ <div
56
+ data-slot="bubble"
57
+ data-variant={variant}
58
+ data-align={align}
59
+ className={cn(bubbleVariants({ variant }), className)}
60
+ {...props}
61
+ />
62
+ )
63
+ }
64
+
65
+ function BubbleContent({
66
+ className,
67
+ render,
68
+ ...props
69
+ }: useRender.ComponentProps<"div">) {
70
+ return useRender({
71
+ defaultTagName: "div",
72
+ props: mergeProps<"div">(
73
+ {
74
+ className: cn(
75
+ "moloo-focus w-fit max-w-full min-w-0 overflow-hidden rounded-md border-2 border-transparent px-2.5 py-2 text-sm font-semibold leading-relaxed wrap-break-word group-data-[align=end]/bubble:self-end [button]:text-left [button,a]:transition-colors [button,a]:outline-none",
76
+ className
77
+ ),
78
+ },
79
+ props
80
+ ),
81
+ render,
82
+ state: {
83
+ slot: "bubble-content",
84
+ },
85
+ })
86
+ }
87
+
88
+ const bubbleReactionsVariants = cva(
89
+ "absolute z-10 flex w-fit shrink-0 items-center justify-center gap-1 rounded-md bg-muted px-1.5 py-0.5 text-sm font-semibold ring-2 ring-card has-[button]:p-0",
90
+ {
91
+ variants: {
92
+ side: {
93
+ top: "top-0 -translate-y-3/4",
94
+ bottom: "bottom-0 translate-y-3/4",
95
+ },
96
+ align: {
97
+ start: "left-3",
98
+ end: "right-3",
99
+ },
100
+ },
101
+ defaultVariants: {
102
+ side: "bottom",
103
+ align: "end",
104
+ },
105
+ }
106
+ )
107
+
108
+ function BubbleReactions({
109
+ side = "bottom",
110
+ align = "end",
111
+ className,
112
+ ...props
113
+ }: React.ComponentProps<"div"> & {
114
+ align?: "start" | "end"
115
+ side?: "top" | "bottom"
116
+ }) {
117
+ return (
118
+ <div
119
+ data-slot="bubble-reactions"
120
+ data-align={align}
121
+ data-side={side}
122
+ className={cn(bubbleReactionsVariants({ side, align }), className)}
123
+ {...props}
124
+ />
125
+ )
126
+ }
127
+
128
+ export { BubbleGroup, Bubble, BubbleContent, BubbleReactions }
@@ -0,0 +1,103 @@
1
+ import { mergeProps } from "@base-ui/react/merge-props"
2
+ import { useRender } from "@base-ui/react/use-render"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "../../lib/utils"
6
+ import { Separator } from "./separator"
7
+
8
+ const buttonGroupVariants = cva(
9
+ [
10
+ "flex w-fit items-stretch isolate rounded-md",
11
+ "has-[>[data-slot=button-group]]:gap-2",
12
+ "[&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
13
+ /* Hover/focus chrome must paint above neighbors */
14
+ "*:hover:relative *:hover:z-10 *:focus-visible:relative *:focus-visible:z-10 *:focus-within:relative *:focus-within:z-10",
15
+ ].join(" "),
16
+ {
17
+ variants: {
18
+ orientation: {
19
+ horizontal: [
20
+ "[&>[data-slot]:not(:first-child)]:-ml-0.5",
21
+ "[&>[data-slot]]:rounded-none!",
22
+ "[&>[data-slot]:first-child]:rounded-l-md!",
23
+ "[&>[data-slot]:last-child]:rounded-r-md!",
24
+ "has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md!",
25
+ ].join(" "),
26
+ vertical: [
27
+ "flex-col",
28
+ "[&>[data-slot]:not(:first-child)]:-mt-0.5",
29
+ "[&>[data-slot]]:rounded-none!",
30
+ "[&>[data-slot]:first-child]:rounded-t-md!",
31
+ "[&>[data-slot]:last-child]:rounded-b-md!",
32
+ ].join(" "),
33
+ },
34
+ },
35
+ defaultVariants: {
36
+ orientation: "horizontal",
37
+ },
38
+ }
39
+ )
40
+
41
+ function ButtonGroup({
42
+ className,
43
+ orientation,
44
+ ...props
45
+ }: React.ComponentProps<"div"> & VariantProps<typeof buttonGroupVariants>) {
46
+ return (
47
+ <div
48
+ role="group"
49
+ data-slot="button-group"
50
+ data-orientation={orientation}
51
+ className={cn(buttonGroupVariants({ orientation }), className)}
52
+ {...props}
53
+ />
54
+ )
55
+ }
56
+
57
+ function ButtonGroupText({
58
+ className,
59
+ render,
60
+ ...props
61
+ }: useRender.ComponentProps<"div">) {
62
+ return useRender({
63
+ defaultTagName: "div",
64
+ props: mergeProps<"div">(
65
+ {
66
+ className: cn(
67
+ "flex items-center gap-2 border-2 border-border bg-muted px-2.5 text-sm font-semibold [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
68
+ className
69
+ ),
70
+ },
71
+ props
72
+ ),
73
+ render,
74
+ state: {
75
+ slot: "button-group-text",
76
+ },
77
+ })
78
+ }
79
+
80
+ function ButtonGroupSeparator({
81
+ className,
82
+ orientation = "vertical",
83
+ ...props
84
+ }: React.ComponentProps<typeof Separator>) {
85
+ return (
86
+ <Separator
87
+ data-slot="button-group-separator"
88
+ orientation={orientation}
89
+ className={cn(
90
+ "relative z-0 self-stretch bg-input data-[orientation=horizontal]:mx-px data-[orientation=horizontal]:w-auto data-[orientation=vertical]:my-px data-[orientation=vertical]:h-auto",
91
+ className
92
+ )}
93
+ {...props}
94
+ />
95
+ )
96
+ }
97
+
98
+ export {
99
+ ButtonGroup,
100
+ ButtonGroupSeparator,
101
+ ButtonGroupText,
102
+ buttonGroupVariants,
103
+ }
@@ -0,0 +1,68 @@
1
+ import { isValidElement } from "react"
2
+ import { Button as ButtonPrimitive } from "@base-ui/react/button"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "../../lib/utils"
6
+
7
+ const buttonVariants = cva(
8
+ "group/button moloo-press moloo-focus inline-flex shrink-0 items-center justify-center rounded-md bg-clip-padding font-display text-sm font-bold tracking-[-0.02em] whitespace-nowrap outline-none select-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default:
13
+ "bg-primary text-primary-foreground shadow-moloo hover:bg-primary-hover",
14
+ outline:
15
+ "border-2 border-border bg-card text-foreground shadow-moloo hover:border-primary hover:text-primary aria-invalid:border-destructive",
16
+ secondary:
17
+ "bg-secondary text-secondary-foreground shadow-moloo-sm hover:brightness-95 dark:hover:brightness-110",
18
+ ghost:
19
+ "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground",
20
+ accent:
21
+ "bg-accent text-accent-foreground shadow-moloo hover:brightness-105",
22
+ destructive:
23
+ "bg-destructive-fill text-destructive-foreground shadow-moloo hover:brightness-110",
24
+ link: "font-sans text-sm font-medium tracking-normal text-primary underline-offset-4 hover:underline shadow-none",
25
+ },
26
+ size: {
27
+ default:
28
+ "h-9 gap-1.5 px-3.5 has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5",
29
+ xs: "h-6 gap-1 rounded-md px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
30
+ sm: "h-8 gap-1 rounded-md px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
31
+ lg: "h-10 gap-1.5 px-4 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
32
+ icon: "size-9",
33
+ "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
34
+ "icon-sm": "size-8 rounded-md",
35
+ "icon-lg": "size-10",
36
+ },
37
+ },
38
+ defaultVariants: {
39
+ variant: "default",
40
+ size: "default",
41
+ },
42
+ }
43
+ )
44
+
45
+ function rendersNativeButton(render: ButtonPrimitive.Props["render"]): boolean {
46
+ return !(isValidElement(render) && render.type !== "button")
47
+ }
48
+
49
+ function Button({
50
+ className,
51
+ variant = "default",
52
+ size = "default",
53
+ nativeButton,
54
+ ...props
55
+ }: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {
56
+ return (
57
+ <ButtonPrimitive
58
+ data-slot="button"
59
+ data-variant={variant}
60
+ data-size={size}
61
+ nativeButton={nativeButton ?? rendersNativeButton(props.render)}
62
+ className={cn(buttonVariants({ variant, size, className }))}
63
+ {...props}
64
+ />
65
+ )
66
+ }
67
+
68
+ export { Button, buttonVariants }
@@ -0,0 +1,222 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import {
5
+ DayPicker,
6
+ getDefaultClassNames,
7
+ type DayButton,
8
+ type Locale,
9
+ } from "react-day-picker"
10
+
11
+ import { cn } from "../../lib/utils"
12
+ import { Button, buttonVariants } from "./button"
13
+ import { IconChevronLeft, IconChevronRight, IconChevronDown } from "@tabler/icons-react"
14
+
15
+ function Calendar({
16
+ className,
17
+ classNames,
18
+ showOutsideDays = true,
19
+ captionLayout = "label",
20
+ buttonVariant = "ghost",
21
+ locale,
22
+ formatters,
23
+ components,
24
+ ...props
25
+ }: React.ComponentProps<typeof DayPicker> & {
26
+ buttonVariant?: React.ComponentProps<typeof Button>["variant"]
27
+ }) {
28
+ const defaultClassNames = getDefaultClassNames()
29
+
30
+ return (
31
+ <DayPicker
32
+ showOutsideDays={showOutsideDays}
33
+ className={cn(
34
+ "group/calendar bg-background p-2 [--cell-size:--spacing(7)] in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent",
35
+ String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
36
+ String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
37
+ className
38
+ )}
39
+ captionLayout={captionLayout}
40
+ locale={locale}
41
+ formatters={{
42
+ formatMonthDropdown: (date) =>
43
+ date.toLocaleString(locale?.code, { month: "short" }),
44
+ ...formatters,
45
+ }}
46
+ classNames={{
47
+ root: cn("w-fit", defaultClassNames.root),
48
+ months: cn(
49
+ "relative flex flex-col gap-4 md:flex-row",
50
+ defaultClassNames.months
51
+ ),
52
+ month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
53
+ nav: cn(
54
+ "absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
55
+ defaultClassNames.nav
56
+ ),
57
+ button_previous: cn(
58
+ buttonVariants({ variant: buttonVariant }),
59
+ "size-(--cell-size) p-0 select-none aria-disabled:opacity-50",
60
+ defaultClassNames.button_previous
61
+ ),
62
+ button_next: cn(
63
+ buttonVariants({ variant: buttonVariant }),
64
+ "size-(--cell-size) p-0 select-none aria-disabled:opacity-50",
65
+ defaultClassNames.button_next
66
+ ),
67
+ month_caption: cn(
68
+ "flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)",
69
+ defaultClassNames.month_caption
70
+ ),
71
+ dropdowns: cn(
72
+ "flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-semibold",
73
+ defaultClassNames.dropdowns
74
+ ),
75
+ dropdown_root: cn(
76
+ "relative rounded-md border-2 border-border bg-background px-1.5 py-0.5",
77
+ defaultClassNames.dropdown_root
78
+ ),
79
+ dropdown: cn(
80
+ "absolute inset-0 bg-popover opacity-0",
81
+ defaultClassNames.dropdown
82
+ ),
83
+ caption_label: cn(
84
+ "font-semibold select-none",
85
+ captionLayout === "label"
86
+ ? "text-sm"
87
+ : "flex items-center gap-1 rounded-md text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground",
88
+ defaultClassNames.caption_label
89
+ ),
90
+ month_grid: cn("w-full border-collapse", defaultClassNames.month_grid),
91
+ weekdays: cn("flex", defaultClassNames.weekdays),
92
+ weekday: cn(
93
+ "label flex-1 rounded-md text-[0.8rem] text-muted-foreground select-none",
94
+ defaultClassNames.weekday
95
+ ),
96
+ week: cn("mt-2 flex w-full", defaultClassNames.week),
97
+ week_number_header: cn(
98
+ "w-(--cell-size) select-none",
99
+ defaultClassNames.week_number_header
100
+ ),
101
+ week_number: cn(
102
+ "text-[0.8rem] text-muted-foreground select-none",
103
+ defaultClassNames.week_number
104
+ ),
105
+ day: cn(
106
+ "group/day relative aspect-square h-full w-full rounded-md p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-r-md",
107
+ props.showWeekNumber
108
+ ? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-md"
109
+ : "[&:first-child[data-selected=true]_button]:rounded-l-md",
110
+ defaultClassNames.day
111
+ ),
112
+ range_start: cn(
113
+ "relative isolate z-0 rounded-l-md bg-muted after:absolute after:inset-y-0 after:right-0 after:w-4 after:bg-muted",
114
+ defaultClassNames.range_start
115
+ ),
116
+ range_middle: cn("rounded-md", defaultClassNames.range_middle),
117
+ range_end: cn(
118
+ "relative isolate z-0 rounded-r-md bg-muted after:absolute after:inset-y-0 after:left-0 after:w-4 after:bg-muted",
119
+ defaultClassNames.range_end
120
+ ),
121
+ today: cn(
122
+ "rounded-md bg-muted text-foreground data-[selected=true]:rounded-md",
123
+ defaultClassNames.today
124
+ ),
125
+ outside: cn(
126
+ "text-muted-foreground aria-selected:text-muted-foreground",
127
+ defaultClassNames.outside
128
+ ),
129
+ disabled: cn(
130
+ "text-muted-foreground opacity-50",
131
+ defaultClassNames.disabled
132
+ ),
133
+ hidden: cn("invisible", defaultClassNames.hidden),
134
+ ...classNames,
135
+ }}
136
+ components={{
137
+ Root: ({ className, rootRef, ...props }) => {
138
+ return (
139
+ <div
140
+ data-slot="calendar"
141
+ ref={rootRef}
142
+ className={cn(className)}
143
+ {...props}
144
+ />
145
+ )
146
+ },
147
+ Chevron: ({ className, orientation, ...props }) => {
148
+ if (orientation === "left") {
149
+ return (
150
+ <IconChevronLeft className={cn("size-4", className)} {...props} />
151
+ )
152
+ }
153
+
154
+ if (orientation === "right") {
155
+ return (
156
+ <IconChevronRight className={cn("size-4", className)} {...props} />
157
+ )
158
+ }
159
+
160
+ return (
161
+ <IconChevronDown className={cn("size-4", className)} {...props} />
162
+ )
163
+ },
164
+ DayButton: ({ ...props }) => (
165
+ <CalendarDayButton locale={locale} {...props} />
166
+ ),
167
+ WeekNumber: ({ children, ...props }) => {
168
+ return (
169
+ <td {...props}>
170
+ <div className="flex size-(--cell-size) items-center justify-center text-center">
171
+ {children}
172
+ </div>
173
+ </td>
174
+ )
175
+ },
176
+ ...components,
177
+ }}
178
+ {...props}
179
+ />
180
+ )
181
+ }
182
+
183
+ function CalendarDayButton({
184
+ className,
185
+ day,
186
+ modifiers,
187
+ locale,
188
+ ...props
189
+ }: React.ComponentProps<typeof DayButton> & { locale?: Partial<Locale> }) {
190
+ const defaultClassNames = getDefaultClassNames()
191
+
192
+ const ref = React.useRef<HTMLButtonElement>(null)
193
+ React.useEffect(() => {
194
+ if (modifiers.focused) ref.current?.focus()
195
+ }, [modifiers.focused])
196
+
197
+ return (
198
+ <Button
199
+ ref={ref}
200
+ variant="ghost"
201
+ size="icon"
202
+ data-day={day.date.toLocaleDateString(locale?.code)}
203
+ data-selected-single={
204
+ modifiers.selected &&
205
+ !modifiers.range_start &&
206
+ !modifiers.range_end &&
207
+ !modifiers.range_middle
208
+ }
209
+ data-range-start={modifiers.range_start}
210
+ data-range-end={modifiers.range_end}
211
+ data-range-middle={modifiers.range_middle}
212
+ className={cn(
213
+ "relative isolate z-10 flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 border-0 leading-none font-semibold group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:outline group-data-[focused=true]/day:outline-2 group-data-[focused=true]/day:outline-offset-2 group-data-[focused=true]/day:outline-destructive 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-md data-[range-middle=true]:bg-muted data-[range-middle=true]:text-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 dark:hover:text-foreground [&>span]:text-xs [&>span]:opacity-70",
214
+ defaultClassNames.day,
215
+ className
216
+ )}
217
+ {...props}
218
+ />
219
+ )
220
+ }
221
+
222
+ export { Calendar, CalendarDayButton }