- Add `aria-busy` attribute to Button when loading. - Refactor Button rendering to keep children in DOM (visually hidden) instead of unmounting. - Fix layout shift regression by replicating flex properties in content wrapper. - Move inline styles to CSS modules. - Add tests for loading state accessibility. Co-authored-by: ragusa-it <196988693+ragusa-it@users.noreply.github.com>
43 lines
1.2 KiB
TypeScript
43 lines
1.2 KiB
TypeScript
import { type ReactNode, type ButtonHTMLAttributes } from 'react';
|
|
import { motion } from 'motion/react';
|
|
import styles from './Button.module.css';
|
|
|
|
interface ButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'onAnimationStart' | 'onDragStart' | 'onDragEnd' | 'onDrag'> {
|
|
variant?: 'primary' | 'secondary' | 'outline';
|
|
size?: 'sm' | 'md' | 'lg';
|
|
children: ReactNode;
|
|
isLoading?: boolean;
|
|
}
|
|
|
|
export function Button({
|
|
variant = 'primary',
|
|
size = 'md',
|
|
children,
|
|
isLoading,
|
|
className,
|
|
disabled,
|
|
type = 'button',
|
|
...props
|
|
}: ButtonProps) {
|
|
return (
|
|
<motion.button
|
|
type={type}
|
|
className={`${styles.button} ${styles[variant]} ${styles[size]} ${className || ''}`}
|
|
disabled={disabled || isLoading}
|
|
aria-busy={isLoading}
|
|
whileHover={{ scale: 1.02 }}
|
|
whileTap={{ scale: 0.98 }}
|
|
{...props}
|
|
>
|
|
<span className={`${styles.content} ${isLoading ? styles.contentHidden : ''}`}>
|
|
{children}
|
|
</span>
|
|
{isLoading && (
|
|
<span className={styles.loaderWrapper} aria-hidden="true">
|
|
<span className={styles.loader} />
|
|
</span>
|
|
)}
|
|
</motion.button>
|
|
);
|
|
}
|