All files / src/shared/components/button Button.tsx

100% Statements 63/63
84.61% Branches 22/26
100% Functions 2/2
100% Lines 63/63

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 1031x 1x 1x         1x 1x 1x 1x 1x     1x                                     1x 1141x 1141x   1141x   1141x 1141x 573x 573x 573x 573x 573x 1141x 1141x   1141x   1141x 1142x   1142x 1142x 1142x 1142x 1142x   1142x 1142x 1142x 1142x 1142x 1142x 1142x 1142x 1142x 1142x   1142x 1142x   1142x 1142x 1142x 1142x 1142x 1142x 1142x 1142x   1142x 1142x   1142x   1141x 1141x 1141x 1140x   1x 1x 1x   1141x   1141x   1x  
import React, { memo, MouseEventHandler, useMemo, ButtonHTMLAttributes } from 'react';
import classNames from 'classnames';
import { useFallbackTranslation } from '@/hooks/useFallbackTranslation';
 
export type TypeButton = 'button' | 'submit' | 'reset';
export type IButtonVariantTypes = 'primary' | 'secondary' | 'tertiary' | 'round';
 
export enum ButtonVariant {
  PRIMARY = 'primary',
  SECONDARY = 'secondary',
  TERTIARY = 'tertiary',
  ROUND = 'round',
}
 
const { PRIMARY, SECONDARY, TERTIARY, ROUND } = ButtonVariant;
 
export interface IButtonComponent extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'type'> {
  id?: string;
  key?: string;
  name?: string;
  type?: TypeButton;
  children?: React.ReactNode;
  handleClick?: MouseEventHandler<HTMLButtonElement>;
  active?: boolean;
  className?: string;
  tooltip?: string;
  variant?: IButtonVariantTypes;
  buttonsConfig?: IButtonComponent[];
  configCustomClass?: string;
  size?: 'xs' | 'sm' | 'lg';
  selected?: boolean;
}
 
const Button: React.FC<IButtonComponent> = (props) => {
  const { buttonsConfig, configCustomClass } = props || {};
  const { t } = useFallbackTranslation();
 
  const baseClasses = 'button-component';
 
  const variantClasses = useMemo(
    () => ({
      primary: PRIMARY,
      secondary: SECONDARY,
      tertiary: TERTIARY,
      round: 'round rounded-full',
    }),
    []
  );
 
  const disabledClasses = 'bg-gray text-disabled cursor-not-allowed';
 
  const buttonRender = (btn: IButtonComponent): React.JSX.Element => {
    const { handleClick, className, name, children, size = 'sm', variant = PRIMARY, disabled, active, ...rest } = btn;
 
    const sizeClasses = {
      xs: variant === ROUND ? 'text-sm h-6 w-6' : 'text-xs px-1 py-1',
      sm: variant === ROUND ? 'text-base h-8 w-8' : 'text-sm px-3 py-2',
      lg: variant === ROUND ? 'text-xl h-12 w-12' : 'text-base px-4 py-3',
    };
 
    const buttonVariantClass = classNames(
      baseClasses,
      variantClasses[variant],
      sizeClasses[size],
      disabled ? disabledClasses : '',
      className,
      {
        active: active,
      }
    );
 
    const shouldUseAriaLabelledBy = 'aria-labelledby' in btn && !!btn['aria-labelledby'];
    const fallbackAriaLabel = name ? t(name) : 'Unnamed Button';
 
    return (
      <button
        {...rest}
        type={btn.type || 'button'}
        onClick={handleClick}
        aria-label={!shouldUseAriaLabelledBy ? (btn['aria-label'] ?? fallbackAriaLabel) : undefined}
        className={buttonVariantClass}
        disabled={disabled ?? false}
      >
        {name ? t(name) : children}
      </button>
    );
  };
 
  return (
    <>
      {!buttonsConfig?.length ? (
        buttonRender(props)
      ) : (
        <div className={configCustomClass ?? 'flex gap-8'}>
          {buttonsConfig?.map((btn, i) => <React.Fragment key={btn.id ?? btn.name ?? `btn-${i}`}>{buttonRender(btn)}</React.Fragment>)}
        </div>
      )}
    </>
  );
};
 
export default memo(Button);