jfs-components 0.0.70 → 0.0.72

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 (66) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/lib/commonjs/components/CardAdvisory/CardAdvisory.js +203 -0
  3. package/lib/commonjs/components/CardCTA/CardCTA.js +198 -16
  4. package/lib/commonjs/components/CardFinancialCondition/CardFinancialCondition.js +213 -0
  5. package/lib/commonjs/components/Carousel/Carousel.js +9 -7
  6. package/lib/commonjs/components/CircularProgressBar/CircularProgressBar.js +147 -0
  7. package/lib/commonjs/components/CircularProgressBarDoted/CircularProgressBarDoted.js +258 -0
  8. package/lib/commonjs/components/CircularRating/CircularRating.js +161 -0
  9. package/lib/commonjs/components/Gauge/Gauge.js +223 -0
  10. package/lib/commonjs/components/HoldingsCard/HoldingsCard.js +2 -2
  11. package/lib/commonjs/components/InstitutionBadge/InstitutionBadge.js +132 -0
  12. package/lib/commonjs/components/ListGroup/ListGroup.js +3 -1
  13. package/lib/commonjs/components/Nudge/Nudge.js +179 -87
  14. package/lib/commonjs/components/Radio/Radio.js +194 -0
  15. package/lib/commonjs/components/RadioButton/RadioButton.js +21 -188
  16. package/lib/commonjs/components/index.js +56 -0
  17. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
  18. package/lib/commonjs/icons/registry.js +1 -1
  19. package/lib/module/components/CardAdvisory/CardAdvisory.js +197 -0
  20. package/lib/module/components/CardCTA/CardCTA.js +199 -17
  21. package/lib/module/components/CardFinancialCondition/CardFinancialCondition.js +207 -0
  22. package/lib/module/components/Carousel/Carousel.js +9 -7
  23. package/lib/module/components/CircularProgressBar/CircularProgressBar.js +141 -0
  24. package/lib/module/components/CircularProgressBarDoted/CircularProgressBarDoted.js +253 -0
  25. package/lib/module/components/CircularRating/CircularRating.js +155 -0
  26. package/lib/module/components/Gauge/Gauge.js +217 -0
  27. package/lib/module/components/HoldingsCard/HoldingsCard.js +2 -2
  28. package/lib/module/components/InstitutionBadge/InstitutionBadge.js +127 -0
  29. package/lib/module/components/ListGroup/ListGroup.js +3 -1
  30. package/lib/module/components/Nudge/Nudge.js +178 -87
  31. package/lib/module/components/Radio/Radio.js +188 -0
  32. package/lib/module/components/RadioButton/RadioButton.js +20 -185
  33. package/lib/module/components/index.js +12 -0
  34. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
  35. package/lib/module/icons/registry.js +1 -1
  36. package/lib/typescript/src/components/CardAdvisory/CardAdvisory.d.ts +49 -0
  37. package/lib/typescript/src/components/CardCTA/CardCTA.d.ts +16 -1
  38. package/lib/typescript/src/components/CardFinancialCondition/CardFinancialCondition.d.ts +50 -0
  39. package/lib/typescript/src/components/CircularProgressBar/CircularProgressBar.d.ts +27 -0
  40. package/lib/typescript/src/components/CircularProgressBarDoted/CircularProgressBarDoted.d.ts +48 -0
  41. package/lib/typescript/src/components/CircularRating/CircularRating.d.ts +49 -0
  42. package/lib/typescript/src/components/Gauge/Gauge.d.ts +53 -0
  43. package/lib/typescript/src/components/InstitutionBadge/InstitutionBadge.d.ts +30 -0
  44. package/lib/typescript/src/components/Nudge/Nudge.d.ts +14 -11
  45. package/lib/typescript/src/components/Radio/Radio.d.ts +30 -0
  46. package/lib/typescript/src/components/RadioButton/RadioButton.d.ts +20 -28
  47. package/lib/typescript/src/components/index.d.ts +13 -1
  48. package/lib/typescript/src/icons/registry.d.ts +1 -1
  49. package/package.json +1 -1
  50. package/src/components/CardAdvisory/CardAdvisory.tsx +283 -0
  51. package/src/components/CardCTA/CardCTA.tsx +236 -13
  52. package/src/components/CardFinancialCondition/CardFinancialCondition.tsx +366 -0
  53. package/src/components/Carousel/Carousel.tsx +14 -6
  54. package/src/components/CircularProgressBar/CircularProgressBar.tsx +190 -0
  55. package/src/components/CircularProgressBarDoted/CircularProgressBarDoted.tsx +357 -0
  56. package/src/components/CircularRating/CircularRating.tsx +241 -0
  57. package/src/components/Gauge/Gauge.tsx +303 -0
  58. package/src/components/HoldingsCard/HoldingsCard.tsx +2 -2
  59. package/src/components/InstitutionBadge/InstitutionBadge.tsx +216 -0
  60. package/src/components/ListGroup/ListGroup.tsx +3 -1
  61. package/src/components/Nudge/Nudge.tsx +222 -82
  62. package/src/components/Radio/Radio.tsx +227 -0
  63. package/src/components/RadioButton/RadioButton.tsx +23 -225
  64. package/src/components/index.ts +13 -1
  65. package/src/design-tokens/Coin Variables-variables-full.json +1 -1
  66. package/src/icons/registry.ts +1 -1
