UNPKG

@awsui/components-react

Version:

AWS UI is a collection of [React](https://reactjs.org/) components that help create intuitive, responsive, and accessible user experiences for web applications. It is developed by Amazon Web Services (AWS). This work is available under the terms of the [A

171 lines (170 loc) • 11.3 kB
import React, { useCallback, useMemo, useRef, useState } from 'react'; import clsx from 'clsx'; import { pie } from 'd3-shape'; import { KeyCode } from '../internal/keycode'; import { nodeContains } from '../internal/utils/dom'; import { fireNonCancelableEvent } from '../internal/events'; import { useUniqueId } from '../internal/hooks/use-unique-id'; import ChartPopover from '../internal/components/chart-popover'; import SeriesDetails from '../internal/components/chart-series-details'; import ChartStatusContainer, { getChartStatus } from '../internal/components/chart-status-container'; import Box from '../box'; import Labels from './labels'; import styles from './styles.css.js'; import { defaultDetails, dimensionsBySize } from './utils'; import Segments from './segments'; export default (function (_a) { var _b; var _c; var _d = _a.variant, variant = _d === void 0 ? 'pie' : _d, _e = _a.size, size = _e === void 0 ? 'medium' : _e, _f = _a.i18nStrings, i18nStrings = _f === void 0 ? {} : _f, ariaLabel = _a.ariaLabel, ariaLabelledby = _a.ariaLabelledby, data = _a.data, visibleData = _a.visibleData, ariaDescription = _a.ariaDescription, innerMetricValue = _a.innerMetricValue, innerMetricDescription = _a.innerMetricDescription, _g = _a.hideTitles, hideTitles = _g === void 0 ? false : _g, _h = _a.hideDescriptions, hideDescriptions = _h === void 0 ? false : _h, detailPopoverContent = _a.detailPopoverContent, detailPopoverSize = _a.detailPopoverSize, width = _a.width, additionalFilters = _a.additionalFilters, hideFilter = _a.hideFilter, hideLegend = _a.hideLegend, _j = _a.statusType, statusType = _j === void 0 ? 'finished' : _j, empty = _a.empty, noMatch = _a.noMatch, errorText = _a.errorText, recoveryText = _a.recoveryText, loadingText = _a.loadingText, onRecoveryClick = _a.onRecoveryClick, segmentDescription = _a.segmentDescription, onHighlightChange = _a.onHighlightChange, highlightedSegment = _a.highlightedSegment, setHighlightedSegment = _a.setHighlightedSegment, pinnedSegment = _a.pinnedSegment, setPinnedSegment = _a.setPinnedSegment; var dimensions = dimensionsBySize[size]; var radius = dimensions.outerRadius; var hasLabels = !(hideTitles && hideDescriptions); var height = 2 * (radius + dimensions.padding + (hasLabels ? dimensions.paddingLabels : 0)); var hasInnerContent = variant === 'donut' && (innerMetricValue || (innerMetricDescription && size !== 'small')); var descriptionId = useUniqueId('awsui-pie-chart__description'); var innerMetricId = useUniqueId('awsui-pie-chart__inner'); var ariaDescribedBy = [ariaDescription ? descriptionId : null, hasInnerContent ? innerMetricId : null] .filter(function (id) { return !!id; }) .join(' '); var _k = useState(false), isTooltipOpen = _k[0], setTooltipOpen = _k[1]; var _l = useState(), tooltipData = _l[0], setTooltipData = _l[1]; var _m = useMemo(function () { var dataSum = visibleData.reduce(function (sum, d) { return sum + d.datum.value; }, 0); var pieFactory = pie() .value(function (d) { return (d.datum.value < dataSum / 100 ? dataSum / 100 : d.datum.value); }) .sort(null); var pieData = pieFactory(visibleData.filter(function (d) { return d.datum.value > 0; })); return { pieData: pieData, dataSum: dataSum }; }, [visibleData]), pieData = _m.pieData, dataSum = _m.dataSum; var detailFunction = detailPopoverContent || defaultDetails(i18nStrings); var tooltipContent = tooltipData && React.createElement(SeriesDetails, { details: detailFunction(tooltipData.datum, dataSum) }); var containerRef = useRef(null); var segmentsRef = useRef(null); var focusOutlineRef = useRef(null); var _o = getChartStatus({ externalData: data, visibleData: pieData, statusType: statusType }), isEmpty = _o.isEmpty, showChart = _o.showChart; var isNoMatch = isEmpty && visibleData.length !== data.length; var reserveLegendSpace = !showChart && !hideLegend; var reserveFilterSpace = statusType !== 'finished' && !isNoMatch && (!hideFilter || additionalFilters); var popoverDismissedRecently = useRef(false); var highlightSegment = useCallback(function (internalDatum, trigger) { var segment = internalDatum.datum; if (segment !== highlightedSegment) { setHighlightedSegment(segment); fireNonCancelableEvent(onHighlightChange, { highlightedSegment: segment }); } if (trigger) { setTooltipData({ datum: internalDatum.datum, series: { color: internalDatum.color, index: internalDatum.index, label: internalDatum.datum.title, markerType: 'rectangle' }, trigger: trigger }); setTooltipOpen(true); } }, [highlightedSegment, setTooltipOpen, setHighlightedSegment, onHighlightChange]); var clearHighlightedSegment = useCallback(function () { setTooltipOpen(false); setHighlightedSegment(null); fireNonCancelableEvent(onHighlightChange, { highlightedSegment: null }); }, [setHighlightedSegment, onHighlightChange, setTooltipOpen]); var onClick = useCallback(function (internalDatum, triggerRef) { if (pinnedSegment === internalDatum.datum) { setPinnedSegment(null); clearHighlightedSegment(); } else { setPinnedSegment(internalDatum.datum); highlightSegment(internalDatum, triggerRef); } }, [pinnedSegment, clearHighlightedSegment, setPinnedSegment, highlightSegment]); var onMouseOver = useCallback(function (internalDatum, triggerRef) { if (pinnedSegment !== null) { return; } highlightSegment(internalDatum, triggerRef); }, [pinnedSegment, highlightSegment]); var onMouseOut = useCallback(function () { if (pinnedSegment !== null) { return; } clearHighlightedSegment(); }, [pinnedSegment, clearHighlightedSegment]); var onKeyDown = useCallback(function (index, triggerRef, event) { if (event.keyCode !== KeyCode.right && event.keyCode !== KeyCode.left && event.keyCode !== KeyCode.enter) { return; } event.preventDefault(); var nextIndex = index; var MAX = pieData.length - 1; if (event.keyCode === KeyCode.right) { nextIndex++; if (nextIndex > MAX) { nextIndex = 0; } } else if (event.keyCode === KeyCode.left) { nextIndex--; if (nextIndex < 0) { nextIndex = MAX; } } if (event.keyCode === KeyCode.enter) { setPinnedSegment(pieData[nextIndex].data.datum); } highlightSegment(pieData[nextIndex].data, triggerRef); }, [setPinnedSegment, highlightSegment]); var onFocus = useCallback(function (internalDatum, triggerRef) { if (pinnedSegment !== null || popoverDismissedRecently.current) { return; } highlightSegment(internalDatum, triggerRef); }, [pinnedSegment, highlightSegment]); var onBlur = useCallback(function (event) { var blurTarget = event.relatedTarget || event.target; if (blurTarget === null || !(blurTarget instanceof Element) || !nodeContains(containerRef.current, blurTarget)) { clearHighlightedSegment(); setPinnedSegment(null); } }, [clearHighlightedSegment, setPinnedSegment]); var onPopoverDismiss = function () { setTooltipOpen(false); setPinnedSegment(null); var target = tooltipData === null || tooltipData === void 0 ? void 0 : tooltipData.trigger.parentNode; if (target) { setTimeout(function () { popoverDismissedRecently.current = true; if (target.focus) { target.focus(); } else { window.HTMLElement.prototype.focus.call(target); } popoverDismissedRecently.current = false; }, 0); } }; return (React.createElement("div", { className: clsx(styles.content, styles["content--" + size], (_b = {}, _b[styles['content--reserve-filter']] = reserveFilterSpace, _b[styles['content--reserve-legend']] = reserveLegendSpace, _b)) }, React.createElement(ChartStatusContainer, { isEmpty: isEmpty, isNoMatch: isNoMatch, showChart: showChart, statusType: statusType, empty: empty, noMatch: noMatch, loadingText: loadingText, errorText: errorText, recoveryText: recoveryText, onRecoveryClick: onRecoveryClick }), showChart && (React.createElement("div", { className: styles['chart-container'], ref: containerRef, onBlur: onBlur }, React.createElement("svg", { width: width, height: height, className: styles.pie, role: "graphics-document", "aria-hidden": "false", "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, "aria-describedby": ariaDescribedBy || undefined, "aria-roledescription": i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.chartAriaRoleDescription, focusable: "false" }, ariaDescription && React.createElement("title", { id: descriptionId }, ariaDescription), React.createElement("g", { transform: "translate(" + width / 2 + " " + height / 2 + ")", role: "group", ref: segmentsRef }, React.createElement(Segments, { pieData: pieData, size: size, variant: variant, containerRef: containerRef, focusOutlineRef: focusOutlineRef, highlightedSegment: highlightedSegment, segmentAriaRoleDescription: i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.segmentAriaRoleDescription, onClick: onClick, onMouseOver: onMouseOver, onMouseOut: onMouseOut, onKeyDown: onKeyDown, onFocus: onFocus }), hasLabels && (React.createElement(Labels, { pieData: pieData, size: size, segmentDescription: segmentDescription, visibleDataSum: dataSum, hideTitles: hideTitles, hideDescriptions: hideDescriptions, highlightedSegment: highlightedSegment })))), hasInnerContent && (React.createElement("div", { className: styles['inner-content'], id: innerMetricId }, innerMetricValue && (React.createElement(Box, { fontSize: size === 'small' ? 'heading-m' : 'heading-xl' }, innerMetricValue)), innerMetricDescription && size !== 'small' && (React.createElement(Box, { color: "text-body-secondary", fontSize: "heading-m" }, innerMetricDescription)))), isTooltipOpen && (React.createElement(ChartPopover, { series: tooltipData === null || tooltipData === void 0 ? void 0 : tooltipData.series, trigger: (_c = tooltipData === null || tooltipData === void 0 ? void 0 : tooltipData.trigger) !== null && _c !== void 0 ? _c : null, dismissButton: pinnedSegment !== null, dismissAriaLabel: i18nStrings.detailPopoverDismissAriaLabel, onDismiss: onPopoverDismiss, segmentsRef: segmentsRef.current, size: detailPopoverSize }, tooltipContent)))))); });