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.
- package/CHANGELOG.md +28 -0
- package/lib/commonjs/components/AccordionCheckbox/AccordionCheckbox.js +239 -0
- package/lib/commonjs/components/AccountCard/AccountCard.js +247 -0
- package/lib/commonjs/components/AppBar/AppBar.js +17 -11
- package/lib/commonjs/components/BrandChip/BrandChip.js +149 -0
- package/lib/commonjs/components/CardBankAccount/CardBankAccount.js +229 -0
- package/lib/commonjs/components/CardInsight/CardInsight.js +166 -0
- package/lib/commonjs/components/CheckboxGroup/CheckboxGroup.js +67 -0
- package/lib/commonjs/components/CheckboxItem/CheckboxItem.js +140 -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/Dropdown/Dropdown.js +214 -0
- package/lib/commonjs/components/DropdownInput/DropdownInput.js +542 -0
- package/lib/commonjs/components/FormField/FormField.js +328 -178
- package/lib/commonjs/components/LinearMeter/LinearMeter.js +9 -28
- package/lib/commonjs/components/LinearProgress/LinearProgress.js +68 -0
- package/lib/commonjs/components/LottieIntroBlock/LottieIntroBlock.js +150 -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/PageHero/PageHero.js +153 -0
- package/lib/commonjs/components/PoweredByLabel/PoweredByLabel.js +135 -0
- package/lib/commonjs/components/PoweredByLabel/finvu.png +0 -0
- package/lib/commonjs/components/ProductOverview/ProductOverview.js +147 -0
- 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/Text/Text.js +9 -2
- package/lib/commonjs/components/Tooltip/Tooltip.js +34 -27
- package/lib/commonjs/components/index.js +231 -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/AccountCard/AccountCard.js +241 -0
- package/lib/module/components/AppBar/AppBar.js +17 -11
- package/lib/module/components/BrandChip/BrandChip.js +143 -0
- package/lib/module/components/CardBankAccount/CardBankAccount.js +223 -0
- package/lib/module/components/CardInsight/CardInsight.js +161 -0
- package/lib/module/components/CheckboxGroup/CheckboxGroup.js +62 -0
- package/lib/module/components/CheckboxItem/CheckboxItem.js +134 -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/Dropdown/Dropdown.js +206 -0
- package/lib/module/components/DropdownInput/DropdownInput.js +536 -0
- package/lib/module/components/FormField/FormField.js +330 -180
- package/lib/module/components/LinearMeter/LinearMeter.js +9 -28
- package/lib/module/components/LinearProgress/LinearProgress.js +63 -0
- package/lib/module/components/LottieIntroBlock/LottieIntroBlock.js +144 -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/PageHero/PageHero.js +147 -0
- package/lib/module/components/PoweredByLabel/PoweredByLabel.js +130 -0
- package/lib/module/components/PoweredByLabel/finvu.png +0 -0
- package/lib/module/components/ProductOverview/ProductOverview.js +142 -0
- 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/Text/Text.js +9 -2
- package/lib/module/components/Tooltip/Tooltip.js +34 -27
- package/lib/module/components/index.js +28 -2
- 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/AccountCard/AccountCard.d.ts +81 -0
- package/lib/typescript/src/components/BrandChip/BrandChip.d.ts +43 -0
- package/lib/typescript/src/components/CardBankAccount/CardBankAccount.d.ts +86 -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 +72 -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/Dropdown/Dropdown.d.ts +62 -0
- package/lib/typescript/src/components/DropdownInput/DropdownInput.d.ts +107 -0
- package/lib/typescript/src/components/FormField/FormField.d.ts +76 -19
- package/lib/typescript/src/components/LinearProgress/LinearProgress.d.ts +17 -0
- package/lib/typescript/src/components/LottieIntroBlock/LottieIntroBlock.d.ts +58 -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/PageHero/PageHero.d.ts +53 -0
- package/lib/typescript/src/components/PoweredByLabel/PoweredByLabel.d.ts +70 -0
- package/lib/typescript/src/components/ProductOverview/ProductOverview.d.ts +39 -0
- 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/Text/Text.d.ts +12 -2
- package/lib/typescript/src/components/Tooltip/Tooltip.d.ts +13 -2
- package/lib/typescript/src/components/index.d.ts +29 -3
- 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 -3
- package/src/components/AccordionCheckbox/AccordionCheckbox.tsx +323 -0
- package/src/components/AccountCard/AccountCard.tsx +376 -0
- package/src/components/AppBar/AppBar.tsx +25 -14
- package/src/components/BrandChip/BrandChip.tsx +235 -0
- package/src/components/CardBankAccount/CardBankAccount.tsx +321 -0
- package/src/components/CardInsight/CardInsight.tsx +239 -0
- package/src/components/CheckboxGroup/CheckboxGroup.tsx +86 -0
- package/src/components/CheckboxItem/CheckboxItem.tsx +209 -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/Dropdown/Dropdown.tsx +331 -0
- package/src/components/DropdownInput/DropdownInput.tsx +819 -0
- package/src/components/FormField/FormField.tsx +542 -215
- package/src/components/LinearMeter/LinearMeter.tsx +9 -39
- package/src/components/LinearProgress/LinearProgress.tsx +92 -0
- package/src/components/LottieIntroBlock/LottieIntroBlock.tsx +202 -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/PageHero/PageHero.tsx +200 -0
- package/src/components/PoweredByLabel/PoweredByLabel.tsx +221 -0
- package/src/components/PoweredByLabel/finvu.png +0 -0
- package/src/components/ProductOverview/ProductOverview.tsx +236 -0
- 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/Text/Text.tsx +24 -3
- package/src/components/Tooltip/Tooltip.tsx +50 -25
- package/src/components/index.ts +47 -3
- 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
|
@@ -89,12 +89,13 @@ export default function AppBar({
|
|
|
89
89
|
const containerStyle: ViewStyle = {
|
|
90
90
|
flexDirection: 'row',
|
|
91
91
|
alignItems: 'center',
|
|
92
|
-
justifyContent:
|
|
92
|
+
// No `justifyContent` here: with the inline middle slot using `flex: 1`
|
|
93
|
+
// the three sections lay out naturally (leading | middle | actions).
|
|
94
|
+
// When middleSlot is absent we fall back to `space-between` at the wrapper
|
|
95
|
+
// level so leading & actions still anchor to the edges.
|
|
93
96
|
paddingHorizontal: paddingHorizontal ?? 16,
|
|
94
97
|
paddingVertical: paddingVertical ?? (isMain ? 16 : 10),
|
|
95
98
|
backgroundColor: backgroundColor ?? '#FFFFFF',
|
|
96
|
-
// We can set minHeight if we want to enforce consistency, but padding should dictate it mostly.
|
|
97
|
-
// Figma shows specific heights implicitly via padding + content.
|
|
98
99
|
// MainPage: h=68 (16 top/bot padding? 36 height content?)
|
|
99
100
|
// SubPage: h=52
|
|
100
101
|
}
|
|
@@ -159,9 +160,18 @@ export default function AppBar({
|
|
|
159
160
|
? <View style={actionsStyle}>{cloneChildrenWithModes(React.Children.toArray(actionsSlot), modes)}</View>
|
|
160
161
|
: null
|
|
161
162
|
|
|
163
|
+
// When there is no middleSlot we want leading & actions pinned to the
|
|
164
|
+
// outer edges, so we apply `space-between` at the wrapper. With a middle
|
|
165
|
+
// slot present, the middle (flex: 1) absorbs the remaining space, so
|
|
166
|
+
// `space-between` is a no-op.
|
|
167
|
+
const wrapperStyle: ViewStyle = {
|
|
168
|
+
...containerStyle,
|
|
169
|
+
justifyContent: processedMiddle ? 'flex-start' : 'space-between',
|
|
170
|
+
}
|
|
171
|
+
|
|
162
172
|
return (
|
|
163
173
|
<View
|
|
164
|
-
style={[
|
|
174
|
+
style={[wrapperStyle, style]}
|
|
165
175
|
accessibilityRole="header"
|
|
166
176
|
accessibilityLabel={undefined}
|
|
167
177
|
{...(accessibilityHint ? { accessibilityHint } : {})}
|
|
@@ -172,21 +182,22 @@ export default function AppBar({
|
|
|
172
182
|
{processedLeading}
|
|
173
183
|
</View>
|
|
174
184
|
|
|
175
|
-
{/*
|
|
176
|
-
|
|
177
|
-
|
|
185
|
+
{/*
|
|
186
|
+
* Middle Section — rendered as an in-flow flex item (`flex: 1`) so it
|
|
187
|
+
* occupies the space between leading and actions but never overflows
|
|
188
|
+
* past them. This fixes wide children (e.g. <LinearProgress /> with
|
|
189
|
+
* width: '100%') stretching edge-to-edge under the leading/actions.
|
|
190
|
+
* `minWidth: 0` is required so the flex item can shrink below its
|
|
191
|
+
* content's intrinsic width on platforms that respect it (web).
|
|
192
|
+
*/}
|
|
178
193
|
{processedMiddle && (
|
|
179
194
|
<View
|
|
180
195
|
style={{
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
right: 0,
|
|
184
|
-
top: 0,
|
|
185
|
-
bottom: 0,
|
|
196
|
+
flex: 1,
|
|
197
|
+
minWidth: 0,
|
|
186
198
|
alignItems: 'center',
|
|
187
199
|
justifyContent: 'center',
|
|
188
|
-
|
|
189
|
-
// Usually middle title shouldn't block actions. `pointerEvents="box-none"` is safer.
|
|
200
|
+
paddingHorizontal: 8,
|
|
190
201
|
}}
|
|
191
202
|
pointerEvents="box-none"
|
|
192
203
|
>
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import React, { useMemo } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Pressable,
|
|
4
|
+
Text,
|
|
5
|
+
View,
|
|
6
|
+
type ImageSourcePropType,
|
|
7
|
+
type StyleProp,
|
|
8
|
+
type TextStyle,
|
|
9
|
+
type ViewStyle,
|
|
10
|
+
} from 'react-native'
|
|
11
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
12
|
+
import { useTokens } from '../../design-tokens/JFSThemeProvider'
|
|
13
|
+
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
|
|
14
|
+
import Avatar from '../Avatar/Avatar'
|
|
15
|
+
|
|
16
|
+
export type BrandChipProps = {
|
|
17
|
+
/** Visible label (e.g. `"Axis Bank • 0245"`). */
|
|
18
|
+
label?: string
|
|
19
|
+
/**
|
|
20
|
+
* Image shown in the avatar. Forwarded to the underlying `Avatar`'s
|
|
21
|
+
* `imageSource` prop. Pass a `require()`-ed asset, an `{ uri }` object
|
|
22
|
+
* or a remote URL string. Ignored when `avatarSlot` is provided.
|
|
23
|
+
*/
|
|
24
|
+
imageSource?: ImageSourcePropType | string
|
|
25
|
+
/**
|
|
26
|
+
* Slot replacing the default `Avatar`. Receives `modes` recursively, so
|
|
27
|
+
* inner `Avatar` / `IconCapsule` instances inherit theming.
|
|
28
|
+
*/
|
|
29
|
+
avatarSlot?: React.ReactNode
|
|
30
|
+
/** Optional press handler — when provided, the chip becomes a `Pressable`. */
|
|
31
|
+
onPress?: () => void
|
|
32
|
+
/** Design token modes for theming (e.g. `{ 'Color Mode': 'Light' }`). */
|
|
33
|
+
modes?: Record<string, any>
|
|
34
|
+
/** Container style override. */
|
|
35
|
+
style?: StyleProp<ViewStyle>
|
|
36
|
+
/** Label style override. */
|
|
37
|
+
labelStyle?: StyleProp<TextStyle>
|
|
38
|
+
/** Accessibility label. Defaults to `label`. */
|
|
39
|
+
accessibilityLabel?: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface BrandChipTokens {
|
|
43
|
+
containerStyle: ViewStyle
|
|
44
|
+
labelStyle: TextStyle
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const toNumber = (value: unknown, fallback: number): number => {
|
|
48
|
+
if (typeof value === 'number') {
|
|
49
|
+
return Number.isFinite(value) ? value : fallback
|
|
50
|
+
}
|
|
51
|
+
if (typeof value === 'string') {
|
|
52
|
+
const parsed = Number(value)
|
|
53
|
+
return Number.isFinite(parsed) ? parsed : fallback
|
|
54
|
+
}
|
|
55
|
+
return fallback
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const toFontWeight = (
|
|
59
|
+
value: unknown,
|
|
60
|
+
fallback: TextStyle['fontWeight']
|
|
61
|
+
): TextStyle['fontWeight'] => {
|
|
62
|
+
if (typeof value === 'number') return String(value) as TextStyle['fontWeight']
|
|
63
|
+
if (typeof value === 'string') return value as TextStyle['fontWeight']
|
|
64
|
+
return fallback
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function resolveBrandChipTokens(modes: Record<string, any>): BrandChipTokens {
|
|
68
|
+
const background =
|
|
69
|
+
(getVariableByName('brandChip/background', modes) as string | null) ??
|
|
70
|
+
'#ffffff'
|
|
71
|
+
const foreground =
|
|
72
|
+
(getVariableByName('brandChip/foreground', modes) as string | null) ??
|
|
73
|
+
'#0d0d0f'
|
|
74
|
+
const borderColor =
|
|
75
|
+
(getVariableByName('brandChip/border/color', modes) as string | null) ??
|
|
76
|
+
'#e0e0e3'
|
|
77
|
+
const borderSize = toNumber(
|
|
78
|
+
getVariableByName('brandChip/border/size', modes),
|
|
79
|
+
1
|
|
80
|
+
)
|
|
81
|
+
const gap = toNumber(getVariableByName('brandChip/gap', modes), 8)
|
|
82
|
+
const paddingLeft = toNumber(
|
|
83
|
+
getVariableByName('brandChip/padding/left', modes),
|
|
84
|
+
8
|
|
85
|
+
)
|
|
86
|
+
const paddingRight = toNumber(
|
|
87
|
+
getVariableByName('brandChip/padding/right', modes),
|
|
88
|
+
12
|
|
89
|
+
)
|
|
90
|
+
const paddingVertical = toNumber(
|
|
91
|
+
getVariableByName('brandChip/padding/vertical', modes),
|
|
92
|
+
5
|
|
93
|
+
)
|
|
94
|
+
// The Figma token uses 25000 as a sentinel for "always pill-shaped".
|
|
95
|
+
// Cap it to a value React Native renders predictably.
|
|
96
|
+
const radiusRaw = toNumber(getVariableByName('brandChip/radius', modes), 9999)
|
|
97
|
+
const radius = radiusRaw >= 9999 ? 9999 : radiusRaw
|
|
98
|
+
|
|
99
|
+
const fontFamily =
|
|
100
|
+
(getVariableByName('brandChip/label/fontFamily', modes) as string | null) ??
|
|
101
|
+
'JioType Var'
|
|
102
|
+
const fontSize = toNumber(
|
|
103
|
+
getVariableByName('brandChip/label/fontSize', modes),
|
|
104
|
+
16
|
|
105
|
+
)
|
|
106
|
+
const lineHeight = toNumber(
|
|
107
|
+
getVariableByName('brandChip/label/lineHeight', modes),
|
|
108
|
+
21
|
|
109
|
+
)
|
|
110
|
+
const fontWeight = toFontWeight(
|
|
111
|
+
getVariableByName('brandChip/label/fontWeight', modes),
|
|
112
|
+
'500'
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
containerStyle: {
|
|
117
|
+
flexDirection: 'row',
|
|
118
|
+
alignItems: 'center',
|
|
119
|
+
alignSelf: 'flex-start',
|
|
120
|
+
backgroundColor: background,
|
|
121
|
+
borderColor,
|
|
122
|
+
borderWidth: borderSize,
|
|
123
|
+
borderStyle: 'solid',
|
|
124
|
+
borderRadius: radius,
|
|
125
|
+
paddingLeft,
|
|
126
|
+
paddingRight,
|
|
127
|
+
paddingTop: paddingVertical,
|
|
128
|
+
paddingBottom: paddingVertical,
|
|
129
|
+
gap,
|
|
130
|
+
},
|
|
131
|
+
labelStyle: {
|
|
132
|
+
color: foreground,
|
|
133
|
+
fontFamily,
|
|
134
|
+
fontSize,
|
|
135
|
+
fontWeight,
|
|
136
|
+
lineHeight,
|
|
137
|
+
},
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// The Figma reference renders the avatar at 29px which is the `S` size in
|
|
142
|
+
// the Avatar Size collection. Callers can override via `modes`.
|
|
143
|
+
const DEFAULT_AVATAR_SIZE_MODE = 'S'
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* `BrandChip` renders a compact pill that pairs a small institution avatar
|
|
147
|
+
* with a short identifier label (e.g. `"Axis Bank • 0245"`). It's typically
|
|
148
|
+
* used to surface the currently-selected linked account in headers, sticky
|
|
149
|
+
* bars, or selectors.
|
|
150
|
+
*
|
|
151
|
+
* All visual values resolve through the `brandChip/*` design tokens with
|
|
152
|
+
* sensible Figma defaults so the chip renders correctly out of the box.
|
|
153
|
+
*
|
|
154
|
+
* @component
|
|
155
|
+
* @param {BrandChipProps} props
|
|
156
|
+
*/
|
|
157
|
+
function BrandChip({
|
|
158
|
+
label = 'Axis Bank • 0245',
|
|
159
|
+
imageSource,
|
|
160
|
+
avatarSlot,
|
|
161
|
+
onPress,
|
|
162
|
+
modes: propModes = EMPTY_MODES,
|
|
163
|
+
style,
|
|
164
|
+
labelStyle,
|
|
165
|
+
accessibilityLabel,
|
|
166
|
+
}: BrandChipProps) {
|
|
167
|
+
const { modes: globalModes } = useTokens()
|
|
168
|
+
const modes = useMemo(
|
|
169
|
+
() =>
|
|
170
|
+
globalModes === EMPTY_MODES && propModes === EMPTY_MODES
|
|
171
|
+
? EMPTY_MODES
|
|
172
|
+
: { ...globalModes, ...propModes },
|
|
173
|
+
[globalModes, propModes]
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
const avatarModes = useMemo(
|
|
177
|
+
() => ({ 'Avatar Size': DEFAULT_AVATAR_SIZE_MODE, ...modes }),
|
|
178
|
+
[modes]
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
const tokens = useMemo(() => resolveBrandChipTokens(modes), [modes])
|
|
182
|
+
|
|
183
|
+
const processedAvatarSlot = useMemo(() => {
|
|
184
|
+
if (!avatarSlot) return null
|
|
185
|
+
const processed = cloneChildrenWithModes(avatarSlot, avatarModes)
|
|
186
|
+
return processed.length === 1 ? processed[0] : processed
|
|
187
|
+
}, [avatarSlot, avatarModes])
|
|
188
|
+
|
|
189
|
+
const avatarNode =
|
|
190
|
+
processedAvatarSlot ??
|
|
191
|
+
(imageSource !== undefined ? (
|
|
192
|
+
<Avatar style="Image" imageSource={imageSource} modes={avatarModes} />
|
|
193
|
+
) : (
|
|
194
|
+
<Avatar style="Image" modes={avatarModes} />
|
|
195
|
+
))
|
|
196
|
+
|
|
197
|
+
const a11yLabel = accessibilityLabel ?? label
|
|
198
|
+
|
|
199
|
+
const content = (
|
|
200
|
+
<>
|
|
201
|
+
{avatarNode}
|
|
202
|
+
<Text
|
|
203
|
+
style={[tokens.labelStyle, labelStyle]}
|
|
204
|
+
numberOfLines={1}
|
|
205
|
+
ellipsizeMode="tail"
|
|
206
|
+
>
|
|
207
|
+
{label}
|
|
208
|
+
</Text>
|
|
209
|
+
</>
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
if (onPress) {
|
|
213
|
+
return (
|
|
214
|
+
<Pressable
|
|
215
|
+
accessibilityRole="button"
|
|
216
|
+
accessibilityLabel={a11yLabel}
|
|
217
|
+
onPress={onPress}
|
|
218
|
+
style={[tokens.containerStyle, style]}
|
|
219
|
+
>
|
|
220
|
+
{content}
|
|
221
|
+
</Pressable>
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return (
|
|
226
|
+
<View
|
|
227
|
+
accessibilityLabel={a11yLabel}
|
|
228
|
+
style={[tokens.containerStyle, style]}
|
|
229
|
+
>
|
|
230
|
+
{content}
|
|
231
|
+
</View>
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export default React.memo(BrandChip)
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import React, { useMemo } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Text,
|
|
5
|
+
type StyleProp,
|
|
6
|
+
type ViewStyle,
|
|
7
|
+
type TextStyle,
|
|
8
|
+
} from 'react-native'
|
|
9
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
10
|
+
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
|
|
11
|
+
import Badge from '../Badge/Badge'
|
|
12
|
+
import Button from '../Button/Button'
|
|
13
|
+
import InstitutionBadge from '../InstitutionBadge/InstitutionBadge'
|
|
14
|
+
import type { UnifiedSource } from '../../utils/MediaSource'
|
|
15
|
+
|
|
16
|
+
export type CardBankAccountItem = {
|
|
17
|
+
/** Left-aligned label text (e.g. "Account type"). */
|
|
18
|
+
label: string
|
|
19
|
+
/**
|
|
20
|
+
* Right-aligned value. Strings render through the `text/*` design tokens.
|
|
21
|
+
* Pass any `ReactNode` to fully customise the value (e.g. obfuscated
|
|
22
|
+
* account numbers, masked dots, icons).
|
|
23
|
+
*/
|
|
24
|
+
value: React.ReactNode
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type CardBankAccountProps = {
|
|
28
|
+
/**
|
|
29
|
+
* Institution name shown in the header next to the avatar. Pass an empty
|
|
30
|
+
* string and `headerSlot` to fully replace the default header.
|
|
31
|
+
*/
|
|
32
|
+
institutionName?: string
|
|
33
|
+
/**
|
|
34
|
+
* Avatar source for the institution. Forwarded to the underlying
|
|
35
|
+
* `InstitutionBadge`. Accepts any {@link UnifiedSource}.
|
|
36
|
+
*/
|
|
37
|
+
institutionAvatar?: UnifiedSource
|
|
38
|
+
/**
|
|
39
|
+
* Header badge. Pass a string to render the default `Badge` with that
|
|
40
|
+
* label, or a custom `ReactNode` for full control. Pass `false`/`null`
|
|
41
|
+
* to hide the badge entirely.
|
|
42
|
+
*/
|
|
43
|
+
badge?: React.ReactNode | string | false | null
|
|
44
|
+
/**
|
|
45
|
+
* Override the entire header. When provided, replaces the default
|
|
46
|
+
* `InstitutionBadge` + `Badge` row.
|
|
47
|
+
*/
|
|
48
|
+
headerSlot?: React.ReactNode
|
|
49
|
+
/**
|
|
50
|
+
* Data rows shown in the middle. Each entry renders a left-aligned label
|
|
51
|
+
* and a right-aligned value. Defaults to three placeholder rows that
|
|
52
|
+
* match the Figma reference.
|
|
53
|
+
*/
|
|
54
|
+
items?: CardBankAccountItem[]
|
|
55
|
+
/**
|
|
56
|
+
* Custom content slot rendered between the header and the footer.
|
|
57
|
+
* Overrides `items` when provided.
|
|
58
|
+
*/
|
|
59
|
+
children?: React.ReactNode
|
|
60
|
+
/** Footer button label. Ignored when `footer` is provided. */
|
|
61
|
+
buttonLabel?: string
|
|
62
|
+
/** Footer button press handler. Ignored when `footer` is provided. */
|
|
63
|
+
onButtonPress?: () => void
|
|
64
|
+
/**
|
|
65
|
+
* Override the footer. Pass a `ReactNode` for full control or
|
|
66
|
+
* `false`/`null` to hide it entirely.
|
|
67
|
+
*/
|
|
68
|
+
footer?: React.ReactNode | false | null
|
|
69
|
+
/**
|
|
70
|
+
* Design token modes for theming (e.g. `{ 'Color Mode': 'Light' }`).
|
|
71
|
+
*
|
|
72
|
+
* Defaults to `{ 'Button / Size': 'S', AppearanceBrand: 'Secondary',
|
|
73
|
+
* Emphasis: 'Medium' }` so the footer button matches the Figma reference
|
|
74
|
+
* out of the box. Caller-supplied modes are merged on top and can
|
|
75
|
+
* override any of the default keys.
|
|
76
|
+
*/
|
|
77
|
+
modes?: Record<string, any>
|
|
78
|
+
/** Container style override. */
|
|
79
|
+
style?: StyleProp<ViewStyle>
|
|
80
|
+
/** Accessibility label for the card region. */
|
|
81
|
+
accessibilityLabel?: string
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const DEFAULT_ITEMS: CardBankAccountItem[] = [
|
|
85
|
+
{ label: 'Account type', value: 'Korem ipsum' },
|
|
86
|
+
{ label: 'Account number', value: 'Korem ipsum' },
|
|
87
|
+
{ label: 'Last updated', value: 'Korem ipsum' },
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
// Component-level defaults that match the Figma reference. Caller-provided
|
|
91
|
+
// `modes` are merged on top so every key here can be overridden per-instance.
|
|
92
|
+
const DEFAULT_MODES: Readonly<Record<string, any>> = Object.freeze({
|
|
93
|
+
'Button / Size': 'S',
|
|
94
|
+
AppearanceBrand: 'Secondary',
|
|
95
|
+
Emphasis: 'Medium',
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
const toNumber = (value: unknown, fallback: number): number => {
|
|
99
|
+
if (typeof value === 'number') return Number.isFinite(value) ? value : fallback
|
|
100
|
+
if (typeof value === 'string') {
|
|
101
|
+
const parsed = Number(value)
|
|
102
|
+
return Number.isFinite(parsed) ? parsed : fallback
|
|
103
|
+
}
|
|
104
|
+
return fallback
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const toFontWeight = (value: unknown, fallback: TextStyle['fontWeight']): TextStyle['fontWeight'] => {
|
|
108
|
+
if (typeof value === 'number') return String(value) as TextStyle['fontWeight']
|
|
109
|
+
if (typeof value === 'string') return value as TextStyle['fontWeight']
|
|
110
|
+
return fallback
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* `CardBankAccount` renders a bank account summary card composed of:
|
|
115
|
+
*
|
|
116
|
+
* 1. A header with an `InstitutionBadge` (avatar + bank name) and an
|
|
117
|
+
* optional trailing `Badge`.
|
|
118
|
+
* 2. A configurable list of label/value rows describing the account.
|
|
119
|
+
* 3. A footer `Button` for the primary action.
|
|
120
|
+
*
|
|
121
|
+
* All values resolve through the `bankAccountCard/*` design tokens with
|
|
122
|
+
* sensible Figma defaults so the card renders correctly out of the box.
|
|
123
|
+
*
|
|
124
|
+
* @component
|
|
125
|
+
* @param {CardBankAccountProps} props
|
|
126
|
+
*/
|
|
127
|
+
function CardBankAccount({
|
|
128
|
+
institutionName = 'State Bank of India',
|
|
129
|
+
institutionAvatar,
|
|
130
|
+
badge,
|
|
131
|
+
headerSlot,
|
|
132
|
+
items = DEFAULT_ITEMS,
|
|
133
|
+
children,
|
|
134
|
+
buttonLabel = 'Button',
|
|
135
|
+
onButtonPress,
|
|
136
|
+
footer,
|
|
137
|
+
modes: propModes = EMPTY_MODES,
|
|
138
|
+
style,
|
|
139
|
+
accessibilityLabel,
|
|
140
|
+
}: CardBankAccountProps) {
|
|
141
|
+
// Merge caller modes on top of `DEFAULT_MODES` so every default key
|
|
142
|
+
// (e.g. `Button / Size`, `AppearanceBrand`, `Emphasis`) can be overridden
|
|
143
|
+
// per-instance while still applying out of the box.
|
|
144
|
+
const modes = useMemo(
|
|
145
|
+
() =>
|
|
146
|
+
propModes === EMPTY_MODES
|
|
147
|
+
? DEFAULT_MODES
|
|
148
|
+
: { ...DEFAULT_MODES, ...propModes },
|
|
149
|
+
[propModes],
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
const background =
|
|
153
|
+
(getVariableByName('bankAccountCard/background', modes) as string | null) ?? '#ffffff'
|
|
154
|
+
const radius = toNumber(getVariableByName('bankAccountCard/radius', modes), 16)
|
|
155
|
+
const paddingHorizontal = toNumber(
|
|
156
|
+
getVariableByName('bankAccountCard/padding/horizontal', modes),
|
|
157
|
+
12
|
|
158
|
+
)
|
|
159
|
+
const paddingVertical = toNumber(
|
|
160
|
+
getVariableByName('bankAccountCard/padding/vertical', modes),
|
|
161
|
+
16
|
|
162
|
+
)
|
|
163
|
+
const gap = toNumber(getVariableByName('bankAccountCard/gap', modes), 16)
|
|
164
|
+
const headerGap = toNumber(getVariableByName('bankAccountCard/header/gap', modes), 8)
|
|
165
|
+
|
|
166
|
+
const listGroupGap = toNumber(getVariableByName('listGroup/gap', modes), 12)
|
|
167
|
+
const rowGap = toNumber(getVariableByName('listItem/gap', modes), 8)
|
|
168
|
+
|
|
169
|
+
const titleColor =
|
|
170
|
+
(getVariableByName('listItem/title/color', modes) as string | null) ?? '#0f0d0a'
|
|
171
|
+
const titleFontFamily =
|
|
172
|
+
(getVariableByName('listItem/title/fontFamily', modes) as string | null) ?? 'JioType Var'
|
|
173
|
+
const titleFontSize = toNumber(getVariableByName('listItem/title/fontSize', modes), 14)
|
|
174
|
+
const titleLineHeight = toNumber(getVariableByName('listItem/title/lineHeight', modes), 16)
|
|
175
|
+
const titleFontWeight = toFontWeight(
|
|
176
|
+
getVariableByName('listItem/title/fontWeight', modes),
|
|
177
|
+
'700'
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
const valueColor =
|
|
181
|
+
(getVariableByName('text/foreground', modes) as string | null) ?? '#000000'
|
|
182
|
+
const valueFontFamily =
|
|
183
|
+
(getVariableByName('text/fontFamily', modes) as string | null) ?? 'JioType Var'
|
|
184
|
+
const valueFontSize = toNumber(getVariableByName('text/fontSize', modes), 14)
|
|
185
|
+
const valueLineHeight = toNumber(getVariableByName('text/lineHeight', modes), 20)
|
|
186
|
+
const valueLetterSpacing = toNumber(getVariableByName('text/letterSpacing', modes), -0.5)
|
|
187
|
+
const valueFontWeight = toFontWeight(getVariableByName('text/fontWeight', modes), '500')
|
|
188
|
+
|
|
189
|
+
const labelStyle: TextStyle = {
|
|
190
|
+
color: titleColor,
|
|
191
|
+
fontFamily: titleFontFamily,
|
|
192
|
+
fontSize: titleFontSize,
|
|
193
|
+
lineHeight: titleLineHeight,
|
|
194
|
+
fontWeight: titleFontWeight,
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const valueTextStyle: TextStyle = {
|
|
198
|
+
color: valueColor,
|
|
199
|
+
fontFamily: valueFontFamily,
|
|
200
|
+
fontSize: valueFontSize,
|
|
201
|
+
lineHeight: valueLineHeight,
|
|
202
|
+
letterSpacing: valueLetterSpacing,
|
|
203
|
+
fontWeight: valueFontWeight,
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const renderBadge = (): React.ReactNode => {
|
|
207
|
+
if (badge === false || badge === null || badge === undefined) return null
|
|
208
|
+
if (typeof badge === 'string') {
|
|
209
|
+
return <Badge label={badge} modes={modes} />
|
|
210
|
+
}
|
|
211
|
+
const processed = cloneChildrenWithModes(badge, modes)
|
|
212
|
+
return processed.length === 1 ? processed[0] : processed
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const renderHeader = (): React.ReactNode => {
|
|
216
|
+
if (headerSlot !== undefined) {
|
|
217
|
+
const processed = cloneChildrenWithModes(headerSlot, modes)
|
|
218
|
+
return processed.length === 1 ? processed[0] : processed
|
|
219
|
+
}
|
|
220
|
+
return (
|
|
221
|
+
<View
|
|
222
|
+
style={{
|
|
223
|
+
flexDirection: 'row',
|
|
224
|
+
alignItems: 'center',
|
|
225
|
+
gap: headerGap,
|
|
226
|
+
width: '100%',
|
|
227
|
+
}}
|
|
228
|
+
>
|
|
229
|
+
<View style={{ flex: 1, minWidth: 0 }}>
|
|
230
|
+
<InstitutionBadge
|
|
231
|
+
label={institutionName}
|
|
232
|
+
{...(institutionAvatar !== undefined ? { source: institutionAvatar } : {})}
|
|
233
|
+
modes={modes}
|
|
234
|
+
/>
|
|
235
|
+
</View>
|
|
236
|
+
{renderBadge()}
|
|
237
|
+
</View>
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const renderItems = (): React.ReactNode => {
|
|
242
|
+
if (children !== undefined && children !== null) {
|
|
243
|
+
const processed = cloneChildrenWithModes(children, modes)
|
|
244
|
+
if (processed.length === 0) return null
|
|
245
|
+
return (
|
|
246
|
+
<View style={{ width: '100%', gap: listGroupGap }}>
|
|
247
|
+
{processed.length === 1 ? processed[0] : processed}
|
|
248
|
+
</View>
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (!items || items.length === 0) return null
|
|
253
|
+
|
|
254
|
+
return (
|
|
255
|
+
<View style={{ width: '100%', gap: listGroupGap }}>
|
|
256
|
+
{items.map((item, index) => (
|
|
257
|
+
<View
|
|
258
|
+
key={`${item.label}-${index}`}
|
|
259
|
+
style={{
|
|
260
|
+
flexDirection: 'row',
|
|
261
|
+
alignItems: 'center',
|
|
262
|
+
gap: rowGap,
|
|
263
|
+
width: '100%',
|
|
264
|
+
}}
|
|
265
|
+
>
|
|
266
|
+
<View style={{ flex: 1, minWidth: 0 }}>
|
|
267
|
+
<Text style={labelStyle}>{item.label}</Text>
|
|
268
|
+
</View>
|
|
269
|
+
{typeof item.value === 'string' || typeof item.value === 'number' ? (
|
|
270
|
+
<Text style={valueTextStyle} numberOfLines={1}>
|
|
271
|
+
{item.value}
|
|
272
|
+
</Text>
|
|
273
|
+
) : (
|
|
274
|
+
cloneChildrenWithModes(item.value, modes)
|
|
275
|
+
)}
|
|
276
|
+
</View>
|
|
277
|
+
))}
|
|
278
|
+
</View>
|
|
279
|
+
)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const renderFooter = (): React.ReactNode => {
|
|
283
|
+
if (footer === false || footer === null) return null
|
|
284
|
+
if (footer === undefined) {
|
|
285
|
+
return (
|
|
286
|
+
<Button
|
|
287
|
+
label={buttonLabel}
|
|
288
|
+
{...(onButtonPress ? { onPress: onButtonPress } : {})}
|
|
289
|
+
modes={modes}
|
|
290
|
+
style={{ width: '100%' }}
|
|
291
|
+
/>
|
|
292
|
+
)
|
|
293
|
+
}
|
|
294
|
+
const processed = cloneChildrenWithModes(footer, modes)
|
|
295
|
+
return processed.length === 1 ? processed[0] : processed
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return (
|
|
299
|
+
<View
|
|
300
|
+
accessibilityLabel={accessibilityLabel ?? institutionName}
|
|
301
|
+
style={[
|
|
302
|
+
{
|
|
303
|
+
backgroundColor: background,
|
|
304
|
+
borderRadius: radius,
|
|
305
|
+
paddingHorizontal,
|
|
306
|
+
paddingVertical,
|
|
307
|
+
gap,
|
|
308
|
+
alignItems: 'flex-start',
|
|
309
|
+
width: '100%',
|
|
310
|
+
},
|
|
311
|
+
style,
|
|
312
|
+
]}
|
|
313
|
+
>
|
|
314
|
+
{renderHeader()}
|
|
315
|
+
{renderItems()}
|
|
316
|
+
{renderFooter()}
|
|
317
|
+
</View>
|
|
318
|
+
)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export default CardBankAccount
|