react-circular-slider icon indicating copy to clipboard operation
react-circular-slider copied to clipboard

Support multi-stop gradients for track and progress

Open jlarmstrongiv opened this issue 3 years ago • 4 comments

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

jlarmstrongiv avatar Nov 23 '20 16:11 jlarmstrongiv

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

fseehawer avatar Nov 23 '20 19:11 fseehawer

progressGradient

@jlarmstrongiv hi do you have working sample for multi stops

dev2-piniada avatar Aug 10 '21 06:08 dev2-piniada

@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.

jlarmstrongiv avatar Aug 10 '21 22:08 jlarmstrongiv

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!

monecchi avatar May 30 '22 04:05 monecchi