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,799 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as DialogPrimitive from "@radix-ui/react-dialog"
5
+ import { cva, type VariantProps } from "class-variance-authority"
6
+ import { InfoIcon, TriangleAlertIcon, XIcon } from "lucide-react"
7
+
8
+ import { Button, type ButtonProps } from "@/components/ui/button"
9
+ import { Checkbox, type CheckboxProps } from "@/components/ui/checkbox"
10
+ import { Spinner } from "@/components/ui/spinner"
11
+ import { cn } from "@/lib/utils"
12
+
13
+ type ModalSize = "sm" | "default" | "lg"
14
+ type ModalIntent = "default" | "warning" | "danger"
15
+
16
+ const modalSurfaceBaseClassName = [
17
+ "flex max-h-[calc(100vh-(var(--bh-space-8xl-48)*2))] w-[var(--bh-modal-width)] max-w-[calc(100vw-(var(--bh-space-4xl-20)*2))] flex-col overflow-hidden rounded-[var(--bh-radius-2xl-12)] bg-[var(--bh-bg-raised)] text-[var(--bh-content-default)] shadow-[var(--shadow-modal)] outline-none",
18
+ "[--bh-modal-border:var(--bh-border-subtle)] [--shadow-modal:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-modal-border),var(--shadow-component-default)]",
19
+ ]
20
+
21
+ const modalSizeVariants = {
22
+ sm: "[--bh-modal-width:calc(var(--bh-space-19xl-384)+var(--bh-space-8xl-48)+var(--bh-space-md-8))]",
23
+ default:
24
+ "[--bh-modal-width:calc(var(--bh-space-19xl-384)+var(--bh-space-12xl-128)+var(--bh-space-8xl-48))]",
25
+ lg: "[--bh-modal-width:calc(var(--bh-space-19xl-384)+var(--bh-space-16xl-256)+var(--bh-space-9xl-64)-var(--bh-space-xs-4))]",
26
+ } as const
27
+
28
+ const modalSurfaceVariants = cva(modalSurfaceBaseClassName, {
29
+ variants: {
30
+ size: modalSizeVariants,
31
+ },
32
+ defaultVariants: {
33
+ size: "sm",
34
+ },
35
+ })
36
+
37
+ const modalContentVariants = cva(
38
+ [
39
+ "fixed left-1/2 top-1/2 z-[var(--bh-z-overlay)] -translate-x-1/2 -translate-y-1/2",
40
+ ...modalSurfaceBaseClassName,
41
+ ],
42
+ {
43
+ variants: {
44
+ size: modalSizeVariants,
45
+ },
46
+ defaultVariants: {
47
+ size: "sm",
48
+ },
49
+ }
50
+ )
51
+
52
+ const modalHeaderIconIntentClassNames: Record<ModalIntent, string> = {
53
+ default: "text-[var(--bh-content-subtle)]",
54
+ danger:
55
+ "rounded-[var(--bh-radius-full)] bg-[var(--bh-bg-danger-subtle)] text-[var(--bh-content-danger-default)]",
56
+ warning:
57
+ "rounded-[var(--bh-radius-full)] bg-[var(--bh-bg-warning-subtle)] text-[var(--bh-content-warning-default)]",
58
+ }
59
+
60
+ type ModalRootProps = React.ComponentProps<typeof DialogPrimitive.Root>
61
+ type ModalContentProps = React.ComponentPropsWithoutRef<
62
+ typeof DialogPrimitive.Content
63
+ > &
64
+ VariantProps<typeof modalContentVariants> & {
65
+ overlayClassName?: string
66
+ }
67
+ type ModalOverlayProps = React.ComponentPropsWithoutRef<
68
+ typeof DialogPrimitive.Overlay
69
+ >
70
+ type ModalSurfaceProps = React.ComponentProps<"section"> &
71
+ VariantProps<typeof modalSurfaceVariants>
72
+ type ModalHeaderProps = React.ComponentProps<"header">
73
+ type ModalBodyProps = React.ComponentProps<"div">
74
+ type ModalFooterProps = React.ComponentProps<"footer">
75
+ type ModalActionsProps = React.ComponentProps<"div">
76
+ type ModalHeaderIconProps = React.ComponentProps<"span"> & {
77
+ intent?: ModalIntent
78
+ }
79
+ type ModalCloseButtonProps = React.ComponentProps<"button"> & {
80
+ label?: string
81
+ }
82
+ type ModalFooterCheckboxProps = Omit<React.ComponentProps<"div">, "children"> & {
83
+ checked?: CheckboxProps["checked"]
84
+ defaultChecked?: CheckboxProps["defaultChecked"]
85
+ disabled?: CheckboxProps["disabled"]
86
+ label?: React.ReactNode
87
+ onCheckedChange?: CheckboxProps["onCheckedChange"]
88
+ }
89
+ type ModalActionButtonProps = ButtonProps
90
+
91
+ type ModalProps = Omit<ModalRootProps, "children"> &
92
+ VariantProps<typeof modalContentVariants> & {
93
+ checkboxLabel?: React.ReactNode
94
+ children?: React.ReactNode
95
+ className?: string
96
+ description?: React.ReactNode
97
+ dir?: "ltr" | "rtl"
98
+ footer?: React.ReactNode
99
+ overlayClassName?: string
100
+ primaryActionLabel?: React.ReactNode
101
+ secondaryActionLabel?: React.ReactNode
102
+ showCheckbox?: boolean
103
+ showCloseButton?: boolean
104
+ showDivider?: boolean
105
+ showFooter?: boolean
106
+ showIcon?: boolean
107
+ title?: React.ReactNode
108
+ }
109
+
110
+ type ConfirmModalProps = Omit<
111
+ ModalProps,
112
+ | "checkboxLabel"
113
+ | "footer"
114
+ | "primaryActionLabel"
115
+ | "secondaryActionLabel"
116
+ | "showCheckbox"
117
+ > & {
118
+ cancelButtonProps?: ModalActionButtonProps
119
+ cancelLabel?: React.ReactNode
120
+ closeOnCancel?: boolean
121
+ closeOnConfirm?: boolean
122
+ confirmButtonProps?: ModalActionButtonProps
123
+ confirmDisabled?: boolean
124
+ confirmLabel?: React.ReactNode
125
+ confirmLoading?: boolean
126
+ confirmLoadingLabel?: React.ReactNode
127
+ confirmVariant?: ModalActionButtonProps["variant"]
128
+ intent?: ModalIntent
129
+ onCancel?: React.MouseEventHandler<HTMLButtonElement>
130
+ onConfirm?: React.MouseEventHandler<HTMLButtonElement>
131
+ onEscapeKeyDown?: ModalContentProps["onEscapeKeyDown"]
132
+ onInteractOutside?: ModalContentProps["onInteractOutside"]
133
+ onOpenAutoFocus?: ModalContentProps["onOpenAutoFocus"]
134
+ onPointerDownOutside?: ModalContentProps["onPointerDownOutside"]
135
+ preventEscapeDismiss?: boolean
136
+ preventOutsideDismiss?: boolean
137
+ showCancelButton?: boolean
138
+ }
139
+
140
+ const defaultModalTitle = "Modal Title"
141
+ const defaultModalDescription =
142
+ "Are you sure you want to proceed with this action? Please review your choices before confirming."
143
+ const defaultModalCheckboxLabel = "Don't show me again"
144
+ const defaultConfirmTitle = "Confirm action"
145
+ const defaultConfirmDescription =
146
+ "This action may affect saved data. Review the details before confirming."
147
+ const defaultConfirmCancelLabel = "Cancel"
148
+ const defaultConfirmActionLabel = "Confirm"
149
+ const defaultConfirmLoadingLabel = "Confirming"
150
+ const defaultRtlModalTitle = "\u0639\u0646\u0648\u0627\u0646 \u0627\u0644\u0646\u0645\u0648\u0630\u062c"
151
+ const defaultRtlModalDescription =
152
+ "\u0647\u0644 \u0623\u0646\u062a \u0645\u062a\u0623\u0643\u062f \u0623\u0646\u0643 \u062a\u0631\u064a\u062f \u0627\u0644\u0645\u062a\u0627\u0628\u0639\u0629 \u0645\u0639 \u0647\u0630\u0627 \u0627\u0644\u0625\u062c\u0631\u0627\u0621\u061f \u064a\u0631\u062c\u0649 \u0645\u0631\u0627\u062c\u0639\u0629 \u062e\u064a\u0627\u0631\u0627\u062a\u0643 \u0642\u0628\u0644 \u0627\u0644\u062a\u0623\u0643\u064a\u062f."
153
+ const defaultRtlModalCheckboxLabel = "\u0627\u0644\u062a\u0633\u0645\u064a\u0629"
154
+ const defaultRtlActionLabel = "\u0627\u0646\u0642\u0631 \u0647\u0646\u0627"
155
+ const defaultRtlConfirmTitle = "\u062a\u0623\u0643\u064a\u062f \u0627\u0644\u0625\u062c\u0631\u0627\u0621"
156
+ const defaultRtlConfirmDescription =
157
+ "\u0642\u062f \u064a\u0624\u062b\u0631 \u0647\u0630\u0627 \u0627\u0644\u0625\u062c\u0631\u0627\u0621 \u0639\u0644\u0649 \u0627\u0644\u0628\u064a\u0627\u0646\u0627\u062a \u0627\u0644\u0645\u062d\u0641\u0648\u0638\u0629. \u0631\u0627\u062c\u0639 \u0627\u0644\u062a\u0641\u0627\u0635\u064a\u0644 \u0642\u0628\u0644 \u0627\u0644\u062a\u0623\u0643\u064a\u062f."
158
+ const defaultRtlConfirmCancelLabel = "\u0625\u0644\u063a\u0627\u0621"
159
+ const defaultRtlConfirmActionLabel = "\u062a\u0623\u0643\u064a\u062f"
160
+ const defaultRtlConfirmLoadingLabel = "\u062c\u0627\u0631\u064a \u0627\u0644\u062a\u0623\u0643\u064a\u062f"
161
+
162
+ const ModalRoot = DialogPrimitive.Root
163
+ const ModalTrigger = DialogPrimitive.Trigger
164
+ const ModalPortal = DialogPrimitive.Portal
165
+ const ModalClose = DialogPrimitive.Close
166
+
167
+ const ModalOverlay = React.forwardRef<
168
+ React.ElementRef<typeof DialogPrimitive.Overlay>,
169
+ ModalOverlayProps
170
+ >(function ModalOverlay({ className, ...props }, ref) {
171
+ return (
172
+ <DialogPrimitive.Overlay
173
+ data-slot="modal-overlay"
174
+ ref={ref}
175
+ className={cn("fixed inset-0 z-[var(--bh-z-overlay)] bg-[var(--bh-bg-backdrop)]", className)}
176
+ {...props}
177
+ />
178
+ )
179
+ })
180
+
181
+ const ModalContent = React.forwardRef<
182
+ React.ElementRef<typeof DialogPrimitive.Content>,
183
+ ModalContentProps
184
+ >(function ModalContent(
185
+ { children, className, overlayClassName, size, ...props },
186
+ ref
187
+ ) {
188
+ return (
189
+ <ModalPortal>
190
+ <ModalOverlay className={overlayClassName} />
191
+ <DialogPrimitive.Content
192
+ data-size={size ?? "sm"}
193
+ data-slot="modal-content"
194
+ ref={ref}
195
+ className={cn(modalContentVariants({ size, className }))}
196
+ {...props}
197
+ >
198
+ {children}
199
+ </DialogPrimitive.Content>
200
+ </ModalPortal>
201
+ )
202
+ })
203
+
204
+ const ModalSurface = React.forwardRef<HTMLElement, ModalSurfaceProps>(
205
+ function ModalSurface({ className, size, ...props }, ref) {
206
+ return (
207
+ <section
208
+ data-size={size ?? "sm"}
209
+ data-slot="modal-surface"
210
+ ref={ref}
211
+ className={cn(modalSurfaceVariants({ size, className }))}
212
+ {...props}
213
+ />
214
+ )
215
+ }
216
+ )
217
+
218
+ const ModalHeader = React.forwardRef<HTMLElement, ModalHeaderProps>(function ModalHeader({
219
+ className,
220
+ ...props
221
+ }, ref) {
222
+ return (
223
+ <header
224
+ data-slot="modal-header"
225
+ ref={ref}
226
+ className={cn(
227
+ "relative flex w-full shrink-0 items-center gap-[var(--bh-space-sm-6)] px-[var(--bh-space-4xl-20)] pb-[var(--bh-space-md-8)] pe-[var(--bh-space-9xl-64)] pt-[var(--bh-space-4xl-20)] text-start",
228
+ className
229
+ )}
230
+ {...props}
231
+ />
232
+ )
233
+ })
234
+
235
+ const ModalTitle = React.forwardRef<
236
+ React.ElementRef<typeof DialogPrimitive.Title>,
237
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
238
+ >(function ModalTitle({ className, dir = "auto", ...props }, ref) {
239
+ return (
240
+ <DialogPrimitive.Title
241
+ data-slot="modal-title"
242
+ dir={dir}
243
+ ref={ref}
244
+ className={cn(
245
+ "min-w-0 flex-1 text-start text-[length:var(--bh-text-heading-sm-semibold-font-size)] font-[var(--bh-text-heading-sm-semibold-font-weight)] leading-[var(--bh-text-heading-sm-semibold-line-height)] tracking-[var(--bh-text-heading-sm-semibold-letter-spacing)] text-[var(--bh-content-default)]",
246
+ className
247
+ )}
248
+ {...props}
249
+ />
250
+ )
251
+ })
252
+
253
+ const ModalDescription = React.forwardRef<
254
+ React.ElementRef<typeof DialogPrimitive.Description>,
255
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
256
+ >(function ModalDescription({ className, dir = "auto", ...props }, ref) {
257
+ return (
258
+ <DialogPrimitive.Description
259
+ data-slot="modal-description"
260
+ dir={dir}
261
+ ref={ref}
262
+ className={cn(
263
+ "min-w-0 flex-1 text-start 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)]",
264
+ className
265
+ )}
266
+ {...props}
267
+ />
268
+ )
269
+ })
270
+
271
+ const ModalBody = React.forwardRef<HTMLDivElement, ModalBodyProps>(function ModalBody({
272
+ className,
273
+ ...props
274
+ }, ref) {
275
+ return (
276
+ <div
277
+ data-slot="modal-body"
278
+ ref={ref}
279
+ className={cn(
280
+ "flex w-full shrink-0 items-start px-[var(--bh-space-4xl-20)] pb-[var(--bh-space-4xl-20)] pt-[var(--bh-space-lg-10)]",
281
+ className
282
+ )}
283
+ {...props}
284
+ />
285
+ )
286
+ })
287
+
288
+ const ModalDivider = React.forwardRef<HTMLDivElement, React.ComponentProps<"div">>(
289
+ function ModalDivider({ className, role = "separator", ...props }, ref) {
290
+ return (
291
+ <div
292
+ data-slot="modal-divider"
293
+ ref={ref}
294
+ role={role}
295
+ className={cn("w-full border-t border-[var(--bh-border-subtle)]", className)}
296
+ {...props}
297
+ />
298
+ )
299
+ }
300
+ )
301
+
302
+ const ModalFooter = React.forwardRef<HTMLElement, ModalFooterProps>(function ModalFooter({
303
+ className,
304
+ ...props
305
+ }, ref) {
306
+ return (
307
+ <footer
308
+ data-slot="modal-footer"
309
+ ref={ref}
310
+ className={cn(
311
+ "flex w-full shrink-0 items-center justify-end gap-[var(--bh-space-lg-10)] px-[var(--bh-space-4xl-20)] py-[var(--bh-space-3xl-16)]",
312
+ className
313
+ )}
314
+ {...props}
315
+ />
316
+ )
317
+ })
318
+
319
+ function ModalHeaderIcon({
320
+ children,
321
+ className,
322
+ intent = "default",
323
+ ...props
324
+ }: ModalHeaderIconProps) {
325
+ return (
326
+ <span
327
+ aria-hidden="true"
328
+ data-intent={intent}
329
+ data-slot="modal-header-icon"
330
+ className={cn(
331
+ "inline-flex size-[var(--bh-space-5xl-24)] shrink-0 items-center justify-center",
332
+ modalHeaderIconIntentClassNames[intent],
333
+ className
334
+ )}
335
+ {...props}
336
+ >
337
+ {children ?? (
338
+ <InfoIcon className="size-[var(--bh-space-3xl-16)]" strokeWidth={2} />
339
+ )}
340
+ </span>
341
+ )
342
+ }
343
+
344
+ const ModalCloseButton = React.forwardRef<HTMLButtonElement, ModalCloseButtonProps>(
345
+ function ModalCloseButton(
346
+ { children, className, label = "Close modal", type = "button", ...props },
347
+ ref
348
+ ) {
349
+ return (
350
+ <ModalClose asChild>
351
+ <button
352
+ aria-label={label}
353
+ data-slot="modal-close"
354
+ ref={ref}
355
+ type={type}
356
+ className={cn(
357
+ "absolute end-[var(--bh-space-3xl-16)] top-1/2 inline-flex size-[var(--bh-space-6xl-32)] -translate-y-1/2 items-center justify-center rounded-[var(--bh-radius-full)] border-0 bg-transparent p-0 text-[var(--bh-content-subtle)] outline-none transition-[background-color,box-shadow,color] hover:bg-[var(--bh-interactive-ghost-hover)] hover:text-[var(--bh-content-default)] focus-visible:shadow-[var(--shadow-button-focus)]",
358
+ className
359
+ )}
360
+ {...props}
361
+ >
362
+ {children ?? (
363
+ <XIcon
364
+ aria-hidden="true"
365
+ className="size-[var(--bh-space-5xl-24)]"
366
+ strokeWidth={2}
367
+ />
368
+ )}
369
+ </button>
370
+ </ModalClose>
371
+ )
372
+ }
373
+ )
374
+
375
+ function ModalFooterCheckbox({
376
+ checked,
377
+ className,
378
+ defaultChecked,
379
+ disabled,
380
+ label = defaultModalCheckboxLabel,
381
+ onCheckedChange,
382
+ ...props
383
+ }: ModalFooterCheckboxProps) {
384
+ const generatedId = React.useId()
385
+ const labelId = `${generatedId}-label`
386
+
387
+ return (
388
+ <div
389
+ data-slot="modal-checkbox"
390
+ className={cn(
391
+ "flex min-w-0 flex-1 items-center gap-[var(--bh-space-md-8)] text-start",
392
+ className
393
+ )}
394
+ {...props}
395
+ >
396
+ <Checkbox
397
+ aria-labelledby={labelId}
398
+ checked={checked}
399
+ defaultChecked={defaultChecked}
400
+ disabled={disabled}
401
+ onCheckedChange={onCheckedChange}
402
+ />
403
+ <span
404
+ data-slot="modal-checkbox-label"
405
+ dir="auto"
406
+ id={labelId}
407
+ className="min-w-0 flex-1 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)]"
408
+ >
409
+ {label}
410
+ </span>
411
+ </div>
412
+ )
413
+ }
414
+
415
+ function ModalActions({ className, ...props }: ModalActionsProps) {
416
+ return (
417
+ <div
418
+ data-slot="modal-actions"
419
+ className={cn(
420
+ "flex shrink-0 items-center justify-end gap-[var(--bh-space-xs-4)]",
421
+ className
422
+ )}
423
+ {...props}
424
+ />
425
+ )
426
+ }
427
+
428
+ const ModalActionButton = React.forwardRef<HTMLButtonElement, ModalActionButtonProps>(
429
+ function ModalActionButton(
430
+ { children = "Action", size = "sm", type = "button", variant = "default", ...props },
431
+ ref
432
+ ) {
433
+ return (
434
+ <Button
435
+ data-slot="modal-action"
436
+ ref={ref}
437
+ size={size}
438
+ type={type}
439
+ variant={variant}
440
+ {...props}
441
+ >
442
+ {children}
443
+ </Button>
444
+ )
445
+ }
446
+ )
447
+
448
+ function Modal({
449
+ checkboxLabel,
450
+ children,
451
+ className,
452
+ defaultOpen = true,
453
+ description,
454
+ dir,
455
+ footer,
456
+ modal = true,
457
+ onOpenChange,
458
+ open,
459
+ overlayClassName,
460
+ primaryActionLabel,
461
+ secondaryActionLabel,
462
+ showCheckbox = true,
463
+ showCloseButton = true,
464
+ showDivider = true,
465
+ showFooter = true,
466
+ showIcon = true,
467
+ size = "sm",
468
+ title,
469
+ ...props
470
+ }: ModalProps) {
471
+ const isRtl = dir === "rtl"
472
+ const rootProps =
473
+ open === undefined
474
+ ? { defaultOpen, modal, onOpenChange }
475
+ : { modal, onOpenChange, open }
476
+ const resolvedTitle = title ?? (isRtl ? defaultRtlModalTitle : defaultModalTitle)
477
+ const resolvedDescription =
478
+ description ?? (isRtl ? defaultRtlModalDescription : defaultModalDescription)
479
+ const resolvedCheckboxLabel =
480
+ checkboxLabel ?? (isRtl ? defaultRtlModalCheckboxLabel : defaultModalCheckboxLabel)
481
+ const resolvedPrimaryAction =
482
+ primaryActionLabel ?? (isRtl ? defaultRtlActionLabel : "Action")
483
+ const resolvedSecondaryAction =
484
+ secondaryActionLabel ?? (isRtl ? defaultRtlActionLabel : "Action")
485
+
486
+ return (
487
+ <ModalRoot {...rootProps}>
488
+ <ModalContent
489
+ data-slot="modal"
490
+ dir={dir}
491
+ overlayClassName={overlayClassName}
492
+ size={size}
493
+ className={className}
494
+ {...props}
495
+ >
496
+ <ModalHeader>
497
+ {showIcon ? <ModalHeaderIcon /> : null}
498
+ <ModalTitle>{resolvedTitle}</ModalTitle>
499
+ {showCloseButton ? <ModalCloseButton /> : null}
500
+ </ModalHeader>
501
+ <ModalBody>
502
+ {children ?? <ModalDescription>{resolvedDescription}</ModalDescription>}
503
+ </ModalBody>
504
+ {showDivider ? <ModalDivider /> : null}
505
+ {showFooter ? (
506
+ footer ?? (
507
+ <ModalFooter>
508
+ {showCheckbox ? (
509
+ <ModalFooterCheckbox label={resolvedCheckboxLabel} />
510
+ ) : null}
511
+ <ModalActions>
512
+ <ModalActionButton variant="soft">
513
+ {resolvedSecondaryAction}
514
+ </ModalActionButton>
515
+ <ModalActionButton>{resolvedPrimaryAction}</ModalActionButton>
516
+ </ModalActions>
517
+ </ModalFooter>
518
+ )
519
+ ) : null}
520
+ </ModalContent>
521
+ </ModalRoot>
522
+ )
523
+ }
524
+
525
+ function ConfirmModal({
526
+ cancelButtonProps,
527
+ cancelLabel,
528
+ children,
529
+ className,
530
+ closeOnCancel = true,
531
+ closeOnConfirm = true,
532
+ confirmButtonProps,
533
+ confirmDisabled = false,
534
+ confirmLabel,
535
+ confirmLoading = false,
536
+ confirmLoadingLabel,
537
+ confirmVariant,
538
+ defaultOpen = true,
539
+ description,
540
+ dir,
541
+ intent = "danger",
542
+ modal = true,
543
+ onCancel,
544
+ onConfirm,
545
+ onEscapeKeyDown,
546
+ onInteractOutside,
547
+ onOpenAutoFocus,
548
+ onOpenChange,
549
+ onPointerDownOutside,
550
+ open,
551
+ overlayClassName,
552
+ preventEscapeDismiss = false,
553
+ preventOutsideDismiss = true,
554
+ showCancelButton = true,
555
+ showCloseButton = false,
556
+ showDivider = true,
557
+ showFooter = true,
558
+ showIcon = true,
559
+ size = "sm",
560
+ title,
561
+ ...props
562
+ }: ConfirmModalProps) {
563
+ const cancelButtonRef = React.useRef<HTMLButtonElement>(null)
564
+ const confirmButtonRef = React.useRef<HTMLButtonElement>(null)
565
+ const isRtl = dir === "rtl"
566
+ const rootProps =
567
+ open === undefined
568
+ ? { defaultOpen, modal, onOpenChange }
569
+ : { modal, onOpenChange, open }
570
+ const resolvedTitle = title ?? (isRtl ? defaultRtlConfirmTitle : defaultConfirmTitle)
571
+ const resolvedDescription =
572
+ description ?? (isRtl ? defaultRtlConfirmDescription : defaultConfirmDescription)
573
+ const resolvedCancelLabel =
574
+ cancelLabel ?? (isRtl ? defaultRtlConfirmCancelLabel : defaultConfirmCancelLabel)
575
+ const resolvedConfirmLabel =
576
+ confirmLabel ?? (isRtl ? defaultRtlConfirmActionLabel : defaultConfirmActionLabel)
577
+ const resolvedConfirmLoadingLabel =
578
+ confirmLoadingLabel ??
579
+ (isRtl ? defaultRtlConfirmLoadingLabel : defaultConfirmLoadingLabel)
580
+ const {
581
+ children: cancelButtonChildren,
582
+ onClick: cancelButtonOnClick,
583
+ variant: cancelButtonVariant = "soft",
584
+ ...cancelActionProps
585
+ } = cancelButtonProps ?? {}
586
+ const {
587
+ children: confirmButtonChildren,
588
+ disabled: confirmButtonDisabled,
589
+ onClick: confirmButtonOnClick,
590
+ variant: confirmButtonVariant = confirmVariant ?? getConfirmActionVariant(intent),
591
+ ...confirmActionProps
592
+ } = confirmButtonProps ?? {}
593
+ const isConfirmDisabled = confirmDisabled || confirmLoading || confirmButtonDisabled
594
+
595
+ const handleOpenAutoFocus: NonNullable<ModalContentProps["onOpenAutoFocus"]> =
596
+ React.useCallback(
597
+ (event) => {
598
+ onOpenAutoFocus?.(event)
599
+ if (event.defaultPrevented) return
600
+
601
+ const focusTarget = showCancelButton
602
+ ? cancelButtonRef.current
603
+ : confirmButtonRef.current
604
+ if (!focusTarget) return
605
+
606
+ event.preventDefault()
607
+ focusTarget.focus()
608
+ },
609
+ [onOpenAutoFocus, showCancelButton]
610
+ )
611
+
612
+ const handlePointerDownOutside: NonNullable<
613
+ ModalContentProps["onPointerDownOutside"]
614
+ > = React.useCallback(
615
+ (event) => {
616
+ onPointerDownOutside?.(event)
617
+ if (!event.defaultPrevented && preventOutsideDismiss) {
618
+ event.preventDefault()
619
+ }
620
+ },
621
+ [onPointerDownOutside, preventOutsideDismiss]
622
+ )
623
+
624
+ const handleInteractOutside: NonNullable<ModalContentProps["onInteractOutside"]> =
625
+ React.useCallback(
626
+ (event) => {
627
+ onInteractOutside?.(event)
628
+ if (!event.defaultPrevented && preventOutsideDismiss) {
629
+ event.preventDefault()
630
+ }
631
+ },
632
+ [onInteractOutside, preventOutsideDismiss]
633
+ )
634
+
635
+ const handleEscapeKeyDown: NonNullable<ModalContentProps["onEscapeKeyDown"]> =
636
+ React.useCallback(
637
+ (event) => {
638
+ onEscapeKeyDown?.(event)
639
+ if (!event.defaultPrevented && preventEscapeDismiss) {
640
+ event.preventDefault()
641
+ }
642
+ },
643
+ [onEscapeKeyDown, preventEscapeDismiss]
644
+ )
645
+
646
+ const handleCancel: React.MouseEventHandler<HTMLButtonElement> = (event) => {
647
+ cancelButtonOnClick?.(event)
648
+ if (!event.defaultPrevented) {
649
+ onCancel?.(event)
650
+ }
651
+ }
652
+
653
+ const handleConfirm: React.MouseEventHandler<HTMLButtonElement> = (event) => {
654
+ confirmButtonOnClick?.(event)
655
+ if (!event.defaultPrevented) {
656
+ onConfirm?.(event)
657
+ }
658
+ }
659
+
660
+ const cancelAction = showCancelButton ? (
661
+ <ModalActionButton
662
+ ref={cancelButtonRef}
663
+ variant={cancelButtonVariant}
664
+ onClick={handleCancel}
665
+ {...cancelActionProps}
666
+ >
667
+ {cancelButtonChildren ?? resolvedCancelLabel}
668
+ </ModalActionButton>
669
+ ) : null
670
+ const confirmAction = (
671
+ <ModalActionButton
672
+ aria-busy={confirmLoading || undefined}
673
+ disabled={isConfirmDisabled}
674
+ ref={confirmButtonRef}
675
+ variant={confirmButtonVariant}
676
+ onClick={handleConfirm}
677
+ {...confirmActionProps}
678
+ >
679
+ {confirmButtonChildren ??
680
+ (confirmLoading ? (
681
+ <>
682
+ <Spinner className="size-[var(--bh-space-3xl-16)]" />
683
+ <span data-slot="button-label" dir="auto" className="min-w-0 px-[var(--bh-modal-action-label-padding-x)]">
684
+ {resolvedConfirmLoadingLabel}
685
+ </span>
686
+ </>
687
+ ) : (
688
+ resolvedConfirmLabel
689
+ ))}
690
+ </ModalActionButton>
691
+ )
692
+
693
+ return (
694
+ <ModalRoot {...rootProps}>
695
+ <ModalContent
696
+ data-intent={intent}
697
+ data-slot="confirm-modal"
698
+ dir={dir}
699
+ overlayClassName={overlayClassName}
700
+ role="alertdialog"
701
+ size={size}
702
+ className={className}
703
+ onEscapeKeyDown={handleEscapeKeyDown}
704
+ onInteractOutside={handleInteractOutside}
705
+ onOpenAutoFocus={handleOpenAutoFocus}
706
+ onPointerDownOutside={handlePointerDownOutside}
707
+ {...props}
708
+ >
709
+ <ModalHeader>
710
+ {showIcon ? (
711
+ <ModalHeaderIcon intent={intent}>
712
+ {intent === "default" ? (
713
+ <InfoIcon className="size-[var(--bh-space-3xl-16)]" strokeWidth={2} />
714
+ ) : (
715
+ <TriangleAlertIcon
716
+ className="size-[var(--bh-space-3xl-16)]"
717
+ strokeWidth={2}
718
+ />
719
+ )}
720
+ </ModalHeaderIcon>
721
+ ) : null}
722
+ <ModalTitle>{resolvedTitle}</ModalTitle>
723
+ {showCloseButton ? <ModalCloseButton /> : null}
724
+ </ModalHeader>
725
+ <ModalBody>
726
+ {children ?? <ModalDescription>{resolvedDescription}</ModalDescription>}
727
+ </ModalBody>
728
+ {showDivider ? <ModalDivider /> : null}
729
+ {showFooter ? (
730
+ <ModalFooter>
731
+ <ModalActions>
732
+ {cancelAction && closeOnCancel ? (
733
+ <ModalClose asChild>{cancelAction}</ModalClose>
734
+ ) : (
735
+ cancelAction
736
+ )}
737
+ {closeOnConfirm ? (
738
+ <ModalClose asChild>{confirmAction}</ModalClose>
739
+ ) : (
740
+ confirmAction
741
+ )}
742
+ </ModalActions>
743
+ </ModalFooter>
744
+ ) : null}
745
+ </ModalContent>
746
+ </ModalRoot>
747
+ )
748
+ }
749
+
750
+ function getConfirmActionVariant(
751
+ intent: ModalIntent
752
+ ): ModalActionButtonProps["variant"] {
753
+ if (intent === "danger") return "danger"
754
+ if (intent === "warning") return "warning"
755
+ return "default"
756
+ }
757
+
758
+ export {
759
+ ConfirmModal,
760
+ Modal,
761
+ ModalActionButton,
762
+ ModalActions,
763
+ ModalBody,
764
+ ModalClose,
765
+ ModalCloseButton,
766
+ ModalContent,
767
+ ModalDescription,
768
+ ModalDivider,
769
+ ModalFooter,
770
+ ModalFooterCheckbox,
771
+ ModalHeader,
772
+ ModalHeaderIcon,
773
+ ModalOverlay,
774
+ ModalPortal,
775
+ ModalRoot,
776
+ ModalSurface,
777
+ ModalTitle,
778
+ ModalTrigger,
779
+ modalContentVariants,
780
+ modalSurfaceVariants,
781
+ }
782
+ export type {
783
+ ConfirmModalProps,
784
+ ModalActionButtonProps,
785
+ ModalActionsProps,
786
+ ModalBodyProps,
787
+ ModalCloseButtonProps,
788
+ ModalContentProps,
789
+ ModalFooterCheckboxProps,
790
+ ModalFooterProps,
791
+ ModalHeaderIconProps,
792
+ ModalHeaderProps,
793
+ ModalIntent,
794
+ ModalOverlayProps,
795
+ ModalProps,
796
+ ModalRootProps,
797
+ ModalSize,
798
+ ModalSurfaceProps,
799
+ }