'use client'; import * as React from 'react'; import * as Slot from 'radix-ui/slot'; import { cva, type VariantProps } from '@/lib/cva'; import { cn } from '@/lib/utils'; import { colorPalettes, controlHeights, controlVariants, disabledState, focusRing, iconReset, iconSizes, invalidState, } from '@/lib/cva-presets'; const buttonVariants = cva( cn( 'inline-flex items-center justify-center whitespace-nowrap rounded-xs font-medium shrink-0', 'transition-[color,box-shadow,background-color] duration-(--ui-duration-fast) ease-(--ui-ease-standard)', focusRing, disabledState, invalidState, /* Only the reset — Button's icon grows with its size, so the size rule sits on each rung below rather than here. */ iconReset ), { variants: { /** * How filled in the button is. Independent of {@link colorPalettes} — * `variant` decides the treatment, `colorPalette` decides the hue. */ variant: { ...controlVariants, /** * Reads as a link but behaves as a button. Kept from the kit's own set — * CBAR has no equivalent treatment, and a button that must look like * inline text still comes up. */ link: 'text-(--ctl-fg) underline-offset-4 hover:underline', /* * Shorthands for the combinations that come up constantly, so the * common case stays one word. They paint from the semantic roles in * theme.css rather than the `--ctl-*` palette roles, which is what * keeps them independent: a shorthand pins its own colour and simply * ignores `colorPalette` instead of racing it on CSS specificity. */ /** Equivalent to `variant="solid"` on the default palette. */ default: 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90', /** Irreversible or data-losing actions. Same as `colorPalette="red"`. */ destructive: 'bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/40', /** Neutral action sitting next to a primary one. */ secondary: 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80', }, /** * Which CBAR ramp the treatment draws from. Ignored by the three * shorthand variants, which pin their own colour. */ colorPalette: colorPalettes, /** * Height, padding, type size and glyph size. The icon steps 16 → 24px at * `md`, matching CBAR's own set; the four `icon-*` rungs are square. */ size: { /* Heights, padding, type and icon sizes come from CBAR's Button component set. The horizontal padding ladder is not monotonic — lg is roomier than xl — but that holds across all 35 of its variants, so it is reproduced rather than smoothed. Two rungs are the kit's own. `sm` has no CBAR counterpart (the set jumps xs → md) so it keeps the 16px icon its neighbouring xs uses, which is also all a 36px box has room for. `py-0.5` is CBAR's 2px and changes nothing on a fixed-height box — it is here so the Figma parity panel does not report a padding gap that is not one. */ xs: `${controlHeights.xs} gap-1 px-2.5 py-0.5 text-xs ${iconSizes.base}`, sm: `${controlHeights.sm} gap-1.5 px-3 py-0.5 text-sm ${iconSizes.base}`, md: `${controlHeights.md} gap-2 px-4 py-0.5 text-sm leading-7 ${iconSizes.lg}`, lg: `${controlHeights.lg} gap-2.5 px-5 py-0.5 text-base leading-7 ${iconSizes.lg}`, xl: `${controlHeights.xl} gap-2.5 px-4 py-0.5 text-lg leading-7 ${iconSizes.lg}`, /** * Square button for a lone icon — always pair with `aria-label`. These * state `iconSizes.base` to hold their 16px glyph, not to change it: * with the size rule off the base string, an unstated rung would fall * through to the `Icon` chassis' own 24px default. */ 'icon-xs': `size-8 rounded-md ${iconSizes.base}`, 'icon-sm': `size-9 rounded-md ${iconSizes.base}`, icon: `size-10 rounded-md ${iconSizes.base}`, 'icon-lg': `size-11 rounded-md ${iconSizes.base}`, }, }, defaultVariants: { variant: 'solid', colorPalette: 'primary', size: 'md', }, } ); export interface ButtonProps extends React.ComponentProps<'button'>, VariantProps { /** * Render the child element instead of a ` // solid, navy * * * * ``` * * An `svg` child is sized by the button rather than by the caller, and it grows * with the size the way CBAR draws it: 16px on `xs` and `sm`, 24px from `md` up. * Pass your own `size-*` on the icon to opt out of that entirely. * * `default`, `destructive` and `secondary` remain as one-word shorthands for * the combinations that come up constantly. They pin their own colour, so * `colorPalette` has no effect alongside them — use `variant="solid"` with an * explicit palette when you want the two axes to compose. * * ```tsx * // shorthand * // same thing, composable * ``` */ function Button({ className, variant, colorPalette, size, asChild = false, ...props }: ButtonProps) { const Comp = asChild ? Slot.Root : 'button'; return ( ); } export { Button, buttonVariants };