jfs-components 0.0.70 → 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.
Files changed (43) hide show
  1. package/lib/commonjs/components/CardAdvisory/CardAdvisory.js +203 -0
  2. package/lib/commonjs/components/CardCTA/CardCTA.js +198 -16
  3. package/lib/commonjs/components/CircularProgressBar/CircularProgressBar.js +147 -0
  4. package/lib/commonjs/components/CircularProgressBarDoted/CircularProgressBarDoted.js +258 -0
  5. package/lib/commonjs/components/CircularRating/CircularRating.js +161 -0
  6. package/lib/commonjs/components/Gauge/Gauge.js +223 -0
  7. package/lib/commonjs/components/ListGroup/ListGroup.js +3 -1
  8. package/lib/commonjs/components/Nudge/Nudge.js +179 -87
  9. package/lib/commonjs/components/index.js +35 -0
  10. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
  11. package/lib/commonjs/icons/registry.js +1 -1
  12. package/lib/module/components/CardAdvisory/CardAdvisory.js +197 -0
  13. package/lib/module/components/CardCTA/CardCTA.js +199 -17
  14. package/lib/module/components/CircularProgressBar/CircularProgressBar.js +141 -0
  15. package/lib/module/components/CircularProgressBarDoted/CircularProgressBarDoted.js +253 -0
  16. package/lib/module/components/CircularRating/CircularRating.js +155 -0
  17. package/lib/module/components/Gauge/Gauge.js +217 -0
  18. package/lib/module/components/ListGroup/ListGroup.js +3 -1
  19. package/lib/module/components/Nudge/Nudge.js +178 -87
  20. package/lib/module/components/index.js +5 -0
  21. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
  22. package/lib/module/icons/registry.js +1 -1
  23. package/lib/typescript/src/components/CardAdvisory/CardAdvisory.d.ts +49 -0
  24. package/lib/typescript/src/components/CardCTA/CardCTA.d.ts +16 -1
  25. package/lib/typescript/src/components/CircularProgressBar/CircularProgressBar.d.ts +27 -0
  26. package/lib/typescript/src/components/CircularProgressBarDoted/CircularProgressBarDoted.d.ts +48 -0
  27. package/lib/typescript/src/components/CircularRating/CircularRating.d.ts +49 -0
  28. package/lib/typescript/src/components/Gauge/Gauge.d.ts +53 -0
  29. package/lib/typescript/src/components/Nudge/Nudge.d.ts +14 -11
  30. package/lib/typescript/src/components/index.d.ts +6 -1
  31. package/lib/typescript/src/icons/registry.d.ts +1 -1
  32. package/package.json +1 -1
  33. package/src/components/CardAdvisory/CardAdvisory.tsx +283 -0
  34. package/src/components/CardCTA/CardCTA.tsx +236 -13
  35. package/src/components/CircularProgressBar/CircularProgressBar.tsx +190 -0
  36. package/src/components/CircularProgressBarDoted/CircularProgressBarDoted.tsx +357 -0
  37. package/src/components/CircularRating/CircularRating.tsx +241 -0
  38. package/src/components/Gauge/Gauge.tsx +303 -0
  39. package/src/components/ListGroup/ListGroup.tsx +3 -1
  40. package/src/components/Nudge/Nudge.tsx +222 -82
  41. package/src/components/index.ts +6 -1
  42. package/src/design-tokens/Coin Variables-variables-full.json +1 -1
  43. package/src/icons/registry.ts +1 -1
