/
Meter.tsx
110 lines (102 loc) · 3.4 KB
/
Meter.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
import * as React from 'react';
import {type BoxProps, Box} from '@twilio-paste/box';
import {Text} from '@twilio-paste/text';
import type {HTMLPasteProps} from '@twilio-paste/types';
import {useMeter} from '@twilio-paste/react-spectrum-library';
import {LABEL_SUFFIX} from './constants';
export interface MeterProps extends HTMLPasteProps<'meter'>, Pick<BoxProps, 'element'> {
minValue?: number;
maxValue?: number;
minLabel?: string;
maxLabel?: string;
value?: number;
id: string;
'aria-label'?: string;
'aria-describedby'?: string;
'aria-labelledby'?: string;
}
const Meter = React.forwardRef<HTMLMeterElement, MeterProps>(
({element = 'METER', id, minLabel, maxLabel, ...props}, ref) => {
const {value = 0, minValue = 0, maxValue = 100} = props;
const {meterProps} = useMeter(props);
// Calculate the width of the bar as a percentage
const percentage = (value - minValue) / (maxValue - minValue);
const fillWidth = `${Math.round(percentage * 100)}%`;
/*
* Since Meter isn't a form element, we cannot use htmlFor from the regular Label
* so we created a MeterLabel component that behaves like a regular form Label
* but leverages aria-labelledby instead of htmlFor under the hood.
* `aria-labelledby` and `aria-label` can still be passed for custom labelling options.
*/
let labelledBy = props['aria-labelledby'];
if (labelledBy == null && props['aria-label'] == null && id != null) {
labelledBy = `${id}${LABEL_SUFFIX}`;
}
return (
<Box
as="div"
{...meterProps}
role="meter"
id={id}
ref={ref}
width="100%"
position="relative"
element={element}
aria-labelledby={labelledBy}
>
<Box
height="10px"
backgroundColor="colorBackgroundStrong"
borderRadius="borderRadiusPill"
element={`${element}_BAR`}
>
<Box
width={fillWidth}
height="10px"
backgroundColor="colorBackgroundPrimaryStronger"
borderTopLeftRadius="borderRadiusPill"
borderBottomLeftRadius="borderRadiusPill"
borderTopRightRadius={fillWidth === '100%' ? 'borderRadiusPill' : 'borderRadius10'}
borderBottomRightRadius={fillWidth === '100%' ? 'borderRadiusPill' : 'borderRadius10'}
element={`${element}_FILL`}
/>
</Box>
{(minLabel || maxLabel) && (
<Box
display="flex"
flexDirection="row"
justifyContent="space-between"
columnGap="space20"
marginTop="space20"
aria-hidden="true"
overflowWrap="anywhere"
element={`${element}_MIN_MAX_WRAPPER`}
>
{minLabel ? (
<Text as="span" color="colorTextWeak" fontWeight="fontWeightNormal" element={`${element}_MIN`}>
{minLabel}
</Text>
) : (
<span />
)}
{maxLabel ? (
<Text
as="span"
color="colorTextWeak"
fontWeight="fontWeightNormal"
textAlign="end"
element={`${element}_MAX`}
>
{maxLabel}
</Text>
) : (
<span />
)}
</Box>
)}
</Box>
);
}
);
Meter.displayName = 'Meter';
export {Meter};