react-circular-input
A declarative and composable approach means we have a lot of flexibility, here are a few examples that showcase it.
Or play around with the examples at CodeSandbox
Default
An example of using the 3 components included with their default styling.
const [value, setValue] = useState(0.25)
return (
<CircularInput value={value} onChange={setValue}>
<CircularTrack />
<CircularProgress />
<CircularThumb />
</CircularInput>
)Animated
You can use libraries like react-spring to add animation.
const [value, setValue] = useState(0.25)
return (
<Spring to={{ value }}>
{props => (
<CircularInput value={props.value} onChange={setValue}>
<CircularTrack />
<CircularProgress />
<CircularThumb />
</CircularInput>
)}
</Spring>
)Styled
The recommended way to style the components is to use CSS classes or CSS-in-JS as you normally do for other components 🙂
CircularTrack, CircularProgress and CircularThumb are just SVG <circle /> so you can also just tweak most (see Component docs) attributes 💅
const [value, setValue] = useState(0.25)
return (
<StyledCircularInput value={value} onChange={setValue}>
{/* CSS-in-JS */}
<StyledCircularTrack />
{/* CSS class */}
<CircularProgress className="custom-progress" />
{/* SVG style prop */}
<CircularThumb r={10} fill="rgba(255,255,255,0.5)" />
</StyledCircularInput>
)Custom Component
Using the provided Hooks you can create your own components! 🤩
const CustomComponent = () => {
const { getPointFromValue, value } = useCircularInputContext()
const { x, y } = getPointFromValue()
return (
<text x={x} y={y} style={{ pointerEvents: 'none' }}>
{Math.round(value * 100)}
</text>
)
}
const CustomComponentExample = () => {
const [value, setValue] = useState(0.25)
return (
<CircularInput value={value} onChange={setValue}>
<CircularProgress />
<CircularThumb />
{/* Add any component and use the provided hooks! */}
<CustomComponent />
</CircularInput>
)
}Progress
Can be used to simply display progress/gauge.
<CircularInput value={Math.random()}>
<CircularTrack strokeWidth={5} stroke="#eee" />
<CircularProgress stroke={`hsl(${props.value * 100}, 100%, 50%)`} />
</CircularInput>Min, Max & Scale
There are no props for min/max/etc as you can easily achieve these and much more with simple code.
const [value, setValue] = useState(0.25)
// custom limits
const min = 0.25
const max = 0.75
// get value within limits
const valueWithinLimits = v => Math.min(Math.max(v, min), max)
// custom range
const range = [0, 100]
// scaled range value
const rangeValue = value * (range[1] - range[0]) + range[0]
return (
<CircularInput
value={valueWithinLimits(value)}
onChange={v => setValue(valueWithinLimits(v))}
>
<CircularTrack />
<CircularProgress />
{/* limit lines */}
<line x1={-10} x2={10} y1={100} y2={100} stroke="black" />
<line x1={190} x2={210} y1={100} y2={100} stroke="black" />
<CircularThumb />
{/* range value in center */}
<text x={100} y={100} textAnchor="middle" dy="0.3em" fontWeight="bold">
{Math.round(rangeValue)}%
</text>
</CircularInput>
)Steps
You're in full control of the value so it's easy to introduce the stepped interaction.
const [value, setValue] = useState(0.25)
const stepValue = v => Math.round(v * 10) / 10
return (
<CircularInput
value={stepValue(value)}
onChange={v => setValue(stepValue(v))}
>
<CircularTrack />
<CircularProgress />
<CircularThumb />
<text x={100} y={100} textAnchor="middle" dy="0.3em" fontWeight="bold">
{Math.round(stepValue(value) * 100)}%
</text>
</CircularInput>
)Readonly
Omitting the onChange prop makes it readonly.
<CircularInput value={0.25}>
<CircularTrack />
<CircularProgress />
</CircularInput>Render Prop
Use children prop as a function to receive the current context.
const [value, setValue] = useState(0.25)
return (
<CircularInput value={value} onChange={setValue}>
{({ getPointFromValue }) => (
<>
<CircularTrack />
<CircularProgress />
<text {...getPointFromValue()} dx="30" dy="0.3em">
wee!
</text>
<CircularThumb />
</>
)}
</CircularInput>
)It might be a good time to dive deep into customisation with components and hooks: