@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,186 @@
1
+ # Skeleton Component Recipe
2
+
3
+ ## Structure
4
+ - Placeholder shapes that mimic content layout
5
+ - Subtle animation (pulse or shimmer)
6
+ - Various preset shapes (text, circle, rectangle)
7
+ - Composable for complex layouts
8
+
9
+ ## Tailwind Classes
10
+
11
+ ### Base
12
+ ```
13
+ animate-pulse rounded-md bg-muted
14
+ ```
15
+
16
+ ### Shapes
17
+ ```
18
+ text: h-4 w-full rounded
19
+ heading: h-6 w-3/4 rounded
20
+ paragraph: h-4 w-full rounded (multiple with varying widths)
21
+ circle: rounded-full aspect-square
22
+ rectangle: rounded-md
23
+ avatar: h-10 w-10 rounded-full
24
+ button: h-10 w-24 rounded-lg
25
+ card: h-48 w-full rounded-lg
26
+ image: aspect-video w-full rounded-lg
27
+ ```
28
+
29
+ ### Animation Variants
30
+ ```
31
+ pulse: animate-pulse (default, uses opacity)
32
+ shimmer: animate-shimmer (gradient sweep)
33
+
34
+ @keyframes shimmer {
35
+ 0% { background-position: -200% 0; }
36
+ 100% { background-position: 200% 0; }
37
+ }
38
+
39
+ shimmer class:
40
+ bg-gradient-to-r from-muted via-muted/50 to-muted
41
+ bg-[length:200%_100%] animate-shimmer
42
+ ```
43
+
44
+ ### Sizes
45
+ ```
46
+ sm: Scale down by 75%
47
+ md: Default size
48
+ lg: Scale up by 125%
49
+ ```
50
+
51
+ ## Props Interface
52
+ ```typescript
53
+ interface SkeletonProps {
54
+ className?: string
55
+ variant?: 'pulse' | 'shimmer'
56
+ }
57
+
58
+ interface SkeletonTextProps extends SkeletonProps {
59
+ lines?: number
60
+ }
61
+
62
+ interface SkeletonAvatarProps extends SkeletonProps {
63
+ size?: 'sm' | 'md' | 'lg'
64
+ }
65
+
66
+ interface SkeletonCardProps extends SkeletonProps {
67
+ hasImage?: boolean
68
+ hasTitle?: boolean
69
+ hasDescription?: boolean
70
+ }
71
+ ```
72
+
73
+ ## Do
74
+ - Match skeleton shapes to actual content layout
75
+ - Use consistent animation across page
76
+ - Provide presets for common patterns
77
+ - Keep animations subtle (not distracting)
78
+
79
+ ## Don't
80
+ - Hardcode colors
81
+ - Use jarring animations
82
+ - Show skeleton indefinitely (add timeout)
83
+ - Mix different animation styles
84
+
85
+ ## Example
86
+ ```tsx
87
+ import { cn } from '@/lib/utils'
88
+
89
+ const Skeleton = ({ className, ...props }) => (
90
+ <div
91
+ className={cn('animate-pulse rounded-md bg-muted', className)}
92
+ {...props}
93
+ />
94
+ )
95
+
96
+ // Shimmer variant
97
+ const SkeletonShimmer = ({ className, ...props }) => (
98
+ <div
99
+ className={cn(
100
+ 'rounded-md bg-gradient-to-r from-muted via-muted/50 to-muted',
101
+ 'bg-[length:200%_100%] animate-shimmer',
102
+ className
103
+ )}
104
+ {...props}
105
+ />
106
+ )
107
+
108
+ // Text skeleton (multiple lines)
109
+ const SkeletonText = ({ lines = 3, className }) => (
110
+ <div className={cn('space-y-2', className)}>
111
+ {Array.from({ length: lines }).map((_, i) => (
112
+ <Skeleton
113
+ key={i}
114
+ className={cn(
115
+ 'h-4',
116
+ i === lines - 1 ? 'w-4/5' : 'w-full' // Last line shorter
117
+ )}
118
+ />
119
+ ))}
120
+ </div>
121
+ )
122
+
123
+ // Avatar skeleton
124
+ const SkeletonAvatar = ({ size = 'md', className }) => {
125
+ const sizes = {
126
+ sm: 'h-8 w-8',
127
+ md: 'h-10 w-10',
128
+ lg: 'h-12 w-12',
129
+ }
130
+ return <Skeleton className={cn('rounded-full', sizes[size], className)} />
131
+ }
132
+
133
+ // Card skeleton
134
+ const SkeletonCard = ({ hasImage = true, className }) => (
135
+ <div className={cn('rounded-lg border border-border p-4 space-y-4', className)}>
136
+ {hasImage && <Skeleton className="aspect-video w-full rounded-lg" />}
137
+ <div className="space-y-2">
138
+ <Skeleton className="h-5 w-2/3" />
139
+ <Skeleton className="h-4 w-full" />
140
+ <Skeleton className="h-4 w-4/5" />
141
+ </div>
142
+ </div>
143
+ )
144
+
145
+ // Table row skeleton
146
+ const SkeletonTableRow = ({ columns = 4 }) => (
147
+ <div className="flex items-center space-x-4 py-3">
148
+ {Array.from({ length: columns }).map((_, i) => (
149
+ <Skeleton
150
+ key={i}
151
+ className={cn(
152
+ 'h-4',
153
+ i === 0 ? 'w-8' : 'flex-1'
154
+ )}
155
+ />
156
+ ))}
157
+ </div>
158
+ )
159
+
160
+ // Full page skeleton (dashboard example)
161
+ const SkeletonDashboard = () => (
162
+ <div className="space-y-6 p-6">
163
+ {/* Header */}
164
+ <div className="flex items-center justify-between">
165
+ <Skeleton className="h-8 w-48" />
166
+ <Skeleton className="h-10 w-32" />
167
+ </div>
168
+
169
+ {/* Stats row */}
170
+ <div className="grid grid-cols-4 gap-4">
171
+ {Array.from({ length: 4 }).map((_, i) => (
172
+ <div key={i} className="rounded-lg border border-border p-4 space-y-2">
173
+ <Skeleton className="h-4 w-20" />
174
+ <Skeleton className="h-8 w-16" />
175
+ </div>
176
+ ))}
177
+ </div>
178
+
179
+ {/* Content area */}
180
+ <div className="grid grid-cols-2 gap-6">
181
+ <SkeletonCard />
182
+ <SkeletonCard />
183
+ </div>
184
+ </div>
185
+ )
186
+ ```
@@ -0,0 +1,114 @@
1
+ # Slider Component Recipe
2
+
3
+ ## Structure
4
+ - Track with filled range indicator
5
+ - Draggable thumb(s)
6
+ - Support single value or range (two thumbs)
7
+ - Optional step markers and value display
8
+
9
+ ## Tailwind Classes
10
+
11
+ ### Root Container
12
+ ```
13
+ relative flex w-full touch-none select-none items-center
14
+ ```
15
+
16
+ ### Track
17
+ ```
18
+ relative h-1.5 w-full grow overflow-hidden rounded-full bg-muted
19
+ ```
20
+
21
+ ### Range (filled portion)
22
+ ```
23
+ absolute h-full bg-primary
24
+ ```
25
+
26
+ ### Thumb
27
+ ```
28
+ block h-4 w-4 rounded-full border border-primary/50 bg-background shadow
29
+ transition-colors
30
+ focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary
31
+ disabled:pointer-events-none disabled:opacity-50
32
+ hover:border-primary
33
+ ```
34
+
35
+ ### Sizes
36
+ ```
37
+ sm: Track h-1, Thumb h-3 w-3
38
+ md: Track h-1.5, Thumb h-4 w-4 (default)
39
+ lg: Track h-2, Thumb h-5 w-5
40
+ ```
41
+
42
+ ### With Value Display
43
+ ```
44
+ Value label: absolute -top-7 left-1/2 -translate-x-1/2 text-xs font-medium
45
+ ```
46
+
47
+ ## Props Interface
48
+ ```typescript
49
+ interface SliderProps {
50
+ value?: number[]
51
+ defaultValue?: number[]
52
+ onValueChange?: (value: number[]) => void
53
+ min?: number
54
+ max?: number
55
+ step?: number
56
+ disabled?: boolean
57
+ orientation?: 'horizontal' | 'vertical'
58
+ inverted?: boolean
59
+ minStepsBetweenThumbs?: number
60
+ }
61
+ ```
62
+
63
+ ## Do
64
+ - Use Radix Slider for accessibility
65
+ - Support keyboard navigation (arrows, Home, End)
66
+ - Show current value on drag (tooltip or label)
67
+ - Support both single and range modes
68
+
69
+ ## Don't
70
+ - Hardcode colors
71
+ - Forget touch support
72
+ - Skip min/max/step configuration
73
+ - Use for non-numeric values
74
+
75
+ ## Example
76
+ ```tsx
77
+ import * as SliderPrimitive from '@radix-ui/react-slider'
78
+ import { cn } from '@/lib/utils'
79
+
80
+ const Slider = ({ className, ...props }) => (
81
+ <SliderPrimitive.Root
82
+ className={cn(
83
+ 'relative flex w-full touch-none select-none items-center',
84
+ className
85
+ )}
86
+ {...props}
87
+ >
88
+ <SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-muted">
89
+ <SliderPrimitive.Range className="absolute h-full bg-primary" />
90
+ </SliderPrimitive.Track>
91
+ <SliderPrimitive.Thumb
92
+ className={cn(
93
+ 'block h-4 w-4 rounded-full border border-primary/50 bg-background shadow',
94
+ 'transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary',
95
+ 'disabled:pointer-events-none disabled:opacity-50 hover:border-primary'
96
+ )}
97
+ />
98
+ </SliderPrimitive.Root>
99
+ )
100
+
101
+ // With value display
102
+ const SliderWithValue = ({ value, onValueChange, ...props }) => (
103
+ <div className="space-y-2">
104
+ <div className="flex justify-between text-sm">
105
+ <span className="text-muted-foreground">Value</span>
106
+ <span className="font-medium">{value?.[0] ?? 0}</span>
107
+ </div>
108
+ <Slider value={value} onValueChange={onValueChange} {...props} />
109
+ </div>
110
+ )
111
+
112
+ // Range slider (two thumbs)
113
+ <Slider defaultValue={[25, 75]} max={100} step={1} />
114
+ ```
@@ -0,0 +1,225 @@
1
+ # Spinner Component Recipe
2
+
3
+ ## Structure
4
+ - Animated loading indicator
5
+ - SVG-based for crisp rendering
6
+ - Support for multiple sizes
7
+ - Optional label text
8
+ - Accessible with aria-label
9
+
10
+ ## Tailwind Classes
11
+
12
+ ### Base
13
+ ```
14
+ animate-spin text-primary
15
+ ```
16
+
17
+ ### SVG Structure
18
+ ```
19
+ <svg className="animate-spin" viewBox="0 0 24 24" fill="none">
20
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
21
+ <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
22
+ </svg>
23
+ ```
24
+
25
+ ### Sizes
26
+ ```
27
+ xs: h-3 w-3
28
+ sm: h-4 w-4
29
+ md: h-6 w-6 (default)
30
+ lg: h-8 w-8
31
+ xl: h-12 w-12
32
+ ```
33
+
34
+ ### With Label
35
+ ```
36
+ Container: inline-flex items-center gap-2
37
+ Label: text-sm text-muted-foreground
38
+ ```
39
+
40
+ ### Overlay Spinner
41
+ ```
42
+ Container: fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm
43
+ ```
44
+
45
+ ### Button Spinner (inline)
46
+ ```
47
+ mr-2 h-4 w-4 animate-spin
48
+ ```
49
+
50
+ ### Colors
51
+ ```
52
+ default: text-primary
53
+ muted: text-muted-foreground
54
+ white: text-white
55
+ current: text-current (inherits)
56
+ ```
57
+
58
+ ## Props Interface
59
+ ```typescript
60
+ interface SpinnerProps {
61
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
62
+ color?: 'default' | 'muted' | 'white' | 'current'
63
+ className?: string
64
+ label?: string
65
+ 'aria-label'?: string
66
+ }
67
+
68
+ interface LoadingOverlayProps {
69
+ visible: boolean
70
+ label?: string
71
+ blur?: boolean
72
+ }
73
+ ```
74
+
75
+ ## Do
76
+ - Always include aria-label for screen readers
77
+ - Use currentColor for flexible coloring
78
+ - Match spinner size to context (button, page, etc.)
79
+ - Consider reduced motion preferences
80
+
81
+ ## Don't
82
+ - Hardcode colors
83
+ - Use CSS-only spinners (less control)
84
+ - Forget loading state announcements
85
+ - Make spinners too large or distracting
86
+
87
+ ## Example
88
+ ```tsx
89
+ import { cn } from '@/lib/utils'
90
+
91
+ const spinnerSizes = {
92
+ xs: 'h-3 w-3',
93
+ sm: 'h-4 w-4',
94
+ md: 'h-6 w-6',
95
+ lg: 'h-8 w-8',
96
+ xl: 'h-12 w-12',
97
+ }
98
+
99
+ const spinnerColors = {
100
+ default: 'text-primary',
101
+ muted: 'text-muted-foreground',
102
+ white: 'text-white',
103
+ current: 'text-current',
104
+ }
105
+
106
+ const Spinner = ({
107
+ size = 'md',
108
+ color = 'default',
109
+ className,
110
+ label,
111
+ 'aria-label': ariaLabel = 'Loading',
112
+ }: SpinnerProps) => {
113
+ const spinner = (
114
+ <svg
115
+ className={cn(
116
+ 'animate-spin',
117
+ spinnerSizes[size],
118
+ spinnerColors[color],
119
+ className
120
+ )}
121
+ viewBox="0 0 24 24"
122
+ fill="none"
123
+ aria-label={ariaLabel}
124
+ role="status"
125
+ >
126
+ <circle
127
+ className="opacity-25"
128
+ cx="12"
129
+ cy="12"
130
+ r="10"
131
+ stroke="currentColor"
132
+ strokeWidth="4"
133
+ />
134
+ <path
135
+ className="opacity-75"
136
+ fill="currentColor"
137
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
138
+ />
139
+ </svg>
140
+ )
141
+
142
+ if (label) {
143
+ return (
144
+ <div className="inline-flex items-center gap-2">
145
+ {spinner}
146
+ <span className="text-sm text-muted-foreground">{label}</span>
147
+ </div>
148
+ )
149
+ }
150
+
151
+ return spinner
152
+ }
153
+
154
+ // Dots variant spinner
155
+ const DotsSpinner = ({ size = 'md', className }: SpinnerProps) => (
156
+ <div className={cn('flex items-center gap-1', className)}>
157
+ {[0, 1, 2].map((i) => (
158
+ <div
159
+ key={i}
160
+ className={cn(
161
+ 'rounded-full bg-primary animate-bounce',
162
+ size === 'sm' ? 'h-1.5 w-1.5' : 'h-2 w-2'
163
+ )}
164
+ style={{ animationDelay: `${i * 0.1}s` }}
165
+ />
166
+ ))}
167
+ </div>
168
+ )
169
+
170
+ // Loading overlay
171
+ const LoadingOverlay = ({
172
+ visible,
173
+ label = 'Loading...',
174
+ blur = true,
175
+ }: LoadingOverlayProps) => {
176
+ if (!visible) return null
177
+
178
+ return (
179
+ <div
180
+ className={cn(
181
+ 'fixed inset-0 z-50 flex flex-col items-center justify-center',
182
+ 'bg-background/80',
183
+ blur && 'backdrop-blur-sm'
184
+ )}
185
+ >
186
+ <Spinner size="xl" />
187
+ {label && (
188
+ <p className="mt-4 text-sm text-muted-foreground">{label}</p>
189
+ )}
190
+ </div>
191
+ )
192
+ }
193
+
194
+ // Button with loading state
195
+ const LoadingButton = ({ loading, children, ...props }) => (
196
+ <Button disabled={loading} {...props}>
197
+ {loading && <Spinner size="sm" color="current" className="mr-2" />}
198
+ {children}
199
+ </Button>
200
+ )
201
+
202
+ // Usage examples
203
+ <Spinner />
204
+ <Spinner size="lg" color="muted" />
205
+ <Spinner size="sm" label="Loading data..." />
206
+ <DotsSpinner />
207
+ <LoadingOverlay visible={isLoading} label="Saving changes..." />
208
+
209
+ <LoadingButton loading={isSubmitting}>
210
+ {isSubmitting ? 'Saving...' : 'Save'}
211
+ </LoadingButton>
212
+ ```
213
+
214
+ ## Reduced Motion Support
215
+ ```css
216
+ @media (prefers-reduced-motion: reduce) {
217
+ .animate-spin {
218
+ animation: none;
219
+ opacity: 0.5;
220
+ }
221
+ .animate-bounce {
222
+ animation: none;
223
+ }
224
+ }
225
+ ```
@@ -0,0 +1,100 @@
1
+ # Switch Component Recipe
2
+
3
+ ## Structure
4
+ - Toggle control with sliding thumb
5
+ - On/Off states with visual indication
6
+ - Optional label alongside
7
+ - Accessible with keyboard
8
+
9
+ ## Tailwind Classes
10
+
11
+ ### Root/Track
12
+ ```
13
+ peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center {tokens.radius}
14
+ border-2 border-transparent shadow-sm transition-colors
15
+ focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2
16
+ disabled:cursor-not-allowed disabled:opacity-50
17
+ data-[state=checked]:bg-primary data-[state=unchecked]:bg-muted
18
+ ```
19
+
20
+ ### Thumb
21
+ ```
22
+ pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0
23
+ transition-transform
24
+ data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0
25
+ ```
26
+
27
+ ### Sizes
28
+ ```
29
+ sm: Track h-4 w-7, Thumb h-3 w-3, translate-x-3
30
+ md: Track h-5 w-9, Thumb h-4 w-4, translate-x-4 (default)
31
+ lg: Track h-6 w-11, Thumb h-5 w-5, translate-x-5
32
+ ```
33
+
34
+ ### With Label
35
+ ```
36
+ flex items-center space-x-2
37
+ Label: text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70
38
+ ```
39
+
40
+ ## Props Interface
41
+ ```typescript
42
+ interface SwitchProps {
43
+ checked?: boolean
44
+ onCheckedChange?: (checked: boolean) => void
45
+ disabled?: boolean
46
+ required?: boolean
47
+ name?: string
48
+ value?: string
49
+ id?: string
50
+ }
51
+ ```
52
+
53
+ ## Do
54
+ - Use Radix Switch for accessibility
55
+ - Include focus ring
56
+ - Animate thumb movement smoothly
57
+ - Use semantic colors (primary for checked)
58
+
59
+ ## Don't
60
+ - Hardcode colors
61
+ - Skip keyboard support (Space to toggle)
62
+ - Forget disabled state
63
+ - Use for multiple selections (use checkboxes)
64
+
65
+ ## Example
66
+ ```tsx
67
+ import * as SwitchPrimitive from '@radix-ui/react-switch'
68
+ import { cn } from '@/lib/utils'
69
+
70
+ const Switch = ({ className, ...props }) => (
71
+ <SwitchPrimitive.Root
72
+ className={cn(
73
+ 'peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full',
74
+ 'border-2 border-transparent shadow-sm transition-colors',
75
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2',
76
+ 'disabled:cursor-not-allowed disabled:opacity-50',
77
+ 'data-[state=checked]:bg-primary data-[state=unchecked]:bg-muted',
78
+ className
79
+ )}
80
+ {...props}
81
+ >
82
+ <SwitchPrimitive.Thumb
83
+ className={cn(
84
+ 'pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0',
85
+ 'transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0'
86
+ )}
87
+ />
88
+ </SwitchPrimitive.Root>
89
+ )
90
+
91
+ // With label
92
+ const SwitchWithLabel = ({ label, id, ...props }) => (
93
+ <div className="flex items-center space-x-2">
94
+ <Switch id={id} {...props} />
95
+ <label htmlFor={id} className="text-sm font-medium leading-none">
96
+ {label}
97
+ </label>
98
+ </div>
99
+ )
100
+ ```