diff --git a/packages/frontend/src/components/shared/Button/Button.tsx b/packages/frontend/src/components/shared/Button/Button.tsx index d47c0162..af845e0c 100644 --- a/packages/frontend/src/components/shared/Button/Button.tsx +++ b/packages/frontend/src/components/shared/Button/Button.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from 'react'; +import React, { forwardRef, useCallback } from 'react'; import type { ComponentPropsWithoutRef, ReactNode } from 'react'; import { buttonTheme } from './Button.theme'; @@ -64,104 +64,114 @@ export type ButtonOrLinkProps = (ButtonLinkProps | ButtonProps) & /** * A custom button component that can be used in React applications. */ -const Button = ({ - children, - className, - leftIcon, - rightIcon, - fullWidth, - iconOnly, - shape, - variant, - ...props -}: ButtonOrLinkProps) => { - // Conditionally render between , or ; - } +const Button = forwardRef< + HTMLButtonElement | HTMLAnchorElement, + ButtonOrLinkProps +>( + ( + { + children, + className, + leftIcon, + rightIcon, + fullWidth, + iconOnly, + shape, + variant, + ...props }, - [], - ); + ref, + ) => { + // Conditionally render between , or ; + } + }, + [], ); - } - 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 }; - } + /** + * 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', + ); } - }, [styleProps.size])(); - return ( - - {cloneIcon(leftIcon, { ...iconSize })} - {children} - {cloneIcon(rightIcon, { ...iconSize })} - - ); -}; + 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';