about summary refs log tree commit diff
path: root/app/javascript/flavours/glitch/components/short_number.js
blob: e4ba09634c84b935aba1f066d932bd03fc4dffcc (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import React from 'react';
import PropTypes from 'prop-types';
import { toShortNumber, pluralReady, DECIMAL_UNITS } from '../util/numbers';
import { FormattedMessage, FormattedNumber } from 'react-intl';
// @ts-check

/**
 * @callback ShortNumberRenderer
 * @param {JSX.Element} displayNumber Number to display
 * @param {number} pluralReady Number used for pluralization
 * @returns {JSX.Element} Final render of number
 */

/**
 * @typedef {object} ShortNumberProps
 * @property {number} value Number to display in short variant
 * @property {ShortNumberRenderer} [renderer]
 * Custom renderer for numbers, provided as a prop. If another renderer
 * passed as a child of this component, this prop won't be used.
 * @property {ShortNumberRenderer} [children]
 * Custom renderer for numbers, provided as a child. If another renderer
 * passed as a prop of this component, this one will be used instead.
 */

/**
 * Component that renders short big number to a shorter version
 *
 * @param {ShortNumberProps} param0 Props for the component
 * @returns {JSX.Element} Rendered number
 */
function ShortNumber({ value, renderer, children }) {
  const shortNumber = toShortNumber(value);
  const [, division] = shortNumber;

  // eslint-disable-next-line eqeqeq
  if (children != null && renderer != null) {
    console.warn('Both renderer prop and renderer as a child provided. This is a mistake and you really should fix that. Only renderer passed as a child will be used.');
  }

  // eslint-disable-next-line eqeqeq
  const customRenderer = children != null ? children : renderer;

  const displayNumber = <ShortNumberCounter value={shortNumber} />;

  // eslint-disable-next-line eqeqeq
  return customRenderer != null
    ? customRenderer(displayNumber, pluralReady(value, division))
    : displayNumber;
}

ShortNumber.propTypes = {
  value: PropTypes.number.isRequired,
  renderer: PropTypes.func,
  children: PropTypes.func,
};

/**
 * @typedef {object} ShortNumberCounterProps
 * @property {import('../util/number').ShortNumber} value Short number
 */

/**
 * Renders short number into corresponding localizable react fragment
 *
 * @param {ShortNumberCounterProps} param0 Props for the component
 * @returns {JSX.Element} FormattedMessage ready to be embedded in code
 */
function ShortNumberCounter({ value }) {
  const [rawNumber, unit, maxFractionDigits = 0] = value;

  const count = (
    <FormattedNumber
      value={rawNumber}
      maximumFractionDigits={maxFractionDigits}
    />
  );

  let values = { count, rawNumber };

  switch (unit) {
  case DECIMAL_UNITS.THOUSAND: {
    return (
      <FormattedMessage
        id='units.short.thousand'
        defaultMessage='{count}K'
        values={values}
      />
    );
  }
  case DECIMAL_UNITS.MILLION: {
    return (
      <FormattedMessage
        id='units.short.million'
        defaultMessage='{count}M'
        values={values}
      />
    );
  }
  case DECIMAL_UNITS.BILLION: {
    return (
      <FormattedMessage
        id='units.short.billion'
        defaultMessage='{count}B'
        values={values}
      />
    );
  }
  // Not sure if we should go farther - @Sasha-Sorokin
  default: return count;
  }
}

ShortNumberCounter.propTypes = {
  value: PropTypes.arrayOf(PropTypes.number),
};

export default React.memo(ShortNumber);