UNPKG

wix-style-react

Version:
161 lines (140 loc) 4.29 kB
import React from 'react'; import PropTypes from 'prop-types'; import Tooltip from '../Tooltip'; import Heading from '../Heading'; import AdaptiveHeading from '../utils/AdaptiveHeading'; import { st, classes, vars } from './BarChart.st.css'; import dataHooks from './dataHooks'; class BarChart extends React.PureComponent { static displayName = 'BarChart'; static defaultProps = { items: [], }; static propTypes = { /** Applied as data-hook HTML attribute that can be used to create driver in testing */ dataHook: PropTypes.string, /** * Array of items * * `value` - This prop is used for sorting bars. Displayed as big text on a bar, when there is no caption prop. * * `label` - Displayed as big text on a bar. * * `labelShort` - Is shown instead of a `label` when there is not enough space. * * `description` - short label under the bar. * * `descriptionInfo` - long description. */ items: PropTypes.arrayOf( PropTypes.shape({ value: PropTypes.number.isRequired, label: PropTypes.node, labelShort: PropTypes.node, description: PropTypes.node, descriptionInfo: PropTypes.node, }), ), /** Used to calculate space for bars inside a widget. Should be specified if the actual total is different from the sum of values of all items */ total: PropTypes.number, /** Callback called every time when descriptionInfo tooltip is shown*/ onDescriptionInfoShown: PropTypes.func, }; MIN_BAR_WIDTH = 50; state = { width: 0, }; componentDidMount() { this.setState({ width: this.node.offsetWidth, }); } _getCalculatedTotal() { return this.props.items.reduce((a, b) => a + b.value, 0); } _renderValue = ({ descriptionInfo, value, label, labelShort, showText }) => { const text = String(label || value); const { onDescriptionInfoShown } = this.props; const headingProps = { text, textInShort: labelShort, dataHook: dataHooks.value, appearance: 'H3', light: true, }; return descriptionInfo ? ( <Tooltip textAlign="start" dataHook={dataHooks.tooltip} content={descriptionInfo} onShow={onDescriptionInfoShown} zIndex={5999} > <div className={classes.value}> {showText && <AdaptiveHeading {...headingProps} emptyLast />} </div> </Tooltip> ) : ( <div className={classes.value}> {showText && <AdaptiveHeading {...headingProps} />} </div> ); }; _renderItem = ( { value, label, labelShort, description, descriptionInfo }, key, ) => { const { width } = this.state; const { total } = this.props; const calculatedTotal = this._getCalculatedTotal(); const coefficient = total ? calculatedTotal / total : 1; const showText = width === 0 || (value * width) / (calculatedTotal * coefficient) > this.MIN_BAR_WIDTH; return ( <div className={st(classes.item)} key={key} data-hook={dataHooks.bar} style={{ // avoid too big numbers from getting into a css [vars.barValue]: value / 10 ** (calculatedTotal.toString().length - 1), }} > {this._renderValue({ descriptionInfo, value, label, labelShort, showText, })} <div className={classes.description}> <Heading ellipsis dataHook={dataHooks.description} appearance="H5"> {showText && description} </Heading> </div> </div> ); }; render() { const { dataHook, items, total } = this.props; const calculatedTotal = this._getCalculatedTotal(); const width = total ? (calculatedTotal / total) * 100 : 100; return ( <div data-hook={dataHook} ref={elem => (this.node = elem)} className={classes.wrapper} > <div className={classes.root} style={{ width: `${width}%`, }} > {items .slice() .sort((a, b) => b.value - a.value) .map(this._renderItem)} </div> </div> ); } } export default BarChart;