| 1 | import { type VariantProps, cva } from "class-variance-authority"; |
| 2 | import { Slot } from "radix-ui"; |
| 3 | import type { ComponentProps } from "react"; |
| 4 | |
| 5 | import { cn } from "../../lib/cn"; |
| 6 | |
| 7 | // shadcn/ui's button, styled with g1t's tokens: the one button. `asChild` |
| 8 | // puts its look on a <Link> or a menu's trigger; a form's submit button |
| 9 | // that says it is working is `SubmitButton` in ./index, and a link that |
| 10 | // looks like one is `ButtonLink` there, both drawn with `buttonVariants`. |
| 11 | // |
| 12 | // Variants: `default` is the plain dark-on-light (light-on-dark) button for |
| 13 | // the main action; `accent` the lavender one, for the one action a page |
| 14 | // is for; `outline` a bordered quiet one; `secondary` a raised one; |
| 15 | // `ghost` bare until hovered, for icons and toolbars; `soft` the accent as |
| 16 | // a wash, the one tinted control in a row, such as Ask g1t; `destructive` |
| 17 | // for what cannot be undone; `link` words alone. |
| 18 | // |
| 19 | // Sizes: `default` 36px, `sm` 32px, `xs` 28px, `lg` 40px; the icon sizes |
| 20 | // are squares of the same. `bar` is the top bar's: 32px on a computer, 44px |
| 21 | // to a finger on a phone, and `icon-bar` its square. |
| 22 | |
| 23 | export const buttonVariants = cva( |
| 24 | "inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap outline-none transition-[color,background-color,border-color,box-shadow] focus-visible:ring-2 focus-visible:ring-accent disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", |
| 25 | { |
| 26 | variants: { |
| 27 | variant: { |
| 28 | default: "bg-fg text-bg hover:bg-fg-hover", |
| 29 | accent: "bg-accent text-bg hover:bg-accent-hover", |
| 30 | outline: "border border-line text-fg/80 hover:border-line-strong hover:bg-surface hover:text-fg", |
| 31 | secondary: "bg-raised text-fg hover:bg-line", |
| 32 | ghost: "text-muted hover:bg-raised hover:text-fg data-[state=open]:bg-raised data-[state=open]:text-fg aria-pressed:bg-raised aria-pressed:text-fg", |
| 33 | soft: "bg-accent/10 text-fg ring-1 ring-accent/20 ring-inset hover:bg-accent/15 hover:ring-accent/35", |
| 34 | destructive: "border border-danger/40 text-danger hover:border-danger hover:bg-danger/10", |
| 35 | link: "font-normal text-accent underline-offset-4 hover:underline", |
| 36 | }, |
| 37 | size: { |
| 38 | default: "h-9 px-3.5", |
| 39 | sm: "h-8 gap-1.5 px-2.5 text-[0.8125rem]", |
| 40 | xs: "h-7 gap-1 px-2 text-xs", |
| 41 | lg: "h-10 px-5", |
| 42 | bar: "h-8 gap-1.5 px-2 text-[0.8125rem] max-md:h-11 max-md:min-w-11", |
| 43 | icon: "size-9", |
| 44 | "icon-sm": "size-8", |
| 45 | "icon-xs": "size-7", |
| 46 | "icon-lg": "size-10", |
| 47 | "icon-bar": "size-8 max-md:size-11", |
| 48 | // Words in a sentence, for the `link` variant: no box of its own. |
| 49 | inline: "h-auto gap-1.5 p-0 text-[length:inherit]", |
| 50 | }, |
| 51 | }, |
| 52 | defaultVariants: { variant: "default", size: "default" }, |
| 53 | }, |
| 54 | ); |
| 55 | |
| 56 | export type ButtonVariant = NonNullable<VariantProps<typeof buttonVariants>["variant"]>; |
| 57 | export type ButtonSize = NonNullable<VariantProps<typeof buttonVariants>["size"]>; |
| 58 | |
| 59 | export type ButtonProps = ComponentProps<"button"> & VariantProps<typeof buttonVariants> & { asChild?: boolean }; |
| 60 | |
| 61 | export function Button({ className, variant, size, asChild = false, ...props }: ButtonProps) { |
| 62 | const Comp = asChild ? Slot.Root : "button"; |
| 63 | return <Comp data-slot="button" className={cn(buttonVariants({ variant, size }), className)} {...props} />; |
| 64 | } |