@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,65 @@
1
+ import React from 'react';
2
+
3
+ export interface FilterButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
4
+ active?: boolean;
5
+ count?: number;
6
+ children: React.ReactNode;
7
+ /**
8
+ * Shape of the button
9
+ * @default 'pill'
10
+ */
11
+ shape?: 'pill' | 'rounded';
12
+ }
13
+
14
+ export const FilterButton = (
15
+ {
16
+ ref,
17
+ active = false,
18
+ count,
19
+ children,
20
+ shape = 'pill',
21
+ className = '',
22
+ ...props
23
+ }: FilterButtonProps & {
24
+ ref?: React.Ref<HTMLButtonElement>;
25
+ }
26
+ ) => {
27
+ const shapes = {
28
+ pill: "rounded-full",
29
+ rounded: "rounded-lg"
30
+ };
31
+
32
+ return (
33
+ <button
34
+ ref={ref}
35
+ className={`
36
+ flex items-center gap-2 px-4 py-2 ${shapes[shape]} text-sm font-medium transition-all duration-200
37
+ border
38
+ ${active
39
+ ? 'bg-[var(--color-primary)] border-[var(--color-primary)] text-[var(--color-primary-foreground)] shadow-[var(--effect-shadow-sm)]'
40
+ : 'bg-[var(--color-surface)] border-[var(--color-border)] text-[var(--color-text-secondary)] hover:border-[var(--color-primary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-background)]'
41
+ }
42
+ focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus)] focus-visible:ring-offset-2
43
+ disabled:opacity-50 disabled:cursor-not-allowed
44
+ ${className}
45
+ `}
46
+ aria-pressed={active}
47
+ {...props}
48
+ >
49
+ <span>{children}</span>
50
+ {count !== undefined && (
51
+ <span
52
+ className={`
53
+ flex items-center justify-center min-w-[1.25rem] h-5 px-1.5 rounded-full text-xs font-bold
54
+ ${active
55
+ ? 'bg-[var(--color-background)] text-[var(--color-primary)]'
56
+ : 'bg-[var(--color-border)] text-[var(--color-text-secondary)]'
57
+ }
58
+ `}
59
+ >
60
+ {count}
61
+ </span>
62
+ )}
63
+ </button>
64
+ );
65
+ };
@@ -0,0 +1,197 @@
1
+ "use client";
2
+ import * as React from "react"
3
+ import * as LabelPrimitive from "@radix-ui/react-label"
4
+ import { Slot } from "@radix-ui/react-slot"
5
+ import {
6
+ Controller,
7
+ ControllerProps,
8
+ FieldPath,
9
+ FieldValues,
10
+ FormProvider,
11
+ useFormContext,
12
+ } from "react-hook-form"
13
+
14
+ import { cn } from "../../lib/utils"
15
+ import { Label } from "./Label"
16
+
17
+ const Form: typeof FormProvider = 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>(
27
+ {} as FormFieldContextValue
28
+ )
29
+
30
+ const FormField = <
31
+ TFieldValues extends FieldValues = FieldValues,
32
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
33
+ >({
34
+ ...props
35
+ }: ControllerProps<TFieldValues, TName>) => {
36
+ return (
37
+ <FormFieldContext.Provider value={{ name: props.name }}>
38
+ <Controller {...props} />
39
+ </FormFieldContext.Provider>
40
+ )
41
+ }
42
+
43
+ const useFormField = () => {
44
+ const fieldContext = React.useContext(FormFieldContext)
45
+ const itemContext = React.useContext(FormItemContext)
46
+ const { getFieldState, formState } = useFormContext()
47
+
48
+ const fieldState = getFieldState(fieldContext.name, formState)
49
+
50
+ if (!fieldContext) {
51
+ throw new Error("useFormField should be used within <FormField>")
52
+ }
53
+
54
+ const { id } = itemContext
55
+
56
+ return {
57
+ id,
58
+ name: fieldContext.name,
59
+ formItemId: `${id}-form-item`,
60
+ formDescriptionId: `${id}-form-item-description`,
61
+ formMessageId: `${id}-form-item-message`,
62
+ ...fieldState,
63
+ }
64
+ }
65
+
66
+ type FormItemContextValue = {
67
+ id: string
68
+ }
69
+
70
+ const FormItemContext = React.createContext<FormItemContextValue>(
71
+ {} as FormItemContextValue
72
+ )
73
+
74
+ const FormItem = (
75
+ {
76
+ ref,
77
+ className,
78
+ ...props
79
+ }: React.HTMLAttributes<HTMLDivElement> & {
80
+ ref?: React.Ref<HTMLDivElement>;
81
+ }
82
+ ) => {
83
+ const id = React.useId()
84
+
85
+ return (
86
+ <FormItemContext.Provider value={{ id }}>
87
+ <div ref={ref} className={cn("space-y-2", className)} {...props} />
88
+ </FormItemContext.Provider>
89
+ )
90
+ }
91
+
92
+ const FormLabel = (
93
+ {
94
+ ref,
95
+ className,
96
+ ...props
97
+ }: React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & {
98
+ ref?: React.Ref<React.ElementRef<typeof LabelPrimitive.Root>>;
99
+ }
100
+ ) => {
101
+ const { error, formItemId } = useFormField()
102
+
103
+ return (
104
+ <Label
105
+ ref={ref}
106
+ className={cn(error && "text-destructive", className)}
107
+ htmlFor={formItemId}
108
+ {...props}
109
+ />
110
+ )
111
+ }
112
+
113
+ const FormControl = (
114
+ {
115
+ ref,
116
+ ...props
117
+ }: React.ComponentPropsWithoutRef<typeof Slot> & {
118
+ ref?: React.Ref<React.ElementRef<typeof Slot>>;
119
+ }
120
+ ) => {
121
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
122
+
123
+ return (
124
+ <Slot
125
+ ref={ref}
126
+ id={formItemId}
127
+ aria-describedby={
128
+ !error
129
+ ? `${formDescriptionId}`
130
+ : `${formDescriptionId} ${formMessageId}`
131
+ }
132
+ aria-invalid={!!error}
133
+ {...props}
134
+ />
135
+ )
136
+ }
137
+
138
+ const FormDescription = (
139
+ {
140
+ ref,
141
+ className,
142
+ ...props
143
+ }: React.HTMLAttributes<HTMLParagraphElement> & {
144
+ ref?: React.Ref<HTMLParagraphElement>;
145
+ }
146
+ ) => {
147
+ const { formDescriptionId } = useFormField()
148
+
149
+ return (
150
+ <p
151
+ ref={ref}
152
+ id={formDescriptionId}
153
+ className={cn("text-sm text-muted-foreground", className)}
154
+ {...props}
155
+ />
156
+ )
157
+ }
158
+
159
+ const FormMessage = (
160
+ {
161
+ ref,
162
+ className,
163
+ children,
164
+ ...props
165
+ }: React.HTMLAttributes<HTMLParagraphElement> & {
166
+ ref?: React.Ref<HTMLParagraphElement>;
167
+ }
168
+ ) => {
169
+ const { error, formMessageId } = useFormField()
170
+ const body = error ? String(error?.message) : children
171
+
172
+ if (!body) {
173
+ return null
174
+ }
175
+
176
+ return (
177
+ <p
178
+ ref={ref}
179
+ id={formMessageId}
180
+ className={cn("text-sm font-medium text-destructive", className)}
181
+ {...props}
182
+ >
183
+ {body}
184
+ </p>
185
+ )
186
+ }
187
+
188
+ export {
189
+ useFormField,
190
+ Form,
191
+ FormItem,
192
+ FormLabel,
193
+ FormControl,
194
+ FormDescription,
195
+ FormMessage,
196
+ FormField,
197
+ }
@@ -0,0 +1,46 @@
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 { createRef } from 'react'
5
+ import { Input } from './Input'
6
+
7
+ describe('Input', () => {
8
+ it('renders input element', () => {
9
+ render(<Input placeholder="Enter text" />)
10
+ const input = screen.getByPlaceholderText('Enter text')
11
+ expect(input).toBeInTheDocument()
12
+ expect(input.tagName).toBe('INPUT')
13
+ })
14
+
15
+ it('applies custom className', () => {
16
+ render(<Input className="custom-input" placeholder="styled" />)
17
+ const input = screen.getByPlaceholderText('styled')
18
+ expect(input).toHaveClass('custom-input')
19
+ })
20
+
21
+ it('forwards ref', () => {
22
+ const ref = createRef<HTMLInputElement>()
23
+ render(<Input ref={ref} placeholder="ref test" />)
24
+ expect(ref.current).toBeInstanceOf(HTMLInputElement)
25
+ })
26
+
27
+ it('handles controlled input', async () => {
28
+ const user = userEvent.setup()
29
+ render(<Input placeholder="type here" />)
30
+ const input = screen.getByPlaceholderText('type here')
31
+
32
+ await user.type(input, 'Hello World')
33
+ expect(input).toHaveValue('Hello World')
34
+ })
35
+
36
+ it('handles different types', () => {
37
+ const { rerender } = render(<Input type="password" placeholder="password" />)
38
+ expect(screen.getByPlaceholderText('password')).toHaveAttribute('type', 'password')
39
+
40
+ rerender(<Input type="email" placeholder="email" />)
41
+ expect(screen.getByPlaceholderText('email')).toHaveAttribute('type', 'email')
42
+
43
+ rerender(<Input type="number" placeholder="number" />)
44
+ expect(screen.getByPlaceholderText('number')).toHaveAttribute('type', 'number')
45
+ })
46
+ })
@@ -0,0 +1,43 @@
1
+ import * as React from "react"
2
+ import { cn } from "../../lib/utils"
3
+
4
+ export interface InputProps
5
+ extends React.InputHTMLAttributes<HTMLInputElement> { }
6
+
7
+ const Input = (
8
+ {
9
+ ref,
10
+ className,
11
+ type,
12
+ style,
13
+ ...props
14
+ }: InputProps & {
15
+ ref?: React.Ref<HTMLInputElement>;
16
+ }
17
+ ) => {
18
+ return (
19
+ <input
20
+ type={type}
21
+ className={cn(
22
+ "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
23
+ className
24
+ )}
25
+ style={{
26
+ height: '2.25rem',
27
+ width: '100%',
28
+ border: '1px solid var(--color-input, #DFDFDF)',
29
+ borderRadius: 'var(--radius-md, 0.375rem)',
30
+ padding: '0.25rem 0.75rem',
31
+ fontSize: 'var(--text-sm, 0.875rem)',
32
+ backgroundColor: 'transparent',
33
+ boxShadow: '0 1px 2px 0 rgb(0 0 0 / 0.05)',
34
+ outline: 'none',
35
+ ...style,
36
+ }}
37
+ ref={ref}
38
+ {...props}
39
+ />
40
+ )
41
+ }
42
+
43
+ export { Input }
@@ -0,0 +1,81 @@
1
+ "use client";
2
+ import * as React from "react"
3
+ import { OTPInput, OTPInputContext } from "input-otp"
4
+ import { Dot } from "lucide-react"
5
+
6
+ import { cn } from "../../lib/utils"
7
+
8
+ const InputOTP = (
9
+ {
10
+ ref,
11
+ className,
12
+ containerClassName,
13
+ ...props
14
+ }: React.ComponentPropsWithoutRef<typeof OTPInput> & {
15
+ ref?: React.Ref<React.ElementRef<typeof OTPInput>>;
16
+ }
17
+ ) => (<OTPInput
18
+ ref={ref}
19
+ containerClassName={cn(
20
+ "flex items-center gap-2 has-[:disabled]:opacity-50",
21
+ containerClassName
22
+ )}
23
+ className={cn("disabled:cursor-not-allowed", className)}
24
+ {...props}
25
+ />)
26
+
27
+ const InputOTPGroup = (
28
+ {
29
+ ref,
30
+ className,
31
+ ...props
32
+ }: React.ComponentPropsWithoutRef<"div"> & {
33
+ ref?: React.Ref<HTMLDivElement>;
34
+ }
35
+ ) => (<div ref={ref} className={cn("flex items-center", className)} {...props} />)
36
+
37
+ const InputOTPSlot = (
38
+ {
39
+ ref,
40
+ index,
41
+ className,
42
+ ...props
43
+ }: React.ComponentPropsWithoutRef<"div"> & { index: number } & {
44
+ ref?: React.Ref<HTMLDivElement>;
45
+ }
46
+ ) => {
47
+ const inputOTPContext = React.useContext(OTPInputContext)
48
+ const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]
49
+
50
+ return (
51
+ <div
52
+ ref={ref}
53
+ className={cn(
54
+ "relative flex h-10 w-10 items-center justify-center border-y border-r border-input text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md",
55
+ isActive && "z-10 ring-2 ring-ring ring-offset-background",
56
+ className
57
+ )}
58
+ {...props}
59
+ >
60
+ {char}
61
+ {hasFakeCaret && (
62
+ <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
63
+ <div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
64
+ </div>
65
+ )}
66
+ </div>
67
+ )
68
+ }
69
+
70
+ const InputOTPSeparator = (
71
+ {
72
+ ref,
73
+ ...props
74
+ }: React.ComponentPropsWithoutRef<"div"> & {
75
+ ref?: React.Ref<HTMLDivElement>;
76
+ }
77
+ ) => (<div ref={ref} role="separator" {...props}>
78
+ <Dot />
79
+ </div>)
80
+
81
+ export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
@@ -0,0 +1,20 @@
1
+ import { render, screen } from '@testing-library/react'
2
+ import { describe, it, expect } from 'vitest'
3
+ import { Label } from './Label'
4
+
5
+ describe('Label', () => {
6
+ it('renders a label element', () => {
7
+ render(<Label>Name</Label>)
8
+ expect(screen.getByText('Name')).toBeInTheDocument()
9
+ })
10
+
11
+ it('associates with input via htmlFor', () => {
12
+ render(<Label htmlFor="name-input">Name</Label>)
13
+ expect(screen.getByText('Name')).toHaveAttribute('for', 'name-input')
14
+ })
15
+
16
+ it('applies custom className', () => {
17
+ render(<Label className="custom-label">Email</Label>)
18
+ expect(screen.getByText('Email')).toHaveClass('custom-label')
19
+ })
20
+ })
@@ -0,0 +1,25 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import * as LabelPrimitive from "@radix-ui/react-label"
4
+ import { cn } from "../../lib/utils"
5
+
6
+ const labelVariants = cva(
7
+ "block mb-1.5 text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
8
+ )
9
+
10
+ const Label = (
11
+ {
12
+ ref,
13
+ className,
14
+ ...props
15
+ }: React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
16
+ VariantProps<typeof labelVariants> & {
17
+ ref?: React.Ref<React.ElementRef<typeof LabelPrimitive.Root>>;
18
+ }
19
+ ) => (<LabelPrimitive.Root
20
+ ref={ref}
21
+ className={cn(labelVariants(), className)}
22
+ {...props}
23
+ />)
24
+
25
+ export { Label, labelVariants }
@@ -0,0 +1,51 @@
1
+ "use client";
2
+ import * as React from "react"
3
+ import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
4
+ import { Circle } from "lucide-react"
5
+
6
+ import { cn } from "../../lib/utils"
7
+
8
+ const RadioGroup = (
9
+ {
10
+ ref,
11
+ className,
12
+ ...props
13
+ }: React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root> & {
14
+ ref?: React.Ref<React.ElementRef<typeof RadioGroupPrimitive.Root>>;
15
+ }
16
+ ) => {
17
+ return (
18
+ <RadioGroupPrimitive.Root
19
+ className={cn("grid gap-2", className)}
20
+ {...props}
21
+ ref={ref}
22
+ />
23
+ )
24
+ }
25
+
26
+ const RadioGroupItem = (
27
+ {
28
+ ref,
29
+ className,
30
+ ...props
31
+ }: React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item> & {
32
+ ref?: React.Ref<React.ElementRef<typeof RadioGroupPrimitive.Item>>;
33
+ }
34
+ ) => {
35
+ return (
36
+ <RadioGroupPrimitive.Item
37
+ ref={ref}
38
+ className={cn(
39
+ "aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
40
+ className
41
+ )}
42
+ {...props}
43
+ >
44
+ <RadioGroupPrimitive.Indicator className="flex items-center justify-center">
45
+ <Circle className="h-2.5 w-2.5 fill-current text-current" />
46
+ </RadioGroupPrimitive.Indicator>
47
+ </RadioGroupPrimitive.Item>
48
+ )
49
+ }
50
+
51
+ export { RadioGroup, RadioGroupItem }