create-manifest 1.3.4 → 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 (57) hide show
  1. package/README.md +40 -21
  2. package/index.js +51 -0
  3. package/package.json +11 -89
  4. package/starter/.claude/settings.local.json +21 -0
  5. package/starter/.env.example +1 -0
  6. package/starter/@/components/table.tsx +478 -0
  7. package/starter/@/components/ui/button.tsx +62 -0
  8. package/starter/@/components/ui/checkbox.tsx +30 -0
  9. package/starter/README-DEV.md +167 -0
  10. package/starter/components.json +24 -0
  11. package/starter/package.json +42 -0
  12. package/starter/src/flows/list-pokemons.flow.ts +131 -0
  13. package/starter/src/server.ts +165 -0
  14. package/starter/src/web/PokemonList.tsx +125 -0
  15. package/starter/src/web/components/blog-post-card.tsx +286 -0
  16. package/starter/src/web/components/blog-post-list.tsx +291 -0
  17. package/starter/src/web/components/ui/.gitkeep +0 -0
  18. package/starter/src/web/components/ui/button.tsx +62 -0
  19. package/starter/src/web/globals.css +98 -0
  20. package/starter/src/web/hooks/.gitkeep +0 -0
  21. package/starter/src/web/lib/utils.ts +6 -0
  22. package/starter/src/web/root.tsx +36 -0
  23. package/starter/src/web/tsconfig.json +3 -0
  24. package/starter/tsconfig.json +21 -0
  25. package/starter/tsconfig.web.json +24 -0
  26. package/starter/vite.config.ts +36 -0
  27. package/assets/monorepo/README.md +0 -52
  28. package/assets/monorepo/api-package.json +0 -9
  29. package/assets/monorepo/api-readme.md +0 -50
  30. package/assets/monorepo/manifest.yml +0 -34
  31. package/assets/monorepo/root-package.json +0 -15
  32. package/assets/monorepo/web-package.json +0 -10
  33. package/assets/monorepo/web-readme.md +0 -9
  34. package/assets/standalone/README.md +0 -50
  35. package/assets/standalone/api-package.json +0 -9
  36. package/assets/standalone/manifest.yml +0 -34
  37. package/bin/dev.cmd +0 -3
  38. package/bin/dev.js +0 -5
  39. package/bin/run.cmd +0 -3
  40. package/bin/run.js +0 -5
  41. package/dist/commands/index.d.ts +0 -65
  42. package/dist/commands/index.js +0 -480
  43. package/dist/index.d.ts +0 -1
  44. package/dist/index.js +0 -1
  45. package/dist/utils/GetBackendFileContent.d.ts +0 -1
  46. package/dist/utils/GetBackendFileContent.js +0 -21
  47. package/dist/utils/GetLatestPackageVersion.d.ts +0 -1
  48. package/dist/utils/GetLatestPackageVersion.js +0 -5
  49. package/dist/utils/UpdateExtensionJsonFile.d.ts +0 -6
  50. package/dist/utils/UpdateExtensionJsonFile.js +0 -8
  51. package/dist/utils/UpdatePackageJsonFile.d.ts +0 -18
  52. package/dist/utils/UpdatePackageJsonFile.js +0 -21
  53. package/dist/utils/UpdateSettingsJsonFile.d.ts +0 -4
  54. package/dist/utils/UpdateSettingsJsonFile.js +0 -6
  55. package/dist/utils/helpers.d.ts +0 -1
  56. package/dist/utils/helpers.js +0 -11
  57. package/oclif.manifest.json +0 -47
