banhaten 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 (201) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +361 -0
  3. package/banhaten.config.example.json +13 -0
  4. package/package.json +59 -0
  5. package/registry/assets/activity-feed-avatar.png +0 -0
  6. package/registry/assets/avatars/avatar-01.jpg +0 -0
  7. package/registry/assets/avatars/avatar-02.jpg +0 -0
  8. package/registry/assets/avatars/avatar-03.jpg +0 -0
  9. package/registry/assets/avatars/avatar-04.jpg +0 -0
  10. package/registry/assets/avatars/avatar-05.jpg +0 -0
  11. package/registry/assets/avatars/avatar-06.jpg +0 -0
  12. package/registry/assets/avatars/avatar-07.jpg +0 -0
  13. package/registry/assets/avatars/avatar-08.jpg +0 -0
  14. package/registry/assets/avatars/avatar-09.jpg +0 -0
  15. package/registry/assets/avatars/avatar-10.jpg +0 -0
  16. package/registry/assets/avatars/avatar-11.jpg +0 -0
  17. package/registry/assets/avatars/avatar-12.jpg +0 -0
  18. package/registry/assets/avatars/avatar-13.jpg +0 -0
  19. package/registry/assets/avatars/avatar-14.jpg +0 -0
  20. package/registry/assets/avatars/avatar-15.jpg +0 -0
  21. package/registry/assets/avatars/avatar-16.jpg +0 -0
  22. package/registry/assets/avatars/avatar-17.jpg +0 -0
  23. package/registry/assets/avatars/avatar-18.jpg +0 -0
  24. package/registry/assets/avatars/avatar-19.jpg +0 -0
  25. package/registry/assets/avatars/avatar-20.jpg +0 -0
  26. package/registry/assets/avatars/avatar-21.jpg +0 -0
  27. package/registry/assets/avatars/avatar-22.jpg +0 -0
  28. package/registry/assets/avatars/avatar-23.jpg +0 -0
  29. package/registry/assets/avatars/avatar-24.jpg +0 -0
  30. package/registry/assets/avatars/avatar-25.jpg +0 -0
  31. package/registry/assets/avatars/avatar-26.jpg +0 -0
  32. package/registry/assets/avatars/avatar-27.jpg +0 -0
  33. package/registry/assets/avatars/avatar-28.jpg +0 -0
  34. package/registry/assets/avatars/avatar-29.jpg +0 -0
  35. package/registry/assets/avatars/avatar-30.jpg +0 -0
  36. package/registry/assets/avatars/avatar-31.jpg +0 -0
  37. package/registry/assets/avatars/avatar-32.jpg +0 -0
  38. package/registry/assets/avatars/avatar-33.jpg +0 -0
  39. package/registry/assets/avatars/avatar-34.jpg +0 -0
  40. package/registry/assets/avatars/avatar-35.jpg +0 -0
  41. package/registry/assets/image-assets.json +744 -0
  42. package/registry/assets/images/art-01.jpg +0 -0
  43. package/registry/assets/images/art-02.jpg +0 -0
  44. package/registry/assets/images/art-03.jpg +0 -0
  45. package/registry/assets/images/art-04.jpg +0 -0
  46. package/registry/assets/images/art-05.jpg +0 -0
  47. package/registry/assets/images/art-06.jpg +0 -0
  48. package/registry/assets/images/art-07.jpg +0 -0
  49. package/registry/assets/images/art-08.jpg +0 -0
  50. package/registry/assets/images/art-09.jpg +0 -0
  51. package/registry/assets/images/art-10.jpg +0 -0
  52. package/registry/assets/images/art-11.jpg +0 -0
  53. package/registry/assets/images/art-12.jpg +0 -0
  54. package/registry/assets/images/art-13.jpg +0 -0
  55. package/registry/assets/images/art-14.jpg +0 -0
  56. package/registry/assets/images/art-15.jpg +0 -0
  57. package/registry/assets/images/art-16.jpg +0 -0
  58. package/registry/assets/images/art-17.jpg +0 -0
  59. package/registry/assets/images/art-18.jpg +0 -0
  60. package/registry/assets/images/art-19.jpg +0 -0
  61. package/registry/assets/images/art-20.jpg +0 -0
  62. package/registry/assets/images/art-21.jpg +0 -0
  63. package/registry/assets/images/art-22.jpg +0 -0
  64. package/registry/assets/images/art-23.jpg +0 -0
  65. package/registry/assets/images/art-24.jpg +0 -0
  66. package/registry/assets/images/art-25.jpg +0 -0
  67. package/registry/assets/images/art-26.jpg +0 -0
  68. package/registry/assets/images/art-27.jpg +0 -0
  69. package/registry/assets/images/nature-01.jpg +0 -0
  70. package/registry/assets/images/nature-02.jpg +0 -0
  71. package/registry/assets/images/nature-03.jpg +0 -0
  72. package/registry/assets/images/nature-04.jpg +0 -0
  73. package/registry/assets/images/nature-05.jpg +0 -0
  74. package/registry/assets/images/nature-06.jpg +0 -0
  75. package/registry/assets/images/nature-07.jpg +0 -0
  76. package/registry/assets/images/nature-08.jpg +0 -0
  77. package/registry/assets/images/nature-09.jpg +0 -0
  78. package/registry/assets/images/nature-10.jpg +0 -0
  79. package/registry/assets/images/nature-11.jpg +0 -0
  80. package/registry/assets/images/nature-12.jpg +0 -0
  81. package/registry/assets/images/nature-13.jpg +0 -0
  82. package/registry/assets/images/nature-14.jpg +0 -0
  83. package/registry/assets/images/nature-15.jpg +0 -0
  84. package/registry/assets/images/nature-16.jpg +0 -0
  85. package/registry/assets/images/nature-17.jpg +0 -0
  86. package/registry/assets/images/nature-18.jpg +0 -0
  87. package/registry/assets/images/nature-19.jpg +0 -0
  88. package/registry/assets/images/nature-20.jpg +0 -0
  89. package/registry/components/accordion.tsx +119 -0
  90. package/registry/components/alert.tsx +282 -0
  91. package/registry/components/attribute.tsx +452 -0
  92. package/registry/components/avatar.tsx +142 -0
  93. package/registry/components/badge.tsx +567 -0
  94. package/registry/components/button-group.tsx +246 -0
  95. package/registry/components/button.tsx +102 -0
  96. package/registry/components/card.tsx +613 -0
  97. package/registry/components/checkbox.tsx +244 -0
  98. package/registry/components/date-picker.tsx +1143 -0
  99. package/registry/components/divider.tsx +82 -0
  100. package/registry/components/expanded/ActivityFeed.tsx +226 -0
  101. package/registry/components/expanded/Banner.tsx +145 -0
  102. package/registry/components/expanded/BannerBoard.tsx +225 -0
  103. package/registry/components/expanded/Breadcrumbs.tsx +156 -0
  104. package/registry/components/expanded/CatalogComponentsShowcase.tsx +211 -0
  105. package/registry/components/expanded/CatalogDivider.tsx +48 -0
  106. package/registry/components/expanded/CatalogTag.tsx +92 -0
  107. package/registry/components/expanded/CommandBar.tsx +406 -0
  108. package/registry/components/expanded/FileUpload.tsx +231 -0
  109. package/registry/components/expanded/IconExplorer.tsx +612 -0
  110. package/registry/components/expanded/OnboardingStepListItem.tsx +67 -0
  111. package/registry/components/expanded/PageHeader.tsx +184 -0
  112. package/registry/components/expanded/Slideout.tsx +514 -0
  113. package/registry/components/expanded/Steps.tsx +266 -0
  114. package/registry/components/expanded/Table.tsx +1014 -0
  115. package/registry/components/expanded/Tabs.tsx +86 -0
  116. package/registry/components/expanded/Timeline.tsx +235 -0
  117. package/registry/components/expanded/TimelineShowcase.tsx +158 -0
  118. package/registry/components/expanded/activityFeed.css +292 -0
  119. package/registry/components/expanded/banner.css +312 -0
  120. package/registry/components/expanded/breadcrumbs.css +140 -0
  121. package/registry/components/expanded/catalogComponentsShowcase.css +87 -0
  122. package/registry/components/expanded/commandBar.css +473 -0
  123. package/registry/components/expanded/divider.css +75 -0
  124. package/registry/components/expanded/fileUpload.css +228 -0
  125. package/registry/components/expanded/iconExplorer.css +764 -0
  126. package/registry/components/expanded/iconPacks.ts +866 -0
  127. package/registry/components/expanded/onboardingStepListItem.css +126 -0
  128. package/registry/components/expanded/pageHeader.css +287 -0
  129. package/registry/components/expanded/slideout.css +955 -0
  130. package/registry/components/expanded/steps.css +329 -0
  131. package/registry/components/expanded/table.css +607 -0
  132. package/registry/components/expanded/tabs.css +197 -0
  133. package/registry/components/expanded/tag.css +148 -0
  134. package/registry/components/expanded/timeline.css +282 -0
  135. package/registry/components/input-content.ts +106 -0
  136. package/registry/components/input.tsx +866 -0
  137. package/registry/components/menu.tsx +758 -0
  138. package/registry/components/modal.tsx +799 -0
  139. package/registry/components/pagination.tsx +543 -0
  140. package/registry/components/progress-slider.tsx +216 -0
  141. package/registry/components/progress.tsx +367 -0
  142. package/registry/components/radio-card.tsx +654 -0
  143. package/registry/components/radio-group.tsx +570 -0
  144. package/registry/components/select-content.tsx +313 -0
  145. package/registry/components/select.tsx +871 -0
  146. package/registry/components/slider.tsx +380 -0
  147. package/registry/components/social-button.tsx +360 -0
  148. package/registry/components/spinner.tsx +31 -0
  149. package/registry/components/tag.tsx +423 -0
  150. package/registry/components/textarea.tsx +625 -0
  151. package/registry/components/toggle.tsx +272 -0
  152. package/registry/components/toolbar.tsx +467 -0
  153. package/registry/components/tooltip.tsx +427 -0
  154. package/registry/examples/accordion-demo.tsx +34 -0
  155. package/registry/examples/alert-demo.tsx +14 -0
  156. package/registry/examples/attribute-demo.tsx +65 -0
  157. package/registry/examples/avatar-demo.tsx +74 -0
  158. package/registry/examples/badge-demo.tsx +53 -0
  159. package/registry/examples/button-demo.tsx +83 -0
  160. package/registry/examples/button-group-demo.tsx +42 -0
  161. package/registry/examples/card-demo.tsx +48 -0
  162. package/registry/examples/checkbox-demo.tsx +67 -0
  163. package/registry/examples/date-picker-demo.tsx +74 -0
  164. package/registry/examples/divider-demo.tsx +17 -0
  165. package/registry/examples/expanded/activity-feed-demo.tsx +22 -0
  166. package/registry/examples/expanded/banner-demo.tsx +23 -0
  167. package/registry/examples/expanded/catalog-components-demo.tsx +5 -0
  168. package/registry/examples/expanded/command-bar-demo.tsx +10 -0
  169. package/registry/examples/expanded/icons-demo.tsx +5 -0
  170. package/registry/examples/expanded/onboarding-step-demo.tsx +11 -0
  171. package/registry/examples/expanded/page-header-demo.tsx +19 -0
  172. package/registry/examples/expanded/slideout-demo.tsx +15 -0
  173. package/registry/examples/expanded/steps-demo.tsx +18 -0
  174. package/registry/examples/expanded/tabs-demo.tsx +13 -0
  175. package/registry/examples/expanded/timeline-demo.tsx +18 -0
  176. package/registry/examples/input-demo.tsx +87 -0
  177. package/registry/examples/menu-demo.tsx +109 -0
  178. package/registry/examples/modal-demo.tsx +16 -0
  179. package/registry/examples/pagination-demo.tsx +17 -0
  180. package/registry/examples/progress-demo.tsx +37 -0
  181. package/registry/examples/progress-slider-demo.tsx +29 -0
  182. package/registry/examples/radio-card-demo.tsx +51 -0
  183. package/registry/examples/radio-group-demo.tsx +62 -0
  184. package/registry/examples/select-demo.tsx +73 -0
  185. package/registry/examples/slider-demo.tsx +31 -0
  186. package/registry/examples/social-button-demo.tsx +51 -0
  187. package/registry/examples/tag-demo.tsx +29 -0
  188. package/registry/examples/textarea-demo.tsx +79 -0
  189. package/registry/examples/toggle-demo.tsx +59 -0
  190. package/registry/examples/toolbar-demo.tsx +80 -0
  191. package/registry/examples/tooltip-demo.tsx +115 -0
  192. package/registry/hooks/use-direction.ts +27 -0
  193. package/registry/index.json +1213 -0
  194. package/registry/styles/globals.css +4600 -0
  195. package/registry/utils/cn.ts +6 -0
  196. package/src/cli/index.js +826 -0
  197. package/tokens/Color mode.zip +0 -0
  198. package/tokens/Numbers.zip +0 -0
  199. package/tokens/Radius.zip +0 -0
  200. package/tokens/Theme.zip +0 -0
  201. package/tokens/banhaten.tokens.json +5525 -0
