jfs-components 0.0.71 → 0.0.73

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 (141) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/lib/commonjs/components/AccordionCheckbox/AccordionCheckbox.js +239 -0
  3. package/lib/commonjs/components/BrandChip/BrandChip.js +149 -0
  4. package/lib/commonjs/components/CardAdvisory/CardAdvisory.js +2 -2
  5. package/lib/commonjs/components/CardBankAccount/CardBankAccount.js +213 -0
  6. package/lib/commonjs/components/CardFinancialCondition/CardFinancialCondition.js +213 -0
  7. package/lib/commonjs/components/CardInsight/CardInsight.js +166 -0
  8. package/lib/commonjs/components/Carousel/Carousel.js +9 -7
  9. package/lib/commonjs/components/CheckboxGroup/CheckboxGroup.js +67 -0
  10. package/lib/commonjs/components/CheckboxItem/CheckboxItem.js +125 -0
  11. package/lib/commonjs/components/CircularProgressBar/CircularProgressBar.js +56 -9
  12. package/lib/commonjs/components/CoverageBarComparison/CoverageBarComparison.js +272 -0
  13. package/lib/commonjs/components/CoverageRing/CoverageRing.js +141 -0
  14. package/lib/commonjs/components/DonutChart/DonutChart.js +309 -0
  15. package/lib/commonjs/components/DonutChartSummary/DonutChartSummary.js +155 -0
  16. package/lib/commonjs/components/HoldingsCard/HoldingsCard.js +2 -2
  17. package/lib/commonjs/components/InstitutionBadge/InstitutionBadge.js +132 -0
  18. package/lib/commonjs/components/LinearMeter/LinearMeter.js +9 -28
  19. package/lib/commonjs/components/LinearProgress/LinearProgress.js +68 -0
  20. package/lib/commonjs/components/MetricLegendItem/MetricLegendItem.js +95 -0
  21. package/lib/commonjs/components/MonthlyStatusGrid/MonthlyStatusGrid.js +286 -0
  22. package/lib/commonjs/components/OTP/OTP.js +381 -37
  23. package/lib/commonjs/components/ProductOverview/ProductOverview.js +147 -0
  24. package/lib/commonjs/components/Radio/Radio.js +194 -0
  25. package/lib/commonjs/components/RadioButton/RadioButton.js +21 -188
  26. package/lib/commonjs/components/RangeTrack/RangeTrack.js +269 -0
  27. package/lib/commonjs/components/SavingsGoalSummary/SavingsGoalSummary.js +181 -0
  28. package/lib/commonjs/components/SegmentedTrack/SegmentedTrack.js +171 -0
  29. package/lib/commonjs/components/StatGroup/StatGroup.js +128 -0
  30. package/lib/commonjs/components/StatItem/StatItem.js +65 -35
  31. package/lib/commonjs/components/StrengthIndicator/StrengthIndicator.js +157 -0
  32. package/lib/commonjs/components/SummaryTile/SummaryTile.js +150 -0
  33. package/lib/commonjs/components/index.js +192 -1
  34. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
  35. package/lib/commonjs/icons/registry.js +1 -1
  36. package/lib/commonjs/utils/index.js +7 -0
  37. package/lib/commonjs/utils/number-utils.js +57 -0
  38. package/lib/module/components/AccordionCheckbox/AccordionCheckbox.js +233 -0
  39. package/lib/module/components/BrandChip/BrandChip.js +143 -0
  40. package/lib/module/components/CardAdvisory/CardAdvisory.js +2 -2
  41. package/lib/module/components/CardBankAccount/CardBankAccount.js +208 -0
  42. package/lib/module/components/CardFinancialCondition/CardFinancialCondition.js +207 -0
  43. package/lib/module/components/CardInsight/CardInsight.js +161 -0
  44. package/lib/module/components/Carousel/Carousel.js +9 -7
  45. package/lib/module/components/CheckboxGroup/CheckboxGroup.js +62 -0
  46. package/lib/module/components/CheckboxItem/CheckboxItem.js +119 -0
  47. package/lib/module/components/CircularProgressBar/CircularProgressBar.js +56 -9
  48. package/lib/module/components/CoverageBarComparison/CoverageBarComparison.js +266 -0
  49. package/lib/module/components/CoverageRing/CoverageRing.js +136 -0
  50. package/lib/module/components/DonutChart/DonutChart.js +303 -0
  51. package/lib/module/components/DonutChartSummary/DonutChartSummary.js +150 -0
  52. package/lib/module/components/HoldingsCard/HoldingsCard.js +2 -2
  53. package/lib/module/components/InstitutionBadge/InstitutionBadge.js +127 -0
  54. package/lib/module/components/LinearMeter/LinearMeter.js +9 -28
  55. package/lib/module/components/LinearProgress/LinearProgress.js +63 -0
  56. package/lib/module/components/MetricLegendItem/MetricLegendItem.js +90 -0
  57. package/lib/module/components/MonthlyStatusGrid/MonthlyStatusGrid.js +281 -0
  58. package/lib/module/components/OTP/OTP.js +381 -38
  59. package/lib/module/components/ProductOverview/ProductOverview.js +142 -0
  60. package/lib/module/components/Radio/Radio.js +188 -0
  61. package/lib/module/components/RadioButton/RadioButton.js +20 -185
  62. package/lib/module/components/RangeTrack/RangeTrack.js +263 -0
  63. package/lib/module/components/SavingsGoalSummary/SavingsGoalSummary.js +175 -0
  64. package/lib/module/components/SegmentedTrack/SegmentedTrack.js +166 -0
  65. package/lib/module/components/StatGroup/StatGroup.js +123 -0
  66. package/lib/module/components/StatItem/StatItem.js +66 -36
  67. package/lib/module/components/StrengthIndicator/StrengthIndicator.js +152 -0
  68. package/lib/module/components/SummaryTile/SummaryTile.js +145 -0
  69. package/lib/module/components/index.js +28 -1
  70. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
  71. package/lib/module/icons/registry.js +1 -1
  72. package/lib/module/utils/index.js +2 -1
  73. package/lib/module/utils/number-utils.js +53 -0
  74. package/lib/typescript/src/components/AccordionCheckbox/AccordionCheckbox.d.ts +71 -0
  75. package/lib/typescript/src/components/BrandChip/BrandChip.d.ts +43 -0
  76. package/lib/typescript/src/components/CardBankAccount/CardBankAccount.d.ts +79 -0
  77. package/lib/typescript/src/components/CardFinancialCondition/CardFinancialCondition.d.ts +50 -0
  78. package/lib/typescript/src/components/CardInsight/CardInsight.d.ts +48 -0
  79. package/lib/typescript/src/components/CheckboxGroup/CheckboxGroup.d.ts +41 -0
  80. package/lib/typescript/src/components/CheckboxItem/CheckboxItem.d.ts +56 -0
  81. package/lib/typescript/src/components/CircularProgressBar/CircularProgressBar.d.ts +11 -1
  82. package/lib/typescript/src/components/CoverageBarComparison/CoverageBarComparison.d.ts +105 -0
  83. package/lib/typescript/src/components/CoverageRing/CoverageRing.d.ts +90 -0
  84. package/lib/typescript/src/components/DonutChart/DonutChart.d.ts +117 -0
  85. package/lib/typescript/src/components/DonutChartSummary/DonutChartSummary.d.ts +103 -0
  86. package/lib/typescript/src/components/InstitutionBadge/InstitutionBadge.d.ts +30 -0
  87. package/lib/typescript/src/components/LinearProgress/LinearProgress.d.ts +17 -0
  88. package/lib/typescript/src/components/MetricLegendItem/MetricLegendItem.d.ts +37 -0
  89. package/lib/typescript/src/components/MonthlyStatusGrid/MonthlyStatusGrid.d.ts +119 -0
  90. package/lib/typescript/src/components/OTP/OTP.d.ts +88 -2
  91. package/lib/typescript/src/components/ProductOverview/ProductOverview.d.ts +39 -0
  92. package/lib/typescript/src/components/Radio/Radio.d.ts +30 -0
  93. package/lib/typescript/src/components/RadioButton/RadioButton.d.ts +20 -28
  94. package/lib/typescript/src/components/RangeTrack/RangeTrack.d.ts +173 -0
  95. package/lib/typescript/src/components/SavingsGoalSummary/SavingsGoalSummary.d.ts +95 -0
  96. package/lib/typescript/src/components/SegmentedTrack/SegmentedTrack.d.ts +108 -0
  97. package/lib/typescript/src/components/StatGroup/StatGroup.d.ts +45 -0
  98. package/lib/typescript/src/components/StatItem/StatItem.d.ts +24 -7
  99. package/lib/typescript/src/components/StrengthIndicator/StrengthIndicator.d.ts +58 -0
  100. package/lib/typescript/src/components/SummaryTile/SummaryTile.d.ts +60 -0
  101. package/lib/typescript/src/components/index.d.ts +29 -2
  102. package/lib/typescript/src/icons/registry.d.ts +1 -1
  103. package/lib/typescript/src/utils/index.d.ts +1 -0
  104. package/lib/typescript/src/utils/number-utils.d.ts +29 -0
  105. package/package.json +1 -1
  106. package/src/components/AccordionCheckbox/AccordionCheckbox.tsx +323 -0
  107. package/src/components/BrandChip/BrandChip.tsx +235 -0
  108. package/src/components/CardAdvisory/CardAdvisory.tsx +2 -2
  109. package/src/components/CardBankAccount/CardBankAccount.tsx +295 -0
  110. package/src/components/CardFinancialCondition/CardFinancialCondition.tsx +366 -0
  111. package/src/components/CardInsight/CardInsight.tsx +239 -0
  112. package/src/components/Carousel/Carousel.tsx +14 -6
  113. package/src/components/CheckboxGroup/CheckboxGroup.tsx +86 -0
  114. package/src/components/CheckboxItem/CheckboxItem.tsx +174 -0
  115. package/src/components/CircularProgressBar/CircularProgressBar.tsx +74 -9
  116. package/src/components/CoverageBarComparison/CoverageBarComparison.tsx +378 -0
  117. package/src/components/CoverageRing/CoverageRing.tsx +225 -0
  118. package/src/components/DonutChart/DonutChart.tsx +503 -0
  119. package/src/components/DonutChartSummary/DonutChartSummary.tsx +256 -0
  120. package/src/components/HoldingsCard/HoldingsCard.tsx +2 -2
  121. package/src/components/InstitutionBadge/InstitutionBadge.tsx +216 -0
  122. package/src/components/LinearMeter/LinearMeter.tsx +9 -39
  123. package/src/components/LinearProgress/LinearProgress.tsx +92 -0
  124. package/src/components/MetricLegendItem/MetricLegendItem.tsx +167 -0
  125. package/src/components/MonthlyStatusGrid/MonthlyStatusGrid.tsx +438 -0
  126. package/src/components/OTP/OTP.tsx +476 -29
  127. package/src/components/ProductOverview/ProductOverview.tsx +236 -0
  128. package/src/components/Radio/Radio.tsx +227 -0
  129. package/src/components/RadioButton/RadioButton.tsx +23 -225
  130. package/src/components/RangeTrack/RangeTrack.tsx +394 -0
  131. package/src/components/SavingsGoalSummary/SavingsGoalSummary.tsx +269 -0
  132. package/src/components/SegmentedTrack/SegmentedTrack.tsx +268 -0
  133. package/src/components/StatGroup/StatGroup.tsx +169 -0
  134. package/src/components/StatItem/StatItem.tsx +117 -40
  135. package/src/components/StrengthIndicator/StrengthIndicator.tsx +205 -0
  136. package/src/components/SummaryTile/SummaryTile.tsx +251 -0
  137. package/src/components/index.ts +39 -2
  138. package/src/design-tokens/Coin Variables-variables-full.json +1 -1
  139. package/src/icons/registry.ts +1 -1
  140. package/src/utils/index.ts +1 -0
  141. package/src/utils/number-utils.ts +60 -0
