@modern-admin/ui 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 (268) hide show
  1. package/dist/components/accordion.d.ts +7 -0
  2. package/dist/components/accordion.d.ts.map +1 -0
  3. package/dist/components/accordion.jsx +19 -0
  4. package/dist/components/accordion.jsx.map +1 -0
  5. package/dist/components/alert-dialog.d.ts +22 -0
  6. package/dist/components/alert-dialog.d.ts.map +1 -0
  7. package/dist/components/alert-dialog.jsx +27 -0
  8. package/dist/components/alert-dialog.jsx.map +1 -0
  9. package/dist/components/audit-timeline.d.ts +24 -0
  10. package/dist/components/audit-timeline.d.ts.map +1 -0
  11. package/dist/components/audit-timeline.jsx +60 -0
  12. package/dist/components/audit-timeline.jsx.map +1 -0
  13. package/dist/components/avatar.d.ts +6 -0
  14. package/dist/components/avatar.d.ts.map +1 -0
  15. package/dist/components/avatar.jsx +10 -0
  16. package/dist/components/avatar.jsx.map +1 -0
  17. package/dist/components/badge.d.ts +10 -0
  18. package/dist/components/badge.d.ts.map +1 -0
  19. package/dist/components/badge.jsx +19 -0
  20. package/dist/components/badge.jsx.map +1 -0
  21. package/dist/components/breadcrumb.d.ts +17 -0
  22. package/dist/components/breadcrumb.d.ts.map +1 -0
  23. package/dist/components/breadcrumb.jsx +27 -0
  24. package/dist/components/breadcrumb.jsx.map +1 -0
  25. package/dist/components/button.d.ts +12 -0
  26. package/dist/components/button.d.ts.map +1 -0
  27. package/dist/components/button.jsx +37 -0
  28. package/dist/components/button.jsx.map +1 -0
  29. package/dist/components/calendar.d.ts +9 -0
  30. package/dist/components/calendar.d.ts.map +1 -0
  31. package/dist/components/calendar.jsx +102 -0
  32. package/dist/components/calendar.jsx.map +1 -0
  33. package/dist/components/card.d.ts +8 -0
  34. package/dist/components/card.d.ts.map +1 -0
  35. package/dist/components/card.jsx +18 -0
  36. package/dist/components/card.jsx.map +1 -0
  37. package/dist/components/chart.d.ts +97 -0
  38. package/dist/components/chart.d.ts.map +1 -0
  39. package/dist/components/chart.jsx +233 -0
  40. package/dist/components/chart.jsx.map +1 -0
  41. package/dist/components/checkbox.d.ts +4 -0
  42. package/dist/components/checkbox.d.ts.map +1 -0
  43. package/dist/components/checkbox.jsx +11 -0
  44. package/dist/components/checkbox.jsx.map +1 -0
  45. package/dist/components/combobox.d.ts +46 -0
  46. package/dist/components/combobox.d.ts.map +1 -0
  47. package/dist/components/combobox.jsx +145 -0
  48. package/dist/components/combobox.jsx.map +1 -0
  49. package/dist/components/command.d.ts +80 -0
  50. package/dist/components/command.d.ts.map +1 -0
  51. package/dist/components/command.jsx +32 -0
  52. package/dist/components/command.jsx.map +1 -0
  53. package/dist/components/date-picker.d.ts +24 -0
  54. package/dist/components/date-picker.d.ts.map +1 -0
  55. package/dist/components/date-picker.jsx +149 -0
  56. package/dist/components/date-picker.jsx.map +1 -0
  57. package/dist/components/date-range-input.d.ts +22 -0
  58. package/dist/components/date-range-input.d.ts.map +1 -0
  59. package/dist/components/date-range-input.jsx +202 -0
  60. package/dist/components/date-range-input.jsx.map +1 -0
  61. package/dist/components/dialog.d.ts +19 -0
  62. package/dist/components/dialog.d.ts.map +1 -0
  63. package/dist/components/dialog.jsx +30 -0
  64. package/dist/components/dialog.jsx.map +1 -0
  65. package/dist/components/diff-view.d.ts +24 -0
  66. package/dist/components/diff-view.d.ts.map +1 -0
  67. package/dist/components/diff-view.jsx +69 -0
  68. package/dist/components/diff-view.jsx.map +1 -0
  69. package/dist/components/dropdown-menu.d.ts +27 -0
  70. package/dist/components/dropdown-menu.d.ts.map +1 -0
  71. package/dist/components/dropdown-menu.jsx +48 -0
  72. package/dist/components/dropdown-menu.jsx.map +1 -0
  73. package/dist/components/empty.d.ts +15 -0
  74. package/dist/components/empty.d.ts.map +1 -0
  75. package/dist/components/empty.jsx +27 -0
  76. package/dist/components/empty.jsx.map +1 -0
  77. package/dist/components/field.d.ts +23 -0
  78. package/dist/components/field.d.ts.map +1 -0
  79. package/dist/components/field.jsx +60 -0
  80. package/dist/components/field.jsx.map +1 -0
  81. package/dist/components/file-input.d.ts +50 -0
  82. package/dist/components/file-input.d.ts.map +1 -0
  83. package/dist/components/file-input.jsx +104 -0
  84. package/dist/components/file-input.jsx.map +1 -0
  85. package/dist/components/form.d.ts +20 -0
  86. package/dist/components/form.d.ts.map +1 -0
  87. package/dist/components/form.jsx +66 -0
  88. package/dist/components/form.jsx.map +1 -0
  89. package/dist/components/info-tooltip.d.ts +11 -0
  90. package/dist/components/info-tooltip.d.ts.map +1 -0
  91. package/dist/components/info-tooltip.jsx +17 -0
  92. package/dist/components/info-tooltip.jsx.map +1 -0
  93. package/dist/components/input.d.ts +13 -0
  94. package/dist/components/input.d.ts.map +1 -0
  95. package/dist/components/input.jsx +19 -0
  96. package/dist/components/input.jsx.map +1 -0
  97. package/dist/components/json-editor.d.ts +23 -0
  98. package/dist/components/json-editor.d.ts.map +1 -0
  99. package/dist/components/json-editor.jsx +143 -0
  100. package/dist/components/json-editor.jsx.map +1 -0
  101. package/dist/components/kbd.d.ts +15 -0
  102. package/dist/components/kbd.d.ts.map +1 -0
  103. package/dist/components/kbd.jsx +23 -0
  104. package/dist/components/kbd.jsx.map +1 -0
  105. package/dist/components/key-value-editor.d.ts +92 -0
  106. package/dist/components/key-value-editor.d.ts.map +1 -0
  107. package/dist/components/key-value-editor.jsx +187 -0
  108. package/dist/components/key-value-editor.jsx.map +1 -0
  109. package/dist/components/keyboard-shortcuts-help.d.ts +17 -0
  110. package/dist/components/keyboard-shortcuts-help.d.ts.map +1 -0
  111. package/dist/components/keyboard-shortcuts-help.jsx +97 -0
  112. package/dist/components/keyboard-shortcuts-help.jsx.map +1 -0
  113. package/dist/components/label.d.ts +5 -0
  114. package/dist/components/label.d.ts.map +1 -0
  115. package/dist/components/label.jsx +8 -0
  116. package/dist/components/label.jsx.map +1 -0
  117. package/dist/components/media-preview.d.ts +30 -0
  118. package/dist/components/media-preview.d.ts.map +1 -0
  119. package/dist/components/media-preview.jsx +189 -0
  120. package/dist/components/media-preview.jsx.map +1 -0
  121. package/dist/components/multi-file-input.d.ts +76 -0
  122. package/dist/components/multi-file-input.d.ts.map +1 -0
  123. package/dist/components/multi-file-input.jsx +131 -0
  124. package/dist/components/multi-file-input.jsx.map +1 -0
  125. package/dist/components/password-input.d.ts +10 -0
  126. package/dist/components/password-input.d.ts.map +1 -0
  127. package/dist/components/password-input.jsx +18 -0
  128. package/dist/components/password-input.jsx.map +1 -0
  129. package/dist/components/popover.d.ts +7 -0
  130. package/dist/components/popover.d.ts.map +1 -0
  131. package/dist/components/popover.jsx +11 -0
  132. package/dist/components/popover.jsx.map +1 -0
  133. package/dist/components/revision-timeline.d.ts +30 -0
  134. package/dist/components/revision-timeline.d.ts.map +1 -0
  135. package/dist/components/revision-timeline.jsx +42 -0
  136. package/dist/components/revision-timeline.jsx.map +1 -0
  137. package/dist/components/richtext-editor.d.ts +43 -0
  138. package/dist/components/richtext-editor.d.ts.map +1 -0
  139. package/dist/components/richtext-editor.jsx +319 -0
  140. package/dist/components/richtext-editor.jsx.map +1 -0
  141. package/dist/components/richtext-mode.d.ts +23 -0
  142. package/dist/components/richtext-mode.d.ts.map +1 -0
  143. package/dist/components/richtext-mode.js +36 -0
  144. package/dist/components/richtext-mode.js.map +1 -0
  145. package/dist/components/richtext-render.d.ts +8 -0
  146. package/dist/components/richtext-render.d.ts.map +1 -0
  147. package/dist/components/richtext-render.jsx +33 -0
  148. package/dist/components/richtext-render.jsx.map +1 -0
  149. package/dist/components/richtext-sync.d.ts +37 -0
  150. package/dist/components/richtext-sync.d.ts.map +1 -0
  151. package/dist/components/richtext-sync.js +46 -0
  152. package/dist/components/richtext-sync.js.map +1 -0
  153. package/dist/components/scroll-area.d.ts +5 -0
  154. package/dist/components/scroll-area.d.ts.map +1 -0
  155. package/dist/components/scroll-area.jsx +16 -0
  156. package/dist/components/scroll-area.jsx.map +1 -0
  157. package/dist/components/select.d.ts +36 -0
  158. package/dist/components/select.d.ts.map +1 -0
  159. package/dist/components/select.jsx +87 -0
  160. package/dist/components/select.jsx.map +1 -0
  161. package/dist/components/separator.d.ts +4 -0
  162. package/dist/components/separator.d.ts.map +1 -0
  163. package/dist/components/separator.jsx +6 -0
  164. package/dist/components/separator.jsx.map +1 -0
  165. package/dist/components/sheet.d.ts +29 -0
  166. package/dist/components/sheet.d.ts.map +1 -0
  167. package/dist/components/sheet.jsx +44 -0
  168. package/dist/components/sheet.jsx.map +1 -0
  169. package/dist/components/sidebar.d.ts +70 -0
  170. package/dist/components/sidebar.d.ts.map +1 -0
  171. package/dist/components/sidebar.jsx +245 -0
  172. package/dist/components/sidebar.jsx.map +1 -0
  173. package/dist/components/skeleton.d.ts +3 -0
  174. package/dist/components/skeleton.d.ts.map +1 -0
  175. package/dist/components/skeleton.jsx +6 -0
  176. package/dist/components/skeleton.jsx.map +1 -0
  177. package/dist/components/sonner.d.ts +6 -0
  178. package/dist/components/sonner.d.ts.map +1 -0
  179. package/dist/components/sonner.jsx +29 -0
  180. package/dist/components/sonner.jsx.map +1 -0
  181. package/dist/components/switch.d.ts +4 -0
  182. package/dist/components/switch.d.ts.map +1 -0
  183. package/dist/components/switch.jsx +8 -0
  184. package/dist/components/switch.jsx.map +1 -0
  185. package/dist/components/table.d.ts +10 -0
  186. package/dist/components/table.d.ts.map +1 -0
  187. package/dist/components/table.jsx +21 -0
  188. package/dist/components/table.jsx.map +1 -0
  189. package/dist/components/tabs.d.ts +7 -0
  190. package/dist/components/tabs.d.ts.map +1 -0
  191. package/dist/components/tabs.jsx +14 -0
  192. package/dist/components/tabs.jsx.map +1 -0
  193. package/dist/components/textarea.d.ts +4 -0
  194. package/dist/components/textarea.d.ts.map +1 -0
  195. package/dist/components/textarea.jsx +5 -0
  196. package/dist/components/textarea.jsx.map +1 -0
  197. package/dist/components/tooltip.d.ts +7 -0
  198. package/dist/components/tooltip.d.ts.map +1 -0
  199. package/dist/components/tooltip.jsx +11 -0
  200. package/dist/components/tooltip.jsx.map +1 -0
  201. package/dist/index.d.ts +52 -0
  202. package/dist/index.d.ts.map +1 -0
  203. package/dist/index.js +72 -0
  204. package/dist/index.js.map +1 -0
  205. package/dist/lib/theme.d.ts +11 -0
  206. package/dist/lib/theme.d.ts.map +1 -0
  207. package/dist/lib/theme.js +44 -0
  208. package/dist/lib/theme.js.map +1 -0
  209. package/dist/lib/utils.d.ts +3 -0
  210. package/dist/lib/utils.d.ts.map +1 -0
  211. package/dist/lib/utils.js +6 -0
  212. package/dist/lib/utils.js.map +1 -0
  213. package/dist/styles.css +242 -0
  214. package/package.json +85 -0
  215. package/src/components/accordion.tsx +48 -0
  216. package/src/components/alert-dialog.tsx +113 -0
  217. package/src/components/audit-timeline.tsx +102 -0
  218. package/src/components/avatar.tsx +42 -0
  219. package/src/components/badge.tsx +34 -0
  220. package/src/components/breadcrumb.tsx +99 -0
  221. package/src/components/button.tsx +58 -0
  222. package/src/components/calendar.tsx +176 -0
  223. package/src/components/card.tsx +60 -0
  224. package/src/components/chart.tsx +558 -0
  225. package/src/components/checkbox.tsx +23 -0
  226. package/src/components/combobox.tsx +264 -0
  227. package/src/components/command.tsx +120 -0
  228. package/src/components/date-picker.tsx +221 -0
  229. package/src/components/date-range-input.tsx +295 -0
  230. package/src/components/dialog.tsx +94 -0
  231. package/src/components/diff-view.tsx +182 -0
  232. package/src/components/dropdown-menu.tsx +165 -0
  233. package/src/components/empty.tsx +100 -0
  234. package/src/components/field.tsx +168 -0
  235. package/src/components/file-input.tsx +233 -0
  236. package/src/components/form.tsx +152 -0
  237. package/src/components/info-tooltip.tsx +40 -0
  238. package/src/components/input.tsx +55 -0
  239. package/src/components/json-editor.tsx +210 -0
  240. package/src/components/kbd.tsx +35 -0
  241. package/src/components/key-value-editor.tsx +423 -0
  242. package/src/components/keyboard-shortcuts-help.tsx +136 -0
  243. package/src/components/label.tsx +16 -0
  244. package/src/components/media-preview.tsx +278 -0
  245. package/src/components/multi-file-input.tsx +315 -0
  246. package/src/components/password-input.tsx +50 -0
  247. package/src/components/popover.tsx +26 -0
  248. package/src/components/revision-timeline.tsx +93 -0
  249. package/src/components/richtext-editor.tsx +624 -0
  250. package/src/components/richtext-mode.ts +39 -0
  251. package/src/components/richtext-render.tsx +51 -0
  252. package/src/components/richtext-sync.ts +57 -0
  253. package/src/components/scroll-area.tsx +41 -0
  254. package/src/components/select.tsx +200 -0
  255. package/src/components/separator.tsx +21 -0
  256. package/src/components/sheet.tsx +109 -0
  257. package/src/components/sidebar.tsx +660 -0
  258. package/src/components/skeleton.tsx +9 -0
  259. package/src/components/sonner.tsx +45 -0
  260. package/src/components/switch.tsx +24 -0
  261. package/src/components/table.tsx +93 -0
  262. package/src/components/tabs.tsx +57 -0
  263. package/src/components/textarea.tsx +18 -0
  264. package/src/components/tooltip.tsx +25 -0
  265. package/src/index.ts +342 -0
  266. package/src/lib/theme.ts +45 -0
  267. package/src/lib/utils.ts +6 -0
  268. package/src/styles.css +242 -0
