jfs-components 0.0.72 → 0.0.74

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 (158) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/lib/commonjs/components/AccordionCheckbox/AccordionCheckbox.js +239 -0
  3. package/lib/commonjs/components/AccountCard/AccountCard.js +247 -0
  4. package/lib/commonjs/components/AppBar/AppBar.js +17 -11
  5. package/lib/commonjs/components/BrandChip/BrandChip.js +149 -0
  6. package/lib/commonjs/components/CardBankAccount/CardBankAccount.js +229 -0
  7. package/lib/commonjs/components/CardInsight/CardInsight.js +166 -0
  8. package/lib/commonjs/components/CheckboxGroup/CheckboxGroup.js +67 -0
  9. package/lib/commonjs/components/CheckboxItem/CheckboxItem.js +140 -0
  10. package/lib/commonjs/components/CircularProgressBar/CircularProgressBar.js +56 -9
  11. package/lib/commonjs/components/CoverageBarComparison/CoverageBarComparison.js +272 -0
  12. package/lib/commonjs/components/CoverageRing/CoverageRing.js +141 -0
  13. package/lib/commonjs/components/DonutChart/DonutChart.js +309 -0
  14. package/lib/commonjs/components/DonutChartSummary/DonutChartSummary.js +155 -0
  15. package/lib/commonjs/components/Dropdown/Dropdown.js +214 -0
  16. package/lib/commonjs/components/DropdownInput/DropdownInput.js +542 -0
  17. package/lib/commonjs/components/FormField/FormField.js +328 -178
  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/LottieIntroBlock/LottieIntroBlock.js +150 -0
  21. package/lib/commonjs/components/MetricLegendItem/MetricLegendItem.js +95 -0
  22. package/lib/commonjs/components/MonthlyStatusGrid/MonthlyStatusGrid.js +286 -0
  23. package/lib/commonjs/components/OTP/OTP.js +381 -37
  24. package/lib/commonjs/components/PageHero/PageHero.js +153 -0
  25. package/lib/commonjs/components/PoweredByLabel/PoweredByLabel.js +135 -0
  26. package/lib/commonjs/components/PoweredByLabel/finvu.png +0 -0
  27. package/lib/commonjs/components/ProductOverview/ProductOverview.js +147 -0
  28. package/lib/commonjs/components/RangeTrack/RangeTrack.js +269 -0
  29. package/lib/commonjs/components/SavingsGoalSummary/SavingsGoalSummary.js +181 -0
  30. package/lib/commonjs/components/SegmentedTrack/SegmentedTrack.js +171 -0
  31. package/lib/commonjs/components/StatGroup/StatGroup.js +128 -0
  32. package/lib/commonjs/components/StatItem/StatItem.js +65 -35
  33. package/lib/commonjs/components/StrengthIndicator/StrengthIndicator.js +157 -0
  34. package/lib/commonjs/components/SummaryTile/SummaryTile.js +150 -0
  35. package/lib/commonjs/components/Text/Text.js +9 -2
  36. package/lib/commonjs/components/Tooltip/Tooltip.js +34 -27
  37. package/lib/commonjs/components/index.js +231 -1
  38. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
  39. package/lib/commonjs/icons/registry.js +1 -1
  40. package/lib/commonjs/utils/index.js +7 -0
  41. package/lib/commonjs/utils/number-utils.js +57 -0
  42. package/lib/module/components/AccordionCheckbox/AccordionCheckbox.js +233 -0
  43. package/lib/module/components/AccountCard/AccountCard.js +241 -0
  44. package/lib/module/components/AppBar/AppBar.js +17 -11
  45. package/lib/module/components/BrandChip/BrandChip.js +143 -0
  46. package/lib/module/components/CardBankAccount/CardBankAccount.js +223 -0
  47. package/lib/module/components/CardInsight/CardInsight.js +161 -0
  48. package/lib/module/components/CheckboxGroup/CheckboxGroup.js +62 -0
  49. package/lib/module/components/CheckboxItem/CheckboxItem.js +134 -0
  50. package/lib/module/components/CircularProgressBar/CircularProgressBar.js +56 -9
  51. package/lib/module/components/CoverageBarComparison/CoverageBarComparison.js +266 -0
  52. package/lib/module/components/CoverageRing/CoverageRing.js +136 -0
  53. package/lib/module/components/DonutChart/DonutChart.js +303 -0
  54. package/lib/module/components/DonutChartSummary/DonutChartSummary.js +150 -0
  55. package/lib/module/components/Dropdown/Dropdown.js +206 -0
  56. package/lib/module/components/DropdownInput/DropdownInput.js +536 -0
  57. package/lib/module/components/FormField/FormField.js +330 -180
  58. package/lib/module/components/LinearMeter/LinearMeter.js +9 -28
  59. package/lib/module/components/LinearProgress/LinearProgress.js +63 -0
  60. package/lib/module/components/LottieIntroBlock/LottieIntroBlock.js +144 -0
  61. package/lib/module/components/MetricLegendItem/MetricLegendItem.js +90 -0
  62. package/lib/module/components/MonthlyStatusGrid/MonthlyStatusGrid.js +281 -0
  63. package/lib/module/components/OTP/OTP.js +381 -38
  64. package/lib/module/components/PageHero/PageHero.js +147 -0
  65. package/lib/module/components/PoweredByLabel/PoweredByLabel.js +130 -0
  66. package/lib/module/components/PoweredByLabel/finvu.png +0 -0
  67. package/lib/module/components/ProductOverview/ProductOverview.js +142 -0
  68. package/lib/module/components/RangeTrack/RangeTrack.js +263 -0
  69. package/lib/module/components/SavingsGoalSummary/SavingsGoalSummary.js +175 -0
  70. package/lib/module/components/SegmentedTrack/SegmentedTrack.js +166 -0
  71. package/lib/module/components/StatGroup/StatGroup.js +123 -0
  72. package/lib/module/components/StatItem/StatItem.js +66 -36
  73. package/lib/module/components/StrengthIndicator/StrengthIndicator.js +152 -0
  74. package/lib/module/components/SummaryTile/SummaryTile.js +145 -0
  75. package/lib/module/components/Text/Text.js +9 -2
  76. package/lib/module/components/Tooltip/Tooltip.js +34 -27
  77. package/lib/module/components/index.js +28 -2
  78. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
  79. package/lib/module/icons/registry.js +1 -1
  80. package/lib/module/utils/index.js +2 -1
  81. package/lib/module/utils/number-utils.js +53 -0
  82. package/lib/typescript/src/components/AccordionCheckbox/AccordionCheckbox.d.ts +71 -0
  83. package/lib/typescript/src/components/AccountCard/AccountCard.d.ts +81 -0
  84. package/lib/typescript/src/components/BrandChip/BrandChip.d.ts +43 -0
  85. package/lib/typescript/src/components/CardBankAccount/CardBankAccount.d.ts +86 -0
  86. package/lib/typescript/src/components/CardInsight/CardInsight.d.ts +48 -0
  87. package/lib/typescript/src/components/CheckboxGroup/CheckboxGroup.d.ts +41 -0
  88. package/lib/typescript/src/components/CheckboxItem/CheckboxItem.d.ts +72 -0
  89. package/lib/typescript/src/components/CircularProgressBar/CircularProgressBar.d.ts +11 -1
  90. package/lib/typescript/src/components/CoverageBarComparison/CoverageBarComparison.d.ts +105 -0
  91. package/lib/typescript/src/components/CoverageRing/CoverageRing.d.ts +90 -0
  92. package/lib/typescript/src/components/DonutChart/DonutChart.d.ts +117 -0
  93. package/lib/typescript/src/components/DonutChartSummary/DonutChartSummary.d.ts +103 -0
  94. package/lib/typescript/src/components/Dropdown/Dropdown.d.ts +62 -0
  95. package/lib/typescript/src/components/DropdownInput/DropdownInput.d.ts +107 -0
  96. package/lib/typescript/src/components/FormField/FormField.d.ts +76 -19
  97. package/lib/typescript/src/components/LinearProgress/LinearProgress.d.ts +17 -0
  98. package/lib/typescript/src/components/LottieIntroBlock/LottieIntroBlock.d.ts +58 -0
  99. package/lib/typescript/src/components/MetricLegendItem/MetricLegendItem.d.ts +37 -0
  100. package/lib/typescript/src/components/MonthlyStatusGrid/MonthlyStatusGrid.d.ts +119 -0
  101. package/lib/typescript/src/components/OTP/OTP.d.ts +88 -2
  102. package/lib/typescript/src/components/PageHero/PageHero.d.ts +53 -0
  103. package/lib/typescript/src/components/PoweredByLabel/PoweredByLabel.d.ts +70 -0
  104. package/lib/typescript/src/components/ProductOverview/ProductOverview.d.ts +39 -0
  105. package/lib/typescript/src/components/RangeTrack/RangeTrack.d.ts +173 -0
  106. package/lib/typescript/src/components/SavingsGoalSummary/SavingsGoalSummary.d.ts +95 -0
  107. package/lib/typescript/src/components/SegmentedTrack/SegmentedTrack.d.ts +108 -0
  108. package/lib/typescript/src/components/StatGroup/StatGroup.d.ts +45 -0
  109. package/lib/typescript/src/components/StatItem/StatItem.d.ts +24 -7
  110. package/lib/typescript/src/components/StrengthIndicator/StrengthIndicator.d.ts +58 -0
  111. package/lib/typescript/src/components/SummaryTile/SummaryTile.d.ts +60 -0
  112. package/lib/typescript/src/components/Text/Text.d.ts +12 -2
  113. package/lib/typescript/src/components/Tooltip/Tooltip.d.ts +13 -2
  114. package/lib/typescript/src/components/index.d.ts +29 -3
  115. package/lib/typescript/src/icons/registry.d.ts +1 -1
  116. package/lib/typescript/src/utils/index.d.ts +1 -0
  117. package/lib/typescript/src/utils/number-utils.d.ts +29 -0
  118. package/package.json +1 -3
  119. package/src/components/AccordionCheckbox/AccordionCheckbox.tsx +323 -0
  120. package/src/components/AccountCard/AccountCard.tsx +376 -0
  121. package/src/components/AppBar/AppBar.tsx +25 -14
  122. package/src/components/BrandChip/BrandChip.tsx +235 -0
  123. package/src/components/CardBankAccount/CardBankAccount.tsx +321 -0
  124. package/src/components/CardInsight/CardInsight.tsx +239 -0
  125. package/src/components/CheckboxGroup/CheckboxGroup.tsx +86 -0
  126. package/src/components/CheckboxItem/CheckboxItem.tsx +209 -0
  127. package/src/components/CircularProgressBar/CircularProgressBar.tsx +74 -9
  128. package/src/components/CoverageBarComparison/CoverageBarComparison.tsx +378 -0
  129. package/src/components/CoverageRing/CoverageRing.tsx +225 -0
  130. package/src/components/DonutChart/DonutChart.tsx +503 -0
  131. package/src/components/DonutChartSummary/DonutChartSummary.tsx +256 -0
  132. package/src/components/Dropdown/Dropdown.tsx +331 -0
  133. package/src/components/DropdownInput/DropdownInput.tsx +819 -0
  134. package/src/components/FormField/FormField.tsx +542 -215
  135. package/src/components/LinearMeter/LinearMeter.tsx +9 -39
  136. package/src/components/LinearProgress/LinearProgress.tsx +92 -0
  137. package/src/components/LottieIntroBlock/LottieIntroBlock.tsx +202 -0
  138. package/src/components/MetricLegendItem/MetricLegendItem.tsx +167 -0
  139. package/src/components/MonthlyStatusGrid/MonthlyStatusGrid.tsx +438 -0
  140. package/src/components/OTP/OTP.tsx +476 -29
  141. package/src/components/PageHero/PageHero.tsx +200 -0
  142. package/src/components/PoweredByLabel/PoweredByLabel.tsx +221 -0
  143. package/src/components/PoweredByLabel/finvu.png +0 -0
  144. package/src/components/ProductOverview/ProductOverview.tsx +236 -0
  145. package/src/components/RangeTrack/RangeTrack.tsx +394 -0
  146. package/src/components/SavingsGoalSummary/SavingsGoalSummary.tsx +269 -0
  147. package/src/components/SegmentedTrack/SegmentedTrack.tsx +268 -0
  148. package/src/components/StatGroup/StatGroup.tsx +169 -0
  149. package/src/components/StatItem/StatItem.tsx +117 -40
  150. package/src/components/StrengthIndicator/StrengthIndicator.tsx +205 -0
  151. package/src/components/SummaryTile/SummaryTile.tsx +251 -0
  152. package/src/components/Text/Text.tsx +24 -3
  153. package/src/components/Tooltip/Tooltip.tsx +50 -25
  154. package/src/components/index.ts +47 -3
  155. package/src/design-tokens/Coin Variables-variables-full.json +1 -1
  156. package/src/icons/registry.ts +1 -1
  157. package/src/utils/index.ts +1 -0
  158. package/src/utils/number-utils.ts +60 -0
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+
3
+ import React, { useCallback, useState } from 'react';
4
+ import { View, Text, Pressable } from 'react-native';
5
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
+ import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils';
7
+ import Checkbox from '../Checkbox/Checkbox';
8
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
9
+ /**
10
+ * CheckboxItem composes a `Checkbox`, a label and an optional `endSlot` into a
11
+ * single horizontal pressable row. Pressing anywhere on the row (outside of the
12
+ * `endSlot`) toggles the checkbox, mirroring the typical native form pattern.
13
+ *
14
+ * Use the `control` prop to swap the checkbox between the leading (left, default)
15
+ * and trailing (right) edge of the row. The `endSlot` flips to the opposite edge.
16
+ *
17
+ * Mirrors the Figma "Checkbox Item" component and uses the `checkboxItem/*`
18
+ * design tokens for typography and spacing.
19
+ *
20
+ * @component
21
+ * @param {CheckboxItemProps} props
22
+ *
23
+ * @example
24
+ * ```tsx
25
+ * const [checked, setChecked] = useState(false)
26
+ *
27
+ * <CheckboxItem
28
+ * label="Fixed deposit • 0245"
29
+ * checked={checked}
30
+ * onValueChange={setChecked}
31
+ * control="leading"
32
+ * modes={{ 'Color Mode': 'Light' }}
33
+ * />
34
+ * ```
35
+ */
36
+ function CheckboxItem({
37
+ checked: controlledChecked,
38
+ defaultChecked = false,
39
+ onValueChange,
40
+ disabled = false,
41
+ label = 'Fixed deposit • 0245',
42
+ control = 'leading',
43
+ endSlot,
44
+ endSlotWidth = 80,
45
+ modes = EMPTY_MODES,
46
+ style,
47
+ labelStyle,
48
+ accessibilityLabel
49
+ }) {
50
+ const isTrailing = control === 'trailing';
51
+ const isControlled = controlledChecked !== undefined;
52
+ const [internalChecked, setInternalChecked] = useState(defaultChecked);
53
+ const isChecked = isControlled ? controlledChecked : internalChecked;
54
+ const handleToggle = useCallback(() => {
55
+ if (disabled) return;
56
+ const next = !isChecked;
57
+ if (!isControlled) {
58
+ setInternalChecked(next);
59
+ }
60
+ onValueChange?.(next);
61
+ }, [disabled, isChecked, isControlled, onValueChange]);
62
+ const gap = getVariableByName('checkboxItem/gap', modes) ?? 8;
63
+ const paddingHorizontal = getVariableByName('checkboxItem/padding/horizontal', modes) ?? 0;
64
+ const paddingVertical = getVariableByName('checkboxItem/padding/vertical', modes) ?? 0;
65
+ const labelColor = getVariableByName('checkboxItem/foreground', modes) ?? '#1a1c1f';
66
+ const labelFontFamily = getVariableByName('checkboxItem/label/fontFamily', modes) ?? 'JioType Var';
67
+ const labelFontSize = getVariableByName('checkboxItem/label/fontSize', modes) ?? 14;
68
+ const labelLineHeight = getVariableByName('checkboxItem/label/lineHeight', modes) ?? 19;
69
+ const labelFontWeightRaw = getVariableByName('checkboxItem/label/fontWeight', modes) ?? 400;
70
+ const labelFontWeight = String(labelFontWeightRaw);
71
+ const containerStyle = {
72
+ flexDirection: 'row',
73
+ alignItems: 'center',
74
+ gap,
75
+ paddingHorizontal,
76
+ paddingVertical,
77
+ width: '100%'
78
+ };
79
+ const resolvedLabelStyle = {
80
+ flex: 1,
81
+ minWidth: 0,
82
+ color: labelColor,
83
+ fontFamily: labelFontFamily,
84
+ fontSize: labelFontSize,
85
+ lineHeight: labelLineHeight,
86
+ fontWeight: labelFontWeight
87
+ };
88
+ const a11yLabel = accessibilityLabel ?? (typeof label === 'string' ? label : undefined);
89
+ const checkboxNode = /*#__PURE__*/_jsx(Checkbox, {
90
+ checked: isChecked,
91
+ disabled: disabled,
92
+ onValueChange: handleToggle,
93
+ modes: modes,
94
+ ...(a11yLabel !== undefined ? {
95
+ accessibilityLabel: a11yLabel
96
+ } : {})
97
+ });
98
+ const labelNode = label != null && label !== false ? typeof label === 'string' || typeof label === 'number' ? /*#__PURE__*/_jsx(Text, {
99
+ style: [resolvedLabelStyle, labelStyle],
100
+ selectable: false,
101
+ children: label
102
+ }) : /*#__PURE__*/_jsx(View, {
103
+ style: {
104
+ flex: 1,
105
+ minWidth: 0
106
+ },
107
+ children: label
108
+ }) : null;
109
+ const endSlotNode = endSlot ? /*#__PURE__*/_jsx(View, {
110
+ style: {
111
+ width: endSlotWidth,
112
+ flexShrink: 0,
113
+ alignItems: 'stretch'
114
+ },
115
+ children: cloneChildrenWithModes(endSlot, modes)
116
+ }) : null;
117
+ return /*#__PURE__*/_jsx(Pressable, {
118
+ style: [containerStyle, style],
119
+ onPress: handleToggle,
120
+ disabled: disabled,
121
+ accessibilityRole: "checkbox",
122
+ accessibilityState: {
123
+ checked: isChecked,
124
+ disabled
125
+ },
126
+ accessibilityLabel: a11yLabel,
127
+ children: isTrailing ? /*#__PURE__*/_jsxs(_Fragment, {
128
+ children: [endSlotNode, labelNode, checkboxNode]
129
+ }) : /*#__PURE__*/_jsxs(_Fragment, {
130
+ children: [checkboxNode, labelNode, endSlotNode]
131
+ })
132
+ });
133
+ }
134
+ export default CheckboxItem;
@@ -37,11 +37,13 @@ function CircularProgressBar({
37
37
  value = 70,
38
38
  state = 'Inactive',
39
39
  valueLabel,
40
+ supportText,
40
41
  modes: propModes = EMPTY_MODES,
41
42
  style,
42
43
  trackStyle,
43
44
  progressStyle,
44
45
  valueStyle,
46
+ supportTextStyle,
45
47
  accessibilityLabel,
46
48
  ...rest
47
49
  }) {
@@ -52,6 +54,17 @@ function CircularProgressBar({
52
54
  ...globalModes,
53
55
  ...propModes
54
56
  };
57
+ // The Figma `circularProgressBar/track/color` variable aliases to the same
58
+ // chain as `progress/color`, so user-selected Brand modes collapse the
59
+ // track and progress to a single hue. The Figma source of truth always
60
+ // renders the track as a neutral gray, so we force Neutral/Medium when
61
+ // resolving the track color while keeping the user modes for everything
62
+ // else.
63
+ const trackModes = {
64
+ ...modes,
65
+ 'AppearanceBrand': 'Neutral',
66
+ 'Emphasis': 'Medium'
67
+ };
55
68
  const isActive = state === true || state === 'Active';
56
69
  const normalizedValue = clamp(value, 0, 100);
57
70
  const size = toNumber(getVariableByName('circularProgressBar/size', modes), 60);
@@ -59,15 +72,18 @@ function CircularProgressBar({
59
72
  const radius = Math.max(0, (size - strokeWidth) / 2);
60
73
  const center = size / 2;
61
74
  const circumference = 2 * Math.PI * radius;
62
- const trackColor = getStrokeColor(trackStyle, getVariableByName('circularProgressBar/track/color', modes) || '#ebebed');
75
+ const trackColor = getStrokeColor(trackStyle, getVariableByName('circularProgressBar/track/color', trackModes) || '#ebebed');
63
76
  const progressColor = getStrokeColor(progressStyle, getVariableByName('circularProgressBar/progress/color', modes) || '#25ab21');
64
77
  const iconColor = getVariableByName('circularProgressBar/icon/color', modes) || '#666666';
65
78
  const iconSize = toNumber(getVariableByName('circularProgressBar/icon/size', modes), 24);
66
79
  const foreground = getVariableByName('circularProgressBar/foreground', modes) || '#0d0d0f';
67
- const fontSize = toNumber(getVariableByName('circularProgressBar/fontSize', modes), 18);
80
+ const fontSize = toNumber(getVariableByName('circularProgressBar/value/fontSize', modes), 18);
68
81
  const fontFamily = getVariableByName('circularProgressBar/fontFamily', modes) || 'JioType Var';
69
- const lineHeight = toNumber(getVariableByName('circularProgressBar/lineHeight', modes), 21);
70
- const fontWeight = toFontWeight(getVariableByName('circularProgressBar/fontWeight', modes), '700');
82
+ const lineHeight = toNumber(getVariableByName('circularProgressBar/value/lineHeight', modes), 21);
83
+ const fontWeight = toFontWeight(getVariableByName('circularProgressBar/value/fontWeight', modes), '700');
84
+ const supportFontSize = toNumber(getVariableByName('circularProgressBar/supportText/fontSize', modes), 11);
85
+ const supportLineHeight = toNumber(getVariableByName('circularProgressBar/supportText/lineHeight', modes), 13);
86
+ const supportFontWeight = toFontWeight(getVariableByName('circularProgressBar/supportText/fontWeight', modes), '500');
71
87
  const computedContainerStyle = {
72
88
  alignItems: 'center',
73
89
  height: size,
@@ -75,13 +91,34 @@ function CircularProgressBar({
75
91
  position: 'relative',
76
92
  width: size
77
93
  };
94
+
95
+ // The text stack (support text + value) is centered inside the ring using
96
+ // an absolutely-positioned column. Keeping the wrapper absolute (rather
97
+ // than the individual texts, as in earlier versions) lets multi-line stacks
98
+ // render correctly without sub-pixel misalignment.
99
+ const textStackStyle = {
100
+ alignItems: 'center',
101
+ justifyContent: 'center',
102
+ left: 0,
103
+ position: 'absolute',
104
+ right: 0,
105
+ top: 0,
106
+ bottom: 0
107
+ };
78
108
  const computedValueStyle = {
79
109
  color: foreground,
80
110
  fontFamily,
81
111
  fontSize,
82
112
  fontWeight,
83
113
  lineHeight,
84
- position: 'absolute',
114
+ textAlign: 'center'
115
+ };
116
+ const computedSupportTextStyle = {
117
+ color: foreground,
118
+ fontFamily,
119
+ fontSize: supportFontSize,
120
+ fontWeight: supportFontWeight,
121
+ lineHeight: supportLineHeight,
85
122
  textAlign: 'center'
86
123
  };
87
124
  const iconStyle = {
@@ -90,7 +127,8 @@ function CircularProgressBar({
90
127
  top: (size - iconSize) / 2
91
128
  };
92
129
  const displayValue = valueLabel ?? String(Math.round(normalizedValue));
93
- const defaultAccessibilityLabel = accessibilityLabel ?? (isActive ? `${displayValue} out of 100` : 'Inactive progress');
130
+ const hasSupportText = typeof supportText === 'string' && supportText.length > 0;
131
+ const defaultAccessibilityLabel = accessibilityLabel ?? (isActive ? hasSupportText ? `${supportText}, ${displayValue} out of 100` : `${displayValue} out of 100` : 'Inactive progress');
94
132
  return /*#__PURE__*/_jsxs(View, {
95
133
  accessibilityRole: "progressbar",
96
134
  accessibilityLabel: defaultAccessibilityLabel,
@@ -126,9 +164,18 @@ function CircularProgressBar({
126
164
  originX: center,
127
165
  originY: center
128
166
  }) : null]
129
- }), isActive ? /*#__PURE__*/_jsx(Text, {
130
- style: [computedValueStyle, valueStyle],
131
- children: displayValue
167
+ }), isActive ? /*#__PURE__*/_jsxs(View, {
168
+ style: textStackStyle,
169
+ pointerEvents: "none",
170
+ children: [hasSupportText ? /*#__PURE__*/_jsx(Text, {
171
+ style: [computedSupportTextStyle, supportTextStyle],
172
+ numberOfLines: 1,
173
+ children: supportText
174
+ }) : null, /*#__PURE__*/_jsx(Text, {
175
+ style: [computedValueStyle, valueStyle],
176
+ numberOfLines: 1,
177
+ children: displayValue
178
+ })]
132
179
  }) : /*#__PURE__*/_jsx(IconMinus, {
133
180
  width: iconSize,
134
181
  height: iconSize,
@@ -0,0 +1,266 @@
1
+ "use strict";
2
+
3
+ import React from 'react';
4
+ import { View, Text } from 'react-native';
5
+ import Svg, { Rect } from 'react-native-svg';
6
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
7
+ import { useTokens } from '../../design-tokens/JFSThemeProvider';
8
+ import { EMPTY_MODES } from '../../utils/react-utils';
9
+ import MetricLegendItem from '../MetricLegendItem/MetricLegendItem';
10
+
11
+ /**
12
+ * One entry in the {@link CoverageBarComparisonProps.bars} array.
13
+ *
14
+ * Each entry carries **both** the bar's data and its legend label, so the
15
+ * number of bars is intrinsically equal to the number of legend items.
16
+ * There is no separate `legends` prop — that is by design.
17
+ */
18
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
+ /**
20
+ * Default per-index `Emphasis / DataViz` modes applied when the caller does
21
+ * not provide its own override. Ascends Low → Medium → High so the natural
22
+ * "Current vs Recommended" two-bar story matches the Figma reference (left
23
+ * = lighter / Low, right = darker / High). Cycles for >3 bars.
24
+ */
25
+ const DEFAULT_EMPHASIS_CYCLE = ['Low', 'Medium', 'High'];
26
+ function defaultEmphasisFor(index, total) {
27
+ if (total <= 1) {
28
+ return 'High';
29
+ }
30
+ if (total === 2) {
31
+ return index === 0 ? 'Low' : 'High';
32
+ }
33
+ return DEFAULT_EMPHASIS_CYCLE[index % DEFAULT_EMPHASIS_CYCLE.length];
34
+ }
35
+ const DEFAULT_BARS = [{
36
+ value: 40,
37
+ label: '₹1L (40%)',
38
+ legend: 'Current coverage'
39
+ }, {
40
+ value: 100,
41
+ label: '₹2.5L (100%)',
42
+ legend: 'Recommended coverage'
43
+ }];
44
+ const toNumber = (value, fallback) => {
45
+ if (typeof value === 'number') {
46
+ return Number.isFinite(value) ? value : fallback;
47
+ }
48
+ if (typeof value === 'string') {
49
+ const parsed = Number(value);
50
+ return Number.isFinite(parsed) ? parsed : fallback;
51
+ }
52
+ return fallback;
53
+ };
54
+ const toFontWeight = (value, fallback) => {
55
+ if (typeof value === 'number') {
56
+ return String(value);
57
+ }
58
+ if (typeof value === 'string') {
59
+ return value;
60
+ }
61
+ return fallback;
62
+ };
63
+ /**
64
+ * Bar shape rendered with `react-native-svg`. The wrapper measures its width
65
+ * via `onLayout` so the SVG can use concrete dimensions (RN SVG does not
66
+ * resolve percentage shape attributes when the parent SVG itself has a
67
+ * percentage `width`).
68
+ */
69
+ function BarShape({
70
+ height,
71
+ color,
72
+ radius
73
+ }) {
74
+ const [width, setWidth] = React.useState(0);
75
+ const handleLayout = React.useCallback(event => {
76
+ const next = event.nativeEvent.layout.width;
77
+ setWidth(prev => prev === next ? prev : next);
78
+ }, []);
79
+ if (height <= 0) {
80
+ return /*#__PURE__*/_jsx(View, {
81
+ onLayout: handleLayout,
82
+ style: {
83
+ width: '100%',
84
+ height: 0
85
+ }
86
+ });
87
+ }
88
+ const safeRadius = Math.max(0, Math.min(radius, width / 2, height / 2));
89
+ return /*#__PURE__*/_jsx(View, {
90
+ onLayout: handleLayout,
91
+ style: {
92
+ width: '100%',
93
+ height,
94
+ minWidth: 1
95
+ },
96
+ children: width > 0 ? /*#__PURE__*/_jsx(Svg, {
97
+ width: width,
98
+ height: height,
99
+ children: /*#__PURE__*/_jsx(Rect, {
100
+ x: 0,
101
+ y: 0,
102
+ width: width,
103
+ height: height,
104
+ rx: safeRadius,
105
+ ry: safeRadius,
106
+ fill: color
107
+ })
108
+ }) : null
109
+ });
110
+ }
111
+
112
+ /**
113
+ * `CoverageBarComparison` renders a small vertical-bar chart that compares
114
+ * 2+ values side by side, with a legend row directly below where each item
115
+ * is intrinsically tied to one bar.
116
+ *
117
+ * Cohesiveness guarantees:
118
+ * - The legend row is **derived** from the same `bars` prop, so the count
119
+ * and order can never desynchronise from the chart.
120
+ * - Each bar's color, its tokenized `Emphasis / DataViz` mode and its
121
+ * legend indicator dot are the same value.
122
+ *
123
+ * Bars are drawn with `react-native-svg` (`<Rect>` with `rx`), and the
124
+ * fonts/spacing/colors are sourced from the Figma `valueBar/*` and
125
+ * `coverageBarComparison/*` tokens.
126
+ *
127
+ * @component
128
+ */
129
+ function CoverageBarComparison({
130
+ bars = DEFAULT_BARS,
131
+ max,
132
+ height = 100,
133
+ legendGap = 12,
134
+ modes: propModes = EMPTY_MODES,
135
+ style,
136
+ chartStyle,
137
+ legendStyle,
138
+ labelStyle,
139
+ accessibilityLabel
140
+ }) {
141
+ const {
142
+ modes: globalModes
143
+ } = useTokens();
144
+ const modes = React.useMemo(() => ({
145
+ ...globalModes,
146
+ ...propModes
147
+ }), [globalModes, propModes]);
148
+ const wrapGap = toNumber(getVariableByName('coverageBarComparison/wrap/gap', modes), 6);
149
+ const valueBarGap = toNumber(getVariableByName('valueBar/gap', modes), 4);
150
+ const barRadius = toNumber(getVariableByName('valueBar/bar/radius', modes), 10);
151
+ const fontFamily = getVariableByName('valueBar/fontFamily', modes) ?? 'JioType Var';
152
+ const fontSize = toNumber(getVariableByName('valueBar/fontSize', modes), 12);
153
+ const lineHeight = toNumber(getVariableByName('valueBar/lineHeight', modes), 16);
154
+ const fontWeight = toFontWeight(getVariableByName('valueBar/fontWeight', modes), '700');
155
+ const foreground = getVariableByName('valueBar/foreground', modes) ?? '#000000';
156
+ const labelHeight = lineHeight;
157
+ const barAreaHeight = Math.max(0, height - labelHeight - valueBarGap);
158
+ const total = bars.length;
159
+ const computedMax = max ?? bars.reduce((acc, bar) => Math.max(acc, bar.value), 0);
160
+ const safeMax = computedMax > 0 ? computedMax : 1;
161
+ const resolvedBars = React.useMemo(() => bars.map((bar, index) => {
162
+ const barModes = {
163
+ ...modes,
164
+ 'Emphasis / DataViz': defaultEmphasisFor(index, total),
165
+ ...(bar.modes || {})
166
+ };
167
+ const ratio = Math.max(0, Math.min(1, bar.value / safeMax));
168
+ const tokenColor = getVariableByName('valueBar/bar/background', barModes) ?? '#c9b7ff';
169
+ const bgColor = bar.color ?? tokenColor;
170
+ return {
171
+ original: bar,
172
+ index,
173
+ barModes,
174
+ ratio,
175
+ bgColor
176
+ };
177
+ }), [bars, modes, safeMax, total]);
178
+ const computedLabelStyle = {
179
+ color: foreground,
180
+ fontFamily,
181
+ fontSize,
182
+ lineHeight,
183
+ fontWeight,
184
+ textAlign: 'center'
185
+ };
186
+ const defaultAccessibilityLabel = accessibilityLabel ?? `Comparison of ${total} bar${total === 1 ? '' : 's'}: ` + bars.map((bar, i) => {
187
+ const legend = typeof bar.legend === 'string' ? bar.legend : `bar ${i + 1}`;
188
+ return `${legend} ${bar.value}`;
189
+ }).join(', ');
190
+ return /*#__PURE__*/_jsxs(View, {
191
+ accessibilityLabel: defaultAccessibilityLabel,
192
+ style: [{
193
+ width: '100%'
194
+ }, style],
195
+ children: [/*#__PURE__*/_jsx(View, {
196
+ accessibilityRole: "image",
197
+ style: [{
198
+ flexDirection: 'row',
199
+ alignItems: 'flex-end',
200
+ height,
201
+ gap: wrapGap,
202
+ width: '100%'
203
+ }, chartStyle],
204
+ children: resolvedBars.map(({
205
+ original,
206
+ index,
207
+ ratio,
208
+ bgColor
209
+ }) => {
210
+ const barHeightPx = Math.max(0, barAreaHeight * ratio);
211
+ const valueBarTotalHeight = labelHeight + valueBarGap + barHeightPx;
212
+ const hasLabel = original.label !== undefined && original.label !== null && original.label !== false;
213
+ return /*#__PURE__*/_jsxs(View, {
214
+ accessibilityLabel: original.accessibilityLabel,
215
+ style: {
216
+ flex: 1,
217
+ flexDirection: 'column',
218
+ alignItems: 'stretch',
219
+ justifyContent: 'flex-end',
220
+ height: valueBarTotalHeight,
221
+ gap: valueBarGap,
222
+ minWidth: 1
223
+ },
224
+ children: [hasLabel ? /*#__PURE__*/_jsx(Text, {
225
+ numberOfLines: 1,
226
+ style: [computedLabelStyle, labelStyle],
227
+ children: original.label
228
+ }) : null, /*#__PURE__*/_jsx(BarShape, {
229
+ height: barHeightPx,
230
+ color: bgColor,
231
+ radius: barRadius
232
+ })]
233
+ }, original.key ?? `bar-${index}`);
234
+ })
235
+ }), /*#__PURE__*/_jsx(View, {
236
+ style: [{
237
+ marginTop: legendGap,
238
+ flexDirection: 'row',
239
+ alignItems: 'center',
240
+ justifyContent: 'space-between',
241
+ width: '100%'
242
+ }, legendStyle],
243
+ children: resolvedBars.map(({
244
+ original,
245
+ index,
246
+ barModes,
247
+ bgColor
248
+ }) => {
249
+ const indicatorOverride = original.color ? bgColor : undefined;
250
+ return /*#__PURE__*/_jsx(MetricLegendItem, {
251
+ label: original.legend,
252
+ value: original.legendValue,
253
+ modes: barModes,
254
+ ...(indicatorOverride !== undefined ? {
255
+ indicatorColor: indicatorOverride
256
+ } : {}),
257
+ style: {
258
+ flex: 1,
259
+ minWidth: 0
260
+ }
261
+ }, original.key ?? `legend-${index}`);
262
+ })
263
+ })]
264
+ });
265
+ }
266
+ export default CoverageBarComparison;
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+
3
+ import React from 'react';
4
+ import { View } from 'react-native';
5
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
+ import { useTokens } from '../../design-tokens/JFSThemeProvider';
7
+ import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils';
8
+ import Button from '../Button/Button';
9
+ import CircularProgressBar from '../CircularProgressBar/CircularProgressBar';
10
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
+ const toNumber = (value, fallback) => {
12
+ if (typeof value === 'number') {
13
+ return Number.isFinite(value) ? value : fallback;
14
+ }
15
+ if (typeof value === 'string') {
16
+ const parsed = Number(value);
17
+ return Number.isFinite(parsed) ? parsed : fallback;
18
+ }
19
+ return fallback;
20
+ };
21
+ const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
22
+
23
+ // The Figma "Coverage Ring" pairs a `circularProgressBar Size: M` ring with a
24
+ // `Button / Size: S` Secondary button. Locking these in as component defaults
25
+ // keeps the visual identical to the Figma reference when the caller supplies
26
+ // only their own brand/theme modes — caller modes are still merged on top so
27
+ // any of these can be overridden per-instance.
28
+ const COMPONENT_DEFAULT_MODES = Object.freeze({
29
+ 'circularProgressBar Size': 'M',
30
+ 'Button / Size': 'S'
31
+ });
32
+
33
+ // Match the Figma source: regardless of the overall AppearanceBrand/Emphasis
34
+ // the caller picks (used to colour the ring), the Coverage Ring CTA renders
35
+ // with the Secondary brand at Medium emphasis — which resolves to the
36
+ // pale-lavender background (`#dbcfff`) + deep-purple foreground (`#5d00b5`)
37
+ // shown in Figma. Mirrors the pattern used by `CardFinancialCondition.tsx`.
38
+ const BUTTON_FORCED_BRAND = Object.freeze({
39
+ AppearanceBrand: 'Secondary',
40
+ Emphasis: 'Medium'
41
+ });
42
+
43
+ /**
44
+ * `CoverageRing` renders a single-purpose insight: how many items out of a
45
+ * total have been covered (e.g. "4 of 7 benefits availed"), paired with a
46
+ * call-to-action button below the ring.
47
+ *
48
+ * It composes the existing {@link CircularProgressBar} (in its `M` size so
49
+ * there is room for a caption + value inside the ring) and {@link Button}
50
+ * (in its `S`, Secondary brand variant) — both driven by the same design
51
+ * tokens used by the rest of the design system.
52
+ *
53
+ * The ring fill is derived from `value / total`, so the percentage, the
54
+ * displayed `"{value} of {total}"` label and the support caption stay in
55
+ * sync automatically. There is no separate `progress` prop.
56
+ *
57
+ * @component
58
+ */
59
+ function CoverageRing({
60
+ value = 4,
61
+ total = 7,
62
+ supportText = 'Benefits availed',
63
+ valueLabel,
64
+ actionLabel = 'Learn more',
65
+ onActionPress,
66
+ actionProps,
67
+ action,
68
+ children,
69
+ modes: propModes = EMPTY_MODES,
70
+ style,
71
+ supportTextStyle,
72
+ valueStyle,
73
+ accessibilityLabel,
74
+ ...rest
75
+ }) {
76
+ const {
77
+ modes: globalModes
78
+ } = useTokens();
79
+
80
+ // Merge order matches the rest of the design system (see `Gauge.tsx`):
81
+ // 1. Component-level defaults — the lowest-priority safety net.
82
+ // 2. Global modes from `JFSThemeProvider` — app-wide theme.
83
+ // 3. Caller-supplied `propModes` — highest priority, overrides everything.
84
+ const modes = React.useMemo(() => ({
85
+ ...COMPONENT_DEFAULT_MODES,
86
+ ...globalModes,
87
+ ...propModes
88
+ }), [propModes, globalModes]);
89
+
90
+ // Force the Secondary brand on the button only; the ring stays on whatever
91
+ // brand the caller selected. Done via a forcedModes layer so callers can't
92
+ // accidentally bring back a Primary-coloured CTA through their own modes.
93
+ const buttonModes = React.useMemo(() => ({
94
+ ...modes,
95
+ ...BUTTON_FORCED_BRAND
96
+ }), [modes]);
97
+ const safeTotal = total > 0 ? total : 0;
98
+ const clampedValue = clamp(value, 0, safeTotal);
99
+ const progressPercent = safeTotal > 0 ? clampedValue / safeTotal * 100 : 0;
100
+ const computedValueLabel = valueLabel ?? `${clampedValue} of ${safeTotal}`;
101
+ const gap = toNumber(getVariableByName('coverageRing/gap', modes), 16);
102
+ const containerStyle = {
103
+ alignItems: 'center',
104
+ gap
105
+ };
106
+
107
+ // Custom action slot resolution order: explicit `action` prop wins over
108
+ // `children`. We pass `modes` down via cloneChildrenWithModes so any
109
+ // token-driven child (e.g. a custom `<Button>`) inherits the parent theme.
110
+ const customAction = action ?? children;
111
+ const hasCustomAction = customAction !== undefined && customAction !== null;
112
+ const defaultAccessibilityLabel = accessibilityLabel ?? `${supportText}, ${computedValueLabel}`;
113
+ return /*#__PURE__*/_jsxs(View, {
114
+ accessibilityLabel: defaultAccessibilityLabel,
115
+ style: [containerStyle, style],
116
+ ...rest,
117
+ children: [/*#__PURE__*/_jsx(CircularProgressBar, {
118
+ state: "Active",
119
+ value: progressPercent,
120
+ valueLabel: computedValueLabel,
121
+ supportText: supportText,
122
+ supportTextStyle: supportTextStyle,
123
+ valueStyle: valueStyle,
124
+ modes: modes,
125
+ accessibilityLabel: `${supportText}, ${computedValueLabel}`
126
+ }), hasCustomAction ? cloneChildrenWithModes(customAction, buttonModes) : /*#__PURE__*/_jsx(Button, {
127
+ label: actionLabel,
128
+ modes: buttonModes,
129
+ ...(onActionPress !== undefined ? {
130
+ onPress: onActionPress
131
+ } : {}),
132
+ ...actionProps
133
+ })]
134
+ });
135
+ }
136
+ export default CoverageRing;