button.tsx58 lines · main
1import { cva, type VariantProps } from 'class-variance-authority';
2import { forwardRef, type ButtonHTMLAttributes } from 'react';
3
4import { cn } from '../lib/cn.js';
5
6const buttonVariants = cva(
7 [
8 'inline-flex items-center justify-center gap-2 whitespace-nowrap',
9 'rounded-[var(--radius-md)] font-medium',
10 'transition-[background-color,border-color,color,box-shadow] duration-[var(--duration-fast)] ease-[var(--ease-briven)]',
11 'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-primary)]',
12 'disabled:pointer-events-none disabled:opacity-50',
13 ].join(' '),
14 {
15 variants: {
16 variant: {
17 primary: [
18 'bg-[var(--color-primary)] text-[var(--color-text-inverse)]',
19 'hover:bg-[var(--color-primary-hover)] active:bg-[var(--color-primary-pressed)]',
20 'shadow-[var(--shadow-sm)]',
21 ].join(' '),
22 outline: [
23 'border border-[var(--color-border)] bg-transparent text-[var(--color-text)]',
24 'hover:border-[var(--color-border-strong)] hover:bg-[var(--color-surface-raised)]',
25 ].join(' '),
26 ghost: [
27 'bg-transparent text-[var(--color-text)]',
28 'hover:bg-[var(--color-primary-ghost)]',
29 ].join(' '),
30 danger: [
31 'bg-[var(--color-error)] text-[var(--color-text-inverse)]',
32 'hover:opacity-90',
33 ].join(' '),
34 },
35 size: {
36 sm: 'h-8 px-3 text-[var(--text-small)]',
37 md: 'h-10 px-4 text-[var(--text-body)]',
38 lg: 'h-12 px-6 text-[var(--text-body)]',
39 },
40 },
41 defaultVariants: {
42 variant: 'primary',
43 size: 'md',
44 },
45 },
46);
47
48export type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> &
49 VariantProps<typeof buttonVariants>;
50
51export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
52 { className, variant, size, ...rest },
53 ref,
54) {
55 return (
56 <button ref={ref} className={cn(buttonVariants({ variant, size }), className)} {...rest} />
57 );
58});