@@ -0,0 +1,1143 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import {
5
+ CalendarDaysIcon,
6
+ ChevronDownIcon,
7
+ ChevronLeftIcon,
8
+ ChevronRightIcon,
9
+ ChevronUpIcon,
10
+ } from "lucide-react"
11
+ import { cva } from "class-variance-authority"
12
+
13
+ import { cn } from "@/lib/utils"
14
+
15
+ type DatePickerDirection = "ltr" | "rtl" | "auto"
16
+ type CalendarView = "days" | "month-year"
17
+ type CalendarMode = "single" | "range"
18
+ type RangeCalendarType = "single" | "double" | "double-with-presets"
19
+
20
+ type CalendarTimeValue = {
21
+ hours?: string
22
+ minutes?: string
23
+ period?: string
24
+ zone?: string
25
+ }
26
+
27
+ type CalendarMonthYearItem = {
28
+ id?: string
29
+ month: React.ReactNode
30
+ year: React.ReactNode
31
+ selected?: boolean
32
+ disabled?: boolean
33
+ }
34
+
35
+ type CalendarRange = {
36
+ from?: Date
37
+ to?: Date
38
+ }
39
+
40
+ type CalendarPreset = {
41
+ id?: string
42
+ label: React.ReactNode
43
+ range?: CalendarRange
44
+ disabled?: boolean
45
+ }
46
+
47
+ type CalendarProps = Omit<
48
+ React.ComponentProps<"div">,
49
+ "defaultValue" | "dir" | "onSelect"
50
+ > & {
51
+ defaultValue?: Date
52
+ dir?: DatePickerDirection
53
+ displayMonth?: Date
54
+ events?: Date[]
55
+ monthYearItems?: CalendarMonthYearItem[]
56
+ nextLabel?: string
57
+ onDisplayMonthChange?: (month: Date) => void
58
+ onSelect?: (date: Date) => void
59
+ onViewChange?: (view: CalendarView) => void
60
+ previousLabel?: string
61
+ selectedDate?: Date
62
+ showTime?: boolean
63
+ surfaceSize?: "calendar" | "picker"
64
+ timeLabel?: React.ReactNode
65
+ timeValue?: CalendarTimeValue
66
+ title?: React.ReactNode
67
+ value?: Date
68
+ view?: CalendarView
69
+ }
70
+
71
+ type RangeCalendarProps = Omit<
72
+ React.ComponentProps<"div">,
73
+ "defaultValue" | "dir" | "onSelect"
74
+ > & {
75
+ defaultValue?: CalendarRange
76
+ dir?: DatePickerDirection
77
+ displayMonth?: Date
78
+ nextLabel?: string
79
+ onDisplayMonthChange?: (month: Date) => void
80
+ onPresetSelect?: (preset: CalendarPreset) => void
81
+ onSelect?: (range: CalendarRange) => void
82
+ presets?: CalendarPreset[]
83
+ previousLabel?: string
84
+ showPresets?: boolean
85
+ showTime?: boolean
86
+ timeLabel?: React.ReactNode
87
+ timeValue?: CalendarTimeValue
88
+ type?: RangeCalendarType
89
+ value?: CalendarRange
90
+ }
91
+
92
+ type DatePickerProps = Omit<
93
+ React.ComponentProps<"div">,
94
+ "defaultValue" | "dir" | "onSelect"
95
+ > & {
96
+ calendarProps?: Omit<
97
+ CalendarProps,
98
+ "defaultValue" | "selectedDate" | "value"
99
+ >
100
+ defaultOpen?: boolean
101
+ defaultValue?: Date
102
+ dir?: DatePickerDirection
103
+ disabled?: boolean
104
+ icon?: React.ReactNode
105
+ onOpenChange?: (open: boolean) => void
106
+ onSelect?: (date: Date) => void
107
+ open?: boolean
108
+ placeholder?: React.ReactNode
109
+ triggerClassName?: string
110
+ value?: Date
111
+ }
112
+
113
+ type DateRangePickerProps = Omit<
114
+ React.ComponentProps<"div">,
115
+ "defaultValue" | "dir" | "onSelect"
116
+ > & {
117
+ calendarProps?: Omit<
118
+ RangeCalendarProps,
119
+ "defaultValue" | "type" | "value"
120
+ >
121
+ defaultOpen?: boolean
122
+ defaultValue?: CalendarRange
123
+ dir?: DatePickerDirection
124
+ disabled?: boolean
125
+ icon?: React.ReactNode
126
+ onOpenChange?: (open: boolean) => void
127
+ onSelect?: (range: CalendarRange) => void
128
+ open?: boolean
129
+ placeholder?: React.ReactNode
130
+ triggerClassName?: string
131
+ type?: RangeCalendarType
132
+ value?: CalendarRange
133
+ }
134
+
135
+ const defaultDisplayMonth = new Date(2050, 6, 1)
136
+ const defaultSelectedDate = new Date(2050, 6, 18)
137
+ const defaultRange: CalendarRange = {
138
+ from: new Date(2050, 6, 15),
139
+ to: new Date(2050, 7, 23),
140
+ }
141
+ const defaultTimeValue: Required<CalendarTimeValue> = {
142
+ hours: "05",
143
+ minutes: "21",
144
+ period: "AM",
145
+ zone: "GTM-3",
146
+ }
147
+ const defaultDayLabels = ["S", "M", "T", "W", "T", "F", "S"]
148
+ const defaultMonthYearItems: CalendarMonthYearItem[] = [
149
+ { month: "April", year: "2023" },
150
+ { month: "May", year: "2022" },
151
+ { month: "June", year: "2023" },
152
+ { month: "July", year: "2024", selected: true },
153
+ { month: "August", year: "2025" },
154
+ { month: "September", year: "2026" },
155
+ { month: "October", year: "2027" },
156
+ ]
157
+ const defaultPresets: CalendarPreset[] = [
158
+ { label: "Today" },
159
+ { label: "Last 7 days" },
160
+ { label: "Last 14 days" },
161
+ { label: "Last 30 days" },
162
+ { label: "Last 3 months" },
163
+ { label: "Last 12 months" },
164
+ { label: "Quarter to date" },
165
+ { label: "Year to date" },
166
+ { label: "All time" },
167
+ ]
168
+
169
+ const calendarSurface = cva(
170
+ [
171
+ "relative bg-[var(--bh-bg-raised)]",
172
+ "[--bh-date-surface-border:var(--bh-border-subtle)]",
173
+ "shadow-[var(--shadow-date-surface)]",
174
+ ],
175
+ {
176
+ variants: {
177
+ layout: {
178
+ single:
179
+ "flex flex-col items-start rounded-[var(--bh-radius-2xl-12)] p-[var(--bh-space-md-8)]",
180
+ range:
181
+ "inline-flex max-w-[var(--bh-date-range-surface-max-width)] items-start gap-[var(--bh-space-3xl-16)] overflow-auto rounded-[var(--bh-radius-2xl-12)] p-[var(--bh-space-3xl-16)]",
182
+ },
183
+ surfaceSize: {
184
+ calendar: "w-[var(--bh-date-surface-width-calendar)]",
185
+ picker: "w-[var(--bh-date-surface-width-picker)]",
186
+ },
187
+ },
188
+ defaultVariants: {
189
+ layout: "single",
190
+ surfaceSize: "calendar",
191
+ },
192
+ }
193
+ )
194
+
195
+ const pickerTrigger = cva(
196
+ [
197
+ "group/date-trigger relative flex h-[var(--bh-date-trigger-height)] w-[var(--bh-date-trigger-width)] max-w-full items-center gap-[var(--bh-space-md-8)] rounded-[var(--bh-radius-lg-8)]",
198
+ "bg-[var(--bh-interactive-input-default)] px-[var(--bh-space-lg-10)] text-start outline-none",
199
+ "[--bh-date-trigger-border:var(--bh-border-input)] [--bh-date-trigger-shadow:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-date-trigger-border),var(--shadow-component-default)]",
200
+ "text-[length:var(--bh-text-body-md-regular-font-size)] font-[var(--bh-text-body-md-regular-font-weight)] leading-[var(--bh-text-body-md-regular-line-height)] tracking-[var(--bh-text-body-md-regular-letter-spacing)]",
201
+ "text-[var(--bh-content-default)] shadow-[var(--bh-date-trigger-shadow)] transition-[background-color,box-shadow]",
202
+ "hover:bg-[var(--bh-bg-default-hover)] focus-visible:shadow-[var(--shadow-input-focus-ring)]",
203
+ "disabled:pointer-events-none disabled:bg-[var(--bh-interactive-input-disabled)] disabled:text-[var(--bh-content-disabled)] disabled:shadow-[inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-border-disabled)]",
204
+ ]
205
+ )
206
+
207
+ const monthButton = cva(
208
+ [
209
+ "flex size-[var(--bh-date-nav-button-size)] shrink-0 items-center justify-center rounded-[var(--bh-control-default)]",
210
+ "bg-[var(--bh-interactive-soft-default)] text-[var(--bh-content-default)] outline-none transition-[background-color,box-shadow,color]",
211
+ "hover:bg-[var(--bh-interactive-soft-hover)] focus-visible:shadow-[var(--shadow-button-focus)] disabled:opacity-0",
212
+ "[&_svg]:size-[var(--bh-date-nav-icon-size)]",
213
+ ]
214
+ )
215
+
216
+ const calendarTitleButton = cva(
217
+ [
218
+ "flex h-[var(--bh-date-title-height)] min-w-0 flex-1 items-center justify-center gap-[var(--bh-space-xs-4)] rounded-[var(--bh-radius-lg-8)]",
219
+ "px-[var(--bh-space-md-8)] text-center outline-none transition-[background-color,box-shadow]",
220
+ "text-[length:var(--bh-text-body-md-medium-font-size)] font-[var(--bh-text-body-md-medium-font-weight)] leading-[var(--bh-text-body-md-medium-line-height)] tracking-[var(--bh-text-body-md-medium-letter-spacing)] text-[var(--bh-content-default)]",
221
+ "hover:bg-[var(--bh-interactive-ghost-hover)] focus-visible:shadow-[var(--shadow-button-focus)]",
222
+ ],
223
+ {
224
+ variants: {
225
+ framed: {
226
+ true:
227
+ "shadow-[inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-border-subtle)]",
228
+ false: "",
229
+ },
230
+ active: {
231
+ true: "bg-[var(--bh-interactive-ghost-hover)]",
232
+ false: "",
233
+ },
234
+ },
235
+ defaultVariants: {
236
+ active: false,
237
+ framed: false,
238
+ },
239
+ }
240
+ )
241
+
242
+ const dayCellButton = cva(
243
+ [
244
+ "relative flex h-[var(--bh-date-day-cell-height)] min-w-0 flex-1 items-center justify-center text-center outline-none",
245
+ "text-[length:var(--bh-text-body-md-regular-font-size)] font-[var(--bh-text-body-md-regular-font-weight)] leading-[var(--bh-text-body-md-regular-line-height)] tracking-[var(--bh-text-body-md-regular-letter-spacing)]",
246
+ "transition-[background-color,color,box-shadow] focus-visible:z-[var(--bh-z-raised)] focus-visible:shadow-[var(--shadow-button-focus)]",
247
+ ],
248
+ {
249
+ variants: {
250
+ state: {
251
+ default:
252
+ "rounded-[var(--bh-control-default)] text-[var(--bh-content-default)] hover:bg-[var(--bh-interactive-ghost-hover)]",
253
+ outside:
254
+ "rounded-[var(--bh-control-default)] text-[var(--bh-content-disabled)]",
255
+ event:
256
+ "rounded-[var(--bh-control-default)] text-[var(--bh-content-brand-default)] after:absolute after:bottom-[var(--bh-date-event-dot-offset-bottom)] after:left-1/2 after:size-[var(--bh-date-event-dot-size)] after:-translate-x-1/2 after:rounded-[var(--bh-radius-full)] after:bg-[var(--bh-content-brand-default)] rtl:after:translate-x-1/2",
257
+ selected:
258
+ "rounded-[var(--bh-control-default)] bg-[var(--bh-bg-brand-default)] text-[var(--bh-content-on-color)] hover:bg-[var(--bh-bg-brand-default)]",
259
+ range:
260
+ "rounded-none bg-[var(--bh-bg-brand-subtle)] text-[var(--bh-content-brand-strong)] hover:bg-[var(--bh-bg-brand-subtle)]",
261
+ },
262
+ },
263
+ defaultVariants: {
264
+ state: "default",
265
+ },
266
+ }
267
+ )
268
+
269
+ const dateListItem = cva(
270
+ [
271
+ "flex h-[var(--bh-date-list-item-height)] w-full items-center rounded-[var(--bh-radius-lg-8)] px-[var(--bh-space-md-8)] py-[var(--bh-space-sm-6)] text-start outline-none",
272
+ "text-[length:var(--bh-text-body-md-medium-font-size)] font-[var(--bh-text-body-md-medium-font-weight)] leading-[var(--bh-text-body-md-medium-line-height)] tracking-[var(--bh-text-body-md-medium-letter-spacing)]",
273
+ "text-[var(--bh-content-default)] transition-colors hover:bg-[var(--bh-interactive-ghost-hover)] focus-visible:bg-[var(--bh-interactive-ghost-hover)] disabled:pointer-events-none disabled:text-[var(--bh-content-disabled)]",
274
+ ]
275
+ )
276
+
277
+ function Calendar({
278
+ className,
279
+ defaultValue = defaultSelectedDate,
280
+ dir,
281
+ displayMonth,
282
+ events,
283
+ monthYearItems = defaultMonthYearItems,
284
+ nextLabel = "Next month",
285
+ onDisplayMonthChange,
286
+ onSelect,
287
+ onViewChange,
288
+ previousLabel = "Previous month",
289
+ selectedDate,
290
+ showTime = true,
291
+ surfaceSize = "calendar",
292
+ timeLabel = "Time",
293
+ timeValue = defaultTimeValue,
294
+ title,
295
+ value,
296
+ view,
297
+ ...props
298
+ }: CalendarProps) {
299
+ const controlledValue = value ?? selectedDate
300
+ const [internalValue, setInternalValue] = React.useState(defaultValue)
301
+ const [internalDisplayMonth, setInternalDisplayMonth] = React.useState(
302
+ startOfMonth(displayMonth ?? controlledValue ?? defaultValue)
303
+ )
304
+ const [internalView, setInternalView] = React.useState<CalendarView>("days")
305
+ const selected = controlledValue ?? internalValue
306
+ const activeDisplayMonth = startOfMonth(displayMonth ?? internalDisplayMonth)
307
+ const activeView = view ?? internalView
308
+ const eventDates = events ?? [new Date(2050, 6, 15)]
309
+
310
+ function updateDisplayMonth(nextMonth: Date) {
311
+ const normalized = startOfMonth(nextMonth)
312
+ if (displayMonth === undefined) {
313
+ setInternalDisplayMonth(normalized)
314
+ }
315
+ onDisplayMonthChange?.(normalized)
316
+ }
317
+
318
+ function updateView(nextView: CalendarView) {
319
+ if (view === undefined) {
320
+ setInternalView(nextView)
321
+ }
322
+ onViewChange?.(nextView)
323
+ }
324
+
325
+ function selectDate(nextDate: Date) {
326
+ if (value === undefined && selectedDate === undefined) {
327
+ setInternalValue(nextDate)
328
+ }
329
+ onSelect?.(nextDate)
330
+ }
331
+
332
+ return (
333
+ <div
334
+ data-slot="calendar"
335
+ data-view={activeView}
336
+ dir={dir}
337
+ className={cn(calendarSurface({ layout: "single", surfaceSize }), className)}
338
+ {...props}
339
+ >
340
+ <CalendarMonth
341
+ displayMonth={activeDisplayMonth}
342
+ eventDates={eventDates}
343
+ mode="single"
344
+ className="w-full"
345
+ nextLabel={nextLabel}
346
+ onDisplayMonthChange={updateDisplayMonth}
347
+ onSelectDate={selectDate}
348
+ onToggleView={() =>
349
+ updateView(activeView === "days" ? "month-year" : "days")
350
+ }
351
+ previousLabel={previousLabel}
352
+ selectedDate={selected}
353
+ showTime={showTime && activeView === "days"}
354
+ timeLabel={timeLabel}
355
+ timeValue={timeValue}
356
+ title={title}
357
+ view={activeView}
358
+ />
359
+
360
+ {activeView === "month-year" ? (
361
+ <>
362
+ <div aria-hidden="true" className="h-[var(--bh-date-month-year-spacer-height)] w-full" />
363
+ <MonthYearList items={monthYearItems} />
364
+ </>
365
+ ) : null}
366
+ </div>
367
+ )
368
+ }
369
+
370
+ function RangeCalendar({
371
+ className,
372
+ defaultValue = defaultRange,
373
+ dir,
374
+ displayMonth,
375
+ nextLabel = "Next month",
376
+ onDisplayMonthChange,
377
+ onPresetSelect,
378
+ onSelect,
379
+ presets = defaultPresets,
380
+ previousLabel = "Previous month",
381
+ showPresets,
382
+ showTime = true,
383
+ timeLabel = "Time",
384
+ timeValue = defaultTimeValue,
385
+ type = "double",
386
+ value,
387
+ ...props
388
+ }: RangeCalendarProps) {
389
+ const [internalRange, setInternalRange] = React.useState(defaultValue)
390
+ const [internalDisplayMonth, setInternalDisplayMonth] = React.useState(
391
+ startOfMonth(displayMonth ?? defaultValue.from ?? defaultDisplayMonth)
392
+ )
393
+ const selectedRange = normalizeRange(value ?? internalRange)
394
+ const activeDisplayMonth = startOfMonth(displayMonth ?? internalDisplayMonth)
395
+ const monthsToRender = type === "single" ? 1 : 2
396
+ const shouldShowPresets = showPresets ?? type === "double-with-presets"
397
+ const monthNodes = Array.from({ length: monthsToRender }, (_, index) =>
398
+ addMonths(activeDisplayMonth, index)
399
+ )
400
+
401
+ function updateDisplayMonth(nextMonth: Date) {
402
+ const normalized = startOfMonth(nextMonth)
403
+ if (displayMonth === undefined) {
404
+ setInternalDisplayMonth(normalized)
405
+ }
406
+ onDisplayMonthChange?.(normalized)
407
+ }
408
+
409
+ function selectRangeDate(nextDate: Date) {
410
+ const nextRange = getNextRange(selectedRange, nextDate)
411
+ if (value === undefined) {
412
+ setInternalRange(nextRange)
413
+ }
414
+ onSelect?.(nextRange)
415
+ }
416
+
417
+ function selectPreset(preset: CalendarPreset) {
418
+ if (preset.disabled) return
419
+ if (preset.range) {
420
+ const nextRange = normalizeRange(preset.range)
421
+ if (value === undefined) {
422
+ setInternalRange(nextRange)
423
+ }
424
+ onSelect?.(nextRange)
425
+ }
426
+ onPresetSelect?.(preset)
427
+ }
428
+
429
+ return (
430
+ <div
431
+ data-slot="range-calendar"
432
+ data-type={type}
433
+ dir={dir}
434
+ className={cn(calendarSurface({ layout: "range" }), className)}
435
+ {...props}
436
+ >
437
+ {shouldShowPresets ? (
438
+ <div
439
+ data-slot="range-calendar-presets"
440
+ className="grid w-[var(--bh-date-presets-width)] shrink-0 border-e border-[var(--bh-border-subtle)] pe-[var(--bh-space-md-8)]"
441
+ >
442
+ {presets.map((preset, index) => (
443
+ <button
444
+ aria-disabled={preset.disabled || undefined}
445
+ className={dateListItem()}
446
+ data-slot="range-calendar-preset"
447
+ disabled={preset.disabled}
448
+ key={preset.id || index}
449
+ type="button"
450
+ onClick={() => selectPreset(preset)}
451
+ >
452
+ <span dir="auto" className="min-w-0 truncate">
453
+ {preset.label}
454
+ </span>
455
+ </button>
456
+ ))}
457
+ </div>
458
+ ) : null}
459
+
460
+ <div
461
+ data-slot="range-calendar-months"
462
+ className="flex min-w-max gap-[var(--bh-space-3xl-16)]"
463
+ >
464
+ {monthNodes.map((month, index) => (
465
+ <CalendarMonth
466
+ displayMonth={month}
467
+ key={month.toISOString()}
468
+ mode="range"
469
+ className="w-[var(--bh-date-range-month-width)]"
470
+ nextLabel={nextLabel}
471
+ onDisplayMonthChange={updateDisplayMonth}
472
+ onSelectDate={selectRangeDate}
473
+ previousLabel={previousLabel}
474
+ range={selectedRange}
475
+ showNextButton={index === monthNodes.length - 1}
476
+ showPreviousButton={index === 0}
477
+ showTime={showTime}
478
+ timeLabel={timeLabel}
479
+ timeValue={timeValue}
480
+ />
481
+ ))}
482
+ </div>
483
+ </div>
484
+ )
485
+ }
486
+
487
+ function DatePicker({
488
+ calendarProps,
489
+ className,
490
+ defaultOpen = false,
491
+ defaultValue,
492
+ dir,
493
+ disabled,
494
+ icon,
495
+ onOpenChange,
496
+ onSelect,
497
+ open,
498
+ placeholder = "Select date",
499
+ triggerClassName,
500
+ value,
501
+ ...props
502
+ }: DatePickerProps) {
503
+ const [internalOpen, setInternalOpen] = React.useState(defaultOpen)
504
+ const [internalValue, setInternalValue] = React.useState(defaultValue)
505
+ const selected = value ?? internalValue
506
+ const isOpen = open ?? internalOpen
507
+
508
+ function updateOpen(nextOpen: boolean) {
509
+ if (open === undefined) {
510
+ setInternalOpen(nextOpen)
511
+ }
512
+ onOpenChange?.(nextOpen)
513
+ }
514
+
515
+ function selectDate(nextDate: Date) {
516
+ if (value === undefined) {
517
+ setInternalValue(nextDate)
518
+ }
519
+ onSelect?.(nextDate)
520
+ }
521
+
522
+ return (
523
+ <div
524
+ data-open={isOpen ? "true" : "false"}
525
+ data-slot="date-picker"
526
+ dir={dir}
527
+ className={cn("relative w-[var(--bh-date-picker-width)] max-w-full", className)}
528
+ {...props}
529
+ >
530
+ <DatePickerTrigger
531
+ disabled={disabled}
532
+ icon={icon}
533
+ open={isOpen}
534
+ placeholder={placeholder}
535
+ value={selected ? formatCalendarDate(selected, { month: "long" }) : undefined}
536
+ className={triggerClassName}
537
+ onClick={() => updateOpen(!isOpen)}
538
+ />
539
+
540
+ {isOpen ? (
541
+ <Calendar
542
+ defaultValue={defaultValue ?? defaultSelectedDate}
543
+ displayMonth={selected ?? defaultValue ?? defaultDisplayMonth}
544
+ selectedDate={selected}
545
+ surfaceSize="picker"
546
+ {...calendarProps}
547
+ className={cn(
548
+ "absolute left-1/2 top-[calc(100%+var(--bh-space-md-8))] z-[var(--bh-z-popover)] -translate-x-1/2",
549
+ calendarProps?.className
550
+ )}
551
+ dir={calendarProps?.dir ?? dir}
552
+ onSelect={(nextDate) => {
553
+ selectDate(nextDate)
554
+ calendarProps?.onSelect?.(nextDate)
555
+ }}
556
+ />
557
+ ) : null}
558
+ </div>
559
+ )
560
+ }
561
+
562
+ function DateRangePicker({
563
+ calendarProps,
564
+ className,
565
+ defaultOpen = false,
566
+ defaultValue,
567
+ dir,
568
+ disabled,
569
+ icon,
570
+ onOpenChange,
571
+ onSelect,
572
+ open,
573
+ placeholder = "Select date range",
574
+ triggerClassName,
575
+ type = "double",
576
+ value,
577
+ ...props
578
+ }: DateRangePickerProps) {
579
+ const [internalOpen, setInternalOpen] = React.useState(defaultOpen)
580
+ const [internalValue, setInternalValue] = React.useState(defaultValue)
581
+ const selectedRange = normalizeRange(value ?? internalValue ?? {})
582
+ const isOpen = open ?? internalOpen
583
+ const displayMonth = selectedRange.from ?? defaultDisplayMonth
584
+
585
+ function updateOpen(nextOpen: boolean) {
586
+ if (open === undefined) {
587
+ setInternalOpen(nextOpen)
588
+ }
589
+ onOpenChange?.(nextOpen)
590
+ }
591
+
592
+ function selectRange(nextRange: CalendarRange) {
593
+ if (value === undefined) {
594
+ setInternalValue(nextRange)
595
+ }
596
+ onSelect?.(nextRange)
597
+ }
598
+
599
+ return (
600
+ <div
601
+ data-open={isOpen ? "true" : "false"}
602
+ data-slot="date-range-picker"
603
+ data-type={type}
604
+ dir={dir}
605
+ className={cn("relative w-[var(--bh-date-picker-width)] max-w-full", className)}
606
+ {...props}
607
+ >
608
+ <DatePickerTrigger
609
+ disabled={disabled}
610
+ icon={icon}
611
+ open={isOpen}
612
+ placeholder={placeholder}
613
+ value={formatCalendarRange(selectedRange)}
614
+ className={triggerClassName}
615
+ onClick={() => updateOpen(!isOpen)}
616
+ />
617
+
618
+ {isOpen ? (
619
+ <RangeCalendar
620
+ defaultValue={defaultValue ?? defaultRange}
621
+ displayMonth={displayMonth}
622
+ type={type}
623
+ value={selectedRange}
624
+ {...calendarProps}
625
+ className={cn(
626
+ "absolute left-1/2 top-[calc(100%+var(--bh-space-md-8))] z-[var(--bh-z-popover)] -translate-x-1/2",
627
+ calendarProps?.className
628
+ )}
629
+ dir={calendarProps?.dir ?? dir}
630
+ onSelect={(nextRange) => {
631
+ selectRange(nextRange)
632
+ calendarProps?.onSelect?.(nextRange)
633
+ }}
634
+ />
635
+ ) : null}
636
+ </div>
637
+ )
638
+ }
639
+
640
+ function DatePickerTrigger({
641
+ className,
642
+ disabled,
643
+ icon,
644
+ onClick,
645
+ open,
646
+ placeholder,
647
+ value,
648
+ }: {
649
+ className?: string
650
+ disabled?: boolean
651
+ icon?: React.ReactNode
652
+ onClick?: React.MouseEventHandler<HTMLButtonElement>
653
+ open?: boolean
654
+ placeholder: React.ReactNode
655
+ value?: React.ReactNode
656
+ }) {
657
+ const hasValue = hasRenderableContent(value)
658
+
659
+ return (
660
+ <button
661
+ aria-expanded={open || undefined}
662
+ data-slot="date-picker-trigger"
663
+ disabled={disabled}
664
+ type="button"
665
+ className={cn(pickerTrigger(), className)}
666
+ onClick={onClick}
667
+ >
668
+ <span
669
+ aria-hidden="true"
670
+ data-slot="date-picker-trigger-icon"
671
+ className="flex size-[var(--bh-date-trigger-icon-size)] shrink-0 items-center justify-center text-[var(--bh-content-muted)] group-disabled/date-trigger:text-[var(--bh-content-disabled)] [&_svg:not([class*='size-'])]:size-[var(--bh-date-trigger-icon-size)]"
672
+ >
673
+ {icon ?? <CalendarDaysIcon strokeWidth={1.8} />}
674
+ </span>
675
+ <span
676
+ data-slot="date-picker-trigger-value"
677
+ dir="auto"
678
+ className={cn(
679
+ "min-w-0 flex-1 truncate text-start",
680
+ hasValue ? "text-[var(--bh-content-default)]" : "text-[var(--bh-content-muted)]",
681
+ disabled && "text-[var(--bh-content-disabled)]"
682
+ )}
683
+ >
684
+ {hasValue ? value : placeholder}
685
+ </span>
686
+ </button>
687
+ )
688
+ }
689
+
690
+ function CalendarMonth({
691
+ className,
692
+ displayMonth,
693
+ eventDates,
694
+ mode,
695
+ nextLabel,
696
+ onDisplayMonthChange,
697
+ onSelectDate,
698
+ onToggleView,
699
+ previousLabel,
700
+ range,
701
+ selectedDate,
702
+ showNextButton = true,
703
+ showPreviousButton = true,
704
+ showTime,
705
+ timeLabel,
706
+ timeValue,
707
+ title,
708
+ view = "days",
709
+ }: {
710
+ className?: string
711
+ displayMonth: Date
712
+ eventDates?: Date[]
713
+ mode: CalendarMode
714
+ nextLabel: string
715
+ onDisplayMonthChange: (month: Date) => void
716
+ onSelectDate: (date: Date) => void
717
+ onToggleView?: () => void
718
+ previousLabel: string
719
+ range?: CalendarRange
720
+ selectedDate?: Date
721
+ showNextButton?: boolean
722
+ showPreviousButton?: boolean
723
+ showTime?: boolean
724
+ timeLabel: React.ReactNode
725
+ timeValue: CalendarTimeValue
726
+ title?: React.ReactNode
727
+ view?: CalendarView
728
+ }) {
729
+ const cells = React.useMemo(() => getCalendarCells(displayMonth), [displayMonth])
730
+ const monthTitle = title ?? formatMonthTitle(displayMonth)
731
+ const hasMonthToggle = mode === "single"
732
+
733
+ return (
734
+ <div
735
+ data-slot="calendar-month"
736
+ className={cn(
737
+ "flex shrink-0 flex-col items-start gap-[var(--bh-space-md-8)]",
738
+ className
739
+ )}
740
+ >
741
+ <div
742
+ data-slot="calendar-header"
743
+ className="flex w-full items-center gap-[var(--bh-space-md-8)]"
744
+ >
745
+ <button
746
+ aria-label={previousLabel}
747
+ disabled={!showPreviousButton || view === "month-year"}
748
+ type="button"
749
+ className={monthButton()}
750
+ onClick={() => onDisplayMonthChange(addMonths(displayMonth, -1))}
751
+ >
752
+ <ChevronLeftIcon aria-hidden="true" data-rtl-flip="true" />
753
+ </button>
754
+
755
+ {hasMonthToggle ? (
756
+ <button
757
+ aria-expanded={view === "month-year"}
758
+ data-slot="calendar-title"
759
+ type="button"
760
+ className={calendarTitleButton({
761
+ active: view === "month-year",
762
+ framed: true,
763
+ })}
764
+ onClick={onToggleView}
765
+ >
766
+ <span className="min-w-0 truncate">{monthTitle}</span>
767
+ {view === "month-year" ? (
768
+ <ChevronUpIcon aria-hidden="true" className="size-[var(--bh-date-title-chevron-size)]" />
769
+ ) : (
770
+ <ChevronDownIcon aria-hidden="true" className="size-[var(--bh-date-title-chevron-size)]" />
771
+ )}
772
+ </button>
773
+ ) : (
774
+ <div
775
+ data-slot="calendar-title"
776
+ className={calendarTitleButton({ framed: false })}
777
+ >
778
+ <span className="min-w-0 truncate">{monthTitle}</span>
779
+ </div>
780
+ )}
781
+
782
+ <button
783
+ aria-label={nextLabel}
784
+ disabled={!showNextButton || view === "month-year"}
785
+ type="button"
786
+ className={monthButton()}
787
+ onClick={() => onDisplayMonthChange(addMonths(displayMonth, 1))}
788
+ >
789
+ <ChevronRightIcon aria-hidden="true" data-rtl-flip="true" />
790
+ </button>
791
+ </div>
792
+
793
+ {view === "days" ? (
794
+ <>
795
+ <div
796
+ aria-hidden="true"
797
+ data-slot="calendar-weekdays"
798
+ className="grid w-full grid-cols-7"
799
+ >
800
+ {defaultDayLabels.map((day, index) => (
801
+ <div
802
+ className="flex h-[var(--bh-date-day-cell-height)] min-w-0 items-center justify-center rounded-[var(--bh-radius-lg-8)] text-center text-[length:var(--bh-text-body-md-regular-font-size)] font-[var(--bh-text-body-md-regular-font-weight)] leading-[var(--bh-text-body-md-regular-line-height)] tracking-[var(--bh-text-body-md-regular-letter-spacing)] text-[var(--bh-content-subtle)]"
803
+ key={`${day}-${index}`}
804
+ >
805
+ {day}
806
+ </div>
807
+ ))}
808
+ </div>
809
+
810
+ <div
811
+ data-slot="calendar-grid"
812
+ role="grid"
813
+ className="grid w-full grid-cols-7"
814
+ >
815
+ {cells.map((cell) => (
816
+ <CalendarDayCell
817
+ cell={cell}
818
+ displayMonth={displayMonth}
819
+ eventDates={eventDates}
820
+ key={cell.date.toISOString()}
821
+ mode={mode}
822
+ onSelectDate={onSelectDate}
823
+ range={range}
824
+ selectedDate={selectedDate}
825
+ />
826
+ ))}
827
+ </div>
828
+
829
+ {showTime ? (
830
+ <CalendarTime
831
+ label={timeLabel}
832
+ value={timeValue}
833
+ />
834
+ ) : null}
835
+ </>
836
+ ) : null}
837
+ </div>
838
+ )
839
+ }
840
+
841
+ function CalendarDayCell({
842
+ cell,
843
+ displayMonth,
844
+ eventDates,
845
+ mode,
846
+ onSelectDate,
847
+ range,
848
+ selectedDate,
849
+ }: {
850
+ cell: CalendarCell
851
+ displayMonth: Date
852
+ eventDates?: Date[]
853
+ mode: CalendarMode
854
+ onSelectDate: (date: Date) => void
855
+ range?: CalendarRange
856
+ selectedDate?: Date
857
+ }) {
858
+ const outside = cell.date.getMonth() !== displayMonth.getMonth()
859
+ const hasEvent = eventDates?.some((date) => isSameDay(date, cell.date)) ?? false
860
+ const selected =
861
+ mode === "single"
862
+ ? Boolean(selectedDate && isSameDay(selectedDate, cell.date))
863
+ : Boolean(
864
+ range?.from &&
865
+ (isSameDay(range.from, cell.date) ||
866
+ (range.to && isSameDay(range.to, cell.date)))
867
+ )
868
+ const inRange =
869
+ mode === "range" &&
870
+ Boolean(
871
+ range?.from &&
872
+ range?.to &&
873
+ isAfterOrSameDay(cell.date, range.from) &&
874
+ isBeforeOrSameDay(cell.date, range.to)
875
+ )
876
+ const rangeEndpoint =
877
+ mode === "range" &&
878
+ Boolean(
879
+ range?.from &&
880
+ (isSameDay(range.from, cell.date) ||
881
+ (range.to && isSameDay(range.to, cell.date)))
882
+ )
883
+ const rangeState = selected
884
+ ? "selected"
885
+ : inRange
886
+ ? "range"
887
+ : outside
888
+ ? "outside"
889
+ : hasEvent
890
+ ? "event"
891
+ : "default"
892
+
893
+ return (
894
+ <button
895
+ aria-disabled={outside || undefined}
896
+ aria-label={formatCalendarDate(cell.date, { month: "long" })}
897
+ aria-selected={selected || undefined}
898
+ data-outside={outside ? "true" : undefined}
899
+ data-range-endpoint={rangeEndpoint ? "true" : undefined}
900
+ data-slot="calendar-cell"
901
+ disabled={outside}
902
+ role="gridcell"
903
+ type="button"
904
+ className={cn(
905
+ dayCellButton({ state: rangeState }),
906
+ inRange && cell.weekday === 0 && "rounded-s-[var(--bh-control-default)]",
907
+ inRange && cell.weekday === 6 && "rounded-e-[var(--bh-control-default)]"
908
+ )}
909
+ onClick={() => onSelectDate(cell.date)}
910
+ >
911
+ {cell.date.getDate()}
912
+ </button>
913
+ )
914
+ }
915
+
916
+ function CalendarTime({
917
+ label,
918
+ value,
919
+ }: {
920
+ label: React.ReactNode
921
+ value: CalendarTimeValue
922
+ }) {
923
+ const resolved = {
924
+ ...defaultTimeValue,
925
+ ...value,
926
+ }
927
+
928
+ return (
929
+ <div
930
+ data-slot="calendar-time"
931
+ className="flex w-full items-center gap-[var(--bh-space-5xl-24)] ps-[var(--bh-space-md-8)]"
932
+ >
933
+ <span
934
+ data-slot="calendar-time-label"
935
+ dir="auto"
936
+ className="shrink-0 text-[length:var(--bh-text-body-2xs-regular-font-size)] font-[var(--bh-text-body-2xs-regular-font-weight)] leading-[var(--bh-text-body-2xs-regular-line-height)] tracking-[var(--bh-text-body-2xs-regular-letter-spacing)] text-[var(--bh-content-subtle)]"
937
+ >
938
+ {label}
939
+ </span>
940
+ <span
941
+ data-slot="calendar-time-input"
942
+ className="flex min-h-[var(--bh-date-time-input-min-height)] min-w-0 flex-1 items-center gap-[var(--bh-space-xs-4)] rounded-[var(--bh-radius-lg-8)] bg-[var(--bh-interactive-input-default)] px-[var(--bh-space-md-8)] py-[var(--bh-space-sm-6)] shadow-[var(--shadow-date-time-input)]"
943
+ >
944
+ <CalendarTimeSegment active>{resolved.hours}</CalendarTimeSegment>
945
+ <span className="text-[var(--bh-content-subtle)]">:</span>
946
+ <CalendarTimeSegment>{resolved.minutes}</CalendarTimeSegment>
947
+ <CalendarTimeSegment>{resolved.period}</CalendarTimeSegment>
948
+ <CalendarTimeSegment subtle>{resolved.zone}</CalendarTimeSegment>
949
+ </span>
950
+ </div>
951
+ )
952
+ }
953
+
954
+ function CalendarTimeSegment({
955
+ active,
956
+ children,
957
+ subtle,
958
+ }: {
959
+ active?: boolean
960
+ children: React.ReactNode
961
+ subtle?: boolean
962
+ }) {
963
+ return (
964
+ <span
965
+ data-active={active ? "true" : undefined}
966
+ className={cn(
967
+ "inline-flex min-w-0 items-center justify-center rounded-[var(--bh-radius-sm-4)] px-[var(--bh-space-xxs-2)] text-[length:var(--bh-text-body-2xs-medium-font-size)] font-[var(--bh-text-body-2xs-medium-font-weight)] leading-[var(--bh-text-body-2xs-medium-line-height)] tracking-[var(--bh-text-body-2xs-medium-letter-spacing)]",
968
+ active && "bg-[var(--bh-interactive-ghost-hover)]",
969
+ subtle ? "text-[var(--bh-content-subtle)]" : "text-[var(--bh-content-default)]"
970
+ )}
971
+ >
972
+ {children}
973
+ </span>
974
+ )
975
+ }
976
+
977
+ function MonthYearList({ items }: { items: CalendarMonthYearItem[] }) {
978
+ return (
979
+ <div
980
+ data-slot="calendar-month-year-list"
981
+ className="absolute bottom-[var(--bh-space-md-8)] left-1/2 top-[var(--bh-date-month-year-list-top)] flex w-[var(--bh-date-month-year-list-width)] -translate-x-1/2 flex-col justify-center overflow-hidden rounded-[var(--bh-radius-md-6)] bg-[var(--bh-bg-default)] p-[var(--bh-space-md-8)] shadow-[var(--shadow-date-month-year-list)] rtl:translate-x-1/2"
982
+ >
983
+ {items.map((item, index) => (
984
+ <button
985
+ className={cn(
986
+ "grid h-[var(--bh-date-month-year-item-height)] grid-cols-2 items-center rounded-[var(--bh-radius-md-6)] px-[var(--bh-space-md-8)] py-[var(--bh-space-xs-4)] text-center outline-none transition-colors",
987
+ "text-[length:var(--bh-text-body-md-regular-font-size)] font-[var(--bh-text-body-md-regular-font-weight)] leading-[var(--bh-text-body-md-regular-line-height)] tracking-[var(--bh-text-body-md-regular-letter-spacing)] text-[var(--bh-content-default)]",
988
+ "hover:bg-[var(--bh-interactive-ghost-hover)] focus-visible:bg-[var(--bh-interactive-ghost-hover)] disabled:text-[var(--bh-content-disabled)]",
989
+ item.selected && "bg-[var(--bh-interactive-ghost-hover)]"
990
+ )}
991
+ data-selected={item.selected ? "true" : undefined}
992
+ data-slot="calendar-month-year-item"
993
+ disabled={item.disabled}
994
+ key={item.id || index}
995
+ type="button"
996
+ >
997
+ <span className="truncate">{item.month}</span>
998
+ <span className="truncate">{item.year}</span>
999
+ </button>
1000
+ ))}
1001
+ </div>
1002
+ )
1003
+ }
1004
+
1005
+ type CalendarCell = {
1006
+ date: Date
1007
+ weekday: number
1008
+ }
1009
+
1010
+ function getCalendarCells(month: Date): CalendarCell[] {
1011
+ const first = startOfMonth(month)
1012
+ const start = addDays(first, -first.getDay())
1013
+ const last = new Date(first.getFullYear(), first.getMonth() + 1, 0)
1014
+ const end = addDays(last, 6 - last.getDay())
1015
+ const dayCount = differenceInCalendarDays(end, start) + 1
1016
+
1017
+ return Array.from({ length: dayCount }, (_, index) => {
1018
+ const date = addDays(start, index)
1019
+ return {
1020
+ date,
1021
+ weekday: date.getDay(),
1022
+ }
1023
+ })
1024
+ }
1025
+
1026
+ function getNextRange(currentRange: CalendarRange, date: Date): CalendarRange {
1027
+ const nextDate = startOfDay(date)
1028
+ const from = currentRange.from ? startOfDay(currentRange.from) : undefined
1029
+ const to = currentRange.to ? startOfDay(currentRange.to) : undefined
1030
+
1031
+ if (!from || to) {
1032
+ return { from: nextDate, to: undefined }
1033
+ }
1034
+
1035
+ if (isBeforeDay(nextDate, from)) {
1036
+ return { from: nextDate, to: from }
1037
+ }
1038
+
1039
+ return { from, to: nextDate }
1040
+ }
1041
+
1042
+ function normalizeRange(range: CalendarRange): CalendarRange {
1043
+ if (!range.from || !range.to) return range
1044
+
1045
+ return isBeforeDay(range.to, range.from)
1046
+ ? { from: range.to, to: range.from }
1047
+ : range
1048
+ }
1049
+
1050
+ function formatCalendarRange(range: CalendarRange) {
1051
+ if (!range.from && !range.to) return undefined
1052
+ if (range.from && !range.to) {
1053
+ return `${formatCalendarDate(range.from, { month: "short" })} -`
1054
+ }
1055
+ if (!range.from && range.to) {
1056
+ return `- ${formatCalendarDate(range.to, { month: "short" })}`
1057
+ }
1058
+
1059
+ return `${formatCalendarDate(range.from, {
1060
+ month: "short",
1061
+ })} - ${formatCalendarDate(range.to, { month: "short" })}`
1062
+ }
1063
+
1064
+ function formatCalendarDate(date: Date | undefined, options?: { month?: "short" | "long" }) {
1065
+ if (!date) return ""
1066
+ const month = date.toLocaleString("en", { month: options?.month ?? "short" })
1067
+ return `${month} ${date.getDate()}`
1068
+ }
1069
+
1070
+ function formatMonthTitle(date: Date) {
1071
+ return `${date.toLocaleString("en", { month: "long" })} ${date.getFullYear()}`
1072
+ }
1073
+
1074
+ function startOfDay(date: Date) {
1075
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate())
1076
+ }
1077
+
1078
+ function startOfMonth(date: Date) {
1079
+ return new Date(date.getFullYear(), date.getMonth(), 1)
1080
+ }
1081
+
1082
+ function addDays(date: Date, amount: number) {
1083
+ const next = new Date(date)
1084
+ next.setDate(next.getDate() + amount)
1085
+ return startOfDay(next)
1086
+ }
1087
+
1088
+ function addMonths(date: Date, amount: number) {
1089
+ return new Date(date.getFullYear(), date.getMonth() + amount, 1)
1090
+ }
1091
+
1092
+ function differenceInCalendarDays(left: Date, right: Date) {
1093
+ const dayLength = 24 * 60 * 60 * 1000
1094
+ return Math.round((startOfDay(left).getTime() - startOfDay(right).getTime()) / dayLength)
1095
+ }
1096
+
1097
+ function isSameDay(left: Date, right: Date) {
1098
+ return startOfDay(left).getTime() === startOfDay(right).getTime()
1099
+ }
1100
+
1101
+ function isBeforeDay(left: Date, right: Date) {
1102
+ return startOfDay(left).getTime() < startOfDay(right).getTime()
1103
+ }
1104
+
1105
+ function isAfterOrSameDay(left: Date, right: Date) {
1106
+ return startOfDay(left).getTime() >= startOfDay(right).getTime()
1107
+ }
1108
+
1109
+ function isBeforeOrSameDay(left: Date, right: Date) {
1110
+ return startOfDay(left).getTime() <= startOfDay(right).getTime()
1111
+ }
1112
+
1113
+ function hasRenderableContent(content: React.ReactNode) {
1114
+ return (
1115
+ content !== undefined &&
1116
+ content !== null &&
1117
+ content !== false &&
1118
+ content !== ""
1119
+ )
1120
+ }
1121
+
1122
+ export {
1123
+ Calendar,
1124
+ DatePicker,
1125
+ DatePickerTrigger,
1126
+ DateRangePicker,
1127
+ RangeCalendar,
1128
+ calendarSurface,
1129
+ pickerTrigger,
1130
+ }
1131
+ export type {
1132
+ CalendarMonthYearItem,
1133
+ CalendarPreset,
1134
+ CalendarProps,
1135
+ CalendarRange,
1136
+ CalendarTimeValue,
1137
+ CalendarView,
1138
+ DatePickerDirection,
1139
+ DatePickerProps,
1140
+ DateRangePickerProps,
1141
+ RangeCalendarProps,
1142
+ RangeCalendarType,
1143
+ }