@betterstart/cli 0.1.3 → 0.1.5

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 (234) hide show
  1. package/README.md +133 -0
  2. package/dist/cli.d.ts +1 -9
  3. package/dist/cli.js +13585 -367
  4. package/dist/cli.js.map +1 -1
  5. package/dist/index.d.ts +24 -266
  6. package/dist/index.js +4 -11378
  7. package/dist/index.js.map +1 -1
  8. package/package.json +29 -42
  9. package/templates/schema.json +959 -0
  10. package/templates/tiptap/hooks/use-composed-ref.ts +43 -0
  11. package/templates/tiptap/hooks/use-cursor-visibility.ts +68 -0
  12. package/templates/tiptap/hooks/use-element-rect.ts +166 -0
  13. package/templates/tiptap/hooks/use-is-breakpoint.ts +32 -0
  14. package/templates/tiptap/hooks/use-menu-navigation.ts +182 -0
  15. package/templates/tiptap/hooks/use-scrolling.ts +64 -0
  16. package/templates/tiptap/hooks/use-throttled-callback.ts +146 -0
  17. package/templates/tiptap/hooks/use-tiptap-editor.ts +46 -0
  18. package/templates/tiptap/hooks/use-unmount.ts +21 -0
  19. package/templates/tiptap/hooks/use-window-size.ts +87 -0
  20. package/templates/tiptap/lib/tiptap-utils.ts +587 -0
  21. package/templates/tiptap/styles/_keyframe-animations.scss +91 -0
  22. package/templates/tiptap/styles/_variables.scss +296 -0
  23. package/templates/tiptap/tiptap-extension/node-background-extension.ts +138 -0
  24. package/templates/tiptap/tiptap-icons/align-center-icon.tsx +38 -0
  25. package/templates/tiptap/tiptap-icons/align-justify-icon.tsx +38 -0
  26. package/templates/tiptap/tiptap-icons/align-left-icon.tsx +38 -0
  27. package/templates/tiptap/tiptap-icons/align-right-icon.tsx +38 -0
  28. package/templates/tiptap/tiptap-icons/arrow-left-icon.tsx +24 -0
  29. package/templates/tiptap/tiptap-icons/ban-icon.tsx +26 -0
  30. package/templates/tiptap/tiptap-icons/blockquote-icon.tsx +44 -0
  31. package/templates/tiptap/tiptap-icons/bold-icon.tsx +26 -0
  32. package/templates/tiptap/tiptap-icons/chevron-down-icon.tsx +26 -0
  33. package/templates/tiptap/tiptap-icons/close-icon.tsx +24 -0
  34. package/templates/tiptap/tiptap-icons/code-block-icon.tsx +38 -0
  35. package/templates/tiptap/tiptap-icons/code2-icon.tsx +32 -0
  36. package/templates/tiptap/tiptap-icons/corner-down-left-icon.tsx +26 -0
  37. package/templates/tiptap/tiptap-icons/external-link-icon.tsx +28 -0
  38. package/templates/tiptap/tiptap-icons/heading-five-icon.tsx +28 -0
  39. package/templates/tiptap/tiptap-icons/heading-four-icon.tsx +28 -0
  40. package/templates/tiptap/tiptap-icons/heading-icon.tsx +24 -0
  41. package/templates/tiptap/tiptap-icons/heading-one-icon.tsx +28 -0
  42. package/templates/tiptap/tiptap-icons/heading-six-icon.tsx +30 -0
  43. package/templates/tiptap/tiptap-icons/heading-three-icon.tsx +36 -0
  44. package/templates/tiptap/tiptap-icons/heading-two-icon.tsx +28 -0
  45. package/templates/tiptap/tiptap-icons/highlighter-icon.tsx +26 -0
  46. package/templates/tiptap/tiptap-icons/image-plus-icon.tsx +26 -0
  47. package/templates/tiptap/tiptap-icons/italic-icon.tsx +24 -0
  48. package/templates/tiptap/tiptap-icons/link-icon.tsx +28 -0
  49. package/templates/tiptap/tiptap-icons/list-icon.tsx +56 -0
  50. package/templates/tiptap/tiptap-icons/list-ordered-icon.tsx +56 -0
  51. package/templates/tiptap/tiptap-icons/list-todo-icon.tsx +50 -0
  52. package/templates/tiptap/tiptap-icons/moon-star-icon.tsx +30 -0
  53. package/templates/tiptap/tiptap-icons/redo2-icon.tsx +26 -0
  54. package/templates/tiptap/tiptap-icons/strike-icon.tsx +28 -0
  55. package/templates/tiptap/tiptap-icons/subscript-icon.tsx +38 -0
  56. package/templates/tiptap/tiptap-icons/sun-icon.tsx +58 -0
  57. package/templates/tiptap/tiptap-icons/superscript-icon.tsx +38 -0
  58. package/templates/tiptap/tiptap-icons/trash-icon.tsx +26 -0
  59. package/templates/tiptap/tiptap-icons/underline-icon.tsx +26 -0
  60. package/templates/tiptap/tiptap-icons/undo2-icon.tsx +26 -0
  61. package/templates/tiptap/tiptap-node/blockquote-node/blockquote-node.scss +37 -0
  62. package/templates/tiptap/tiptap-node/code-block-node/code-block-node.scss +54 -0
  63. package/templates/tiptap/tiptap-node/heading-node/heading-node.scss +45 -0
  64. package/templates/tiptap/tiptap-node/horizontal-rule-node/horizontal-rule-node-extension.ts +10 -0
  65. package/templates/tiptap/tiptap-node/horizontal-rule-node/horizontal-rule-node.scss +25 -0
  66. package/templates/tiptap/tiptap-node/image-node/image-node.scss +35 -0
  67. package/templates/tiptap/tiptap-node/image-upload-node/image-upload-node-extension.ts +154 -0
  68. package/templates/tiptap/tiptap-node/image-upload-node/image-upload-node.scss +249 -0
  69. package/templates/tiptap/tiptap-node/image-upload-node/image-upload-node.tsx +522 -0
  70. package/templates/tiptap/tiptap-node/image-upload-node/index.tsx +1 -0
  71. package/templates/tiptap/tiptap-node/list-node/list-node.scss +208 -0
  72. package/templates/tiptap/tiptap-node/paragraph-node/paragraph-node.scss +273 -0
  73. package/templates/tiptap/tiptap-ui/blockquote-button/blockquote-button.tsx +104 -0
  74. package/templates/tiptap/tiptap-ui/blockquote-button/index.tsx +2 -0
  75. package/templates/tiptap/tiptap-ui/blockquote-button/use-blockquote.ts +252 -0
  76. package/templates/tiptap/tiptap-ui/code-block-button/code-block-button.tsx +106 -0
  77. package/templates/tiptap/tiptap-ui/code-block-button/index.tsx +2 -0
  78. package/templates/tiptap/tiptap-ui/code-block-button/use-code-block.ts +261 -0
  79. package/templates/tiptap/tiptap-ui/color-highlight-button/color-highlight-button.scss +49 -0
  80. package/templates/tiptap/tiptap-ui/color-highlight-button/color-highlight-button.tsx +153 -0
  81. package/templates/tiptap/tiptap-ui/color-highlight-button/index.tsx +2 -0
  82. package/templates/tiptap/tiptap-ui/color-highlight-button/use-color-highlight.ts +345 -0
  83. package/templates/tiptap/tiptap-ui/color-highlight-popover/color-highlight-popover.tsx +207 -0
  84. package/templates/tiptap/tiptap-ui/color-highlight-popover/index.tsx +1 -0
  85. package/templates/tiptap/tiptap-ui/heading-button/heading-button.tsx +107 -0
  86. package/templates/tiptap/tiptap-ui/heading-button/index.tsx +2 -0
  87. package/templates/tiptap/tiptap-ui/heading-button/use-heading.ts +314 -0
  88. package/templates/tiptap/tiptap-ui/heading-dropdown-menu/heading-dropdown-menu.tsx +131 -0
  89. package/templates/tiptap/tiptap-ui/heading-dropdown-menu/index.tsx +2 -0
  90. package/templates/tiptap/tiptap-ui/heading-dropdown-menu/use-heading-dropdown-menu.ts +130 -0
  91. package/templates/tiptap/tiptap-ui/image-upload-button/image-upload-button.tsx +114 -0
  92. package/templates/tiptap/tiptap-ui/image-upload-button/index.tsx +2 -0
  93. package/templates/tiptap/tiptap-ui/image-upload-button/use-image-upload.ts +192 -0
  94. package/templates/tiptap/tiptap-ui/link-popover/index.tsx +2 -0
  95. package/templates/tiptap/tiptap-ui/link-popover/link-popover.tsx +285 -0
  96. package/templates/tiptap/tiptap-ui/link-popover/use-link-popover.ts +286 -0
  97. package/templates/tiptap/tiptap-ui/list-button/index.tsx +2 -0
  98. package/templates/tiptap/tiptap-ui/list-button/list-button.tsx +108 -0
  99. package/templates/tiptap/tiptap-ui/list-button/use-list.ts +329 -0
  100. package/templates/tiptap/tiptap-ui/list-dropdown-menu/index.tsx +1 -0
  101. package/templates/tiptap/tiptap-ui/list-dropdown-menu/list-dropdown-menu.tsx +123 -0
  102. package/templates/tiptap/tiptap-ui/list-dropdown-menu/use-list-dropdown-menu.ts +203 -0
  103. package/templates/tiptap/tiptap-ui/mark-button/index.tsx +2 -0
  104. package/templates/tiptap/tiptap-ui/mark-button/mark-button.tsx +107 -0
  105. package/templates/tiptap/tiptap-ui/mark-button/use-mark.ts +206 -0
  106. package/templates/tiptap/tiptap-ui/text-align-button/index.tsx +2 -0
  107. package/templates/tiptap/tiptap-ui/text-align-button/text-align-button.tsx +118 -0
  108. package/templates/tiptap/tiptap-ui/text-align-button/use-text-align.ts +212 -0
  109. package/templates/tiptap/tiptap-ui/undo-redo-button/index.tsx +2 -0
  110. package/templates/tiptap/tiptap-ui/undo-redo-button/undo-redo-button.tsx +105 -0
  111. package/templates/tiptap/tiptap-ui/undo-redo-button/use-undo-redo.ts +173 -0
  112. package/templates/tiptap/tiptap-ui-primitive/badge/badge-colors.scss +395 -0
  113. package/templates/tiptap/tiptap-ui-primitive/badge/badge-group.scss +16 -0
  114. package/templates/tiptap/tiptap-ui-primitive/badge/badge.scss +99 -0
  115. package/templates/tiptap/tiptap-ui-primitive/badge/badge.tsx +46 -0
  116. package/templates/tiptap/tiptap-ui-primitive/badge/index.tsx +1 -0
  117. package/templates/tiptap/tiptap-ui-primitive/button/button-colors.scss +429 -0
  118. package/templates/tiptap/tiptap-ui-primitive/button/button-group.scss +22 -0
  119. package/templates/tiptap/tiptap-ui-primitive/button/button.scss +314 -0
  120. package/templates/tiptap/tiptap-ui-primitive/button/button.tsx +102 -0
  121. package/templates/tiptap/tiptap-ui-primitive/button/index.tsx +1 -0
  122. package/templates/tiptap/tiptap-ui-primitive/card/card.scss +77 -0
  123. package/templates/tiptap/tiptap-ui-primitive/card/card.tsx +59 -0
  124. package/templates/tiptap/tiptap-ui-primitive/card/index.tsx +1 -0
  125. package/templates/tiptap/tiptap-ui-primitive/dropdown-menu/dropdown-menu.scss +63 -0
  126. package/templates/tiptap/tiptap-ui-primitive/dropdown-menu/dropdown-menu.tsx +95 -0
  127. package/templates/tiptap/tiptap-ui-primitive/dropdown-menu/index.tsx +1 -0
  128. package/templates/tiptap/tiptap-ui-primitive/input/index.tsx +1 -0
  129. package/templates/tiptap/tiptap-ui-primitive/input/input.scss +45 -0
  130. package/templates/tiptap/tiptap-ui-primitive/input/input.tsx +18 -0
  131. package/templates/tiptap/tiptap-ui-primitive/popover/index.tsx +1 -0
  132. package/templates/tiptap/tiptap-ui-primitive/popover/popover.scss +63 -0
  133. package/templates/tiptap/tiptap-ui-primitive/popover/popover.tsx +33 -0
  134. package/templates/tiptap/tiptap-ui-primitive/separator/index.tsx +1 -0
  135. package/templates/tiptap/tiptap-ui-primitive/separator/separator.scss +23 -0
  136. package/templates/tiptap/tiptap-ui-primitive/separator/separator.tsx +33 -0
  137. package/templates/tiptap/tiptap-ui-primitive/spacer/index.tsx +1 -0
  138. package/templates/tiptap/tiptap-ui-primitive/spacer/spacer.tsx +21 -0
  139. package/templates/tiptap/tiptap-ui-primitive/toolbar/index.tsx +1 -0
  140. package/templates/tiptap/tiptap-ui-primitive/toolbar/toolbar.scss +98 -0
  141. package/templates/tiptap/tiptap-ui-primitive/toolbar/toolbar.tsx +113 -0
  142. package/templates/tiptap/tiptap-ui-primitive/tooltip/index.tsx +1 -0
  143. package/templates/tiptap/tiptap-ui-primitive/tooltip/tooltip.scss +43 -0
  144. package/templates/tiptap/tiptap-ui-primitive/tooltip/tooltip.tsx +223 -0
  145. package/templates/ui/accordion.tsx +52 -0
  146. package/templates/ui/alert-dialog.tsx +116 -0
  147. package/templates/ui/alert.tsx +48 -0
  148. package/templates/ui/aspect-ratio.tsx +7 -0
  149. package/templates/ui/avatar.tsx +46 -0
  150. package/templates/ui/badge.tsx +32 -0
  151. package/templates/ui/breadcrumb.tsx +98 -0
  152. package/templates/ui/button-group.tsx +77 -0
  153. package/templates/ui/button.tsx +48 -0
  154. package/templates/ui/calendar.tsx +176 -0
  155. package/templates/ui/card.tsx +54 -0
  156. package/templates/ui/carousel.tsx +234 -0
  157. package/templates/ui/chart.tsx +349 -0
  158. package/templates/ui/checkbox.tsx +27 -0
  159. package/templates/ui/collapsible.tsx +11 -0
  160. package/templates/ui/command.tsx +142 -0
  161. package/templates/ui/context-menu.tsx +188 -0
  162. package/templates/ui/curriculum-editor.tsx +601 -0
  163. package/templates/ui/date-picker.tsx +70 -0
  164. package/templates/ui/dialog.tsx +103 -0
  165. package/templates/ui/drawer.tsx +99 -0
  166. package/templates/ui/dropdown-menu.tsx +185 -0
  167. package/templates/ui/dynamic-list-field.tsx +95 -0
  168. package/templates/ui/empty.tsx +90 -0
  169. package/templates/ui/field.tsx +231 -0
  170. package/templates/ui/file-upload-example.tsx +113 -0
  171. package/templates/ui/form.tsx +172 -0
  172. package/templates/ui/hover-card.tsx +28 -0
  173. package/templates/ui/icon-picker.tsx +435 -0
  174. package/templates/ui/icons-data.ts +6 -0
  175. package/templates/ui/image-upload-field.tsx +360 -0
  176. package/templates/ui/input-group.tsx +160 -0
  177. package/templates/ui/input-otp.tsx +70 -0
  178. package/templates/ui/input.tsx +21 -0
  179. package/templates/ui/item.tsx +171 -0
  180. package/templates/ui/kbd.tsx +28 -0
  181. package/templates/ui/label.tsx +20 -0
  182. package/templates/ui/logo.tsx +113 -0
  183. package/templates/ui/markdown-editor.tsx +303 -0
  184. package/templates/ui/markdown-utils.ts +128 -0
  185. package/templates/ui/media-upload-field.tsx +255 -0
  186. package/templates/ui/menubar.tsx +230 -0
  187. package/templates/ui/navigation-menu.tsx +119 -0
  188. package/templates/ui/pagination.tsx +96 -0
  189. package/templates/ui/placeholder.tsx +25 -0
  190. package/templates/ui/popover.tsx +32 -0
  191. package/templates/ui/progress.tsx +24 -0
  192. package/templates/ui/radio-group.tsx +37 -0
  193. package/templates/ui/resizable.tsx +41 -0
  194. package/templates/ui/rich-text-editor.tsx +374 -0
  195. package/templates/ui/scroll-area.tsx +45 -0
  196. package/templates/ui/select.tsx +151 -0
  197. package/templates/ui/separator.tsx +25 -0
  198. package/templates/ui/sheet.tsx +120 -0
  199. package/templates/ui/sidebar.tsx +687 -0
  200. package/templates/ui/skeleton.tsx +7 -0
  201. package/templates/ui/slider.tsx +24 -0
  202. package/templates/ui/sonner.tsx +29 -0
  203. package/templates/ui/spinner.tsx +15 -0
  204. package/templates/ui/switch.tsx +28 -0
  205. package/templates/ui/table.tsx +93 -0
  206. package/templates/ui/tabs.tsx +54 -0
  207. package/templates/ui/textarea.tsx +20 -0
  208. package/templates/ui/toast.tsx +127 -0
  209. package/templates/ui/toggle-group.tsx +56 -0
  210. package/templates/ui/toggle.tsx +43 -0
  211. package/templates/ui/tooltip.tsx +31 -0
  212. package/templates/ui/use-mobile.tsx +19 -0
  213. package/templates/ui/video-upload-field.tsx +368 -0
  214. package/dist/chunk-EIH4RRIJ.js +0 -183
  215. package/dist/chunk-EIH4RRIJ.js.map +0 -1
  216. package/dist/chunk-NKRQYAS6.js +0 -260
  217. package/dist/chunk-NKRQYAS6.js.map +0 -1
  218. package/dist/chunk-QLVSHP7X.js +0 -235
  219. package/dist/chunk-QLVSHP7X.js.map +0 -1
  220. package/dist/chunk-WY6BC55D.js +0 -357
  221. package/dist/chunk-WY6BC55D.js.map +0 -1
  222. package/dist/config/index.d.ts +0 -93
  223. package/dist/config/index.js +0 -58
  224. package/dist/config/index.js.map +0 -1
  225. package/dist/core/index.d.ts +0 -415
  226. package/dist/core/index.js +0 -906
  227. package/dist/core/index.js.map +0 -1
  228. package/dist/import-resolver-BaZ-rzkH.d.ts +0 -123
  229. package/dist/logger-awLb347n.d.ts +0 -81
  230. package/dist/plugins/index.d.ts +0 -213
  231. package/dist/plugins/index.js +0 -365
  232. package/dist/plugins/index.js.map +0 -1
  233. package/dist/types-ByX_gl6y.d.ts +0 -232
  234. package/dist/types-eI549DEG.d.ts +0 -331
