import React, { forwardRef, useCallback } from 'react'; import type { ComponentPropsWithoutRef, ReactNode } from 'react'; import { buttonTheme } from './Button.theme'; import type { ButtonTheme } from './Button.theme'; import { Link } from 'react-router-dom'; import { cloneIcon } from 'utils/cloneIcon'; /** * Represents the properties of a base button component. */ export interface ButtonBaseProps { /** * The optional left icon element for a component. * @type {ReactNode} */ leftIcon?: ReactNode; /** * The optional right icon element to display. * @type {ReactNode} */ rightIcon?: ReactNode; } /** * Interface for the props of a button link component. */ export interface ButtonLinkProps extends Omit, 'color'> { /** * Specifies the optional property `as` with a value of `'a'`. * @type {'a'} */ as?: 'a'; /** * Indicates whether the item is external or not. * @type {boolean} */ external?: boolean; /** * The URL of a web page or resource. * @type {string} */ href: string; } export interface ButtonProps extends Omit, 'color'> { /** * Specifies the optional property `as` with a value of `'button'`. * @type {'button'} */ as?: 'button'; } /** * Interface representing the props for a button component. * Extends the ComponentPropsWithoutRef<'button'> and ButtonTheme interfaces. */ export type ButtonOrLinkProps = (ButtonLinkProps | ButtonProps) & ButtonBaseProps & ButtonTheme; /** * A custom button component that can be used in React applications. */ const Button = forwardRef< HTMLButtonElement | HTMLAnchorElement, ButtonOrLinkProps >( ( { children, className, leftIcon, rightIcon, fullWidth, iconOnly, shape, variant, ...props }, ref, ) => { // Conditionally render between , or ); } }, [], ); /** * Extracts specific style properties from the given props object and returns them as a new object. */ const styleProps = (({ variant = 'primary', size = 'md', fullWidth = false, iconOnly = false, shape = 'rounded', as, }) => ({ variant, size, fullWidth, iconOnly, shape, as, }))({ ...props, fullWidth, iconOnly, shape, variant }); /** * Validates that a button component has either children or an aria-label prop. */ if (typeof children === 'undefined' && !props['aria-label']) { throw new Error( 'Button components must have either children or an aria-label prop', ); } const iconSize = useCallback(() => { switch (styleProps.size) { case 'lg': return { width: 20, height: 20 }; case 'sm': case 'xs': return { width: 16, height: 16 }; case 'md': default: { return { width: 18, height: 18 }; } } }, [styleProps.size])(); return ( {cloneIcon(leftIcon, { ...iconSize })} {children} {cloneIcon(rightIcon, { ...iconSize })} ); }, ); Button.displayName = 'Button'; export { Button };