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,366 @@
|
|
|
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 Button from '../Button/Button'
|
|
13
|
+
import CircularProgressBar, {
|
|
14
|
+
type CircularProgressBarProps,
|
|
15
|
+
} from '../CircularProgressBar/CircularProgressBar'
|
|
16
|
+
import Divider from '../Divider/Divider'
|
|
17
|
+
import Nudge from '../Nudge/Nudge'
|
|
18
|
+
|
|
19
|
+
type CardFinancialConditionBaseProps = Omit<
|
|
20
|
+
React.ComponentProps<typeof View>,
|
|
21
|
+
'children' | 'style'
|
|
22
|
+
>
|
|
23
|
+
|
|
24
|
+
export type CardFinancialConditionProps = CardFinancialConditionBaseProps & {
|
|
25
|
+
/** Title text shown in the header. */
|
|
26
|
+
title?: string
|
|
27
|
+
/** Body / description shown below the title. */
|
|
28
|
+
body?: string
|
|
29
|
+
/** Progress score for the circular indicator. Forwarded to CircularProgressBar. */
|
|
30
|
+
value?: number
|
|
31
|
+
/** Visual state of the circular indicator. Forwarded to CircularProgressBar. */
|
|
32
|
+
progressState?: CircularProgressBarProps['state']
|
|
33
|
+
/** Optional formatted value label for the active state. */
|
|
34
|
+
valueLabel?: string
|
|
35
|
+
/** Show the bottom advisory nudge. */
|
|
36
|
+
showNudge?: boolean
|
|
37
|
+
/** Body text for the default nudge. Use `\n` to add a second line. */
|
|
38
|
+
nudgeBody?: string
|
|
39
|
+
/** Show the bottom divider above the action button. */
|
|
40
|
+
showDivider?: boolean
|
|
41
|
+
/** Show the action button at the bottom of the card. */
|
|
42
|
+
showButton?: boolean
|
|
43
|
+
/** Label for the default action button. */
|
|
44
|
+
buttonLabel?: string
|
|
45
|
+
/** Callback for the default action button press. */
|
|
46
|
+
onPressButton?: () => void
|
|
47
|
+
/** Slot replacing the default circular progress bar. Receives `modes` recursively. */
|
|
48
|
+
progressSlot?: React.ReactNode
|
|
49
|
+
/** Slot replacing the default Nudge. Receives `modes` recursively. */
|
|
50
|
+
nudgeSlot?: React.ReactNode
|
|
51
|
+
/** Slot replacing the default action button. Receives `modes` recursively. */
|
|
52
|
+
buttonSlot?: React.ReactNode
|
|
53
|
+
/** Design token modes forwarded to token lookups and child components. */
|
|
54
|
+
modes?: Record<string, any>
|
|
55
|
+
/** Container style override. */
|
|
56
|
+
style?: StyleProp<ViewStyle>
|
|
57
|
+
/** Header row style override. */
|
|
58
|
+
headerStyle?: StyleProp<ViewStyle>
|
|
59
|
+
/** Title text style override. */
|
|
60
|
+
titleStyle?: StyleProp<TextStyle>
|
|
61
|
+
/** Body text style override. */
|
|
62
|
+
bodyStyle?: StyleProp<TextStyle>
|
|
63
|
+
/** Accessibility label for the whole card. */
|
|
64
|
+
accessibilityLabel?: string
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface CardFinancialConditionTokens {
|
|
68
|
+
containerStyle: ViewStyle
|
|
69
|
+
headerStyle: ViewStyle
|
|
70
|
+
textContentStyle: ViewStyle
|
|
71
|
+
titleStyle: TextStyle
|
|
72
|
+
bodyStyle: TextStyle
|
|
73
|
+
nudgeBodyStyle: TextStyle
|
|
74
|
+
buttonWrapStyle: ViewStyle
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const toNumber = (value: unknown, fallback: number) => {
|
|
78
|
+
if (typeof value === 'number') {
|
|
79
|
+
return Number.isFinite(value) ? value : fallback
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (typeof value === 'string') {
|
|
83
|
+
const parsed = Number(value)
|
|
84
|
+
return Number.isFinite(parsed) ? parsed : fallback
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return fallback
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const toFontWeight = (value: unknown, fallback: TextStyle['fontWeight']) => {
|
|
91
|
+
if (typeof value === 'number') {
|
|
92
|
+
return String(value) as TextStyle['fontWeight']
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (typeof value === 'string') {
|
|
96
|
+
return value as TextStyle['fontWeight']
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return fallback
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function resolveCardFinancialConditionTokens(
|
|
103
|
+
modes: Record<string, any>
|
|
104
|
+
): CardFinancialConditionTokens {
|
|
105
|
+
const background =
|
|
106
|
+
(getVariableByName('financialConditionCard/background', modes) as string) ||
|
|
107
|
+
'#ffffff'
|
|
108
|
+
const radius = toNumber(
|
|
109
|
+
getVariableByName('financialConditionCard/radius', modes),
|
|
110
|
+
16
|
|
111
|
+
)
|
|
112
|
+
const paddingH = toNumber(
|
|
113
|
+
getVariableByName('financialConditionCard/padding/horizontal', modes),
|
|
114
|
+
16
|
|
115
|
+
)
|
|
116
|
+
const paddingV = toNumber(
|
|
117
|
+
getVariableByName('financialConditionCard/padding/vertical', modes),
|
|
118
|
+
16
|
|
119
|
+
)
|
|
120
|
+
const gap = toNumber(
|
|
121
|
+
getVariableByName('financialConditionCard/gap', modes),
|
|
122
|
+
8
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
const headerGap = toNumber(
|
|
126
|
+
getVariableByName('financialConditionCard/header/gap', modes),
|
|
127
|
+
16
|
|
128
|
+
)
|
|
129
|
+
const textContentGap = toNumber(
|
|
130
|
+
getVariableByName('financialConditionCard/textContent/gap', modes),
|
|
131
|
+
8
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
const titleColor =
|
|
135
|
+
(getVariableByName('financialConditionCard/title/color', modes) as string) ||
|
|
136
|
+
'#0c0d10'
|
|
137
|
+
const titleFontSize = toNumber(
|
|
138
|
+
getVariableByName('financialConditionCard/title/fontSize', modes),
|
|
139
|
+
16
|
|
140
|
+
)
|
|
141
|
+
const titleFontFamily =
|
|
142
|
+
(getVariableByName(
|
|
143
|
+
'financialConditionCard/title/fontFamily',
|
|
144
|
+
modes
|
|
145
|
+
) as string) || 'JioType Var'
|
|
146
|
+
const titleLineHeight = toNumber(
|
|
147
|
+
getVariableByName('financialConditionCard/title/lineHeight', modes),
|
|
148
|
+
18
|
|
149
|
+
)
|
|
150
|
+
const titleFontWeight = toFontWeight(
|
|
151
|
+
getVariableByName('financialConditionCard/title/fontWeight', modes),
|
|
152
|
+
'700'
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
const bodyColor =
|
|
156
|
+
(getVariableByName('financialConditionCard/body/color', modes) as string) ||
|
|
157
|
+
'#0c0d10'
|
|
158
|
+
const bodyFontSize = toNumber(
|
|
159
|
+
getVariableByName('financialConditionCard/body/fontSize', modes),
|
|
160
|
+
14
|
|
161
|
+
)
|
|
162
|
+
const bodyFontFamily =
|
|
163
|
+
(getVariableByName(
|
|
164
|
+
'financialConditionCard/body/fontFamily',
|
|
165
|
+
modes
|
|
166
|
+
) as string) || 'JioType Var'
|
|
167
|
+
const bodyLineHeight = toNumber(
|
|
168
|
+
getVariableByName('financialConditionCard/body/lineHeight', modes),
|
|
169
|
+
17
|
|
170
|
+
)
|
|
171
|
+
const bodyFontWeight = toFontWeight(
|
|
172
|
+
getVariableByName('financialConditionCard/body/fontWeight', modes),
|
|
173
|
+
'400'
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
const nudgeBodyColor =
|
|
177
|
+
(getVariableByName('nudge/body/color', modes) as string) || '#1a1c1f'
|
|
178
|
+
const nudgeBodyFontSize = toNumber(
|
|
179
|
+
getVariableByName('nudge/body/fontSize', modes),
|
|
180
|
+
12
|
|
181
|
+
)
|
|
182
|
+
const nudgeBodyFontFamily =
|
|
183
|
+
(getVariableByName('nudge/body/fontFamily', modes) as string) || 'JioType Var'
|
|
184
|
+
const nudgeBodyLineHeight = toNumber(
|
|
185
|
+
getVariableByName('nudge/body/lineHeight', modes),
|
|
186
|
+
16
|
|
187
|
+
)
|
|
188
|
+
const nudgeBodyFontWeight = toFontWeight(
|
|
189
|
+
getVariableByName('nudge/body/fontWeight', modes),
|
|
190
|
+
'500'
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
containerStyle: {
|
|
195
|
+
alignItems: 'flex-start',
|
|
196
|
+
backgroundColor: background,
|
|
197
|
+
borderRadius: radius,
|
|
198
|
+
gap,
|
|
199
|
+
justifyContent: 'center',
|
|
200
|
+
overflow: 'hidden',
|
|
201
|
+
paddingHorizontal: paddingH,
|
|
202
|
+
paddingVertical: paddingV,
|
|
203
|
+
},
|
|
204
|
+
headerStyle: {
|
|
205
|
+
alignItems: 'center',
|
|
206
|
+
flexDirection: 'row',
|
|
207
|
+
gap: headerGap,
|
|
208
|
+
width: '100%',
|
|
209
|
+
},
|
|
210
|
+
textContentStyle: {
|
|
211
|
+
alignItems: 'flex-start',
|
|
212
|
+
flex: 1,
|
|
213
|
+
gap: textContentGap,
|
|
214
|
+
minWidth: 1,
|
|
215
|
+
},
|
|
216
|
+
titleStyle: {
|
|
217
|
+
color: titleColor,
|
|
218
|
+
fontFamily: titleFontFamily,
|
|
219
|
+
fontSize: titleFontSize,
|
|
220
|
+
fontWeight: titleFontWeight,
|
|
221
|
+
lineHeight: titleLineHeight,
|
|
222
|
+
},
|
|
223
|
+
bodyStyle: {
|
|
224
|
+
color: bodyColor,
|
|
225
|
+
fontFamily: bodyFontFamily,
|
|
226
|
+
fontSize: bodyFontSize,
|
|
227
|
+
fontWeight: bodyFontWeight,
|
|
228
|
+
lineHeight: bodyLineHeight,
|
|
229
|
+
},
|
|
230
|
+
nudgeBodyStyle: {
|
|
231
|
+
color: nudgeBodyColor,
|
|
232
|
+
fontFamily: nudgeBodyFontFamily,
|
|
233
|
+
fontSize: nudgeBodyFontSize,
|
|
234
|
+
fontWeight: nudgeBodyFontWeight,
|
|
235
|
+
lineHeight: nudgeBodyLineHeight,
|
|
236
|
+
},
|
|
237
|
+
buttonWrapStyle: {
|
|
238
|
+
alignSelf: 'stretch',
|
|
239
|
+
alignItems: 'flex-start',
|
|
240
|
+
},
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function CardFinancialCondition({
|
|
245
|
+
title = 'Protection',
|
|
246
|
+
body = 'Check your coverage and gaps',
|
|
247
|
+
value = 0,
|
|
248
|
+
progressState = 'Inactive',
|
|
249
|
+
valueLabel,
|
|
250
|
+
showNudge = true,
|
|
251
|
+
nudgeBody = 'There may be gaps in your health or life cover\nAdd coverage to stay financially secure',
|
|
252
|
+
showDivider = true,
|
|
253
|
+
showButton = true,
|
|
254
|
+
buttonLabel = 'View Details',
|
|
255
|
+
onPressButton,
|
|
256
|
+
progressSlot,
|
|
257
|
+
nudgeSlot,
|
|
258
|
+
buttonSlot,
|
|
259
|
+
modes: propModes = EMPTY_MODES,
|
|
260
|
+
style,
|
|
261
|
+
headerStyle,
|
|
262
|
+
titleStyle,
|
|
263
|
+
bodyStyle,
|
|
264
|
+
accessibilityLabel,
|
|
265
|
+
...rest
|
|
266
|
+
}: CardFinancialConditionProps) {
|
|
267
|
+
const { modes: globalModes } = useTokens()
|
|
268
|
+
const modes = useMemo(
|
|
269
|
+
() =>
|
|
270
|
+
globalModes === EMPTY_MODES && propModes === EMPTY_MODES
|
|
271
|
+
? EMPTY_MODES
|
|
272
|
+
: { ...globalModes, ...propModes },
|
|
273
|
+
[globalModes, propModes]
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
const tokens = useMemo(
|
|
277
|
+
() => resolveCardFinancialConditionTokens(modes),
|
|
278
|
+
[modes]
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
// Match Figma: the action button is the primary call-to-action and uses the
|
|
282
|
+
// Secondary brand (purple) regardless of the card's overall AppearanceBrand,
|
|
283
|
+
// while Nudge / CircularProgressBar still follow the user-supplied modes.
|
|
284
|
+
const buttonModes = useMemo(
|
|
285
|
+
() => ({ ...modes, AppearanceBrand: 'Secondary' }),
|
|
286
|
+
[modes]
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
const processedProgressSlot = useMemo(() => {
|
|
290
|
+
if (!progressSlot) return null
|
|
291
|
+
const processed = cloneChildrenWithModes(
|
|
292
|
+
React.Children.toArray(progressSlot),
|
|
293
|
+
modes
|
|
294
|
+
)
|
|
295
|
+
return processed.length === 1 ? processed[0] : processed
|
|
296
|
+
}, [progressSlot, modes])
|
|
297
|
+
|
|
298
|
+
const processedNudgeSlot = useMemo(() => {
|
|
299
|
+
if (!nudgeSlot) return null
|
|
300
|
+
const processed = cloneChildrenWithModes(
|
|
301
|
+
React.Children.toArray(nudgeSlot),
|
|
302
|
+
modes
|
|
303
|
+
)
|
|
304
|
+
return processed.length === 1 ? processed[0] : processed
|
|
305
|
+
}, [nudgeSlot, modes])
|
|
306
|
+
|
|
307
|
+
const processedButtonSlot = useMemo(() => {
|
|
308
|
+
if (!buttonSlot) return null
|
|
309
|
+
const processed = cloneChildrenWithModes(
|
|
310
|
+
React.Children.toArray(buttonSlot),
|
|
311
|
+
buttonModes
|
|
312
|
+
)
|
|
313
|
+
return processed.length === 1 ? processed[0] : processed
|
|
314
|
+
}, [buttonSlot, buttonModes])
|
|
315
|
+
|
|
316
|
+
const defaultAccessibilityLabel =
|
|
317
|
+
accessibilityLabel ?? `${title}. ${body}.`
|
|
318
|
+
|
|
319
|
+
return (
|
|
320
|
+
<View
|
|
321
|
+
accessibilityLabel={defaultAccessibilityLabel}
|
|
322
|
+
style={[tokens.containerStyle, style]}
|
|
323
|
+
{...rest}
|
|
324
|
+
>
|
|
325
|
+
<View style={[tokens.headerStyle, headerStyle]}>
|
|
326
|
+
<View style={tokens.textContentStyle}>
|
|
327
|
+
<Text style={[tokens.titleStyle, titleStyle]}>{title}</Text>
|
|
328
|
+
<Text style={[tokens.bodyStyle, bodyStyle]}>{body}</Text>
|
|
329
|
+
</View>
|
|
330
|
+
|
|
331
|
+
{processedProgressSlot || (
|
|
332
|
+
<CircularProgressBar
|
|
333
|
+
state={progressState}
|
|
334
|
+
value={value}
|
|
335
|
+
modes={modes}
|
|
336
|
+
{...(valueLabel ? { valueLabel } : {})}
|
|
337
|
+
/>
|
|
338
|
+
)}
|
|
339
|
+
</View>
|
|
340
|
+
|
|
341
|
+
{showNudge ? (
|
|
342
|
+
processedNudgeSlot || (
|
|
343
|
+
<Nudge type="inline-compact" modes={modes} style={{ width: '100%' }}>
|
|
344
|
+
<Text style={tokens.nudgeBodyStyle}>{nudgeBody}</Text>
|
|
345
|
+
</Nudge>
|
|
346
|
+
)
|
|
347
|
+
) : null}
|
|
348
|
+
|
|
349
|
+
{showDivider ? <Divider modes={modes} /> : null}
|
|
350
|
+
|
|
351
|
+
{showButton ? (
|
|
352
|
+
<View style={tokens.buttonWrapStyle}>
|
|
353
|
+
{processedButtonSlot || (
|
|
354
|
+
<Button
|
|
355
|
+
label={buttonLabel}
|
|
356
|
+
modes={buttonModes}
|
|
357
|
+
{...(onPressButton ? { onPress: onPressButton } : {})}
|
|
358
|
+
/>
|
|
359
|
+
)}
|
|
360
|
+
</View>
|
|
361
|
+
) : null}
|
|
362
|
+
</View>
|
|
363
|
+
)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export default React.memo(CardFinancialCondition)
|
|
@@ -102,6 +102,10 @@ export function Carousel({
|
|
|
102
102
|
const containerPaddingV = parseFloat(
|
|
103
103
|
getVariableByName('carousel/padding/vertical', modes) || '0',
|
|
104
104
|
)
|
|
105
|
+
// Outer container max height per Figma (`carousel/maxHeight`).
|
|
106
|
+
const maxHeight = parseFloat(
|
|
107
|
+
getVariableByName('carousel/maxHeight', modes) || '280',
|
|
108
|
+
)
|
|
105
109
|
// Spacing between the cards row and the pagination dots uses `carousel/gap`.
|
|
106
110
|
const paginationOffset = gap
|
|
107
111
|
|
|
@@ -251,6 +255,7 @@ export function Carousel({
|
|
|
251
255
|
// ---- Render ----
|
|
252
256
|
const outerStyle: ViewStyle = {
|
|
253
257
|
paddingVertical: containerPaddingV,
|
|
258
|
+
maxHeight,
|
|
254
259
|
}
|
|
255
260
|
|
|
256
261
|
const contentContainerStyle: ViewStyle = {
|
|
@@ -364,21 +369,24 @@ export function Pagination({ modes: propModes, style }: PaginationProps) {
|
|
|
364
369
|
const modes = propModes || ctxModes || {}
|
|
365
370
|
|
|
366
371
|
// Token resolution for dots — matches Figma tokens
|
|
367
|
-
// (carousel/pagination/gap, carousel/pagination/indicator/{
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
372
|
+
// (carousel/pagination/gap, carousel/pagination/indicator/{activeColor,inactiveColor,radius,size,activeWidth}).
|
|
373
|
+
const dotSize = parseFloat(
|
|
374
|
+
getVariableByName('carousel/pagination/indicator/size', modes) || '6',
|
|
375
|
+
)
|
|
376
|
+
const dotActiveWidth = parseFloat(
|
|
377
|
+
getVariableByName('carousel/pagination/indicator/activeWidth', modes) || '16',
|
|
378
|
+
)
|
|
371
379
|
const dotGap = parseFloat(
|
|
372
380
|
getVariableByName('carousel/pagination/gap', modes) || '4',
|
|
373
381
|
)
|
|
374
382
|
const dotColor =
|
|
375
383
|
(getVariableByName(
|
|
376
|
-
'carousel/pagination/indicator/
|
|
384
|
+
'carousel/pagination/indicator/inactiveColor',
|
|
377
385
|
modes,
|
|
378
386
|
) as string) || 'rgba(0,0,0,0.3)'
|
|
379
387
|
const dotActiveColor =
|
|
380
388
|
(getVariableByName(
|
|
381
|
-
'carousel/pagination/indicator/
|
|
389
|
+
'carousel/pagination/indicator/activeColor',
|
|
382
390
|
modes,
|
|
383
391
|
) as string) || '#170d0a'
|
|
384
392
|
const dotRadius = parseFloat(
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { StyleSheet, Text, View, type StyleProp, type TextStyle, type ViewStyle } from 'react-native'
|
|
3
|
+
import Svg, { Circle } from 'react-native-svg'
|
|
4
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
5
|
+
import { useTokens } from '../../design-tokens/JFSThemeProvider'
|
|
6
|
+
import { EMPTY_MODES } from '../../utils/react-utils'
|
|
7
|
+
import { IconMinus } from '../../icons/components/IconMinus'
|
|
8
|
+
|
|
9
|
+
type CircularProgressBarBaseProps = Omit<React.ComponentProps<typeof View>, 'children' | 'style'>
|
|
10
|
+
|
|
11
|
+
export type CircularProgressBarState = 'Active' | 'Inactive'
|
|
12
|
+
|
|
13
|
+
export type CircularProgressBarProps = CircularProgressBarBaseProps & {
|
|
14
|
+
/** Current progress value. Clamped between 0 and 100. */
|
|
15
|
+
value?: number
|
|
16
|
+
/** Active shows progress and value; inactive shows the track and minus icon. */
|
|
17
|
+
state?: CircularProgressBarState | boolean
|
|
18
|
+
/** Optional formatted value shown in the active state. */
|
|
19
|
+
valueLabel?: string
|
|
20
|
+
/** Design token modes forwarded to token lookups. */
|
|
21
|
+
modes?: Record<string, any>
|
|
22
|
+
/** Container style override. */
|
|
23
|
+
style?: StyleProp<ViewStyle>
|
|
24
|
+
/** Track stroke style override. */
|
|
25
|
+
trackStyle?: StyleProp<ViewStyle>
|
|
26
|
+
/** Progress stroke style override. */
|
|
27
|
+
progressStyle?: StyleProp<ViewStyle>
|
|
28
|
+
/** Value text style override. */
|
|
29
|
+
valueStyle?: StyleProp<TextStyle>
|
|
30
|
+
/** Accessibility label for the whole progress component. */
|
|
31
|
+
accessibilityLabel?: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const STROKE_WIDTH_RATIO = 8 / 60
|
|
35
|
+
|
|
36
|
+
const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value))
|
|
37
|
+
|
|
38
|
+
const toNumber = (value: unknown, fallback: number) => {
|
|
39
|
+
if (typeof value === 'number') {
|
|
40
|
+
return Number.isFinite(value) ? value : fallback
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (typeof value === 'string') {
|
|
44
|
+
const parsed = Number(value)
|
|
45
|
+
return Number.isFinite(parsed) ? parsed : fallback
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return fallback
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const toFontWeight = (value: unknown, fallback: TextStyle['fontWeight']) => {
|
|
52
|
+
if (typeof value === 'number') {
|
|
53
|
+
return String(value) as TextStyle['fontWeight']
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (typeof value === 'string') {
|
|
57
|
+
return value as TextStyle['fontWeight']
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return fallback
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const getStrokeColor = (style: StyleProp<ViewStyle>, fallback: string) => {
|
|
64
|
+
const flattened = StyleSheet.flatten(style)
|
|
65
|
+
return typeof flattened.backgroundColor === 'string'
|
|
66
|
+
? flattened.backgroundColor
|
|
67
|
+
: fallback
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function CircularProgressBar({
|
|
71
|
+
value = 70,
|
|
72
|
+
state = 'Inactive',
|
|
73
|
+
valueLabel,
|
|
74
|
+
modes: propModes = EMPTY_MODES,
|
|
75
|
+
style,
|
|
76
|
+
trackStyle,
|
|
77
|
+
progressStyle,
|
|
78
|
+
valueStyle,
|
|
79
|
+
accessibilityLabel,
|
|
80
|
+
...rest
|
|
81
|
+
}: CircularProgressBarProps) {
|
|
82
|
+
const { modes: globalModes } = useTokens()
|
|
83
|
+
const modes = { ...globalModes, ...propModes }
|
|
84
|
+
|
|
85
|
+
const isActive = state === true || state === 'Active'
|
|
86
|
+
const normalizedValue = clamp(value, 0, 100)
|
|
87
|
+
const size = toNumber(getVariableByName('circularProgressBar/size', modes), 60)
|
|
88
|
+
const strokeWidth = Math.max(1, size * STROKE_WIDTH_RATIO)
|
|
89
|
+
const radius = Math.max(0, (size - strokeWidth) / 2)
|
|
90
|
+
const center = size / 2
|
|
91
|
+
const circumference = 2 * Math.PI * radius
|
|
92
|
+
|
|
93
|
+
const trackColor = getStrokeColor(
|
|
94
|
+
trackStyle,
|
|
95
|
+
getVariableByName('circularProgressBar/track/color', modes) as string || '#ebebed'
|
|
96
|
+
)
|
|
97
|
+
const progressColor = getStrokeColor(
|
|
98
|
+
progressStyle,
|
|
99
|
+
getVariableByName('circularProgressBar/progress/color', modes) as string || '#25ab21'
|
|
100
|
+
)
|
|
101
|
+
const iconColor = getVariableByName('circularProgressBar/icon/color', modes) as string || '#666666'
|
|
102
|
+
const iconSize = toNumber(getVariableByName('circularProgressBar/icon/size', modes), 24)
|
|
103
|
+
|
|
104
|
+
const foreground = getVariableByName('circularProgressBar/foreground', modes) as string || '#0d0d0f'
|
|
105
|
+
const fontSize = toNumber(getVariableByName('circularProgressBar/fontSize', modes), 18)
|
|
106
|
+
const fontFamily = getVariableByName('circularProgressBar/fontFamily', modes) as string || 'JioType Var'
|
|
107
|
+
const lineHeight = toNumber(getVariableByName('circularProgressBar/lineHeight', modes), 21)
|
|
108
|
+
const fontWeight = toFontWeight(getVariableByName('circularProgressBar/fontWeight', modes), '700')
|
|
109
|
+
|
|
110
|
+
const computedContainerStyle: ViewStyle = {
|
|
111
|
+
alignItems: 'center',
|
|
112
|
+
height: size,
|
|
113
|
+
justifyContent: 'center',
|
|
114
|
+
position: 'relative',
|
|
115
|
+
width: size,
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const computedValueStyle: TextStyle = {
|
|
119
|
+
color: foreground,
|
|
120
|
+
fontFamily,
|
|
121
|
+
fontSize,
|
|
122
|
+
fontWeight,
|
|
123
|
+
lineHeight,
|
|
124
|
+
position: 'absolute',
|
|
125
|
+
textAlign: 'center',
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const iconStyle: ViewStyle = {
|
|
129
|
+
left: (size - iconSize) / 2,
|
|
130
|
+
position: 'absolute',
|
|
131
|
+
top: (size - iconSize) / 2,
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const displayValue = valueLabel ?? String(Math.round(normalizedValue))
|
|
135
|
+
const defaultAccessibilityLabel =
|
|
136
|
+
accessibilityLabel ?? (isActive ? `${displayValue} out of 100` : 'Inactive progress')
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<View
|
|
140
|
+
accessibilityRole="progressbar"
|
|
141
|
+
accessibilityLabel={defaultAccessibilityLabel}
|
|
142
|
+
accessibilityValue={{ min: 0, max: 100, now: normalizedValue }}
|
|
143
|
+
style={[computedContainerStyle, style]}
|
|
144
|
+
{...rest}
|
|
145
|
+
>
|
|
146
|
+
<Svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
|
|
147
|
+
<Circle
|
|
148
|
+
cx={center}
|
|
149
|
+
cy={center}
|
|
150
|
+
r={radius}
|
|
151
|
+
stroke={trackColor}
|
|
152
|
+
strokeWidth={strokeWidth}
|
|
153
|
+
fill="none"
|
|
154
|
+
/>
|
|
155
|
+
{isActive ? (
|
|
156
|
+
<Circle
|
|
157
|
+
cx={center}
|
|
158
|
+
cy={center}
|
|
159
|
+
r={radius}
|
|
160
|
+
stroke={progressColor}
|
|
161
|
+
strokeWidth={strokeWidth}
|
|
162
|
+
strokeLinecap="round"
|
|
163
|
+
fill="none"
|
|
164
|
+
strokeDasharray={`${circumference} ${circumference}`}
|
|
165
|
+
strokeDashoffset={circumference * (1 - normalizedValue / 100)}
|
|
166
|
+
rotation="-90"
|
|
167
|
+
originX={center}
|
|
168
|
+
originY={center}
|
|
169
|
+
/>
|
|
170
|
+
) : null}
|
|
171
|
+
</Svg>
|
|
172
|
+
|
|
173
|
+
{isActive ? (
|
|
174
|
+
<Text style={[computedValueStyle, valueStyle]}>
|
|
175
|
+
{displayValue}
|
|
176
|
+
</Text>
|
|
177
|
+
) : (
|
|
178
|
+
<IconMinus
|
|
179
|
+
width={iconSize}
|
|
180
|
+
height={iconSize}
|
|
181
|
+
fill={iconColor}
|
|
182
|
+
color={iconColor}
|
|
183
|
+
style={iconStyle}
|
|
184
|
+
/>
|
|
185
|
+
)}
|
|
186
|
+
</View>
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export default CircularProgressBar
|