@@ -0,0 +1,231 @@
1
+ 'use client'
2
+
3
+ import { cn } from '@cms/utils/cn'
4
+ import { cva, type VariantProps } from 'class-variance-authority'
5
+ import { useMemo } from 'react'
6
+ import { Label } from './label'
7
+ import { Separator } from './separator'
8
+
9
+ function FieldSet({ className, ...props }: React.ComponentProps<'fieldset'>) {
10
+ return (
11
+ <fieldset
12
+ data-slot="field-set"
13
+ className={cn(
14
+ 'flex flex-col gap-6',
15
+ 'has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3',
16
+ className
17
+ )}
18
+ {...props}
19
+ />
20
+ )
21
+ }
22
+
23
+ function FieldLegend({
24
+ className,
25
+ variant = 'legend',
26
+ ...props
27
+ }: React.ComponentProps<'legend'> & { variant?: 'legend' | 'label' }) {
28
+ return (
29
+ <legend
30
+ data-slot="field-legend"
31
+ data-variant={variant}
32
+ className={cn(
33
+ 'mb-3 font-medium',
34
+ 'data-[variant=legend]:text-base',
35
+ 'data-[variant=label]:text-sm',
36
+ className
37
+ )}
38
+ {...props}
39
+ />
40
+ )
41
+ }
42
+
43
+ function FieldGroup({ className, ...props }: React.ComponentProps<'div'>) {
44
+ return (
45
+ <div
46
+ data-slot="field-group"
47
+ className={cn(
48
+ 'group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4',
49
+ className
50
+ )}
51
+ {...props}
52
+ />
53
+ )
54
+ }
55
+
56
+ const fieldVariants = cva('group/field data-[invalid=true]:text-destructive flex w-full gap-3', {
57
+ variants: {
58
+ orientation: {
59
+ vertical: ['flex-col [&>*]:w-full [&>.sr-only]:w-auto'],
60
+ horizontal: [
61
+ 'flex-row items-center',
62
+ '[&>[data-slot=field-label]]:flex-auto',
63
+ 'has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px has-[>[data-slot=field-content]]:items-start'
64
+ ],
65
+ responsive: [
66
+ '@md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto flex-col [&>*]:w-full [&>.sr-only]:w-auto',
67
+ '@md/field-group:[&>[data-slot=field-label]]:flex-auto',
68
+ '@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px'
69
+ ]
70
+ }
71
+ },
72
+ defaultVariants: {
73
+ orientation: 'vertical'
74
+ }
75
+ })
76
+
77
+ function Field({
78
+ className,
79
+ orientation = 'vertical',
80
+ ...props
81
+ }: React.ComponentProps<'div'> & VariantProps<typeof fieldVariants>) {
82
+ return (
83
+ <div
84
+ role="group"
85
+ data-slot="field"
86
+ data-orientation={orientation}
87
+ className={cn(fieldVariants({ orientation }), className)}
88
+ {...props}
89
+ />
90
+ )
91
+ }
92
+
93
+ function FieldContent({ className, ...props }: React.ComponentProps<'div'>) {
94
+ return (
95
+ <div
96
+ data-slot="field-content"
97
+ className={cn('group/field-content flex flex-1 flex-col gap-1.5 leading-snug', className)}
98
+ {...props}
99
+ />
100
+ )
101
+ }
102
+
103
+ function FieldLabel({ className, ...props }: React.ComponentProps<typeof Label>) {
104
+ return (
105
+ <Label
106
+ data-slot="field-label"
107
+ className={cn(
108
+ 'group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50',
109
+ 'has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>[data-slot=field]]:p-4',
110
+ 'has-data-[state=checked]:bg-primary/5 has-data-[state=checked]:border-primary dark:has-data-[state=checked]:bg-primary/10',
111
+ className
112
+ )}
113
+ {...props}
114
+ />
115
+ )
116
+ }
117
+
118
+ function FieldTitle({ className, ...props }: React.ComponentProps<'div'>) {
119
+ return (
120
+ <div
121
+ data-slot="field-label"
122
+ className={cn(
123
+ 'flex w-fit items-center gap-2 text-sm font-medium leading-snug group-data-[disabled=true]/field:opacity-50',
124
+ className
125
+ )}
126
+ {...props}
127
+ />
128
+ )
129
+ }
130
+
131
+ function FieldDescription({ className, ...props }: React.ComponentProps<'p'>) {
132
+ return (
133
+ <p
134
+ data-slot="field-description"
135
+ className={cn(
136
+ 'text-muted-foreground text-sm font-normal leading-normal',
137
+ 'nth-last-2:-mt-1 last:mt-0 [[data-variant=legend]+&]:-mt-1.5',
138
+ '[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',
139
+ className
140
+ )}
141
+ {...props}
142
+ />
143
+ )
144
+ }
145
+
146
+ function FieldSeparator({
147
+ children,
148
+ className,
149
+ ...props
150
+ }: React.ComponentProps<'div'> & {
151
+ children?: React.ReactNode
152
+ }) {
153
+ return (
154
+ <div
155
+ data-slot="field-separator"
156
+ data-content={!!children}
157
+ className={cn(
158
+ 'relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2',
159
+ className
160
+ )}
161
+ {...props}
162
+ >
163
+ <Separator className="absolute inset-0 top-1/2" />
164
+ {children && (
165
+ <span
166
+ className="bg-background text-muted-foreground relative mx-auto block w-fit px-2"
167
+ data-slot="field-separator-content"
168
+ >
169
+ {children}
170
+ </span>
171
+ )}
172
+ </div>
173
+ )
174
+ }
175
+
176
+ function FieldError({
177
+ className,
178
+ children,
179
+ errors,
180
+ ...props
181
+ }: React.ComponentProps<'div'> & {
182
+ errors?: Array<{ message?: string } | undefined>
183
+ }) {
184
+ const content = useMemo(() => {
185
+ if (children) {
186
+ return children
187
+ }
188
+
189
+ if (!errors) {
190
+ return null
191
+ }
192
+
193
+ if (errors?.length === 1 && errors[0]?.message) {
194
+ return errors[0].message
195
+ }
196
+
197
+ return (
198
+ <ul className="ml-4 flex list-disc flex-col gap-1">
199
+ {errors.map((error, index) => error?.message && <li key={index}>{error.message}</li>)}
200
+ </ul>
201
+ )
202
+ }, [children, errors])
203
+
204
+ if (!content) {
205
+ return null
206
+ }
207
+
208
+ return (
209
+ <div
210
+ role="alert"
211
+ data-slot="field-error"
212
+ className={cn('text-destructive text-sm font-normal', className)}
213
+ {...props}
214
+ >
215
+ {content}
216
+ </div>
217
+ )
218
+ }
219
+
220
+ export {
221
+ Field,
222
+ FieldLabel,
223
+ FieldDescription,
224
+ FieldError,
225
+ FieldGroup,
226
+ FieldLegend,
227
+ FieldSeparator,
228
+ FieldSet,
229
+ FieldContent,
230
+ FieldTitle
231
+ }
@@ -0,0 +1,113 @@
1
+ 'use client'
2
+
3
+ import { useUpload } from '@cms/hooks/use-upload'
4
+ import * as React from 'react'
5
+ import { Button } from './button'
6
+ import { Progress } from './progress'
7
+
8
+ /**
9
+ * Example component demonstrating the useUpload hook
10
+ * This is a reference implementation showing all features
11
+ */
12
+ export function FileUploadExample() {
13
+ const fileInputRef = React.useRef<HTMLInputElement>(null)
14
+
15
+ const { upload, mutation, progress, validate } = useUpload({
16
+ validationConfig: {
17
+ maxSizeInBytes: 10 * 1024 * 1024, // 10MB
18
+ allowedTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
19
+ maxFiles: 5
20
+ },
21
+ onProgress: (progressData) => {
22
+ console.log('Upload progress:', progressData)
23
+ },
24
+ onSuccess: (result) => {
25
+ console.log('Upload successful:', result)
26
+ },
27
+ onError: (error) => {
28
+ console.error('Upload failed:', error)
29
+ },
30
+ prefix: 'user-uploads'
31
+ })
32
+
33
+ const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
34
+ const files = Array.from(e.target.files || [])
35
+
36
+ // Validate files before upload
37
+ const validation = validate(files)
38
+ if (!validation.valid) {
39
+ alert(`Validation failed:\n${validation.errors.join('\n')}`)
40
+ return
41
+ }
42
+
43
+ // Upload files
44
+ upload(files)
45
+ }
46
+
47
+ const handleButtonClick = () => {
48
+ fileInputRef.current?.click()
49
+ }
50
+
51
+ return (
52
+ <div className="w-full max-w-md space-y-4">
53
+ <div className="space-y-2">
54
+ <input
55
+ ref={fileInputRef}
56
+ type="file"
57
+ multiple
58
+ accept="image/*"
59
+ onChange={handleFileChange}
60
+ className="hidden"
61
+ />
62
+
63
+ <Button onClick={handleButtonClick} disabled={mutation.isPending} className="w-full">
64
+ {mutation.isPending ? 'Uploading...' : 'Select Files'}
65
+ </Button>
66
+ </div>
67
+
68
+ {/* Progress indicators */}
69
+ {progress.length > 0 && (
70
+ <div className="space-y-2">
71
+ {progress.map((p) => (
72
+ <div key={p.filename} className="space-y-1">
73
+ <div className="flex justify-between text-sm">
74
+ <span className="truncate">{p.filename}</span>
75
+ <span>{p.progress}%</span>
76
+ </div>
77
+ <Progress value={p.progress} />
78
+ </div>
79
+ ))}
80
+ </div>
81
+ )}
82
+
83
+ {/* Success message */}
84
+ {mutation.isSuccess && mutation.data?.success && (
85
+ <div className="rounded-md corner-squircle bg-green-50 p-4 text-sm text-green-800">
86
+ <p className="font-medium">Upload successful!</p>
87
+ {mutation.data.files?.map((file) => (
88
+ <div key={file.key} className="mt-2">
89
+ <a href={file.url} target="_blank" rel="noopener noreferrer" className="underline">
90
+ {file.filename}
91
+ </a>
92
+ </div>
93
+ ))}
94
+ </div>
95
+ )}
96
+
97
+ {/* Error message */}
98
+ {mutation.isError && (
99
+ <div className="rounded-md corner-squircle bg-red-50 p-4 text-sm text-red-800">
100
+ <p className="font-medium">Upload failed</p>
101
+ <p>{mutation.error.message}</p>
102
+ </div>
103
+ )}
104
+
105
+ {mutation.data && !mutation.data.success && (
106
+ <div className="rounded-md corner-squircle bg-red-50 p-4 text-sm text-red-800">
107
+ <p className="font-medium">Upload failed</p>
108
+ <p>{mutation.data.error}</p>
109
+ </div>
110
+ )}
111
+ </div>
112
+ )
113
+ }
@@ -0,0 +1,172 @@
1
+ 'use client'
2
+
3
+ import { cn } from '@cms/utils/cn'
4
+ import type * as LabelPrimitive from '@radix-ui/react-label'
5
+ import { Slot } from '@radix-ui/react-slot'
6
+ import * as React from 'react'
7
+ import {
8
+ Controller,
9
+ type ControllerProps,
10
+ type FieldPath,
11
+ type FieldValues,
12
+ FormProvider,
13
+ useFormContext
14
+ } from 'react-hook-form'
15
+ import { Label } from './label'
16
+
17
+ const Form = FormProvider
18
+
19
+ type FormFieldContextValue<
20
+ TFieldValues extends FieldValues = FieldValues,
21
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
22
+ > = {
23
+ name: TName
24
+ }
25
+
26
+ const FormFieldContext = React.createContext<FormFieldContextValue | null>(null)
27
+
28
+ const FormField = <
29
+ TFieldValues extends FieldValues = FieldValues,
30
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
31
+ >({
32
+ ...props
33
+ }: ControllerProps<TFieldValues, TName>) => {
34
+ return (
35
+ <FormFieldContext.Provider value={{ name: props.name }}>
36
+ <Controller {...props} />
37
+ </FormFieldContext.Provider>
38
+ )
39
+ }
40
+
41
+ const useFormField = () => {
42
+ const fieldContext = React.useContext(FormFieldContext)
43
+ const itemContext = React.useContext(FormItemContext)
44
+ const { getFieldState, formState } = useFormContext()
45
+
46
+ if (!fieldContext) {
47
+ throw new Error('useFormField should be used within <FormField>')
48
+ }
49
+
50
+ if (!itemContext) {
51
+ throw new Error('useFormField should be used within <FormItem>')
52
+ }
53
+
54
+ const fieldState = getFieldState(fieldContext.name, formState)
55
+
56
+ const { id } = itemContext
57
+
58
+ return {
59
+ id,
60
+ name: fieldContext.name,
61
+ formItemId: `${id}-form-item`,
62
+ formDescriptionId: `${id}-form-item-description`,
63
+ formMessageId: `${id}-form-item-message`,
64
+ ...fieldState
65
+ }
66
+ }
67
+
68
+ type FormItemContextValue = {
69
+ id: string
70
+ }
71
+
72
+ const FormItemContext = React.createContext<FormItemContextValue | null>(null)
73
+
74
+ const FormItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
75
+ ({ className, ...props }, ref) => {
76
+ const id = React.useId()
77
+
78
+ return (
79
+ <FormItemContext.Provider value={{ id }}>
80
+ <div ref={ref} className={cn('space-y-2', className)} {...props} />
81
+ </FormItemContext.Provider>
82
+ )
83
+ }
84
+ )
85
+ FormItem.displayName = 'FormItem'
86
+
87
+ const FormLabel = React.forwardRef<
88
+ React.ElementRef<typeof LabelPrimitive.Root>,
89
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
90
+ >(({ className, ...props }, ref) => {
91
+ const { error, formItemId } = useFormField()
92
+
93
+ return (
94
+ <Label
95
+ ref={ref}
96
+ className={cn(error && 'text-destructive', className)}
97
+ htmlFor={formItemId}
98
+ {...props}
99
+ />
100
+ )
101
+ })
102
+ FormLabel.displayName = 'FormLabel'
103
+
104
+ const FormControl = React.forwardRef<
105
+ React.ElementRef<typeof Slot>,
106
+ React.ComponentPropsWithoutRef<typeof Slot>
107
+ >(({ ...props }, ref) => {
108
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
109
+
110
+ return (
111
+ <Slot
112
+ ref={ref}
113
+ id={formItemId}
114
+ aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
115
+ aria-invalid={!!error}
116
+ {...props}
117
+ />
118
+ )
119
+ })
120
+ FormControl.displayName = 'FormControl'
121
+
122
+ const FormDescription = React.forwardRef<
123
+ HTMLParagraphElement,
124
+ React.HTMLAttributes<HTMLParagraphElement>
125
+ >(({ className, ...props }, ref) => {
126
+ const { formDescriptionId } = useFormField()
127
+
128
+ return (
129
+ <p
130
+ ref={ref}
131
+ id={formDescriptionId}
132
+ className={cn('text-[0.8rem] text-muted-foreground', className)}
133
+ {...props}
134
+ />
135
+ )
136
+ })
137
+ FormDescription.displayName = 'FormDescription'
138
+
139
+ const FormMessage = React.forwardRef<
140
+ HTMLParagraphElement,
141
+ React.HTMLAttributes<HTMLParagraphElement>
142
+ >(({ className, children, ...props }, ref) => {
143
+ const { error, formMessageId } = useFormField()
144
+ const body = error ? String(error?.message ?? '') : children
145
+
146
+ if (!body) {
147
+ return null
148
+ }
149
+
150
+ return (
151
+ <p
152
+ ref={ref}
153
+ id={formMessageId}
154
+ className={cn('text-[0.8rem] font-medium text-destructive', className)}
155
+ {...props}
156
+ >
157
+ {body}
158
+ </p>
159
+ )
160
+ })
161
+ FormMessage.displayName = 'FormMessage'
162
+
163
+ export {
164
+ useFormField,
165
+ Form,
166
+ FormItem,
167
+ FormLabel,
168
+ FormControl,
169
+ FormDescription,
170
+ FormMessage,
171
+ FormField
172
+ }
@@ -0,0 +1,28 @@
1
+ 'use client'
2
+
3
+ import { cn } from '@cms/utils/cn'
4
+ import * as HoverCardPrimitive from '@radix-ui/react-hover-card'
5
+ import * as React from 'react'
6
+
7
+ const HoverCard = HoverCardPrimitive.Root
8
+
9
+ const HoverCardTrigger = HoverCardPrimitive.Trigger
10
+
11
+ const HoverCardContent = React.forwardRef<
12
+ React.ElementRef<typeof HoverCardPrimitive.Content>,
13
+ React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
14
+ >(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
15
+ <HoverCardPrimitive.Content
16
+ ref={ref}
17
+ align={align}
18
+ sideOffset={sideOffset}
19
+ className={cn(
20
+ 'z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-hover-card-content-transform-origin]',
21
+ className
22
+ )}
23
+ {...props}
24
+ />
25
+ ))
26
+ HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
27
+
28
+ export { HoverCard, HoverCardTrigger, HoverCardContent }