// Copyright 2025 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import React, { memo, forwardRef } from 'react'; import type { ButtonHTMLAttributes, FC, ForwardedRef, ReactNode } from 'react'; import type { TailwindStyles } from './tw.js'; import { tw } from './tw.js'; import { AxoSymbol } from './AxoSymbol.js'; import { assert } from './_internal/assert.js'; const Namespace = 'AxoButton'; const baseAxoButtonStyles = tw( 'flex items-center-safe justify-center-safe gap-1 truncate rounded-full select-none', 'outline-0 outline-border-focused focused:outline-[2.5px]', 'forced-colors:border' ); const AxoButtonTypes = { default: tw(baseAxoButtonStyles), subtle: tw( baseAxoButtonStyles, 'bg-fill-secondary', 'pressed:bg-fill-secondary-pressed' ), floating: tw( baseAxoButtonStyles, 'bg-fill-floating', 'shadow-elevation-1', 'pressed:bg-fill-floating-pressed' ), borderless: tw( baseAxoButtonStyles, 'bg-transparent', 'hovered:bg-fill-secondary', 'pressed:bg-fill-secondary-pressed' ), } as const satisfies Record; const AxoButtonVariants = { // default secondary: tw( AxoButtonTypes.default, 'bg-fill-secondary text-label-primary', 'pressed:bg-fill-secondary-pressed', 'disabled:text-label-disabled' ), primary: tw( AxoButtonTypes.default, 'bg-color-fill-primary text-label-primary-on-color', 'pressed:bg-color-fill-primary-pressed', 'disabled:text-label-disabled-on-color' ), affirmative: tw( AxoButtonTypes.default, 'bg-color-fill-affirmative text-label-primary-on-color', 'pressed:bg-color-fill-affirmative-pressed', 'disabled:text-label-disabled-on-color' ), destructive: tw( AxoButtonTypes.default, 'bg-color-fill-destructive text-label-primary-on-color', 'pressed:bg-color-fill-destructive-pressed', 'disabled:text-label-disabled-on-color' ), // subtle 'subtle-primary': tw( AxoButtonTypes.subtle, 'text-color-label-primary', 'disabled:text-color-label-primary-disabled' ), 'subtle-affirmative': tw( AxoButtonTypes.subtle, 'text-color-label-affirmative', 'disabled:text-color-label-affirmative-disabled' ), 'subtle-destructive': tw( AxoButtonTypes.subtle, 'text-color-label-destructive', 'disabled:text-color-label-destructive-disabled' ), // floating 'floating-secondary': tw( AxoButtonTypes.floating, 'text-label-primary', 'disabled:text-label-disabled' ), 'floating-primary': tw( AxoButtonTypes.floating, 'text-color-label-primary', 'disabled:text-color-label-primary-disabled' ), 'floating-affirmative': tw( AxoButtonTypes.floating, 'text-color-label-affirmative', 'disabled:text-color-label-affirmative-disabled' ), 'floating-destructive': tw( AxoButtonTypes.floating, 'text-color-label-destructive', 'disabled:text-color-label-destructive-disabled' ), // borderless 'borderless-secondary': tw( AxoButtonTypes.borderless, 'text-label-primary', 'disabled:text-label-disabled' ), 'borderless-primary': tw( AxoButtonTypes.borderless, 'text-color-label-primary', 'disabled:text-color-label-primary-disabled' ), 'borderless-affirmative': tw( AxoButtonTypes.borderless, 'text-color-label-affirmative', 'disabled:text-color-label-affirmative-disabled' ), 'borderless-destructive': tw( AxoButtonTypes.borderless, 'text-color-label-destructive', 'disabled:text-color-label-destructive-disabled' ), }; const AxoButtonSizes = { large: tw('px-4 py-2 type-body-medium font-medium'), medium: tw('px-3 py-1.5 type-body-medium font-medium'), small: tw('px-2 py-1 type-body-small font-medium'), } as const satisfies Record; type BaseButtonAttrs = Omit< ButtonHTMLAttributes, 'className' | 'style' | 'children' >; type AxoButtonVariant = keyof typeof AxoButtonVariants; type AxoButtonSize = keyof typeof AxoButtonSizes; export function _getAllAxoButtonVariants(): ReadonlyArray { return Object.keys(AxoButtonVariants) as Array; } export function _getAllAxoButtonSizes(): ReadonlyArray { return Object.keys(AxoButtonSizes) as Array; } export namespace AxoButton { export type Variant = AxoButtonVariant; export type Size = AxoButtonSize; export type RootProps = BaseButtonAttrs & Readonly<{ variant: AxoButtonVariant; size: AxoButtonSize; symbol?: AxoSymbol.InlineGlyphName; arrow?: boolean; children: ReactNode; }>; export const Root: FC = memo( forwardRef((props, ref: ForwardedRef) => { const { variant, size, symbol, arrow, children, ...rest } = props; const variantStyles = assert( AxoButtonVariants[variant], `${Namespace}: Invalid variant ${variant}` ); const sizeStyles = assert( AxoButtonSizes[size], `${Namespace}: Invalid size ${size}` ); return ( ); }) ); Root.displayName = `${Namespace}.Root`; }