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,256 @@
1
+ import React from 'react'
2
+ import { View, type StyleProp, type ViewStyle } from 'react-native'
3
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
4
+ import { useTokens } from '../../design-tokens/JFSThemeProvider'
5
+ import { EMPTY_MODES } from '../../utils/react-utils'
6
+ import DonutChart, { type DonutChartSegmentData } from '../DonutChart/DonutChart'
7
+ import MetricLegendItem from '../MetricLegendItem/MetricLegendItem'
8
+
9
+ /**
10
+ * One row of the `DonutChartSummary`. Each item drives BOTH a donut
11
+ * segment and the matching legend row, so the segment and indicator
12
+ * always share the same color by construction.
13
+ */
14
+ export type DonutChartSummaryItem = {
15
+ /** Stable React key. Falls back to the item label / index. */
16
+ key?: React.Key
17
+ /** The descriptive label rendered next to the indicator dot. */
18
+ label?: React.ReactNode
19
+ /**
20
+ * The data point — drives the segment's angular share. Same
21
+ * semantics as `DonutChartSegmentData.value`. When the legend's
22
+ * right-side text is shown, the resolution order is:
23
+ * `displayValue` → `formatValue(value)` → hide.
24
+ */
25
+ value: number
26
+ /**
27
+ * Optional override for the legend row's right-side text. Falls
28
+ * back to `formatValue(value)` if a parent-level `formatValue` is
29
+ * provided; otherwise the value slot is hidden (matches the Figma
30
+ * `data` boolean toggle on `MetricLegendItem`). Pass `null` or
31
+ * `false` to explicitly hide on a per-item basis.
32
+ */
33
+ displayValue?: React.ReactNode
34
+ /**
35
+ * Hard-override the shared color used for both the donut segment
36
+ * and the legend indicator. Bypasses `dataViz/bg` token resolution.
37
+ */
38
+ color?: string
39
+ /**
40
+ * Per-item design token mode overrides. Merged on top of parent
41
+ * `modes` and the per-index `Appearance / DataViz` defaults
42
+ * (`Senary`, `Primary`, `Secondary`, `Tertiary`, `Quaternary`,
43
+ * `Quinary`, then cycles).
44
+ */
45
+ modes?: Record<string, any>
46
+ /** Accessibility label for the segment + legend row pairing. */
47
+ accessibilityLabel?: string
48
+ }
49
+
50
+ export type DonutChartSummaryProps = {
51
+ /**
52
+ * Data-driven rows. Each entry produces a donut segment AND a
53
+ * matching `MetricLegendItem` — the count of legend rows is locked
54
+ * in step with the count of donut segments by construction.
55
+ */
56
+ items?: DonutChartSummaryItem[]
57
+ /**
58
+ * Optional formatter applied to each item's numeric `value` when
59
+ * the item does not provide an explicit `displayValue`. Useful for
60
+ * the common "format every weight the same way" case
61
+ * (e.g. `(v) => `${v}%``).
62
+ */
63
+ formatValue?: (value: number) => React.ReactNode
64
+ /** Center value text inside the donut (large/bold). */
65
+ centerValue?: React.ReactNode
66
+ /** Center label text inside the donut (smaller, beneath the value). */
67
+ centerLabel?: React.ReactNode
68
+ /**
69
+ * Slot for fully-custom donut center content. When provided it
70
+ * replaces the default `centerValue`/`centerLabel` text wrap.
71
+ */
72
+ donutCenter?: React.ReactNode
73
+ /** Outer donut diameter in px. Defaults to 194 (Figma reference). */
74
+ donutSize?: number
75
+ /** Donut ring stroke width in px. Defaults to ~18% of `donutSize`. */
76
+ donutStrokeWidth?: number
77
+ /**
78
+ * Visual gap between adjacent donut segments expressed in degrees
79
+ * of arc. Defaults to 0 (segments touch).
80
+ */
81
+ donutGap?: number
82
+ /** Design token modes for theming (e.g. `{ 'Color Mode': 'Light' }`). */
83
+ modes?: Record<string, any>
84
+ /** Override outer container styles. */
85
+ style?: StyleProp<ViewStyle>
86
+ /** Override the legend container styles. */
87
+ legendStyle?: StyleProp<ViewStyle>
88
+ /** Accessibility label announced for the whole summary. */
89
+ accessibilityLabel?: string
90
+ }
91
+
92
+ const DEFAULT_APPEARANCE_CYCLE = [
93
+ 'Senary',
94
+ 'Primary',
95
+ 'Secondary',
96
+ 'Tertiary',
97
+ 'Quaternary',
98
+ 'Quinary',
99
+ ] as const
100
+
101
+ const DEFAULT_ITEMS: DonutChartSummaryItem[] = [
102
+ { label: 'Equity', value: 40, displayValue: '40%' },
103
+ { label: 'Recommended coverage', value: 25, displayValue: '25%' },
104
+ { label: 'Additional benefits', value: 20, displayValue: '20%' },
105
+ { label: 'Cost analysis', value: 15, displayValue: '15%' },
106
+ ]
107
+
108
+ const defaultAppearanceFor = (index: number) =>
109
+ DEFAULT_APPEARANCE_CYCLE[index % DEFAULT_APPEARANCE_CYCLE.length]
110
+
111
+ /**
112
+ * Resolve the shared color for an item. Honors any explicit `color`
113
+ * override, then falls back to `dataViz/bg` for the merged mode set,
114
+ * then to the Figma reference purple.
115
+ */
116
+ function resolveItemColor(
117
+ parentModes: Record<string, any>,
118
+ item: DonutChartSummaryItem,
119
+ index: number
120
+ ): string {
121
+ if (item.color) return item.color
122
+ const itemModes = {
123
+ ...parentModes,
124
+ 'Appearance / DataViz': defaultAppearanceFor(index),
125
+ ...(item.modes || {}),
126
+ }
127
+ return (
128
+ (getVariableByName('dataViz/bg', itemModes) as string | null) ?? '#5d00b5'
129
+ )
130
+ }
131
+
132
+ /**
133
+ * `DonutChartSummary` pairs a `DonutChart` with a vertical list of
134
+ * `MetricLegendItem` rows. The component takes a single `items` array,
135
+ * so the number of legend rows is locked in step with the number of
136
+ * donut segments by construction — every segment has exactly one
137
+ * legend row, and they share the same color through the same
138
+ * `Appearance / DataViz` cascade as the standalone `DonutChart`.
139
+ *
140
+ * The default 4-item layout receives per-index `Appearance / DataViz`
141
+ * defaults (item 1 → `Senary`, 2 → `Primary`, 3 → `Secondary`, 4 →
142
+ * `Tertiary`, then cycles). Override `modes` per item to remix, or set
143
+ * `Emphasis / DataViz` on the parent `modes` to dim or brighten the
144
+ * whole component at once.
145
+ *
146
+ * @component
147
+ * @param {DonutChartSummaryProps} props
148
+ */
149
+ function DonutChartSummary({
150
+ items,
151
+ formatValue,
152
+ centerValue = '₹51,230',
153
+ centerLabel = 'Total invested',
154
+ donutCenter,
155
+ donutSize = 194,
156
+ donutStrokeWidth,
157
+ donutGap = 0,
158
+ modes: propModes = EMPTY_MODES,
159
+ style,
160
+ legendStyle,
161
+ accessibilityLabel,
162
+ }: DonutChartSummaryProps) {
163
+ const { modes: globalModes } = useTokens()
164
+ const modes = { ...globalModes, ...propModes }
165
+
166
+ const gap =
167
+ (getVariableByName('donutChartSummary/gap', modes) as number | null) ?? 16
168
+ const legendGap =
169
+ (getVariableByName('donutChartSummary/legend/gap', modes) as
170
+ | number
171
+ | null) ?? 8
172
+
173
+ const resolvedItems = items && items.length > 0 ? items : DEFAULT_ITEMS
174
+
175
+ const segments: DonutChartSegmentData[] = resolvedItems.map(
176
+ (item, index) => ({
177
+ key: item.key ?? `segment-${index}`,
178
+ value: item.value,
179
+ color: resolveItemColor(modes, item, index),
180
+ accessibilityLabel: item.accessibilityLabel,
181
+ })
182
+ )
183
+
184
+ const showCustomCenter = donutCenter !== undefined && donutCenter !== null
185
+
186
+ return (
187
+ <View
188
+ accessibilityRole="summary"
189
+ accessibilityLabel={accessibilityLabel}
190
+ style={[
191
+ {
192
+ width: '100%',
193
+ alignItems: 'center',
194
+ gap,
195
+ },
196
+ style,
197
+ ]}
198
+ >
199
+ <DonutChart
200
+ size={donutSize}
201
+ {...(donutStrokeWidth !== undefined && {
202
+ strokeWidth: donutStrokeWidth,
203
+ })}
204
+ gap={donutGap}
205
+ segments={segments}
206
+ modes={modes}
207
+ {...(!showCustomCenter && {
208
+ value: centerValue,
209
+ label: centerLabel,
210
+ })}
211
+ >
212
+ {showCustomCenter ? donutCenter : null}
213
+ </DonutChart>
214
+
215
+ <View
216
+ style={[
217
+ {
218
+ width: '100%',
219
+ gap: legendGap,
220
+ },
221
+ legendStyle,
222
+ ]}
223
+ >
224
+ {resolvedItems.map((item, index) => (
225
+ <MetricLegendItem
226
+ key={item.key ?? `legend-${index}`}
227
+ label={item.label}
228
+ value={resolveLegendValue(item, formatValue)}
229
+ indicatorColor={resolveItemColor(modes, item, index)}
230
+ modes={modes}
231
+ />
232
+ ))}
233
+ </View>
234
+ </View>
235
+ )
236
+ }
237
+
238
+ /**
239
+ * Resolve what to render in the legend row's right-side slot. The
240
+ * order of precedence is:
241
+ * 1. `item.displayValue` if explicitly provided (including `null` /
242
+ * `false`, which the underlying `MetricLegendItem` treats as
243
+ * "hide the value slot").
244
+ * 2. `formatValue(item.value)` when a parent-level formatter exists.
245
+ * 3. `undefined` — the value slot is hidden.
246
+ */
247
+ function resolveLegendValue(
248
+ item: DonutChartSummaryItem,
249
+ formatValue: ((value: number) => React.ReactNode) | undefined
250
+ ): React.ReactNode {
251
+ if (item.displayValue !== undefined) return item.displayValue
252
+ if (formatValue) return formatValue(item.value)
253
+ return undefined
254
+ }
255
+
256
+ export default DonutChartSummary
@@ -9,7 +9,7 @@ import {
9
9
  type ImageSourcePropType,
10
10
  } from 'react-native'
