feat(web): adopt shadcn/ui primitives for auth surface

Add shadcn/ui plumbing:
- components.json (style: new-york, baseColor: slate, primary: green ~142 71%)
- src/lib/utils.ts with cn() helper (clsx + tailwind-merge)
- Tailwind config switched to CSS variables theming with primary/destructive/
  muted/card etc. tokens, dark-mode class, tailwindcss-animate plugin
- src/styles/globals.css declares :root and .dark variable palettes

Initial component set under src/components/ui:
- Button (cva variants: default/destructive/outline/secondary/ghost/link, asChild)
- Input
- Label (Radix Label)
- Card + CardHeader/Title/Description/Content/Footer

Refactor existing auth surface to the new primitives:
- sign-in / sign-up pages wrap form in a Card with Title/Description (+ footer
  for sign-up disclaimer)
- sign-in-form / sign-up-form use Input, Label, Button; tokens replace bespoke
  emerald/slate classes; divider and "Continua con Google" use the same Button
- Home page CTAs become Button asChild around Link; SignOutButton uses Button
- Hide-Google logic still works: enabledSocialProviders is now
  ReadonlyArray<SocialProvider> for ergonomic .includes() checks

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
lucianoandClaude Opus 4.7 committed 2026-04-29 14:09:59 +02:00
1 parent 646b98cccf
commit ac2227fdec
17 files changed
+374 -100

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/styles/globals.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
+10 -1
View File
@@ -11,14 +11,23 @@
"test:e2e": "playwright test"
},
"dependencies": {
"@hookform/resolvers": "^3.9.0",
"@ketopath/auth": "workspace:*",
"@ketopath/shared": "workspace:*",
"@ketopath/ui": "workspace:*",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-slot": "^1.1.0",
"better-auth": "^1.0.21",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"lucide-react": "^0.439.0",
"next": "^14.2.5",
"next-intl": "^3.17.2",
"react": "^18.3.1",
"react-dom": "^18.3.1"
"react-dom": "^18.3.1",
"react-hook-form": "^7.53.0",
"tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@ketopath/eslint-config": "workspace:*",
@@ -2,6 +2,8 @@ import { enabledSocialProviders } from '@ketopath/auth';
import { useTranslations } from 'next-intl';
import { setRequestLocale } from 'next-intl/server';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { SignInForm } from './sign-in-form';
export default function SignInPage({ params: { locale } }: { params: { locale: string } }) {
@@ -9,12 +11,16 @@ export default function SignInPage({ params: { locale } }: { params: { locale: s
const t = useTranslations('Auth.SignIn');
return (
<main className="mx-auto flex min-h-screen max-w-md flex-col justify-center gap-8 px-6 py-16">
<header className="space-y-2">
<h1 className="text-3xl font-bold tracking-tight">{t('title')}</h1>
<p className="text-sm text-slate-600">{t('subtitle')}</p>
</header>
<SignInForm googleEnabled={enabledSocialProviders.includes('google')} />
<main className="mx-auto flex min-h-screen max-w-md flex-col justify-center px-6 py-16">
<Card>
<CardHeader>
<CardTitle>{t('title')}</CardTitle>
<CardDescription>{t('subtitle')}</CardDescription>
</CardHeader>
<CardContent>
<SignInForm googleEnabled={enabledSocialProviders.includes('google')} />
</CardContent>
</Card>
</main>
);
}
@@ -5,6 +5,9 @@ import { useRouter } from 'next/navigation';
import { useTranslations } from 'next-intl';
import { useState, type FormEvent } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { authClient } from '@/lib/auth-client';
export function SignInForm({ googleEnabled }: { googleEnabled: boolean }) {
@@ -46,67 +49,59 @@ export function SignInForm({ googleEnabled }: { googleEnabled: boolean }) {
return (
<div className="space-y-6">
<form onSubmit={handleSubmit} className="space-y-4" noValidate>
<label className="block space-y-1">
<span className="text-sm font-medium text-slate-700">{t('emailLabel')}</span>
<input
<div className="space-y-1.5">
<Label htmlFor="email">{t('emailLabel')}</Label>
<Input
id="email"
type="email"
required
autoComplete="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full rounded-md border border-slate-300 px-3 py-2 text-sm shadow-sm focus:border-emerald-500 focus:outline-none focus:ring-1 focus:ring-emerald-500"
/>
</label>
<label className="block space-y-1">
<span className="text-sm font-medium text-slate-700">{t('passwordLabel')}</span>
<input
</div>
<div className="space-y-1.5">
<Label htmlFor="password">{t('passwordLabel')}</Label>
<Input
id="password"
type="password"
required
autoComplete="current-password"
minLength={8}
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full rounded-md border border-slate-300 px-3 py-2 text-sm shadow-sm focus:border-emerald-500 focus:outline-none focus:ring-1 focus:ring-emerald-500"
/>
</label>
</div>
{error ? (
<p role="alert" className="text-sm text-red-600">
<p role="alert" className="text-destructive text-sm">
{error}
</p>
) : null}
<button
type="submit"
disabled={pending}
className="w-full rounded-md bg-emerald-600 px-5 py-2.5 text-sm font-medium text-white shadow-sm hover:bg-emerald-700 disabled:opacity-60"
>
<Button type="submit" disabled={pending} className="w-full">
{pending ? t('submitting') : t('submit')}
</button>
</Button>
</form>
{googleEnabled ? (
<>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-slate-200" />
<div className="border-border w-full border-t" />
</div>
<div className="relative flex justify-center">
<span className="bg-white px-2 text-xs uppercase tracking-wider text-slate-500">
<span className="bg-background text-muted-foreground px-2 text-xs uppercase tracking-wider">
{t('or')}
</span>
</div>
</div>
<button
type="button"
onClick={handleGoogle}
className="w-full rounded-md border border-slate-300 bg-white px-5 py-2.5 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50"
>
<Button type="button" variant="outline" onClick={handleGoogle} className="w-full">
{t('google')}
</button>
</Button>
</>
) : null}
<p className="text-center text-sm text-slate-600">
<p className="text-muted-foreground text-center text-sm">
{t.rich('noAccount', {
link: (chunks) => (
<Link href="/sign-up" className="font-medium text-emerald-700 hover:underline">
<Link href="/sign-up" className="text-primary font-medium hover:underline">
{chunks}
</Link>
),
@@ -2,6 +2,15 @@ import { enabledSocialProviders } from '@ketopath/auth';
import { useTranslations } from 'next-intl';
import { setRequestLocale } from 'next-intl/server';
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { SignUpForm } from './sign-up-form';
export default function SignUpPage({ params: { locale } }: { params: { locale: string } }) {
@@ -9,13 +18,19 @@ export default function SignUpPage({ params: { locale } }: { params: { locale: s
const t = useTranslations('Auth.SignUp');
return (
<main className="mx-auto flex min-h-screen max-w-md flex-col justify-center gap-8 px-6 py-16">
<header className="space-y-2">
<h1 className="text-3xl font-bold tracking-tight">{t('title')}</h1>
<p className="text-sm text-slate-600">{t('subtitle')}</p>
</header>
<SignUpForm googleEnabled={enabledSocialProviders.includes('google')} />
<p className="text-xs text-slate-500">{t('disclaimer')}</p>
<main className="mx-auto flex min-h-screen max-w-md flex-col justify-center px-6 py-16">
<Card>
<CardHeader>
<CardTitle>{t('title')}</CardTitle>
<CardDescription>{t('subtitle')}</CardDescription>
</CardHeader>
<CardContent>
<SignUpForm googleEnabled={enabledSocialProviders.includes('google')} />
</CardContent>
<CardFooter>
<p className="text-muted-foreground text-xs">{t('disclaimer')}</p>
</CardFooter>
</Card>
</main>
);
}
@@ -5,6 +5,9 @@ import { useRouter } from 'next/navigation';
import { useTranslations } from 'next-intl';
import { useState, type FormEvent } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { authClient } from '@/lib/auth-client';
export function SignUpForm({ googleEnabled }: { googleEnabled: boolean }) {
@@ -47,79 +50,71 @@ export function SignUpForm({ googleEnabled }: { googleEnabled: boolean }) {
return (
<div className="space-y-6">
<form onSubmit={handleSubmit} className="space-y-4" noValidate>
<label className="block space-y-1">
<span className="text-sm font-medium text-slate-700">{t('nameLabel')}</span>
<input
<div className="space-y-1.5">
<Label htmlFor="name">{t('nameLabel')}</Label>
<Input
id="name"
type="text"
required
autoComplete="name"
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full rounded-md border border-slate-300 px-3 py-2 text-sm shadow-sm focus:border-emerald-500 focus:outline-none focus:ring-1 focus:ring-emerald-500"
/>
</label>
<label className="block space-y-1">
<span className="text-sm font-medium text-slate-700">{t('emailLabel')}</span>
<input
</div>
<div className="space-y-1.5">
<Label htmlFor="email">{t('emailLabel')}</Label>
<Input
id="email"
type="email"
required
autoComplete="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full rounded-md border border-slate-300 px-3 py-2 text-sm shadow-sm focus:border-emerald-500 focus:outline-none focus:ring-1 focus:ring-emerald-500"
/>
</label>
<label className="block space-y-1">
<span className="text-sm font-medium text-slate-700">{t('passwordLabel')}</span>
<input
</div>
<div className="space-y-1.5">
<Label htmlFor="password">{t('passwordLabel')}</Label>
<Input
id="password"
type="password"
required
autoComplete="new-password"
minLength={8}
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full rounded-md border border-slate-300 px-3 py-2 text-sm shadow-sm focus:border-emerald-500 focus:outline-none focus:ring-1 focus:ring-emerald-500"
/>
<span className="block text-xs text-slate-500">{t('passwordHint')}</span>
</label>
<p className="text-muted-foreground text-xs">{t('passwordHint')}</p>
</div>
{error ? (
<p role="alert" className="text-sm text-red-600">
<p role="alert" className="text-destructive text-sm">
{error}
</p>
) : null}
<button
type="submit"
disabled={pending}
className="w-full rounded-md bg-emerald-600 px-5 py-2.5 text-sm font-medium text-white shadow-sm hover:bg-emerald-700 disabled:opacity-60"
>
<Button type="submit" disabled={pending} className="w-full">
{pending ? t('submitting') : t('submit')}
</button>
</Button>
</form>
{googleEnabled ? (
<>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-slate-200" />
<div className="border-border w-full border-t" />
</div>
<div className="relative flex justify-center">
<span className="bg-white px-2 text-xs uppercase tracking-wider text-slate-500">
<span className="bg-background text-muted-foreground px-2 text-xs uppercase tracking-wider">
{t('or')}
</span>
</div>
</div>
<button
type="button"
onClick={handleGoogle}
className="w-full rounded-md border border-slate-300 bg-white px-5 py-2.5 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50"
>
<Button type="button" variant="outline" onClick={handleGoogle} className="w-full">
{t('google')}
</button>
</Button>
</>
) : null}
<p className="text-center text-sm text-slate-600">
<p className="text-muted-foreground text-center text-sm">
{t.rich('haveAccount', {
link: (chunks) => (
<Link href="/sign-in" className="font-medium text-emerald-700 hover:underline">
<Link href="/sign-in" className="text-primary font-medium hover:underline">
{chunks}
</Link>
),
+10 -15
View File
@@ -2,6 +2,7 @@ import Link from 'next/link';
import { useTranslations } from 'next-intl';
import { setRequestLocale } from 'next-intl/server';
import { Button } from '@/components/ui/button';
import { getServerSession } from '@/lib/auth';
import { SignOutButton } from './sign-out-button';
@@ -19,29 +20,23 @@ function HomeContent({ userName }: { userName: string | null }) {
return (
<main className="mx-auto flex min-h-screen max-w-3xl flex-col items-start justify-center gap-6 px-6 py-16">
<h1 className="text-4xl font-bold tracking-tight sm:text-5xl">{t('title')}</h1>
<p className="text-lg text-slate-600">{t('tagline')}</p>
<p className="text-muted-foreground text-lg">{t('tagline')}</p>
{userName ? (
<div className="flex flex-wrap items-center gap-3">
<span className="text-sm text-slate-700">{t('signedInAs', { name: userName })}</span>
<span className="text-foreground text-sm">{t('signedInAs', { name: userName })}</span>
<SignOutButton label={t('signOut')} />
</div>
) : (
<div className="flex flex-wrap gap-3">
<Link
href="/sign-up"
className="rounded-md bg-emerald-600 px-5 py-2.5 text-sm font-medium text-white shadow-sm hover:bg-emerald-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500"
>
{t('ctaPrimary')}
</Link>
<Link
href="/sign-in"
className="rounded-md border border-slate-300 bg-white px-5 py-2.5 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-slate-400"
>
{t('ctaSecondary')}
</Link>
<Button asChild>
<Link href="/sign-up">{t('ctaPrimary')}</Link>
</Button>
<Button asChild variant="outline">
<Link href="/sign-in">{t('ctaSecondary')}</Link>
</Button>
</div>
)}
<p className="mt-8 text-xs text-slate-500">{t('disclaimer')}</p>
<p className="text-muted-foreground mt-8 text-xs">{t('disclaimer')}</p>
</main>
);
}
@@ -3,6 +3,7 @@
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { authClient } from '@/lib/auth-client';
export function SignOutButton({ label }: { label: string }) {
@@ -17,13 +18,8 @@ export function SignOutButton({ label }: { label: string }) {
}
return (
<button
type="button"
onClick={handleClick}
disabled={pending}
className="rounded-md border border-slate-300 bg-white px-3 py-1.5 text-xs font-medium text-slate-700 shadow-sm hover:bg-slate-50 disabled:opacity-60"
>
<Button type="button" variant="outline" size="sm" onClick={handleClick} disabled={pending}>
{label}
</button>
</Button>
);
}
+45
View File
@@ -0,0 +1,45 @@
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';
import { forwardRef, type ButtonHTMLAttributes } from 'react';
import { cn } from '@/lib/utils';
const buttonVariants = cva(
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-10 px-4 py-2',
sm: 'h-9 rounded-md px-3',
lg: 'h-11 rounded-md px-6',
icon: 'h-10 w-10',
},
},
defaultVariants: { variant: 'default', size: 'default' },
},
);
export interface ButtonProps
extends ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : 'button';
return (
<Comp ref={ref} className={cn(buttonVariants({ variant, size }), className)} {...props} />
);
},
);
Button.displayName = 'Button';
export { buttonVariants };
+54
View File
@@ -0,0 +1,54 @@
import { forwardRef, type HTMLAttributes } from 'react';
import { cn } from '@/lib/utils';
export const Card = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('bg-card text-card-foreground rounded-lg border shadow-sm', className)}
{...props}
/>
),
);
Card.displayName = 'Card';
export const CardHeader = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} />
),
);
CardHeader.displayName = 'CardHeader';
export const CardTitle = forwardRef<HTMLHeadingElement, HTMLAttributes<HTMLHeadingElement>>(
({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn('text-2xl font-semibold leading-none tracking-tight', className)}
{...props}
/>
),
);
CardTitle.displayName = 'CardTitle';
export const CardDescription = forwardRef<
HTMLParagraphElement,
HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p ref={ref} className={cn('text-muted-foreground text-sm', className)} {...props} />
));
CardDescription.displayName = 'CardDescription';
export const CardContent = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
),
);
CardContent.displayName = 'CardContent';
export const CardFooter = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn('flex items-center p-6 pt-0', className)} {...props} />
),
);
CardFooter.displayName = 'CardFooter';
+18
View File
@@ -0,0 +1,18 @@
import { forwardRef, type InputHTMLAttributes } from 'react';
import { cn } from '@/lib/utils';
export const Input = forwardRef<HTMLInputElement, InputHTMLAttributes<HTMLInputElement>>(
({ className, type, ...props }, ref) => (
<input
type={type}
ref={ref}
className={cn(
'border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
{...props}
/>
),
);
Input.displayName = 'Input';
+19
View File
@@ -0,0 +1,19 @@
'use client';
import * as LabelPrimitive from '@radix-ui/react-label';
import { cva, type VariantProps } from 'class-variance-authority';
import { forwardRef, type ComponentPropsWithoutRef, type ElementRef } from 'react';
import { cn } from '@/lib/utils';
const labelVariants = cva(
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
);
export const Label = forwardRef<
ElementRef<typeof LabelPrimitive.Root>,
ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />
));
Label.displayName = LabelPrimitive.Root.displayName;
+6
View File
@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]): string {
return twMerge(clsx(inputs));
}
+52 -1
View File
@@ -3,7 +3,58 @@
@tailwind utilities;
@layer base {
html {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 142 71% 32%;
--primary-foreground: 0 0% 100%;
--secondary: 210 40% 96%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 142 71% 32%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 142 71% 45%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 142 71% 45%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
-webkit-text-size-adjust: 100%;
}
}
+48 -1
View File
@@ -1,15 +1,62 @@
import type { Config } from 'tailwindcss';
import animate from 'tailwindcss-animate';
const config: Config = {
darkMode: ['class'],
content: ['./src/**/*.{ts,tsx}', '../../packages/ui/src/**/*.{ts,tsx}'],
theme: {
container: {
center: true,
padding: '2rem',
screens: { '2xl': '1400px' },
},
extend: {
fontFamily: {
sans: ['var(--font-sans)', 'system-ui', 'sans-serif'],
},
colors: {
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))',
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))',
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))',
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))',
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))',
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))',
},
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))',
},
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)',
},
},
},
plugins: [],
plugins: [animate],
};
export default config;