All files / Checkbox Checkbox.tsx

100% Statements 50/50
100% Branches 25/25
100% Functions 12/12
100% Lines 46/46

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 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 1213x   3x 3x 3x 3x 3x   3x     19x 19x 19x 19x 19x 19x 19x 19x 19x 19x 19x 19x 19x       19x 19x 19x   19x 17x     19x 19x   19x 1x 1x   1x     19x                                           3x   3x 36x   4x   30x   2x       3x                   18x               18x 18x   18x               18x   18x           18x      
import React, { forwardRef, useEffect, useState } from "react";
import { CheckboxProps, CheckboxStyleProps } from "./Checkbox.types";
import { IconCheck } from "@tabler/icons-react";
import { useTheme } from "../ThemeProvider/ThemeProvider";
import styled from "styled-components";
import colorTokens from "../../tokens/colors.json"
import { getColorPalette } from "../../helpers/helpers";
 
const Checkbox = forwardRef(
  (
    {
      id,
      value,
      size = "medium",
      color = colorTokens.default.primary.main,
      checked,
      disabled,
      label,
      name,
      className,
      style,
      onChange,
      "data-testid": dataTestid,
      ...props
    }: CheckboxProps,
    ref
  ) => {
    const innerRef = React.useRef<HTMLInputElement>(null);
    const _ref = (ref ?? innerRef) as React.RefObject<HTMLInputElement>;
    const [isChecked, setIsChecked] = useState(checked ?? false);
 
    useEffect(() => {
      setIsChecked(checked ?? false);
    }, [checked]);
 
    const theme = useTheme().theme;
    const colorPalette = getColorPalette(theme,color);
 
    const _onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
      setIsChecked((prev) => {
        return !prev;
      });
      onChange && onChange(event);
    };
 
    return (
      <StyledCheckbox
        role="checkbox"
        aria-checked={isChecked}
        $size={size}
        $colorPalette={colorPalette}
        $color={color}
        data-testid={dataTestid}
        {...props}
      >
        <input
          ref={_ref}
          checked={isChecked}
          type="checkbox"
          name={name}
          onChange={_onChange}
        />
        <span>{isChecked && <IconCheck />}</span>
      </StyledCheckbox>
    );
  }
);
export default Checkbox;
 
const getSize = (size?: "small" | "medium" | "large") => {
  switch (size) {
    case "small":
      return 0.875;
    case "medium":
      return 1;
    case "large":
      return 1.25;
  }
};
 
const StyledCheckbox = styled.span<CheckboxStyleProps>`
  display: flex;
  align-items: center;
  justify-content: center;
  width: fit-content;
  cursor: pointer;
  padding: 0.5rem;
  border-radius: 50%;
  "&:hover": {
    background: ${(props) =>
      props.$colorPalette[props.$color].accentScale[1]};
  }
  & input[type="checkbox"] {
    position: absolute;
    opacity: 0;
    margin: 0;
  }
  & input[type="checkbox"] + span {
    width: ${(props) => getSize(props.$size)}rem;
    height: ${(props) => getSize(props.$size)}rem;
    border: 1.5px solid
      ${(props) => props.$colorPalette[props.$color].accentScale[10]};
    border-radius: 0.25rem;
    position: relative;
 
    & svg {
      width: 100%;
      height: 100%;
      background-color: ${(props) =>
        props.$colorPalette[props.$color].accentScale[8]};
      color: ${(props) =>
        props.$colorPalette[props.$color].accentContrast};
      border-radius: 0.15rem;
    }
  }
  & input[type="checkbox"]:focus + span {
    outline: 1px solid
      ${(props) => props.$colorPalette[props.$color].accentScale[8]};
    outline-offset: 1px;
  }
`;