11
11
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
12
- import RadioButton from '../RadioButton/RadioButton'
12
+ import Radio from '../Radio/Radio'
13
13
  import { EMPTY_MODES } from '../../utils/react-utils'
14
14
 
15
15
  export interface HoldingsCardDetailItem {
@@ -198,7 +198,7 @@ export default function HoldingsCard({
198
198
 
199
199
  {/* Pointer events disabled — the whole card is the pressable target */}
200
200
  <View pointerEvents="none">
201
- <RadioButton
201
+ <Radio
202
202
  selected={selected}
203
203
  disabled={disabled}
204
204
  modes={modes}
@@ -0,0 +1,216 @@
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 MediaSource, { type UnifiedSource } from '../../utils/MediaSource'
13
+
14
+ // Default avatar asset (shared with the Avatar component) so that
15
+ // InstitutionBadge has a sensible visual fallback when no `source` is
16
+ // provided in stories or playgrounds.
17
+ const DEFAULT_AVATAR_IMAGE = require('../Avatar/31595e70c4181263f9971590224b12934b280c9b.png')
18
+
19
+ type InstitutionBadgeBaseProps = Omit<
20
+ React.ComponentProps<typeof View>,
21
+ 'children' | 'style'
22
+ >
23
+
24
+ export type InstitutionBadgeProps = InstitutionBadgeBaseProps & {
25
+ /** Visible label for the institution (e.g. bank name). */
26
+ label?: string
27
+ /**
28
+ * Unified avatar source. Accepts a remote URI (raster or `.svg`), an
29
+ * inline SVG XML string, a `require()` asset, an SVG React component,
30
+ * or an already-rendered React element. Smart-detects raster vs SVG so
31
+ * the same prop works on iOS, Android and web. See {@link UnifiedSource}.
32
+ * Ignored when `avatarSlot` is provided.
33
+ */
34
+ source?: UnifiedSource
35
+ /** Slot replacing the default Avatar (e.g. for monogram avatars). Receives `modes` recursively. */
36
+ avatarSlot?: React.ReactNode
37
+ /** Design token modes forwarded to token lookups and the Avatar slot. */
38
+ modes?: Record<string, any>
39
+ /** Container style override. */
40
+ style?: StyleProp<ViewStyle>
41
+ /** Label style override. */
42
+ labelStyle?: StyleProp<TextStyle>
43
+ /** Accessibility label. Defaults to `label`. */
44
+ accessibilityLabel?: string
45
+ }
46
+
47
+ interface InstitutionBadgeTokens {
48
+ containerStyle: ViewStyle
49
+ avatarStyle: ViewStyle
50
+ avatarSize: number
51
+ labelStyle: TextStyle
52
+ }
53
+
54
+ const toNumber = (value: unknown, fallback: number) => {
55
+ if (typeof value === 'number') {
56
+ return Number.isFinite(value) ? value : fallback
57
+ }
58
+
59
+ if (typeof value === 'string') {
60
+ const parsed = Number(value)
61
+ return Number.isFinite(parsed) ? parsed : fallback
62
+ }
63
+
64
+ return fallback
65
+ }
66
+
67
+ const toFontWeight = (value: unknown, fallback: TextStyle['fontWeight']) => {
68
+ if (typeof value === 'number') {
69
+ return String(value) as TextStyle['fontWeight']
70
+ }
71
+
72
+ if (typeof value === 'string') {
73
+ return value as TextStyle['fontWeight']
74
+ }
75
+
76
+ return fallback
77
+ }
78
+
79
+ function resolveInstitutionBadgeTokens(
80
+ modes: Record<string, any>
81
+ ): InstitutionBadgeTokens {
82
+ const gap = toNumber(getVariableByName('institutionBadge/gap', modes), 8)
83
+
84
+ const foreground =
85
+ (getVariableByName('institutionBadge/foreground', modes) as string) ||
86
+ '#080d1a'
87
+ const fontSize = toNumber(
88
+ getVariableByName('institutionBadge/fontSize', modes),
89
+ 14
90
+ )
91
+ const fontFamily =
92
+ (getVariableByName('institutionBadge/fontFamily', modes) as string) ||
93
+ 'JioType Var'
94
+ const lineHeight = toNumber(
95
+ getVariableByName('institutionBadge/lineHeight', modes),
96
+ 18
97
+ )
98
+ const fontWeight = toFontWeight(
99
+ getVariableByName('institutionBadge/fontWeight', modes),
100
+ '500'
101
+ )
102
+
103
+ // Avatar wrapper styled from shared `avatar/*` tokens so the badge stays
104
+ // visually consistent with other Avatar-bearing components.
105
+ const avatarSize = toNumber(getVariableByName('avatar/size', modes), 36)
106
+ const avatarRadiusRaw = toNumber(
107
+ getVariableByName('avatar/radius', modes),
108
+ 9999
109
+ )
110
+ // 9999 is the design-token sentinel for "perfect circle".
111
+ const avatarRadius = avatarRadiusRaw === 9999 ? avatarSize / 2 : avatarRadiusRaw
112
+ const avatarBorderColor =
113
+ (getVariableByName('avatar/border/color', modes) as string) ||
114
+ 'rgba(255,255,255,0)'
115
+ const avatarBorderSize = toNumber(
116
+ getVariableByName('avatar/border/size', modes),
117
+ 1
118
+ )
119
+
120
+ return {
121
+ containerStyle: {
122
+ alignItems: 'center',
123
+ flexDirection: 'row',
124
+ gap,
125
+ },
126
+ avatarStyle: {
127
+ width: avatarSize,
128
+ height: avatarSize,
129
+ borderRadius: avatarRadius,
130
+ borderWidth: avatarBorderSize,
131
+ borderColor: avatarBorderColor,
132
+ overflow: 'hidden',
133
+ },
134
+ avatarSize,
135
+ labelStyle: {
136
+ color: foreground,
137
+ fontFamily,
138
+ fontSize,
139
+ fontWeight,
140
+ lineHeight,
141
+ },
142
+ }
143
+ }
144
+
145
+ // Default Avatar Size = M (36px in tokens) — matches the Figma badge. Callers
146
+ // can override via `modes` if needed.
147
+ const DEFAULT_AVATAR_MODE = 'M'
148
+
149
+ function InstitutionBadge({
150
+ label = 'State Bank of India',
151
+ source,
152
+ avatarSlot,
153
+ modes: propModes = EMPTY_MODES,
154
+ style,
155
+ labelStyle,
156
+ accessibilityLabel,
157
+ ...rest
158
+ }: InstitutionBadgeProps) {
159
+ const { modes: globalModes } = useTokens()
160
+ const modes = useMemo(
161
+ () =>
162
+ globalModes === EMPTY_MODES && propModes === EMPTY_MODES
163
+ ? EMPTY_MODES
164
+ : { ...globalModes, ...propModes },
165
+ [globalModes, propModes]
166
+ )
167
+
168
+ const avatarModes = useMemo(
169
+ () => ({ 'Avatar Size': DEFAULT_AVATAR_MODE, ...modes }),
170
+ [modes]
171
+ )
172
+
173
+ const tokens = useMemo(
174
+ () => resolveInstitutionBadgeTokens(avatarModes),
175
+ [avatarModes]
176
+ )
177
+
178
+ const processedAvatarSlot = useMemo(() => {
179
+ if (!avatarSlot) return null
180
+ const processed = cloneChildrenWithModes(
181
+ React.Children.toArray(avatarSlot),
182
+ avatarModes
183
+ )
184
+ return processed.length === 1 ? processed[0] : processed
185
+ }, [avatarSlot, avatarModes])
186
+
187
+ const resolvedSource: UnifiedSource =
188
+ (source as UnifiedSource | undefined) ?? (DEFAULT_AVATAR_IMAGE as UnifiedSource)
189
+
190
+ const defaultAccessibilityLabel = accessibilityLabel ?? label
191
+
192
+ return (
193
+ <View
194
+ accessibilityLabel={defaultAccessibilityLabel}
195
+ style={[tokens.containerStyle, style]}
196
+ {...rest}
197
+ >
198
+ {processedAvatarSlot || (
199
+ <View style={tokens.avatarStyle}>
200
+ <MediaSource
201
+ source={resolvedSource}
202
+ size={tokens.avatarSize}
203
+ resizeMode="cover"
204
+ accessibilityElementsHidden={true}
205
+ importantForAccessibility="no"
206
+ />
207
+ </View>
208
+ )}
209
+ <Text style={[tokens.labelStyle, labelStyle]} numberOfLines={1}>
210
+ {label}
211
+ </Text>
212
+ </View>
213
+ )
214
+ }
215
+
216
+ export default React.memo(InstitutionBadge)
@@ -1,8 +1,9 @@
1
1
  import React from 'react'
2
- import { View, Text, type StyleProp, type ViewStyle, type TextStyle, type DimensionValue } from 'react-native'
2
+ import { View, Text, type StyleProp, type ViewStyle, type TextStyle } from 'react-native'
3
3
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
4
4
  import { EMPTY_MODES } from '../../utils/react-utils'
5
5
  import MoneyValue from '../MoneyValue/MoneyValue'
6
+ import LinearProgress from '../LinearProgress/LinearProgress'
6
7
 
7
8
  type LinearMeterLabelProps = {
8
9
  children: React.ReactNode
@@ -57,14 +58,6 @@ const LinearMeter = ({
57
58
  ...rest
58
59
  }: LinearMeterProps) => {
59
60
  const gap = getVariableByName('linearMeter/gap', modes)
60
- // Track tokens
61
- const trackBg = getVariableByName('linearMeter/track/background', modes)
62
- const trackHeight = getVariableByName('linearMeter/track/height', modes)
63
- const trackRadius = getVariableByName('linearMeter/track/radius', modes)
64
-
65
- // Indicator tokens
66
- const indicatorBg = getVariableByName('linearMeter/indicator/background', modes)
67
- const indicatorRadius = getVariableByName('linearMeter/indicator/radius', modes)
68
61
 
69
62
  // Wrap tokens
70
63
  const wrapGap = getVariableByName('linearMeter/wrap/gap', modes)
@@ -94,10 +87,6 @@ const LinearMeter = ({
94
87
 
95
88
  const content = children ? childrenWithModes : defaultContent
96
89
 
97
- // Calculate width percentage
98
- const clampedValue = Math.min(Math.max(value, 0), 1)
99
- const widthPercent = `${clampedValue * 100}%`
100
-
101
90
  return (
102
91
  <View
103
92
  style={[
@@ -110,32 +99,13 @@ const LinearMeter = ({
110
99
  ]}
111
100
  {...rest}
112
101
  >
113
- {/* Track */}
114
- <View
115
- style={[
116
- {
117
- flex: 1,
118
- height: trackHeight,
119
- backgroundColor: trackBg,
120
- borderRadius: trackRadius,
121
- overflow: 'hidden', // Ensure indicator stays inside
122
- },
123
- trackStyle,
124
- ]}
125
- >
126
- {/* Indicator */}
127
- <View
128
- style={[
129
- {
130
- width: widthPercent as DimensionValue,
131
- height: '100%',
132
- backgroundColor: indicatorBg,
133
- borderRadius: indicatorRadius,
134
- },
135
- indicatorStyle,
136
- ]}
137
- />
138
- </View>
102
+ <LinearProgress
103
+ value={value}
104
+ modes={modes}
105
+ style={{ flex: 1 }}
106
+ trackStyle={trackStyle}
107
+ indicatorStyle={indicatorStyle}
108
+ />
139
109
 
140
110
  {/* Right Slot Wrapper */}
141
111
  <View
@@ -0,0 +1,92 @@
1
+ import React from 'react'
2
+ import {
3
+ View,
4
+ type StyleProp,
5
+ type ViewStyle,
6
+ type DimensionValue,
7
+ } from 'react-native'
8
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
9
+ import { EMPTY_MODES } from '../../utils/react-utils'
10
+
11
+ export type LinearProgressProps = {
12
+ /** Progress value between 0 and 1. Values are clamped. */
13
+ value?: number
14
+ /** Design token modes for theming */
15
+ modes?: Record<string, any>
16
+ /** Override container styles (the track wrapper) */
17
+ style?: StyleProp<ViewStyle>
18
+ /** Override the track styles */
19
+ trackStyle?: StyleProp<ViewStyle>
20
+ /** Override the indicator styles */
21
+ indicatorStyle?: StyleProp<ViewStyle>
22
+ } & Omit<React.ComponentProps<typeof View>, 'style'>
23
+
24
+ const LinearProgress = ({
25
+ value = 0,
26
+ modes = EMPTY_MODES,
27
+ style,
28
+ trackStyle,
29
+ indicatorStyle,
30
+ ...rest
31
+ }: LinearProgressProps) => {
32
+ // The track and the progress indicator are intentionally rendered at
33
+ // different emphasis levels by default: the track sits in the
34
+ // background as a low-emphasis surface, while the progress indicator
35
+ // is the high-emphasis foreground. Defaults are placed *before* the
36
+ // user-provided modes spread, so callers can still override
37
+ // `Emphasis / DataViz` (or any other mode) via the `modes` prop.
38
+ const trackModes = { 'Emphasis': 'Low', ...modes }
39
+ const progressModes = { 'Emphasis': 'High', ...modes }
40
+
41
+ const trackHeight =
42
+ (getVariableByName('linearProgress/track/height', trackModes) as number | null) ?? 8
43
+ const trackRadius =
44
+ (getVariableByName('linearProgress/track/radius', trackModes) as number | null) ?? 999
45
+ const trackBg =
46
+ (getVariableByName('linearProgress/track/background', trackModes) as string | null) ??
47
+ '#ede7ff'
48
+
49
+ const indicatorHeight =
50
+ (getVariableByName('linearProgress/indicator/height', progressModes) as number | null) ?? 8
51
+ const indicatorRadius =
52
+ (getVariableByName('linearProgress/indicator/radius', progressModes) as number | null) ?? 999
53
+ const indicatorBg =
54
+ (getVariableByName('linearProgress/indicator/background', progressModes) as string | null) ??
55
+ '#5d00b5'
56
+
57
+ const clampedValue = Math.min(Math.max(value, 0), 1)
58
+ const widthPercent = `${clampedValue * 100}%` as DimensionValue
59
+
60
+ return (
61
+ <View
62
+ style={[
63
+ {
64
+ height: trackHeight,
65
+ backgroundColor: trackBg,
66
+ borderRadius: trackRadius,
67
+ overflow: 'hidden',
68
+ width: '100%',
69
+ },
70
+ style,
71
+ trackStyle,
72
+ ]}
73
+ accessibilityRole="progressbar"
74
+ accessibilityValue={{ min: 0, max: 1, now: clampedValue }}
75
+ {...rest}
76
+ >
77
+ <View
78
+ style={[
79
+ {
80
+ width: widthPercent,
81
+ height: indicatorHeight,
82
+ backgroundColor: indicatorBg,
83
+ borderRadius: indicatorRadius,
84
+ },
85
+ indicatorStyle,
86
+ ]}
87
+ />
88
+ </View>
89
+ )
90
+ }
91
+
92
+ export default LinearProgress