jfs-components 0.0.69 → 0.0.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/lib/commonjs/components/CardAdvisory/CardAdvisory.js +203 -0
- package/lib/commonjs/components/CardCTA/CardCTA.js +198 -16
- package/lib/commonjs/components/CircularProgressBar/CircularProgressBar.js +147 -0
- package/lib/commonjs/components/CircularProgressBarDoted/CircularProgressBarDoted.js +258 -0
- package/lib/commonjs/components/CircularRating/CircularRating.js +161 -0
- package/lib/commonjs/components/Gauge/Gauge.js +223 -0
- package/lib/commonjs/components/ListGroup/ListGroup.js +3 -1
- package/lib/commonjs/components/MediaCard/GlassFill.js +62 -0
- package/lib/commonjs/components/MediaCard/GlassFill.web.js +48 -0
- package/lib/commonjs/components/MediaCard/MediaCard.js +28 -31
- package/lib/commonjs/components/Nudge/Nudge.js +179 -87
- package/lib/commonjs/components/index.js +35 -0
- package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/module/components/CardAdvisory/CardAdvisory.js +197 -0
- package/lib/module/components/CardCTA/CardCTA.js +199 -17
- package/lib/module/components/CircularProgressBar/CircularProgressBar.js +141 -0
- package/lib/module/components/CircularProgressBarDoted/CircularProgressBarDoted.js +253 -0
- package/lib/module/components/CircularRating/CircularRating.js +155 -0
- package/lib/module/components/Gauge/Gauge.js +217 -0
- package/lib/module/components/ListGroup/ListGroup.js +3 -1
- package/lib/module/components/MediaCard/GlassFill.js +57 -0
- package/lib/module/components/MediaCard/GlassFill.web.js +43 -0
- package/lib/module/components/MediaCard/MediaCard.js +29 -32
- package/lib/module/components/Nudge/Nudge.js +178 -87
- package/lib/module/components/index.js +5 -0
- package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/module/icons/registry.js +1 -1
- package/lib/typescript/src/components/CardAdvisory/CardAdvisory.d.ts +49 -0
- package/lib/typescript/src/components/CardCTA/CardCTA.d.ts +16 -1
- package/lib/typescript/src/components/CircularProgressBar/CircularProgressBar.d.ts +27 -0
- package/lib/typescript/src/components/CircularProgressBarDoted/CircularProgressBarDoted.d.ts +48 -0
- package/lib/typescript/src/components/CircularRating/CircularRating.d.ts +49 -0
- package/lib/typescript/src/components/Gauge/Gauge.d.ts +53 -0
- package/lib/typescript/src/components/MediaCard/GlassFill.d.ts +47 -0
- package/lib/typescript/src/components/MediaCard/GlassFill.web.d.ts +20 -0
- package/lib/typescript/src/components/MediaCard/MediaCard.d.ts +17 -13
- package/lib/typescript/src/components/Nudge/Nudge.d.ts +14 -11
- package/lib/typescript/src/components/index.d.ts +6 -1
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +3 -2
- package/src/components/CardAdvisory/CardAdvisory.tsx +283 -0
- package/src/components/CardCTA/CardCTA.tsx +236 -13
- package/src/components/CircularProgressBar/CircularProgressBar.tsx +190 -0
- package/src/components/CircularProgressBarDoted/CircularProgressBarDoted.tsx +357 -0
- package/src/components/CircularRating/CircularRating.tsx +241 -0
- package/src/components/Gauge/Gauge.tsx +303 -0
- package/src/components/ListGroup/ListGroup.tsx +3 -1
- package/src/components/MediaCard/GlassFill.tsx +89 -0
- package/src/components/MediaCard/GlassFill.web.tsx +53 -0
- package/src/components/MediaCard/MediaCard.tsx +29 -48
- package/src/components/Nudge/Nudge.tsx +222 -82
- package/src/components/index.ts +6 -1
- package/src/design-tokens/Coin Variables-variables-full.json +1 -1
- package/src/icons/registry.ts +1 -1
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { StyleSheet, Text, View, type StyleProp, type TextStyle, type ViewStyle } from 'react-native'
|
|
3
|
+
import Svg, { Circle } from 'react-native-svg'
|
|
4
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
5
|
+
import { useTokens } from '../../design-tokens/JFSThemeProvider'
|
|
6
|
+
import { EMPTY_MODES } from '../../utils/react-utils'
|
|
7
|
+
import { IconMinus } from '../../icons/components/IconMinus'
|
|
8
|
+
|
|
9
|
+
type CircularProgressBarBaseProps = Omit<React.ComponentProps<typeof View>, 'children' | 'style'>
|
|
10
|
+
|
|
11
|
+
export type CircularProgressBarState = 'Active' | 'Inactive'
|
|
12
|
+
|
|
13
|
+
export type CircularProgressBarProps = CircularProgressBarBaseProps & {
|
|
14
|
+
/** Current progress value. Clamped between 0 and 100. */
|
|
15
|
+
value?: number
|
|
16
|
+
/** Active shows progress and value; inactive shows the track and minus icon. */
|
|
17
|
+
state?: CircularProgressBarState | boolean
|
|
18
|
+
/** Optional formatted value shown in the active state. */
|
|
19
|
+
valueLabel?: string
|
|
20
|
+
/** Design token modes forwarded to token lookups. */
|
|
21
|
+
modes?: Record<string, any>
|
|
22
|
+
/** Container style override. */
|
|
23
|
+
style?: StyleProp<ViewStyle>
|
|
24
|
+
/** Track stroke style override. */
|
|
25
|
+
trackStyle?: StyleProp<ViewStyle>
|
|
26
|
+
/** Progress stroke style override. */
|
|
27
|
+
progressStyle?: StyleProp<ViewStyle>
|
|
28
|
+
/** Value text style override. */
|
|
29
|
+
valueStyle?: StyleProp<TextStyle>
|
|
30
|
+
/** Accessibility label for the whole progress component. */
|
|
31
|
+
accessibilityLabel?: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const STROKE_WIDTH_RATIO = 8 / 60
|
|
35
|
+
|
|
36
|
+
const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value))
|
|
37
|
+
|
|
38
|
+
const toNumber = (value: unknown, fallback: number) => {
|
|
39
|
+
if (typeof value === 'number') {
|
|
40
|
+
return Number.isFinite(value) ? value : fallback
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (typeof value === 'string') {
|
|
44
|
+
const parsed = Number(value)
|
|
45
|
+
return Number.isFinite(parsed) ? parsed : fallback
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return fallback
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const toFontWeight = (value: unknown, fallback: TextStyle['fontWeight']) => {
|
|
52
|
+
if (typeof value === 'number') {
|
|
53
|
+
return String(value) as TextStyle['fontWeight']
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (typeof value === 'string') {
|
|
57
|
+
return value as TextStyle['fontWeight']
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return fallback
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const getStrokeColor = (style: StyleProp<ViewStyle>, fallback: string) => {
|
|
64
|
+
const flattened = StyleSheet.flatten(style)
|
|
65
|
+
return typeof flattened.backgroundColor === 'string'
|
|
66
|
+
? flattened.backgroundColor
|
|
67
|
+
: fallback
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function CircularProgressBar({
|
|
71
|
+
value = 70,
|
|
72
|
+
state = 'Inactive',
|
|
73
|
+
valueLabel,
|
|
74
|
+
modes: propModes = EMPTY_MODES,
|
|
75
|
+
style,
|
|
76
|
+
trackStyle,
|
|
77
|
+
progressStyle,
|
|
78
|
+
valueStyle,
|
|
79
|
+
accessibilityLabel,
|
|
80
|
+
...rest
|
|
81
|
+
}: CircularProgressBarProps) {
|
|
82
|
+
const { modes: globalModes } = useTokens()
|
|
83
|
+
const modes = { ...globalModes, ...propModes }
|
|
84
|
+
|
|
85
|
+
const isActive = state === true || state === 'Active'
|
|
86
|
+
const normalizedValue = clamp(value, 0, 100)
|
|
87
|
+
const size = toNumber(getVariableByName('circularProgressBar/size', modes), 60)
|
|
88
|
+
const strokeWidth = Math.max(1, size * STROKE_WIDTH_RATIO)
|
|
89
|
+
const radius = Math.max(0, (size - strokeWidth) / 2)
|
|
90
|
+
const center = size / 2
|
|
91
|
+
const circumference = 2 * Math.PI * radius
|
|
92
|
+
|
|
93
|
+
const trackColor = getStrokeColor(
|
|
94
|
+
trackStyle,
|
|
95
|
+
getVariableByName('circularProgressBar/track/color', modes) as string || '#ebebed'
|
|
96
|
+
)
|
|
97
|
+
const progressColor = getStrokeColor(
|
|
98
|
+
progressStyle,
|
|
99
|
+
getVariableByName('circularProgressBar/progress/color', modes) as string || '#25ab21'
|
|
100
|
+
)
|
|
101
|
+
const iconColor = getVariableByName('circularProgressBar/icon/color', modes) as string || '#666666'
|
|
102
|
+
const iconSize = toNumber(getVariableByName('circularProgressBar/icon/size', modes), 24)
|
|
103
|
+
|
|
104
|
+
const foreground = getVariableByName('circularProgressBar/foreground', modes) as string || '#0d0d0f'
|
|
105
|
+
const fontSize = toNumber(getVariableByName('circularProgressBar/fontSize', modes), 18)
|
|
106
|
+
const fontFamily = getVariableByName('circularProgressBar/fontFamily', modes) as string || 'JioType Var'
|
|
107
|
+
const lineHeight = toNumber(getVariableByName('circularProgressBar/lineHeight', modes), 21)
|
|
108
|
+
const fontWeight = toFontWeight(getVariableByName('circularProgressBar/fontWeight', modes), '700')
|
|
109
|
+
|
|
110
|
+
const computedContainerStyle: ViewStyle = {
|
|
111
|
+
alignItems: 'center',
|
|
112
|
+
height: size,
|
|
113
|
+
justifyContent: 'center',
|
|
114
|
+
position: 'relative',
|
|
115
|
+
width: size,
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const computedValueStyle: TextStyle = {
|
|
119
|
+
color: foreground,
|
|
120
|
+
fontFamily,
|
|
121
|
+
fontSize,
|
|
122
|
+
fontWeight,
|
|
123
|
+
lineHeight,
|
|
124
|
+
position: 'absolute',
|
|
125
|
+
textAlign: 'center',
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const iconStyle: ViewStyle = {
|
|
129
|
+
left: (size - iconSize) / 2,
|
|
130
|
+
position: 'absolute',
|
|
131
|
+
top: (size - iconSize) / 2,
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const displayValue = valueLabel ?? String(Math.round(normalizedValue))
|
|
135
|
+
const defaultAccessibilityLabel =
|
|
136
|
+
accessibilityLabel ?? (isActive ? `${displayValue} out of 100` : 'Inactive progress')
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<View
|
|
140
|
+
accessibilityRole="progressbar"
|
|
141
|
+
accessibilityLabel={defaultAccessibilityLabel}
|
|
142
|
+
accessibilityValue={{ min: 0, max: 100, now: normalizedValue }}
|
|
143
|
+
style={[computedContainerStyle, style]}
|
|
144
|
+
{...rest}
|
|
145
|
+
>
|
|
146
|
+
<Svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
|
|
147
|
+
<Circle
|
|
148
|
+
cx={center}
|
|
149
|
+
cy={center}
|
|
150
|
+
r={radius}
|
|
151
|
+
stroke={trackColor}
|
|
152
|
+
strokeWidth={strokeWidth}
|
|
153
|
+
fill="none"
|
|
154
|
+
/>
|
|
155
|
+
{isActive ? (
|
|
156
|
+
<Circle
|
|
157
|
+
cx={center}
|
|
158
|
+
cy={center}
|
|
159
|
+
r={radius}
|
|
160
|
+
stroke={progressColor}
|
|
161
|
+
strokeWidth={strokeWidth}
|
|
162
|
+
strokeLinecap="round"
|
|
163
|
+
fill="none"
|
|
164
|
+
strokeDasharray={`${circumference} ${circumference}`}
|
|
165
|
+
strokeDashoffset={circumference * (1 - normalizedValue / 100)}
|
|
166
|
+
rotation="-90"
|
|
167
|
+
originX={center}
|
|
168
|
+
originY={center}
|
|
169
|
+
/>
|
|
170
|
+
) : null}
|
|
171
|
+
</Svg>
|
|
172
|
+
|
|
173
|
+
{isActive ? (
|
|
174
|
+
<Text style={[computedValueStyle, valueStyle]}>
|
|
175
|
+
{displayValue}
|
|
176
|
+
</Text>
|
|
177
|
+
) : (
|
|
178
|
+
<IconMinus
|
|
179
|
+
width={iconSize}
|
|
180
|
+
height={iconSize}
|
|
181
|
+
fill={iconColor}
|
|
182
|
+
color={iconColor}
|
|
183
|
+
style={iconStyle}
|
|
184
|
+
/>
|
|
185
|
+
)}
|
|
186
|
+
</View>
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export default CircularProgressBar
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import React, { useMemo, useState } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
type LayoutChangeEvent,
|
|
4
|
+
Pressable,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
Text,
|
|
7
|
+
View,
|
|
8
|
+
type StyleProp,
|
|
9
|
+
type TextStyle,
|
|
10
|
+
type ViewStyle,
|
|
11
|
+
} from 'react-native'
|
|
12
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
13
|
+
import { useTokens } from '../../design-tokens/JFSThemeProvider'
|
|
14
|
+
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
|
|
15
|
+
import { IconChevronright } from '../../icons/components/IconChevronright'
|
|
16
|
+
|
|
17
|
+
type CircularProgressBarDotedBaseProps = Omit<React.ComponentProps<typeof View>, 'children' | 'style'>
|
|
18
|
+
|
|
19
|
+
export type CircularProgressBarDotedProps = CircularProgressBarDotedBaseProps & {
|
|
20
|
+
/** Progress value. Clamped between 0 and 100. */
|
|
21
|
+
value?: number
|
|
22
|
+
/** Number of dots in the ring. */
|
|
23
|
+
dotCount?: number
|
|
24
|
+
/** Small label above the score. */
|
|
25
|
+
label?: string
|
|
26
|
+
/** Text below the score. */
|
|
27
|
+
tierLabel?: string
|
|
28
|
+
/** Show or hide the chevron after the tier label. */
|
|
29
|
+
showChevron?: boolean
|
|
30
|
+
/** Called when the component is pressed. */
|
|
31
|
+
onPress?: () => void
|
|
32
|
+
/** Called when the tier row is pressed. */
|
|
33
|
+
onTierPress?: () => void
|
|
34
|
+
/** Design token modes forwarded to token lookups and slot children. */
|
|
35
|
+
modes?: Record<string, any>
|
|
36
|
+
/** Slot rendered in the center of the dotted ring. Receives `modes` recursively. */
|
|
37
|
+
children?: React.ReactNode
|
|
38
|
+
/** Container style override. */
|
|
39
|
+
style?: StyleProp<ViewStyle>
|
|
40
|
+
/** Ring wrapper style override. */
|
|
41
|
+
ringStyle?: StyleProp<ViewStyle>
|
|
42
|
+
/** Track dot style override. */
|
|
43
|
+
trackDotStyle?: StyleProp<ViewStyle>
|
|
44
|
+
/** Progress dot style override. */
|
|
45
|
+
progressDotStyle?: StyleProp<ViewStyle>
|
|
46
|
+
/** Center content style override. */
|
|
47
|
+
contentStyle?: StyleProp<ViewStyle>
|
|
48
|
+
/** Score tier stack style override. */
|
|
49
|
+
scoreTierStyle?: StyleProp<ViewStyle>
|
|
50
|
+
/** Score trend row style override. */
|
|
51
|
+
scoreTrendStyle?: StyleProp<ViewStyle>
|
|
52
|
+
/** Label text style override. */
|
|
53
|
+
labelStyle?: StyleProp<TextStyle>
|
|
54
|
+
/** Score text style override. */
|
|
55
|
+
scoreLabelStyle?: StyleProp<TextStyle>
|
|
56
|
+
/** Tier text style override. */
|
|
57
|
+
tierLabelStyle?: StyleProp<TextStyle>
|
|
58
|
+
/** Accessibility label for the whole progress component. */
|
|
59
|
+
accessibilityLabel?: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const DEFAULT_DOT_COUNT = 24
|
|
63
|
+
const START_ANGLE_DEGREES = -90
|
|
64
|
+
|
|
65
|
+
const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value))
|
|
66
|
+
|
|
67
|
+
const toNumber = (value: unknown, fallback: number) => {
|
|
68
|
+
if (typeof value === 'number') {
|
|
69
|
+
return Number.isFinite(value) ? value : fallback
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (typeof value === 'string') {
|
|
73
|
+
const parsed = Number(value)
|
|
74
|
+
return Number.isFinite(parsed) ? parsed : fallback
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return fallback
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const toFontWeight = (value: unknown, fallback: TextStyle['fontWeight']) => {
|
|
81
|
+
if (typeof value === 'number') {
|
|
82
|
+
return String(value) as TextStyle['fontWeight']
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (typeof value === 'string') {
|
|
86
|
+
return value as TextStyle['fontWeight']
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return fallback
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const getBackgroundColor = (style: StyleProp<ViewStyle>, fallback: string) => {
|
|
93
|
+
const flattened = StyleSheet.flatten(style)
|
|
94
|
+
return typeof flattened.backgroundColor === 'string'
|
|
95
|
+
? flattened.backgroundColor
|
|
96
|
+
: fallback
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function CircularProgressBarDoted({
|
|
100
|
+
value = 72,
|
|
101
|
+
dotCount = DEFAULT_DOT_COUNT,
|
|
102
|
+
label = 'Rating',
|
|
103
|
+
tierLabel = 'Doing great',
|
|
104
|
+
showChevron = true,
|
|
105
|
+
onPress,
|
|
106
|
+
onTierPress,
|
|
107
|
+
modes: propModes = EMPTY_MODES,
|
|
108
|
+
children,
|
|
109
|
+
style,
|
|
110
|
+
ringStyle,
|
|
111
|
+
trackDotStyle,
|
|
112
|
+
progressDotStyle,
|
|
113
|
+
contentStyle,
|
|
114
|
+
scoreTierStyle,
|
|
115
|
+
scoreTrendStyle,
|
|
116
|
+
labelStyle,
|
|
117
|
+
scoreLabelStyle,
|
|
118
|
+
tierLabelStyle,
|
|
119
|
+
accessibilityLabel,
|
|
120
|
+
onLayout,
|
|
121
|
+
...rest
|
|
122
|
+
}: CircularProgressBarDotedProps) {
|
|
123
|
+
const { modes: globalModes } = useTokens()
|
|
124
|
+
const modes = { ...globalModes, ...propModes }
|
|
125
|
+
const [layoutSize, setLayoutSize] = useState(0)
|
|
126
|
+
|
|
127
|
+
const normalizedValue = clamp(value, 0, 100)
|
|
128
|
+
const resolvedDotCount = Math.max(1, Math.floor(dotCount))
|
|
129
|
+
const activeDots = normalizedValue <= 0
|
|
130
|
+
? 0
|
|
131
|
+
: Math.ceil((normalizedValue / 100) * resolvedDotCount)
|
|
132
|
+
|
|
133
|
+
const dotShadowSize = toNumber(getVariableByName('circularProgressBarDoted/dot/shadow/size', modes), 6)
|
|
134
|
+
const baseDotSize = toNumber(getVariableByName('circularProgressBarDoted/dot/size', modes), 6)
|
|
135
|
+
const dotSize = baseDotSize + dotShadowSize
|
|
136
|
+
const outerDotSize = dotSize
|
|
137
|
+
const ringSize = layoutSize
|
|
138
|
+
const ringRadius = Math.max(0, (ringSize - outerDotSize) / 2)
|
|
139
|
+
|
|
140
|
+
const trackDotColor = getBackgroundColor(
|
|
141
|
+
trackDotStyle,
|
|
142
|
+
getVariableByName('circularProgressBarDoted/trackDot/bg', modes) as string || '#ebebed'
|
|
143
|
+
)
|
|
144
|
+
const progressDotColor = getBackgroundColor(
|
|
145
|
+
progressDotStyle,
|
|
146
|
+
getVariableByName('circularProgressBarDoted/progressDot/bg', modes) as string || '#25ab21'
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
const contentGap = toNumber(getVariableByName('circularProgressBarDoted/gap', modes), 12)
|
|
150
|
+
const scoreTierGap = toNumber(getVariableByName('circularProgressBarDoted/scoreTier/gap', modes), 6)
|
|
151
|
+
const scoreTierWidth = toNumber(getVariableByName('circularProgressBarDoted/scoreTier/width', modes), 116)
|
|
152
|
+
const scoreTrendGap = toNumber(getVariableByName('circularProgressBarDoted/scoreTrend/gap', modes), 2)
|
|
153
|
+
const scoreTrendHeight = toNumber(getVariableByName('circularProgressBarDoted/scoreTrend/height', modes), 24)
|
|
154
|
+
|
|
155
|
+
const typographyFontFamily = getVariableByName('Typography/Font Family', modes) as string || 'JioType Var'
|
|
156
|
+
const labelColor = getVariableByName('circularProgressBarDoted/label/color', modes) as string || '#080d1a'
|
|
157
|
+
const labelFontSize = toNumber(getVariableByName('Typography/Size/Body/XS', modes), 12)
|
|
158
|
+
const labelFontWeight = toFontWeight(getVariableByName('Typography/Font Weight/Body Low (Regular)', modes), '400')
|
|
159
|
+
|
|
160
|
+
const scoreColor = getVariableByName('circularProgressBarDoted/scoreLabel/color', modes) as string || '#080d1a'
|
|
161
|
+
const scoreFontSize = toNumber(getVariableByName('circularProgressBarDoted/scoreLabel/fontSize', modes), 56)
|
|
162
|
+
const scoreFontFamily = getVariableByName('circularProgressBarDoted/scoreLabel/fontFamily', modes) as string || 'JioType Var'
|
|
163
|
+
const scoreLineHeight = toNumber(getVariableByName('circularProgressBarDoted/scoreLabel/lineHeight', modes), 56)
|
|
164
|
+
const scoreFontWeight = toFontWeight(getVariableByName('circularProgressBarDoted/scoreLabel/fontWeight', modes), '900')
|
|
165
|
+
|
|
166
|
+
const tierColor = getVariableByName('circularProgressBarDoted/tierLabel/color', modes) as string || '#080d1a'
|
|
167
|
+
const tierFontSize = toNumber(getVariableByName('Typography/Size/Body/M', modes), 16)
|
|
168
|
+
const tierFontWeight = toFontWeight(getVariableByName('Typography/Font Weight/Body High', modes), '700')
|
|
169
|
+
const iconColor = getVariableByName('circularProgressBarDoted/icon/color', modes) as string || '#303338'
|
|
170
|
+
const iconSize = toNumber(getVariableByName('circularProgressBarDoted/icon/size', modes), 24)
|
|
171
|
+
|
|
172
|
+
const dots = useMemo(
|
|
173
|
+
() => Array.from({ length: resolvedDotCount }, (_, index) => {
|
|
174
|
+
const angle = ((360 / resolvedDotCount) * index + START_ANGLE_DEGREES) * (Math.PI / 180)
|
|
175
|
+
const center = ringSize / 2
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
isActive: index < activeDots,
|
|
179
|
+
left: center + ringRadius * Math.cos(angle) - outerDotSize / 2,
|
|
180
|
+
top: center + ringRadius * Math.sin(angle) - outerDotSize / 2,
|
|
181
|
+
}
|
|
182
|
+
}),
|
|
183
|
+
[activeDots, outerDotSize, resolvedDotCount, ringRadius, ringSize]
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
const containerStyle: ViewStyle = {
|
|
187
|
+
alignItems: 'center',
|
|
188
|
+
alignSelf: 'stretch',
|
|
189
|
+
height: '100%',
|
|
190
|
+
justifyContent: 'center',
|
|
191
|
+
position: 'relative',
|
|
192
|
+
width: '100%',
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const computedRingStyle: ViewStyle = {
|
|
196
|
+
height: ringSize,
|
|
197
|
+
position: 'absolute',
|
|
198
|
+
width: ringSize,
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const dotOuterStyle: ViewStyle = {
|
|
202
|
+
alignItems: 'center',
|
|
203
|
+
height: outerDotSize,
|
|
204
|
+
justifyContent: 'center',
|
|
205
|
+
position: 'absolute',
|
|
206
|
+
width: outerDotSize,
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const dotInnerStyle: ViewStyle = {
|
|
210
|
+
borderRadius: dotSize / 2,
|
|
211
|
+
height: dotSize,
|
|
212
|
+
width: dotSize,
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const computedContentStyle: ViewStyle = {
|
|
216
|
+
alignItems: 'center',
|
|
217
|
+
gap: contentGap,
|
|
218
|
+
justifyContent: 'center',
|
|
219
|
+
minWidth: scoreTierWidth,
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const computedScoreTierStyle: ViewStyle = {
|
|
223
|
+
alignItems: 'center',
|
|
224
|
+
gap: scoreTierGap,
|
|
225
|
+
justifyContent: 'center',
|
|
226
|
+
width: '100%',
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const computedScoreTrendStyle: ViewStyle = {
|
|
230
|
+
alignItems: 'center',
|
|
231
|
+
flexDirection: 'row',
|
|
232
|
+
gap: scoreTrendGap,
|
|
233
|
+
height: scoreTrendHeight,
|
|
234
|
+
justifyContent: 'center',
|
|
235
|
+
minWidth: scoreTierWidth,
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const computedLabelStyle: TextStyle = {
|
|
239
|
+
color: labelColor,
|
|
240
|
+
fontFamily: typographyFontFamily,
|
|
241
|
+
fontSize: labelFontSize,
|
|
242
|
+
fontWeight: labelFontWeight,
|
|
243
|
+
lineHeight: labelFontSize * 1.3,
|
|
244
|
+
textAlign: 'center',
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const computedScoreLabelStyle: TextStyle = {
|
|
248
|
+
color: scoreColor,
|
|
249
|
+
fontFamily: scoreFontFamily,
|
|
250
|
+
fontSize: scoreFontSize,
|
|
251
|
+
fontWeight: scoreFontWeight,
|
|
252
|
+
lineHeight: scoreLineHeight,
|
|
253
|
+
textAlign: 'center',
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const computedTierLabelStyle: TextStyle = {
|
|
257
|
+
color: tierColor,
|
|
258
|
+
fontFamily: typographyFontFamily,
|
|
259
|
+
fontSize: tierFontSize,
|
|
260
|
+
fontWeight: tierFontWeight,
|
|
261
|
+
lineHeight: tierFontSize * 1.3,
|
|
262
|
+
textAlign: 'center',
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const resolvedScoreLabel = String(Math.round(normalizedValue))
|
|
266
|
+
const defaultAccessibilityLabel =
|
|
267
|
+
accessibilityLabel ?? `${label}. ${resolvedScoreLabel} out of 100. ${tierLabel}`
|
|
268
|
+
|
|
269
|
+
const handleLayout = (event: LayoutChangeEvent) => {
|
|
270
|
+
onLayout?.(event)
|
|
271
|
+
|
|
272
|
+
const { width, height } = event.nativeEvent.layout
|
|
273
|
+
const nextSize = Math.max(0, Math.min(width, height))
|
|
274
|
+
setLayoutSize((currentSize) => currentSize === nextSize ? currentSize : nextSize)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const trendContent = (
|
|
278
|
+
<>
|
|
279
|
+
<Text numberOfLines={1} style={[computedTierLabelStyle, tierLabelStyle]}>
|
|
280
|
+
{tierLabel}
|
|
281
|
+
</Text>
|
|
282
|
+
{showChevron ? (
|
|
283
|
+
<IconChevronright
|
|
284
|
+
width={iconSize}
|
|
285
|
+
height={iconSize}
|
|
286
|
+
fill={iconColor}
|
|
287
|
+
color={iconColor}
|
|
288
|
+
/>
|
|
289
|
+
) : null}
|
|
290
|
+
</>
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
return (
|
|
294
|
+
<Pressable
|
|
295
|
+
accessibilityRole="progressbar"
|
|
296
|
+
accessibilityLabel={defaultAccessibilityLabel}
|
|
297
|
+
accessibilityValue={{ min: 0, max: 100, now: normalizedValue }}
|
|
298
|
+
disabled={!onPress}
|
|
299
|
+
onLayout={handleLayout}
|
|
300
|
+
onPress={onPress}
|
|
301
|
+
style={[containerStyle, style]}
|
|
302
|
+
{...rest}
|
|
303
|
+
>
|
|
304
|
+
<View pointerEvents="none" style={[computedRingStyle, ringStyle]}>
|
|
305
|
+
{dots.map((dot, index) => (
|
|
306
|
+
<View
|
|
307
|
+
key={index}
|
|
308
|
+
style={[
|
|
309
|
+
dotOuterStyle,
|
|
310
|
+
{ left: dot.left, top: dot.top },
|
|
311
|
+
]}
|
|
312
|
+
>
|
|
313
|
+
<View
|
|
314
|
+
style={[
|
|
315
|
+
dotInnerStyle,
|
|
316
|
+
{ backgroundColor: dot.isActive ? progressDotColor : trackDotColor },
|
|
317
|
+
dot.isActive ? progressDotStyle : trackDotStyle,
|
|
318
|
+
]}
|
|
319
|
+
/>
|
|
320
|
+
</View>
|
|
321
|
+
))}
|
|
322
|
+
</View>
|
|
323
|
+
|
|
324
|
+
<View style={[computedContentStyle, contentStyle]}>
|
|
325
|
+
{children ? (
|
|
326
|
+
cloneChildrenWithModes(children, modes)
|
|
327
|
+
) : (
|
|
328
|
+
<>
|
|
329
|
+
<View style={[computedScoreTierStyle, scoreTierStyle]}>
|
|
330
|
+
<Text style={[computedLabelStyle, labelStyle]}>
|
|
331
|
+
{label}
|
|
332
|
+
</Text>
|
|
333
|
+
<Text style={[computedScoreLabelStyle, scoreLabelStyle]}>
|
|
334
|
+
{resolvedScoreLabel}
|
|
335
|
+
</Text>
|
|
336
|
+
</View>
|
|
337
|
+
{onTierPress ? (
|
|
338
|
+
<Pressable
|
|
339
|
+
accessibilityRole="button"
|
|
340
|
+
onPress={onTierPress}
|
|
341
|
+
style={[computedScoreTrendStyle, scoreTrendStyle]}
|
|
342
|
+
>
|
|
343
|
+
{trendContent}
|
|
344
|
+
</Pressable>
|
|
345
|
+
) : (
|
|
346
|
+
<View style={[computedScoreTrendStyle, scoreTrendStyle]}>
|
|
347
|
+
{trendContent}
|
|
348
|
+
</View>
|
|
349
|
+
)}
|
|
350
|
+
</>
|
|
351
|
+
)}
|
|
352
|
+
</View>
|
|
353
|
+
</Pressable>
|
|
354
|
+
)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export default CircularProgressBarDoted
|