@@ -0,0 +1,366 @@
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 { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
12
+ import Button from '../Button/Button'
13
+ import CircularProgressBar, {
14
+ type CircularProgressBarProps,
15
+ } from '../CircularProgressBar/CircularProgressBar'
16
+ import Divider from '../Divider/Divider'
17
+ import Nudge from '../Nudge/Nudge'
18
+
19
+ type CardFinancialConditionBaseProps = Omit<
20
+ React.ComponentProps<typeof View>,
21
+ 'children' | 'style'
22
+ >
23
+
24
+ export type CardFinancialConditionProps = CardFinancialConditionBaseProps & {
25
+ /** Title text shown in the header. */
26
+ title?: string
27
+ /** Body / description shown below the title. */
28
+ body?: string
29
+ /** Progress score for the circular indicator. Forwarded to CircularProgressBar. */
30
+ value?: number
31
+ /** Visual state of the circular indicator. Forwarded to CircularProgressBar. */
32
+ progressState?: CircularProgressBarProps['state']
33
+ /** Optional formatted value label for the active state. */
34
+ valueLabel?: string
35
+ /** Show the bottom advisory nudge. */
36
+ showNudge?: boolean
37
+ /** Body text for the default nudge. Use `\n` to add a second line. */
38
+ nudgeBody?: string
39
+ /** Show the bottom divider above the action button. */
40
+ showDivider?: boolean
41
+ /** Show the action button at the bottom of the card. */
42
+ showButton?: boolean
43
+ /** Label for the default action button. */
44
+ buttonLabel?: string
45
+ /** Callback for the default action button press. */
46
+ onPressButton?: () => void
47
+ /** Slot replacing the default circular progress bar. Receives `modes` recursively. */
48
+ progressSlot?: React.ReactNode
49
+ /** Slot replacing the default Nudge. Receives `modes` recursively. */
50
+ nudgeSlot?: React.ReactNode
51
+ /** Slot replacing the default action button. Receives `modes` recursively. */
52
+ buttonSlot?: React.ReactNode
53
+ /** Design token modes forwarded to token lookups and child components. */
54
+ modes?: Record<string, any>
55
+ /** Container style override. */
56
+ style?: StyleProp<ViewStyle>
57
+ /** Header row style override. */
58
+ headerStyle?: StyleProp<ViewStyle>
59
+ /** Title text style override. */
60
+ titleStyle?: StyleProp<TextStyle>
61
+ /** Body text style override. */
62
+ bodyStyle?: StyleProp<TextStyle>
63
+ /** Accessibility label for the whole card. */
64
+ accessibilityLabel?: string
65
+ }
66
+
67
+ interface CardFinancialConditionTokens {
68
+ containerStyle: ViewStyle
69
+ headerStyle: ViewStyle
70
+ textContentStyle: ViewStyle
71
+ titleStyle: TextStyle
72
+ bodyStyle: TextStyle
73
+ nudgeBodyStyle: TextStyle
74
+ buttonWrapStyle: ViewStyle
75
+ }
76
+
77
+ const toNumber = (value: unknown, fallback: number) => {
78
+ if (typeof value === 'number') {
79
+ return Number.isFinite(value) ? value : fallback
80
+ }
81
+
82
+ if (typeof value === 'string') {
83
+ const parsed = Number(value)
84
+ return Number.isFinite(parsed) ? parsed : fallback
85
+ }
86
+
87
+ return fallback
88
+ }
89
+
90
+ const toFontWeight = (value: unknown, fallback: TextStyle['fontWeight']) => {
91
+ if (typeof value === 'number') {
92
+ return String(value) as TextStyle['fontWeight']
93
+ }
94
+
95
+ if (typeof value === 'string') {
96
+ return value as TextStyle['fontWeight']
97
+ }
98
+
99
+ return fallback
100
+ }
101
+
102
+ function resolveCardFinancialConditionTokens(
103
+ modes: Record<string, any>
104
+ ): CardFinancialConditionTokens {
105
+ const background =
106
+ (getVariableByName('financialConditionCard/background', modes) as string) ||
107
+ '#ffffff'
108
+ const radius = toNumber(
109
+ getVariableByName('financialConditionCard/radius', modes),
110
+ 16
111
+ )
112
+ const paddingH = toNumber(
113
+ getVariableByName('financialConditionCard/padding/horizontal', modes),
114
+ 16
115
+ )
116
+ const paddingV = toNumber(
117
+ getVariableByName('financialConditionCard/padding/vertical', modes),
118
+ 16
119
+ )
120
+ const gap = toNumber(
121
+ getVariableByName('financialConditionCard/gap', modes),
122
+ 8
123
+ )
124
+
125
+ const headerGap = toNumber(
126
+ getVariableByName('financialConditionCard/header/gap', modes),
127
+ 16
128
+ )
129
+ const textContentGap = toNumber(
130
+ getVariableByName('financialConditionCard/textContent/gap', modes),
131
+ 8
132
+ )
133
+
134
+ const titleColor =
135
+ (getVariableByName('financialConditionCard/title/color', modes) as string) ||
136
+ '#0c0d10'
137
+ const titleFontSize = toNumber(
138
+ getVariableByName('financialConditionCard/title/fontSize', modes),
139
+ 16
140
+ )
141
+ const titleFontFamily =
142
+ (getVariableByName(
143
+ 'financialConditionCard/title/fontFamily',
144
+ modes
145
+ ) as string) || 'JioType Var'
146
+ const titleLineHeight = toNumber(
147
+ getVariableByName('financialConditionCard/title/lineHeight', modes),
148
+ 18
149
+ )
150
+ const titleFontWeight = toFontWeight(
151
+ getVariableByName('financialConditionCard/title/fontWeight', modes),
152
+ '700'
153
+ )
154
+
155
+ const bodyColor =
156
+ (getVariableByName('financialConditionCard/body/color', modes) as string) ||
157
+ '#0c0d10'
158
+ const bodyFontSize = toNumber(
159
+ getVariableByName('financialConditionCard/body/fontSize', modes),
160
+ 14
161
+ )
162
+ const bodyFontFamily =
163
+ (getVariableByName(
164
+ 'financialConditionCard/body/fontFamily',
165
+ modes
166
+ ) as string) || 'JioType Var'
167
+ const bodyLineHeight = toNumber(
168
+ getVariableByName('financialConditionCard/body/lineHeight', modes),
169
+ 17
170
+ )
171
+ const bodyFontWeight = toFontWeight(
172
+ getVariableByName('financialConditionCard/body/fontWeight', modes),
173
+ '400'
174
+ )
175
+
176
+ const nudgeBodyColor =
177
+ (getVariableByName('nudge/body/color', modes) as string) || '#1a1c1f'
178
+ const nudgeBodyFontSize = toNumber(
179
+ getVariableByName('nudge/body/fontSize', modes),
180
+ 12
181
+ )
182
+ const nudgeBodyFontFamily =
183
+ (getVariableByName('nudge/body/fontFamily', modes) as string) || 'JioType Var'
184
+ const nudgeBodyLineHeight = toNumber(
185
+ getVariableByName('nudge/body/lineHeight', modes),
186
+ 16
187
+ )
188
+ const nudgeBodyFontWeight = toFontWeight(
189
+ getVariableByName('nudge/body/fontWeight', modes),
190
+ '500'
191
+ )
192
+
193
+ return {
194
+ containerStyle: {
195
+ alignItems: 'flex-start',
196
+ backgroundColor: background,
197
+ borderRadius: radius,
198
+ gap,
199
+ justifyContent: 'center',
200
+ overflow: 'hidden',
201
+ paddingHorizontal: paddingH,
202
+ paddingVertical: paddingV,
203
+ },
204
+ headerStyle: {
205
+ alignItems: 'center',
206
+ flexDirection: 'row',
207
+ gap: headerGap,
208
+ width: '100%',
209
+ },
210
+ textContentStyle: {
211
+ alignItems: 'flex-start',
212
+ flex: 1,
213
+ gap: textContentGap,
214
+ minWidth: 1,
215
+ },
216
+ titleStyle: {
217
+ color: titleColor,
218
+ fontFamily: titleFontFamily,
219
+ fontSize: titleFontSize,
220
+ fontWeight: titleFontWeight,
221
+ lineHeight: titleLineHeight,
222
+ },
223
+ bodyStyle: {
224
+ color: bodyColor,
225
+ fontFamily: bodyFontFamily,
226
+ fontSize: bodyFontSize,
227
+ fontWeight: bodyFontWeight,
228
+ lineHeight: bodyLineHeight,
229
+ },
230
+ nudgeBodyStyle: {
231
+ color: nudgeBodyColor,
232
+ fontFamily: nudgeBodyFontFamily,
233
+ fontSize: nudgeBodyFontSize,
234
+ fontWeight: nudgeBodyFontWeight,
235
+ lineHeight: nudgeBodyLineHeight,
236
+ },
237
+ buttonWrapStyle: {
238
+ alignSelf: 'stretch',
239
+ alignItems: 'flex-start',
240
+ },
241
+ }
242
+ }
243
+
244
+ function CardFinancialCondition({
245
+ title = 'Protection',
246
+ body = 'Check your coverage and gaps',
247
+ value = 0,
248
+ progressState = 'Inactive',
249
+ valueLabel,
250
+ showNudge = true,
251
+ nudgeBody = 'There may be gaps in your health or life cover\nAdd coverage to stay financially secure',
252
+ showDivider = true,
253
+ showButton = true,
254
+ buttonLabel = 'View Details',
255
+ onPressButton,
256
+ progressSlot,
257
+ nudgeSlot,
258
+ buttonSlot,
259
+ modes: propModes = EMPTY_MODES,
260
+ style,
261
+ headerStyle,
262
+ titleStyle,
263
+ bodyStyle,
264
+ accessibilityLabel,
265
+ ...rest
266
+ }: CardFinancialConditionProps) {
267
+ const { modes: globalModes } = useTokens()
268
+ const modes = useMemo(
269
+ () =>
270
+ globalModes === EMPTY_MODES && propModes === EMPTY_MODES
271
+ ? EMPTY_MODES
272
+ : { ...globalModes, ...propModes },
273
+ [globalModes, propModes]
274
+ )
275
+
276
+ const tokens = useMemo(
277
+ () => resolveCardFinancialConditionTokens(modes),
278
+ [modes]
279
+ )
280
+
281
+ // Match Figma: the action button is the primary call-to-action and uses the
282
+ // Secondary brand (purple) regardless of the card's overall AppearanceBrand,
283
+ // while Nudge / CircularProgressBar still follow the user-supplied modes.
284
+ const buttonModes = useMemo(
285
+ () => ({ ...modes, AppearanceBrand: 'Secondary' }),
286
+ [modes]
287
+ )
288
+
289
+ const processedProgressSlot = useMemo(() => {
290
+ if (!progressSlot) return null
291
+ const processed = cloneChildrenWithModes(
292
+ React.Children.toArray(progressSlot),
293
+ modes
294
+ )
295
+ return processed.length === 1 ? processed[0] : processed
296
+ }, [progressSlot, modes])
297
+
298
+ const processedNudgeSlot = useMemo(() => {
299
+ if (!nudgeSlot) return null
300
+ const processed = cloneChildrenWithModes(
301
+ React.Children.toArray(nudgeSlot),
302
+ modes
303
+ )
304
+ return processed.length === 1 ? processed[0] : processed
305
+ }, [nudgeSlot, modes])
306
+
307
+ const processedButtonSlot = useMemo(() => {
308
+ if (!buttonSlot) return null
309
+ const processed = cloneChildrenWithModes(
310
+ React.Children.toArray(buttonSlot),
311
+ buttonModes
312
+ )
313
+ return processed.length === 1 ? processed[0] : processed
314
+ }, [buttonSlot, buttonModes])
315
+
316
+ const defaultAccessibilityLabel =
317
+ accessibilityLabel ?? `${title}. ${body}.`
318
+
319
+ return (
320
+ <View
321
+ accessibilityLabel={defaultAccessibilityLabel}
322
+ style={[tokens.containerStyle, style]}
323
+ {...rest}
324
+ >
325
+ <View style={[tokens.headerStyle, headerStyle]}>
326
+ <View style={tokens.textContentStyle}>
327
+ <Text style={[tokens.titleStyle, titleStyle]}>{title}</Text>
328
+ <Text style={[tokens.bodyStyle, bodyStyle]}>{body}</Text>
329
+ </View>
330
+
331
+ {processedProgressSlot || (
332
+ <CircularProgressBar
333
+ state={progressState}
334
+ value={value}
335
+ modes={modes}
336
+ {...(valueLabel ? { valueLabel } : {})}
337
+ />
338
+ )}
339
+ </View>
340
+
341
+ {showNudge ? (
342
+ processedNudgeSlot || (
343
+ <Nudge type="inline-compact" modes={modes} style={{ width: '100%' }}>
344
+ <Text style={tokens.nudgeBodyStyle}>{nudgeBody}</Text>
345
+ </Nudge>
346
+ )
347
+ ) : null}
348
+
349
+ {showDivider ? <Divider modes={modes} /> : null}
350
+
351
+ {showButton ? (
352
+ <View style={tokens.buttonWrapStyle}>
353
+ {processedButtonSlot || (
354
+ <Button
355
+ label={buttonLabel}
356
+ modes={buttonModes}
357
+ {...(onPressButton ? { onPress: onPressButton } : {})}
358
+ />
359
+ )}
360
+ </View>
361
+ ) : null}
362
+ </View>
363
+ )
364
+ }
365
+
366
+ export default React.memo(CardFinancialCondition)
@@ -102,6 +102,10 @@ export function Carousel({
102
102
  const containerPaddingV = parseFloat(
103
103
  getVariableByName('carousel/padding/vertical', modes) || '0',
104
104
  )
105
+ // Outer container max height per Figma (`carousel/maxHeight`).
106
+ const maxHeight = parseFloat(
107
+ getVariableByName('carousel/maxHeight', modes) || '280',
108
+ )
105
109
  // Spacing between the cards row and the pagination dots uses `carousel/gap`.
106
110
  const paginationOffset = gap
107
111
 
@@ -251,6 +255,7 @@ export function Carousel({
251
255
  // ---- Render ----
252
256
  const outerStyle: ViewStyle = {
253
257
  paddingVertical: containerPaddingV,
258
+ maxHeight,
254
259
  }
255
260
 
256
261
  const contentContainerStyle: ViewStyle = {
@@ -364,21 +369,24 @@ export function Pagination({ modes: propModes, style }: PaginationProps) {
364
369
  const modes = propModes || ctxModes || {}
365
370
 
366
371
  // Token resolution for dots — matches Figma tokens
367
- // (carousel/pagination/gap, carousel/pagination/indicator/{activecolor,inactivecolor,radius}).
368
- // Dot dimensions are fixed per Figma spec: inactive 6x6, active 16x6.
369
- const dotSize = 6
370
- const dotActiveWidth = 16
372
+ // (carousel/pagination/gap, carousel/pagination/indicator/{activeColor,inactiveColor,radius,size,activeWidth}).
373
+ const dotSize = parseFloat(
374
+ getVariableByName('carousel/pagination/indicator/size', modes) || '6',
375
+ )
376
+ const dotActiveWidth = parseFloat(
377
+ getVariableByName('carousel/pagination/indicator/activeWidth', modes) || '16',
378
+ )
371
379
  const dotGap = parseFloat(
372
380
  getVariableByName('carousel/pagination/gap', modes) || '4',
373
381
  )
374
382
  const dotColor =
375
383
  (getVariableByName(
376
- 'carousel/pagination/indicator/inactivecolor',
384
+ 'carousel/pagination/indicator/inactiveColor',
377
385
  modes,
378
386
  ) as string) || 'rgba(0,0,0,0.3)'
379
387
  const dotActiveColor =
380
388
  (getVariableByName(
381
- 'carousel/pagination/indicator/activecolor',
389
+ 'carousel/pagination/indicator/activeColor',
382
390
  modes,
383
391
  ) as string) || '#170d0a'
384
392
  const dotRadius = parseFloat(
@@ -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