@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,220 @@
1
+ # Breadcrumb Component Recipe
2
+
3
+ ## Structure
4
+ - Ordered list of navigation links
5
+ - Separator between items (chevron or slash)
6
+ - Current page indicator (not a link)
7
+ - Optional: collapsible for long paths
8
+ - Support for icons
9
+
10
+ ## Tailwind Classes
11
+
12
+ ### Nav Container
13
+ ```
14
+ <nav aria-label="Breadcrumb">
15
+ ```
16
+
17
+ ### List
18
+ ```
19
+ flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground
20
+ sm:gap-2.5
21
+ ```
22
+
23
+ ### Item
24
+ ```
25
+ inline-flex items-center gap-1.5
26
+ ```
27
+
28
+ ### Link
29
+ ```
30
+ transition-colors hover:text-foreground
31
+ ```
32
+
33
+ ### Current Page (not a link)
34
+ ```
35
+ font-normal text-foreground
36
+ ```
37
+
38
+ ### Separator
39
+ ```
40
+ [&>svg]:h-3.5 [&>svg]:w-3.5
41
+ text-muted-foreground
42
+ ```
43
+
44
+ ### Ellipsis (collapsed items)
45
+ ```
46
+ flex h-9 w-9 items-center justify-center
47
+ ```
48
+
49
+ ### With Icons
50
+ ```
51
+ [&>svg]:mr-1.5 [&>svg]:h-4 [&>svg]:w-4
52
+ ```
53
+
54
+ ## Props Interface
55
+ ```typescript
56
+ interface BreadcrumbProps {
57
+ children: React.ReactNode
58
+ separator?: React.ReactNode
59
+ className?: string
60
+ }
61
+
62
+ interface BreadcrumbItemProps {
63
+ href?: string
64
+ current?: boolean
65
+ children: React.ReactNode
66
+ }
67
+
68
+ interface BreadcrumbListProps {
69
+ children: React.ReactNode
70
+ className?: string
71
+ }
72
+
73
+ interface BreadcrumbLinkProps {
74
+ href: string
75
+ asChild?: boolean
76
+ children: React.ReactNode
77
+ }
78
+
79
+ interface BreadcrumbPageProps {
80
+ children: React.ReactNode
81
+ }
82
+
83
+ interface BreadcrumbSeparatorProps {
84
+ children?: React.ReactNode
85
+ }
86
+
87
+ interface BreadcrumbEllipsisProps {
88
+ className?: string
89
+ }
90
+ ```
91
+
92
+ ## Accessibility
93
+ - Use `<nav aria-label="Breadcrumb">`
94
+ - Use `<ol>` for ordered list semantics
95
+ - Mark current page with `aria-current="page"`
96
+ - Last item should not be a link
97
+
98
+ ## Do
99
+ - Keep breadcrumb trails short (collapse if needed)
100
+ - Use consistent separator throughout
101
+ - Make all items except current clickable
102
+ - Include home/root as first item
103
+
104
+ ## Don't
105
+ - Hardcode colors
106
+ - Make current page a link
107
+ - Use for non-hierarchical navigation
108
+ - Show breadcrumbs on home page
109
+
110
+ ## Example
111
+ ```tsx
112
+ import { ChevronRight, MoreHorizontal } from 'lucide-react'
113
+ import { Slot } from '@radix-ui/react-slot'
114
+ import { cn } from '@/lib/utils'
115
+
116
+ const Breadcrumb = ({ children, ...props }) => (
117
+ <nav aria-label="breadcrumb" {...props}>
118
+ {children}
119
+ </nav>
120
+ )
121
+
122
+ const BreadcrumbList = ({ className, ...props }) => (
123
+ <ol
124
+ className={cn(
125
+ 'flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5',
126
+ className
127
+ )}
128
+ {...props}
129
+ />
130
+ )
131
+
132
+ const BreadcrumbItem = ({ className, ...props }) => (
133
+ <li className={cn('inline-flex items-center gap-1.5', className)} {...props} />
134
+ )
135
+
136
+ const BreadcrumbLink = ({ asChild, className, ...props }) => {
137
+ const Comp = asChild ? Slot : 'a'
138
+ return (
139
+ <Comp
140
+ className={cn('transition-colors hover:text-foreground', className)}
141
+ {...props}
142
+ />
143
+ )
144
+ }
145
+
146
+ const BreadcrumbPage = ({ className, ...props }) => (
147
+ <span
148
+ role="link"
149
+ aria-disabled="true"
150
+ aria-current="page"
151
+ className={cn('font-normal text-foreground', className)}
152
+ {...props}
153
+ />
154
+ )
155
+
156
+ const BreadcrumbSeparator = ({ children, className, ...props }) => (
157
+ <li
158
+ role="presentation"
159
+ aria-hidden="true"
160
+ className={cn('[&>svg]:h-3.5 [&>svg]:w-3.5', className)}
161
+ {...props}
162
+ >
163
+ {children ?? <ChevronRight />}
164
+ </li>
165
+ )
166
+
167
+ const BreadcrumbEllipsis = ({ className, ...props }) => (
168
+ <span
169
+ role="presentation"
170
+ aria-hidden="true"
171
+ className={cn('flex h-9 w-9 items-center justify-center', className)}
172
+ {...props}
173
+ >
174
+ <MoreHorizontal className="h-4 w-4" />
175
+ <span className="sr-only">More</span>
176
+ </span>
177
+ )
178
+
179
+ // Usage
180
+ <Breadcrumb>
181
+ <BreadcrumbList>
182
+ <BreadcrumbItem>
183
+ <BreadcrumbLink href="/">Home</BreadcrumbLink>
184
+ </BreadcrumbItem>
185
+ <BreadcrumbSeparator />
186
+ <BreadcrumbItem>
187
+ <BreadcrumbLink href="/products">Products</BreadcrumbLink>
188
+ </BreadcrumbItem>
189
+ <BreadcrumbSeparator />
190
+ <BreadcrumbItem>
191
+ <BreadcrumbPage>Current Product</BreadcrumbPage>
192
+ </BreadcrumbItem>
193
+ </BreadcrumbList>
194
+ </Breadcrumb>
195
+
196
+ // With collapsed items
197
+ <Breadcrumb>
198
+ <BreadcrumbList>
199
+ <BreadcrumbItem>
200
+ <BreadcrumbLink href="/">Home</BreadcrumbLink>
201
+ </BreadcrumbItem>
202
+ <BreadcrumbSeparator />
203
+ <BreadcrumbItem>
204
+ <DropdownMenu>
205
+ <DropdownMenuTrigger>
206
+ <BreadcrumbEllipsis />
207
+ </DropdownMenuTrigger>
208
+ <DropdownMenuContent>
209
+ <DropdownMenuItem>Products</DropdownMenuItem>
210
+ <DropdownMenuItem>Category</DropdownMenuItem>
211
+ </DropdownMenuContent>
212
+ </DropdownMenu>
213
+ </BreadcrumbItem>
214
+ <BreadcrumbSeparator />
215
+ <BreadcrumbItem>
216
+ <BreadcrumbPage>Item</BreadcrumbPage>
217
+ </BreadcrumbItem>
218
+ </BreadcrumbList>
219
+ </Breadcrumb>
220
+ ```
@@ -0,0 +1,90 @@
1
+ # Button Component Recipe
2
+
3
+ ## Structure
4
+ - Use `<button>` element with `type` attribute (default: "button")
5
+ - Support variants: primary, secondary, outline, ghost, destructive
6
+ - Support sizes: sm, md, lg
7
+ - Include states: loading, disabled
8
+ - Optionally support `asChild` for composition (render as link, etc.)
9
+
10
+ ## Tailwind Classes
11
+
12
+ ### Base
13
+ ```
14
+ inline-flex items-center justify-center gap-2 font-medium transition-colors
15
+ focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2
16
+ disabled:pointer-events-none disabled:opacity-50
17
+ ```
18
+
19
+ ### Variants
20
+ ```
21
+ primary: bg-primary text-primary-foreground hover:bg-primary/90 focus-visible:ring-primary
22
+ secondary: bg-secondary text-secondary-foreground hover:bg-secondary/90 focus-visible:ring-secondary
23
+ outline: border border-border bg-transparent hover:bg-surface focus-visible:ring-primary
24
+ ghost: bg-transparent hover:bg-surface focus-visible:ring-primary
25
+ destructive: bg-destructive text-destructive-foreground hover:bg-destructive/90 focus-visible:ring-destructive
26
+ ```
27
+
28
+ ### Sizes
29
+ ```
30
+ sm: h-8 px-3 text-sm {tokens.radius}
31
+ md: h-10 px-4 text-sm {tokens.radius}
32
+ lg: h-12 px-6 text-base {tokens.radius}
33
+ ```
34
+
35
+ ### States
36
+ ```
37
+ loading: relative [&>*]:invisible
38
+ Add spinner overlay with absolute positioning
39
+ disabled: opacity-50 cursor-not-allowed
40
+ ```
41
+
42
+ ## Props Interface
43
+ ```typescript
44
+ interface ButtonProps {
45
+ variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive'
46
+ size?: 'sm' | 'md' | 'lg'
47
+ loading?: boolean
48
+ disabled?: boolean
49
+ asChild?: boolean
50
+ children: React.ReactNode
51
+ }
52
+ ```
53
+
54
+ ## Do
55
+ - Use `cn()` utility for class merging
56
+ - Include `focus-visible` ring for accessibility
57
+ - Support icon-only buttons (square aspect ratio)
58
+ - Use semantic color tokens from config
59
+
60
+ ## Don't
61
+ - Hardcode colors (use `bg-primary` not `bg-blue-500`)
62
+ - Forget hover states
63
+ - Skip focus indicators
64
+ - Use inline styles
65
+
66
+ ## Example
67
+ ```tsx
68
+ import { cn } from '@/lib/utils'
69
+
70
+ const Button = ({ variant = 'primary', size = 'md', loading, disabled, className, children, ...props }) => {
71
+ return (
72
+ <button
73
+ className={cn(
74
+ 'inline-flex items-center justify-center gap-2 font-medium transition-colors',
75
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
76
+ 'disabled:pointer-events-none disabled:opacity-50',
77
+ variants[variant],
78
+ sizes[size],
79
+ loading && 'relative [&>*]:invisible',
80
+ className
81
+ )}
82
+ disabled={disabled || loading}
83
+ {...props}
84
+ >
85
+ {loading && <Spinner className="absolute" />}
86
+ {children}
87
+ </button>
88
+ )
89
+ }
90
+ ```
@@ -0,0 +1,130 @@
1
+ # Card Component Recipe
2
+
3
+ ## Structure
4
+ - Container with `<div>` element
5
+ - Optional subcomponents: CardHeader, CardTitle, CardDescription, CardContent, CardFooter
6
+ - Composable pattern - each part is optional
7
+
8
+ ## Tailwind Classes
9
+
10
+ ### Card (Container)
11
+ ```
12
+ {tokens.radius} border border-border bg-surface {tokens.shadow}
13
+ ```
14
+
15
+ ### CardHeader
16
+ ```
17
+ flex flex-col space-y-1.5 p-{tokens.spacing.normal}
18
+ ```
19
+
20
+ ### CardTitle
21
+ ```
22
+ {tokens.typography.heading} text-lg text-foreground
23
+ ```
24
+
25
+ ### CardDescription
26
+ ```
27
+ text-sm text-muted-foreground
28
+ ```
29
+
30
+ ### CardContent
31
+ ```
32
+ p-{tokens.spacing.normal} pt-0
33
+ ```
34
+
35
+ ### CardFooter
36
+ ```
37
+ flex items-center p-{tokens.spacing.normal} pt-0
38
+ ```
39
+
40
+ ## Props Interface
41
+ ```typescript
42
+ interface CardProps {
43
+ className?: string
44
+ children: React.ReactNode
45
+ }
46
+
47
+ interface CardHeaderProps {
48
+ className?: string
49
+ children: React.ReactNode
50
+ }
51
+
52
+ interface CardTitleProps {
53
+ className?: string
54
+ children: React.ReactNode
55
+ }
56
+
57
+ interface CardDescriptionProps {
58
+ className?: string
59
+ children: React.ReactNode
60
+ }
61
+
62
+ interface CardContentProps {
63
+ className?: string
64
+ children: React.ReactNode
65
+ }
66
+
67
+ interface CardFooterProps {
68
+ className?: string
69
+ children: React.ReactNode
70
+ }
71
+ ```
72
+
73
+ ## Do
74
+ - Use composable pattern (Card.Header, Card.Title, etc.)
75
+ - Apply shadow from `{tokens.shadow}`
76
+ - Use `bg-surface` for background (not `bg-white`)
77
+ - Support flexible content layouts
78
+
79
+ ## Don't
80
+ - Hardcode padding values
81
+ - Use `bg-white` (use `bg-surface` for theme support)
82
+ - Forget border for definition
83
+ - Apply heavy shadows unless config specifies
84
+
85
+ ## Example
86
+ ```tsx
87
+ import { cn } from '@/lib/utils'
88
+
89
+ const Card = ({ className, children, ...props }) => (
90
+ <div
91
+ className={cn(
92
+ 'rounded-lg border border-border bg-surface shadow-sm',
93
+ className
94
+ )}
95
+ {...props}
96
+ >
97
+ {children}
98
+ </div>
99
+ )
100
+
101
+ const CardHeader = ({ className, children, ...props }) => (
102
+ <div className={cn('flex flex-col space-y-1.5 p-4', className)} {...props}>
103
+ {children}
104
+ </div>
105
+ )
106
+
107
+ const CardTitle = ({ className, children, ...props }) => (
108
+ <h3 className={cn('font-semibold text-lg text-foreground', className)} {...props}>
109
+ {children}
110
+ </h3>
111
+ )
112
+
113
+ const CardDescription = ({ className, children, ...props }) => (
114
+ <p className={cn('text-sm text-muted-foreground', className)} {...props}>
115
+ {children}
116
+ </p>
117
+ )
118
+
119
+ const CardContent = ({ className, children, ...props }) => (
120
+ <div className={cn('p-4 pt-0', className)} {...props}>
121
+ {children}
122
+ </div>
123
+ )
124
+
125
+ const CardFooter = ({ className, children, ...props }) => (
126
+ <div className={cn('flex items-center p-4 pt-0', className)} {...props}>
127
+ {children}
128
+ </div>
129
+ )
130
+ ```
@@ -0,0 +1,277 @@
1
+ # Carousel Component Recipe
2
+
3
+ ## Structure
4
+ - Container with overflow hidden
5
+ - Scrollable track with items
6
+ - Previous/Next navigation buttons
7
+ - Optional pagination dots
8
+ - Support for auto-play
9
+ - Touch/swipe support
10
+
11
+ ## Tailwind Classes
12
+
13
+ ### Container
14
+ ```
15
+ relative w-full overflow-hidden
16
+ ```
17
+
18
+ ### Track
19
+ ```
20
+ flex transition-transform duration-300 ease-out
21
+ ```
22
+
23
+ ### Item
24
+ ```
25
+ min-w-0 shrink-0 grow-0
26
+ ```
27
+
28
+ ### Item Sizes
29
+ ```
30
+ full: basis-full
31
+ half: basis-1/2
32
+ third: basis-1/3
33
+ quarter: basis-1/4
34
+ ```
35
+
36
+ ### Navigation Buttons
37
+ ```
38
+ absolute top-1/2 -translate-y-1/2 z-10
39
+ inline-flex items-center justify-center
40
+ h-10 w-10 {tokens.radius}
41
+ bg-background/80 backdrop-blur-sm border border-border
42
+ hover:bg-muted
43
+ disabled:opacity-50 disabled:pointer-events-none
44
+ transition-colors
45
+
46
+ Previous: left-2
47
+ Next: right-2
48
+ ```
49
+
50
+ ### Pagination Dots
51
+ ```
52
+ Container: absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2
53
+ Dot: h-2 w-2 rounded-full bg-background/50 transition-colors
54
+ Dot active: bg-background
55
+ ```
56
+
57
+ ### With Gap
58
+ ```
59
+ Track: -ml-4 (negative margin)
60
+ Item: pl-4 (padding for gap)
61
+ ```
62
+
63
+ ## Props Interface
64
+ ```typescript
65
+ interface CarouselProps {
66
+ children: React.ReactNode
67
+ slidesPerView?: number | 'auto'
68
+ spaceBetween?: number
69
+ loop?: boolean
70
+ autoplay?: boolean | { delay: number }
71
+ showNavigation?: boolean
72
+ showPagination?: boolean
73
+ className?: string
74
+ }
75
+
76
+ interface CarouselItemProps {
77
+ children: React.ReactNode
78
+ className?: string
79
+ }
80
+
81
+ interface CarouselApi {
82
+ scrollPrev: () => void
83
+ scrollNext: () => void
84
+ scrollTo: (index: number) => void
85
+ canScrollPrev: boolean
86
+ canScrollNext: boolean
87
+ selectedIndex: number
88
+ }
89
+ ```
90
+
91
+ ## Do
92
+ - Use embla-carousel for robust implementation
93
+ - Support touch/swipe gestures
94
+ - Provide keyboard navigation
95
+ - Include aria labels for accessibility
96
+ - Respect reduced-motion preferences
97
+
98
+ ## Don't
99
+ - Hardcode colors or dimensions
100
+ - Auto-play without user control to pause
101
+ - Forget mobile responsiveness
102
+ - Skip focus management
103
+
104
+ ## Example
105
+ ```tsx
106
+ import { useCallback, useEffect, useState } from 'react'
107
+ import useEmblaCarousel from 'embla-carousel-react'
108
+ import { ChevronLeft, ChevronRight } from 'lucide-react'
109
+ import { cn } from '@/lib/utils'
110
+
111
+ const Carousel = ({
112
+ children,
113
+ showNavigation = true,
114
+ showPagination = true,
115
+ loop = false,
116
+ className,
117
+ ...options
118
+ }) => {
119
+ const [emblaRef, emblaApi] = useEmblaCarousel({ loop, ...options })
120
+ const [canScrollPrev, setCanScrollPrev] = useState(false)
121
+ const [canScrollNext, setCanScrollNext] = useState(false)
122
+ const [selectedIndex, setSelectedIndex] = useState(0)
123
+ const [scrollSnaps, setScrollSnaps] = useState<number[]>([])
124
+
125
+ const scrollPrev = useCallback(() => emblaApi?.scrollPrev(), [emblaApi])
126
+ const scrollNext = useCallback(() => emblaApi?.scrollNext(), [emblaApi])
127
+ const scrollTo = useCallback((index: number) => emblaApi?.scrollTo(index), [emblaApi])
128
+
129
+ const onSelect = useCallback(() => {
130
+ if (!emblaApi) return
131
+ setSelectedIndex(emblaApi.selectedScrollSnap())
132
+ setCanScrollPrev(emblaApi.canScrollPrev())
133
+ setCanScrollNext(emblaApi.canScrollNext())
134
+ }, [emblaApi])
135
+
136
+ useEffect(() => {
137
+ if (!emblaApi) return
138
+ setScrollSnaps(emblaApi.scrollSnapList())
139
+ onSelect()
140
+ emblaApi.on('select', onSelect)
141
+ emblaApi.on('reInit', onSelect)
142
+ return () => {
143
+ emblaApi.off('select', onSelect)
144
+ emblaApi.off('reInit', onSelect)
145
+ }
146
+ }, [emblaApi, onSelect])
147
+
148
+ return (
149
+ <div className={cn('relative w-full', className)} aria-roledescription="carousel">
150
+ <div ref={emblaRef} className="overflow-hidden">
151
+ <div className="flex -ml-4">
152
+ {children}
153
+ </div>
154
+ </div>
155
+
156
+ {showNavigation && (
157
+ <>
158
+ <button
159
+ onClick={scrollPrev}
160
+ disabled={!canScrollPrev}
161
+ className={cn(
162
+ 'absolute left-2 top-1/2 -translate-y-1/2 z-10',
163
+ 'inline-flex items-center justify-center h-10 w-10 rounded-full',
164
+ 'bg-background/80 backdrop-blur-sm border border-border',
165
+ 'hover:bg-muted transition-colors',
166
+ 'disabled:opacity-50 disabled:pointer-events-none'
167
+ )}
168
+ aria-label="Previous slide"
169
+ >
170
+ <ChevronLeft className="h-5 w-5" />
171
+ </button>
172
+ <button
173
+ onClick={scrollNext}
174
+ disabled={!canScrollNext}
175
+ className={cn(
176
+ 'absolute right-2 top-1/2 -translate-y-1/2 z-10',
177
+ 'inline-flex items-center justify-center h-10 w-10 rounded-full',
178
+ 'bg-background/80 backdrop-blur-sm border border-border',
179
+ 'hover:bg-muted transition-colors',
180
+ 'disabled:opacity-50 disabled:pointer-events-none'
181
+ )}
182
+ aria-label="Next slide"
183
+ >
184
+ <ChevronRight className="h-5 w-5" />
185
+ </button>
186
+ </>
187
+ )}
188
+
189
+ {showPagination && scrollSnaps.length > 1 && (
190
+ <div className="absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2">
191
+ {scrollSnaps.map((_, index) => (
192
+ <button
193
+ key={index}
194
+ onClick={() => scrollTo(index)}
195
+ className={cn(
196
+ 'h-2 w-2 rounded-full transition-colors',
197
+ index === selectedIndex
198
+ ? 'bg-primary'
199
+ : 'bg-primary/30 hover:bg-primary/50'
200
+ )}
201
+ aria-label={`Go to slide ${index + 1}`}
202
+ aria-current={index === selectedIndex ? 'true' : undefined}
203
+ />
204
+ ))}
205
+ </div>
206
+ )}
207
+ </div>
208
+ )
209
+ }
210
+
211
+ const CarouselItem = ({ children, className }) => (
212
+ <div
213
+ className={cn('min-w-0 shrink-0 grow-0 basis-full pl-4', className)}
214
+ role="group"
215
+ aria-roledescription="slide"
216
+ >
217
+ {children}
218
+ </div>
219
+ )
220
+
221
+ // With autoplay
222
+ const AutoplayCarousel = ({ children, delay = 4000, ...props }) => {
223
+ const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true })
224
+ const [isPaused, setIsPaused] = useState(false)
225
+
226
+ useEffect(() => {
227
+ if (!emblaApi || isPaused) return
228
+ const interval = setInterval(() => emblaApi.scrollNext(), delay)
229
+ return () => clearInterval(interval)
230
+ }, [emblaApi, delay, isPaused])
231
+
232
+ return (
233
+ <div
234
+ onMouseEnter={() => setIsPaused(true)}
235
+ onMouseLeave={() => setIsPaused(false)}
236
+ >
237
+ <Carousel {...props}>{children}</Carousel>
238
+ </div>
239
+ )
240
+ }
241
+
242
+ // Usage examples
243
+ <Carousel>
244
+ <CarouselItem>
245
+ <img src="/slide-1.jpg" alt="Slide 1" className="w-full aspect-video object-cover rounded-lg" />
246
+ </CarouselItem>
247
+ <CarouselItem>
248
+ <img src="/slide-2.jpg" alt="Slide 2" className="w-full aspect-video object-cover rounded-lg" />
249
+ </CarouselItem>
250
+ <CarouselItem>
251
+ <img src="/slide-3.jpg" alt="Slide 3" className="w-full aspect-video object-cover rounded-lg" />
252
+ </CarouselItem>
253
+ </Carousel>
254
+
255
+ // Multiple slides visible
256
+ <Carousel slidesPerView={3}>
257
+ {products.map((product) => (
258
+ <CarouselItem key={product.id} className="basis-1/3">
259
+ <ProductCard product={product} />
260
+ </CarouselItem>
261
+ ))}
262
+ </Carousel>
263
+
264
+ // Card carousel
265
+ <Carousel showPagination={false}>
266
+ {testimonials.map((t) => (
267
+ <CarouselItem key={t.id}>
268
+ <Card className="mx-4">
269
+ <CardContent className="p-6">
270
+ <p className="text-muted-foreground">{t.quote}</p>
271
+ <p className="mt-4 font-medium">{t.author}</p>
272
+ </CardContent>
273
+ </Card>
274
+ </CarouselItem>
275
+ ))}
276
+ </Carousel>
277
+ ```