fseehawer / react-circular-slider

A simple circular slider for react
https://fseehawer.github.io/react-circular-slider/
170 stars 63 forks source link

Support multi-stop gradients for track and progress #23

Open jlarmstrongiv opened 3 years ago

jlarmstrongiv commented 3 years ago

I love how smooth this implementation of a circular slider is! My end goal is to create a hue slider for a color picker.

Luckily, svg stop-color accepts transparent as a value. Translucent colors with stop-opacity are not currently supported as props in this library.

The main problem is the trackColor, which is eventually passed down to the svg circle as stroke={trackColor}. Unfortunately, the stroke does not support the css gradient syntax. Instead, it requires a linearGradient definition within the svg. The current implementation of the progress bar supports two color gradients, but not the near infinite (~360) multi-color gradient needed for the color picker.

I think it would be great to upgrade the stroke of the trackColor and the progress bar at the same time. I noticed this library prides itself on having zero dependencies; otherwise, I would suggest parsing colors via tinycolor.

I think it would be great to have a new color and gradient syntax for the trackColor and the progress bar. Perhaps a syntax like:

const example = (
  <CircularSlider
    trackColor="#eeeeee"
    progressGradient={[
      "#ffb347",
      {
        offset: '100%',
        stopColor: "#ffcc33",
        stopOpacity: 0.5,
      },
    ]}
  />
);

Implemented like:

const createColorStops = (color, index, colors) => {
  if (typeof color === "string") {
    color = { stopColor: color };
  }
  let { offset, stopColor, stopOpacity } = color;

  if (index === 0) {
    return (
      <stop
        offset={offset ?? `0%`}
        stopColor={stopColor}
        stopOpacity={stopOpacity ?? 1}
      />
    );
  } else if (index === gradients.length - 1) {
    return (
      <stop
        offset={offset ?? `100%`}
        stopColor={stopColor}
        stopOpacity={stopOpacity ?? 1}
      />
    );
  } else {
    return (
      <stop
        offset={offset ?? `${(100 / (gradients.length - 1)) * index}%`}
        stopColor={stopColor}
        stopOpacity={stopOpacity ?? 1}
      />
    );
  }
};

const svg = (props) => {
  const {
    label,
    trackGradient, 
    progressGradient, 
    trackColor, 
    progressColor, 
  } = props;

  let defs;
  if (trackGradient || progressGradient) {
    defs = (
      <defs>
        {trackGradient && (
          <linearGradient id={`${label}-track`} x1="100%" x2="0%">
            {trackGradient.map(createColorStops)}
          </linearGradient>
        )}
        {progressGradient && (
          <linearGradient id={`${label}-progress`} x1="100%" x2="0%">
            {progressGradient.map(createColorStops)}
          </linearGradient>
        )}
      </defs>
    );
  }

  return (
    <svg>
      {defs}
      <circle stroke={trackColor || `url(#${label}-track)`} />
      <path stroke={progressColor || `url(#${label}-progress)`} />
    </svg>
  );
};

My only other comment on the implementation is that we could cache the defs with React.useMemo, since we don’t need to recalculate that on every input change.

Of course, changing the syntax would probably mean a major version bump. If you’re okay with that, I could formalize a pull request. Anyway, thanks for this awesome library!

TODO: references

fseehawer commented 3 years ago

Hi @jlarmstrongiv, I always appreciate it when someone contributes to my library. You are welcomed to create a PR. Thanks!

dev2-piniada commented 3 years ago
progressGradient

@jlarmstrongiv hi do you have working sample for multi stops

jlarmstrongiv commented 3 years ago

@dev2-piniada The example code above should work for multiple color stops. Unfortunately, my project ended up going in a different direction and I was unable to finish a PR for it.

monecchi commented 2 years ago

That would be a great feature! I was hoping I could mimic a kelvin color temperature gradient, but now I see this is not yet implemented... Awesome Circular Slider by the way!