@@ -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
@@ -0,0 +1,241 @@
1
+ import React, { useMemo } from 'react'
2
+ import {
3
+ Text,
4
+ View,
5
+ type StyleProp,
6
+ type TextStyle,
7
+ type ViewStyle,
8
+ } from 'react-native'
9
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
10
+ import { useTokens } from '../../design-tokens/JFSThemeProvider'
11
+ import Icon from '../../icons/Icon'
12
+ import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
13
+ import CircularProgressBarDoted from '../CircularProgressBarDoted/CircularProgressBarDoted'
14
+ import Nudge from '../Nudge/Nudge'
15
+
16
+ type CircularRatingBaseProps = Omit<React.ComponentProps<typeof View>, 'children' | 'style'>
17
+
18
+ export type CircularRatingProps = CircularRatingBaseProps & {
19
+ /** Rating value. Clamped by CircularProgressBarDoted between 0 and 100. */
20
+ value?: number
21
+ /** Number of dots rendered around the rating ring. */
22
+ dotCount?: number
23
+ /** Small label above the score. */
24
+ label?: string
25
+ /** Text below the score. */
26
+ tierLabel?: string
27
+ /** Footer timestamp/copy shown below the rating. */
28
+ footerText?: string
29
+ /** Show the footer info icon. */
30
+ showFooterIcon?: boolean
31
+ /** Show the bottom inline nudge. */
32
+ showNudge?: boolean
33
+ /** Body text for the default bottom nudge. */
34
+ nudgeBody?: string
35
+ /** Button label for the default bottom nudge. */
36
+ nudgeButtonLabel?: string
37
+ /** Called when the nudge button is pressed. */
38
+ onPressNudgeButton?: () => void
39
+ /** Called when the rating tier row is pressed. */
40
+ onTierPress?: () => void
41
+ /** Optional footer slot. Receives `modes` recursively. */
42
+ footerSlot?: React.ReactNode
43
+ /** Optional nudge slot. Receives `modes` recursively. */
44
+ nudgeSlot?: React.ReactNode
45
+ /** Design token modes forwarded to token lookups and child components. */
46
+ modes?: Record<string, any>
47
+ /** Optional container style overrides. */
48
+ style?: StyleProp<ViewStyle>
49
+ /** Optional rating ring wrapper style overrides. */
50
+ ratingStyle?: StyleProp<ViewStyle>
51
+ /** Optional footer row style overrides. */
52
+ footerStyle?: StyleProp<ViewStyle>
53
+ /** Optional footer text style overrides. */
54
+ footerTextStyle?: StyleProp<TextStyle>
55
+ /** Optional nudge style overrides. */
56
+ nudgeStyle?: StyleProp<ViewStyle>
57
+ /** Accessibility label for the whole component. */
58
+ accessibilityLabel?: string
59
+ }
60
+
61
+ interface CircularRatingTokens {
62
+ containerStyle: ViewStyle
63
+ ratingStyle: ViewStyle
64
+ footerStyle: ViewStyle
65
+ footerTextStyle: TextStyle
66
+ footerIconColor: string
67
+ footerIconSize: number
68
+ }
69
+
70
+ const toNumber = (value: unknown, fallback: number) => {
71
+ if (typeof value === 'number') {
72
+ return Number.isFinite(value) ? value : fallback
73
+ }
74
+
75
+ if (typeof value === 'string') {
76
+ const parsed = Number(value)
77
+ return Number.isFinite(parsed) ? parsed : fallback
78
+ }
79
+
80
+ return fallback
81
+ }
82
+
83
+ const toFontWeight = (value: unknown, fallback: TextStyle['fontWeight']) => {
84
+ if (typeof value === 'number') {
85
+ return String(value) as TextStyle['fontWeight']
86
+ }
87
+
88
+ if (typeof value === 'string') {
89
+ return value as TextStyle['fontWeight']
90
+ }
91
+
92
+ return fallback
93
+ }
94
+
95
+ function resolveCircularRatingTokens(modes: Record<string, any>): CircularRatingTokens {
96
+ const gap = toNumber(getVariableByName('circularRating/gap', modes), 32)
97
+ const padding = toNumber(getVariableByName('circularRating/padding', modes), 10)
98
+
99
+ const footerGap = toNumber(getVariableByName('circularRating/footer/gap', modes), 4)
100
+ const footerHeight = toNumber(getVariableByName('circularRating/footer/height', modes), 16)
101
+ const footerTextColor = getVariableByName('circularRating/footer/text/color', modes) || '#0d0d0f'
102
+ const footerTextFontSize = toNumber(getVariableByName('circularRating/footer/text/fontSize', modes), 12)
103
+ const footerTextFontFamily = getVariableByName('circularRating/footer/text/fontFamily', modes) || 'JioType Var'
104
+ const footerTextLineHeight = toNumber(getVariableByName('circularRating/footer/text/lineHeight', modes), 16)
105
+ const footerTextFontWeight = toFontWeight(
106
+ getVariableByName('circularRating/footer/text/fontWeight', modes),
107
+ '400'
108
+ )
109
+
110
+ return {
111
+ containerStyle: {
112
+ alignItems: 'flex-start',
113
+ gap,
114
+ justifyContent: 'center',
115
+ padding,
116
+ },
117
+ ratingStyle: {
118
+ height: 320,
119
+ width: 320,
120
+ },
121
+ footerStyle: {
122
+ alignItems: 'center',
123
+ flexDirection: 'row',
124
+ gap: footerGap,
125
+ height: footerHeight,
126
+ justifyContent: 'center',
127
+ width: '100%',
128
+ },
129
+ footerTextStyle: {
130
+ color: footerTextColor as string,
131
+ fontFamily: footerTextFontFamily as string,
132
+ fontSize: footerTextFontSize,
133
+ fontWeight: footerTextFontWeight,
134
+ lineHeight: footerTextLineHeight,
135
+ textAlign: 'center',
136
+ },
137
+ footerIconColor: (getVariableByName('circularRating/footer/icon/color', modes) || '#1a1c1f') as string,
138
+ footerIconSize: toNumber(getVariableByName('circularRating/footer/icon/size', modes), 16),
139
+ }
140
+ }
141
+
142
+ function CircularRating({
143
+ value = 72,
144
+ dotCount = 24,
145
+ label = 'Rating',
146
+ tierLabel = 'Doing great',
147
+ footerText = 'Updated on 1 March',
148
+ showFooterIcon = true,
149
+ showNudge = true,
150
+ nudgeBody = 'Split this transaction into installments',
151
+ nudgeButtonLabel = 'Button',
152
+ onPressNudgeButton,
153
+ onTierPress,
154
+ footerSlot,
155
+ nudgeSlot,
156
+ modes: propModes = EMPTY_MODES,
157
+ style,
158
+ ratingStyle,
159
+ footerStyle,
160
+ footerTextStyle,
161
+ nudgeStyle,
162
+ accessibilityLabel,
163
+ ...rest
164
+ }: CircularRatingProps) {
165
+ const { modes: globalModes } = useTokens()
166
+ const modes = useMemo(
167
+ () => (globalModes === EMPTY_MODES && propModes === EMPTY_MODES
168
+ ? EMPTY_MODES
169
+ : { ...globalModes, ...propModes }),
170
+ [globalModes, propModes]
171
+ )
172
+ const tokens = useMemo(() => resolveCircularRatingTokens(modes), [modes])
173
+
174
+ const processedFooterSlot = useMemo(() => {
175
+ if (!footerSlot) return null
176
+ const processed = cloneChildrenWithModes(React.Children.toArray(footerSlot), modes)
177
+ return processed.length === 1 ? processed[0] : processed
178
+ }, [footerSlot, modes])
179
+
180
+ const processedNudgeSlot = useMemo(() => {
181
+ if (!nudgeSlot) return null
182
+ const processed = cloneChildrenWithModes(React.Children.toArray(nudgeSlot), modes)
183
+ return processed.length === 1 ? processed[0] : processed
184
+ }, [nudgeSlot, modes])
185
+
186
+ const defaultAccessibilityLabel =
187
+ accessibilityLabel ?? `${label}. ${Math.round(value)} out of 100. ${tierLabel}. ${footerText}`
188
+
189
+ return (
190
+ <View
191
+ accessibilityLabel={defaultAccessibilityLabel}
192
+ style={[tokens.containerStyle, style]}
193
+ {...rest}
194
+ >
195
+ <View style={[tokens.ratingStyle, ratingStyle]}>
196
+ <CircularProgressBarDoted
197
+ value={value}
198
+ dotCount={dotCount}
199
+ label={label}
200
+ tierLabel={tierLabel}
201
+ onTierPress={onTierPress}
202
+ modes={modes}
203
+ />
204
+ </View>
205
+
206
+ <View style={[tokens.footerStyle, footerStyle]}>
207
+ {processedFooterSlot || (
208
+ <>
209
+ <Text numberOfLines={1} style={[tokens.footerTextStyle, footerTextStyle]}>
210
+ {footerText}
211
+ </Text>
212
+ {showFooterIcon ? (
213
+ <Icon
214
+ name="ic_info"
215
+ size={tokens.footerIconSize}
216
+ color={tokens.footerIconColor}
217
+ accessibilityElementsHidden={true}
218
+ importantForAccessibility="no"
219
+ />
220
+ ) : null}
221
+ </>
222
+ )}
223
+ </View>
224
+
225
+ {showNudge ? (
226
+ processedNudgeSlot || (
227
+ <Nudge
228
+ type="inline-compact"
229
+ body={nudgeBody}
230
+ buttonLabel={nudgeButtonLabel}
231
+ onPressButton={onPressNudgeButton}
232
+ modes={modes}
233
+ style={[{ width: 312 }, nudgeStyle]}
234
+ />
235
+ )
236
+ ) : null}
237
+ </View>
238
+ )
239
+ }
240
+
241
+ export default React.memo(CircularRating)