@opencosmos/ui 1.3.1

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 (260) hide show
  1. package/.claude/CLAUDE.md +239 -0
  2. package/README.md +161 -0
  3. package/dist/cli.mjs +151 -0
  4. package/dist/dates.d.mts +20 -0
  5. package/dist/dates.d.ts +20 -0
  6. package/dist/dates.js +240 -0
  7. package/dist/dates.js.map +1 -0
  8. package/dist/dates.mjs +203 -0
  9. package/dist/dates.mjs.map +1 -0
  10. package/dist/dnd.d.mts +126 -0
  11. package/dist/dnd.d.ts +126 -0
  12. package/dist/dnd.js +274 -0
  13. package/dist/dnd.js.map +1 -0
  14. package/dist/dnd.mjs +250 -0
  15. package/dist/dnd.mjs.map +1 -0
  16. package/dist/fontThemes-Dh8mtXES.d.mts +868 -0
  17. package/dist/fontThemes-Dh8mtXES.d.ts +868 -0
  18. package/dist/forms.d.mts +38 -0
  19. package/dist/forms.d.ts +38 -0
  20. package/dist/forms.js +198 -0
  21. package/dist/forms.js.map +1 -0
  22. package/dist/forms.mjs +159 -0
  23. package/dist/forms.mjs.map +1 -0
  24. package/dist/hooks-1b8WaQf1.d.mts +225 -0
  25. package/dist/hooks-CKW8vE9H.d.ts +225 -0
  26. package/dist/hooks.d.mts +3 -0
  27. package/dist/hooks.d.ts +3 -0
  28. package/dist/hooks.js +971 -0
  29. package/dist/hooks.js.map +1 -0
  30. package/dist/hooks.mjs +943 -0
  31. package/dist/hooks.mjs.map +1 -0
  32. package/dist/index-DscTIrZ2.d.mts +29 -0
  33. package/dist/index-DscTIrZ2.d.ts +29 -0
  34. package/dist/index.d.mts +3382 -0
  35. package/dist/index.d.ts +3382 -0
  36. package/dist/index.js +15146 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/index.mjs +14802 -0
  39. package/dist/index.mjs.map +1 -0
  40. package/dist/providers-CXPDMsl7.d.mts +30 -0
  41. package/dist/providers-Dn_Msjvz.d.ts +30 -0
  42. package/dist/providers.d.mts +3 -0
  43. package/dist/providers.d.ts +3 -0
  44. package/dist/providers.js +1885 -0
  45. package/dist/providers.js.map +1 -0
  46. package/dist/providers.mjs +1859 -0
  47. package/dist/providers.mjs.map +1 -0
  48. package/dist/tables.d.mts +10 -0
  49. package/dist/tables.d.ts +10 -0
  50. package/dist/tables.js +248 -0
  51. package/dist/tables.js.map +1 -0
  52. package/dist/tables.mjs +218 -0
  53. package/dist/tables.mjs.map +1 -0
  54. package/dist/tokens.d.mts +1065 -0
  55. package/dist/tokens.d.ts +1065 -0
  56. package/dist/tokens.js +2637 -0
  57. package/dist/tokens.js.map +1 -0
  58. package/dist/tokens.mjs +2555 -0
  59. package/dist/tokens.mjs.map +1 -0
  60. package/dist/utils-CIIM7dAC.d.ts +986 -0
  61. package/dist/utils-Cs04sxth.d.mts +986 -0
  62. package/dist/utils.d.mts +4 -0
  63. package/dist/utils.d.ts +4 -0
  64. package/dist/utils.js +874 -0
  65. package/dist/utils.js.map +1 -0
  66. package/dist/utils.mjs +806 -0
  67. package/dist/utils.mjs.map +1 -0
  68. package/dist/validation-Bj1ye-v_.d.mts +114 -0
  69. package/dist/validation-Bj1ye-v_.d.ts +114 -0
  70. package/dist/webgl.d.mts +104 -0
  71. package/dist/webgl.d.ts +104 -0
  72. package/dist/webgl.js +226 -0
  73. package/dist/webgl.js.map +1 -0
  74. package/dist/webgl.mjs +195 -0
  75. package/dist/webgl.mjs.map +1 -0
  76. package/package.json +267 -0
  77. package/src/cli.ts +206 -0
  78. package/src/component-registry.ts +183 -0
  79. package/src/components/actions/Button.test.tsx +61 -0
  80. package/src/components/actions/Button.tsx +70 -0
  81. package/src/components/actions/Link.tsx +78 -0
  82. package/src/components/actions/Magnetic.tsx +68 -0
  83. package/src/components/actions/Toggle.test.tsx +40 -0
  84. package/src/components/actions/Toggle.tsx +47 -0
  85. package/src/components/actions/ToggleGroup.tsx +70 -0
  86. package/src/components/actions/index.ts +5 -0
  87. package/src/components/backgrounds/FaultyTerminal.tsx +426 -0
  88. package/src/components/backgrounds/OrbBackground.tsx +424 -0
  89. package/src/components/backgrounds/WarpBackground.tsx +358 -0
  90. package/src/components/backgrounds/index.ts +3 -0
  91. package/src/components/blocks/Hero.tsx +142 -0
  92. package/src/components/blocks/social/OpenGraphCard.tsx +243 -0
  93. package/src/components/cursor/SplashCursor.tsx +1315 -0
  94. package/src/components/cursor/TargetCursor.tsx +187 -0
  95. package/src/components/cursor/index.ts +2 -0
  96. package/src/components/data-display/AspectImage.tsx +73 -0
  97. package/src/components/data-display/Avatar.test.tsx +35 -0
  98. package/src/components/data-display/Avatar.tsx +55 -0
  99. package/src/components/data-display/Badge.test.tsx +43 -0
  100. package/src/components/data-display/Badge.tsx +84 -0
  101. package/src/components/data-display/Brand.tsx +123 -0
  102. package/src/components/data-display/Calendar.tsx +70 -0
  103. package/src/components/data-display/Card.test.tsx +92 -0
  104. package/src/components/data-display/Card.tsx +115 -0
  105. package/src/components/data-display/Code.tsx +210 -0
  106. package/src/components/data-display/CollapsibleCodeBlock.tsx +238 -0
  107. package/src/components/data-display/DataTable.tsx +119 -0
  108. package/src/components/data-display/DescriptionList.tsx +41 -0
  109. package/src/components/data-display/GitHubIcon.tsx +44 -0
  110. package/src/components/data-display/Heading.test.tsx +36 -0
  111. package/src/components/data-display/Heading.tsx +83 -0
  112. package/src/components/data-display/StatCard.tsx +195 -0
  113. package/src/components/data-display/Table.tsx +133 -0
  114. package/src/components/data-display/Text.test.tsx +48 -0
  115. package/src/components/data-display/Text.tsx +144 -0
  116. package/src/components/data-display/Timeline.tsx +194 -0
  117. package/src/components/data-display/TreeView.tsx +226 -0
  118. package/src/components/data-display/Typewriter.tsx +119 -0
  119. package/src/components/data-display/VariableWeightText.tsx +130 -0
  120. package/src/components/data-display/index.ts +19 -0
  121. package/src/components/feedback/Alert.test.tsx +44 -0
  122. package/src/components/feedback/Alert.tsx +65 -0
  123. package/src/components/feedback/EmptyState.tsx +113 -0
  124. package/src/components/feedback/Progress.test.tsx +60 -0
  125. package/src/components/feedback/Progress.tsx +30 -0
  126. package/src/components/feedback/ProgressBar.tsx +158 -0
  127. package/src/components/feedback/Skeleton.test.tsx +39 -0
  128. package/src/components/feedback/Skeleton.tsx +45 -0
  129. package/src/components/feedback/Sonner.tsx +28 -0
  130. package/src/components/feedback/Spinner.test.tsx +33 -0
  131. package/src/components/feedback/Spinner.tsx +99 -0
  132. package/src/components/feedback/Stepper.tsx +307 -0
  133. package/src/components/feedback/Toast/Toast.tsx +243 -0
  134. package/src/components/feedback/Toast/index.ts +2 -0
  135. package/src/components/feedback/index.ts +9 -0
  136. package/src/components/forms/Checkbox.test.tsx +40 -0
  137. package/src/components/forms/Checkbox.tsx +31 -0
  138. package/src/components/forms/ColorPicker.tsx +118 -0
  139. package/src/components/forms/Combobox.tsx +96 -0
  140. package/src/components/forms/DragDrop.tsx +440 -0
  141. package/src/components/forms/FileUpload.tsx +252 -0
  142. package/src/components/forms/FilterButton.tsx +65 -0
  143. package/src/components/forms/Form.tsx +197 -0
  144. package/src/components/forms/Input.test.tsx +46 -0
  145. package/src/components/forms/Input.tsx +43 -0
  146. package/src/components/forms/InputOTP.tsx +81 -0
  147. package/src/components/forms/Label.test.tsx +20 -0
  148. package/src/components/forms/Label.tsx +25 -0
  149. package/src/components/forms/RadioGroup.tsx +51 -0
  150. package/src/components/forms/SearchBar.tsx +215 -0
  151. package/src/components/forms/Select.test.tsx +118 -0
  152. package/src/components/forms/Select.tsx +274 -0
  153. package/src/components/forms/Slider.tsx +29 -0
  154. package/src/components/forms/Switch.test.tsx +76 -0
  155. package/src/components/forms/Switch.tsx +30 -0
  156. package/src/components/forms/TextField.tsx +152 -0
  157. package/src/components/forms/Textarea.test.tsx +41 -0
  158. package/src/components/forms/Textarea.tsx +29 -0
  159. package/src/components/forms/ThemeSwitcher.tsx +290 -0
  160. package/src/components/forms/ThemeToggle.tsx +151 -0
  161. package/src/components/forms/index.ts +19 -0
  162. package/src/components/layout/Accordion.test.tsx +66 -0
  163. package/src/components/layout/Accordion.tsx +64 -0
  164. package/src/components/layout/AspectRatio.tsx +7 -0
  165. package/src/components/layout/Carousel.tsx +277 -0
  166. package/src/components/layout/Collapsible.test.tsx +40 -0
  167. package/src/components/layout/Collapsible.tsx +31 -0
  168. package/src/components/layout/Container.test.tsx +45 -0
  169. package/src/components/layout/Container.tsx +99 -0
  170. package/src/components/layout/CustomizerPanel.tsx +400 -0
  171. package/src/components/layout/DatePicker.tsx +57 -0
  172. package/src/components/layout/Footer/Footer.tsx +175 -0
  173. package/src/components/layout/Footer/index.ts +2 -0
  174. package/src/components/layout/GlassSurface.tsx +82 -0
  175. package/src/components/layout/Grid.test.tsx +31 -0
  176. package/src/components/layout/Grid.tsx +130 -0
  177. package/src/components/layout/Header/Header.tsx +450 -0
  178. package/src/components/layout/Header/index.ts +2 -0
  179. package/src/components/layout/PageLayout.tsx +180 -0
  180. package/src/components/layout/PageTemplate.tsx +158 -0
  181. package/src/components/layout/Resizable.tsx +48 -0
  182. package/src/components/layout/ScrollArea.tsx +53 -0
  183. package/src/components/layout/Separator.test.tsx +28 -0
  184. package/src/components/layout/Separator.tsx +29 -0
  185. package/src/components/layout/Sidebar.tsx +171 -0
  186. package/src/components/layout/Stack.test.tsx +41 -0
  187. package/src/components/layout/Stack.tsx +89 -0
  188. package/src/components/layout/glass-surface.css +60 -0
  189. package/src/components/layout/index.ts +18 -0
  190. package/src/components/motion/AnimatedBeam.tsx +159 -0
  191. package/src/components/navigation/Breadcrumb.test.tsx +57 -0
  192. package/src/components/navigation/Breadcrumb.tsx +119 -0
  193. package/src/components/navigation/Breadcrumbs.tsx +221 -0
  194. package/src/components/navigation/Command.tsx +159 -0
  195. package/src/components/navigation/Menubar.tsx +115 -0
  196. package/src/components/navigation/NavLink.tsx +55 -0
  197. package/src/components/navigation/NavigationMenu.tsx +125 -0
  198. package/src/components/navigation/Pagination.tsx +121 -0
  199. package/src/components/navigation/SecondaryNav.tsx +100 -0
  200. package/src/components/navigation/Tabs.test.tsx +47 -0
  201. package/src/components/navigation/Tabs.tsx +60 -0
  202. package/src/components/navigation/TertiaryNav.tsx +90 -0
  203. package/src/components/navigation/index.ts +10 -0
  204. package/src/components/overlays/AlertDialog.test.tsx +69 -0
  205. package/src/components/overlays/AlertDialog.tsx +166 -0
  206. package/src/components/overlays/ContextMenu.tsx +243 -0
  207. package/src/components/overlays/Dialog.test.tsx +79 -0
  208. package/src/components/overlays/Dialog.tsx +158 -0
  209. package/src/components/overlays/Drawer.tsx +128 -0
  210. package/src/components/overlays/Dropdown.tsx +253 -0
  211. package/src/components/overlays/DropdownMenu.tsx +242 -0
  212. package/src/components/overlays/HoverCard.tsx +32 -0
  213. package/src/components/overlays/Modal.tsx +250 -0
  214. package/src/components/overlays/NotificationCenter.tsx +364 -0
  215. package/src/components/overlays/Popover.test.tsx +40 -0
  216. package/src/components/overlays/Popover.tsx +46 -0
  217. package/src/components/overlays/Sheet.tsx +163 -0
  218. package/src/components/overlays/Tooltip.test.tsx +33 -0
  219. package/src/components/overlays/Tooltip.tsx +32 -0
  220. package/src/components/overlays/index.ts +12 -0
  221. package/src/dates.ts +2 -0
  222. package/src/dnd.ts +1 -0
  223. package/src/forms.ts +1 -0
  224. package/src/globals.css +187 -0
  225. package/src/hooks/index.ts +6 -0
  226. package/src/hooks/useForm.ts +247 -0
  227. package/src/hooks/useMotionPreference.test.ts +102 -0
  228. package/src/hooks/useMotionPreference.ts +78 -0
  229. package/src/hooks/useTheme.ts +58 -0
  230. package/src/hooks.ts +9 -0
  231. package/src/index.ts +168 -0
  232. package/src/lib/animations.ts +356 -0
  233. package/src/lib/breadcrumbs.ts +94 -0
  234. package/src/lib/colors.ts +493 -0
  235. package/src/lib/store/customizer.ts +482 -0
  236. package/src/lib/store/index.ts +3 -0
  237. package/src/lib/store/theme.ts +55 -0
  238. package/src/lib/syntax-parser/index.ts +50 -0
  239. package/src/lib/syntax-parser/patterns.ts +64 -0
  240. package/src/lib/syntax-parser/tokenizer.ts +117 -0
  241. package/src/lib/syntax-parser/types.ts +27 -0
  242. package/src/lib/utils.ts +6 -0
  243. package/src/lib/validation.ts +204 -0
  244. package/src/lib/webgl/Color.ts +11 -0
  245. package/src/lib/webgl/Mesh.ts +41 -0
  246. package/src/lib/webgl/Program.ts +118 -0
  247. package/src/lib/webgl/Renderer.ts +51 -0
  248. package/src/lib/webgl/Triangle.ts +27 -0
  249. package/src/lib/webgl/Vec3.ts +18 -0
  250. package/src/lib/webgl/index.ts +13 -0
  251. package/src/nativewind-env.d.ts +1 -0
  252. package/src/providers/ThemeProvider.tsx +461 -0
  253. package/src/providers/index.ts +1 -0
  254. package/src/providers.ts +7 -0
  255. package/src/tables.ts +1 -0
  256. package/src/test/setup.ts +39 -0
  257. package/src/theme.css +158 -0
  258. package/src/tokens.ts +7 -0
  259. package/src/utils.ts +12 -0
  260. package/src/webgl.ts +1 -0
