@canonical/react-components 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,58 @@
1
+ import { FC } from "react";
2
+ import "./DoughnutChart.scss";
3
+ export type Segment = {
4
+ /**
5
+ * The colour of the segment.
6
+ */
7
+ color: string;
8
+ /**
9
+ * The segment tooltip.
10
+ */
11
+ tooltip?: string;
12
+ /**
13
+ * The segment length.
14
+ */
15
+ value: number;
16
+ };
17
+ export type Props = {
18
+ /**
19
+ * The label in the centre of the doughnut.
20
+ */
21
+ label?: string;
22
+ /**
23
+ * An optional class name applied to the label.
24
+ */
25
+ labelClassname?: string;
26
+ /**
27
+ * An optional class name applied to the wrapping element.
28
+ */
29
+ className?: string;
30
+ /**
31
+ * The width of the segments when hovered.
32
+ */
33
+ segmentHoverWidth: number;
34
+ /**
35
+ * The width of the segments.
36
+ */
37
+ segmentThickness: number;
38
+ /**
39
+ * The doughnut segments.
40
+ */
41
+ segments: Segment[];
42
+ /**
43
+ * The size of the doughnut.
44
+ */
45
+ size: number;
46
+ /**
47
+ * ID associated to the specific instance of a Chart.
48
+ */
49
+ chartID: string;
50
+ };
51
+ export declare enum TestIds {
52
+ Label = "label",
53
+ Segment = "segment",
54
+ Chart = "chart",
55
+ Section = "Section"
56
+ }
57
+ declare const DoughnutChart: FC<Props>;
58
+ export default DoughnutChart;
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = exports.TestIds = void 0;
7
+ var _propTypes = _interopRequireDefault(require("prop-types"));
8
+ var _react = _interopRequireWildcard(require("react"));
9
+ var _classnames = _interopRequireDefault(require("classnames"));
10
+ var _Tooltip = _interopRequireDefault(require("../Tooltip"));
11
+ require("./DoughnutChart.scss");
12
+ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
13
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
14
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
15
+ let TestIds = exports.TestIds = /*#__PURE__*/function (TestIds) {
16
+ TestIds["Label"] = "label";
17
+ TestIds["Segment"] = "segment";
18
+ TestIds["Chart"] = "chart";
19
+ TestIds["Section"] = "Section";
20
+ return TestIds;
21
+ }({});
22
+ const DoughnutChart = _ref => {
23
+ let {
24
+ className,
25
+ label,
26
+ labelClassname,
27
+ segmentHoverWidth,
28
+ segmentThickness,
29
+ segments,
30
+ size,
31
+ chartID
32
+ } = _ref;
33
+ const [tooltipMessage, setTooltipMessage] = (0, _react.useState)(null);
34
+ const id = (0, _react.useRef)("doughnut-chart-".concat(chartID));
35
+ const hoverIncrease = segmentHoverWidth - segmentThickness;
36
+ const adjustedHoverWidth = segmentHoverWidth + hoverIncrease;
37
+ // The canvas needs enough space so that the hover state does not get cut off.
38
+ const canvasSize = size + adjustedHoverWidth - segmentThickness;
39
+ const diameter = size - segmentThickness;
40
+ const radius = diameter / 2;
41
+ const circumference = Math.round(diameter * Math.PI);
42
+ // Calculate the total value of all segments.
43
+ const total = segments.reduce((totalValue, segment) => totalValue += segment.value, 0);
44
+ let accumulatedLength = 0;
45
+ const segmentNodes = segments.map((_ref2, i) => {
46
+ let {
47
+ color,
48
+ tooltip,
49
+ value
50
+ } = _ref2;
51
+ // The start position is the value of all previous segments.
52
+ const startPosition = accumulatedLength;
53
+ // The length of the segment (as a portion of the doughnut circumference)
54
+ const segmentLength = value / total * circumference;
55
+ // The space left until the end of the circle.
56
+ const remainingSpace = circumference - (segmentLength + startPosition);
57
+ // Add this segment length to the running tally.
58
+ accumulatedLength += segmentLength;
59
+ return /*#__PURE__*/_react.default.createElement("circle", {
60
+ className: "doughnut-chart__segment",
61
+ cx: radius - segmentThickness / 2 - hoverIncrease,
62
+ cy: radius + segmentThickness / 2 + hoverIncrease,
63
+ "data-testid": TestIds.Segment,
64
+ key: i,
65
+ tabIndex: 0,
66
+ "aria-label": tooltip ? "".concat(tooltip, ": ").concat(value) : "".concat(value),
67
+ onMouseOut: tooltip ? () => {
68
+ // Hide the tooltip.
69
+ setTooltipMessage(null);
70
+ } : undefined,
71
+ onMouseOver: tooltip ? () => {
72
+ setTooltipMessage(tooltip);
73
+ } : undefined,
74
+ r: radius,
75
+ style: {
76
+ stroke: color,
77
+ strokeWidth: segmentThickness,
78
+ // The dash array used is:
79
+ // 1 - We want there to be a space before the first visible dash so
80
+ // by setting this to 0 we can use the next dash for the space.
81
+ // 2 - This gap is the distance of all previous segments
82
+ // so that the segment starts in the correct spot.
83
+ // 3 - A dash that is the length of the segment.
84
+ // 4 - A gap from the end of the segment to the start of the circle
85
+ // so that the dash array doesn't repeat and be visible.
86
+ strokeDasharray: "0 ".concat(startPosition.toFixed(2), " ").concat(segmentLength.toFixed(2), " ").concat(remainingSpace.toFixed(2))
87
+ }
88
+ // Rotate the segment so that the segments start at the top of
89
+ // the chart.
90
+ ,
91
+ transform: "rotate(-90 ".concat(radius, ",").concat(radius, ")")
92
+ });
93
+ });
94
+ return /*#__PURE__*/_react.default.createElement("div", {
95
+ className: (0, _classnames.default)("doughnut-chart", className),
96
+ style: {
97
+ maxWidth: "".concat(canvasSize, "px")
98
+ },
99
+ "data-testid": TestIds.Chart
100
+ }, /*#__PURE__*/_react.default.createElement(_Tooltip.default, {
101
+ className: "doughnut-chart__tooltip",
102
+ followMouse: true,
103
+ message: tooltipMessage,
104
+ position: "right"
105
+ }, /*#__PURE__*/_react.default.createElement("style", null, "#".concat(id.current, " .doughnut-chart__segment:hover {\n stroke-width: ").concat(adjustedHoverWidth, " !important;\n }")), /*#__PURE__*/_react.default.createElement("svg", {
106
+ className: "doughnut-chart__chart",
107
+ id: id.current,
108
+ viewBox: "0 0 ".concat(canvasSize, " ").concat(canvasSize),
109
+ "data-testid": TestIds.Section,
110
+ "aria-labelledby": "".concat(id.current, "-chart-title ").concat(id.current, "-chart-desc")
111
+ }, label && /*#__PURE__*/_react.default.createElement("title", {
112
+ id: "".concat(id.current, "-chart-title")
113
+ }, label), /*#__PURE__*/_react.default.createElement("desc", {
114
+ id: "".concat(id.current, "-chart-desc")
115
+ }, segments.map(segment => {
116
+ let description = "";
117
+ if (segment.tooltip) description += "".concat(segment.tooltip, ": ");
118
+ description += segment.value;
119
+ return description;
120
+ }).join(",")), /*#__PURE__*/_react.default.createElement("mask", {
121
+ id: "canvasMask"
122
+ }, /*#__PURE__*/_react.default.createElement("rect", {
123
+ fill: "white",
124
+ height: canvasSize,
125
+ width: canvasSize,
126
+ x: "0",
127
+ y: "0"
128
+ }), /*#__PURE__*/_react.default.createElement("circle", {
129
+ cx: canvasSize / 2,
130
+ cy: canvasSize / 2,
131
+ fill: "black",
132
+ r: radius - segmentThickness / 2
133
+ })), /*#__PURE__*/_react.default.createElement("g", {
134
+ mask: "url(#canvasMask)"
135
+ }, /*#__PURE__*/_react.default.createElement("rect", {
136
+ fill: "transparent",
137
+ height: canvasSize,
138
+ width: canvasSize,
139
+ x: "0",
140
+ y: "0"
141
+ }), /*#__PURE__*/_react.default.createElement("g", null, segmentNodes)), label ? /*#__PURE__*/_react.default.createElement("text", {
142
+ x: radius + adjustedHoverWidth / 2,
143
+ y: radius + adjustedHoverWidth / 2
144
+ }, /*#__PURE__*/_react.default.createElement("tspan", {
145
+ className: (0, _classnames.default)("doughnut-chart__label", labelClassname),
146
+ "data-testid": TestIds.Label
147
+ }, label)) : null)));
148
+ };
149
+ DoughnutChart.propTypes = {
150
+ label: _propTypes.default.string,
151
+ labelClassname: _propTypes.default.string,
152
+ className: _propTypes.default.string,
153
+ segmentHoverWidth: _propTypes.default.number.isRequired,
154
+ segmentThickness: _propTypes.default.number.isRequired,
155
+ segments: _propTypes.default.arrayOf(_propTypes.default.shape({
156
+ color: _propTypes.default.string.isRequired,
157
+ tooltip: _propTypes.default.string,
158
+ value: _propTypes.default.number.isRequired
159
+ })).isRequired,
160
+ size: _propTypes.default.number.isRequired,
161
+ chartID: _propTypes.default.string.isRequired
162
+ };
163
+ var _default = exports.default = DoughnutChart;
@@ -0,0 +1,30 @@
1
+ @import "vanilla-framework";
2
+
3
+ .doughnut-chart {
4
+ width: 6.5rem;
5
+
6
+ .doughnut-chart__tooltip {
7
+ display: block;
8
+ }
9
+
10
+ .doughnut-chart__tooltip > :only-child {
11
+ // Override the tooltip wrapper.
12
+ display: block !important;
13
+ }
14
+
15
+ .doughnut-chart__chart {
16
+ // Restrict hover areas to the strokes.
17
+ pointer-events: stroke;
18
+ }
19
+
20
+ .doughnut-chart__segment {
21
+ fill: transparent;
22
+
23
+ // Animate stroke size changes on hover.
24
+ transition: stroke-width 0.3s ease;
25
+ }
26
+ }
27
+
28
+ .doughnut-chart__legend {
29
+ list-style-type: none;
30
+ }
@@ -0,0 +1,3 @@
1
+ export { default } from "./DoughnutChart";
2
+ export type { Props as DoughnutChartProps } from "./DoughnutChart";
3
+ export type { Segment } from "./DoughnutChart";
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "default", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _DoughnutChart.default;
10
+ }
11
+ });
12
+ var _DoughnutChart = _interopRequireDefault(require("./DoughnutChart"));
13
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
package/dist/index.d.ts CHANGED
@@ -19,6 +19,7 @@ export { default as Col } from "./components/Col";
19
19
  export { default as ConfirmationButton } from "./components/ConfirmationButton";
