@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,161 @@
1
+ # Table Component Recipe
2
+
3
+ ## Structure
4
+ - Wrapper with horizontal scroll for responsive
5
+ - Table, Header, Body, Row, Head cell, Data cell
6
+ - Support for sorting indicators
7
+ - Optional row selection/hover states
8
+
9
+ ## Tailwind Classes
10
+
11
+ ### Wrapper
12
+ ```
13
+ relative w-full overflow-auto
14
+ ```
15
+
16
+ ### Table
17
+ ```
18
+ w-full caption-bottom text-sm
19
+ ```
20
+
21
+ ### Header
22
+ ```
23
+ [&_tr]:border-b border-border
24
+ ```
25
+
26
+ ### Body
27
+ ```
28
+ [&_tr:last-child]:border-0
29
+ ```
30
+
31
+ ### Row
32
+ ```
33
+ border-b border-border transition-colors
34
+ hover:bg-muted/50
35
+ data-[state=selected]:bg-muted
36
+ ```
37
+
38
+ ### Head Cell (th)
39
+ ```
40
+ h-10 px-2 text-left align-middle font-medium text-muted-foreground
41
+ [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]
42
+ ```
43
+
44
+ ### Data Cell (td)
45
+ ```
46
+ p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]
47
+ ```
48
+
49
+ ### Caption
50
+ ```
51
+ mt-4 text-sm text-muted-foreground
52
+ ```
53
+
54
+ ### Sortable Header
55
+ ```
56
+ cursor-pointer select-none hover:text-foreground
57
+ [&_svg]:ml-2 [&_svg]:h-4 [&_svg]:w-4
58
+ ```
59
+
60
+ ## Props Interface
61
+ ```typescript
62
+ interface TableProps extends React.HTMLAttributes<HTMLTableElement> {}
63
+
64
+ interface TableHeaderProps extends React.HTMLAttributes<HTMLTableSectionElement> {}
65
+
66
+ interface TableBodyProps extends React.HTMLAttributes<HTMLTableSectionElement> {}
67
+
68
+ interface TableRowProps extends React.HTMLAttributes<HTMLTableRowElement> {
69
+ selected?: boolean
70
+ }
71
+
72
+ interface TableHeadProps extends React.ThHTMLAttributes<HTMLTableCellElement> {
73
+ sortable?: boolean
74
+ sortDirection?: 'asc' | 'desc' | null
75
+ onSort?: () => void
76
+ }
77
+
78
+ interface TableCellProps extends React.TdHTMLAttributes<HTMLTableCellElement> {}
79
+
80
+ interface TableCaptionProps extends React.HTMLAttributes<HTMLTableCaptionElement> {}
81
+ ```
82
+
83
+ ## Do
84
+ - Use semantic table elements
85
+ - Include hover states for rows
86
+ - Support horizontal scroll for wide tables
87
+ - Use muted colors for headers
88
+ - Align numbers/dates right, text left
89
+
90
+ ## Don't
91
+ - Hardcode colors
92
+ - Use divs for table layout
93
+ - Forget responsive scroll wrapper
94
+ - Skip border definition
95
+
96
+ ## Example
97
+ ```tsx
98
+ import { cn } from '@/lib/utils'
99
+ import { ArrowUpDown, ArrowUp, ArrowDown } from 'lucide-react'
100
+
101
+ const Table = ({ className, ...props }) => (
102
+ <div className="relative w-full overflow-auto">
103
+ <table className={cn('w-full caption-bottom text-sm', className)} {...props} />
104
+ </div>
105
+ )
106
+
107
+ const TableHeader = ({ className, ...props }) => (
108
+ <thead className={cn('[&_tr]:border-b border-border', className)} {...props} />
109
+ )
110
+
111
+ const TableBody = ({ className, ...props }) => (
112
+ <tbody className={cn('[&_tr:last-child]:border-0', className)} {...props} />
113
+ )
114
+
115
+ const TableRow = ({ className, selected, ...props }) => (
116
+ <tr
117
+ className={cn(
118
+ 'border-b border-border transition-colors hover:bg-muted/50',
119
+ selected && 'bg-muted',
120
+ className
121
+ )}
122
+ data-state={selected ? 'selected' : undefined}
123
+ {...props}
124
+ />
125
+ )
126
+
127
+ const TableHead = ({ className, sortable, sortDirection, onSort, children, ...props }) => (
128
+ <th
129
+ className={cn(
130
+ 'h-10 px-2 text-left align-middle font-medium text-muted-foreground',
131
+ '[&:has([role=checkbox])]:pr-0',
132
+ sortable && 'cursor-pointer select-none hover:text-foreground',
133
+ className
134
+ )}
135
+ onClick={sortable ? onSort : undefined}
136
+ {...props}
137
+ >
138
+ <div className="flex items-center">
139
+ {children}
140
+ {sortable && (
141
+ <span className="ml-2">
142
+ {sortDirection === 'asc' && <ArrowUp className="h-4 w-4" />}
143
+ {sortDirection === 'desc' && <ArrowDown className="h-4 w-4" />}
144
+ {!sortDirection && <ArrowUpDown className="h-4 w-4 opacity-50" />}
145
+ </span>
146
+ )}
147
+ </div>
148
+ </th>
149
+ )
150
+
151
+ const TableCell = ({ className, ...props }) => (
152
+ <td
153
+ className={cn('p-2 align-middle [&:has([role=checkbox])]:pr-0', className)}
154
+ {...props}
155
+ />
156
+ )
157
+
158
+ const TableCaption = ({ className, ...props }) => (
159
+ <caption className={cn('mt-4 text-sm text-muted-foreground', className)} {...props} />
160
+ )
161
+ ```
@@ -0,0 +1,145 @@
1
+ # Tabs Component Recipe
2
+
3
+ ## Structure
4
+ - Tab list container with tab triggers
5
+ - Tab content panels (one per tab)
6
+ - Support horizontal and vertical orientations
7
+ - Keyboard navigation between tabs
8
+
9
+ ## Tailwind Classes
10
+
11
+ ### Tab List
12
+ ```
13
+ inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground
14
+ ```
15
+
16
+ ### Tab Trigger
17
+ ```
18
+ inline-flex items-center justify-center whitespace-nowrap {tokens.radius} px-3 py-1
19
+ text-sm font-medium ring-offset-background
20
+ transition-all
21
+ focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2
22
+ disabled:pointer-events-none disabled:opacity-50
23
+ data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:{tokens.shadow}
24
+ ```
25
+
26
+ ### Tab Content
27
+ ```
28
+ mt-2 ring-offset-background
29
+ focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2
30
+ ```
31
+
32
+ ### Alternative: Underline Style
33
+ ```
34
+ Tab List:
35
+ border-b border-border
36
+
37
+ Tab Trigger:
38
+ border-b-2 border-transparent pb-3 pt-2
39
+ data-[state=active]:border-primary data-[state=active]:text-foreground
40
+ ```
41
+
42
+ ### Alternative: Pills Style
43
+ ```
44
+ Tab List:
45
+ flex gap-2
46
+
47
+ Tab Trigger:
48
+ rounded-full px-4 py-2
49
+ data-[state=active]:bg-primary data-[state=active]:text-primary-foreground
50
+ ```
51
+
52
+ ## Props Interface
53
+ ```typescript
54
+ interface TabsProps {
55
+ defaultValue?: string
56
+ value?: string
57
+ onValueChange?: (value: string) => void
58
+ orientation?: 'horizontal' | 'vertical'
59
+ children: React.ReactNode
60
+ }
61
+
62
+ interface TabsListProps {
63
+ className?: string
64
+ children: React.ReactNode
65
+ }
66
+
67
+ interface TabsTriggerProps {
68
+ value: string
69
+ disabled?: boolean
70
+ children: React.ReactNode
71
+ }
72
+
73
+ interface TabsContentProps {
74
+ value: string
75
+ forceMount?: boolean
76
+ children: React.ReactNode
77
+ }
78
+ ```
79
+
80
+ ## Do
81
+ - Use Radix Tabs for accessibility
82
+ - Support keyboard navigation (arrow keys)
83
+ - Include focus ring for triggers
84
+ - Use subtle background for active state
85
+
86
+ ## Don't
87
+ - Hardcode colors
88
+ - Skip focus indicators
89
+ - Use tabs for navigation (use nav links)
90
+ - Forget disabled state styling
91
+
92
+ ## Example
93
+ ```tsx
94
+ import * as TabsPrimitive from '@radix-ui/react-tabs'
95
+ import { cn } from '@/lib/utils'
96
+
97
+ const Tabs = TabsPrimitive.Root
98
+
99
+ const TabsList = ({ className, ...props }) => (
100
+ <TabsPrimitive.List
101
+ className={cn(
102
+ 'inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground',
103
+ className
104
+ )}
105
+ {...props}
106
+ />
107
+ )
108
+
109
+ const TabsTrigger = ({ className, ...props }) => (
110
+ <TabsPrimitive.Trigger
111
+ className={cn(
112
+ 'inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1',
113
+ 'text-sm font-medium ring-offset-background transition-all',
114
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2',
115
+ 'disabled:pointer-events-none disabled:opacity-50',
116
+ 'data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm',
117
+ className
118
+ )}
119
+ {...props}
120
+ />
121
+ )
122
+
123
+ const TabsContent = ({ className, ...props }) => (
124
+ <TabsPrimitive.Content
125
+ className={cn(
126
+ 'mt-2 ring-offset-background',
127
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2',
128
+ className
129
+ )}
130
+ {...props}
131
+ />
132
+ )
133
+
134
+ // Usage
135
+ <Tabs defaultValue="account">
136
+ <TabsList>
137
+ <TabsTrigger value="account">Account</TabsTrigger>
138
+ <TabsTrigger value="password">Password</TabsTrigger>
139
+ <TabsTrigger value="team">Team</TabsTrigger>
140
+ </TabsList>
141
+ <TabsContent value="account">Account settings...</TabsContent>
142
+ <TabsContent value="password">Password settings...</TabsContent>
143
+ <TabsContent value="team">Team settings...</TabsContent>
144
+ </Tabs>
145
+ ```
@@ -0,0 +1,234 @@
1
+ # Textarea Component Recipe
2
+
3
+ ## Structure
4
+ - Multi-line text input
5
+ - Support for auto-resize
6
+ - Character count option
7
+ - Error state
8
+ - Disabled state
9
+
10
+ ## Tailwind Classes
11
+
12
+ ### Base
13
+ ```
14
+ flex min-h-[60px] w-full {tokens.radius} border border-border bg-background px-3 py-2
15
+ text-sm text-foreground placeholder:text-muted-foreground
16
+ transition-colors
17
+ focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2
18
+ disabled:cursor-not-allowed disabled:opacity-50
19
+ resize-none
20
+ ```
21
+
22
+ ### With Resize
23
+ ```
24
+ resize-y (vertical only)
25
+ resize (both directions)
26
+ resize-none (no resize - default for controlled height)
27
+ ```
28
+
29
+ ### Sizes
30
+ ```
31
+ sm: min-h-[40px] text-sm
32
+ md: min-h-[60px] text-sm (default)
33
+ lg: min-h-[80px] text-base
34
+ ```
35
+
36
+ ### Error State
37
+ ```
38
+ border-destructive focus-visible:ring-destructive
39
+ ```
40
+
41
+ ### Character Count
42
+ ```
43
+ Container: relative
44
+ Counter: absolute bottom-2 right-2 text-xs text-muted-foreground
45
+ Counter error: text-destructive
46
+ ```
47
+
48
+ ### With Label
49
+ ```
50
+ Container: space-y-2
51
+ Label: text-sm font-medium leading-none
52
+ ```
53
+
54
+ ## Props Interface
55
+ ```typescript
56
+ interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
57
+ error?: boolean
58
+ errorMessage?: string
59
+ }
60
+
61
+ interface TextareaWithCountProps extends TextareaProps {
62
+ maxLength: number
63
+ showCount?: boolean
64
+ }
65
+
66
+ interface AutoResizeTextareaProps extends TextareaProps {
67
+ minRows?: number
68
+ maxRows?: number
69
+ }
70
+ ```
71
+
72
+ ## Auto-Resize Logic
73
+ ```typescript
74
+ const adjustHeight = (textarea: HTMLTextAreaElement) => {
75
+ textarea.style.height = 'auto'
76
+ textarea.style.height = `${textarea.scrollHeight}px`
77
+ }
78
+ ```
79
+
80
+ ## Do
81
+ - Use for multi-line text (comments, descriptions, messages)
82
+ - Include placeholder with example format
83
+ - Show character count for limited inputs
84
+ - Support auto-resize for better UX
85
+
86
+ ## Don't
87
+ - Hardcode colors
88
+ - Use for single-line inputs (use Input)
89
+ - Make too small (frustrating to type in)
90
+ - Forget to handle long content gracefully
91
+
92
+ ## Example
93
+ ```tsx
94
+ import { forwardRef, useRef, useEffect } from 'react'
95
+ import { cn } from '@/lib/utils'
96
+
97
+ const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
98
+ ({ className, error, ...props }, ref) => {
99
+ return (
100
+ <textarea
101
+ className={cn(
102
+ 'flex min-h-[60px] w-full rounded-lg border border-border bg-background px-3 py-2',
103
+ 'text-sm text-foreground placeholder:text-muted-foreground',
104
+ 'transition-colors',
105
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2',
106
+ 'disabled:cursor-not-allowed disabled:opacity-50',
107
+ 'resize-none',
108
+ error && 'border-destructive focus-visible:ring-destructive',
109
+ className
110
+ )}
111
+ ref={ref}
112
+ {...props}
113
+ />
114
+ )
115
+ }
116
+ )
117
+
118
+ Textarea.displayName = 'Textarea'
119
+
120
+ // With character count
121
+ const TextareaWithCount = forwardRef<HTMLTextAreaElement, TextareaWithCountProps>(
122
+ ({ maxLength, showCount = true, value, className, ...props }, ref) => {
123
+ const count = String(value || '').length
124
+ const isOverLimit = count > maxLength
125
+
126
+ return (
127
+ <div className="relative">
128
+ <Textarea
129
+ ref={ref}
130
+ value={value}
131
+ maxLength={maxLength}
132
+ className={cn(showCount && 'pb-6', className)}
133
+ {...props}
134
+ />
135
+ {showCount && (
136
+ <span
137
+ className={cn(
138
+ 'absolute bottom-2 right-3 text-xs',
139
+ isOverLimit ? 'text-destructive' : 'text-muted-foreground'
140
+ )}
141
+ >
142
+ {count}/{maxLength}
143
+ </span>
144
+ )}
145
+ </div>
146
+ )
147
+ }
148
+ )
149
+
150
+ TextareaWithCount.displayName = 'TextareaWithCount'
151
+
152
+ // Auto-resize textarea
153
+ const AutoResizeTextarea = forwardRef<HTMLTextAreaElement, AutoResizeTextareaProps>(
154
+ ({ minRows = 2, maxRows = 10, onChange, className, ...props }, ref) => {
155
+ const textareaRef = useRef<HTMLTextAreaElement>(null)
156
+
157
+ const adjustHeight = () => {
158
+ const textarea = textareaRef.current
159
+ if (!textarea) return
160
+
161
+ textarea.style.height = 'auto'
162
+ const lineHeight = parseInt(getComputedStyle(textarea).lineHeight)
163
+ const minHeight = lineHeight * minRows
164
+ const maxHeight = lineHeight * maxRows
165
+
166
+ const newHeight = Math.min(Math.max(textarea.scrollHeight, minHeight), maxHeight)
167
+ textarea.style.height = `${newHeight}px`
168
+ }
169
+
170
+ useEffect(() => {
171
+ adjustHeight()
172
+ }, [props.value])
173
+
174
+ const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
175
+ adjustHeight()
176
+ onChange?.(e)
177
+ }
178
+
179
+ return (
180
+ <Textarea
181
+ ref={(node) => {
182
+ textareaRef.current = node
183
+ if (typeof ref === 'function') ref(node)
184
+ else if (ref) ref.current = node
185
+ }}
186
+ onChange={handleChange}
187
+ className={cn('resize-none overflow-hidden', className)}
188
+ rows={minRows}
189
+ {...props}
190
+ />
191
+ )
192
+ }
193
+ )
194
+
195
+ AutoResizeTextarea.displayName = 'AutoResizeTextarea'
196
+
197
+ // With label and error
198
+ const TextareaField = ({ label, error, errorMessage, id, ...props }) => (
199
+ <div className="space-y-2">
200
+ {label && (
201
+ <label htmlFor={id} className="text-sm font-medium leading-none">
202
+ {label}
203
+ </label>
204
+ )}
205
+ <Textarea id={id} error={error} {...props} />
206
+ {errorMessage && (
207
+ <p className="text-sm text-destructive">{errorMessage}</p>
208
+ )}
209
+ </div>
210
+ )
211
+
212
+ // Usage examples
213
+ <Textarea placeholder="Type your message here..." />
214
+
215
+ <TextareaWithCount
216
+ maxLength={280}
217
+ placeholder="What's happening?"
218
+ value={message}
219
+ onChange={(e) => setMessage(e.target.value)}
220
+ />
221
+
222
+ <AutoResizeTextarea
223
+ placeholder="Write your comment..."
224
+ minRows={2}
225
+ maxRows={6}
226
+ />
227
+
228
+ <TextareaField
229
+ label="Description"
230
+ placeholder="Describe your project..."
231
+ error={!!errors.description}
232
+ errorMessage={errors.description?.message}
233
+ />
234
+ ```