@@ -0,0 +1,79 @@
1
+ import { render, screen } from '@testing-library/react'
2
+ import userEvent from '@testing-library/user-event'
3
+ import { describe, it, expect } from 'vitest'
4
+ import {
5
+ Dialog,
6
+ DialogTrigger,
7
+ DialogContent,
8
+ DialogTitle,
9
+ DialogDescription,
10
+ DialogClose,
11
+ } from './Dialog'
12
+
13
+ describe('Dialog', () => {
14
+ it('renders trigger and opens on click', async () => {
15
+ const user = userEvent.setup()
16
+ render(
17
+ <Dialog>
18
+ <DialogTrigger>Open Dialog</DialogTrigger>
19
+ <DialogContent>
20
+ <DialogTitle>Dialog Title</DialogTitle>
21
+ <DialogDescription>Dialog description text</DialogDescription>
22
+ </DialogContent>
23
+ </Dialog>
24
+ )
25
+
26
+ expect(screen.getByRole('button', { name: /open dialog/i })).toBeInTheDocument()
27
+ expect(screen.queryByText('Dialog Title')).not.toBeInTheDocument()
28
+
29
+ await user.click(screen.getByRole('button', { name: /open dialog/i }))
30
+ expect(screen.getByText('Dialog Title')).toBeInTheDocument()
31
+ })
32
+
33
+ it('renders content when open', () => {
34
+ render(
35
+ <Dialog open>
36
+ <DialogContent>
37
+ <DialogTitle>Visible Title</DialogTitle>
38
+ <DialogDescription>Visible description</DialogDescription>
39
+ </DialogContent>
40
+ </Dialog>
41
+ )
42
+
43
+ expect(screen.getByText('Visible Title')).toBeInTheDocument()
44
+ expect(screen.getByText('Visible description')).toBeInTheDocument()
45
+ })
46
+
47
+ it('closes on close button click', async () => {
48
+ const user = userEvent.setup()
49
+ render(
50
+ <Dialog>
51
+ <DialogTrigger>Open</DialogTrigger>
52
+ <DialogContent>
53
+ <DialogTitle>Title</DialogTitle>
54
+ <DialogClose>Close Dialog</DialogClose>
55
+ </DialogContent>
56
+ </Dialog>
57
+ )
58
+
59
+ await user.click(screen.getByRole('button', { name: /open/i }))
60
+ expect(screen.getByText('Title')).toBeInTheDocument()
61
+
62
+ await user.click(screen.getByRole('button', { name: /close dialog/i }))
63
+ expect(screen.queryByText('Title')).not.toBeInTheDocument()
64
+ })
65
+
66
+ it('renders title and description', () => {
67
+ render(
68
+ <Dialog open>
69
+ <DialogContent>
70
+ <DialogTitle>My Title</DialogTitle>
71
+ <DialogDescription>My Description</DialogDescription>
72
+ </DialogContent>
73
+ </Dialog>
74
+ )
75
+
76
+ expect(screen.getByText('My Title')).toBeInTheDocument()
77
+ expect(screen.getByText('My Description')).toBeInTheDocument()
78
+ })
79
+ })
@@ -0,0 +1,158 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as DialogPrimitive from "@radix-ui/react-dialog"
5
+ import { X } from "lucide-react"
6
+
7
+ import { cn } from "../../lib/utils"
8
+
9
+ function Dialog({
10
+ ...props
11
+ }: React.ComponentProps<typeof DialogPrimitive.Root>) {
12
+ return <DialogPrimitive.Root data-slot="dialog" {...props} />
13
+ }
14
+
15
+ function DialogTrigger({
16
+ ...props
17
+ }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
18
+ return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
19
+ }
20
+
21
+ function DialogPortal({
22
+ ...props
23
+ }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
24
+ return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
25
+ }
26
+
27
+ function DialogClose({
28
+ ...props
29
+ }: React.ComponentProps<typeof DialogPrimitive.Close>) {
30
+ return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
31
+ }
32
+
33
+ function DialogOverlay({
34
+ className,
35
+ style,
36
+ ...props
37
+ }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
38
+ return (
39
+ <DialogPrimitive.Overlay
40
+ data-slot="dialog-overlay"
41
+ className={cn(
42
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
43
+ className
44
+ )}
45
+ style={{
46
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
47
+ zIndex: 50,
48
+ ...style,
49
+ }}
50
+ {...props}
51
+ />
52
+ )
53
+ }
54
+
55
+ function DialogContent({
56
+ className,
57
+ children,
58
+ showCloseButton = true,
59
+ style,
60
+ ...props
61
+ }: React.ComponentProps<typeof DialogPrimitive.Content> & {
62
+ showCloseButton?: boolean
63
+ }) {
64
+ return (
65
+ <DialogPortal data-slot="dialog-portal">
66
+ <DialogOverlay />
67
+ <DialogPrimitive.Content
68
+ data-slot="dialog-content"
69
+ className={cn(
70
+ "bg-background 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 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",
71
+ className
72
+ )}
73
+ style={{
74
+ backgroundColor: 'var(--color-background, #ffffff)',
75
+ border: '1px solid var(--color-border, #d4d4d4)',
76
+ borderRadius: '0.75rem',
77
+ boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)',
78
+ zIndex: 50,
79
+ ...style,
80
+ }}
81
+ {...props}
82
+ >
83
+ {children}
84
+ {showCloseButton && (
85
+ <DialogPrimitive.Close
86
+ data-slot="dialog-close"
87
+ className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
88
+ >
89
+ <X />
90
+ <span className="sr-only">Close</span>
91
+ </DialogPrimitive.Close>
92
+ )}
93
+ </DialogPrimitive.Content>
94
+ </DialogPortal>
95
+ )
96
+ }
97
+
98
+ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
99
+ return (
100
+ <div
101
+ data-slot="dialog-header"
102
+ className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
103
+ {...props}
104
+ />
105
+ )
106
+ }
107
+
108
+ function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
109
+ return (
110
+ <div
111
+ data-slot="dialog-footer"
112
+ className={cn(
113
+ "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
114
+ className
115
+ )}
116
+ {...props}
117
+ />
118
+ )
119
+ }
120
+
121
+ function DialogTitle({
122
+ className,
123
+ ...props
124
+ }: React.ComponentProps<typeof DialogPrimitive.Title>) {
125
+ return (
126
+ <DialogPrimitive.Title
127
+ data-slot="dialog-title"
128
+ className={cn("text-lg leading-none font-semibold", className)}
129
+ {...props}
130
+ />
131
+ )
132
+ }
133
+
134
+ function DialogDescription({
135
+ className,
136
+ ...props
137
+ }: React.ComponentProps<typeof DialogPrimitive.Description>) {
138
+ return (
139
+ <DialogPrimitive.Description
140
+ data-slot="dialog-description"
141
+ className={cn("text-muted-foreground text-sm", className)}
142
+ {...props}
143
+ />
144
+ )
145
+ }
146
+
147
+ export {
148
+ Dialog,
149
+ DialogClose,
150
+ DialogContent,
151
+ DialogDescription,
152
+ DialogFooter,
153
+ DialogHeader,
154
+ DialogOverlay,
155
+ DialogPortal,
156
+ DialogTitle,
157
+ DialogTrigger,
158
+ }
@@ -0,0 +1,128 @@
1
+ "use client";
2
+ import * as React from "react"
3
+ import { Drawer as DrawerPrimitive } from "vaul"
4
+
5
+ import { cn } from "../../lib/utils"
6
+
7
+ const Drawer: typeof DrawerPrimitive.Root = DrawerPrimitive.Root
8
+
9
+ const DrawerTrigger: typeof DrawerPrimitive.Trigger = DrawerPrimitive.Trigger
10
+
11
+ const DrawerPortal: typeof DrawerPrimitive.Portal = DrawerPrimitive.Portal
12
+
13
+ const DrawerClose: typeof DrawerPrimitive.Close = DrawerPrimitive.Close
14
+
15
+ const DrawerOverlay = (
16
+ {
17
+ ref,
18
+ className,
19
+ style,
20
+ ...props
21
+ }: React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay> & {
22
+ ref?: React.Ref<React.ElementRef<typeof DrawerPrimitive.Overlay>>;
23
+ }
24
+ ) => (<DrawerPrimitive.Overlay
25
+ ref={ref}
26
+ className={cn("fixed inset-0 z-50 bg-black/80", className)}
27
+ style={{
28
+ backgroundColor: 'rgba(0, 0, 0, 0.8)',
29
+ zIndex: 50,
30
+ ...style,
31
+ }}
32
+ {...props}
33
+ />)
34
+
35
+ const DrawerContent = (
36
+ {
37
+ ref,
38
+ className,
39
+ children,
40
+ style,
41
+ ...props
42
+ }: React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content> & {
43
+ ref?: React.Ref<React.ElementRef<typeof DrawerPrimitive.Content>>;
44
+ }
45
+ ) => (<DrawerPortal>
46
+ <DrawerOverlay />
47
+ <DrawerPrimitive.Content
48
+ ref={ref}
49
+ className={cn(
50
+ "fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
51
+ className
52
+ )}
53
+ style={{
54
+ backgroundColor: 'var(--color-background, #ffffff)',
55
+ border: '1px solid var(--color-border, #d4d4d4)',
56
+ borderRadius: '10px 10px 0 0',
57
+ zIndex: 50,
58
+ ...style,
59
+ }}
60
+ {...props}
61
+ >
62
+ <div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
63
+ {children}
64
+ </DrawerPrimitive.Content>
65
+ </DrawerPortal>)
66
+
67
+ const DrawerHeader = ({
68
+ className,
69
+ ...props
70
+ }: React.HTMLAttributes<HTMLDivElement>) => (
71
+ <div
72
+ className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
73
+ {...props}
74
+ />
75
+ )
76
+ DrawerHeader.displayName = "DrawerHeader"
77
+
78
+ const DrawerFooter = ({
79
+ className,
80
+ ...props
81
+ }: React.HTMLAttributes<HTMLDivElement>) => (
82
+ <div className={cn("mt-auto flex flex-col gap-2 p-4", className)} {...props} />
83
+ )
84
+ DrawerFooter.displayName = "DrawerFooter"
85
+
86
+ const DrawerTitle = (
87
+ {
88
+ ref,
89
+ className,
90
+ ...props
91
+ }: React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title> & {
92
+ ref?: React.Ref<React.ElementRef<typeof DrawerPrimitive.Title>>;
93
+ }
94
+ ) => (<DrawerPrimitive.Title
95
+ ref={ref}
96
+ className={cn(
97
+ "text-lg font-semibold leading-none tracking-tight",
98
+ className
99
+ )}
100
+ {...props}
101
+ />)
102
+
103
+ const DrawerDescription = (
104
+ {
105
+ ref,
106
+ className,
107
+ ...props
108
+ }: React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description> & {
109
+ ref?: React.Ref<React.ElementRef<typeof DrawerPrimitive.Description>>;
110
+ }
111
+ ) => (<DrawerPrimitive.Description
112
+ ref={ref}
113
+ className={cn("text-sm text-muted-foreground", className)}
114
+ {...props}
115
+ />)
116
+
117
+ export {
118
+ Drawer,
119
+ DrawerPortal,
120
+ DrawerOverlay,
121
+ DrawerTrigger,
122
+ DrawerClose,
123
+ DrawerContent,
124
+ DrawerHeader,
125
+ DrawerFooter,
126
+ DrawerTitle,
127
+ DrawerDescription,
128
+ }
@@ -0,0 +1,253 @@
1
+ 'use client';
2
+
3
+ import React, { useState, useRef, useEffect } from 'react';
4
+ import { useMotionPreference } from '../../hooks';
5
+
6
+ export interface DropdownItem {
7
+ label: string;
8
+ value: string;
9
+ disabled?: boolean;
10
+ icon?: React.ReactNode;
11
+ divider?: boolean;
12
+ }
13
+
14
+ export interface DropdownProps {
15
+ /**
16
+ * Trigger element (button, link, etc.)
17
+ */
18
+ trigger: React.ReactNode;
19
+
20
+ /**
21
+ * Dropdown menu items
22
+ */
23
+ items: DropdownItem[];
24
+
25
+ /**
26
+ * Callback when an item is selected
27
+ */
28
+ onSelect?: (value: string) => void;
29
+
30
+ /**
31
+ * Dropdown alignment relative to trigger
32
+ * @default 'left'
33
+ */
34
+ align?: 'left' | 'right' | 'center';
35
+
36
+ /**
37
+ * Additional CSS classes for the dropdown menu
38
+ */
39
+ className?: string;
40
+ }
41
+
42
+ /**
43
+ * Dropdown Component
44
+ *
45
+ * A menu that appears when clicking a trigger element.
46
+ *
47
+ * Features:
48
+ * - Click outside to close
49
+ * - Keyboard navigation (Arrow keys, Enter, Escape)
50
+ * - Flexible trigger element
51
+ * - Optional icons
52
+ * - Dividers between items
53
+ * - Theme-aware styling
54
+ * - Smooth animations
55
+ *
56
+ * Example:
57
+ * ```tsx
58
+ * <Dropdown
59
+ * trigger={<Button>Actions</Button>}
60
+ * items={[
61
+ * { label: 'Edit', value: 'edit', icon: <EditIcon /> },
62
+ * { label: 'Delete', value: 'delete', icon: <DeleteIcon /> },
63
+ * { label: 'Divider', value: 'div', divider: true },
64
+ * { label: 'Archive', value: 'archive' },
65
+ * ]}
66
+ * onSelect={(value) => handleAction(value)}
67
+ * />
68
+ * ```
69
+ */
70
+ export const Dropdown: React.FC<DropdownProps> = ({
71
+ trigger,
72
+ items,
73
+ onSelect,
74
+ align = 'left',
75
+ className = '',
76
+ }) => {
77
+ const [isOpen, setIsOpen] = useState(false);
78
+ const [focusedIndex, setFocusedIndex] = useState(-1);
79
+ const dropdownRef = useRef<HTMLDivElement>(null);
80
+ const { shouldAnimate, scale } = useMotionPreference();
81
+
82
+ const animationDuration = shouldAnimate && scale > 0 ? `${0.15 * (5 / scale)}s` : '0s';
83
+
84
+ // Close on click outside
85
+ useEffect(() => {
86
+ const handleClickOutside = (event: MouseEvent) => {
87
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
88
+ setIsOpen(false);
89
+ setFocusedIndex(-1);
90
+ }
91
+ };
92
+
93
+ if (isOpen) {
94
+ document.addEventListener('mousedown', handleClickOutside);
95
+ }
96
+
97
+ return () => {
98
+ document.removeEventListener('mousedown', handleClickOutside);
99
+ };
100
+ }, [isOpen]);
101
+
102
+ // Handle keyboard navigation
103
+ useEffect(() => {
104
+ if (!isOpen) return;
105
+
106
+ const handleKeyDown = (e: KeyboardEvent) => {
107
+ const selectableItems = items.filter((item) => !item.disabled && !item.divider);
108
+
109
+ switch (e.key) {
110
+ case 'ArrowDown':
111
+ e.preventDefault();
112
+ setFocusedIndex((prev) => {
113
+ const next = prev + 1;
114
+ return next >= selectableItems.length ? 0 : next;
115
+ });
116
+ break;
117
+ case 'ArrowUp':
118
+ e.preventDefault();
119
+ setFocusedIndex((prev) => {
120
+ const next = prev - 1;
121
+ return next < 0 ? selectableItems.length - 1 : next;
122
+ });
123
+ break;
124
+ case 'Enter':
125
+ e.preventDefault();
126
+ if (focusedIndex >= 0 && focusedIndex < selectableItems.length) {
127
+ const item = selectableItems[focusedIndex];
128
+ onSelect?.(item.value);
129
+ setIsOpen(false);
130
+ setFocusedIndex(-1);
131
+ }
132
+ break;
133
+ case 'Escape':
134
+ e.preventDefault();
135
+ setIsOpen(false);
136
+ setFocusedIndex(-1);
137
+ break;
138
+ }
139
+ };
140
+
141
+ document.addEventListener('keydown', handleKeyDown);
142
+ return () => document.removeEventListener('keydown', handleKeyDown);
143
+ }, [isOpen, focusedIndex, items, onSelect]);
144
+
145
+ const handleItemClick = (item: DropdownItem) => {
146
+ if (item.disabled || item.divider) return;
147
+ onSelect?.(item.value);
148
+ setIsOpen(false);
149
+ setFocusedIndex(-1);
150
+ };
151
+
152
+ const alignClasses = {
153
+ left: 'left-0',
154
+ right: 'right-0',
155
+ center: 'left-1/2 -translate-x-1/2',
156
+ };
157
+
158
+ return (
159
+ <div ref={dropdownRef} className="relative inline-block">
160
+ <div onClick={() => setIsOpen(!isOpen)} role="button" tabIndex={0}>
161
+ {trigger}
162
+ </div>
163
+
164
+ {isOpen && (
165
+ <div
166
+ className={`
167
+ absolute top-full mt-2 ${alignClasses[align]}
168
+ min-w-[200px]
169
+ bg-[var(--color-background)]/80
170
+ backdrop-blur-md
171
+ border border-[var(--color-border)]
172
+ rounded-lg
173
+ shadow-lg
174
+ py-2
175
+ z-50
176
+ ${shouldAnimate ? 'animate-dropdown-in' : ''}
177
+ ${className}
178
+ `}
179
+ style={{ animationDuration }}
180
+ role="menu"
181
+ aria-orientation="vertical"
182
+ >
183
+ {items.map((item, index) => {
184
+ if (item.divider) {
185
+ return (
186
+ <div
187
+ key={`divider-${index}`}
188
+ className="my-2 border-t border-[var(--color-border)]"
189
+ role="separator"
190
+ />
191
+ );
192
+ }
193
+
194
+ const selectableItems = items.filter((i) => !i.disabled && !i.divider);
195
+ const selectableIndex = selectableItems.indexOf(item);
196
+ const isFocused = selectableIndex === focusedIndex;
197
+
198
+ return (
199
+ <button
200
+ key={item.value}
201
+ onClick={() => handleItemClick(item)}
202
+ disabled={item.disabled}
203
+ className={`
204
+ w-full flex items-center gap-3 px-4 py-2 text-sm
205
+ text-left transition-colors
206
+ ${item.disabled
207
+ ? 'opacity-50 cursor-not-allowed'
208
+ : `
209
+ text-[var(--color-text-primary)]
210
+ hover:bg-[var(--color-hover)]
211
+ ${isFocused ? 'bg-[var(--color-hover)]' : ''}
212
+ `
213
+ }
214
+ `}
215
+ role="menuitem"
216
+ tabIndex={-1}
217
+ >
218
+ {item.icon && <span className="flex-shrink-0">{item.icon}</span>}
219
+ <span className="flex-1">{item.label}</span>
220
+ </button>
221
+ );
222
+ })}
223
+ </div>
224
+ )}
225
+ </div>
226
+ );
227
+ };
228
+
229
+ Dropdown.displayName = 'Dropdown';
230
+
231
+ // Add animation keyframes
232
+ if (typeof document !== 'undefined') {
233
+ const style = document.createElement('style');
234
+ style.textContent = `
235
+ @keyframes dropdown-in {
236
+ from {
237
+ opacity: 0;
238
+ transform: translateY(-8px);
239
+ }
240
+ to {
241
+ opacity: 1;
242
+ transform: translateY(0);
243
+ }
244
+ }
245
+ .animate-dropdown-in {
246
+ animation: dropdown-in 0.15s ease-out;
247
+ }
248
+ `;
249
+ if (!document.querySelector('style[data-dropdown-animations]')) {
250
+ style.setAttribute('data-dropdown-animations', 'true');
251
+ document.head.appendChild(style);
252
+ }
253
+ }