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
@@ -1,22 +1,46 @@
1
1
  import React from 'react'
2
- import { View, Text, type StyleProp, type ViewStyle } from 'react-native'
2
+ import {
3
+ View,
4
+ Text,
5
+ type StyleProp,
6
+ type ViewStyle,
7
+ type TextStyle,
8
+ } from 'react-native'
3
9
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
4
10
  import { EMPTY_MODES } from '../../utils/react-utils'
5
11
 
12
+ export type StatItemLabelPosition = 'Top' | 'Bottom'
13
+
6
14
  export type StatItemProps = {
7
- /** The small descriptive label above the value. */
15
+ /** The small descriptive label. */
8
16
  label?: string
9
- /** The large prominent value to display. */
17
+ /** The prominent value to display. */
10
18
  value?: string
11
- /** Design token modes for theming. */
19
+ /**
20
+ * Whether the label is rendered above or below the value. Matches the
21
+ * Figma `labelPosition` variant. Defaults to `'Top'`.
22
+ *
23
+ * - `'Top'` — Label (12px medium) above large value (26px black). Left-aligned.
24
+ * - `'Bottom'` — Value (12px medium) above smaller label (10px regular). Center-aligned.
25
+ */
26
+ labelPosition?: StatItemLabelPosition
27
+ /** Design token modes for theming (e.g. `{ 'Color Mode': 'Light' }`). */
12
28
  modes?: Record<string, any>
13
29
  /** Override container styles. */
14
30
  style?: StyleProp<ViewStyle>
31
+ /** Override the label text styles. */
32
+ labelStyle?: StyleProp<TextStyle>
33
+ /** Override the value text styles. */
34
+ valueStyle?: StyleProp<TextStyle>
15
35
  }
16
36
 