@@ -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)
@@ -0,0 +1,239 @@
1
+ import React from 'react'
2
+ import {
3
+ View,
4
+ Text,
5
+ type StyleProp,
6
+ type ViewStyle,
7
+ type TextStyle,
8
+ } from 'react-native'
9
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
10
+ import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
11
+ import Badge from '../Badge/Badge'
12
+ import Divider from '../Divider/Divider'
13
+ import Nudge from '../Nudge/Nudge'
14
+ import SavingsGoalSummary from '../SavingsGoalSummary/SavingsGoalSummary'
15
+
16
+ export type CardInsightProps = {
17
+ /** Card title rendered in the header (left of the badge). */
18
+ title?: string
19
+ /** One-line explanation rendered under the title. */
20
+ subtitle?: string
21
+ /**
22
+ * Header badge. Pass a string to render the default `Badge` with that
23
+ * label, or a custom `ReactNode` for full control. Pass `false` or
24
+ * `null` to hide the badge entirely.
25
+ */
26
+ badge?: React.ReactNode | string | false
27
+ /**
28
+ * Main content slot. Defaults to a `SavingsGoalSummary` to match the
29
+ * Figma reference. Pass any `ReactNode` to override.
30
+ */
31
+ children?: React.ReactNode
32
+ /**
33
+ * Footer Nudge. Pass a `ReactNode` to override the default inline-compact
34
+ * `Nudge`, or `false`/`null` to hide it.
35
+ */
36
+ footer?: React.ReactNode | false
37
+ /**
38
+ * Whether to render a `Divider` between the content slot and the footer.
39
+ * Defaults to `true` whenever a footer is shown. Pass `false` to hide it,
40
+ * or a custom `ReactNode` to override.
41
+ */
42
+ divider?: React.ReactNode | boolean
43
+ /** Design token modes for theming (e.g. `{ 'Color Mode': 'Light' }`). */
44
+ modes?: Record<string, any>
45
+ /** Override container styles. */
46
+ style?: StyleProp<ViewStyle>
47
+ }
48
+
49
+ /**
50
+ * `CardInsight` renders a single insight card composed of:
51
+ *
52
+ * 1. A header with a title, subtitle and a trailing `Badge`.
53
+ * 2. A flexible content slot — defaults to `SavingsGoalSummary`.
54
+ * 3. A `Divider` separating the slot from the footer.
55
+ * 4. A footer slot — defaults to an inline-compact `Nudge`.
56
+ *
57
+ * @component
58
+ * @param {CardInsightProps} props
59
+ */
60
+ function CardInsight({
61
+ title = 'Emergency savings',
62
+ subtitle = 'One line explanation here.',
63
+ badge = 'Label',
64
+ children,
65
+ footer,
66
+ divider,
67
+ modes = EMPTY_MODES,
68
+ style,
69
+ }: CardInsightProps) {
70
+ const background =
71
+ (getVariableByName('cardInsight/background', modes) as string | null) ?? '#ffffff'
72
+ const radius =
73
+ (getVariableByName('cardInsight/radius', modes) as number | null) ?? 16
74
+ const paddingHorizontal =
75
+ (getVariableByName('cardInsight/padding/horizontal', modes) as number | null) ?? 12
76
+ const paddingVertical =
77
+ (getVariableByName('cardInsight/padding/vertical', modes) as number | null) ?? 16
78
+ const gap = (getVariableByName('cardInsight/gap', modes) as number | null) ?? 12
79
+ const contentGap =
80
+ (getVariableByName('cardInsight/contentGap', modes) as number | null) ?? 8
81
+ const width = (getVariableByName('cardInsight/width', modes) as number | null) ?? 356
82
+ const slotBackground =
83
+ (getVariableByName('cardInsight/slot/background', modes) as string | null) ?? '#ffffff'
84
+
85
+ const headerGap =
86
+ (getVariableByName('cardInsight/header/gap', modes) as number | null) ?? 8
87
+ const textGap =
88
+ (getVariableByName('cardInsight/text/gap', modes) as number | null) ?? 4
89
+
90
+ const titleColor =
91
+ (getVariableByName('cardInsight/title/color', modes) as string | null) ?? '#0d0d0f'
92
+ const titleFontFamily =
93
+ (getVariableByName('cardInsight/title/fontFamily', modes) as string | null) ??
94
+ 'JioType Var'
95
+ const titleFontSize =
96
+ (getVariableByName('cardInsight/title/fontSize', modes) as number | null) ?? 16
97
+ const titleLineHeight =
98
+ (getVariableByName('cardInsight/title/lineHeight', modes) as number | null) ?? 17.6
99
+ const titleFontWeightRaw =
100
+ getVariableByName('cardInsight/title/fontWeight', modes) ?? 700
101
+ const titleFontWeight = String(titleFontWeightRaw) as TextStyle['fontWeight']
102
+
103
+ const bodyColor =
104
+ (getVariableByName('cardInsight/body/color', modes) as string | null) ?? '#0d0d0f'
105
+ const bodyFontFamily =
106
+ (getVariableByName('cardInsight/body/fontFamily', modes) as string | null) ??
107
+ 'JioType Var'
108
+ const bodyFontSize =
109
+ (getVariableByName('cardInsight/body/fontSize', modes) as number | null) ?? 12
110
+ const bodyLineHeight =
111
+ (getVariableByName('cardInsight/body/lineHeight', modes) as number | null) ?? 15.6
112
+ const bodyFontWeightRaw =
113
+ getVariableByName('cardInsight/body/fontWeight', modes) ?? 400
114
+ const bodyFontWeight = String(bodyFontWeightRaw) as TextStyle['fontWeight']
115
+
116
+ const renderBadge = (): React.ReactNode => {
117
+ if (badge === false || badge === null || badge === undefined) return null
118
+ if (typeof badge === 'string') {
119
+ return <Badge label={badge} modes={modes} />
120
+ }
121
+ const processed = cloneChildrenWithModes(badge, modes)
122
+ return processed.length === 1 ? processed[0] : processed
123
+ }
124
+
125
+ const renderSlot = (): React.ReactNode => {
126
+ if (!children) {
127
+ return <SavingsGoalSummary modes={modes} />
128
+ }
129
+ const processed = cloneChildrenWithModes(children, modes)
130
+ return processed.length === 1 ? processed[0] : processed
131
+ }
132
+
133
+ const renderFooter = (): React.ReactNode => {
134
+ if (footer === false || footer === null) return null
135
+ if (footer === undefined) {
136
+ return (
137
+ <Nudge
138
+ type="inline-compact"
139
+ modes={{ AppearanceBrand: 'Neutral', Context: 'Nudge&Alert', ...modes }}
140
+ />
141
+ )
142
+ }
143
+ const processed = cloneChildrenWithModes(footer, modes)
144
+ return processed.length === 1 ? processed[0] : processed
145
+ }
146
+
147
+ const renderDivider = (): React.ReactNode => {
148
+ if (divider === false || divider === null) return null
149
+ if (divider === undefined || divider === true) {
150
+ return <Divider modes={modes} />
151
+ }
152
+ const processed = cloneChildrenWithModes(divider, modes)
153
+ return processed.length === 1 ? processed[0] : processed
154
+ }
155
+
156
+ const footerNode = renderFooter()
157
+ const showDivider = footerNode !== null && divider !== false && divider !== null
158
+
159
+ return (
160
+ <View
161
+ style={[
162
+ {
163
+ width,
164
+ backgroundColor: background,
165
+ borderRadius: radius,
166
+ paddingHorizontal,
167
+ paddingVertical,
168
+ gap,
169
+ overflow: 'hidden',
170
+ alignItems: 'stretch',
171
+ },
172
+ style,
173
+ ]}
174
+ >
175
+ <View
176
+ style={{
177
+ flexDirection: 'row',
178
+ alignItems: 'flex-start',
179
+ gap: headerGap,
180
+ width: '100%',
181
+ }}
182
+ >
183
+ <View
184
+ style={{
185
+ flex: 1,
186
+ minWidth: 0,
187
+ gap: textGap,
188
+ alignItems: 'flex-start',
189
+ }}
190
+ >
191
+ <Text
192
+ style={{
193
+ color: titleColor,
194
+ fontFamily: titleFontFamily,
195
+ fontSize: titleFontSize,
196
+ lineHeight: titleLineHeight,
197
+ fontWeight: titleFontWeight,
198
+ width: '100%',
199
+ }}
200
+ >
201
+ {title}
202
+ </Text>
203
+ <Text
204
+ style={{
205
+ color: bodyColor,
206
+ fontFamily: bodyFontFamily,
207
+ fontSize: bodyFontSize,
208
+ lineHeight: bodyLineHeight,
209
+ fontWeight: bodyFontWeight,
210
+ width: '100%',
211
+ }}
212
+ >
213
+ {subtitle}
214
+ </Text>
215
+ </View>
216
+
217
+ {renderBadge()}
218
+ </View>
219
+
220
+ <View
221
+ style={{
222
+ width: '100%',
223
+ backgroundColor: slotBackground,
224
+ gap: contentGap,
225
+ overflow: 'hidden',
226
+ alignItems: 'stretch',
227
+ }}
228
+ >
229
+ {renderSlot()}
230
+ </View>
231
+
232
+ {showDivider ? renderDivider() : null}
233
+
234
+ {footerNode}
235
+ </View>
236
+ )
237
+ }
238
+
239
+ export default CardInsight
@@ -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(