'use client'; import { Slot } from '@radix-ui/react-slot'; import { cva, type VariantProps } from 'class-variance-authority'; import React from 'react'; import Link from '../../../embed-shims/next-link'; import { cn } from '../../../utils/cn'; import { buttonSurfaceClasses, outlineBorderClasses, splitDividerColorClasses, splitGlyphSizeClasses, } from './button-styles'; // Default layout: centered single content area, padding/gap on the button itself. const buttonVariants = cva( [ 'relative inline-flex items-center justify-center gap-[var(--spacing-system-xsf)]', 'rounded-md whitespace-nowrap', 'transition-colors duration-200', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ods-focus', 'disabled:pointer-events-none', '[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:h-5 [&_svg]:w-5', ], { variants: { variant: { accent: buttonSurfaceClasses.accent, outline: cn(buttonSurfaceClasses.outline, outlineBorderClasses), transparent: buttonSurfaceClasses.transparent, destructive: buttonSurfaceClasses.destructive, warning: buttonSurfaceClasses.warning, glyph: buttonSurfaceClasses.glyph, overlay: buttonSurfaceClasses.overlay, }, size: { default: 'py-[var(--spacing-system-sf)] px-[var(--spacing-system-m)] text-h3 md:h-12 h-11', small: 'p-[var(--spacing-system-xs)] text-h5 h-6 md:h-8', 'small-legacy': 'py-[var(--spacing-system-xs)] px-[var(--spacing-system-m)] h-10 text-[14px] font-bold', // Temporary alias for "small" — deprecated; grep size="small-legacy" (lib + hub) and migrate the remaining consumers before removal // 24px pill for slim strips (announcement/promo bars, inline banner // actions — Primer banner / Polaris banner / Vercel-bar convention). // The label matches the strip's MESSAGE type ramp exactly (DM Sans // regular, 13px -> md:14px, snug leading — the announcement bar's // subtitle styling): in-strip CTAs must never read louder than the // message beside them, so this deliberately uses neither the // mono-uppercase control style of "small" nor a heavier weight. // Pinned h-6 across breakpoints (24px = the WCAG 2.5.8 target-size // floor) with horizontal-dominant padding and a 14px glyph. compact: 'py-0 px-[var(--spacing-system-sf)] text-h6 font-normal h-6 [&_svg]:h-3.5 [&_svg]:w-3.5', icon: 'p-[var(--spacing-system-sf)] h-11 w-11 md:h-12 md:w-12 [&_svg]:h-4 [&_svg]:w-4 md:[&_svg]:h-6 md:[&_svg]:w-6', // Quiet 32px icon target with a 16px glyph, fixed across breakpoints // (Carbon ghost sm / Primer medium / shadcn icon-sm all pin 32px; // ≥ the 24px WCAG 2.5.8 target floor). For icon actions that read as // metadata rather than CTAs — author-page social rows, share rows. // Pair with variant="transparent" for the ghost treatment. 'icon-sm': 'p-[var(--spacing-system-xxs)] h-8 w-8 [&_svg]:h-4 [&_svg]:w-4', // 20px inline glyph for affordances that sit INSIDE a line of text — // an info hint beside a badge, a copy glyph after an id. `icon-sm`'s // 32px target is correct for a metadata row but visually swamps a // 10px badge or a 14px label, so hosts were reaching for className // overrides to shrink it. Below the WCAG 2.5.8 target floor on purpose: // this is only for glyphs whose information is ALSO available another // way (tooltip content mirrored into an aria-describedby node), never // for a sole means of performing an action. 'icon-inline': 'p-0 h-5 w-5 min-h-0 min-w-0 shrink-0 [&_svg]:h-3.5 [&_svg]:w-3.5', // Bare 56px media glyph (play / unmute over video). A size variant so // hosts stop re-implementing a ); } const classes = cn(buttonVariants({ variant, size, font, fullWidth, noPaddingX }), className); // asChild: consumer fully controls the rendered element; we just stamp our classes. if (asChild) { return ( {children} ); } // Real content stays in layout (preserving width) but goes invisible while loading. // The spinner is absolutely positioned so it never shifts the button's size. const content = ( <> {leftIcon && {leftIcon}} {children} {rightIcon && {rightIcon}} {loading && ( )} ); // Same `linkProps`-wins-over-href resolution as the splitIcon branch. const anchor = linkProps ?? (href ? { href, target: openInNewTab ? ('_blank' as const) : undefined, rel: openInNewTab ? ('noopener noreferrer' as const) : undefined, onClick: onClick as unknown as React.MouseEventHandler | undefined, } : null); if (anchor) { return ( {content} ); } return ( ); }); export { Button, buttonVariants, type ButtonProps };