All files / color-slider/src index.tsx

93.75% Statements 30/32
93.1% Branches 27/29
100% Functions 9/9
96.55% Lines 28/29

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 121                                        1x 15x       52x   1x   10x 10x     10x       10x 24x           1x                         5x 5x     5x 10x 10x 10x       5x   5x 3x 3x     5x               25x 25x 25x 25x 25x                   3x                                       1x      
import React from 'react';
import {
  ColorResult,
  color as handleColor,
  hexToHsva,
  hsvaToRgbaString,
  validHex,
  hsvaToRgba,
  HsvaColor,
  rgbaStringToHsva,
} from '@uiw/color-convert';
 
export interface SliderProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange' | 'color'> {
  prefixCls?: string;
  color?: string | HsvaColor;
  lightness?: number[];
  customColorShades?: { color: string | HsvaColor; lightness: number[] }[];
  onChange?: (color: ColorResult, evn: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
}
 
const hsvaCheck = (color?: string | HsvaColor): HsvaColor => {
  return (typeof color === 'string' && validHex(color) ? hexToHsva(color) : color || {}) as HsvaColor;
};
 
// Check if values are within specified units of each other
const withinRange = (val1: number, val2: number, tolerance: number = 2): boolean => Math.abs(val1 - val2) <= tolerance;
 
const hsvaEqual = (c1: HsvaColor, c2: HsvaColor, lightnessArray?: number[]): boolean => {
  // Check for match within 2 units of all properties
  const baseMatch = withinRange(c1.h, c2.h) && withinRange(c1.s, c2.s) && withinRange(c1.a, c2.a);
  const exactMatch = baseMatch && withinRange(c1.v, c2.v);
 
  // If there's a match within range, return true
  Iif (exactMatch) return true;
 
  // If no exact match and a lightness array exists,
  // check if value is within range of any of the lightness array values
  Eif (lightnessArray) {
    return baseMatch && lightnessArray.some((lightness) => withinRange(c2.v, lightness));
  }
 
  return false;
};
 
const Slider = React.forwardRef<HTMLDivElement, SliderProps>((props, ref) => {
  const {
    prefixCls = 'w-color-slider',
    className,
    style,
    onChange,
    color,
    customColorShades = [
      { color: '#000000', lightness: [50, 40, 30, 20, 10] },
      { color: '#ffffff', lightness: [95, 90, 80, 70, 60] },
    ],
    lightness = [80, 65, 50, 35, 20],
    ...other
  } = props;
  const hsva = hsvaCheck(color);
 
  // Find matching custom color and its lightness array
  const matchingCustomColor = customColorShades.find((shade) => {
    const customHsva = hsvaCheck(shade.color);
    const isMatch = hsvaEqual(customHsva, hsva, shade.lightness);
    return isMatch;
  });
 
  // Determine which lightness array to use
  const activeLightness = matchingCustomColor ? matchingCustomColor.lightness : lightness;
 
  const handleClick = (rgbaStr: string, evn: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
    const newHsva = rgbaStringToHsva(rgbaStr);
    onChange && onChange(handleColor(newHsva), evn);
  };
 
  return (
    <div
      ref={ref}
      style={{ display: 'flex', ...style }}
      className={[prefixCls, className || ''].filter(Boolean).join(' ')}
      {...other}
    >
      {activeLightness.map((num: number, idx: number) => {
        const newHsva = { ...hsva, v: num };
        const rgba = hsvaToRgba(newHsva);
        const rgbaStr = `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a})`;
        const checked = rgbaStr === hsvaToRgbaString(hsva);
        return (
          <div
            key={idx}
            style={{
              paddingLeft: 1,
              width: `${100 / activeLightness.length}%`,
              boxSizing: 'border-box',
            }}
          >
            <div
              onClick={(evn) => handleClick(rgbaStr, evn)}
              style={{
                backgroundColor: rgbaStr,
                height: 12,
                cursor: 'pointer',
                ...(checked
                  ? {
                      borderRadius: 2,
                      transform: 'scale(1, 1.5)',
                    }
                  : {}),
              }}
            />
          </div>
        );
      })}
    </div>
  );
});
 
Slider.displayName = 'Slider';
 
export default Slider;