17
37
  /**
18
- * StatItem displays a label/value pair with the value in a large, bold style.
19
- * Useful for metrics, product stats, or KPI callouts.
38
+ * StatItem displays a label/value pair, useful for product stats, metrics
39
+ * or KPI callouts.
40
+ *
41
+ * Supports two layouts via the `labelPosition` variant:
42
+ * - `'Top'` — Small label above a large prominent value (default).
43
+ * - `'Bottom'` — Value above a smaller label, content centered.
20
44
  *
21
45
  * @component
22
46
  * @param {StatItemProps} props
@@ -24,47 +48,100 @@ export type StatItemProps = {
24
48
  function StatItem({
25
49
  label = 'Purity verified by NABL',
26
50
  value = '99.99%',
51
+ labelPosition = 'Top',
27
52
  modes = EMPTY_MODES,
28
53
  style,
54
+ labelStyle,
55
+ valueStyle,
29
56
  }: StatItemProps) {
30
- const gap = getVariableByName('statItem/gap', modes) ?? 2
57
+ const isBottom = labelPosition === 'Bottom'
58
+
59
+ // The Figma `Bottom` variant overrides token values locally inside the
60
+ // component (it is not exposed in the design-tokens JSON), so the resolved
61
+ // token values reflect only the `Top` variant. For the `Bottom` variant we
62
+ // therefore use hardcoded literals that match the Figma design, while still
63
+ // allowing the resolver to surface any future mode-based overrides for the
64
+ // `Top` variant.
65
+ const gap = isBottom
66
+ ? 6
67
+ : ((getVariableByName('statItem/gap', modes) as number | null) ?? 2)
68
+
69
+ const labelForeground = isBottom
70
+ ? '#24262b'
71
+ : ((getVariableByName('statItem/label/foreground', modes) as string | null) ?? '#1a1c1f')
72
+ const labelFontFamily =
73
+ (getVariableByName('statItem/label/fontFamily', modes) as string | null) ?? 'JioType Var'
74
+ const labelFontSize = isBottom
75
+ ? 10
76
+ : ((getVariableByName('statItem/label/fontSize', modes) as number | null) ?? 12)
77
+ const labelFontWeightRaw = isBottom
78
+ ? 400
79
+ : (getVariableByName('statItem/label/fontWeight', modes) ?? 500)
80
+ const labelFontWeight = String(labelFontWeightRaw) as TextStyle['fontWeight']
81
+ const labelLineHeight = isBottom
82
+ ? 13
83
+ : ((getVariableByName('statItem/label/lineHeight', modes) as number | null) ?? 16)
84
+
85
+ const valueForeground = isBottom
86
+ ? '#141414'
87
+ : ((getVariableByName('statItem/value/foreground', modes) as string | null) ?? '#0d0d0f')
88
+ const valueFontFamily =
89
+ (getVariableByName('statItem/value/fontFamily', modes) as string | null) ?? 'JioType Var'
90
+ const valueFontSize = isBottom
91
+ ? 12
92
+ : ((getVariableByName('statItem/value/fontSize', modes) as number | null) ?? 26)
93
+ const valueFontWeightRaw = isBottom
94
+ ? 500
95
+ : (getVariableByName('statItem/value/fontWeight', modes) ?? 900)
96
+ const valueFontWeight = String(valueFontWeightRaw) as TextStyle['fontWeight']
97
+ const valueLineHeight = isBottom
98
+ ? 16
99
+ : ((getVariableByName('statItem/value/lineHeight', modes) as number | null) ?? 26)
31
100
 
32
- const labelForeground = getVariableByName('statItem/label/foreground', modes) ?? '#1a1c1f'
33
- const labelFontFamily = getVariableByName('statItem/label/fontFamily', modes) ?? 'JioType Var'
34
- const labelFontSize = getVariableByName('statItem/label/fontSize', modes) ?? 12
35
- const labelFontWeight = getVariableByName('statItem/label/fontWeight', modes) ?? '500'
36
- const labelLineHeight = getVariableByName('statItem/label/lineHeight', modes) ?? 16
101
+ const containerStyle: ViewStyle = {
102
+ gap,
103
+ alignItems: isBottom ? 'center' : 'flex-start',
104
+ justifyContent: isBottom ? 'center' : 'flex-start',
105
+ }
37
106
 
38
- const valueForeground = getVariableByName('statItem/value/foreground', modes) ?? '#0d0d0f'
39
- const valueFontFamily = getVariableByName('statItem/value/fontFamily', modes) ?? 'JioType Var'
40
- const valueFontSize = getVariableByName('statItem/value/fontSize', modes) ?? 26
41
- const valueFontWeight = getVariableByName('statItem/value/fontWeight', modes) ?? '900'
42
- const valueLineHeight = getVariableByName('statItem/value/lineHeight', modes) ?? 26
107
+ const labelTextStyle: TextStyle = {
108
+ color: labelForeground,
109
+ fontFamily: labelFontFamily,
110
+ fontSize: labelFontSize,
111
+ fontWeight: labelFontWeight,
112
+ lineHeight: labelLineHeight,
113
+ textAlign: isBottom ? 'center' : 'left',
114
+ }
115
+
116
+ const valueTextStyle: TextStyle = {
117
+ color: valueForeground,
118
+ fontFamily: valueFontFamily,
119
+ fontSize: valueFontSize,
120
+ fontWeight: valueFontWeight,
121
+ lineHeight: valueLineHeight,
122
+ textAlign: isBottom ? 'center' : 'left',
123
+ }
124
+
125
+ const labelNode = (
126
+ <Text style={[labelTextStyle, labelStyle]}>{label}</Text>
127
+ )
128
+ const valueNode = (
129
+ <Text style={[valueTextStyle, valueStyle]}>{value}</Text>
130
+ )
43
131
 
44
132
  return (
45
- <View style={[{ gap: gap as number }, style]}>
46
- <Text
47
- style={{
48
- color: labelForeground as string,
49
- fontFamily: labelFontFamily as string,
50
- fontSize: labelFontSize as number,
51
- fontWeight: String(labelFontWeight) as any,
52
- lineHeight: labelLineHeight as number,
53
- }}
54
- >
55
- {label}
56
- </Text>
57
- <Text
58
- style={{
59
- color: valueForeground as string,
60
- fontFamily: valueFontFamily as string,
61
- fontSize: valueFontSize as number,
62
- fontWeight: String(valueFontWeight) as any,
63
- lineHeight: valueLineHeight as number,
64
- }}
65
- >
66
- {value}
67
- </Text>
133
+ <View style={[containerStyle, style]}>
134
+ {isBottom ? (
135
+ <>
136
+ {valueNode}
137
+ {labelNode}
138
+ </>
139
+ ) : (
140
+ <>
141
+ {labelNode}
142
+ {valueNode}
143
+ </>
144
+ )}
68
145
  </View>
69
146
  )
70
147
  }
@@ -0,0 +1,205 @@
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 { EMPTY_MODES } from '../../utils/react-utils'
5
+
6
+ /**
7
+ * Confidence levels supported by the indicator. Mirrors the Figma `Confidence`
8
+ * mode collection exactly (`High` | `Medium` | `Low` | `None`).
9
+ */
10
+ export type StrengthIndicatorConfidence = 'High' | 'Medium' | 'Low' | 'None'
11
+
12
+ /**
13
+ * Accepted shape for the `confidence` prop. Either:
14
+ * - one of the named levels (`'High' | 'Medium' | 'Low' | 'None'`), or
15
+ * - a float in `[0, 1]`. Floats are bucketed to the nearest level using
16
+ * midpoint thresholds (1/6, 1/2, 5/6) so anchor values land squarely:
17
+ * `0` → `None`, `~0.33` → `Low`, `~0.66` → `Medium`, `1` → `High`.
18
+ * Values outside `[0, 1]` are clamped.
19
+ */
20
+ export type StrengthIndicatorConfidenceValue =
21
+ | StrengthIndicatorConfidence
22
+ | number
23
+
24
+ export type StrengthIndicatorProps = {
25
+ /**
26
+ * Overall confidence level. Accepts either the named enum value
27
+ * (`'High' | 'Medium' | 'Low' | 'None'`) or a float in `[0, 1]`:
28
+ *
29
+ * `1.0` (or `'High'`) → bars `[High, High, High]` (3/3 lit)
30
+ * `0.66` (or `'Medium'`) → bars `[Medium, Medium, None]` (2/3 lit)
31
+ * `0.33` (or `'Low'`) → bars `[Low, None, None]` (1/3 lit)
32
+ * `0.0` (or `'None'`) → bars `[None, None, None]` (0/3 lit)
33
+ *
34
+ * The component maps the resolved level to a per-bar confidence mode
35
+ * internally, so each bar's color resolves through the
36
+ * `strengthIndicator/bar/<size>/background` token using its own
37
+ * `Confidence` mode.
38
+ *
39
+ * Default: `'High'`.
40
+ */
41
+ confidence?: StrengthIndicatorConfidenceValue
42
+ /** Design token modes for theming (e.g. `{ 'Color Mode': 'Light' }`). */
43
+ modes?: Record<string, any>
44
+ /** Override container styles. */
45
+ style?: StyleProp<ViewStyle>
46
+ } & Omit<React.ComponentProps<typeof View>, 'style'>
47
+
48
+ type BarSize = 'small' | 'medium' | 'large'
49
+
50
+ const BAR_SIZES: BarSize[] = ['small', 'medium', 'large']
51
+
52
+ /**
53
+ * Maps an overall confidence level to per-bar confidence modes. Bars are
54
+ * filled from the smallest (left) to the largest (right) as confidence
55
+ * rises, with each lit bar tinted at its own confidence level so the
56
+ * indicator visually communicates *which* level the value is at — not just
57
+ * how many bars are lit.
58
+ *
59
+ * High -> [High, High, High] (3/3 lit, bright)
60
+ * Medium -> [Medium, Medium, None] (2/3 lit, mid intensity)
61
+ * Low -> [Low, None, None] (1/3 lit, low intensity)
62
+ * None -> [None, None, None] (0/3 lit)
63
+ *
64
+ * Each bar resolves its color through `getVariableByName` with its own
65
+ * per-bar `Confidence` mode, so the mapping survives any token updates in
66
+ * the Figma variables export.
67
+ */
68
+ const CONFIDENCE_TO_BARS: Record<
69
+ StrengthIndicatorConfidence,
70
+ [StrengthIndicatorConfidence, StrengthIndicatorConfidence, StrengthIndicatorConfidence]
71
+ > = {
72
+ High: ['High', 'High', 'High'],
73
+ Medium: ['Medium', 'Medium', 'None'],
74
+ Low: ['Low', 'None', 'None'],
75
+ None: ['None', 'None', 'None'],
76
+ }
77
+
78
+ /**
79
+ * Fallback colors used when the `strengthIndicator/*` design tokens have not
80
+ * yet been imported into `Coin Variables-variables-full.json`. The `High`
81
+ * fallback matches the Figma export (`#25ab21`); the intermediate levels
82
+ * use progressively lighter greens, and `None` uses a neutral gray for
83
+ * unlit bars. Once the tokens land, `getVariableByName` returns the
84
+ * canonical value and these fallbacks are bypassed.
85
+ */
86
+ const FALLBACK_BAR_COLOR: Record<StrengthIndicatorConfidence, string> = {
87
+ High: '#25ab21',
88
+ Medium: '#5cc257',
89
+ Low: '#a8dba2',
90
+ None: '#d9d9dd',
91
+ }
92
+
93
+ /**
94
+ * Bucket a float `[0, 1]` into the nearest named confidence level using
95
+ * midpoint thresholds between the four anchor values (`0`, `1/3`, `2/3`,
96
+ * `1`). Out-of-range values are clamped.
97
+ */
98
+ function floatToConfidence(value: number): StrengthIndicatorConfidence {
99
+ const clamped = Math.min(Math.max(value, 0), 1)
100
+ if (clamped > 5 / 6) return 'High'
101
+ if (clamped > 1 / 2) return 'Medium'
102
+ if (clamped > 1 / 6) return 'Low'
103
+ return 'None'
104
+ }
105
+
106
+ /**
107
+ * Normalize the union `confidence` prop into a single named level.
108
+ * Strings pass through; numbers are bucketed via {@link floatToConfidence}.
109
+ * Anything else (e.g. `null`/`undefined`) defaults to `'High'` to match
110
+ * Figma's default mode.
111
+ */
112
+ function resolveConfidence(
113
+ value: StrengthIndicatorConfidenceValue | undefined
114
+ ): StrengthIndicatorConfidence {
115
+ if (typeof value === 'number') return floatToConfidence(value)
116
+ if (value === 'High' || value === 'Medium' || value === 'Low' || value === 'None') {
117
+ return value
118
+ }
119
+ return 'High'
120
+ }
121
+
122
+ /**
123
+ * StrengthIndicator renders three fixed-size, ascending bars that
124
+ * communicate a discrete confidence/strength level — typically used to
125
+ * signal model confidence, signal quality, or password strength.
126
+ *
127
+ * Sizes are intentionally fixed (matching the Figma spec): 3px-wide bars
128
+ * 6/9/12px tall with a 2px gap and 2px container padding.
129
+ *
130
+ * Pass a single `confidence` value (named enum *or* float `[0, 1]`); the
131
+ * component normalizes it to a level, maps that level to a per-bar
132
+ * confidence mode, and resolves each bar's color through design tokens
133
+ * (`strengthIndicator/bar/<size>/background`).
134
+ *
135
+ * @component
136
+ * @param {StrengthIndicatorProps} props
137
+ */
138
+ function StrengthIndicator({
139
+ confidence = 'High',
140
+ modes = EMPTY_MODES,
141
+ style,
142
+ ...rest
143
+ }: StrengthIndicatorProps) {
144
+ const containerGap =
145
+ (getVariableByName('strengthIndicator/container/gap', modes) as number | null) ?? 2
146
+ const containerPadding =
147
+ (getVariableByName('strengthIndicator/container/padding', modes) as number | null) ?? 2
148
+ const barWidth =
149
+ (getVariableByName('strengthIndicator/bar/width', modes) as number | null) ?? 3
150
+ const barRadius =
151
+ (getVariableByName('strengthIndicator/bar/radius', modes) as number | null) ?? 1
152
+
153
+ const barHeights: Record<BarSize, number> = {
154
+ small:
155
+ (getVariableByName('strengthIndicator/bar/height/small', modes) as number | null) ?? 6,
156
+ medium:
157
+ (getVariableByName('strengthIndicator/bar/height/medium', modes) as number | null) ?? 9,
158
+ large:
159
+ (getVariableByName('strengthIndicator/bar/height/large', modes) as number | null) ?? 12,
160
+ }
161
+
162
+ const resolvedLevel = resolveConfidence(confidence)
163
+ const perBarConfidence = CONFIDENCE_TO_BARS[resolvedLevel]
164
+
165
+ const containerStyle: ViewStyle = {
166
+ flexDirection: 'row',
167
+ alignItems: 'flex-end',
168
+ gap: containerGap,
169
+ padding: containerPadding,
170
+ }
171
+
172
+ return (
173
+ <View
174
+ style={[containerStyle, style]}
175
+ accessibilityRole="image"
176
+ accessibilityLabel={`Strength indicator: ${resolvedLevel} confidence`}
177
+ {...rest}
178
+ >
179
+ {BAR_SIZES.map((size, index) => {
180
+ const barConfidence: StrengthIndicatorConfidence =
181
+ perBarConfidence[index] ?? 'None'
182
+ const barModes = { ...modes, Confidence: barConfidence }
183
+ const tokenColor = getVariableByName(
184
+ `strengthIndicator/bar/${size}/background`,
185
+ barModes
186
+ ) as string | null
187
+ const backgroundColor = tokenColor ?? FALLBACK_BAR_COLOR[barConfidence]
188
+
189
+ return (
190
+ <View
191
+ key={size}
192
+ style={{
193
+ width: barWidth,
194
+ height: barHeights[size],
195
+ borderRadius: barRadius,
196
+ backgroundColor,
197
+ }}
198
+ />
199
+ )
200
+ })}
201
+ </View>
202
+ )
203
+ }
204
+
205
+ export default StrengthIndicator
@@ -0,0 +1,251 @@
1
+ import React from 'react'
2
+ import {
3
+ View,
4
+ Text,
5
+ Pressable,
6
+ type StyleProp,
7
+ type ViewStyle,
8
+ type TextStyle,
9
+ type PressableStateCallbackType,
10
+ } from 'react-native'
11
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
12
+ import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
13
+ import Button from '../Button/Button'
14
+ import NavArrow from '../NavArrow/NavArrow'
15
+ import StrengthIndicator, {
16
+ type StrengthIndicatorConfidenceValue,
17
+ } from '../StrengthIndicator/StrengthIndicator'
18
+
19
+ export type SummaryTileProps = {
20
+ /** Bold heading shown at the top-left of the tile. */
21
+ title?: string
22
+ /** Smaller supporting copy rendered beneath the title. */
23
+ description?: string
24
+ /**
25
+ * Confidence level for the inline `StrengthIndicator` next to the title.
26
+ * Defaults to `'None'` to match the Figma reference (all bars unlit).
27
+ * Pass `null` to hide the indicator entirely.
28
+ */
29
+ confidence?: StrengthIndicatorConfidenceValue | null
30
+ /**
31
+ * When `true`, render a trailing forward chevron (`NavArrow`) — the
32
+ * convention for "tap the tile to drill in" affordance. Defaults to
33
+ * `false`.
34
+ */
35
+ chevron?: boolean
36
+ /**
37
+ * Action slot rendered to the right of the text content. Defaults to a
38
+ * small `Button` (`Button / Size: 'S'`) labelled "Button" to match the
39
+ * Figma default. Pass `null` to render no action slot (typical for
40
+ * static tiles that want only a chevron).
41
+ */
42
+ children?: React.ReactNode
43
+ /**
44
+ * Optional press handler for the entire tile. When provided, the tile
45
+ * is wrapped in a `Pressable`, gets `accessibilityRole="button"` and
46
+ * the default `Button` action slot is automatically suppressed so the
47
+ * rendered HTML doesn't contain a `<button>` inside another `<button>`
48
+ * (which would trigger a hydration warning on web). Pair this with
49
+ * `chevron` for the navigational tile pattern.
50
+ */
51
+ onPress?: () => void
52
+ /** Design token modes for theming (e.g. `{ 'Color Mode': 'Light' }`). */
53
+ modes?: Record<string, any>
54
+ /** Override container styles. */
55
+ style?: StyleProp<ViewStyle>
56
+ /** Override the title text styles. */
57
+ titleStyle?: StyleProp<TextStyle>
58
+ /** Override the description text styles. */
59
+ descriptionStyle?: StyleProp<TextStyle>
60
+ /** Accessibility label override (defaults to `title`). */
61
+ accessibilityLabel?: string
62
+ }
63
+
64
+ /**
65
+ * SummaryTile composes a title (with an inline `StrengthIndicator`), a
66
+ * supporting description and an action slot into a compact horizontal row,
67
+ * commonly used at the top of dashboard cards (e.g. "Spending — know your
68
+ * spending and savings"). An optional trailing chevron and tile-level
69
+ * `onPress` handler turn the whole row into a navigational entry point.
70
+ *
71
+ * @component
72
+ * @param {SummaryTileProps} props
73
+ */
74
+ function SummaryTile({
75
+ title = 'Spending',
76
+ description = 'Know your spending and savings',
77
+ confidence = 'None',
78
+ chevron = false,
79
+ children,
80
+ onPress,
81
+ modes = EMPTY_MODES,
82
+ style,
83
+ titleStyle,
84
+ descriptionStyle,
85
+ accessibilityLabel,
86
+ }: SummaryTileProps) {
87
+ const containerGap =
88
+ (getVariableByName('summaryTile/gap', modes) as number | null) ?? 8
89
+ const containerPadding =
90
+ (getVariableByName('summaryTile/padding', modes) as number | null) ?? 0
91
+
92
+ const contentGap =
93
+ (getVariableByName('summaryTile/content/gap', modes) as number | null) ?? 8
94
+ const titleWrapGap =
95
+ (getVariableByName('summaryTile/titleWrap/gap', modes) as number | null) ?? 4
96
+
97
+ const titleColor =
98
+ (getVariableByName('summaryTile/title/color', modes) as string | null) ?? '#0d0d0f'
99
+ const titleFontFamily =
100
+ (getVariableByName('summaryTile/title/fontFamily', modes) as string | null) ??
101
+ 'JioType Var'
102
+ const titleFontSize =
103
+ (getVariableByName('summaryTile/title/fontSize', modes) as number | null) ?? 16
104
+ const titleFontWeight =
105
+ getVariableByName('summaryTile/title/fontWeight', modes) ?? 700
106
+ const titleLineHeight =
107
+ (getVariableByName('summaryTile/title/lineHeight', modes) as number | null) ?? 18
108
+
109
+ const descriptionColor =
110
+ (getVariableByName('summaryTile/description/color', modes) as string | null) ??
111
+ '#24262b'
112
+ const descriptionFontFamily =
113
+ (getVariableByName('summaryTile/description/fontFamily', modes) as string | null) ??
114
+ 'JioType Var'
115
+ const descriptionFontSize =
116
+ (getVariableByName('summaryTile/description/fontSize', modes) as number | null) ?? 12
117
+ const descriptionFontWeight =
118
+ getVariableByName('summaryTile/description/fontWeight', modes) ?? 500
119
+ const descriptionLineHeight =
120
+ (getVariableByName('summaryTile/description/lineHeight', modes) as number | null) ?? 16
121
+
122
+ const titleTextStyle: TextStyle = {
123
+ color: titleColor,
124
+ fontFamily: titleFontFamily,
125
+ fontSize: titleFontSize,
126
+ fontWeight: String(titleFontWeight) as TextStyle['fontWeight'],
127
+ lineHeight: titleLineHeight,
128
+ }
129
+
130
+ const descriptionTextStyle: TextStyle = {
131
+ color: descriptionColor,
132
+ fontFamily: descriptionFontFamily,
133
+ fontSize: descriptionFontSize,
134
+ fontWeight: String(descriptionFontWeight) as TextStyle['fontWeight'],
135
+ lineHeight: descriptionLineHeight,
136
+ }
137
+
138
+ const showIndicator = confidence !== null
139
+
140
+ // When the entire tile is pressable, the default `Button` slot is
141
+ // suppressed so we don't render a `<button>` inside another `<button>`
142
+ // (which is invalid HTML and triggers a React hydration warning on
143
+ // web). Consumers can still pass an explicit non-button node via
144
+ // `children` if they want a visible affordance alongside `onPress`.
145
+ const isPressable = onPress != null
146
+
147
+ const actionSlot =
148
+ children !== undefined ? (
149
+ children !== null ? (
150
+ <>{cloneChildrenWithModes(children, modes)}</>
151
+ ) : null
152
+ ) : isPressable ? null : (
153
+ <Button
154
+ label="Button"
155
+ modes={{'AppearanceBrand': 'Secondary', ...modes, 'Button / Size': 'S' }}
156
+ />
157
+ )
158
+
159
+ const tileContent = (
160
+ <>
161
+ <View
162
+ style={{
163
+ flex: 1,
164
+ minWidth: 0,
165
+ flexDirection: 'column',
166
+ gap: contentGap,
167
+ alignItems: 'flex-start',
168
+ }}
169
+ >
170
+ <View
171
+ style={{
172
+ flexDirection: 'row',
173
+ alignItems: 'center',
174
+ gap: titleWrapGap,
175
+ width: '100%',
176
+ }}
177
+ >
178
+ {title ? (
179
+ <Text
180
+ style={[titleTextStyle, titleStyle]}
181
+ numberOfLines={1}
182
+ >
183
+ {title}
184
+ </Text>
185
+ ) : null}
186
+ {showIndicator ? (
187
+ <StrengthIndicator
188
+ confidence={confidence as StrengthIndicatorConfidenceValue}
189
+ modes={modes}
190
+ />
191
+ ) : null}
192
+ </View>
193
+
194
+ {description ? (
195
+ <Text style={[descriptionTextStyle, descriptionStyle]}>
196
+ {description}
197
+ </Text>
198
+ ) : null}
199
+ </View>
200
+
201
+ {actionSlot != null ? (
202
+ <View
203
+ style={{
204
+ flexDirection: 'row',
205
+ alignItems: 'center',
206
+ }}
207
+ >
208
+ {actionSlot}
209
+ </View>
210
+ ) : null}
211
+
212
+ {chevron ? <NavArrow direction="Forward" modes={modes} /> : null}
213
+ </>
214
+ )
215
+
216
+ const containerLayoutStyle: ViewStyle = {
217
+ flexDirection: 'row',
218
+ alignItems: 'center',
219
+ gap: containerGap,
220
+ padding: containerPadding,
221
+ width: '100%',
222
+ }
223
+
224
+ if (onPress) {
225
+ return (
226
+ <Pressable
227
+ onPress={onPress}
228
+ accessibilityRole="button"
229
+ accessibilityLabel={accessibilityLabel ?? title}
230
+ style={({ pressed }: PressableStateCallbackType): StyleProp<ViewStyle> => [
231
+ containerLayoutStyle,
232
+ style,
233
+ pressed ? { opacity: 0.7 } : null,
234
+ ]}
235
+ >
236
+ {tileContent}
237
+ </Pressable>
238
+ )
239
+ }
240
+
241
+ return (
242
+ <View
243
+ style={[containerLayoutStyle, style]}
244
+ accessibilityLabel={accessibilityLabel}
245
+ >
246
+ {tileContent}
247
+ </View>
248
+ )
249
+ }
250
+
251
+ export default SummaryTile