-
-
Notifications
You must be signed in to change notification settings - Fork 5.2k
/
NumberField.tsx
127 lines (115 loc) · 3.81 KB
/
NumberField.tsx
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
118
119
120
121
122
123
124
125
126
127
import * as React from 'react';
import PropTypes from 'prop-types';
import get from 'lodash/get';
import Typography, { TypographyProps } from '@mui/material/Typography';
import { useRecordContext, useTranslate } from 'ra-core';
import { sanitizeFieldRestProps } from './sanitizeFieldRestProps';
import { FieldProps, fieldPropTypes } from './types';
import { genericMemo } from './genericMemo';
/**
* Display a numeric value as a locale string.
*
* Uses Intl.NumberFormat() if available, passing the locales and options props as arguments.
* If Intl is not available, it outputs number as is (and ignores the locales and options props).
*
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString
* @example
* <NumberField source="score" />
* // renders the record { id: 1234, score: 567 } as
* <span>567</span>
*
* <NumberField source="score" className="red" />
* // renders the record { id: 1234, score: 567 } as
* <span class="red">567</span>
*
* <NumberField source="share" options={{ style: 'percent' }} />
* // renders the record { id: 1234, share: 0.2545 } as
* <span>25%</span>
*
* <NumberField source="price" options={{ style: 'currency', currency: 'USD' }} />
* // renders the record { id: 1234, price: 25.99 } as
* <span>$25.99</span>
*
* <NumberField source="price" locales="fr-FR" options={{ style: 'currency', currency: 'USD' }} />
* // renders the record { id: 1234, price: 25.99 } as
* <span>25,99 $US</span>
*/
const NumberFieldImpl = <
RecordType extends Record<string, any> = Record<string, any>
>(
props: NumberFieldProps<RecordType>
) => {
const {
className,
emptyText,
source,
locales,
options,
textAlign,
transform = defaultTransform,
...rest
} = props;
const record = useRecordContext<RecordType>(props);
const translate = useTranslate();
if (!record) {
return null;
}
let value: any = get(record, source);
if (value == null) {
return emptyText ? (
<Typography
component="span"
variant="body2"
className={className}
{...sanitizeFieldRestProps(rest)}
>
{emptyText && translate(emptyText, { _: emptyText })}
</Typography>
) : null;
}
if (transform) {
value = transform(value);
}
return (
<Typography
variant="body2"
component="span"
className={className}
{...sanitizeFieldRestProps(rest)}
>
{hasNumberFormat && typeof value === 'number'
? value.toLocaleString(locales, options)
: value}
</Typography>
);
};
const defaultTransform = value =>
value && typeof value === 'string' && !isNaN(value as any) ? +value : value;
NumberFieldImpl.propTypes = {
// @ts-ignore
...Typography.propTypes,
...fieldPropTypes,
locales: PropTypes.oneOfType([
PropTypes.string,
PropTypes.arrayOf(PropTypes.string),
]),
options: PropTypes.object,
};
// what? TypeScript loses the displayName if we don't set it explicitly
NumberFieldImpl.displayName = 'NumberFieldImpl';
export const NumberField = genericMemo(NumberFieldImpl);
// @ts-expect-error This is a hack that replaces react support for defaultProps. We currently need this for the Datagrid.
NumberField.textAlign = 'right';
export interface NumberFieldProps<
RecordType extends Record<string, any> = Record<string, any>
> extends FieldProps<RecordType>,
Omit<TypographyProps, 'textAlign'> {
locales?: string | string[];
options?: object;
transform?: (value: any) => number;
}
const hasNumberFormat = !!(
typeof Intl === 'object' &&
Intl &&
typeof Intl.NumberFormat === 'function'
);