azamat-ui-kit-cli 0.2.2

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 (213) hide show
  1. package/README.md +8 -0
  2. package/dist/index.js +432 -0
  3. package/package.json +34 -0
  4. package/vendor/package.json +4 -0
  5. package/vendor/src/components/actions/action-bar.tsx +35 -0
  6. package/vendor/src/components/actions/action-menu.tsx +120 -0
  7. package/vendor/src/components/actions/button-group.tsx +47 -0
  8. package/vendor/src/components/actions/copy-button.tsx +91 -0
  9. package/vendor/src/components/actions/copy-field.tsx +31 -0
  10. package/vendor/src/components/actions/floating-action-button.tsx +33 -0
  11. package/vendor/src/components/actions/index.ts +7 -0
  12. package/vendor/src/components/actions/public.ts +5 -0
  13. package/vendor/src/components/actions/quick-action-grid.tsx +162 -0
  14. package/vendor/src/components/calendar/calendar.tsx +328 -0
  15. package/vendor/src/components/calendar/date-picker.tsx +78 -0
  16. package/vendor/src/components/calendar/date-range-picker.tsx +96 -0
  17. package/vendor/src/components/calendar/date-utils.ts +89 -0
  18. package/vendor/src/components/calendar/index.ts +4 -0
  19. package/vendor/src/components/charts/charts.tsx +275 -0
  20. package/vendor/src/components/charts/horizontal-bar-chart.tsx +46 -0
  21. package/vendor/src/components/charts/index.ts +4 -0
  22. package/vendor/src/components/charts/kpi.tsx +68 -0
  23. package/vendor/src/components/charts/progress-ring.tsx +45 -0
  24. package/vendor/src/components/charts/public.ts +1 -0
  25. package/vendor/src/components/command/command-palette.tsx +375 -0
  26. package/vendor/src/components/command/index.ts +1 -0
  27. package/vendor/src/components/data-table/data-table-actions-column.tsx +58 -0
  28. package/vendor/src/components/data-table/data-table-bulk-actions.tsx +84 -0
  29. package/vendor/src/components/data-table/data-table-column-visibility-menu.tsx +79 -0
  30. package/vendor/src/components/data-table/data-table-pagination.tsx +91 -0
  31. package/vendor/src/components/data-table/data-table-row-actions.tsx +48 -0
  32. package/vendor/src/components/data-table/data-table-select-column.tsx +59 -0
  33. package/vendor/src/components/data-table/data-table-sortable-header.tsx +45 -0
  34. package/vendor/src/components/data-table/data-table-toolbar.tsx +76 -0
  35. package/vendor/src/components/data-table/data-table-view-presets.tsx +128 -0
  36. package/vendor/src/components/data-table/data-table.tsx +507 -0
  37. package/vendor/src/components/data-table/index.ts +12 -0
  38. package/vendor/src/components/data-table/public.ts +10 -0
  39. package/vendor/src/components/data-table/table-export-menu.tsx +56 -0
  40. package/vendor/src/components/data-table/table-import-button.tsx +43 -0
  41. package/vendor/src/components/display/activity-feed.tsx +97 -0
  42. package/vendor/src/components/display/avatar.tsx +131 -0
  43. package/vendor/src/components/display/code-block.tsx +33 -0
  44. package/vendor/src/components/display/data-state.tsx +63 -0
  45. package/vendor/src/components/display/description-list.tsx +119 -0
  46. package/vendor/src/components/display/descriptions.tsx +83 -0
  47. package/vendor/src/components/display/entity-card.tsx +53 -0
  48. package/vendor/src/components/display/file-card.tsx +54 -0
  49. package/vendor/src/components/display/index.ts +30 -0
  50. package/vendor/src/components/display/kanban.tsx +104 -0
  51. package/vendor/src/components/display/keyboard-shortcut.tsx +31 -0
  52. package/vendor/src/components/display/list.tsx +100 -0
  53. package/vendor/src/components/display/metric-grid.tsx +86 -0
  54. package/vendor/src/components/display/progress.tsx +162 -0
  55. package/vendor/src/components/display/property-grid.tsx +54 -0
  56. package/vendor/src/components/display/result.tsx +90 -0
  57. package/vendor/src/components/display/smart-card.tsx +168 -0
  58. package/vendor/src/components/display/statistic.tsx +107 -0
  59. package/vendor/src/components/display/status-legend.tsx +108 -0
  60. package/vendor/src/components/display/tag-list.tsx +52 -0
  61. package/vendor/src/components/display/timeline.tsx +132 -0
  62. package/vendor/src/components/display/tree-view.tsx +116 -0
  63. package/vendor/src/components/feedback/alert.tsx +69 -0
  64. package/vendor/src/components/feedback/empty-state.tsx +56 -0
  65. package/vendor/src/components/feedback/index.ts +5 -0
  66. package/vendor/src/components/feedback/loading-state.tsx +39 -0
  67. package/vendor/src/components/feedback/page-state.tsx +69 -0
  68. package/vendor/src/components/feedback/status-badge.tsx +62 -0
  69. package/vendor/src/components/filters/filter-bar.tsx +89 -0
  70. package/vendor/src/components/filters/filter-chips.tsx +69 -0
  71. package/vendor/src/components/filters/index.ts +2 -0
  72. package/vendor/src/components/form/form-actions.tsx +53 -0
  73. package/vendor/src/components/form/form-async-select.tsx +26 -0
  74. package/vendor/src/components/form/form-date-input.tsx +19 -0
  75. package/vendor/src/components/form/form-date-picker.tsx +54 -0
  76. package/vendor/src/components/form/form-date-range-input.tsx +79 -0
  77. package/vendor/src/components/form/form-date-range-picker.tsx +57 -0
  78. package/vendor/src/components/form/form-field-shell.tsx +191 -0
  79. package/vendor/src/components/form/form-input.tsx +480 -0
  80. package/vendor/src/components/form/form-number-input.tsx +19 -0
  81. package/vendor/src/components/form/form-password-input.tsx +19 -0
  82. package/vendor/src/components/form/form-phone-input.tsx +22 -0
  83. package/vendor/src/components/form/form-search-input.tsx +19 -0
  84. package/vendor/src/components/form/form-section.tsx +29 -0
  85. package/vendor/src/components/form/form-select.tsx +194 -0
  86. package/vendor/src/components/form/form-switch.tsx +145 -0
  87. package/vendor/src/components/form/form-textarea.tsx +103 -0
  88. package/vendor/src/components/form/index.ts +17 -0
  89. package/vendor/src/components/form/public.ts +14 -0
  90. package/vendor/src/components/form/smart-form-shell.tsx +59 -0
  91. package/vendor/src/components/inputs/async-select.tsx +1143 -0
  92. package/vendor/src/components/inputs/clearable-input.tsx +78 -0
  93. package/vendor/src/components/inputs/color-input.tsx +47 -0
  94. package/vendor/src/components/inputs/combobox.tsx +89 -0
  95. package/vendor/src/components/inputs/date-input.tsx +32 -0
  96. package/vendor/src/components/inputs/date-range-input.tsx +67 -0
  97. package/vendor/src/components/inputs/index.ts +19 -0
  98. package/vendor/src/components/inputs/input-chrome.tsx +37 -0
  99. package/vendor/src/components/inputs/input-decorator.tsx +64 -0
  100. package/vendor/src/components/inputs/input-value.ts +42 -0
  101. package/vendor/src/components/inputs/masked-input.tsx +51 -0
  102. package/vendor/src/components/inputs/money-input.tsx +73 -0
  103. package/vendor/src/components/inputs/number-input.tsx +87 -0
  104. package/vendor/src/components/inputs/numeric-value.ts +39 -0
  105. package/vendor/src/components/inputs/otp-input.tsx +102 -0
  106. package/vendor/src/components/inputs/password-input.tsx +85 -0
  107. package/vendor/src/components/inputs/phone-input.tsx +46 -0
  108. package/vendor/src/components/inputs/quantity-input.tsx +116 -0
  109. package/vendor/src/components/inputs/quantity-stepper.tsx +49 -0
  110. package/vendor/src/components/inputs/rating.tsx +98 -0
  111. package/vendor/src/components/inputs/search-input.tsx +26 -0
  112. package/vendor/src/components/inputs/simple-select.tsx +72 -0
  113. package/vendor/src/components/inputs/slider.tsx +149 -0
  114. package/vendor/src/components/inputs/tag-input.tsx +104 -0
  115. package/vendor/src/components/layout/app-header.tsx +46 -0
  116. package/vendor/src/components/layout/app-shell.tsx +243 -0
  117. package/vendor/src/components/layout/app-sidebar.tsx +179 -0
  118. package/vendor/src/components/layout/breadcrumbs.tsx +72 -0
  119. package/vendor/src/components/layout/index.ts +11 -0
  120. package/vendor/src/components/layout/page-container.tsx +30 -0
  121. package/vendor/src/components/layout/page-header.tsx +60 -0
  122. package/vendor/src/components/layout/public.ts +10 -0
  123. package/vendor/src/components/layout/section.tsx +76 -0
  124. package/vendor/src/components/layout/sidebar-nav.tsx +147 -0
  125. package/vendor/src/components/layout/stat-card.tsx +88 -0
  126. package/vendor/src/components/layout/sticky-footer-bar.tsx +23 -0
  127. package/vendor/src/components/layout/workspace-shell.tsx +50 -0
  128. package/vendor/src/components/navigation/anchor-nav.tsx +44 -0
  129. package/vendor/src/components/navigation/index.ts +4 -0
  130. package/vendor/src/components/navigation/page-tabs.tsx +67 -0
  131. package/vendor/src/components/navigation/pagination.tsx +179 -0
  132. package/vendor/src/components/navigation/stepper-tabs.tsx +67 -0
  133. package/vendor/src/components/notifications/index.ts +1 -0
  134. package/vendor/src/components/notifications/toast.tsx +259 -0
  135. package/vendor/src/components/overlay/confirm-dialog.tsx +66 -0
  136. package/vendor/src/components/overlay/dialog-actions.tsx +68 -0
  137. package/vendor/src/components/overlay/index.ts +4 -0
  138. package/vendor/src/components/overlay/modal-shell.tsx +93 -0
  139. package/vendor/src/components/overlay/sheet-shell.tsx +212 -0
  140. package/vendor/src/components/patterns/action-system.tsx +116 -0
  141. package/vendor/src/components/patterns/crud-system.tsx +53 -0
  142. package/vendor/src/components/patterns/data-view.tsx +84 -0
  143. package/vendor/src/components/patterns/entity-details.tsx +66 -0
  144. package/vendor/src/components/patterns/filter-builder.tsx +113 -0
  145. package/vendor/src/components/patterns/form-builder-presets.ts +131 -0
  146. package/vendor/src/components/patterns/form-builder.tsx +334 -0
  147. package/vendor/src/components/patterns/index.ts +12 -0
  148. package/vendor/src/components/patterns/public.ts +4 -0
  149. package/vendor/src/components/patterns/resource-detail-page.tsx +160 -0
  150. package/vendor/src/components/patterns/resource-page.tsx +159 -0
  151. package/vendor/src/components/patterns/resource-system.tsx +61 -0
  152. package/vendor/src/components/patterns/settings-section.tsx +46 -0
  153. package/vendor/src/components/patterns/status-system.tsx +89 -0
  154. package/vendor/src/components/theme-provider.tsx +51 -0
  155. package/vendor/src/components/ui/badge.tsx +52 -0
  156. package/vendor/src/components/ui/button.tsx +61 -0
  157. package/vendor/src/components/ui/card.tsx +103 -0
  158. package/vendor/src/components/ui/checkbox.tsx +82 -0
  159. package/vendor/src/components/ui/collapse.tsx +126 -0
  160. package/vendor/src/components/ui/command.tsx +194 -0
  161. package/vendor/src/components/ui/dialog.tsx +160 -0
  162. package/vendor/src/components/ui/divider.tsx +46 -0
  163. package/vendor/src/components/ui/dropdown-menu.tsx +266 -0
  164. package/vendor/src/components/ui/input-group.tsx +158 -0
  165. package/vendor/src/components/ui/input.tsx +20 -0
  166. package/vendor/src/components/ui/popover.tsx +90 -0
  167. package/vendor/src/components/ui/segmented-control.tsx +78 -0
  168. package/vendor/src/components/ui/select.tsx +201 -0
  169. package/vendor/src/components/ui/skeleton.tsx +75 -0
  170. package/vendor/src/components/ui/spinner.tsx +50 -0
  171. package/vendor/src/components/ui/switch.tsx +71 -0
  172. package/vendor/src/components/ui/table.tsx +114 -0
  173. package/vendor/src/components/ui/tabs.tsx +55 -0
  174. package/vendor/src/components/ui/textarea.tsx +18 -0
  175. package/vendor/src/components/ui/tooltip.tsx +38 -0
  176. package/vendor/src/components/upload/file-upload.tsx +483 -0
  177. package/vendor/src/components/upload/image-upload.tsx +118 -0
  178. package/vendor/src/components/upload/index.ts +2 -0
  179. package/vendor/src/components/wizard/index.ts +2 -0
  180. package/vendor/src/components/wizard/stepper.tsx +53 -0
  181. package/vendor/src/components/wizard/wizard.tsx +60 -0
  182. package/vendor/src/families/card-family.ts +28 -0
  183. package/vendor/src/families/catalog.ts +96 -0
  184. package/vendor/src/families/data-table-family.ts +31 -0
  185. package/vendor/src/families/docs-adoption.ts +103 -0
  186. package/vendor/src/families/docs-groups.ts +209 -0
  187. package/vendor/src/families/docs-queries.ts +84 -0
  188. package/vendor/src/families/docs-routing.ts +89 -0
  189. package/vendor/src/families/form-family.ts +45 -0
  190. package/vendor/src/families/index.ts +17 -0
  191. package/vendor/src/families/input-family.ts +61 -0
  192. package/vendor/src/families/member-metadata.ts +466 -0
  193. package/vendor/src/families/member-queries.ts +28 -0
  194. package/vendor/src/families/member-snippet-queries.ts +54 -0
  195. package/vendor/src/families/member-snippets.ts +673 -0
  196. package/vendor/src/families/migration-map.ts +79 -0
  197. package/vendor/src/families/queries.ts +63 -0
  198. package/vendor/src/families/select-family.ts +33 -0
  199. package/vendor/src/families/views.ts +81 -0
  200. package/vendor/src/hooks/index.ts +6 -0
  201. package/vendor/src/hooks/use-before-unload-when-dirty.ts +21 -0
  202. package/vendor/src/hooks/use-data-table-view-state.ts +122 -0
  203. package/vendor/src/hooks/use-debounce.ts +52 -0
  204. package/vendor/src/hooks/use-disclosure.ts +38 -0
  205. package/vendor/src/hooks/use-is-mobile.ts +28 -0
  206. package/vendor/src/hooks/use-session-storage-state.ts +85 -0
  207. package/vendor/src/index.ts +38 -0
  208. package/vendor/src/lib/utils.ts +6 -0
  209. package/vendor/templates/components/button.tsx +0 -0
  210. package/vendor/templates/components/data-table.tsx +0 -0
  211. package/vendor/templates/components/input.tsx +0 -0
  212. package/vendor/templates/lib/utils.ts +0 -0
  213. package/vendor/templates/styles/globals.css +0 -0