@@ -0,0 +1,286 @@
1
+ 'use client'
2
+
3
+ import { Button } from '@/components/ui/button'
4
+ import { ArrowRight } from 'lucide-react'
5
+
6
+ export interface BlogPost {
7
+ id: string
8
+ title: string
9
+ excerpt: string
10
+ coverImage?: string
11
+ author: {
12
+ name: string
13
+ avatar?: string
14
+ }
15
+ publishedAt: string
16
+ readTime?: string
17
+ tags?: string[]
18
+ category?: string
19
+ }
20
+
21
+ const defaultPost: BlogPost = {
22
+ id: '1',
23
+ title: 'Getting Started with Agentic UI Components',
24
+ excerpt:
25
+ 'Learn how to build conversational interfaces with our comprehensive component library designed for AI-powered applications.',
26
+ coverImage:
27
+ 'https://images.unsplash.com/photo-1633356122544-f134324a6cee?w=800',
28
+ author: {
29
+ name: 'Sarah Chen',
30
+ avatar: 'https://i.pravatar.cc/150?u=sarah'
31
+ },
32
+ publishedAt: '2024-01-15',
33
+ readTime: '5 min read',
34
+ tags: ['Tutorial', 'Components'],
35
+ category: 'Tutorial'
36
+ }
37
+
38
+ export interface BlogPostCardProps {
39
+ post?: BlogPost
40
+ variant?: 'default' | 'compact' | 'horizontal' | 'covered'
41
+ showImage?: boolean
42
+ showAuthor?: boolean
43
+ showCategory?: boolean
44
+ onReadMore?: (post: BlogPost) => void
45
+ }
46
+
47
+ export function BlogPostCard({
48
+ post = defaultPost,
49
+ variant = 'default',
50
+ showImage = true,
51
+ showAuthor = true,
52
+ showCategory = true,
53
+ onReadMore
54
+ }: BlogPostCardProps) {
55
+ const formatDate = (dateStr: string) => {
56
+ return new Date(dateStr).toLocaleDateString('en-US', {
57
+ month: 'short',
58
+ day: 'numeric',
59
+ year: 'numeric'
60
+ })
61
+ }
62
+
63
+ if (variant === 'covered') {
64
+ return (
65
+ <div className="relative overflow-hidden rounded-lg">
66
+ <div className="min-h-[320px] sm:aspect-[16/9] sm:min-h-0 w-full">
67
+ {post.coverImage ? (
68
+ <img
69
+ src={post.coverImage}
70
+ alt={post.title}
71
+ className="absolute inset-0 h-full w-full object-cover"
72
+ />
73
+ ) : (
74
+ <div className="absolute inset-0 h-full w-full bg-muted" />
75
+ )}
76
+ </div>
77
+ <div className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/40 to-black/20" />
78
+ <div className="absolute inset-0 flex flex-col justify-between p-4 text-white">
79
+ <div className="flex gap-1">
80
+ {showCategory && post.category && (
81
+ <span className="rounded-full bg-white/20 px-2 py-0.5 text-xs backdrop-blur-sm">
82
+ {post.category}
83
+ </span>
84
+ )}
85
+ {post.tags &&
86
+ post.tags.slice(0, 1).map((tag) => (
87
+ <span
88
+ key={tag}
89
+ className="rounded-full bg-white/20 px-2 py-0.5 text-xs backdrop-blur-sm"
90
+ >
91
+ {tag}
92
+ </span>
93
+ ))}
94
+ </div>
95
+ <div>
96
+ <h2 className="text-lg font-bold leading-tight">{post.title}</h2>
97
+ <p className="mt-1 line-clamp-2 text-sm text-white/80">
98
+ {post.excerpt}
99
+ </p>
100
+ <div className="mt-3 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
101
+ {showAuthor && (
102
+ <div className="flex items-center gap-2">
103
+ {post.author.avatar && (
104
+ <img
105
+ src={post.author.avatar}
106
+ alt={post.author.name}
107
+ className="h-6 w-6 rounded-full ring-2 ring-white/30"
108
+ />
109
+ )}
110
+ <div className="text-xs">
111
+ <p className="font-medium">{post.author.name}</p>
112
+ <p className="text-white/60">{formatDate(post.publishedAt)}</p>
113
+ </div>
114
+ </div>
115
+ )}
116
+ <Button
117
+ size="sm"
118
+ variant="secondary"
119
+ className="w-full sm:w-auto"
120
+ onClick={() => onReadMore?.(post)}
121
+ >
122
+ Read article
123
+ </Button>
124
+ </div>
125
+ </div>
126
+ </div>
127
+ </div>
128
+ )
129
+ }
130
+
131
+ if (variant === 'horizontal') {
132
+ return (
133
+ <div className="test flex flex-col sm:flex-row gap-4 rounded-lg border bg-card p-3 transition-colors hover:bg-muted/50">
134
+ {showImage && post.coverImage && (
135
+ <div className="aspect-video sm:aspect-square sm:h-24 sm:w-24 shrink-0 overflow-hidden rounded-md">
136
+ <img
137
+ src={post.coverImage}
138
+ alt={post.title}
139
+ className="h-full w-full object-cover"
140
+ />
141
+ </div>
142
+ )}
143
+ <div className="flex flex-1 flex-col justify-between">
144
+ <div>
145
+ {showCategory && post.category && (
146
+ <span className="mb-1 inline-block rounded-full bg-primary/10 px-2 py-0.5 text-xs font-medium text-primary">
147
+ {post.category}
148
+ </span>
149
+ )}
150
+ <h3 className="line-clamp-2 text-sm font-medium leading-tight">
151
+ {post.title}
152
+ </h3>
153
+ <p className="mt-1 line-clamp-2 text-xs text-muted-foreground">
154
+ {post.excerpt}
155
+ </p>
156
+ </div>
157
+ <div className="mt-3 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
158
+ <div className="flex items-center gap-2 text-xs text-muted-foreground">
159
+ {showAuthor && post.author.avatar && (
160
+ <img
161
+ src={post.author.avatar}
162
+ alt={post.author.name}
163
+ className="h-4 w-4 rounded-full"
164
+ />
165
+ )}
166
+ <span>{formatDate(post.publishedAt)}</span>
167
+ {post.readTime && (
168
+ <>
169
+ <span>·</span>
170
+ <span>{post.readTime}</span>
171
+ </>
172
+ )}
173
+ </div>
174
+ <Button
175
+ size="sm"
176
+ className="w-full sm:w-auto"
177
+ onClick={() => onReadMore?.(post)}
178
+ >
179
+ Read
180
+ <ArrowRight className="ml-1 h-3 w-3" />
181
+ </Button>
182
+ </div>
183
+ </div>
184
+ </div>
185
+ )
186
+ }
187
+
188
+ if (variant === 'compact') {
189
+ return (
190
+ <div className="flex h-full flex-col justify-between rounded-lg border bg-card p-3 transition-colors hover:bg-muted/50">
191
+ <div>
192
+ {showCategory && post.category && (
193
+ <span className="mb-2 inline-block rounded-full bg-primary/10 px-2 py-0.5 text-xs font-medium text-primary">
194
+ {post.category}
195
+ </span>
196
+ )}
197
+ <h3 className="line-clamp-2 text-sm font-medium">{post.title}</h3>
198
+ <p className="mt-1 line-clamp-2 text-xs text-muted-foreground">
199
+ {post.excerpt}
200
+ </p>
201
+ </div>
202
+ <div className="mt-3 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
203
+ <div className="flex items-center gap-2">
204
+ {showAuthor && post.author.avatar && (
205
+ <img
206
+ src={post.author.avatar}
207
+ alt={post.author.name}
208
+ className="h-5 w-5 rounded-full"
209
+ />
210
+ )}
211
+ <span className="text-xs text-muted-foreground">
212
+ {formatDate(post.publishedAt)}
213
+ </span>
214
+ </div>
215
+ <Button size="sm" onClick={() => onReadMore?.(post)}>
216
+ Read more
217
+ <ArrowRight className="ml-1 h-3 w-3" />
218
+ </Button>
219
+ </div>
220
+ </div>
221
+ )
222
+ }
223
+
224
+ // Default variant
225
+ return (
226
+ <div className="flex h-full flex-col overflow-hidden rounded-lg border bg-card transition-colors hover:bg-muted/50">
227
+ {showImage && post.coverImage && (
228
+ <div className="aspect-video overflow-hidden">
229
+ <img
230
+ src={post.coverImage}
231
+ alt={post.title}
232
+ className="h-full w-full object-cover transition-transform hover:scale-105"
233
+ />
234
+ </div>
235
+ )}
236
+ <div className="flex flex-1 flex-col justify-between p-4">
237
+ <div>
238
+ <div className="mb-2 flex flex-wrap items-center gap-2">
239
+ {showCategory && post.category && (
240
+ <span className="rounded-full bg-primary/10 px-2 py-0.5 text-xs font-medium text-primary">
241
+ {post.category}
242
+ </span>
243
+ )}
244
+ {post.tags &&
245
+ post.tags.length > 0 &&
246
+ post.tags.slice(0, 2).map((tag) => (
247
+ <span
248
+ key={tag}
249
+ className="rounded-full bg-muted px-2 py-0.5 text-xs text-muted-foreground"
250
+ >
251
+ {tag}
252
+ </span>
253
+ ))}
254
+ </div>
255
+ <h3 className="line-clamp-2 font-medium">{post.title}</h3>
256
+ <p className="mt-2 line-clamp-2 text-sm text-muted-foreground">
257
+ {post.excerpt}
258
+ </p>
259
+ </div>
260
+ <div className="mt-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
261
+ {showAuthor && (
262
+ <div className="flex items-center gap-2">
263
+ {post.author.avatar && (
264
+ <img
265
+ src={post.author.avatar}
266
+ alt={post.author.name}
267
+ className="h-6 w-6 rounded-full"
268
+ />
269
+ )}
270
+ <div className="text-xs">
271
+ <p className="font-medium">{post.author.name}</p>
272
+ <p className="text-muted-foreground">
273
+ {formatDate(post.publishedAt)}
274
+ </p>
275
+ </div>
276
+ </div>
277
+ )}
278
+ <Button size="sm" onClick={() => onReadMore?.(post)}>
279
+ Read
280
+ <ArrowRight className="ml-1 h-4 w-4" />
281
+ </Button>
282
+ </div>
283
+ </div>
284
+ </div>
285
+ )
286
+ }
@@ -0,0 +1,291 @@
1
+ import { Button } from '@/components/ui/button'
2
+ import { cn } from '@/lib/utils'
3
+ import { ChevronLeft, ChevronRight } from 'lucide-react'
4
+ import { useState } from 'react'
5
+ import { BlogPost, BlogPostCard } from './blog-post-card'
6
+
7
+ const defaultPosts: BlogPost[] = [
8
+ {
9
+ id: '1',
10
+ title: 'Getting Started with Agentic UI Components',
11
+ excerpt:
12
+ 'Learn how to build conversational interfaces with our comprehensive component library designed for AI-powered applications.',
13
+ coverImage:
14
+ 'https://images.unsplash.com/photo-1633356122544-f134324a6cee?w=800',
15
+ author: {
16
+ name: 'Sarah Chen',
17
+ avatar: 'https://i.pravatar.cc/150?u=sarah'
18
+ },
19
+ publishedAt: '2024-01-15',
20
+ readTime: '5 min read',
21
+ tags: ['Tutorial', 'Components', 'AI'],
22
+ category: 'Tutorial'
23
+ },
24
+ {
25
+ id: '2',
26
+ title: 'Designing for Conversational Interfaces',
27
+ excerpt:
28
+ 'Best practices for creating intuitive UI components that work within chat environments.',
29
+ coverImage:
30
+ 'https://images.unsplash.com/photo-1559028012-481c04fa702d?w=800',
31
+ author: {
32
+ name: 'Alex Rivera',
33
+ avatar: 'https://i.pravatar.cc/150?u=alex'
34
+ },
35
+ publishedAt: '2024-01-12',
36
+ readTime: '8 min read',
37
+ tags: ['Design', 'UX'],
38
+ category: 'Design'
39
+ },
40
+ {
41
+ id: '3',
42
+ title: 'MCP Integration Patterns',
43
+ excerpt:
44
+ 'How to leverage Model Context Protocol for seamless backend communication in your agentic applications.',
45
+ coverImage:
46
+ 'https://images.unsplash.com/photo-1558494949-ef010cbdcc31?w=800',
47
+ author: {
48
+ name: 'Jordan Kim',
49
+ avatar: 'https://i.pravatar.cc/150?u=jordan'
50
+ },
51
+ publishedAt: '2024-01-10',
52
+ readTime: '12 min read',
53
+ tags: ['MCP', 'Backend', 'Integration'],
54
+ category: 'Development'
55
+ },
56
+ {
57
+ id: '4',
58
+ title: 'Building Payment Flows in Chat',
59
+ excerpt:
60
+ 'A complete guide to implementing secure, user-friendly payment experiences within conversational interfaces.',
61
+ coverImage:
62
+ 'https://images.unsplash.com/photo-1556742049-0cfed4f6a45d?w=800',
63
+ author: {
64
+ name: 'Morgan Lee',
65
+ avatar: 'https://i.pravatar.cc/150?u=morgan'
66
+ },
67
+ publishedAt: '2024-01-08',
68
+ readTime: '10 min read',
69
+ tags: ['Payments', 'Security'],
70
+ category: 'Tutorial'
71
+ }
72
+ ]
73
+
74
+ export interface BlogPostListProps {
75
+ posts?: BlogPost[]
76
+ variant?: 'list' | 'grid' | 'carousel'
77
+ columns?: 2 | 3
78
+ showAuthor?: boolean
79
+ showCategory?: boolean
80
+ onReadMore?: (post: BlogPost) => void
81
+ }
82
+
83
+ export function BlogPostList({
84
+ posts = defaultPosts,
85
+ variant = 'list',
86
+ columns = 2,
87
+ showAuthor = true,
88
+ showCategory = true,
89
+ onReadMore
90
+ }: BlogPostListProps) {
91
+ const [currentIndex, setCurrentIndex] = useState(0)
92
+
93
+ // List variant
94
+ if (variant === 'list') {
95
+ return (
96
+ <div className="space-y-3">
97
+ {posts.slice(0, 3).map((post) => (
98
+ <BlogPostCard
99
+ key={post.id}
100
+ post={post}
101
+ variant="horizontal"
102
+ showAuthor={showAuthor}
103
+ showCategory={showCategory}
104
+ onReadMore={onReadMore}
105
+ />
106
+ ))}
107
+ </div>
108
+ )
109
+ }
110
+
111
+ // Grid variant
112
+ if (variant === 'grid') {
113
+ return (
114
+ <div
115
+ className={cn(
116
+ 'grid gap-4 grid-cols-1',
117
+ columns === 2 ? 'sm:grid-cols-2' : 'sm:grid-cols-3'
118
+ )}
119
+ >
120
+ {posts.map((post) => (
121
+ <BlogPostCard
122
+ key={post.id}
123
+ post={post}
124
+ variant="compact"
125
+ showImage={false}
126
+ showAuthor={showAuthor}
127
+ showCategory={showCategory}
128
+ onReadMore={onReadMore}
129
+ />
130
+ ))}
131
+ </div>
132
+ )
133
+ }
134
+
135
+ // Carousel variant
136
+ const maxIndexMobile = posts.length - 1
137
+ const maxIndexTablet = Math.max(0, posts.length - 2)
138
+ const maxIndexDesktop = Math.max(0, posts.length - 3)
139
+
140
+ const prev = () => {
141
+ setCurrentIndex((i) => Math.max(0, i - 1))
142
+ }
143
+
144
+ const next = () => {
145
+ setCurrentIndex((i) => i + 1)
146
+ }
147
+
148
+ const isAtStart = currentIndex === 0
149
+ const isAtEndMobile = currentIndex >= maxIndexMobile
150
+ const isAtEndTablet = currentIndex >= maxIndexTablet
151
+ const isAtEndDesktop = currentIndex >= maxIndexDesktop
152
+
153
+ return (
154
+ <div className="relative">
155
+ <div className="overflow-hidden rounded-lg">
156
+ {/* Mobile: 1 card, slides by 100% */}
157
+ <div
158
+ className="flex transition-transform duration-300 ease-out md:hidden"
159
+ style={{ transform: `translateX(-${currentIndex * 100}%)` }}
160
+ >
161
+ {posts.map((post) => (
162
+ <div key={post.id} className="w-full shrink-0 px-0.5">
163
+ <BlogPostCard
164
+ post={post}
165
+ variant="compact"
166
+ showAuthor={showAuthor}
167
+ showCategory={showCategory}
168
+ onReadMore={onReadMore}
169
+ />
170
+ </div>
171
+ ))}
172
+ </div>
173
+
174
+ {/* Tablet: 2 cards visible, slides by 50% */}
175
+ <div
176
+ className="hidden md:flex lg:hidden transition-transform duration-300 ease-out"
177
+ style={{ transform: `translateX(-${currentIndex * 50}%)` }}
178
+ >
179
+ {posts.map((post) => (
180
+ <div key={post.id} className="w-1/2 shrink-0 px-1.5">
181
+ <BlogPostCard
182
+ post={post}
183
+ variant="compact"
184
+ showAuthor={showAuthor}
185
+ showCategory={showCategory}
186
+ onReadMore={onReadMore}
187
+ />
188
+ </div>
189
+ ))}
190
+ </div>
191
+
192
+ {/* Desktop: 3 cards visible, slides by 33.333% */}
193
+ <div
194
+ className="hidden lg:flex transition-transform duration-300 ease-out"
195
+ style={{ transform: `translateX(-${currentIndex * (100 / 3)}%)` }}
196
+ >
197
+ {posts.map((post) => (
198
+ <div key={post.id} className="w-1/3 shrink-0 px-1.5">
199
+ <BlogPostCard
200
+ post={post}
201
+ variant="compact"
202
+ showAuthor={showAuthor}
203
+ showCategory={showCategory}
204
+ onReadMore={onReadMore}
205
+ />
206
+ </div>
207
+ ))}
208
+ </div>
209
+ </div>
210
+ <div className="mt-3 flex items-center justify-between px-2">
211
+ <div className="flex gap-1">
212
+ {posts.map((_, i) => (
213
+ <button
214
+ key={i}
215
+ onClick={() => setCurrentIndex(i)}
216
+ className={cn(
217
+ 'h-1.5 rounded-full transition-all',
218
+ i === currentIndex
219
+ ? 'w-4 bg-foreground'
220
+ : 'w-1.5 bg-muted-foreground/30'
221
+ )}
222
+ />
223
+ ))}
224
+ </div>
225
+ {/* Mobile navigation */}
226
+ <div className="flex gap-1 md:hidden">
227
+ <Button
228
+ variant="outline"
229
+ size="icon"
230
+ className="h-8 w-8"
231
+ onClick={prev}
232
+ disabled={isAtStart}
233
+ >
234
+ <ChevronLeft className="h-4 w-4" />
235
+ </Button>
236
+ <Button
237
+ variant="outline"
238
+ size="icon"
239
+ className="h-8 w-8"
240
+ onClick={next}
241
+ disabled={isAtEndMobile}
242
+ >
243
+ <ChevronRight className="h-4 w-4" />
244
+ </Button>
245
+ </div>
246
+ {/* Tablet navigation */}
247
+ <div className="hidden md:flex lg:hidden gap-1">
248
+ <Button
249
+ variant="outline"
250
+ size="icon"
251
+ className="h-8 w-8"
252
+ onClick={prev}
253
+ disabled={isAtStart}
254
+ >
255
+ <ChevronLeft className="h-4 w-4" />
256
+ </Button>
257
+ <Button
258
+ variant="outline"
259
+ size="icon"
260
+ className="h-8 w-8"
261
+ onClick={next}
262
+ disabled={isAtEndTablet}
263
+ >
264
+ <ChevronRight className="h-4 w-4" />
265
+ </Button>
266
+ </div>
267
+ {/* Desktop navigation */}
268
+ <div className="hidden lg:flex gap-1">
269
+ <Button
270
+ variant="outline"
271
+ size="icon"
272
+ className="h-8 w-8"
273
+ onClick={prev}
274
+ disabled={isAtStart}
275
+ >
276
+ <ChevronLeft className="h-4 w-4" />
277
+ </Button>
278
+ <Button
279
+ variant="outline"
280
+ size="icon"
281
+ className="h-8 w-8"
282
+ onClick={next}
283
+ disabled={isAtEndDesktop}
284
+ >
285
+ <ChevronRight className="h-4 w-4" />
286
+ </Button>
287
+ </div>
288
+ </div>
289
+ </div>
290
+ )
291
+ }
File without changes
@@ -0,0 +1,62 @@
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
13
+ destructive:
14
+ "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
15
+ outline:
16
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
17
+ secondary:
18
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19
+ ghost:
20
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
21
+ link: "text-primary underline-offset-4 hover:underline",
22
+ },
23
+ size: {
24
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
25
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
26
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
27
+ icon: "size-9",
28
+ "icon-sm": "size-8",
29
+ "icon-lg": "size-10",
30
+ },
31
+ },
32
+ defaultVariants: {
33
+ variant: "default",
34
+ size: "default",
35
+ },
36
+ }
37
+ )
38
+
39
+ function Button({
40
+ className,
41
+ variant = "default",
42
+ size = "default",
43
+ asChild = false,
44
+ ...props
45
+ }: React.ComponentProps<"button"> &
46
+ VariantProps<typeof buttonVariants> & {
47
+ asChild?: boolean
48
+ }) {
49
+ const Comp = asChild ? Slot : "button"
50
+
51
+ return (
52
+ <Comp
53
+ data-slot="button"
54
+ data-variant={variant}
55
+ data-size={size}
56
+ className={cn(buttonVariants({ variant, size, className }))}
57
+ {...props}
58
+ />
59
+ )
60
+ }
61
+
62
+ export { Button, buttonVariants }