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.
- package/CHANGELOG.md +60 -0
- package/lib/commonjs/components/AccordionCheckbox/AccordionCheckbox.js +239 -0
- package/lib/commonjs/components/BrandChip/BrandChip.js +149 -0
- package/lib/commonjs/components/CardAdvisory/CardAdvisory.js +2 -2
- package/lib/commonjs/components/CardBankAccount/CardBankAccount.js +213 -0
- package/lib/commonjs/components/CardFinancialCondition/CardFinancialCondition.js +213 -0
- package/lib/commonjs/components/CardInsight/CardInsight.js +166 -0
- package/lib/commonjs/components/Carousel/Carousel.js +9 -7
- package/lib/commonjs/components/CheckboxGroup/CheckboxGroup.js +67 -0
- package/lib/commonjs/components/CheckboxItem/CheckboxItem.js +125 -0
- package/lib/commonjs/components/CircularProgressBar/CircularProgressBar.js +56 -9
- package/lib/commonjs/components/CoverageBarComparison/CoverageBarComparison.js +272 -0
- package/lib/commonjs/components/CoverageRing/CoverageRing.js +141 -0
- package/lib/commonjs/components/DonutChart/DonutChart.js +309 -0
- package/lib/commonjs/components/DonutChartSummary/DonutChartSummary.js +155 -0
- package/lib/commonjs/components/HoldingsCard/HoldingsCard.js +2 -2
- package/lib/commonjs/components/InstitutionBadge/InstitutionBadge.js +132 -0
- package/lib/commonjs/components/LinearMeter/LinearMeter.js +9 -28
- package/lib/commonjs/components/LinearProgress/LinearProgress.js +68 -0
- package/lib/commonjs/components/MetricLegendItem/MetricLegendItem.js +95 -0
- package/lib/commonjs/components/MonthlyStatusGrid/MonthlyStatusGrid.js +286 -0
- package/lib/commonjs/components/OTP/OTP.js +381 -37
- package/lib/commonjs/components/ProductOverview/ProductOverview.js +147 -0
- package/lib/commonjs/components/Radio/Radio.js +194 -0
- package/lib/commonjs/components/RadioButton/RadioButton.js +21 -188
- package/lib/commonjs/components/RangeTrack/RangeTrack.js +269 -0
- package/lib/commonjs/components/SavingsGoalSummary/SavingsGoalSummary.js +181 -0
- package/lib/commonjs/components/SegmentedTrack/SegmentedTrack.js +171 -0
- package/lib/commonjs/components/StatGroup/StatGroup.js +128 -0
- package/lib/commonjs/components/StatItem/StatItem.js +65 -35
- package/lib/commonjs/components/StrengthIndicator/StrengthIndicator.js +157 -0
- package/lib/commonjs/components/SummaryTile/SummaryTile.js +150 -0
- package/lib/commonjs/components/index.js +192 -1
- package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/commonjs/utils/index.js +7 -0
- package/lib/commonjs/utils/number-utils.js +57 -0
- package/lib/module/components/AccordionCheckbox/AccordionCheckbox.js +233 -0
- package/lib/module/components/BrandChip/BrandChip.js +143 -0
- package/lib/module/components/CardAdvisory/CardAdvisory.js +2 -2
- package/lib/module/components/CardBankAccount/CardBankAccount.js +208 -0
- package/lib/module/components/CardFinancialCondition/CardFinancialCondition.js +207 -0
- package/lib/module/components/CardInsight/CardInsight.js +161 -0
- package/lib/module/components/Carousel/Carousel.js +9 -7
- package/lib/module/components/CheckboxGroup/CheckboxGroup.js +62 -0
- package/lib/module/components/CheckboxItem/CheckboxItem.js +119 -0
- package/lib/module/components/CircularProgressBar/CircularProgressBar.js +56 -9
- package/lib/module/components/CoverageBarComparison/CoverageBarComparison.js +266 -0
- package/lib/module/components/CoverageRing/CoverageRing.js +136 -0
- package/lib/module/components/DonutChart/DonutChart.js +303 -0
- package/lib/module/components/DonutChartSummary/DonutChartSummary.js +150 -0
- package/lib/module/components/HoldingsCard/HoldingsCard.js +2 -2
- package/lib/module/components/InstitutionBadge/InstitutionBadge.js +127 -0
- package/lib/module/components/LinearMeter/LinearMeter.js +9 -28
- package/lib/module/components/LinearProgress/LinearProgress.js +63 -0
- package/lib/module/components/MetricLegendItem/MetricLegendItem.js +90 -0
- package/lib/module/components/MonthlyStatusGrid/MonthlyStatusGrid.js +281 -0
- package/lib/module/components/OTP/OTP.js +381 -38
- package/lib/module/components/ProductOverview/ProductOverview.js +142 -0
- package/lib/module/components/Radio/Radio.js +188 -0
- package/lib/module/components/RadioButton/RadioButton.js +20 -185
- package/lib/module/components/RangeTrack/RangeTrack.js +263 -0
- package/lib/module/components/SavingsGoalSummary/SavingsGoalSummary.js +175 -0
- package/lib/module/components/SegmentedTrack/SegmentedTrack.js +166 -0
- package/lib/module/components/StatGroup/StatGroup.js +123 -0
- package/lib/module/components/StatItem/StatItem.js +66 -36
- package/lib/module/components/StrengthIndicator/StrengthIndicator.js +152 -0
- package/lib/module/components/SummaryTile/SummaryTile.js +145 -0
- package/lib/module/components/index.js +28 -1
- package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/module/icons/registry.js +1 -1
- package/lib/module/utils/index.js +2 -1
- package/lib/module/utils/number-utils.js +53 -0
- package/lib/typescript/src/components/AccordionCheckbox/AccordionCheckbox.d.ts +71 -0
- package/lib/typescript/src/components/BrandChip/BrandChip.d.ts +43 -0
- package/lib/typescript/src/components/CardBankAccount/CardBankAccount.d.ts +79 -0
- package/lib/typescript/src/components/CardFinancialCondition/CardFinancialCondition.d.ts +50 -0
- package/lib/typescript/src/components/CardInsight/CardInsight.d.ts +48 -0
- package/lib/typescript/src/components/CheckboxGroup/CheckboxGroup.d.ts +41 -0
- package/lib/typescript/src/components/CheckboxItem/CheckboxItem.d.ts +56 -0
- package/lib/typescript/src/components/CircularProgressBar/CircularProgressBar.d.ts +11 -1
- package/lib/typescript/src/components/CoverageBarComparison/CoverageBarComparison.d.ts +105 -0
- package/lib/typescript/src/components/CoverageRing/CoverageRing.d.ts +90 -0
- package/lib/typescript/src/components/DonutChart/DonutChart.d.ts +117 -0
- package/lib/typescript/src/components/DonutChartSummary/DonutChartSummary.d.ts +103 -0
- package/lib/typescript/src/components/InstitutionBadge/InstitutionBadge.d.ts +30 -0
- package/lib/typescript/src/components/LinearProgress/LinearProgress.d.ts +17 -0
- package/lib/typescript/src/components/MetricLegendItem/MetricLegendItem.d.ts +37 -0
- package/lib/typescript/src/components/MonthlyStatusGrid/MonthlyStatusGrid.d.ts +119 -0
- package/lib/typescript/src/components/OTP/OTP.d.ts +88 -2
- package/lib/typescript/src/components/ProductOverview/ProductOverview.d.ts +39 -0
- package/lib/typescript/src/components/Radio/Radio.d.ts +30 -0
- package/lib/typescript/src/components/RadioButton/RadioButton.d.ts +20 -28
- package/lib/typescript/src/components/RangeTrack/RangeTrack.d.ts +173 -0
- package/lib/typescript/src/components/SavingsGoalSummary/SavingsGoalSummary.d.ts +95 -0
- package/lib/typescript/src/components/SegmentedTrack/SegmentedTrack.d.ts +108 -0
- package/lib/typescript/src/components/StatGroup/StatGroup.d.ts +45 -0
- package/lib/typescript/src/components/StatItem/StatItem.d.ts +24 -7
- package/lib/typescript/src/components/StrengthIndicator/StrengthIndicator.d.ts +58 -0
- package/lib/typescript/src/components/SummaryTile/SummaryTile.d.ts +60 -0
- package/lib/typescript/src/components/index.d.ts +29 -2
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/lib/typescript/src/utils/index.d.ts +1 -0
- package/lib/typescript/src/utils/number-utils.d.ts +29 -0
- package/package.json +1 -1
- package/src/components/AccordionCheckbox/AccordionCheckbox.tsx +323 -0
- package/src/components/BrandChip/BrandChip.tsx +235 -0
- package/src/components/CardAdvisory/CardAdvisory.tsx +2 -2
- package/src/components/CardBankAccount/CardBankAccount.tsx +295 -0
- package/src/components/CardFinancialCondition/CardFinancialCondition.tsx +366 -0
- package/src/components/CardInsight/CardInsight.tsx +239 -0
- package/src/components/Carousel/Carousel.tsx +14 -6
- package/src/components/CheckboxGroup/CheckboxGroup.tsx +86 -0
- package/src/components/CheckboxItem/CheckboxItem.tsx +174 -0
- package/src/components/CircularProgressBar/CircularProgressBar.tsx +74 -9
- package/src/components/CoverageBarComparison/CoverageBarComparison.tsx +378 -0
- package/src/components/CoverageRing/CoverageRing.tsx +225 -0
- package/src/components/DonutChart/DonutChart.tsx +503 -0
- package/src/components/DonutChartSummary/DonutChartSummary.tsx +256 -0
- package/src/components/HoldingsCard/HoldingsCard.tsx +2 -2
- package/src/components/InstitutionBadge/InstitutionBadge.tsx +216 -0
- package/src/components/LinearMeter/LinearMeter.tsx +9 -39
- package/src/components/LinearProgress/LinearProgress.tsx +92 -0
- package/src/components/MetricLegendItem/MetricLegendItem.tsx +167 -0
- package/src/components/MonthlyStatusGrid/MonthlyStatusGrid.tsx +438 -0
- package/src/components/OTP/OTP.tsx +476 -29
- package/src/components/ProductOverview/ProductOverview.tsx +236 -0
- package/src/components/Radio/Radio.tsx +227 -0
- package/src/components/RadioButton/RadioButton.tsx +23 -225
- package/src/components/RangeTrack/RangeTrack.tsx +394 -0
- package/src/components/SavingsGoalSummary/SavingsGoalSummary.tsx +269 -0
- package/src/components/SegmentedTrack/SegmentedTrack.tsx +268 -0
- package/src/components/StatGroup/StatGroup.tsx +169 -0
- package/src/components/StatItem/StatItem.tsx +117 -40
- package/src/components/StrengthIndicator/StrengthIndicator.tsx +205 -0
- package/src/components/SummaryTile/SummaryTile.tsx +251 -0
- package/src/components/index.ts +39 -2
- package/src/design-tokens/Coin Variables-variables-full.json +1 -1
- package/src/icons/registry.ts +1 -1
- package/src/utils/index.ts +1 -0
- 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
|
|
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
|
-
<
|
|
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
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|