jfs-components 0.0.70 → 0.0.72
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 +49 -0
- package/lib/commonjs/components/CardAdvisory/CardAdvisory.js +203 -0
- package/lib/commonjs/components/CardCTA/CardCTA.js +198 -16
- package/lib/commonjs/components/CardFinancialCondition/CardFinancialCondition.js +213 -0
- package/lib/commonjs/components/Carousel/Carousel.js +9 -7
- package/lib/commonjs/components/CircularProgressBar/CircularProgressBar.js +147 -0
- package/lib/commonjs/components/CircularProgressBarDoted/CircularProgressBarDoted.js +258 -0
- package/lib/commonjs/components/CircularRating/CircularRating.js +161 -0
- package/lib/commonjs/components/Gauge/Gauge.js +223 -0
- package/lib/commonjs/components/HoldingsCard/HoldingsCard.js +2 -2
- package/lib/commonjs/components/InstitutionBadge/InstitutionBadge.js +132 -0
- package/lib/commonjs/components/ListGroup/ListGroup.js +3 -1
- package/lib/commonjs/components/Nudge/Nudge.js +179 -87
- package/lib/commonjs/components/Radio/Radio.js +194 -0
- package/lib/commonjs/components/RadioButton/RadioButton.js +21 -188
- package/lib/commonjs/components/index.js +56 -0
- package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/module/components/CardAdvisory/CardAdvisory.js +197 -0
- package/lib/module/components/CardCTA/CardCTA.js +199 -17
- package/lib/module/components/CardFinancialCondition/CardFinancialCondition.js +207 -0
- package/lib/module/components/Carousel/Carousel.js +9 -7
- package/lib/module/components/CircularProgressBar/CircularProgressBar.js +141 -0
- package/lib/module/components/CircularProgressBarDoted/CircularProgressBarDoted.js +253 -0
- package/lib/module/components/CircularRating/CircularRating.js +155 -0
- package/lib/module/components/Gauge/Gauge.js +217 -0
- package/lib/module/components/HoldingsCard/HoldingsCard.js +2 -2
- package/lib/module/components/InstitutionBadge/InstitutionBadge.js +127 -0
- package/lib/module/components/ListGroup/ListGroup.js +3 -1
- package/lib/module/components/Nudge/Nudge.js +178 -87
- package/lib/module/components/Radio/Radio.js +188 -0
- package/lib/module/components/RadioButton/RadioButton.js +20 -185
- package/lib/module/components/index.js +12 -0
- package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/module/icons/registry.js +1 -1
- package/lib/typescript/src/components/CardAdvisory/CardAdvisory.d.ts +49 -0
- package/lib/typescript/src/components/CardCTA/CardCTA.d.ts +16 -1
- package/lib/typescript/src/components/CardFinancialCondition/CardFinancialCondition.d.ts +50 -0
- package/lib/typescript/src/components/CircularProgressBar/CircularProgressBar.d.ts +27 -0
- package/lib/typescript/src/components/CircularProgressBarDoted/CircularProgressBarDoted.d.ts +48 -0
- package/lib/typescript/src/components/CircularRating/CircularRating.d.ts +49 -0
- package/lib/typescript/src/components/Gauge/Gauge.d.ts +53 -0
- package/lib/typescript/src/components/InstitutionBadge/InstitutionBadge.d.ts +30 -0
- package/lib/typescript/src/components/Nudge/Nudge.d.ts +14 -11
- 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/index.d.ts +13 -1
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/CardAdvisory/CardAdvisory.tsx +283 -0
- package/src/components/CardCTA/CardCTA.tsx +236 -13
- package/src/components/CardFinancialCondition/CardFinancialCondition.tsx +366 -0
- package/src/components/Carousel/Carousel.tsx +14 -6
- package/src/components/CircularProgressBar/CircularProgressBar.tsx +190 -0
- package/src/components/CircularProgressBarDoted/CircularProgressBarDoted.tsx +357 -0
- package/src/components/CircularRating/CircularRating.tsx +241 -0
- package/src/components/Gauge/Gauge.tsx +303 -0
- package/src/components/HoldingsCard/HoldingsCard.tsx +2 -2
- package/src/components/InstitutionBadge/InstitutionBadge.tsx +216 -0
- package/src/components/ListGroup/ListGroup.tsx +3 -1
- package/src/components/Nudge/Nudge.tsx +222 -82
- package/src/components/Radio/Radio.tsx +227 -0
- package/src/components/RadioButton/RadioButton.tsx +23 -225
- package/src/components/index.ts +13 -1
- package/src/design-tokens/Coin Variables-variables-full.json +1 -1
- package/src/icons/registry.ts +1 -1
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { StyleSheet, View, Text, type StyleProp, type TextStyle, type ViewStyle } from 'react-native'
|
|
3
|
+
import Svg, { Path } from 'react-native-svg'
|
|
4
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
5
|
+
import { useTokens } from '../../design-tokens/JFSThemeProvider'
|
|
6
|
+
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
|
|
7
|
+
import SupportText, { type SupportTextProps } from '../SupportText/SupportText'
|
|
8
|
+
|
|
9
|
+
type GaugeBaseProps = Omit<React.ComponentProps<typeof View>, 'children' | 'style'>
|
|
10
|
+
|
|
11
|
+
export type GaugeProps = GaugeBaseProps & {
|
|
12
|
+
/** Current gauge value. Interpreted against `min` and `max`. */
|
|
13
|
+
value?: number
|
|
14
|
+
/** Lower bound used to normalize progress. */
|
|
15
|
+
min?: number
|
|
16
|
+
/** Upper bound used to normalize progress. */
|
|
17
|
+
max?: number
|
|
18
|
+
/** Optional formatted value shown in the default readout. */
|
|
19
|
+
valueLabel?: string
|
|
20
|
+
/** Heading above the arc. */
|
|
21
|
+
title?: string
|
|
22
|
+
/** Caption below the arc. */
|
|
23
|
+
caption?: string
|
|
24
|
+
/** Support text shown in the default readout. */
|
|
25
|
+
supportText?: string
|
|
26
|
+
/** Status passed to the default SupportText component. */
|
|
27
|
+
supportTextStatus?: SupportTextProps['status']
|
|
28
|
+
/** Hides the heading while keeping the rest of the layout intact. */
|
|
29
|
+
showTitle?: boolean
|
|
30
|
+
/** Hides the caption while keeping the rest of the layout intact. */
|
|
31
|
+
showCaption?: boolean
|
|
32
|
+
/** Hides default support text when no custom readout slot is provided. */
|
|
33
|
+
showSupportText?: boolean
|
|
34
|
+
/** Design token modes forwarded to token lookups and slot children. */
|
|
35
|
+
modes?: Record<string, any>
|
|
36
|
+
/** Slot rendered in the center of the gauge arc. Receives `modes` recursively. */
|
|
37
|
+
children?: React.ReactNode
|
|
38
|
+
/** Container style override. */
|
|
39
|
+
style?: StyleProp<ViewStyle>
|
|
40
|
+
/** Arc wrapper style override. */
|
|
41
|
+
arcStyle?: StyleProp<ViewStyle>
|
|
42
|
+
/** Readout container style override. */
|
|
43
|
+
readoutStyle?: StyleProp<ViewStyle>
|
|
44
|
+
/** Title text style override. */
|
|
45
|
+
titleStyle?: StyleProp<TextStyle>
|
|
46
|
+
/** Value text style override. */
|
|
47
|
+
valueStyle?: StyleProp<TextStyle>
|
|
48
|
+
/** Caption text style override. */
|
|
49
|
+
captionStyle?: StyleProp<TextStyle>
|
|
50
|
+
/** Track stroke style override. */
|
|
51
|
+
trackStyle?: StyleProp<ViewStyle>
|
|
52
|
+
/** Progress stroke style override. */
|
|
53
|
+
progressStyle?: StyleProp<ViewStyle>
|
|
54
|
+
/** Accessibility label for the whole gauge. */
|
|
55
|
+
accessibilityLabel?: string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const clamp = (value: number, min: number, max: number) => {
|
|
59
|
+
if (max <= min) {
|
|
60
|
+
return 0
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return Math.min(1, Math.max(0, (value - min) / (max - min)))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const toNumber = (value: unknown, fallback: number) => {
|
|
67
|
+
if (typeof value === 'number') {
|
|
68
|
+
return Number.isFinite(value) ? value : fallback
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (typeof value === 'string') {
|
|
72
|
+
const parsed = Number(value)
|
|
73
|
+
return Number.isFinite(parsed) ? parsed : fallback
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return fallback
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const toFontWeight = (value: unknown, fallback: TextStyle['fontWeight']) => {
|
|
80
|
+
if (typeof value === 'number') {
|
|
81
|
+
return String(value) as TextStyle['fontWeight']
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (typeof value === 'string') {
|
|
85
|
+
return value as TextStyle['fontWeight']
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return fallback
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const getStrokeColor = (style: StyleProp<ViewStyle>, fallback: string) => {
|
|
92
|
+
const flattened = StyleSheet.flatten(style)
|
|
93
|
+
return typeof flattened.backgroundColor === 'string'
|
|
94
|
+
? flattened.backgroundColor
|
|
95
|
+
: fallback
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function Gauge({
|
|
99
|
+
value = 84,
|
|
100
|
+
min = 0,
|
|
101
|
+
max = 100,
|
|
102
|
+
valueLabel,
|
|
103
|
+
title = 'Your score is based on strong data',
|
|
104
|
+
caption = 'Add more details for even better accuracy',
|
|
105
|
+
supportText = 'Support Text',
|
|
106
|
+
supportTextStatus = 'Success',
|
|
107
|
+
showTitle = true,
|
|
108
|
+
showCaption = true,
|
|
109
|
+
showSupportText = true,
|
|
110
|
+
modes: propModes = EMPTY_MODES,
|
|
111
|
+
children,
|
|
112
|
+
style,
|
|
113
|
+
arcStyle,
|
|
114
|
+
readoutStyle,
|
|
115
|
+
titleStyle,
|
|
116
|
+
valueStyle,
|
|
117
|
+
captionStyle,
|
|
118
|
+
trackStyle,
|
|
119
|
+
progressStyle,
|
|
120
|
+
accessibilityLabel,
|
|
121
|
+
...rest
|
|
122
|
+
}: GaugeProps) {
|
|
123
|
+
const { modes: globalModes } = useTokens()
|
|
124
|
+
const modes = { ...globalModes, ...propModes }
|
|
125
|
+
const supportTextModes = { ...modes, Status: supportTextStatus }
|
|
126
|
+
|
|
127
|
+
const gap = toNumber(getVariableByName('gauge/gap', modes), 30)
|
|
128
|
+
const padding = toNumber(getVariableByName('gauge/padding', modes), 16)
|
|
129
|
+
const width = toNumber(getVariableByName('gauge/width', modes), 299)
|
|
130
|
+
const arcWidth = toNumber(getVariableByName('gauge/arc/width', modes), 267)
|
|
131
|
+
const arcHeight = arcWidth * (186 / 328)
|
|
132
|
+
|
|
133
|
+
const trackSize = toNumber(getVariableByName('gauge/track/size', modes), 244)
|
|
134
|
+
const trackInnerRadius = toNumber(getVariableByName('gauge/track/radius', modes), 99)
|
|
135
|
+
const progressInnerRadius = toNumber(getVariableByName('gauge/progress/radius', modes), 99)
|
|
136
|
+
const innerRadius = Math.min(trackInnerRadius, progressInnerRadius)
|
|
137
|
+
const strokeWidth = Math.max(1, trackSize / 2 - innerRadius)
|
|
138
|
+
const radius = innerRadius + strokeWidth / 2
|
|
139
|
+
const centerX = arcWidth / 2
|
|
140
|
+
const centerY = Math.min(arcHeight - strokeWidth / 2, radius + strokeWidth / 2)
|
|
141
|
+
const startX = centerX - radius
|
|
142
|
+
const endX = centerX + radius
|
|
143
|
+
const arcPath = `M ${startX} ${centerY} A ${radius} ${radius} 0 0 1 ${endX} ${centerY}`
|
|
144
|
+
const arcLength = Math.PI * radius
|
|
145
|
+
const progress = clamp(value, min, max)
|
|
146
|
+
|
|
147
|
+
const titleColor = getVariableByName('gauge/title/color', modes) || '#0c0d10'
|
|
148
|
+
const titleFontSize = toNumber(getVariableByName('gauge/title/fontSize', modes), 23)
|
|
149
|
+
const titleLineHeight = toNumber(getVariableByName('gauge/title/lineHeight', modes), 23)
|
|
150
|
+
const titleFontFamily = getVariableByName('gauge/title/fontFamily', modes) || 'JioType Var'
|
|
151
|
+
const titleFontWeight = toFontWeight(getVariableByName('gauge/title/fontWeight', modes), '900')
|
|
152
|
+
const titleHeight = toNumber(getVariableByName('gauge/title/height', modes), 46)
|
|
153
|
+
|
|
154
|
+
const readoutGap = toNumber(getVariableByName('gauge/readout/gap', modes), 8)
|
|
155
|
+
const readoutPadding = toNumber(getVariableByName('gauge/readout/padding', modes), 8)
|
|
156
|
+
const valueColor = getVariableByName('gauge/readout/value/color', modes) || '#0c0d10'
|
|
157
|
+
const valueFontSize = toNumber(getVariableByName('gauge/readout/value/fontSize', modes), 36)
|
|
158
|
+
const valueLineHeight = toNumber(getVariableByName('gauge/readout/value/lineHeight', modes), 36)
|
|
159
|
+
const valueFontFamily = getVariableByName('gauge/readout/value/fontFamily', modes) || 'JioType Var'
|
|
160
|
+
const valueFontWeight = toFontWeight(getVariableByName('gauge/readout/value/fontWeight', modes), '800')
|
|
161
|
+
|
|
162
|
+
const captionColor = getVariableByName('gauge/caption/color', modes) || '#0c0d10'
|
|
163
|
+
const captionFontSize = toNumber(getVariableByName('gauge/caption/fontSize', modes), 14)
|
|
164
|
+
const captionLineHeight = toNumber(getVariableByName('gauge/caption/lineHeight', modes), 18)
|
|
165
|
+
const captionFontFamily = getVariableByName('gauge/caption/fontFamily', modes) || 'JioType Var'
|
|
166
|
+
const captionFontWeight = toFontWeight(getVariableByName('gauge/caption/fontWeight', modes), '500')
|
|
167
|
+
|
|
168
|
+
const trackColor = getStrokeColor(
|
|
169
|
+
trackStyle,
|
|
170
|
+
getVariableByName('gauge/track/color', modes) || '#f5f5f6'
|
|
171
|
+
)
|
|
172
|
+
const progressColor = getStrokeColor(
|
|
173
|
+
progressStyle,
|
|
174
|
+
getVariableByName('gauge/progress/color', modes) || '#25ab21'
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
const containerStyle: ViewStyle = {
|
|
178
|
+
alignItems: 'center',
|
|
179
|
+
gap,
|
|
180
|
+
padding,
|
|
181
|
+
width,
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const computedTitleStyle: TextStyle = {
|
|
185
|
+
color: titleColor as string,
|
|
186
|
+
fontFamily: titleFontFamily as string,
|
|
187
|
+
fontSize: titleFontSize,
|
|
188
|
+
fontWeight: titleFontWeight,
|
|
189
|
+
lineHeight: titleLineHeight,
|
|
190
|
+
minHeight: titleHeight,
|
|
191
|
+
textAlign: 'center',
|
|
192
|
+
width: '100%',
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const computedArcStyle: ViewStyle = {
|
|
196
|
+
height: arcHeight,
|
|
197
|
+
overflow: 'hidden',
|
|
198
|
+
position: 'relative',
|
|
199
|
+
width: '100%',
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const computedReadoutStyle: ViewStyle = {
|
|
203
|
+
alignItems: 'center',
|
|
204
|
+
gap: readoutGap,
|
|
205
|
+
left: 0,
|
|
206
|
+
padding: readoutPadding,
|
|
207
|
+
position: 'absolute',
|
|
208
|
+
right: 0,
|
|
209
|
+
top: arcHeight * 0.42,
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const computedValueStyle: TextStyle = {
|
|
213
|
+
color: valueColor as string,
|
|
214
|
+
fontFamily: valueFontFamily as string,
|
|
215
|
+
fontSize: valueFontSize,
|
|
216
|
+
fontWeight: valueFontWeight,
|
|
217
|
+
lineHeight: valueLineHeight,
|
|
218
|
+
textAlign: 'center',
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const computedCaptionStyle: TextStyle = {
|
|
222
|
+
color: captionColor as string,
|
|
223
|
+
fontFamily: captionFontFamily as string,
|
|
224
|
+
fontSize: captionFontSize,
|
|
225
|
+
fontWeight: captionFontWeight,
|
|
226
|
+
lineHeight: captionLineHeight,
|
|
227
|
+
textAlign: 'center',
|
|
228
|
+
width: '100%',
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const defaultValueLabel = valueLabel ?? String(Math.round(value))
|
|
232
|
+
const defaultAccessibilityLabel =
|
|
233
|
+
accessibilityLabel ??
|
|
234
|
+
`${title}. ${defaultValueLabel} out of ${max}. ${supportText}. ${caption}`
|
|
235
|
+
|
|
236
|
+
return (
|
|
237
|
+
<View
|
|
238
|
+
accessibilityRole="progressbar"
|
|
239
|
+
accessibilityLabel={defaultAccessibilityLabel}
|
|
240
|
+
accessibilityValue={{ min, max, now: value }}
|
|
241
|
+
style={[containerStyle, style]}
|
|
242
|
+
{...rest}
|
|
243
|
+
>
|
|
244
|
+
{showTitle ? (
|
|
245
|
+
<Text style={[computedTitleStyle, titleStyle]}>
|
|
246
|
+
{title}
|
|
247
|
+
</Text>
|
|
248
|
+
) : null}
|
|
249
|
+
|
|
250
|
+
<View style={[computedArcStyle, arcStyle]}>
|
|
251
|
+
<Svg
|
|
252
|
+
width="100%"
|
|
253
|
+
height={arcHeight}
|
|
254
|
+
viewBox={`0 0 ${arcWidth} ${arcHeight}`}
|
|
255
|
+
>
|
|
256
|
+
<Path
|
|
257
|
+
d={arcPath}
|
|
258
|
+
stroke={trackColor}
|
|
259
|
+
strokeWidth={strokeWidth}
|
|
260
|
+
strokeLinecap="butt"
|
|
261
|
+
fill="none"
|
|
262
|
+
/>
|
|
263
|
+
<Path
|
|
264
|
+
d={arcPath}
|
|
265
|
+
stroke={progressColor}
|
|
266
|
+
strokeWidth={strokeWidth}
|
|
267
|
+
strokeLinecap="butt"
|
|
268
|
+
fill="none"
|
|
269
|
+
strokeDasharray={`${arcLength} ${arcLength}`}
|
|
270
|
+
strokeDashoffset={arcLength * (1 - progress)}
|
|
271
|
+
/>
|
|
272
|
+
</Svg>
|
|
273
|
+
|
|
274
|
+
<View style={[computedReadoutStyle, readoutStyle]}>
|
|
275
|
+
{children ? (
|
|
276
|
+
cloneChildrenWithModes(children, modes)
|
|
277
|
+
) : (
|
|
278
|
+
<>
|
|
279
|
+
<Text style={[computedValueStyle, valueStyle]}>
|
|
280
|
+
{defaultValueLabel}
|
|
281
|
+
</Text>
|
|
282
|
+
{showSupportText ? (
|
|
283
|
+
<SupportText
|
|
284
|
+
label={supportText}
|
|
285
|
+
status={supportTextStatus}
|
|
286
|
+
modes={supportTextModes}
|
|
287
|
+
/>
|
|
288
|
+
) : null}
|
|
289
|
+
</>
|
|
290
|
+
)}
|
|
291
|
+
</View>
|
|
292
|
+
</View>
|
|
293
|
+
|
|
294
|
+
{showCaption ? (
|
|
295
|
+
<Text style={[computedCaptionStyle, captionStyle]}>
|
|
296
|
+
{caption}
|
|
297
|
+
</Text>
|
|
298
|
+
) : null}
|
|
299
|
+
</View>
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export default Gauge
|
|
@@ -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)
|
|
@@ -55,7 +55,9 @@ function ListGroup({
|
|
|
55
55
|
}: ListGroupProps) {
|
|
56
56
|
// Resolve container tokens
|
|
57
57
|
const backgroundColor = getVariableByName('listGroup/background', modes) || 'rgba(255,255,255,0)'
|
|
58
|
-
|
|
58
|
+
// The current exported token aliases a missing Figma variable. Keep the
|
|
59
|
+
// transparent fallback without logging the missing alias on every render.
|
|
60
|
+
const borderColor = 'rgba(255,255,255,0)'
|
|
59
61
|
const borderWidth = getVariableByName('listGroup/borderWidth', modes) || 0
|
|
60
62
|
const gap = getVariableByName('listGroup/gap', modes) || 12
|
|
61
63
|
|