/** * Copyright 2025 LY Corporation * * LY Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ 'use client'; import * as React from 'react'; import { Slot, Slottable } from '@radix-ui/react-slot'; import type { VariantProps } from 'class-variance-authority'; import { cva } from 'class-variance-authority'; import type { ButtonVariant, Radius, Size } from '../lib/types'; import { cn, composeRefs } from '../lib/utils'; import { Spinner } from './spinner'; import useTheme from './use-theme'; const defaultVariants: { variant: ButtonVariant; size?: Size; radius?: Radius; loading?: boolean; } = { variant: 'primary', size: undefined, radius: undefined, loading: false, }; const buttonVariants = cva('button', { variants: { variant: { primary: 'button-primary', secondary: 'button-secondary', destructive: 'button-destructive', ghost: 'button-ghost', outline: 'button-outline', }, size: { small: 'button-small', medium: 'button-medium', large: 'button-large', }, radius: { small: 'button-radius-small', medium: 'button-radius-medium', large: 'button-radius-large', }, loading: { true: '!text-transparent [&>*:not(.button-loading)]:!invisible', false: '', }, }, defaultVariants, }); const buttonLoadingVariants = cva('button-loading', { variants: { variant: { primary: 'button-loading-primary', secondary: 'button-loading-secondary', destructive: 'button-loading-destructive', ghost: 'button-loading-ghost', outline: 'button-loading-outline', }, }, defaultVariants, }); interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps { variant?: ButtonVariant; size?: Size; radius?: Radius; loading?: boolean; asChild?: boolean; } const Button = React.forwardRef( ( { className, type = 'button', variant = 'primary', size, radius, disabled = false, loading = false, asChild = false, children, ...props }, ref, ) => { const Comp = asChild ? Slot : 'button'; const { themeSize, themeRadius } = useTheme(); const buttonRef = React.useRef(null); React.useEffect(() => { if (!buttonRef.current) return; const childNodes = buttonRef.current.childNodes; const isSvgOnly = Array.from(childNodes).every( (node) => ((node as HTMLElement).nodeType === Node.ELEMENT_NODE && (node as HTMLElement).tagName.toLowerCase() === 'svg') || ((node as HTMLElement).nodeType === Node.TEXT_NODE && !((node as HTMLElement).textContent || '').trim()), ); if (isSvgOnly) { buttonRef.current.classList.add('svg-only'); } }, []); return ( {children} {loading && ( )} ); }, ); Button.displayName = 'Button'; export { Button, type ButtonProps, buttonVariants };