@fr0mpy/component-system 2.0.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 (43) hide show
  1. package/bin/cli.js +283 -0
  2. package/index.js +12 -0
  3. package/package.json +45 -0
  4. package/templates/commands/component-harness.md +116 -0
  5. package/templates/commands/setup-styling.md +111 -0
  6. package/templates/component-recipes/accordion.md +153 -0
  7. package/templates/component-recipes/alert.md +145 -0
  8. package/templates/component-recipes/avatar.md +165 -0
  9. package/templates/component-recipes/badge.md +126 -0
  10. package/templates/component-recipes/breadcrumb.md +220 -0
  11. package/templates/component-recipes/button.md +90 -0
  12. package/templates/component-recipes/card.md +130 -0
  13. package/templates/component-recipes/carousel.md +277 -0
  14. package/templates/component-recipes/checkbox.md +117 -0
  15. package/templates/component-recipes/collapsible.md +201 -0
  16. package/templates/component-recipes/combobox.md +193 -0
  17. package/templates/component-recipes/context-menu.md +254 -0
  18. package/templates/component-recipes/dialog.md +193 -0
  19. package/templates/component-recipes/drawer.md +196 -0
  20. package/templates/component-recipes/dropdown-menu.md +263 -0
  21. package/templates/component-recipes/hover-card.md +230 -0
  22. package/templates/component-recipes/input.md +113 -0
  23. package/templates/component-recipes/label.md +259 -0
  24. package/templates/component-recipes/modal.md +155 -0
  25. package/templates/component-recipes/navigation-menu.md +310 -0
  26. package/templates/component-recipes/pagination.md +223 -0
  27. package/templates/component-recipes/popover.md +156 -0
  28. package/templates/component-recipes/progress.md +185 -0
  29. package/templates/component-recipes/radio.md +148 -0
  30. package/templates/component-recipes/select.md +154 -0
  31. package/templates/component-recipes/separator.md +124 -0
  32. package/templates/component-recipes/skeleton.md +186 -0
  33. package/templates/component-recipes/slider.md +114 -0
  34. package/templates/component-recipes/spinner.md +225 -0
  35. package/templates/component-recipes/switch.md +100 -0
  36. package/templates/component-recipes/table.md +161 -0
  37. package/templates/component-recipes/tabs.md +145 -0
  38. package/templates/component-recipes/textarea.md +234 -0
  39. package/templates/component-recipes/toast.md +209 -0
  40. package/templates/component-recipes/toggle-group.md +216 -0
  41. package/templates/component-recipes/tooltip.md +115 -0
  42. package/templates/hooks/triggers.d/styling.json +23 -0
  43. package/templates/skills/styling.md +173 -0