@@ -0,0 +1,259 @@
1
+ import * as React from "react"
2
+ import { AlertCircleIcon, CheckCircle2Icon, InfoIcon, Loader2Icon, TriangleAlertIcon, XIcon } from "lucide-react"
3
+
4
+ import { Button } from "@/components/ui/button"
5
+ import { cn } from "@/lib/utils"
6
+
7
+ export type ToastTone = "default" | "success" | "info" | "warning" | "danger" | "loading"
8
+
9
+ export type ToastItem = {
10
+ id: string
11
+ title?: React.ReactNode
12
+ description?: React.ReactNode
13
+ tone?: ToastTone
14
+ action?: React.ReactNode
15
+ duration?: number
16
+ dismissible?: boolean
17
+ }
18
+
19
+ export type CreateToastInput = Omit<ToastItem, "id"> & {
20
+ id?: string
21
+ }
22
+
23
+ export type ToastShortcutInput = React.ReactNode | CreateToastInput
24
+
25
+ export type ToastPromiseMessages<TData = unknown> = {
26
+ loading: ToastShortcutInput
27
+ success: ToastShortcutInput | ((data: TData) => ToastShortcutInput)
28
+ error: ToastShortcutInput | ((error: unknown) => ToastShortcutInput)
29
+ }
30
+
31
+ export type ToastContextValue = {
32
+ toasts: ToastItem[]
33
+ addToast: (toast: CreateToastInput) => string
34
+ updateToast: (id: string, toast: Partial<Omit<ToastItem, "id">>) => void
35
+ dismissToast: (id: string) => void
36
+ clearToasts: () => void
37
+ success: (toast: ToastShortcutInput) => string
38
+ info: (toast: ToastShortcutInput) => string
39
+ warning: (toast: ToastShortcutInput) => string
40
+ error: (toast: ToastShortcutInput) => string
41
+ loading: (toast: ToastShortcutInput) => string
42
+ promise: <TData>(promise: Promise<TData>, messages: ToastPromiseMessages<TData>) => Promise<TData>
43
+ }
44
+
45
+ const ToastContext = React.createContext<ToastContextValue | null>(null)
46
+
47
+ const toneClassName: Record<ToastTone, string> = {
48
+ default: "border-border bg-popover text-popover-foreground",
49
+ success: "border-emerald-500/20 bg-emerald-500/10 text-emerald-950 dark:text-emerald-100",
50
+ info: "border-blue-500/20 bg-blue-500/10 text-blue-950 dark:text-blue-100",
51
+ warning: "border-amber-500/20 bg-amber-500/10 text-amber-950 dark:text-amber-100",
52
+ danger: "border-destructive/20 bg-destructive/10 text-destructive",
53
+ loading: "border-border bg-popover text-popover-foreground",
54
+ }
55
+
56
+ const toneIcon: Record<ToastTone, React.ReactNode> = {
57
+ default: <InfoIcon className="size-4" />,
58
+ success: <CheckCircle2Icon className="size-4" />,
59
+ info: <InfoIcon className="size-4" />,
60
+ warning: <TriangleAlertIcon className="size-4" />,
61
+ danger: <AlertCircleIcon className="size-4" />,
62
+ loading: <Loader2Icon className="size-4 animate-spin" />,
63
+ }
64
+
65
+ function createToastId() {
66
+ return `toast-${Date.now()}-${Math.random().toString(16).slice(2)}`
67
+ }
68
+
69
+ function isToastObject(toast: ToastShortcutInput): toast is CreateToastInput {
70
+ return typeof toast === "object" && toast !== null && !React.isValidElement(toast) && !Array.isArray(toast)
71
+ }
72
+
73
+ function normalizeShortcutToast(toast: ToastShortcutInput, tone: ToastTone): CreateToastInput {
74
+ if (isToastObject(toast)) {
75
+ return { ...toast, tone: toast.tone ?? tone }
76
+ }
77
+
78
+ return { title: toast, tone }
79
+ }
80
+
81
+ function resolveToastMessage<TData>(message: ToastShortcutInput | ((data: TData) => ToastShortcutInput), data: TData) {
82
+ return typeof message === "function" ? message(data) : message
83
+ }
84
+
85
+ export type ToastProviderProps = React.PropsWithChildren<{
86
+ defaultDuration?: number
87
+ maxToasts?: number
88
+ pauseOnHover?: boolean
89
+ position?: "top-right" | "top-left" | "bottom-right" | "bottom-left" | "top-center" | "bottom-center"
90
+ }>
91
+
92
+ const positionClassName: Record<NonNullable<ToastProviderProps["position"]>, string> = {
93
+ "top-right": "right-4 top-4",
94
+ "top-left": "left-4 top-4",
95
+ "bottom-right": "bottom-4 right-4",
96
+ "bottom-left": "bottom-4 left-4",
97
+ "top-center": "left-1/2 top-4 -translate-x-1/2",
98
+ "bottom-center": "bottom-4 left-1/2 -translate-x-1/2",
99
+ }
100
+
101
+ function ToastProvider({
102
+ children,
103
+ defaultDuration = 4000,
104
+ maxToasts = 5,
105
+ pauseOnHover = true,
106
+ position = "top-right",
107
+ }: ToastProviderProps) {
108
+ const [toasts, setToasts] = React.useState<ToastItem[]>([])
109
+
110
+ const dismissToast = React.useCallback((id: string) => {
111
+ setToasts((current) => current.filter((toast) => toast.id !== id))
112
+ }, [])
113
+
114
+ const addToast = React.useCallback(
115
+ (toast: CreateToastInput) => {
116
+ const id = toast.id ?? createToastId()
117
+ const nextToast: ToastItem = {
118
+ tone: "default",
119
+ duration: defaultDuration,
120
+ dismissible: true,
121
+ ...toast,
122
+ id,
123
+ }
124
+
125
+ setToasts((current) => [nextToast, ...current.filter((item) => item.id !== id)].slice(0, maxToasts))
126
+
127
+ return id
128
+ },
129
+ [defaultDuration, maxToasts]
130
+ )
131
+
132
+ const updateToast = React.useCallback((id: string, toast: Partial<Omit<ToastItem, "id">>) => {
133
+ setToasts((current) => current.map((item) => (item.id === id ? { ...item, ...toast } : item)))
134
+ }, [])
135
+
136
+ const clearToasts = React.useCallback(() => {
137
+ setToasts([])
138
+ }, [])
139
+
140
+ const success = React.useCallback((toast: ToastShortcutInput) => addToast(normalizeShortcutToast(toast, "success")), [addToast])
141
+ const info = React.useCallback((toast: ToastShortcutInput) => addToast(normalizeShortcutToast(toast, "info")), [addToast])
142
+ const warning = React.useCallback((toast: ToastShortcutInput) => addToast(normalizeShortcutToast(toast, "warning")), [addToast])
143
+ const error = React.useCallback((toast: ToastShortcutInput) => addToast(normalizeShortcutToast(toast, "danger")), [addToast])
144
+ const loading = React.useCallback((toast: ToastShortcutInput) => addToast({ duration: 0, ...normalizeShortcutToast(toast, "loading") }), [addToast])
145
+
146
+ const promise = React.useCallback(
147
+ async <TData,>(targetPromise: Promise<TData>, messages: ToastPromiseMessages<TData>) => {
148
+ const id = addToast({ duration: 0, dismissible: false, ...normalizeShortcutToast(messages.loading, "loading") })
149
+
150
+ try {
151
+ const data = await targetPromise
152
+ updateToast(id, {
153
+ duration: defaultDuration,
154
+ dismissible: true,
155
+ ...normalizeShortcutToast(resolveToastMessage(messages.success, data), "success"),
156
+ })
157
+ return data
158
+ } catch (promiseError) {
159
+ updateToast(id, {
160
+ duration: defaultDuration,
161
+ dismissible: true,
162
+ ...normalizeShortcutToast(resolveToastMessage(messages.error, promiseError), "danger"),
163
+ })
164
+ throw promiseError
165
+ }
166
+ },
167
+ [addToast, defaultDuration, updateToast]
168
+ )
169
+
170
+ const value = React.useMemo<ToastContextValue>(
171
+ () => ({
172
+ toasts,
173
+ addToast,
174
+ updateToast,
175
+ dismissToast,
176
+ clearToasts,
177
+ success,
178
+ info,
179
+ warning,
180
+ error,
181
+ loading,
182
+ promise,
183
+ }),
184
+ [addToast, clearToasts, dismissToast, error, info, loading, promise, success, toasts, updateToast, warning]
185
+ )
186
+
187
+ return (
188
+ <ToastContext.Provider value={value}>
189
+ {children}
190
+ <div
191
+ data-slot="toast-viewport"
192
+ className={cn("fixed z-[100] flex w-[min(100%-2rem,24rem)] flex-col gap-2", positionClassName[position])}
193
+ >
194
+ {toasts.map((toast) => (
195
+ <ToastCard key={toast.id} toast={toast} pauseOnHover={pauseOnHover} onDismiss={dismissToast} />
196
+ ))}
197
+ </div>
198
+ </ToastContext.Provider>
199
+ )
200
+ }
201
+
202
+ function ToastCard({
203
+ toast,
204
+ pauseOnHover,
205
+ onDismiss,
206
+ }: {
207
+ toast: ToastItem
208
+ pauseOnHover: boolean
209
+ onDismiss: (id: string) => void
210
+ }) {
211
+ const tone = toast.tone ?? "default"
212
+ const [hovered, setHovered] = React.useState(false)
213
+
214
+ React.useEffect(() => {
215
+ if (toast.duration === Infinity || toast.duration === 0) return
216
+ if (pauseOnHover && hovered) return
217
+
218
+ const timer = window.setTimeout(() => onDismiss(toast.id), toast.duration ?? 4000)
219
+
220
+ return () => window.clearTimeout(timer)
221
+ }, [hovered, onDismiss, pauseOnHover, toast.duration, toast.id])
222
+
223
+ return (
224
+ <div
225
+ data-slot="toast"
226
+ data-tone={tone}
227
+ className={cn("flex gap-3 rounded-lg border p-3 shadow-lg backdrop-blur", toneClassName[tone])}
228
+ role="status"
229
+ aria-live="polite"
230
+ onMouseEnter={() => setHovered(true)}
231
+ onMouseLeave={() => setHovered(false)}
232
+ >
233
+ <div className="mt-0.5 shrink-0">{toneIcon[tone]}</div>
234
+ <div className="min-w-0 flex-1 space-y-1">
235
+ {toast.title && <div className="text-sm font-medium leading-none">{toast.title}</div>}
236
+ {toast.description && <div className="text-sm text-muted-foreground">{toast.description}</div>}
237
+ {toast.action && <div className="pt-1">{toast.action}</div>}
238
+ </div>
239
+ {toast.dismissible !== false && (
240
+ <Button type="button" variant="ghost" size="icon-xs" onClick={() => onDismiss(toast.id)}>
241
+ <XIcon />
242
+ <span className="sr-only">Dismiss</span>
243
+ </Button>
244
+ )}
245
+ </div>
246
+ )
247
+ }
248
+
249
+ function useToast() {
250
+ const context = React.useContext(ToastContext)
251
+
252
+ if (!context) {
253
+ throw new Error("useToast must be used inside ToastProvider")
254
+ }
255
+
256
+ return context
257
+ }
258
+
259
+ export { ToastProvider, useToast }
@@ -0,0 +1,66 @@
1
+ import * as React from "react"
2
+
3
+ import { DialogActionButton, DialogActions } from "@/components/overlay/dialog-actions"
4
+ import { ModalShell, type ModalShellProps } from "@/components/overlay/modal-shell"
5
+
6
+ type ConfirmVariant = React.ComponentProps<typeof DialogActionButton>["variant"]
7
+
8
+ type ConfirmDialogProps = Omit<ModalShellProps, "footer"> & {
9
+ cancelText?: React.ReactNode
10
+ confirmText?: React.ReactNode
11
+ confirmVariant?: ConfirmVariant
12
+ confirmDisabled?: boolean
13
+ cancelDisabled?: boolean
14
+ isLoading?: boolean
15
+ onCancel?: () => void
16
+ onConfirm?: () => void
17
+ }
18
+
19
+ function ConfirmDialog({
20
+ cancelText = "Cancel",
21
+ confirmText = "Confirm",
22
+ confirmVariant = "default",
23
+ confirmDisabled = false,
24
+ cancelDisabled = false,
25
+ isLoading = false,
26
+ onCancel,
27
+ onConfirm,
28
+ onOpenChange,
29
+ ...props
30
+ }: ConfirmDialogProps) {
31
+ const handleCancel = () => {
32
+ onCancel?.()
33
+ onOpenChange?.(false)
34
+ }
35
+
36
+ return (
37
+ <ModalShell
38
+ onOpenChange={onOpenChange}
39
+ footer={
40
+ <DialogActions>
41
+ <DialogActionButton
42
+ type="button"
43
+ variant="outline"
44
+ disabled={cancelDisabled || isLoading}
45
+ onClick={handleCancel}
46
+ >
47
+ {cancelText}
48
+ </DialogActionButton>
49
+ <DialogActionButton
50
+ type="button"
51
+ variant={confirmVariant}
52
+ disabled={confirmDisabled}
53
+ isLoading={isLoading}
54
+ onClick={onConfirm}
55
+ >
56
+ {confirmText}
57
+ </DialogActionButton>
58
+ </DialogActions>
59
+ }
60
+ {...props}
61
+ />
62
+ )
63
+ }
64
+
65
+ export { ConfirmDialog }
66
+ export type { ConfirmDialogProps }
@@ -0,0 +1,68 @@
1
+ import * as React from "react"
2
+ import { Loader2Icon } from "lucide-react"
3
+
4
+ import { Button } from "@/components/ui/button"
5
+ import { cn } from "@/lib/utils"
6
+
7
+ type DialogActionsAlign = "start" | "center" | "end" | "between"
8
+
9
+ const alignClassName: Record<DialogActionsAlign, string> = {
10
+ start: "justify-start",
11
+ center: "justify-center",
12
+ end: "justify-end",
13
+ between: "justify-between",
14
+ }
15
+
16
+ export type DialogActionButtonProps = React.ComponentProps<typeof Button> & {
17
+ isLoading?: boolean
18
+ loadingLabel?: React.ReactNode
19
+ }
20
+
21
+ export type DialogActionsProps = React.ComponentProps<"div"> & {
22
+ align?: DialogActionsAlign
23
+ stackOnMobile?: boolean
24
+ }
25
+
26
+ function DialogActionButton({
27
+ children,
28
+ disabled,
29
+ isLoading = false,
30
+ loadingLabel,
31
+ className,
32
+ ...props
33
+ }: DialogActionButtonProps) {
34
+ return (
35
+ <Button
36
+ className={className}
37
+ disabled={disabled || isLoading}
38
+ aria-busy={isLoading || undefined}
39
+ {...props}
40
+ >
41
+ {isLoading && <Loader2Icon data-icon="inline-start" className="animate-spin" />}
42
+ {isLoading && loadingLabel ? loadingLabel : children}
43
+ </Button>
44
+ )
45
+ }
46
+
47
+ function DialogActions({
48
+ className,
49
+ align = "end",
50
+ stackOnMobile = true,
51
+ ...props
52
+ }: DialogActionsProps) {
53
+ return (
54
+ <div
55
+ data-slot="dialog-actions"
56
+ className={cn(
57
+ "flex items-center gap-2",
58
+ stackOnMobile && "flex-col-reverse sm:flex-row",
59
+ !stackOnMobile && "flex-row",
60
+ alignClassName[align],
61
+ className
62
+ )}
63
+ {...props}
64
+ />
65
+ )
66
+ }
67
+
68
+ export { DialogActions, DialogActionButton }
@@ -0,0 +1,4 @@
1
+ export * from "./dialog-actions"
2
+ export * from "./modal-shell"
3
+ export * from "./confirm-dialog"
4
+ export * from "./sheet-shell"
@@ -0,0 +1,93 @@
1
+ import * as React from "react"
2
+
3
+ import {
4
+ Dialog,
5
+ DialogContent,
6
+ DialogDescription,
7
+ DialogHeader,
8
+ DialogTitle,
9
+ DialogTrigger,
10
+ } from "@/components/ui/dialog"
11
+ import { cn } from "@/lib/utils"
12
+
13
+ type ModalShellSize = "sm" | "md" | "lg" | "xl" | "full"
14
+
15
+ const modalSizeClassName: Record<ModalShellSize, string> = {
16
+ sm: "sm:max-w-sm",
17
+ md: "sm:max-w-lg",
18
+ lg: "sm:max-w-2xl",
19
+ xl: "sm:max-w-4xl",
20
+ full: "sm:max-w-[calc(100vw-2rem)] sm:h-[calc(100vh-2rem)]",
21
+ }
22
+
23
+ export type ModalShellProps = {
24
+ open?: boolean
25
+ defaultOpen?: boolean
26
+ onOpenChange?: (open: boolean) => void
27
+ trigger?: React.ReactNode
28
+ title?: React.ReactNode
29
+ description?: React.ReactNode
30
+ children?: React.ReactNode
31
+ footer?: React.ReactNode
32
+ size?: ModalShellSize
33
+ showCloseButton?: boolean
34
+ contentClassName?: string
35
+ headerClassName?: string
36
+ bodyClassName?: string
37
+ }
38
+
39
+ function renderDialogTrigger(trigger: React.ReactNode) {
40
+ if (!trigger) return null
41
+
42
+ if (React.isValidElement(trigger)) {
43
+ return <DialogTrigger render={trigger} />
44
+ }
45
+
46
+ return <DialogTrigger>{trigger}</DialogTrigger>
47
+ }
48
+
49
+ function ModalShell({
50
+ open,
51
+ defaultOpen,
52
+ onOpenChange,
53
+ trigger,
54
+ title,
55
+ description,
56
+ children,
57
+ footer,
58
+ size = "md",
59
+ showCloseButton = true,
60
+ contentClassName,
61
+ headerClassName,
62
+ bodyClassName,
63
+ }: ModalShellProps) {
64
+ const hasHeader = Boolean(title || description)
65
+
66
+ return (
67
+ <Dialog open={open} defaultOpen={defaultOpen} onOpenChange={onOpenChange}>
68
+ {renderDialogTrigger(trigger)}
69
+ <DialogContent
70
+ showCloseButton={showCloseButton}
71
+ className={cn(modalSizeClassName[size], contentClassName)}
72
+ >
73
+ {hasHeader && (
74
+ <DialogHeader className={headerClassName}>
75
+ {title && <DialogTitle>{title}</DialogTitle>}
76
+ {description && <DialogDescription>{description}</DialogDescription>}
77
+ </DialogHeader>
78
+ )}
79
+
80
+ {children && (
81
+ <div data-slot="modal-shell-body" className={cn("min-w-0", bodyClassName)}>
82
+ {children}
83
+ </div>
84
+ )}
85
+
86
+ {footer && <div data-slot="modal-shell-footer">{footer}</div>}
87
+ </DialogContent>
88
+ </Dialog>
89
+ )
90
+ }
91
+
92
+ export { ModalShell }
93
+ export type { ModalShellSize }
@@ -0,0 +1,212 @@
1
+ import * as React from "react"
2
+ import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"
3
+ import { XIcon } from "lucide-react"
4
+
5
+ import { Button } from "@/components/ui/button"
6
+ import { cn } from "@/lib/utils"
7
+
8
+ type SheetSide = "top" | "right" | "bottom" | "left"
9
+
10
+ type SheetShellProps = {
11
+ open?: boolean
12
+ defaultOpen?: boolean
13
+ onOpenChange?: (open: boolean) => void
14
+ trigger?: React.ReactNode
15
+ title?: React.ReactNode
16
+ description?: React.ReactNode
17
+ children?: React.ReactNode
18
+ footer?: React.ReactNode
19
+ side?: SheetSide
20
+ showCloseButton?: boolean
21
+ contentClassName?: string
22
+ headerClassName?: string
23
+ bodyClassName?: string
24
+ }
25
+
26
+ const Sheet = DialogPrimitive.Root
27
+
28
+ function SheetTrigger({ ...props }: DialogPrimitive.Trigger.Props) {
29
+ return <DialogPrimitive.Trigger data-slot="sheet-trigger" {...props} />
30
+ }
31
+
32
+ function SheetPortal({ ...props }: DialogPrimitive.Portal.Props) {
33
+ return <DialogPrimitive.Portal data-slot="sheet-portal" {...props} />
34
+ }
35
+
36
+ function SheetClose({ ...props }: DialogPrimitive.Close.Props) {
37
+ return <DialogPrimitive.Close data-slot="sheet-close" {...props} />
38
+ }
39
+
40
+ function SheetOverlay({ className, ...props }: DialogPrimitive.Backdrop.Props) {
41
+ return (
42
+ <DialogPrimitive.Backdrop
43
+ data-slot="sheet-overlay"
44
+ className={cn(
45
+ "fixed inset-0 z-50 bg-black/30 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
46
+ className
47
+ )}
48
+ {...props}
49
+ />
50
+ )
51
+ }
52
+
53
+ const sheetSideClassName: Record<SheetSide, string> = {
54
+ top: "inset-x-0 top-0 max-h-[85vh] rounded-b-xl border-b data-open:slide-in-from-top data-closed:slide-out-to-top",
55
+ right:
56
+ "inset-y-0 right-0 h-full w-3/4 max-w-sm border-l data-open:slide-in-from-right data-closed:slide-out-to-right sm:max-w-md",
57
+ bottom:
58
+ "inset-x-0 bottom-0 max-h-[85vh] rounded-t-xl border-t data-open:slide-in-from-bottom data-closed:slide-out-to-bottom",
59
+ left: "inset-y-0 left-0 h-full w-3/4 max-w-sm border-r data-open:slide-in-from-left data-closed:slide-out-to-left sm:max-w-md",
60
+ }
61
+
62
+ function SheetContent({
63
+ className,
64
+ children,
65
+ side = "right",
66
+ showCloseButton = true,
67
+ ...props
68
+ }: DialogPrimitive.Popup.Props & {
69
+ side?: SheetSide
70
+ showCloseButton?: boolean
71
+ }) {
72
+ return (
73
+ <SheetPortal>
74
+ <SheetOverlay />
75
+ <DialogPrimitive.Popup
76
+ data-slot="sheet-content"
77
+ className={cn(
78
+ "fixed z-50 flex flex-col gap-4 bg-background p-4 text-foreground shadow-xl outline-none duration-200 data-open:animate-in data-closed:animate-out",
79
+ sheetSideClassName[side],
80
+ className
81
+ )}
82
+ {...props}
83
+ >
84
+ {children}
85
+ {showCloseButton && (
86
+ <SheetClose
87
+ render={
88
+ <Button
89
+ type="button"
90
+ variant="ghost"
91
+ size="icon-sm"
92
+ className="absolute right-3 top-3"
93
+ />
94
+ }
95
+ >
96
+ <XIcon />
97
+ <span className="sr-only">Close</span>
98
+ </SheetClose>
99
+ )}
100
+ </DialogPrimitive.Popup>
101
+ </SheetPortal>
102
+ )
103
+ }
104
+
105
+ function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
106
+ return (
107
+ <div
108
+ data-slot="sheet-header"
109
+ className={cn("flex flex-col gap-2 pr-8", className)}
110
+ {...props}
111
+ />
112
+ )
113
+ }
114
+
115
+ function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
116
+ return (
117
+ <div
118
+ data-slot="sheet-footer"
119
+ className={cn("mt-auto flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
120
+ {...props}
121
+ />
122
+ )
123
+ }
124
+
125
+ function SheetTitle({ className, ...props }: DialogPrimitive.Title.Props) {
126
+ return (
127
+ <DialogPrimitive.Title
128
+ data-slot="sheet-title"
129
+ className={cn("font-heading text-base font-medium leading-none", className)}
130
+ {...props}
131
+ />
132
+ )
133
+ }
134
+
135
+ function SheetDescription({ className, ...props }: DialogPrimitive.Description.Props) {
136
+ return (
137
+ <DialogPrimitive.Description
138
+ data-slot="sheet-description"
139
+ className={cn("text-sm text-muted-foreground", className)}
140
+ {...props}
141
+ />
142
+ )
143
+ }
144
+
145
+ function renderSheetTrigger(trigger: React.ReactNode) {
146
+ if (!trigger) return null
147
+
148
+ if (React.isValidElement(trigger)) {
149
+ return <SheetTrigger render={trigger} />
150
+ }
151
+
152
+ return <SheetTrigger>{trigger}</SheetTrigger>
153
+ }
154
+
155
+ function SheetShell({
156
+ open,
157
+ defaultOpen,
158
+ onOpenChange,
159
+ trigger,
160
+ title,
161
+ description,
162
+ children,
163
+ footer,
164
+ side = "right",
165
+ showCloseButton = true,
166
+ contentClassName,
167
+ headerClassName,
168
+ bodyClassName,
169
+ }: SheetShellProps) {
170
+ const hasHeader = Boolean(title || description)
171
+
172
+ return (
173
+ <Sheet open={open} defaultOpen={defaultOpen} onOpenChange={onOpenChange}>
174
+ {renderSheetTrigger(trigger)}
175
+ <SheetContent
176
+ side={side}
177
+ showCloseButton={showCloseButton}
178
+ className={contentClassName}
179
+ >
180
+ {hasHeader && (
181
+ <SheetHeader className={headerClassName}>
182
+ {title && <SheetTitle>{title}</SheetTitle>}
183
+ {description && <SheetDescription>{description}</SheetDescription>}
184
+ </SheetHeader>
185
+ )}
186
+
187
+ {children && (
188
+ <div data-slot="sheet-shell-body" className={cn("min-w-0 flex-1", bodyClassName)}>
189
+ {children}
190
+ </div>
191
+ )}
192
+
193
+ {footer && <SheetFooter>{footer}</SheetFooter>}
194
+ </SheetContent>
195
+ </Sheet>
196
+ )
197
+ }
198
+
199
+ export {
200
+ Sheet,
201
+ SheetClose,
202
+ SheetContent,
203
+ SheetDescription,
204
+ SheetFooter,
205
+ SheetHeader,
206
+ SheetOverlay,
207
+ SheetPortal,
208
+ SheetShell,
209
+ SheetTitle,
210
+ SheetTrigger,
211
+ }
212
+ export type { SheetShellProps, SheetSide }