components/elements/IconWheel/IconWheel.jsxjavascript
import "./IconWheel.scss";
// ring radius in em, distributed the same way SelectWidget spreads its
// options: evenly around the circle, starting at the top (-90deg) clockwise
const NODE_RADIUS = 11;
const LABEL_RADIUS = 16.5;
/**
* N icon nodes with text labels evenly spaced around a decorative ring
* ("iconWheel" element type) - unlike CircleInfographic/SelectWidget, purely
* decorative and non-interactive.
*
* @param {Object} props
* @param {Array<{icon: string, label: string}>} props.items - Ring nodes.
*/
export const IconWheel = ({ items }) => {
return (
<div className="icon-wheel">
<div className="icon-wheel__ring">
{items.map((item, index) => {
const angle = -90 + index * (360 / items.length);
const radians = (angle * Math.PI) / 180;
const cos = Math.cos(radians);
const sin = Math.sin(radians);
// side determines label text alignment: to the outside of the ring
const side = cos > 0.3 ? "right" : cos < -0.3 ? "left" : "center";
return (
<div key={item.label} className="icon-wheel__spoke">
<div
className="icon-wheel__node"
style={{ "--x": `${NODE_RADIUS * cos}em`, "--y": `${NODE_RADIUS * sin}em` }}>
<img className="icon-wheel__icon" src={item.icon} alt="" />
</div>
<span
className={`icon-wheel__label icon-wheel__label--${side}`}
style={{ "--x": `${LABEL_RADIUS * cos}em`, "--y": `${LABEL_RADIUS * sin}em` }}>
{item.label}
</span>
</div>
);
})}
</div>
</div>
);
};