gilbarbara/colorizr

View on GitHub
demo/src/App.tsx

Summary

Maintainability
D
2 days
Test Coverage
import React, { ChangeEvent, useEffect, useRef, useState } from 'react';
import Colorizr, { formatHex, isValidHex, palette, random, rotate, scheme } from 'colorizr';

import {
  Block,
  Box,
  Checker,
  ColorPicker,
  Contrast,
  Flex,
  Footer,
  Grid,
  H1,
  H2,
  H3,
  H4,
  InputBox,
  Item,
  Label,
  Pattern,
  Properties,
  Title,
  Wrapper,
} from './components';
import Check from './icons/Check';
import Times from './icons/Times';

export default function App() {
  const [color, setColor] = useState(random());
  const [textColor, setTextColor] = useState('');
  const [colorInput, setColorInput] = useState(color);
  const [textColorInput, setTextColorInput] = useState('');

  const [colorizr, setColorizr] = useState(new Colorizr(color));
  const timeout = useRef<number>();

  const text = textColor || colorizr.textColor;
  const analysis = colorizr.compare(text);

  const grade = () => {
    const { contrast } = analysis;

    let output = 'Very Poor';

    if (contrast > 12) {
      output = 'Super';
    } else if (contrast > 7) {
      output = 'Very good';
    } else if (contrast > 4.5) {
      output = 'Good';
    } else if (contrast > 3) {
      output = 'Poor';
    }

    return output;
  };

  const hsl = () => {
    const { h, s, l } = colorizr.hsl;

    return `hsl(${h}, ${s}%, ${l}%)`;
  };

  const rgb = () => {
    const { r, g, b } = colorizr.rgb;

    return `rgb(${r}, ${g}, ${b})`;
  };

  useEffect(() => {
    setColorizr(new Colorizr(color));
    setColorInput(color);
  }, [color]);

  const handleChangeBgColor = (event: ChangeEvent<HTMLInputElement>) => {
    const { value } = event.target;
    const hex = `#${value.replace(/[^\da-f]/gi, '')}`;

    if (hex.length > 7) {
      return;
    }

    setColorInput(hex);

    if (isValidHex(hex, false)) {
      setColor(hex);
    }
  };

  const handleChangeBgColorPicker = (event: ChangeEvent<HTMLInputElement>) => {
    const { value } = event.target;

    clearTimeout(timeout.current);

    timeout.current = setTimeout(() => {
      setColor(value);
    }, 0);
  };

  const handleChangeTextColor = (event: ChangeEvent<HTMLInputElement>) => {
    const { value } = event.currentTarget;
    const hex = `#${value.replace(/[^\da-f]/gi, '')}`;

    if (hex.length > 7) {
      return;
    }

    setTextColorInput(hex);

    if (isValidHex(hex, false)) {
      setTextColor(hex);
    }
  };

  const handleChangeTextColorPicker = (event: ChangeEvent<HTMLInputElement>) => {
    const { value } = event.currentTarget;

    setTextColorInput(value);
    setTextColor(value);
  };

  return (
    <Wrapper bg={colorizr.hex} color={text}>
      <H1>Colorizr</H1>
      <H2>Color conversion, manipulation, comparison, and analysis.</H2>
      <Box>
        <Label>
          Background color
          <InputBox>
            <input onChange={handleChangeBgColor} type="text" value={colorInput} />
            <ColorPicker>
              <input onInput={handleChangeBgColorPicker} type="color" value={formatHex(color)} />
            </ColorPicker>
          </InputBox>
        </Label>
        <Properties>
          <div>
            <span>Luminance</span>
            <span>{colorizr.luminance}</span>
          </div>
          <div>
            <span>Chroma</span>
            <span>{colorizr.chroma}</span>
          </div>
          <div>
            <span>HSL</span>
            <span>{hsl()}</span>
          </div>
          <div>
            <span>RGB</span>
            <span>{rgb()}</span>
          </div>
        </Properties>
      </Box>
      <Box>
        <Checker>
          <Label>
            Text color
            <InputBox>
              <input onChange={handleChangeTextColor} type="text" value={textColorInput || text} />
              <ColorPicker>
                <input onInput={handleChangeTextColorPicker} type="color" value={formatHex(text)} />
              </ColorPicker>
            </InputBox>
          </Label>
          <br />
          <strong>Contrast</strong>
          <Contrast
            contrast={analysis.contrast}
            largeText={[analysis.largeAA, analysis.largeAAA].filter(Boolean).length}
            normalText={[analysis.normalAA, analysis.normalAAA].filter(Boolean).length}
          >
            <div className="top">
              <div className="left">{analysis.contrast}</div>
              <div className="right">{grade()}</div>
            </div>
            <div className="bottom">
              <div className="small">
                <p>Small text</p>
                <Flex className="grades">
                  <Flex>
                    <span>AA</span>
                    {analysis.normalAA ? <Check /> : <Times />}
                  </Flex>
                  <Flex>
                    <span>AAA</span> {analysis.normalAAA ? <Check /> : <Times />}
                  </Flex>
                </Flex>
              </div>
              <div className="large">
                <p>Large text</p>
                <Flex className="grades">
                  <Flex>
                    <span>AA</span>
                    {analysis.largeAA ? <Check /> : <Times />}
                  </Flex>
                  <Flex>
                    <span>AAA</span>
                    {analysis.largeAAA ? <Check /> : <Times />}
                  </Flex>
                </Flex>
              </div>
            </div>
          </Contrast>
          <Properties>
            <div>
              <span>Brightness Difference</span>
              <span>{analysis.brightnessDifference}</span>
            </div>
            <div>
              <span>Color Difference</span>
              <span>{analysis.colorDifference}</span>
            </div>
            <div>
              <span>Compliant</span>
              <span>{analysis.compliant}</span>
            </div>
          </Properties>
        </Checker>
      </Box>

      <H3>utilities</H3>

      <Grid>
        <Item>
          <Title>lighten</Title>
          <Block color={colorizr.lighten(10)} />
        </Item>
        <Item>
          <Title>darken</Title>
          <Block color={colorizr.darken(10)} />
        </Item>
      </Grid>

      <Grid>
        <Item>
          <Title>saturate</Title>
          <Block color={colorizr.saturate(20)} />
        </Item>
        <Item>
          <Title>desaturate</Title>
          <Block color={colorizr.desaturate(20)} />
        </Item>
      </Grid>

      <Grid>
        <Item>
          <Title>fade</Title>
          <Pattern>
            <Block color={colorizr.fade(30)} />
          </Pattern>
        </Item>

        <Item>
          <Title>invert</Title>
          <Block color={colorizr.invert()} />
        </Item>
      </Grid>

      <H3>rotate</H3>

      <Grid>
        {Array.from({ length: 360 / 60 - 1 }, (_, index) => index + 1).map(index => {
          const degrees = index * 60;
          const color = rotate(colorizr.hex, degrees);

          return (
            <Item key={degrees}>
              <Title>{degrees} deg</Title>
              <Block color={color} />
              <Footer>{color}</Footer>
            </Item>
          );
        })}
      </Grid>

      <H3>palette</H3>

      <H4>basic</H4>
      <Grid>
        {palette(colorizr.hex).map((d, index) => (
          <Item key={d + index}>
            <Block color={d} />
            <Footer>{d}</Footer>
          </Item>
        ))}
      </Grid>

      <H4>type: monochromatic, size: 12</H4>
      <Grid>
        {palette(colorizr.hex, { size: 12, type: 'monochromatic' }).map((d, index) => (
          <Item key={d + index}>
            <Block color={d} />
            <Footer>{d}</Footer>
          </Item>
        ))}
      </Grid>

      <H4>lightness(60)</H4>
      <Grid>
        {palette(colorizr.hex, { lightness: 70 }).map((d, index) => (
          <Item key={d + index}>
            <Block color={d} />
            <Footer>{d}</Footer>
          </Item>
        ))}
      </Grid>

      <H4>saturation(100); size(20)</H4>
      <Grid>
        {palette(colorizr.hex, { saturation: 100, size: 24 }).map((d, index) => (
          <Item key={d + index}>
            <Block color={d} />
            <Footer>{d}</Footer>
          </Item>
        ))}
      </Grid>

      <H3>Scheme</H3>

      <H4>analogous</H4>
      <Grid>
        {scheme(colorizr.hex, 'analogous').map((d, index) => (
          <Item key={d + index}>
            <Block color={d} />
            <Footer>{d}</Footer>
          </Item>
        ))}
      </Grid>

      <H4>complementary</H4>
      <Grid>
        {scheme(colorizr.hex, 'complementary').map((d, index) => (
          <Item key={d + index}>
            <Block color={d} />
            <Footer>{d}</Footer>
          </Item>
        ))}
      </Grid>

      <H4>split</H4>
      <Grid>
        {scheme(colorizr.hex, 'split').map((d, index) => (
          <Item key={d + index}>
            <Block color={d} />
            <Footer>{d}</Footer>
          </Item>
        ))}
      </Grid>

      <H4>triadic</H4>
      <Grid>
        {scheme(colorizr.hex, 'triadic').map((d, index) => (
          <Item key={d + index}>
            <Block color={d} />
            <Footer>{d}</Footer>
          </Item>
        ))}
      </Grid>

      <H4>tetradic</H4>
      <Grid>
        {scheme(colorizr.hex, 'tetradic').map((d, index) => (
          <Item key={d + index}>
            <Block color={d} />
            <Footer>{d}</Footer>
          </Item>
        ))}
      </Grid>

      <H4>rectangle</H4>
      <Grid>
        {scheme(colorizr.hex, 'rectangle').map((d, index) => (
          <Item key={d + index}>
            <Block color={d} />
            <Footer>{d}</Footer>
          </Item>
        ))}
      </Grid>

      <H4>square</H4>
      <Grid>
        {scheme(colorizr.hex, 'square').map((d, index) => (
          <Item key={d + index}>
            <Block color={d} />
            <Footer>{d}</Footer>
          </Item>
        ))}
      </Grid>
    </Wrapper>
  );
}