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,235 @@
1
+ import React, { useMemo } from 'react'
2
+ import {
3
+ Pressable,
4
+ Text,
5
+ View,
6
+ type ImageSourcePropType,
7
+ type StyleProp,
8
+ type TextStyle,
9
+ type ViewStyle,
10
+ } from 'react-native'
11
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
12
+ import { useTokens } from '../../design-tokens/JFSThemeProvider'
13
+ import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
14
+ import Avatar from '../Avatar/Avatar'
15
+
16
+ export type BrandChipProps = {
17
+ /** Visible label (e.g. `"Axis Bank • 0245"`). */
18
+ label?: string
19
+ /**
20
+ * Image shown in the avatar. Forwarded to the underlying `Avatar`'s
21
+ * `imageSource` prop. Pass a `require()`-ed asset, an `{ uri }` object
22
+ * or a remote URL string. Ignored when `avatarSlot` is provided.
23
+ */
24
+ imageSource?: ImageSourcePropType | string
25
+ /**
26
+ * Slot replacing the default `Avatar`. Receives `modes` recursively, so
27
+ * inner `Avatar` / `IconCapsule` instances inherit theming.
28
+ */
29
+ avatarSlot?: React.ReactNode
30
+ /** Optional press handler — when provided, the chip becomes a `Pressable`. */
31
+ onPress?: () => void
32
+ /** Design token modes for theming (e.g. `{ 'Color Mode': 'Light' }`). */
33
+ modes?: Record<string, any>
34
+ /** Container style override. */
35
+ style?: StyleProp<ViewStyle>
36
+ /** Label style override. */
37
+ labelStyle?: StyleProp<TextStyle>
38
+ /** Accessibility label. Defaults to `label`. */
39
+ accessibilityLabel?: string
40
+ }
41
+
42
+ interface BrandChipTokens {
43
+ containerStyle: ViewStyle
44
+ labelStyle: TextStyle
45
+ }
46
+
47
+ const toNumber = (value: unknown, fallback: number): number => {
48
+ if (typeof value === 'number') {
49
+ return Number.isFinite(value) ? value : fallback
50
+ }
51
+ if (typeof value === 'string') {
52
+ const parsed = Number(value)
53
+ return Number.isFinite(parsed) ? parsed : fallback
54
+ }
55
+ return fallback
56
+ }
57
+
58
+ const toFontWeight = (
59
+ value: unknown,
60
+ fallback: TextStyle['fontWeight']
61
+ ): TextStyle['fontWeight'] => {
62
+ if (typeof value === 'number') return String(value) as TextStyle['fontWeight']
63
+ if (typeof value === 'string') return value as TextStyle['fontWeight']
64
+ return fallback
65
+ }
66
+
67
+ function resolveBrandChipTokens(modes: Record<string, any>): BrandChipTokens {
68
+ const background =
69
+ (getVariableByName('brandChip/background', modes) as string | null) ??
70
+ '#ffffff'
71
+ const foreground =
72
+ (getVariableByName('brandChip/foreground', modes) as string | null) ??
73
+ '#0d0d0f'
74
+ const borderColor =
75
+ (getVariableByName('brandChip/border/color', modes) as string | null) ??
76
+ '#e0e0e3'
77
+ const borderSize = toNumber(
78
+ getVariableByName('brandChip/border/size', modes),
79
+ 1
80
+ )
81
+ const gap = toNumber(getVariableByName('brandChip/gap', modes), 8)
82
+ const paddingLeft = toNumber(
83
+ getVariableByName('brandChip/padding/left', modes),
84
+ 8
85
+ )
86
+ const paddingRight = toNumber(
87
+ getVariableByName('brandChip/padding/right', modes),
88
+ 12
89
+ )
90
+ const paddingVertical = toNumber(
91
+ getVariableByName('brandChip/padding/vertical', modes),
92
+ 5
93
+ )
94
+ // The Figma token uses 25000 as a sentinel for "always pill-shaped".
95
+ // Cap it to a value React Native renders predictably.
96
+ const radiusRaw = toNumber(getVariableByName('brandChip/radius', modes), 9999)
97
+ const radius = radiusRaw >= 9999 ? 9999 : radiusRaw
98
+
99
+ const fontFamily =
100
+ (getVariableByName('brandChip/label/fontFamily', modes) as string | null) ??
101
+ 'JioType Var'
102
+ const fontSize = toNumber(
103
+ getVariableByName('brandChip/label/fontSize', modes),
104
+ 16
105
+ )
106
+ const lineHeight = toNumber(
107
+ getVariableByName('brandChip/label/lineHeight', modes),
108
+ 21
109
+ )
110
+ const fontWeight = toFontWeight(
111
+ getVariableByName('brandChip/label/fontWeight', modes),
112
+ '500'
113
+ )
114
+
115
+ return {
116
+ containerStyle: {
117
+ flexDirection: 'row',
118
+ alignItems: 'center',
119
+ alignSelf: 'flex-start',
120
+ backgroundColor: background,
121
+ borderColor,
122
+ borderWidth: borderSize,
123
+ borderStyle: 'solid',
124
+ borderRadius: radius,
125
+ paddingLeft,
126
+ paddingRight,
127
+ paddingTop: paddingVertical,
128
+ paddingBottom: paddingVertical,
129
+ gap,
130
+ },
131
+ labelStyle: {
132
+ color: foreground,
133
+ fontFamily,
134
+ fontSize,
135
+ fontWeight,
136
+ lineHeight,
137
+ },
138
+ }
139
+ }
140
+
141
+ // The Figma reference renders the avatar at 29px which is the `S` size in
142
+ // the Avatar Size collection. Callers can override via `modes`.
143
+ const DEFAULT_AVATAR_SIZE_MODE = 'S'
144
+
145
+ /**
146
+ * `BrandChip` renders a compact pill that pairs a small institution avatar
147
+ * with a short identifier label (e.g. `"Axis Bank • 0245"`). It's typically
148
+ * used to surface the currently-selected linked account in headers, sticky
149
+ * bars, or selectors.
150
+ *
151
+ * All visual values resolve through the `brandChip/*` design tokens with
152
+ * sensible Figma defaults so the chip renders correctly out of the box.
153
+ *
154
+ * @component
155
+ * @param {BrandChipProps} props
156
+ */
157
+ function BrandChip({
158
+ label = 'Axis Bank • 0245',
159
+ imageSource,
160
+ avatarSlot,
161
+ onPress,
162
+ modes: propModes = EMPTY_MODES,
163
+ style,
164
+ labelStyle,
165
+ accessibilityLabel,
166
+ }: BrandChipProps) {
167
+ const { modes: globalModes } = useTokens()
168
+ const modes = useMemo(
169
+ () =>
170
+ globalModes === EMPTY_MODES && propModes === EMPTY_MODES
171
+ ? EMPTY_MODES
172
+ : { ...globalModes, ...propModes },
173
+ [globalModes, propModes]
174
+ )
175
+
176
+ const avatarModes = useMemo(
177
+ () => ({ 'Avatar Size': DEFAULT_AVATAR_SIZE_MODE, ...modes }),
178
+ [modes]
179
+ )
180
+
181
+ const tokens = useMemo(() => resolveBrandChipTokens(modes), [modes])
182
+
183
+ const processedAvatarSlot = useMemo(() => {
184
+ if (!avatarSlot) return null
185
+ const processed = cloneChildrenWithModes(avatarSlot, avatarModes)
186
+ return processed.length === 1 ? processed[0] : processed
187
+ }, [avatarSlot, avatarModes])
188
+
189
+ const avatarNode =
190
+ processedAvatarSlot ??
191
+ (imageSource !== undefined ? (
192
+ <Avatar style="Image" imageSource={imageSource} modes={avatarModes} />
193
+ ) : (
194
+ <Avatar style="Image" modes={avatarModes} />
195
+ ))
196
+
197
+ const a11yLabel = accessibilityLabel ?? label
198
+
199
+ const content = (
200
+ <>
201
+ {avatarNode}
202
+ <Text
203
+ style={[tokens.labelStyle, labelStyle]}
204
+ numberOfLines={1}
205
+ ellipsizeMode="tail"
206
+ >
207
+ {label}
208
+ </Text>
209
+ </>
210
+ )
211
+
212
+ if (onPress) {
213
+ return (
214
+ <Pressable
215
+ accessibilityRole="button"
216
+ accessibilityLabel={a11yLabel}
217
+ onPress={onPress}
218
+ style={[tokens.containerStyle, style]}
219
+ >
220
+ {content}
221
+ </Pressable>
222
+ )
223
+ }
224
+
225
+ return (
226
+ <View
227
+ accessibilityLabel={a11yLabel}
228
+ style={[tokens.containerStyle, style]}
229
+ >
230
+ {content}
231
+ </View>
232
+ )
233
+ }
234
+
235
+ export default React.memo(BrandChip)
@@ -97,8 +97,8 @@ const toFontWeight = (value: unknown, fallback: TextStyle['fontWeight']) => {
97
97
  function resolveCardAdvisoryTokens(modes: Record<string, any>): CardAdvisoryTokens {
98
98
  const width = toNumber(getVariableByName('cardAdvisory/width', modes), 360)
99
99
  const gap = toNumber(getVariableByName('cardAdvisory/gap', modes), 16)
100
- const paddingHorizontal = toNumber(getVariableByName('cardAdvisory/padding/horizontal', modes), 16)
101
- const paddingVertical = toNumber(getVariableByName('cardAdvisory/padding/vertical', modes), 12)
100
+ const paddingHorizontal = toNumber(getVariableByName('cardAdvisory/padding/horizontal', modes), 0)
101
+ const paddingVertical = toNumber(getVariableByName('cardAdvisory/padding/vertical', modes), 0)
102
102
  const radius = toNumber(getVariableByName('cardAdvisory/radius', modes), 0)
103
103
  const background = getVariableByName('cardAdvisory/background', modes) || '#ffffff'
104
104
 
@@ -0,0 +1,295 @@
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 Button from '../Button/Button'
13
+ import InstitutionBadge from '../InstitutionBadge/InstitutionBadge'
14
+ import type { UnifiedSource } from '../../utils/MediaSource'
15
+
16
+ export type CardBankAccountItem = {
17
+ /** Left-aligned label text (e.g. "Account type"). */
18
+ label: string
19
+ /**
20
+ * Right-aligned value. Strings render through the `text/*` design tokens.
21
+ * Pass any `ReactNode` to fully customise the value (e.g. obfuscated
22
+ * account numbers, masked dots, icons).
23
+ */
24
+ value: React.ReactNode
25
+ }
26
+
27
+ export type CardBankAccountProps = {
28
+ /**
29
+ * Institution name shown in the header next to the avatar. Pass an empty
30
+ * string and `headerSlot` to fully replace the default header.
31
+ */
32
+ institutionName?: string
33
+ /**
34
+ * Avatar source for the institution. Forwarded to the underlying
35
+ * `InstitutionBadge`. Accepts any {@link UnifiedSource}.
36
+ */
37
+ institutionAvatar?: UnifiedSource
38
+ /**
39
+ * Header badge. Pass a string to render the default `Badge` with that
40
+ * label, or a custom `ReactNode` for full control. Pass `false`/`null`
41
+ * to hide the badge entirely.
42
+ */
43
+ badge?: React.ReactNode | string | false | null
44
+ /**
45
+ * Override the entire header. When provided, replaces the default
46
+ * `InstitutionBadge` + `Badge` row.
47
+ */
48
+ headerSlot?: React.ReactNode
49
+ /**
50
+ * Data rows shown in the middle. Each entry renders a left-aligned label
51
+ * and a right-aligned value. Defaults to three placeholder rows that
52
+ * match the Figma reference.
53
+ */
54
+ items?: CardBankAccountItem[]
55
+ /**
56
+ * Custom content slot rendered between the header and the footer.
57
+ * Overrides `items` when provided.
58
+ */
59
+ children?: React.ReactNode
60
+ /** Footer button label. Ignored when `footer` is provided. */
61
+ buttonLabel?: string
62
+ /** Footer button press handler. Ignored when `footer` is provided. */
63
+ onButtonPress?: () => void
64
+ /**
65
+ * Override the footer. Pass a `ReactNode` for full control or
66
+ * `false`/`null` to hide it entirely.
67
+ */
68
+ footer?: React.ReactNode | false | null
69
+ /** Design token modes for theming (e.g. `{ 'Color Mode': 'Light' }`). */
70
+ modes?: Record<string, any>
71
+ /** Container style override. */
72
+ style?: StyleProp<ViewStyle>
73
+ /** Accessibility label for the card region. */
74
+ accessibilityLabel?: string
75
+ }
76
+
77
+ const DEFAULT_ITEMS: CardBankAccountItem[] = [
78
+ { label: 'Account type', value: 'Korem ipsum' },
79
+ { label: 'Account number', value: 'Korem ipsum' },
80
+ { label: 'Last updated', value: 'Korem ipsum' },
81
+ ]
82
+
83
+ const toNumber = (value: unknown, fallback: number): number => {
84
+ if (typeof value === 'number') return Number.isFinite(value) ? value : fallback
85
+ if (typeof value === 'string') {
86
+ const parsed = Number(value)
87
+ return Number.isFinite(parsed) ? parsed : fallback
88
+ }
89
+ return fallback
90
+ }
91
+
92
+ const toFontWeight = (value: unknown, fallback: TextStyle['fontWeight']): TextStyle['fontWeight'] => {
93
+ if (typeof value === 'number') return String(value) as TextStyle['fontWeight']
94
+ if (typeof value === 'string') return value as TextStyle['fontWeight']
95
+ return fallback
96
+ }
97
+
98
+ /**
99
+ * `CardBankAccount` renders a bank account summary card composed of:
100
+ *
101
+ * 1. A header with an `InstitutionBadge` (avatar + bank name) and an
102
+ * optional trailing `Badge`.
103
+ * 2. A configurable list of label/value rows describing the account.
104
+ * 3. A footer `Button` for the primary action.
105
+ *
106
+ * All values resolve through the `bankAccountCard/*` design tokens with
107
+ * sensible Figma defaults so the card renders correctly out of the box.
108
+ *
109
+ * @component
110
+ * @param {CardBankAccountProps} props
111
+ */
112
+ function CardBankAccount({
113
+ institutionName = 'State Bank of India',
114
+ institutionAvatar,
115
+ badge,
116
+ headerSlot,
117
+ items = DEFAULT_ITEMS,
118
+ children,
119
+ buttonLabel = 'Button',
120
+ onButtonPress,
121
+ footer,
122
+ modes = EMPTY_MODES,
123
+ style,
124
+ accessibilityLabel,
125
+ }: CardBankAccountProps) {
126
+ const background =
127
+ (getVariableByName('bankAccountCard/background', modes) as string | null) ?? '#ffffff'
128
+ const radius = toNumber(getVariableByName('bankAccountCard/radius', modes), 16)
129
+ const paddingHorizontal = toNumber(
130
+ getVariableByName('bankAccountCard/padding/horizontal', modes),
131
+ 12
132
+ )
133
+ const paddingVertical = toNumber(
134
+ getVariableByName('bankAccountCard/padding/vertical', modes),
135
+ 16
136
+ )
137
+ const gap = toNumber(getVariableByName('bankAccountCard/gap', modes), 16)
138
+ const headerGap = toNumber(getVariableByName('bankAccountCard/header/gap', modes), 8)
139
+
140
+ const listGroupGap = toNumber(getVariableByName('listGroup/gap', modes), 12)
141
+ const rowGap = toNumber(getVariableByName('listItem/gap', modes), 8)
142
+
143
+ const titleColor =
144
+ (getVariableByName('listItem/title/color', modes) as string | null) ?? '#0f0d0a'
145
+ const titleFontFamily =
146
+ (getVariableByName('listItem/title/fontFamily', modes) as string | null) ?? 'JioType Var'
147
+ const titleFontSize = toNumber(getVariableByName('listItem/title/fontSize', modes), 14)
148
+ const titleLineHeight = toNumber(getVariableByName('listItem/title/lineHeight', modes), 16)
149
+ const titleFontWeight = toFontWeight(
150
+ getVariableByName('listItem/title/fontWeight', modes),
151
+ '700'
152
+ )
153
+
154
+ const valueColor =
155
+ (getVariableByName('text/foreground', modes) as string | null) ?? '#000000'
156
+ const valueFontFamily =
157
+ (getVariableByName('text/fontFamily', modes) as string | null) ?? 'JioType Var'
158
+ const valueFontSize = toNumber(getVariableByName('text/fontSize', modes), 14)
159
+ const valueLineHeight = toNumber(getVariableByName('text/lineHeight', modes), 20)
160
+ const valueLetterSpacing = toNumber(getVariableByName('text/letterSpacing', modes), -0.5)
161
+ const valueFontWeight = toFontWeight(getVariableByName('text/fontWeight', modes), '500')
162
+
163
+ const labelStyle: TextStyle = {
164
+ color: titleColor,
165
+ fontFamily: titleFontFamily,
166
+ fontSize: titleFontSize,
167
+ lineHeight: titleLineHeight,
168
+ fontWeight: titleFontWeight,
169
+ }
170
+
171
+ const valueTextStyle: TextStyle = {
172
+ color: valueColor,
173
+ fontFamily: valueFontFamily,
174
+ fontSize: valueFontSize,
175
+ lineHeight: valueLineHeight,
176
+ letterSpacing: valueLetterSpacing,
177
+ fontWeight: valueFontWeight,
178
+ }
179
+
180
+ const renderBadge = (): React.ReactNode => {
181
+ if (badge === false || badge === null || badge === undefined) return null
182
+ if (typeof badge === 'string') {
183
+ return <Badge label={badge} modes={modes} />
184
+ }
185
+ const processed = cloneChildrenWithModes(badge, modes)
186
+ return processed.length === 1 ? processed[0] : processed
187
+ }
188
+
189
+ const renderHeader = (): React.ReactNode => {
190
+ if (headerSlot !== undefined) {
191
+ const processed = cloneChildrenWithModes(headerSlot, modes)
192
+ return processed.length === 1 ? processed[0] : processed
193
+ }
194
+ return (
195
+ <View
196
+ style={{
197
+ flexDirection: 'row',
198
+ alignItems: 'center',
199
+ gap: headerGap,
200
+ width: '100%',
201
+ }}
202
+ >
203
+ <View style={{ flex: 1, minWidth: 0 }}>
204
+ <InstitutionBadge
205
+ label={institutionName}
206
+ {...(institutionAvatar !== undefined ? { source: institutionAvatar } : {})}
207
+ modes={modes}
208
+ />
209
+ </View>
210
+ {renderBadge()}
211
+ </View>
212
+ )
213
+ }
214
+
215
+ const renderItems = (): React.ReactNode => {
216
+ if (children !== undefined && children !== null) {
217
+ const processed = cloneChildrenWithModes(children, modes)
218
+ if (processed.length === 0) return null
219
+ return (
220
+ <View style={{ width: '100%', gap: listGroupGap }}>
221
+ {processed.length === 1 ? processed[0] : processed}
222
+ </View>
223
+ )
224
+ }
225
+
226
+ if (!items || items.length === 0) return null
227
+
228
+ return (
229
+ <View style={{ width: '100%', gap: listGroupGap }}>
230
+ {items.map((item, index) => (
231
+ <View
232
+ key={`${item.label}-${index}`}
233
+ style={{
234
+ flexDirection: 'row',
235
+ alignItems: 'center',
236
+ gap: rowGap,
237
+ width: '100%',
238
+ }}
239
+ >
240
+ <View style={{ flex: 1, minWidth: 0 }}>
241
+ <Text style={labelStyle}>{item.label}</Text>
242
+ </View>
243
+ {typeof item.value === 'string' || typeof item.value === 'number' ? (
244
+ <Text style={valueTextStyle} numberOfLines={1}>
245
+ {item.value}
246
+ </Text>
247
+ ) : (
248
+ cloneChildrenWithModes(item.value, modes)
249
+ )}
250
+ </View>
251
+ ))}
252
+ </View>
253
+ )
254
+ }
255
+
256
+ const renderFooter = (): React.ReactNode => {
257
+ if (footer === false || footer === null) return null
258
+ if (footer === undefined) {
259
+ return (
260
+ <Button
261
+ label={buttonLabel}
262
+ {...(onButtonPress ? { onPress: onButtonPress } : {})}
263
+ modes={modes}
264
+ style={{ width: '100%' }}
265
+ />
266
+ )
267
+ }
268
+ const processed = cloneChildrenWithModes(footer, modes)
269
+ return processed.length === 1 ? processed[0] : processed
270
+ }
271
+
272
+ return (
273
+ <View
274
+ accessibilityLabel={accessibilityLabel ?? institutionName}
275
+ style={[
276
+ {
277
+ backgroundColor: background,
278
+ borderRadius: radius,
279
+ paddingHorizontal,
280
+ paddingVertical,
281
+ gap,
282
+ alignItems: 'flex-start',
283
+ width: '100%',
284
+ },
285
+ style,
286
+ ]}
287
+ >
288
+ {renderHeader()}
289
+ {renderItems()}
290
+ {renderFooter()}
291
+ </View>
292
+ )
293
+ }
294
+
295
+ export default CardBankAccount