@onesy/ui-react
Version:
UI for React
637 lines (610 loc) • 24.3 kB
JavaScript
import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
import _defineProperty from "@babel/runtime/helpers/defineProperty";
const _excluded = ["ref", "tonal", "color", "size", "parts", "lineCap", "padding", "gap", "border", "background", "boundary", "boundaryWidth", "arcProgress", "arcsVisible", "marksVisible", "labelsVisible", "marks", "markSize", "markWidth", "labels", "renderLabel", "childrenPosition", "additional", "textProps", "pathProps", "SvgProps", "MarkProps", "LabelProps", "BackgroundProps", "BorderProps", "ArcProps", "ArcMainProps", "ArcsProgressProps", "ArcProgressProps", "Component", "className", "style", "children"],
_excluded2 = ["size", "padding", "position"],
_excluded3 = ["value", "padding", "position"],
_excluded4 = ["x", "y", "value"];
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
import React from 'react';
import { clamp, is, parse, valueFromPercentageWithinRange } from '@onesy/utils';
import { classNames, style as styleMethod, useOnesyTheme } from '@onesy/style-react';
import SurfaceElement from '../Surface';
import { angleToCoordinates, staticClassName, toNumber } from '../utils';
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
const useStyle = styleMethod(theme => ({
root: {
position: 'relative',
width: '100vw'
},
size_small: {
maxWidth: '180px'
},
size_regular: {
maxWidth: '240px'
},
size_large: {
maxWidth: '300px'
},
boundary_1: {
aspectRatio: '1'
},
boundary_075: {
aspectRatio: '1'
},
boundary_05: {
aspectRatio: '1'
},
boundary_025: {
aspectRatio: '1'
},
label: _objectSpread(_objectSpread({}, theme.typography.values.b2), {}, {
textAnchor: 'middle',
alignmentBaseline: 'central',
dominantBaseline: 'central'
}),
svg: {
position: 'relative',
width: '100%',
height: 'auto'
}
}), {
name: 'onesy-RoundMeter'
});
const RoundMeter = props_ => {
const theme = useOnesyTheme();
const props = _objectSpread(_objectSpread(_objectSpread({}, theme?.ui?.elements?.all?.props?.default), theme?.ui?.elements?.onesyRoundMeter?.props?.default), props_);
const Surface = theme?.elements?.Surface || SurfaceElement;
const {
ref,
tonal = true,
color = 'primary',
size = 'regular',
parts: parts_ = 1,
lineCap,
padding: outsidePadding = 0,
gap: gap_ = 0,
border = false,
background = false,
boundary: boundary_ = 1,
boundaryWidth = 1,
arcProgress = false,
arcsVisible = true,
marksVisible = true,
labelsVisible = true,
marks: marks_ = [],
markSize = 4,
markWidth = 1,
labels: labels_ = [],
renderLabel,
childrenPosition = 'post',
additional,
textProps,
pathProps,
SvgProps,
MarkProps,
LabelProps,
BackgroundProps,
BorderProps,
ArcProps,
ArcMainProps,
ArcsProgressProps,
ArcProgressProps,
Component = 'div',
className,
style,
children
} = props,
other = _objectWithoutProperties(props, _excluded);
const {
classes
} = useStyle();
const refs = {
root: React.useRef(undefined)
};
const styles = {
root: {}
};
let radius;
const boundary = parse(boundary_);
const width = 240;
const height = width;
let gap = ['round', 'square'].includes(lineCap) ? gap_ + boundaryWidth / 2 : gap_;
const parts = clamp(parse(parts_), 1, 180);
let min = 0;
let max = 360;
let yViewBox = 0;
// 1
if (boundary === 1) {
// 0 is middle top
// ie. 270 degreese
min = 270;
max = 270 + 360;
}
// 0.75
if (boundary === 0.75) {
// 0 is angle bottom left
// ie. 270 degreese
min = 135;
max = 135 + 270;
yViewBox = -15;
}
// 0.5
if (boundary === 0.5) {
// 0 is left
// ie. 180 degreese
min = 180;
max = 180 + 180;
yViewBox = -50;
}
// 0.25
if (boundary === 0.25) {
// 0 is angle top left
// ie. 225 degreese
min = 225;
max = 225 + 90;
yViewBox = -60;
}
if (!['small', 'regular', 'large'].includes(size)) styles.root.maxWidth = size;
const marks = React.useMemo(() => {
const values = [];
if (marks_.length) {
const center = toNumber(width / 2);
radius = toNumber(width / 2 - boundaryWidth - outsidePadding);
let marksValues = marks_;
if (!is('array', marksValues[0])) marksValues = [marksValues];
marksValues.forEach((marksValue, index) => {
values[index] = [];
marksValue.forEach(mark => {
const {
size: size_,
padding: markPadding = 0,
position
} = mark,
other_ = _objectWithoutProperties(mark, _excluded2);
const itemPadding = toNumber(markPadding);
const angle = valueFromPercentageWithinRange(position, min, max);
const start = angleToCoordinates(angle, center, center, radius - itemPadding);
const end = angleToCoordinates(angle, center, center, radius - (size_ !== undefined ? size_ : markSize) - itemPadding);
values[index].push(_objectSpread({
d: ['M', start.x, start.y, 'L', end.x, end.y].join(' ')
}, other_));
});
});
}
return values;
}, [width, height, parts, marks_, markSize, boundary, boundaryWidth, lineCap, outsidePadding, gap]);
const labels = React.useMemo(() => {
const values_0 = [];
if (labels_.length) {
const center_0 = toNumber(width / 2);
const marksPadding = toNumber(marks_?.length ? (marks_ || []).sort((a, b) => b.size - a.size)[0]?.size || markSize : 0);
radius = toNumber(width / 2 - boundaryWidth - marksPadding - outsidePadding);
let labelsValues = labels_;
if (!is('array', labelsValues[0])) labelsValues = [labelsValues];
labelsValues.forEach((labelsValue, index_0) => {
values_0[index_0] = [];
labelsValue.forEach(label => {
const {
value,
padding: labelPadding = 0,
position: position_0
} = label,
other__0 = _objectWithoutProperties(label, _excluded3);
const itemPadding_0 = toNumber(labelPadding);
const fontSize = toNumber(label.style?.fontSize !== undefined ? label.style.fontSize : 14);
const angle_0 = valueFromPercentageWithinRange(position_0, min, max);
const start_0 = angleToCoordinates(angle_0, center_0, center_0, radius - fontSize / 2 - itemPadding_0);
values_0[index_0].push(_objectSpread({
x: start_0.x,
y: start_0.y,
value
}, other__0));
});
});
}
return values_0;
}, [width, height, parts, marks_, markSize, boundary, boundaryWidth, lineCap, outsidePadding, gap]);
const arcs = React.useMemo(() => {
const values_1 = [];
let value_0 = [];
const offset = outsidePadding;
// 1
if (boundary === 1) {
if (parts === 1) {
radius = width / 2 - boundaryWidth / 2 - offset;
values_1.push({
d: [
// Move
'M', offset + boundaryWidth / 2, width / 2 + 0.001,
// Arc
'A', radius, radius, 0, 1, 0, offset + boundaryWidth / 2, width / 2].join(' ')
});
} else {
const center_1 = width / 2;
radius = width / 2 - boundaryWidth / 2 - offset;
const total = 360;
const part = (total - parts * gap) / parts;
const angles = {
start: angleToCoordinates(0, center_1, center_1, radius)
};
let anglePrevious = 0;
for (let i = 0; i < parts; i++) {
// Move to 0 deg
if (i === 0) value_0.push(
// Move to 0 deg
'M', angles.start.x, angles.start.y);
const angleEnd = anglePrevious + part;
angles.end = angleToCoordinates(angleEnd, center_1, center_1, radius);
angles.move = angleToCoordinates(angleEnd + gap, center_1, center_1, radius);
// Arc
value_0.push('A', radius, radius, 0, 0, 1, angles.end.x, angles.end.y);
// Move the gap if there's a gap
if (gap > 0 && i < parts - 1) {
value_0.push('M', angles.move.x, angles.move.y);
anglePrevious = angleEnd + gap;
} else anglePrevious = angleEnd;
values_1.push({
d: value_0.join(' ')
});
// Move for the next value
if (i < parts - 1) {
value_0 = ['M', angles.move.x, angles.move.y];
}
}
}
}
// 0.75
if (boundary === 0.75) {
value_0 = [];
const center_2 = width / 2;
radius = width / 2 - boundaryWidth / 2 - offset;
const angles_0 = {
end: angleToCoordinates(45, center_2, center_2, radius),
start: angleToCoordinates(135, center_2, center_2, radius)
};
if (parts === 1) {
values_1.push({
d: [
// Line middle bottom
'M', angles_0.start.x, angles_0.start.y,
// Arc
'A', radius, radius, 0, 1, 1, angles_0.end.x, angles_0.end.y].join(' ')
});
} else {
const total_0 = 270;
const part_0 = (total_0 - (parts - 1) * gap) / parts;
const angles_ = {
0: angleToCoordinates(135, center_2, center_2, radius)
};
let anglePrevious_0 = 135;
for (let i_0 = 0; i_0 < parts; i_0++) {
// Move to 135 deg
if (i_0 === 0) value_0.push(
// Move to 0 deg
'M', angles_[0].x, angles_[0].y);
const angleEnd_0 = anglePrevious_0 + part_0;
angles_.end = angleToCoordinates(angleEnd_0, center_2, center_2, radius);
angles_.move = angleToCoordinates(angleEnd_0 + gap, center_2, center_2, radius);
// Arc
value_0.push('A', radius, radius, 0, 0, 1, angles_.end.x, angles_.end.y);
// Move the gap if there's a gap
if (gap > 0 && i_0 < parts - 1) {
value_0.push('M', angles_.move.x, angles_.move.y);
anglePrevious_0 = angleEnd_0 + gap;
} else anglePrevious_0 = angleEnd_0;
values_1.push({
d: value_0.join(' ')
});
// Move for the next value
if (i_0 < parts - 1) {
value_0 = ['M', angles_.move.x, angles_.move.y];
}
}
}
}
// 0.5
if (boundary === 0.5) {
value_0 = [];
const center_3 = width / 2;
radius = width / 2 - boundaryWidth / 2 - offset;
const total_1 = 180;
const part_1 = (total_1 - (parts - 1) * gap) / parts;
const angles_1 = {
start: angleToCoordinates(180, center_3, center_3, radius)
};
let anglePrevious_1 = 180;
for (let i_1 = 0; i_1 < parts; i_1++) {
// Move to 180 deg
if (i_1 === 0) value_0.push(
// Move to 0 deg
'M', angles_1.start.x, angles_1.start.y);
const angleEnd_1 = anglePrevious_1 + part_1;
angles_1.end = angleToCoordinates(angleEnd_1, center_3, center_3, radius);
angles_1.move = angleToCoordinates(angleEnd_1 + gap, center_3, center_3, radius);
// Arc
value_0.push('A', radius, radius, 0, 0, 1, angles_1.end.x, angles_1.end.y);
// Move the gap if there's a gap
if (gap > 0 && i_1 < parts - 1) {
value_0.push('M', angles_1.move.x, angles_1.move.y);
anglePrevious_1 = angleEnd_1 + gap;
} else anglePrevious_1 = angleEnd_1;
values_1.push({
d: value_0.join(' ')
});
// Move for the next value
if (i_1 < parts - 1) {
value_0 = ['M', angles_1.move.x, angles_1.move.y];
}
}
}
// 0.25
if (boundary === 0.25) {
value_0 = [];
const center_4 = width / 2;
radius = width / 2 - boundaryWidth / 2 - offset;
const total_2 = 90;
const part_2 = clamp((total_2 - (parts - 1) * gap) / parts, 0.01);
gap = clamp(gap, 0, (total_2 - part_2 * parts) / (parts - 1));
const angles_2 = {
start: angleToCoordinates(225, center_4, center_4, radius)
};
let anglePrevious_2 = 225;
for (let i_2 = 0; i_2 < parts; i_2++) {
// Move to 225 deg
if (i_2 === 0) value_0.push(
// Move to 0 deg
'M', angles_2.start.x, angles_2.start.y);
const angleEnd_2 = anglePrevious_2 + part_2;
angles_2.end = angleToCoordinates(angleEnd_2, center_4, center_4, radius);
angles_2.move = angleToCoordinates(angleEnd_2 + gap, center_4, center_4, radius);
// Arc
value_0.push('A', radius, radius, 0, 0, 1, angles_2.end.x, angles_2.end.y);
// Move the gap if there's a gap
if (gap > 0 && i_2 < parts - 1) {
value_0.push('M', angles_2.move.x, angles_2.move.y);
anglePrevious_2 = angleEnd_2 + gap;
} else anglePrevious_2 = angleEnd_2;
values_1.push({
d: value_0.join(' ')
});
// Move for the next value
if (i_2 < parts - 1) {
value_0 = ['M', angles_2.move.x, angles_2.move.y];
}
}
}
return values_1;
}, [width, height, parts, boundary, boundaryWidth, lineCap, outsidePadding, gap, gap_]);
const pathBackground = React.useMemo(() => {
const values_2 = [];
const offset_0 = outsidePadding;
// 1
if (boundary === 1) {
radius = width / 2 - boundaryWidth / 2 - offset_0;
values_2.push(
// Move
'M', offset_0 + boundaryWidth / 2, width / 2 + 0.001,
// Arc
'A', radius, radius, 0, 1, 0, offset_0 + boundaryWidth / 2, width / 2);
}
// 0.75
if (boundary === 0.75) {
const center_5 = width / 2;
radius = width / 2 - boundaryWidth / 2 - offset_0;
const angles_3 = {
end: angleToCoordinates(45, center_5, center_5, radius),
start: angleToCoordinates(135, center_5, center_5, radius)
};
values_2.push(
// Move
'M', center_5, center_5,
// Line middle bottom
'L', angles_3.start.x, angles_3.start.y,
// Arc
'A', radius, radius, 0, 1, 1, angles_3.end.x, angles_3.end.y,
// Line bottom middle
'L', center_5, center_5, 'Z');
}
// 0.5
if (boundary === 0.5) {
const center_6 = width / 2;
radius = width / 2 - boundaryWidth / 2 - offset_0;
const total_3 = 180;
const part_3 = (total_3 - (parts - 1) * gap) / parts;
const angles_4 = {
start: angleToCoordinates(180, center_6, center_6, radius)
};
const anglePrevious_3 = 180;
const angleEnd_3 = anglePrevious_3 + part_3;
angles_4.end = angleToCoordinates(angleEnd_3, center_6, center_6, radius);
angles_4.move = angleToCoordinates(angleEnd_3 + gap, center_6, center_6, radius);
values_2.push(
// Move
'M', angles_4.start.x, angles_4.start.y,
// Arc
'A', radius, radius, 0, 0, 1, angles_4.end.x, angles_4.end.y, 'Z');
}
// 0.25
if (boundary === 0.25) {
const center_7 = width / 2;
radius = width / 2 - boundaryWidth / 2 - offset_0;
const total_4 = 90;
const part_4 = clamp((total_4 - (parts - 1) * gap) / parts, 0.01);
gap = clamp(gap, 0, (total_4 - part_4 * parts) / (parts - 1));
const angles_5 = {
start: angleToCoordinates(225, center_7, center_7, radius)
};
const anglePrevious_4 = 225;
const angleEnd_4 = anglePrevious_4 + part_4;
angles_5.end = angleToCoordinates(angleEnd_4, center_7, center_7, radius);
angles_5.move = angleToCoordinates(angleEnd_4 + gap, center_7, center_7, radius);
values_2.push(
// Move
'M', center_7, width / 2 - boundaryWidth,
// Line middle bottom, top quarter left
'L', angles_5.start.x, angles_5.start.y,
// Arc
'A', radius, radius, 0, 0, 1, angles_5.end.x, angles_5.end.y,
// Line top quarter right, middle bottom
'L', center_7, width / 2 - boundaryWidth, 'Z');
}
return values_2.join(' ');
}, [width, height, boundary, boundaryWidth, outsidePadding]);
const pathBorder = React.useMemo(() => {
const values_3 = [];
const offset_1 = outsidePadding;
// 0.75
if (boundary === 0.75) {
const center_8 = width / 2;
radius = width / 2 - boundaryWidth / 2 - offset_1;
const angles_6 = {
end: angleToCoordinates(45, center_8, center_8, radius),
start: angleToCoordinates(135, center_8, center_8, radius)
};
values_3.push(
// Move bottom angle left
'M', angles_6.start.x, angles_6.start.y,
// Line middle
'L', center_8, center_8,
// Line bottom angle right
'L', angles_6.end.x, angles_6.end.y);
}
// 0.5
if (boundary === 0.5) {
const center_9 = width / 2;
radius = width / 2 - boundaryWidth / 2 - offset_1;
const angles_7 = {
start: angleToCoordinates(180, center_9, center_9, radius)
};
values_3.push(
// Move
'M', angles_7.start.x, angles_7.start.y,
// Line
'L', width - boundaryWidth / 2 - offset_1, angles_7.start.y);
}
// 0.25
if (boundary === 0.25) {
const center_10 = width / 2;
radius = width / 2 - boundaryWidth / 2 - offset_1;
const total_5 = 90;
const part_5 = clamp((total_5 - (parts - 1) * gap) / parts, 0.01);
gap = clamp(gap, 0, (total_5 - part_5 * parts) / (parts - 1));
const angles_8 = {
start: angleToCoordinates(225, center_10, center_10, radius),
end: angleToCoordinates(315, center_10, center_10, radius)
};
values_3.push(
// Move middle bottom, top quarter left
'M', angles_8.start.x, angles_8.start.y,
// Line middle bottom
'L', center_10, width / 2 - boundaryWidth,
// Arc top quarter right, middle bottom
'L', angles_8.end.x, angles_8.end.y);
}
return values_3.join(' ');
}, [width, height, boundary, boundaryWidth, outsidePadding]);
const children_ = children && /*#__PURE__*/_jsx("g", {
className: classNames([staticClassName('RoundMeter', theme) && ['onesy-RoundMeter-children'], classes.children]),
children: React.Children.toArray(children).map((item, index_1) => {
return /*#__PURE__*/React.cloneElement(item, {
key: index_1,
fill: item.props.fill !== undefined ? item.props.fill : color,
stroke: item.props.stroke !== undefined ? item.props.stroke : color,
// clean up
value: undefined,
style: _objectSpread(_objectSpread({}, item.props.value !== undefined ? {
transform: `rotate(${valueFromPercentageWithinRange(item.props.value, min, max)}deg)`
} : undefined), item.props.style)
});
})
});
return /*#__PURE__*/_jsxs(Component, _objectSpread(_objectSpread({
ref: item_0 => {
if (ref) {
if (is('function', ref)) ref(item_0);else ref.current = item_0;
}
refs.root.current = item_0;
},
className: classNames([staticClassName('RoundMeter', theme) && ['onesy-RoundMeter-root', `onesy-RoundMeter-size-${size}`], className, classes.root, classes[`size_${size}`], classes[`boundary_${String(boundary).replace('.', '')}`]]),
style: _objectSpread(_objectSpread({}, styles.root), style)
}, other), {}, {
children: [additional, /*#__PURE__*/_jsx(Surface, {
tonal: tonal,
color: color,
children: ({
color: color_,
backgroundColor
}) => /*#__PURE__*/_jsxs("svg", _objectSpread(_objectSpread({
xmlns: "http://www.w3.org/2000/svg",
viewBox: `0 ${yViewBox} ${width || 0} ${height || 0}`
}, SvgProps), {}, {
className: classNames([staticClassName('RoundMeter', theme) && ['onesy-RoundMeter-svg'], SvgProps?.className, classes.svg]),
children: [childrenPosition === 'pre' && children_, background && /*#__PURE__*/_jsx("path", _objectSpread(_objectSpread({
d: pathBackground,
fill: backgroundColor,
stroke: "none"
}, pathProps), BackgroundProps)), border && /*#__PURE__*/_jsx("path", _objectSpread(_objectSpread({
d: pathBorder,
fill: "none",
stroke: color_,
strokeWidth: boundaryWidth
}, pathProps), BorderProps)), arcsVisible && /*#__PURE__*/_jsx("g", {
className: classNames([staticClassName('RoundMeter', theme) && ['onesy-RoundMeter-arcs'], classes.arcs]),
children: arcs.map((item_1, index_2) => /*#__PURE__*/_jsx("path", _objectSpread(_objectSpread(_objectSpread({
d: item_1.d,
fill: "none",
stroke: color_,
strokeWidth: boundaryWidth,
strokeLinecap: lineCap
}, pathProps), ArcProps), ArcMainProps), index_2))
}), arcsVisible && arcProgress && /*#__PURE__*/_jsx("g", _objectSpread(_objectSpread({}, ArcsProgressProps), {}, {
className: classNames([staticClassName('RoundMeter', theme) && ['onesy-RoundMeter-arcs-progress'], ArcsProgressProps?.className, classes.arcs_progress]),
children: arcs.map((item_2, index_3) => /*#__PURE__*/_jsx("path", _objectSpread(_objectSpread(_objectSpread({
d: item_2.d,
fill: "none",
stroke: color_,
strokeWidth: boundaryWidth,
strokeLinecap: lineCap
}, pathProps), ArcProps), ArcProgressProps), index_3))
})), childrenPosition === 'pre-marks' && children_, marksVisible && !!marks_.length && marks.map((marksValue_0, index_4) => /*#__PURE__*/_jsx("g", {
className: classNames([staticClassName('RoundMeter', theme) && ['onesy-RoundMeter-marks'], classes.marks]),
children: marksValue_0.map((item_3, index_) => /*#__PURE__*/_jsx("path", _objectSpread(_objectSpread({
d: item_3.d,
fill: "none",
stroke: color_,
strokeWidth: item_3.width !== undefined ? item_3.width : markWidth,
strokeLinecap: lineCap
}, pathProps), MarkProps), index_))
}, index_4)), childrenPosition === 'pre-labels' && children_, labelsVisible && !!labels_.length && labels.map((labelsValue_0, index_5) => {
return /*#__PURE__*/_jsx("g", {
className: classNames([staticClassName('RoundMeter', theme) && ['onesy-RoundMeter-labels'], classes.labels]),
children: labelsValue_0.map((item_4, index__0) => {
const {
x,
y,
value: value_1
} = item_4,
other__1 = _objectWithoutProperties(item_4, _excluded4);
const propsLabel = _objectSpread(_objectSpread(_objectSpread(_objectSpread({}, other__1), textProps), LabelProps), {}, {
style: _objectSpread(_objectSpread(_objectSpread({
fill: color_
}, other__1.style), textProps?.style), LabelProps?.style)
});
if (is('function', renderLabel)) return renderLabel(x, y, value_1, propsLabel);
return /*#__PURE__*/_jsx("text", _objectSpread(_objectSpread({
x: x,
y: y
}, propsLabel), {}, {
className: classNames([staticClassName('RoundMeter', theme) && ['onesy-RoundMeter-label'], other__1?.className, textProps?.className, LabelProps?.className, classes.label]),
children: value_1
}), index__0);
})
}, index_5);
}), childrenPosition === 'post' && children_]
}))
})]
}));
};
RoundMeter.displayName = 'onesy-RoundMeter';
export default RoundMeter;