import { cva, type VariantProps } from 'class-variance-authority' import type { ComponentProps } from 'react' import { cn } from '../cn.ts' import { SpinnerIcon } from '../icons.tsx' /** * A control: a small white card with a hairline border that gives a little when pressed. The main * action, such as a form's submit, Send, or a page's Add link, wears the green badge, as status * that is live or good does. Something that can't be undone is red, and a second action beside a * status reads as quiet text until hovered, and a way out that shouldn't compete with the page's * action, such as a viewer's sign out, reads as a link. */ export const button = cva( 'inline-flex cursor-pointer items-center justify-center gap-1.5 rounded-small border font-medium no-underline transition-[background-color,color,scale] duration-150 ease-out [-webkit-tap-highlight-color:transparent] disabled:cursor-default disabled:opacity-40 aria-busy:disabled:opacity-75 motion-safe:active:not-disabled:scale-96', { variants: { tone: { default: 'border-line bg-raised text-ink hover:not-disabled:bg-raised-hover', primary: 'border-accent-border bg-accent-soft font-semibold text-accent-strong hover:not-disabled:bg-accent-soft-hover', danger: 'border-danger-border bg-danger-soft text-danger hover:not-disabled:bg-danger-soft-hover', quiet: 'border-transparent bg-transparent text-muted hover:not-disabled:bg-hover hover:not-disabled:text-ink', link: 'border-transparent bg-transparent text-muted text-small underline decoration-faint underline-offset-(--underline-offset) hover:not-disabled:text-ink-strong motion-safe:active:not-disabled:scale-100', }, size: { default: 'px-3 py-1.25', small: 'px-2.75 py-1 text-small', }, }, compoundVariants: [ { tone: 'quiet', size: 'default', className: 'px-2 py-0.75 text-small' }, { tone: 'link', className: 'p-0' }, ], defaultVariants: { tone: 'default', size: 'default' }, }, ) type Variants = VariantProps interface Props extends ComponentProps<'button'> { /** Defaults to primary for a form's submit, and to a plain card otherwise. */ tone?: Variants['tone'] size?: Variants['size'] /** Whether it is doing what it was asked, which disables it and turns a spinner before its label. */ busy?: boolean } export function Button(props: Props) { const { tone, size, busy, disabled, className, type = 'button', children, ...rest } = props return ( ) } export const iconButton = cva( 'inline-flex shrink-0 cursor-pointer items-center justify-center rounded-small p-1.5 transition-[background-color,color] duration-(--dur-feedback) [-webkit-tap-highlight-color:transparent] disabled:cursor-default disabled:opacity-40', { variants: { tone: { default: 'text-muted hover:not-disabled:bg-hover hover:not-disabled:text-ink', danger: 'text-danger-muted hover:not-disabled:bg-danger-soft hover:not-disabled:text-danger', }, }, defaultVariants: { tone: 'default' }, }, ) interface IconButtonProps extends ComponentProps<'button'> { /** What the button does, read out and shown on hover in place of a visible label. */ 'aria-label': string /** Red for something that can't be undone, such as delete. */ tone?: VariantProps['tone'] } /** A small quiet button with only an icon, such as delete or move up. */ export function IconButton(props: IconButtonProps) { const { tone, className, title, type = 'button', ...rest } = props return (