create-nextjs-cms 0.9.15 → 0.9.17

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 (26) hide show
  1. package/package.json +3 -3
  2. package/templates/default/components/ItemEditPage.tsx +25 -25
  3. package/templates/default/components/LocaleSwitcher.tsx +32 -23
  4. package/templates/default/components/SectionPage.tsx +1 -0
  5. package/templates/default/components/form/helpers/_section-hot-reload.js +1 -1
  6. package/templates/default/components/ui/alert-dialog.tsx +84 -128
  7. package/templates/default/components/ui/alert.tsx +1 -1
  8. package/templates/default/components/ui/button.tsx +64 -62
  9. package/templates/default/components/ui/calendar.tsx +220 -166
  10. package/templates/default/components/ui/checkbox.tsx +29 -29
  11. package/templates/default/components/ui/command.tsx +1 -1
  12. package/templates/default/components/ui/custom-alert-dialog.tsx +2 -2
  13. package/templates/default/components/ui/custom-dialog.tsx +3 -3
  14. package/templates/default/components/ui/dialog.tsx +3 -3
  15. package/templates/default/components/ui/dropdown-menu.tsx +9 -9
  16. package/templates/default/components/ui/input-group.tsx +1 -1
  17. package/templates/default/components/ui/scroll-area.tsx +1 -1
  18. package/templates/default/components/ui/select.tsx +3 -3
  19. package/templates/default/components/ui/sheet.tsx +3 -3
  20. package/templates/default/components/ui/switch.tsx +1 -1
  21. package/templates/default/components/ui/table.tsx +2 -2
  22. package/templates/default/components/ui/toast.tsx +3 -3
  23. package/templates/default/components/ui/use-toast.ts +0 -1
  24. package/templates/default/components.json +25 -21
  25. package/templates/default/dynamic-schemas/schema.ts +1 -1
  26. package/templates/default/package.json +3 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-nextjs-cms",
3
- "version": "0.9.15",
3
+ "version": "0.9.17",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -29,8 +29,8 @@
29
29
  "tsx": "^4.20.6",
30
30
  "typescript": "^5.9.2",
31
31
  "@lzcms/eslint-config": "0.3.0",
32
- "@lzcms/tsconfig": "0.1.0",
33
- "@lzcms/prettier-config": "0.1.0"
32
+ "@lzcms/prettier-config": "0.1.0",
33
+ "@lzcms/tsconfig": "0.1.0"
34
34
  },
35
35
  "prettier": "@lzcms/prettier-config",