@@ -0,0 +1,51 @@
1
+ // Read-only renderer for HTML/Markdown produced by <RichtextEditor>.
2
+ // Sanitizes HTML through DOMPurify before injection. Markdown is parsed
3
+ // with `marked`, then sanitized. Output is wrapped in Tailwind `prose`
4
+ // utilities so headings/lists/code blocks/etc. get long-form typography.
5
+
6
+ import * as React from 'react'
7
+ import DOMPurify from 'dompurify'
8
+ import { marked } from 'marked'
9
+ import { cn } from '../lib/utils.js'
10
+
11
+ const proseRenderClass =
12
+ 'prose prose-sm max-w-none text-foreground prose-headings:text-foreground ' +
13
+ 'prose-p:text-foreground prose-strong:text-foreground prose-li:text-foreground ' +
14
+ 'prose-code:text-foreground prose-pre:text-foreground prose-pre:bg-muted ' +
15
+ 'prose-blockquote:text-muted-foreground prose-blockquote:border-border ' +
16
+ 'prose-a:text-foreground dark:prose-invert'
17
+
18
+ export interface RichtextRenderProps {
19
+ value: string
20
+ format?: 'html' | 'markdown'
21
+ className?: string
22
+ }
23
+
24
+ function renderHtml(html: string): string {
25
+ // DOMPurify is browser-only; on SSR fall back to escaping.
26
+ if (typeof window === 'undefined') return html
27
+ return DOMPurify.sanitize(html, { USE_PROFILES: { html: true } })
28
+ }
29
+
30
+ export function RichtextRender({
31
+ value,
32
+ format = 'html',
33
+ className,
34
+ }: RichtextRenderProps): React.ReactElement {
35
+ const html = React.useMemo(() => {
36
+ if (!value) return ''
37
+ if (format === 'markdown') {
38
+ // marked.parse is sync when no async extensions are configured.
39
+ const out = marked.parse(value, { async: false }) as string
40
+ return renderHtml(out)
41
+ }
42
+ return renderHtml(value)
43
+ }, [value, format])
44
+
45
+ return (
46
+ <div
47
+ className={cn(proseRenderClass, className)}
48
+ dangerouslySetInnerHTML={{ __html: html }}
49
+ />
50
+ )
51
+ }
@@ -0,0 +1,57 @@
1
+ // Pure helpers for RichtextEditor content sync logic.
2
+ // Extracted so they can be unit-tested without a DOM/React environment.
3
+
4
+ import type { RichtextFormat } from './richtext-editor.js'
5
+
6
+ /**
7
+ * Reads the current content from a Tiptap editor instance.
8
+ * Returns the HTML or Markdown string depending on `format`.
9
+ */
10
+ export function readEditorContent(
11
+ ed: { getHTML(): string; storage: unknown },
12
+ format: RichtextFormat,
13
+ ): string {
14
+ if (format === 'markdown') {
15
+ return (
16
+ (ed.storage as { markdown?: { getMarkdown?(): string } }).markdown?.getMarkdown?.() ?? ''
17
+ )
18
+ }
19
+ return ed.getHTML()
20
+ }
21
+
22
+ /**
23
+ * Returns `true` if the editor content should be replaced with the external
24
+ * `value` (i.e. `editor.commands.setContent(value)` should be called).
25
+ *
26
+ * Returns `false` when:
27
+ *
28
+ * 1. `pendingFormatChange === true` — a format switch was just initiated.
29
+ * In this intermediate React render, `activeFormat` has already been
30
+ * updated to the new format while the external `value` still holds the
31
+ * previous format's string (the parent hasn't re-rendered yet).
32
+ * Calling `setContent` here would write the stale, wrong-format string
33
+ * into the editor and corrupt the content. The flag is cleared after the
34
+ * first effect run, so once `value` catches up the normal diff logic
35
+ * takes over again.
36
+ *
37
+ * 2. `value === lastEmitted` — the parent is just echoing back a value we
38
+ * ourselves emitted via onChange. This is the common race during fast
39
+ * typing: the user keeps typing while React is still committing the
40
+ * previous onChange, so by the time the effect runs the editor is
41
+ * *already* ahead of value. Calling setContent in that frame would
42
+ * rewind the editor to a stale state, dropping the most recent
43
+ * keystrokes and resetting the cursor. When value matches lastEmitted
44
+ * we trust the editor and skip the sync.
45
+ *
46
+ * 3. `editorContent === value` — the editor already has the correct content.
47
+ */
48
+ export function shouldSyncToEditor(
49
+ editorContent: string,
50
+ value: string,
51
+ pendingFormatChange: boolean,
52
+ lastEmitted?: string,
53
+ ): boolean {
54
+ if (pendingFormatChange) return false
55
+ if (lastEmitted !== undefined && value === lastEmitted) return false
56
+ return editorContent !== value
57
+ }
@@ -0,0 +1,41 @@
1
+ import * as React from 'react'
2
+ import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area'
3
+ import { cn } from '../lib/utils.js'
4
+
5
+ export const ScrollArea = React.forwardRef<
6
+ React.ElementRef<typeof ScrollAreaPrimitive.Root>,
7
+ React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
8
+ >(({ className, children, ...props }, ref) => (
9
+ <ScrollAreaPrimitive.Root
10
+ ref={ref}
11
+ className={cn('relative overflow-hidden', className)}
12
+ {...props}
13
+ >
14
+ <ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
15
+ {children}
16
+ </ScrollAreaPrimitive.Viewport>
17
+ <ScrollBar />
18
+ <ScrollAreaPrimitive.Corner />
19
+ </ScrollAreaPrimitive.Root>
20
+ ))
21
+ ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
22
+
23
+ export const ScrollBar = React.forwardRef<
24
+ React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
25
+ React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
26
+ >(({ className, orientation = 'vertical', ...props }, ref) => (
27
+ <ScrollAreaPrimitive.ScrollAreaScrollbar
28
+ ref={ref}
29
+ orientation={orientation}
30
+ className={cn(
31
+ 'flex touch-none select-none transition-colors',
32
+ orientation === 'vertical' && 'h-full w-2.5 border-l border-l-transparent p-[1px]',
33
+ orientation === 'horizontal' && 'h-2.5 flex-col border-t border-t-transparent p-[1px]',
34
+ className,
35
+ )}
36
+ {...props}
37
+ >
38
+ <ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
39
+ </ScrollAreaPrimitive.ScrollAreaScrollbar>
40
+ ))
41
+ ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
@@ -0,0 +1,200 @@
1
+ import * as React from 'react'
2
+ import * as SelectPrimitive from '@radix-ui/react-select'
3
+ import { Check, ChevronDown, ChevronUp } from 'lucide-react'
4
+ import { cn } from '../lib/utils.js'
5
+
6
+ /**
7
+ * Thin wrapper around `SelectPrimitive.Root` that suppresses the spurious
8
+ * empty-string callback Radix fires through its hidden bubble-input while
9
+ * `SelectItem` nodes register lazily via Portal after an external `value`
10
+ * change (e.g. `form.reset()` after an async data load). Without this guard
11
+ * the externally-set value is immediately overwritten with `''`.
12
+ *
13
+ * The empty string is never a legitimate choice from a rendered `SelectItem`,
14
+ * so dropping it is always safe. Call sites should NOT add their own
15
+ * `if (v === '') return` guards — this component handles it centrally.
16
+ */
17
+ function SelectRoot({
18
+ onValueChange,
19
+ ...props
20
+ }: React.ComponentPropsWithoutRef<typeof SelectPrimitive.Root>): React.ReactElement {
21
+ const handleValueChange = React.useCallback(
22
+ (value: string) => {
23
+ if (value !== '') onValueChange?.(value)
24
+ },
25
+ [onValueChange],
26
+ )
27
+ return <SelectPrimitive.Root onValueChange={handleValueChange} {...props} />
28
+ }
29
+ SelectRoot.displayName = 'Select'
30
+
31
+ export const Select = SelectRoot
32
+ export const SelectGroup = SelectPrimitive.Group
33
+ export const SelectValue = SelectPrimitive.Value
34
+
35
+ export const SelectTrigger = React.forwardRef<
36
+ React.ElementRef<typeof SelectPrimitive.Trigger>,
37
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
38
+ >(({ className, children, onMouseEnter, ...props }, ref) => {
39
+ const handleMouseEnter = (e: React.MouseEvent<HTMLButtonElement>): void => {
40
+ // Set native title only when the value span is actually clipped so the
41
+ // tooltip doesn't appear redundantly on short values.
42
+ const valueSpan = e.currentTarget.querySelector<HTMLElement>('span')
43
+ if (valueSpan && valueSpan.scrollWidth > valueSpan.offsetWidth) {
44
+ e.currentTarget.title = valueSpan.textContent ?? ''
45
+ } else {
46
+ e.currentTarget.removeAttribute('title')
47
+ }
48
+ onMouseEnter?.(e)
49
+ }
50
+
51
+ return (
52
+ <SelectPrimitive.Trigger
53
+ ref={ref}
54
+ className={cn(
55
+ 'flex h-9 w-full cursor-pointer items-center justify-between rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
56
+ className,
57
+ )}
58
+ onMouseEnter={handleMouseEnter}
59
+ {...props}
60
+ >
61
+ {children}
62
+ <SelectPrimitive.Icon asChild>
63
+ <ChevronDown className="h-4 w-4 opacity-50" />
64
+ </SelectPrimitive.Icon>
65
+ </SelectPrimitive.Trigger>
66
+ )
67
+ })
68
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
69
+
70
+ export const SelectScrollUpButton = React.forwardRef<
71
+ React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
72
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
73
+ >(({ className, ...props }, ref) => (
74
+ <SelectPrimitive.ScrollUpButton
75
+ ref={ref}
76
+ className={cn('flex cursor-default items-center justify-center py-1', className)}
77
+ {...props}
78
+ >
79
+ <ChevronUp className="h-4 w-4" />
80
+ </SelectPrimitive.ScrollUpButton>
81
+ ))
82
+ SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
83
+
84
+ export const SelectScrollDownButton = React.forwardRef<
85
+ React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
86
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
87
+ >(({ className, ...props }, ref) => (
88
+ <SelectPrimitive.ScrollDownButton
89
+ ref={ref}
90
+ className={cn('flex cursor-default items-center justify-center py-1', className)}
91
+ {...props}
92
+ >
93
+ <ChevronDown className="h-4 w-4" />
94
+ </SelectPrimitive.ScrollDownButton>
95
+ ))
96
+ SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName
97
+
98
+ export const SelectContent = React.forwardRef<
99
+ React.ElementRef<typeof SelectPrimitive.Content>,
100
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
101
+ >(({ className, children, position = 'popper', ...props }, ref) => (
102
+ <SelectPrimitive.Portal>
103
+ <SelectPrimitive.Content
104
+ ref={ref}
105
+ className={cn(
106
+ 'relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md 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=top]:slide-in-from-bottom-2',
107
+ position === 'popper' &&
108
+ 'data-[side=bottom]:translate-y-1 data-[side=top]:-translate-y-1',
109
+ className,
110
+ )}
111
+ position={position}
112
+ {...props}
113
+ >
114
+ <SelectScrollUpButton />
115
+ <SelectPrimitive.Viewport
116
+ className={cn(
117
+ 'p-1',
118
+ position === 'popper' &&
119
+ 'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]',
120
+ )}
121
+ >
122
+ {children}
123
+ </SelectPrimitive.Viewport>
124
+ <SelectScrollDownButton />
125
+ </SelectPrimitive.Content>
126
+ </SelectPrimitive.Portal>
127
+ ))
128
+ SelectContent.displayName = SelectPrimitive.Content.displayName
129
+
130
+ export const SelectLabel = React.forwardRef<
131
+ React.ElementRef<typeof SelectPrimitive.Label>,
132
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
133
+ >(({ className, ...props }, ref) => (
134
+ <SelectPrimitive.Label
135
+ ref={ref}
136
+ className={cn('px-2 py-1.5 text-sm font-semibold', className)}
137
+ {...props}
138
+ />
139
+ ))
140
+ SelectLabel.displayName = SelectPrimitive.Label.displayName
141
+
142
+ export const SelectItem = React.forwardRef<
143
+ React.ElementRef<typeof SelectPrimitive.Item>,
144
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
145
+ >(({ className, children, ...props }, ref) => (
146
+ <SelectPrimitive.Item
147
+ ref={ref}
148
+ className={cn(
149
+ 'relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
150
+ className,
151
+ )}
152
+ title={typeof children === 'string' ? children : undefined}
153
+ {...props}
154
+ >
155
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
156
+ <SelectPrimitive.ItemIndicator>
157
+ <Check className="h-4 w-4" />
158
+ </SelectPrimitive.ItemIndicator>
159
+ </span>
160
+ <SelectPrimitive.ItemText>
161
+ <span className="block truncate">{children}</span>
162
+ </SelectPrimitive.ItemText>
163
+ </SelectPrimitive.Item>
164
+ ))
165
+ SelectItem.displayName = SelectPrimitive.Item.displayName
166
+
167
+ export const SelectSeparator = React.forwardRef<
168
+ React.ElementRef<typeof SelectPrimitive.Separator>,
169
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
170
+ >(({ className, ...props }, ref) => (
171
+ <SelectPrimitive.Separator
172
+ ref={ref}
173
+ className={cn('-mx-1 my-1 h-px bg-muted', className)}
174
+ {...props}
175
+ />
176
+ ))
177
+ SelectSeparator.displayName = SelectPrimitive.Separator.displayName
178
+
179
+ /**
180
+ * Plain native `<select>` styled to match the rest of the kit. Useful when
181
+ * you need a no-JS, SSR-friendly fallback or simple form posts. Most call
182
+ * sites should prefer the Radix-based `Select` family above.
183
+ */
184
+ export type NativeSelectProps = React.SelectHTMLAttributes<HTMLSelectElement>
185
+
186
+ export const NativeSelect = React.forwardRef<HTMLSelectElement, NativeSelectProps>(
187
+ ({ className, children, ...props }, ref) => (
188
+ <select
189
+ ref={ref}
190
+ className={cn(
191
+ 'flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
192
+ className,
193
+ )}
194
+ {...props}
195
+ >
196
+ {children}
197
+ </select>
198
+ ),
199
+ )
200
+ NativeSelect.displayName = 'NativeSelect'
@@ -0,0 +1,21 @@
1
+ import * as React from 'react'
2
+ import * as SeparatorPrimitive from '@radix-ui/react-separator'
3
+ import { cn } from '../lib/utils.js'
4
+
5
+ export const Separator = React.forwardRef<
6
+ React.ElementRef<typeof SeparatorPrimitive.Root>,
7
+ React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
8
+ >(({ className, orientation = 'horizontal', decorative = true, ...props }, ref) => (
9
+ <SeparatorPrimitive.Root
10
+ ref={ref}
11
+ decorative={decorative}
12
+ orientation={orientation}
13
+ className={cn(
14
+ 'shrink-0 bg-border',
15
+ orientation === 'horizontal' ? 'h-px w-full' : 'h-full w-px',
16
+ className,
17
+ )}
18
+ {...props}
19
+ />
20
+ ))
21
+ Separator.displayName = SeparatorPrimitive.Root.displayName
@@ -0,0 +1,109 @@
1
+ // shadcn-style Sheet — side-anchored Dialog. Built on @radix-ui/react-dialog,
2
+ // shares the same primitive as <Dialog>. Variants: top / bottom / left / right.
3
+
4
+ import * as React from 'react'
5
+ import * as SheetPrimitive from '@radix-ui/react-dialog'
6
+ import { cva, type VariantProps } from 'class-variance-authority'
7
+ import { X } from 'lucide-react'
8
+ import { cn } from '../lib/utils.js'
9
+
10
+ export const Sheet = SheetPrimitive.Root
11
+ export const SheetTrigger = SheetPrimitive.Trigger
12
+ export const SheetClose = SheetPrimitive.Close
13
+ export const SheetPortal = SheetPrimitive.Portal
14
+
15
+ export const SheetOverlay = React.forwardRef<
16
+ React.ElementRef<typeof SheetPrimitive.Overlay>,
17
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
18
+ >(({ className, ...props }, ref) => (
19
+ <SheetPrimitive.Overlay
20
+ ref={ref}
21
+ className={cn(
22
+ 'fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
23
+ className,
24
+ )}
25
+ {...props}
26
+ />
27
+ ))
28
+ SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
29
+
30
+ const sheetVariants = cva(
31
+ 'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
32
+ {
33
+ variants: {
34
+ side: {
35
+ top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
36
+ bottom:
37
+ 'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
38
+ left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
39
+ right:
40
+ 'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
41
+ },
42
+ },
43
+ defaultVariants: { side: 'right' },
44
+ },
45
+ )
46
+
47
+ export interface SheetContentProps
48
+ extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
49
+ VariantProps<typeof sheetVariants> {
50
+ /** Set to true to suppress the built-in absolute close button (e.g. when
51
+ * the consumer renders its own close control inside the header). */
52
+ hideCloseButton?: boolean
53
+ }
54
+
55
+ export const SheetContent = React.forwardRef<
56
+ React.ElementRef<typeof SheetPrimitive.Content>,
57
+ SheetContentProps
58
+ >(({ side = 'right', className, children, hideCloseButton, ...props }, ref) => (
59
+ <SheetPortal>
60
+ <SheetOverlay />
61
+ <SheetPrimitive.Content ref={ref} className={cn(sheetVariants({ side }), className)} {...props}>
62
+ {children}
63
+ {!hideCloseButton && (
64
+ <SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
65
+ <X className="size-4" />
66
+ <span className="sr-only">Close</span>
67
+ </SheetPrimitive.Close>
68
+ )}
69
+ </SheetPrimitive.Content>
70
+ </SheetPortal>
71
+ ))
72
+ SheetContent.displayName = SheetPrimitive.Content.displayName
73
+
74
+ export const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): React.ReactElement => (
75
+ <div className={cn('flex flex-col space-y-2 text-center sm:text-left', className)} {...props} />
76
+ )
77
+ SheetHeader.displayName = 'SheetHeader'
78
+
79
+ export const SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): React.ReactElement => (
80
+ <div
81
+ className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
82
+ {...props}
83
+ />
84
+ )
85
+ SheetFooter.displayName = 'SheetFooter'
86
+
87
+ export const SheetTitle = React.forwardRef<
88
+ React.ElementRef<typeof SheetPrimitive.Title>,
89
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
90
+ >(({ className, ...props }, ref) => (
91
+ <SheetPrimitive.Title
92
+ ref={ref}
93
+ className={cn('text-lg font-semibold text-foreground', className)}
94
+ {...props}
95
+ />
96
+ ))
97
+ SheetTitle.displayName = SheetPrimitive.Title.displayName
98
+
99
+ export const SheetDescription = React.forwardRef<
100
+ React.ElementRef<typeof SheetPrimitive.Description>,
101
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
102
+ >(({ className, ...props }, ref) => (
103
+ <SheetPrimitive.Description
104
+ ref={ref}
105
+ className={cn('text-sm text-muted-foreground', className)}
106
+ {...props}
107
+ />
108
+ ))
109
+ SheetDescription.displayName = SheetPrimitive.Description.displayName