20
20
  export { default as ConfirmationModal } from "./components/ConfirmationModal";
21
21
  export { default as ContextualMenu } from "./components/ContextualMenu";
22
+ export { default as DoughnutChart } from "./components/DoughnutChart";
22
23
  export { default as EmptyState } from "./components/EmptyState";
23
24
  export { default as Field } from "./components/Field";
24
25
  export { default as Form } from "./components/Form";
@@ -87,6 +88,7 @@ export type { ColProps, ColSize } from "./components/Col";
87
88
  export type { ConfirmationButtonProps } from "./components/ConfirmationButton";
88
89
  export type { ConfirmationModalProps } from "./components/ConfirmationModal";
89
90
  export type { ContextualMenuProps, ContextualMenuDropdownProps, MenuLink, Position, } from "./components/ContextualMenu";
91
+ export type { DoughnutChartProps, Segment } from "./components/DoughnutChart";
90
92
  export type { EmptyStateProps } from "./components/EmptyState";
91
93
  export type { FieldProps } from "./components/Field";
92
94
  export type { FormProps } from "./components/Form";
package/dist/index.js CHANGED
@@ -27,6 +27,7 @@ var _exportNames = {
27
27
  ConfirmationButton: true,
28
28
  ConfirmationModal: true,
29
29
  ContextualMenu: true,
30
+ DoughnutChart: true,
30
31
  EmptyState: true,
31
32
  Field: true,
32
33
  Form: true,
@@ -234,6 +235,12 @@ Object.defineProperty(exports, "ContextualMenu", {
234
235
  return _ContextualMenu.default;
235
236
  }
236
237
  });
238
+ Object.defineProperty(exports, "DoughnutChart", {
239
+ enumerable: true,
240
+ get: function () {
241
+ return _DoughnutChart.default;
242
+ }
243
+ });
237
244
  Object.defineProperty(exports, "EmptyState", {
238
245
  enumerable: true,
239
246
  get: function () {
@@ -663,6 +670,7 @@ var _Col = _interopRequireDefault(require("./components/Col"));
663
670
  var _ConfirmationButton = _interopRequireDefault(require("./components/ConfirmationButton"));
664
671
  var _ConfirmationModal = _interopRequireDefault(require("./components/ConfirmationModal"));
665
672
  var _ContextualMenu = _interopRequireDefault(require("./components/ContextualMenu"));
673
+ var _DoughnutChart = _interopRequireDefault(require("./components/DoughnutChart"));
666
674
  var _EmptyState = _interopRequireDefault(require("./components/EmptyState"));
667
675
  var _Field = _interopRequireDefault(require("./components/Field"));
668
676
  var _Form = _interopRequireDefault(require("./components/Form"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonical/react-components",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "author": {