36
36
  "scripts": {
@@ -51,7 +51,7 @@ export default function ItemEditPage({
51
51
  const handleDirtyChange = useCallback((isDirty: boolean) => setFormDirty(isDirty), [])
52
52
  const dropzoneRef: RefObject<DropzoneHandles | null> = useRef(null)
53
53
  const variantRef: RefObject<VariantHandles[]> = useRef([])
54
- const [data, {refetch}] = trpc.hasItemsSections.editItem.useSuspenseQuery({
54
+ const [data, { refetch }] = trpc.hasItemsSections.editItem.useSuspenseQuery({
55
55
  sectionName: section,
56
56
  sectionItemId: itemId,
57
57
  locale,
@@ -222,21 +222,21 @@ export default function ItemEditPage({
222
222
  }, [])
223
223
 
224
224
  if (data.error) {
225
- return <ErrorComponent message={data.error.message} />
225
+ return <ErrorComponent message={data.error.message} />
226
226
  }
227
227
 
228
228
  return (
229
229
  <div className='flex w-full flex-col overflow-hidden'>
230
230
  <div className='flex w-full flex-col'>
231
- <div className='relative z-1 border-b-2 p-8 pt-12 font-extrabold text-foreground'>
232
- <div className='absolute left-0 top-0 z-2 h-4 w-full bg-linear-to-r from-emerald-800 via-emerald-400 to-sky-600'></div>
231
+ <div className='text-foreground relative z-1 border-b-2 p-8 pt-12 font-extrabold'>
232
+ <div className='absolute top-0 left-0 z-2 h-4 w-full bg-linear-to-r from-emerald-800 via-emerald-400 to-sky-600'></div>
233
233
  <h1 className='pb-4 text-4xl'>{data.section?.title.section}</h1>
234
234
  <span>
235
235
  /{t('edit')} {data.section?.title.singular}
236
236
  </span>
237
237
  </div>
238
238
  {data.localization?.localeSwitcherEnabled ? (
239
- <div className="px-4 pt-4">
239
+ <div className='px-4 pt-4'>
240
240
  <LocaleSwitcher
241
241
  section={section}
242
242
  itemId={itemId}
@@ -245,29 +245,29 @@ export default function ItemEditPage({
245
245
  existingTranslations={data.localization.existingTranslations}
246
246
  locales={data.localization.locales}
247
247
  hasUnsavedChanges={formDirty}
248
+ developerOnly={data.localization.developerNoteEnabled}
248
249
  />
249
250
  {locale && (
250
251
  <Alert variant='info' className='mt-2 w-full'>
251
- <AlertDescription className='text-md font-bold'>
252
- <div className='flex items-center justify-between'>
253
- <span>
254
- {t('editingContentTranslation', { locale })}
255
- </span>
256
- {data.localization.existingTranslations.includes(locale) && (
257
- <button
258
- type='button'
259
- onClick={handleDeleteLocale}
260
- disabled={deleteLocaleMutation.isPending}
261
- className='inline-flex items-center gap-1.5 rounded-md bg-red-600 px-3 py-1.5 text-xs font-medium text-white transition-colors hover:bg-red-700 disabled:opacity-50'
262
- >
263
- <Trash2 className='h-3.5 w-3.5' />
264
- {deleteLocaleMutation.isPending ? t('loading') : t('deleteLocaleTranslation')}
265
- </button>
266
- )}
267
- </div>
268
-
269
- </AlertDescription>
270
- </Alert>
252
+ <AlertDescription className='text-md font-bold'>
253
+ <div className='flex items-center justify-between'>
254
+ <span>{t('editingContentTranslation', { locale })}</span>
255
+ {data.localization.existingTranslations.includes(locale) && (
256
+ <button
257
+ type='button'
258
+ onClick={handleDeleteLocale}
259
+ disabled={deleteLocaleMutation.isPending}
260
+ className='inline-flex items-center gap-1.5 rounded-md bg-red-600 px-3 py-1.5 text-xs font-medium text-white transition-colors hover:bg-red-700 disabled:opacity-50'
261
+ >
262
+ <Trash2 className='h-3.5 w-3.5' />
263
+ {deleteLocaleMutation.isPending
264
+ ? t('loading')
265
+ : t('deleteLocaleTranslation')}
266
+ </button>
267
+ )}
268
+ </div>
269
+ </AlertDescription>
270
+ </Alert>
271
271
  )}
272
272
  </div>
273
273
  ) : null}
@@ -6,6 +6,7 @@ import { Check } from 'lucide-react'
6
6
  import { useI18n } from 'nextjs-cms/translations/client'
7
7
  import { useSession } from 'nextjs-cms/auth/react'
8
8
  import ContainerBox from './ContainerBox'
9
+ import { Alert, AlertDescription } from './ui/alert'
9
10
 
10
11
  type LocaleConfig = {
11
12
  code: string
@@ -21,6 +22,7 @@ type Props = {
21
22
  existingTranslations: string[]
22
23
  locales: LocaleConfig[]
23
24
  hasUnsavedChanges?: boolean
25
+ developerOnly?: boolean
24
26
  /** Base path for locale links. Defaults to `/edit/${section}/${itemId}` for hasItems sections. */
25
27
  basePath?: string
26
28
  }
@@ -33,6 +35,7 @@ export default function LocaleSwitcher({
33
35
  existingTranslations,
34
36
  locales,
35
37
  hasUnsavedChanges,
38
+ developerOnly,
36
39
  basePath,
37
40
  }: Props) {
38
41
  const t = useI18n()
@@ -59,31 +62,37 @@ export default function LocaleSwitcher({
59
62
 
60
63
  return (
61
64
  <ContainerBox title={t('localesHeading')}>
62
- <div className="flex flex-wrap items-center gap-2">
63
- {locales.map((locale) => {
64
- const isActive = locale.code === currentLocale.code
65
- const hasTranslation = locale.code === defaultLocale.code || existingTranslations.includes(locale.code)
66
- const isDefault = locale.code === defaultLocale.code
65
+ <div className='flex flex-wrap items-center gap-2'>
66
+ {locales.map((locale) => {
67
+ const isActive = locale.code === currentLocale.code
68
+ const hasTranslation =
69
+ locale.code === defaultLocale.code || existingTranslations.includes(locale.code)
70
+ const isDefault = locale.code === defaultLocale.code
67
71
 
68
- return (
69
- <Link
70
- key={locale.code}
71
- href={getHref(locale.code)}
72
- onClick={(e) => handleClick(e, locale.code)}
73
- className={cn(
74
- 'inline-flex border border-primary/20 items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors',
75
- isActive
76
- ? 'bg-success text-success-foreground'
77
- : 'bg-muted text-muted-foreground hover:bg-accent hover:text-accent-foreground',
78
- )}
79
- >
80
- {locale.label}
81
- {isDefault && <span className="text-[10px] opacity-70">{t('baseLocaleBadge')}</span>}
82
- {hasTranslation && !isDefault && <Check className="h-3 w-3" />}
83
- </Link>
84
- )
85
- })}
72
+ return (
73
+ <Link
74
+ key={locale.code}
75
+ href={getHref(locale.code)}
76
+ onClick={(e) => handleClick(e, locale.code)}
77
+ className={cn(
78
+ 'border-primary/20 inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm font-medium transition-colors',
79
+ isActive
80
+ ? 'bg-success text-success-foreground'
81
+ : 'bg-muted text-muted-foreground hover:bg-accent hover:text-accent-foreground',
82
+ )}
83
+ >
84
+ {locale.label}
85
+ {isDefault && <span className='text-[10px] opacity-70'>{t('baseLocaleBadge')}</span>}
86
+ {hasTranslation && !isDefault && <Check className='h-3 w-3' />}
87
+ </Link>
88
+ )
89
+ })}
86
90
  </div>
91
+ {developerOnly && (
92
+ <Alert variant='info' className='mt-2 w-full'>
93
+ <AlertDescription className='text-md font-bold'>{t('localeSwitcherDevOnly')}</AlertDescription>
94
+ </Alert>
95
+ )}
87
96
  </ContainerBox>
88
97
  )
89
98
  }
@@ -155,6 +155,7 @@ export default function SectionPage({ section }: { section: string }) {
155
155
  locales={data.localization.locales}
156
156
  hasUnsavedChanges={formDirty}
157
157
  basePath={`/section/${section}`}
158
+ developerOnly={data.localization.developerNoteEnabled}
158
159
  />
159
160
  {locale && (
160
161
  <Alert variant='info' className='mt-2 w-full'>
@@ -8,4 +8,4 @@ export const revalidate = 0
8
8
 
9
9
  // @refresh reset
10
10
 
11
- export const configLastUpdated = 1776550157998
11
+ export const configLastUpdated = 1776644832680
@@ -1,157 +1,113 @@
1
- "use client"
1
+ 'use client'
2
2
 
3
- import * as React from "react"
4
- import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
3
+ import * as React from 'react'
4
+ import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
5
5
 
6
- import { cn } from "@/lib/utils"
7
- import { buttonVariants } from "@/components/ui/button"
6
+ import { cn } from '@/lib/utils'
7
+ import { buttonVariants } from '@/components/ui/button'
8
8
 
9
- function AlertDialog({
10
- ...props
11
- }: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
12
- return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
9
+ function AlertDialog({ ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
10
+ return <AlertDialogPrimitive.Root data-slot='alert-dialog' {...props} />
13
11
  }
14
12
 
15
- function AlertDialogTrigger({
16
- ...props
17
- }: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
18
- return (
19
- <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
20
- )
13
+ function AlertDialogTrigger({ ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
14
+ return <AlertDialogPrimitive.Trigger data-slot='alert-dialog-trigger' {...props} />
21
15
  }
22
16
 
23
- function AlertDialogPortal({
24
- ...props
25
- }: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
26
- return (
27
- <AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
28
- )
17
+ function AlertDialogPortal({ ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
18
+ return <AlertDialogPrimitive.Portal data-slot='alert-dialog-portal' {...props} />
29
19
  }
30
20
 
31
- function AlertDialogOverlay({
32
- className,
33
- ...props
34
- }: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
35
- return (
36
- <AlertDialogPrimitive.Overlay
37
- data-slot="alert-dialog-overlay"
38
- className={cn(
39
- "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
40
- className
41
- )}
42
- {...props}
43
- />
44
- )
21
+ function AlertDialogOverlay({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
22
+ return (
23
+ <AlertDialogPrimitive.Overlay
24
+ data-slot='alert-dialog-overlay'
25
+ className={cn(
26
+ 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50',
27
+ className,
28
+ )}
29
+ {...props}
30
+ />
31
+ )
45
32
  }
46
33
 
47
- function AlertDialogContent({
48
- className,
49
- ...props
50
- }: React.ComponentProps<typeof AlertDialogPrimitive.Content>) {
51
- return (
52
- <AlertDialogPortal>
53
- <AlertDialogOverlay />
54
- <AlertDialogPrimitive.Content
55
- data-slot="alert-dialog-content"
56
- className={cn(
57
- "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
58
- className
59
- )}
60
- {...props}
61
- />
62
- </AlertDialogPortal>
63
- )
34
+ function AlertDialogContent({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Content>) {
35
+ return (
36
+ <AlertDialogPortal>
37
+ <AlertDialogOverlay />
38
+ <AlertDialogPrimitive.Content
39
+ data-slot='alert-dialog-content'
40
+ className={cn(
41
+ 'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed start-[50%] top-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg rtl:-translate-x-[-50%]',
42
+ className,
43
+ )}
44
+ {...props}
45
+ />
46
+ </AlertDialogPortal>
47
+ )
64
48
  }
65
49
 
66
- function AlertDialogHeader({
67
- className,
68
- ...props
69
- }: React.ComponentProps<"div">) {
70
- return (
71
- <div
72
- data-slot="alert-dialog-header"
73
- className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
74
- {...props}
75
- />
76
- )
50
+ function AlertDialogHeader({ className, ...props }: React.ComponentProps<'div'>) {
51
+ return (
52
+ <div
53
+ data-slot='alert-dialog-header'
54
+ className={cn('flex flex-col gap-2 text-center sm:text-start', className)}
55
+ {...props}
56
+ />
57
+ )
77
58
  }
78
59
 
79
- function AlertDialogFooter({
80
- className,
81
- ...props
82
- }: React.ComponentProps<"div">) {
83
- return (
84
- <div
85
- data-slot="alert-dialog-footer"
86
- className={cn(
87
- "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
88
- className
89
- )}
90
- {...props}
91
- />
92
- )
60
+ function AlertDialogFooter({ className, ...props }: React.ComponentProps<'div'>) {
61
+ return (
62
+ <div
63
+ data-slot='alert-dialog-footer'
64
+ className={cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)}
65
+ {...props}
66
+ />
67
+ )
93
68
  }
94
69
 
95
- function AlertDialogTitle({
96
- className,
97
- ...props
98
- }: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
99
- return (
100
- <AlertDialogPrimitive.Title
101
- data-slot="alert-dialog-title"
102
- className={cn("text-lg font-semibold", className)}
103
- {...props}
104
- />
105
- )
70
+ function AlertDialogTitle({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
71
+ return (
72
+ <AlertDialogPrimitive.Title
73
+ data-slot='alert-dialog-title'
74
+ className={cn('text-lg font-semibold', className)}
75
+ {...props}
76
+ />
77
+ )
106
78
  }
107
79
 
108
80
  function AlertDialogDescription({
109
- className,
110
- ...props
81
+ className,
82
+ ...props
111
83
  }: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
112
- return (
113
- <AlertDialogPrimitive.Description
114
- data-slot="alert-dialog-description"
115
- className={cn("text-muted-foreground text-sm", className)}
116
- {...props}
117
- />
118
- )
84
+ return (
85
+ <AlertDialogPrimitive.Description
86
+ data-slot='alert-dialog-description'
87
+ className={cn('text-muted-foreground text-sm', className)}
88
+ {...props}
89
+ />
90
+ )
119
91
  }
120
92
 
121
- function AlertDialogAction({
122
- className,
123
- ...props
124
- }: React.ComponentProps<typeof AlertDialogPrimitive.Action>) {
125
- return (
126
- <AlertDialogPrimitive.Action
127
- className={cn(buttonVariants(), className)}
128
- {...props}
129
- />
130
- )
93
+ function AlertDialogAction({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Action>) {
94
+ return <AlertDialogPrimitive.Action className={cn(buttonVariants(), className)} {...props} />
131
95
  }
132
96
 
133
- function AlertDialogCancel({
134
- className,
135
- ...props
136
- }: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) {
137
- return (
138
- <AlertDialogPrimitive.Cancel
139
- className={cn(buttonVariants({ variant: "outline" }), className)}
140
- {...props}
141
- />
142
- )
97
+ function AlertDialogCancel({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) {
98
+ return <AlertDialogPrimitive.Cancel className={cn(buttonVariants({ variant: 'outline' }), className)} {...props} />
143
99
  }
144
100
 
145
101
  export {
146
- AlertDialog,
147
- AlertDialogPortal,
148
- AlertDialogOverlay,
149
- AlertDialogTrigger,
150
- AlertDialogContent,
151
- AlertDialogHeader,
152
- AlertDialogFooter,
153
- AlertDialogTitle,
154
- AlertDialogDescription,
155
- AlertDialogAction,
156
- AlertDialogCancel,
102
+ AlertDialog,
103
+ AlertDialogPortal,
104
+ AlertDialogOverlay,
105
+ AlertDialogTrigger,
106
+ AlertDialogContent,
107
+ AlertDialogHeader,
108
+ AlertDialogFooter,
109
+ AlertDialogTitle,
110
+ AlertDialogDescription,
111
+ AlertDialogAction,
112
+ AlertDialogCancel,
157
113
  }
@@ -4,7 +4,7 @@ import { cva, type VariantProps } from 'class-variance-authority'
4
4
  import { cn } from '@/lib/utils'
5
5
 
6
6
  const alertVariants = cva(
7
- 'relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7',
7
+ 'relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:start-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:ps-7',
8
8
  {
9
9
  variants: {
10
10
  variant: {
@@ -1,62 +1,64 @@
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 }
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { Slot } from "radix-ui"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
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:bg-destructive/60 dark:focus-visible:ring-destructive/40",
15
+ outline:
16
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 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
+ xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
26
+ sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
27
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
28
+ icon: "size-9",
29
+ "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
30
+ "icon-sm": "size-8",
31
+ "icon-lg": "size-10",
32
+ },
33
+ },
34
+ defaultVariants: {
35
+ variant: "default",
36
+ size: "default",
37
+ },
38
+ }
39
+ )
40
+
41
+ function Button({
42
+ className,
43
+ variant = "default",
44
+ size = "default",
45
+ asChild = false,
46
+ ...props
47
+ }: React.ComponentProps<"button"> &
48
+ VariantProps<typeof buttonVariants> & {
49
+ asChild?: boolean
50
+ }) {
51
+ const Comp = asChild ? Slot.Root : "button"
52
+
53
+ return (
54
+ <Comp
55
+ data-slot="button"
56
+ data-variant={variant}
57
+ data-size={size}
58
+ className={cn(buttonVariants({ variant, size, className }))}
59
+ {...props}
60
+ />
61
+ )
62
+ }
63
+
64
+ export { Button, buttonVariants }