@@ -0,0 +1,263 @@
1
+ # Dropdown Menu Component Recipe
2
+
3
+ ## Structure
4
+ - Trigger button/element
5
+ - Menu content with items
6
+ - Support for submenus
7
+ - Checkboxes and radio items
8
+ - Keyboard navigation
9
+ - Separators and labels
10
+
11
+ ## Tailwind Classes
12
+
13
+ ### Content
14
+ ```
15
+ z-50 min-w-[8rem] overflow-hidden {tokens.radius} border border-border
16
+ bg-background p-1 text-foreground {tokens.shadow}
17
+ data-[state=open]:animate-in data-[state=closed]:animate-out
18
+ data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0
19
+ data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95
20
+ data-[side=bottom]:slide-in-from-top-2
21
+ data-[side=left]:slide-in-from-right-2
22
+ data-[side=right]:slide-in-from-left-2
23
+ data-[side=top]:slide-in-from-bottom-2
24
+ ```
25
+
26
+ ### Item
27
+ ```
28
+ relative flex cursor-pointer select-none items-center {tokens.radius} px-2 py-1.5
29
+ text-sm outline-none
30
+ transition-colors
31
+ focus:bg-muted focus:text-foreground
32
+ data-[disabled]:pointer-events-none data-[disabled]:opacity-50
33
+ ```
34
+
35
+ ### Item with Icon
36
+ ```
37
+ [&>svg]:mr-2 [&>svg]:h-4 [&>svg]:w-4
38
+ ```
39
+
40
+ ### Checkbox Item
41
+ ```
42
+ relative flex cursor-pointer select-none items-center {tokens.radius} py-1.5 pl-8 pr-2
43
+ text-sm outline-none
44
+ focus:bg-muted focus:text-foreground
45
+ data-[disabled]:pointer-events-none data-[disabled]:opacity-50
46
+ ```
47
+
48
+ ### Radio Item
49
+ ```
50
+ relative flex cursor-pointer select-none items-center {tokens.radius} py-1.5 pl-8 pr-2
51
+ text-sm outline-none
52
+ focus:bg-muted focus:text-foreground
53
+ data-[disabled]:pointer-events-none data-[disabled]:opacity-50
54
+ ```
55
+
56
+ ### Item Indicator (check/radio)
57
+ ```
58
+ absolute left-2 flex h-3.5 w-3.5 items-center justify-center
59
+ ```
60
+
61
+ ### Label
62
+ ```
63
+ px-2 py-1.5 text-sm font-semibold text-foreground
64
+ ```
65
+
66
+ ### Separator
67
+ ```
68
+ -mx-1 my-1 h-px bg-border
69
+ ```
70
+
71
+ ### Shortcut
72
+ ```
73
+ ml-auto text-xs tracking-widest text-muted-foreground
74
+ ```
75
+
76
+ ### Sub Trigger
77
+ ```
78
+ flex cursor-pointer select-none items-center {tokens.radius} px-2 py-1.5
79
+ text-sm outline-none
80
+ focus:bg-muted
81
+ data-[state=open]:bg-muted
82
+ ```
83
+
84
+ ### Sub Content
85
+ ```
86
+ z-50 min-w-[8rem] overflow-hidden {tokens.radius} border border-border
87
+ bg-background p-1 {tokens.shadow}
88
+ ```
89
+
90
+ ## Props Interface
91
+ ```typescript
92
+ interface DropdownMenuProps {
93
+ open?: boolean
94
+ onOpenChange?: (open: boolean) => void
95
+ modal?: boolean
96
+ children: React.ReactNode
97
+ }
98
+
99
+ interface DropdownMenuItemProps {
100
+ disabled?: boolean
101
+ onSelect?: () => void
102
+ children: React.ReactNode
103
+ }
104
+
105
+ interface DropdownMenuCheckboxItemProps {
106
+ checked?: boolean
107
+ onCheckedChange?: (checked: boolean) => void
108
+ disabled?: boolean
109
+ children: React.ReactNode
110
+ }
111
+
112
+ interface DropdownMenuRadioGroupProps {
113
+ value?: string
114
+ onValueChange?: (value: string) => void
115
+ children: React.ReactNode
116
+ }
117
+
118
+ interface DropdownMenuRadioItemProps {
119
+ value: string
120
+ disabled?: boolean
121
+ children: React.ReactNode
122
+ }
123
+ ```
124
+
125
+ ## Do
126
+ - Use Radix DropdownMenu for accessibility
127
+ - Support keyboard navigation (arrows, enter, escape)
128
+ - Include focus management
129
+ - Support nested submenus
130
+
131
+ ## Don't
132
+ - Hardcode colors
133
+ - Forget keyboard shortcuts display
134
+ - Skip disabled state handling
135
+ - Use for navigation (use NavigationMenu)
136
+
137
+ ## Example
138
+ ```tsx
139
+ import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
140
+ import { Check, ChevronRight, Circle } from 'lucide-react'
141
+ import { cn } from '@/lib/utils'
142
+
143
+ const DropdownMenu = DropdownMenuPrimitive.Root
144
+ const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
145
+ const DropdownMenuGroup = DropdownMenuPrimitive.Group
146
+ const DropdownMenuPortal = DropdownMenuPrimitive.Portal
147
+ const DropdownMenuSub = DropdownMenuPrimitive.Sub
148
+ const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
149
+
150
+ const DropdownMenuContent = ({ className, sideOffset = 4, ...props }) => (
151
+ <DropdownMenuPrimitive.Portal>
152
+ <DropdownMenuPrimitive.Content
153
+ sideOffset={sideOffset}
154
+ className={cn(
155
+ 'z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-background p-1 shadow-md',
156
+ 'data-[state=open]:animate-in data-[state=closed]:animate-out',
157
+ 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
158
+ 'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
159
+ 'data-[side=bottom]:slide-in-from-top-2',
160
+ className
161
+ )}
162
+ {...props}
163
+ />
164
+ </DropdownMenuPrimitive.Portal>
165
+ )
166
+
167
+ const DropdownMenuItem = ({ className, inset, ...props }) => (
168
+ <DropdownMenuPrimitive.Item
169
+ className={cn(
170
+ 'relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none',
171
+ 'transition-colors focus:bg-muted focus:text-foreground',
172
+ 'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
173
+ inset && 'pl-8',
174
+ className
175
+ )}
176
+ {...props}
177
+ />
178
+ )
179
+
180
+ const DropdownMenuCheckboxItem = ({ className, checked, children, ...props }) => (
181
+ <DropdownMenuPrimitive.CheckboxItem
182
+ className={cn(
183
+ 'relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none',
184
+ 'focus:bg-muted focus:text-foreground',
185
+ 'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
186
+ className
187
+ )}
188
+ checked={checked}
189
+ {...props}
190
+ >
191
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
192
+ <DropdownMenuPrimitive.ItemIndicator>
193
+ <Check className="h-4 w-4" />
194
+ </DropdownMenuPrimitive.ItemIndicator>
195
+ </span>
196
+ {children}
197
+ </DropdownMenuPrimitive.CheckboxItem>
198
+ )
199
+
200
+ const DropdownMenuRadioItem = ({ className, children, ...props }) => (
201
+ <DropdownMenuPrimitive.RadioItem
202
+ className={cn(
203
+ 'relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none',
204
+ 'focus:bg-muted focus:text-foreground',
205
+ 'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
206
+ className
207
+ )}
208
+ {...props}
209
+ >
210
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
211
+ <DropdownMenuPrimitive.ItemIndicator>
212
+ <Circle className="h-2 w-2 fill-current" />
213
+ </DropdownMenuPrimitive.ItemIndicator>
214
+ </span>
215
+ {children}
216
+ </DropdownMenuPrimitive.RadioItem>
217
+ )
218
+
219
+ const DropdownMenuLabel = ({ className, inset, ...props }) => (
220
+ <DropdownMenuPrimitive.Label
221
+ className={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
222
+ {...props}
223
+ />
224
+ )
225
+
226
+ const DropdownMenuSeparator = ({ className, ...props }) => (
227
+ <DropdownMenuPrimitive.Separator
228
+ className={cn('-mx-1 my-1 h-px bg-border', className)}
229
+ {...props}
230
+ />
231
+ )
232
+
233
+ const DropdownMenuShortcut = ({ className, ...props }) => (
234
+ <span className={cn('ml-auto text-xs tracking-widest text-muted-foreground', className)} {...props} />
235
+ )
236
+
237
+ const DropdownMenuSubTrigger = ({ className, inset, children, ...props }) => (
238
+ <DropdownMenuPrimitive.SubTrigger
239
+ className={cn(
240
+ 'flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none',
241
+ 'focus:bg-muted data-[state=open]:bg-muted',
242
+ inset && 'pl-8',
243
+ className
244
+ )}
245
+ {...props}
246
+ >
247
+ {children}
248
+ <ChevronRight className="ml-auto h-4 w-4" />
249
+ </DropdownMenuPrimitive.SubTrigger>
250
+ )
251
+
252
+ const DropdownMenuSubContent = ({ className, ...props }) => (
253
+ <DropdownMenuPrimitive.SubContent
254
+ className={cn(
255
+ 'z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-background p-1 shadow-lg',
256
+ 'data-[state=open]:animate-in data-[state=closed]:animate-out',
257
+ 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
258
+ className
259
+ )}
260
+ {...props}
261
+ />
262
+ )
263
+ ```
@@ -0,0 +1,230 @@
1
+ # Hover Card Component Recipe
2
+
3
+ ## Structure
4
+ - Trigger element (usually a link or button)
5
+ - Floating card that appears on hover
6
+ - Arrow pointing to trigger
7
+ - Rich content support (images, text, actions)
8
+ - Delay before showing/hiding
9
+
10
+ ## Tailwind Classes
11
+
12
+ ### Content
13
+ ```
14
+ z-50 w-64 {tokens.radius} border border-border bg-background p-4 text-foreground {tokens.shadow}
15
+ outline-none
16
+ data-[state=open]:animate-in data-[state=closed]:animate-out
17
+ data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0
18
+ data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95
19
+ data-[side=bottom]:slide-in-from-top-2
20
+ data-[side=left]:slide-in-from-right-2
21
+ data-[side=right]:slide-in-from-left-2
22
+ data-[side=top]:slide-in-from-bottom-2
23
+ ```
24
+
25
+ ### Arrow
26
+ ```
27
+ fill-background
28
+ ```
29
+
30
+ ### Arrow with Border
31
+ ```
32
+ fill-background stroke-border
33
+ ```
34
+
35
+ ### User Card Layout
36
+ ```
37
+ Container: flex flex-col gap-4
38
+ Avatar section: flex items-center gap-4
39
+ Info section: space-y-1
40
+ Stats section: flex gap-4 pt-2
41
+ ```
42
+
43
+ ### Link Preview Layout
44
+ ```
45
+ Container: space-y-3
46
+ Image: aspect-video w-full rounded-md object-cover
47
+ Title: font-medium leading-none
48
+ Description: text-sm text-muted-foreground line-clamp-2
49
+ ```
50
+
51
+ ## Props Interface
52
+ ```typescript
53
+ interface HoverCardProps {
54
+ open?: boolean
55
+ onOpenChange?: (open: boolean) => void
56
+ defaultOpen?: boolean
57
+ openDelay?: number // default 700ms
58
+ closeDelay?: number // default 300ms
59
+ children: React.ReactNode
60
+ }
61
+
62
+ interface HoverCardTriggerProps {
63
+ asChild?: boolean
64
+ children: React.ReactNode
65
+ }
66
+
67
+ interface HoverCardContentProps {
68
+ side?: 'top' | 'right' | 'bottom' | 'left'
69
+ sideOffset?: number
70
+ align?: 'start' | 'center' | 'end'
71
+ alignOffset?: number
72
+ className?: string
73
+ children: React.ReactNode
74
+ }
75
+
76
+ interface HoverCardArrowProps {
77
+ className?: string
78
+ width?: number
79
+ height?: number
80
+ }
81
+ ```
82
+
83
+ ## Configuration
84
+ - Default open delay: 700ms (prevents accidental triggers)
85
+ - Default close delay: 300ms (allows moving to card)
86
+ - Supports collision detection
87
+
88
+ ## Do
89
+ - Use for supplementary, non-essential information
90
+ - Keep content scannable (not too much)
91
+ - Include visual hierarchy in content
92
+ - Support touch devices with click fallback
93
+
94
+ ## Don't
95
+ - Use for critical information (use tooltip or inline)
96
+ - Put interactive forms inside
97
+ - Use too short delay (annoying)
98
+ - Block content underneath
99
+
100
+ ## Example
101
+ ```tsx
102
+ import * as HoverCardPrimitive from '@radix-ui/react-hover-card'
103
+ import { cn } from '@/lib/utils'
104
+
105
+ const HoverCard = HoverCardPrimitive.Root
106
+ const HoverCardTrigger = HoverCardPrimitive.Trigger
107
+
108
+ const HoverCardContent = ({
109
+ className,
110
+ align = 'center',
111
+ sideOffset = 4,
112
+ ...props
113
+ }) => (
114
+ <HoverCardPrimitive.Content
115
+ align={align}
116
+ sideOffset={sideOffset}
117
+ className={cn(
118
+ 'z-50 w-64 rounded-lg border border-border bg-background p-4 shadow-md outline-none',
119
+ 'data-[state=open]:animate-in data-[state=closed]:animate-out',
120
+ 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
121
+ 'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
122
+ 'data-[side=bottom]:slide-in-from-top-2',
123
+ 'data-[side=left]:slide-in-from-right-2',
124
+ 'data-[side=right]:slide-in-from-left-2',
125
+ 'data-[side=top]:slide-in-from-bottom-2',
126
+ className
127
+ )}
128
+ {...props}
129
+ />
130
+ )
131
+
132
+ const HoverCardArrow = ({ className, ...props }) => (
133
+ <HoverCardPrimitive.Arrow
134
+ className={cn('fill-background', className)}
135
+ {...props}
136
+ />
137
+ )
138
+
139
+ // User profile card
140
+ const UserHoverCard = ({ user, children }) => (
141
+ <HoverCard>
142
+ <HoverCardTrigger asChild>{children}</HoverCardTrigger>
143
+ <HoverCardContent className="w-80">
144
+ <div className="flex justify-between space-x-4">
145
+ <Avatar>
146
+ <AvatarImage src={user.avatar} />
147
+ <AvatarFallback>{user.initials}</AvatarFallback>
148
+ </Avatar>
149
+ <div className="space-y-1">
150
+ <h4 className="text-sm font-semibold">{user.name}</h4>
151
+ <p className="text-sm text-muted-foreground">@{user.username}</p>
152
+ <p className="text-sm">{user.bio}</p>
153
+ <div className="flex items-center pt-2">
154
+ <span className="text-xs text-muted-foreground">
155
+ Joined {user.joinedDate}
156
+ </span>
157
+ </div>
158
+ </div>
159
+ </div>
160
+ <HoverCardArrow />
161
+ </HoverCardContent>
162
+ </HoverCard>
163
+ )
164
+
165
+ // Link preview card
166
+ const LinkHoverCard = ({ url, title, description, image, children }) => (
167
+ <HoverCard>
168
+ <HoverCardTrigger asChild>{children}</HoverCardTrigger>
169
+ <HoverCardContent className="w-80">
170
+ <div className="space-y-3">
171
+ {image && (
172
+ <img
173
+ src={image}
174
+ alt={title}
175
+ className="aspect-video w-full rounded-md object-cover"
176
+ />
177
+ )}
178
+ <div className="space-y-1">
179
+ <h4 className="text-sm font-semibold leading-none">{title}</h4>
180
+ <p className="text-sm text-muted-foreground line-clamp-2">
181
+ {description}
182
+ </p>
183
+ <p className="text-xs text-muted-foreground">{url}</p>
184
+ </div>
185
+ </div>
186
+ <HoverCardArrow />
187
+ </HoverCardContent>
188
+ </HoverCard>
189
+ )
190
+
191
+ // Product preview card
192
+ const ProductHoverCard = ({ product, children }) => (
193
+ <HoverCard>
194
+ <HoverCardTrigger asChild>{children}</HoverCardTrigger>
195
+ <HoverCardContent className="w-72">
196
+ <div className="space-y-3">
197
+ <img
198
+ src={product.image}
199
+ alt={product.name}
200
+ className="aspect-square w-full rounded-md object-cover"
201
+ />
202
+ <div className="space-y-1">
203
+ <div className="flex items-center justify-between">
204
+ <h4 className="font-medium">{product.name}</h4>
205
+ <span className="font-semibold">${product.price}</span>
206
+ </div>
207
+ <p className="text-sm text-muted-foreground line-clamp-2">
208
+ {product.description}
209
+ </p>
210
+ <div className="flex items-center gap-1">
211
+ {Array.from({ length: 5 }).map((_, i) => (
212
+ <Star
213
+ key={i}
214
+ className={cn(
215
+ 'h-3 w-3',
216
+ i < product.rating ? 'fill-yellow-400 text-yellow-400' : 'text-muted'
217
+ )}
218
+ />
219
+ ))}
220
+ <span className="text-xs text-muted-foreground">
221
+ ({product.reviewCount})
222
+ </span>
223
+ </div>
224
+ </div>
225
+ </div>
226
+ <HoverCardArrow />
227
+ </HoverCardContent>
228
+ </HoverCard>
229
+ )
230
+ ```
@@ -0,0 +1,113 @@
1
+ # Input Component Recipe
2
+
3
+ ## Structure
4
+ - Use `<input>` element
5
+ - Support common types: text, email, password, number, search, tel, url
6
+ - Include states: disabled, error, with icon
7
+ - Optionally wrap with label and error message
8
+
9
+ ## Tailwind Classes
10
+
11
+ ### Base Input
12
+ ```
13
+ flex h-10 w-full {tokens.radius} border border-border bg-background px-3 py-2
14
+ text-sm text-foreground placeholder:text-muted-foreground
15
+ transition-colors
16
+ focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2
17
+ disabled:cursor-not-allowed disabled:opacity-50
18
+ ```
19
+
20
+ ### Error State
21
+ ```
22
+ border-destructive focus:ring-destructive
23
+ ```
24
+
25
+ ### With Icon (left)
26
+ ```
27
+ pl-10
28
+ Icon wrapper: absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground
29
+ ```
30
+
31
+ ### With Icon (right)
32
+ ```
33
+ pr-10
34
+ Icon wrapper: absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground
35
+ ```
36
+
37
+ ## Props Interface
38
+ ```typescript
39
+ interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
40
+ error?: boolean
41
+ errorMessage?: string
42
+ label?: string
43
+ leftIcon?: React.ReactNode
44
+ rightIcon?: React.ReactNode
45
+ }
46
+ ```
47
+
48
+ ## Wrapper Components
49
+
50
+ ### InputWrapper (with label and error)
51
+ ```tsx
52
+ <div className="space-y-2">
53
+ {label && <Label htmlFor={id}>{label}</Label>}
54
+ <Input ... />
55
+ {errorMessage && <p className="text-sm text-destructive">{errorMessage}</p>}
56
+ </div>
57
+ ```
58
+
59
+ ## Do
60
+ - Use `bg-background` for input background
61
+ - Include `placeholder:text-muted-foreground`
62
+ - Add focus ring with primary color
63
+ - Support error states with destructive color
64
+ - Use relative/absolute for icon positioning
65
+
66
+ ## Don't
67
+ - Hardcode colors
68
+ - Forget focus states
69
+ - Skip placeholder styling
70
+ - Use fixed widths (default to `w-full`)
71
+
72
+ ## Example
73
+ ```tsx
74
+ import { cn } from '@/lib/utils'
75
+ import { forwardRef } from 'react'
76
+
77
+ const Input = forwardRef<HTMLInputElement, InputProps>(
78
+ ({ className, type = 'text', error, leftIcon, rightIcon, ...props }, ref) => {
79
+ return (
80
+ <div className="relative">
81
+ {leftIcon && (
82
+ <div className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground">
83
+ {leftIcon}
84
+ </div>
85
+ )}
86
+ <input
87
+ type={type}
88
+ className={cn(
89
+ 'flex h-10 w-full rounded-lg border border-border bg-background px-3 py-2',
90
+ 'text-sm text-foreground placeholder:text-muted-foreground',
91
+ 'transition-colors',
92
+ 'focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2',
93
+ 'disabled:cursor-not-allowed disabled:opacity-50',
94
+ error && 'border-destructive focus:ring-destructive',
95
+ leftIcon && 'pl-10',
96
+ rightIcon && 'pr-10',
97
+ className
98
+ )}
99
+ ref={ref}
100
+ {...props}
101
+ />
102
+ {rightIcon && (
103
+ <div className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground">
104
+ {rightIcon}
105
+ </div>
106
+ )}
107
+ </div>
108
+ )
109
+ }
110
+ )
111
+
112
+ Input.displayName = 'Input'
113
+ ```