jfs-components 0.0.73 → 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 +23 -6
- package/lib/commonjs/components/AccountCard/AccountCard.js +247 -0
- package/lib/commonjs/components/AppBar/AppBar.js +17 -11
- package/lib/commonjs/components/CardBankAccount/CardBankAccount.js +18 -2
- package/lib/commonjs/components/CheckboxItem/CheckboxItem.js +40 -25
- 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/LottieIntroBlock/LottieIntroBlock.js +150 -0
- 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/Text/Text.js +9 -2
- package/lib/commonjs/components/Tooltip/Tooltip.js +34 -27
- package/lib/commonjs/components/index.js +60 -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/AccountCard/AccountCard.js +241 -0
- package/lib/module/components/AppBar/AppBar.js +17 -11
- package/lib/module/components/CardBankAccount/CardBankAccount.js +17 -2
- package/lib/module/components/CheckboxItem/CheckboxItem.js +41 -26
- 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/LottieIntroBlock/LottieIntroBlock.js +144 -0
- 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/Text/Text.js +9 -2
- package/lib/module/components/Tooltip/Tooltip.js +34 -27
- package/lib/module/components/index.js +7 -1
- 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/AccountCard/AccountCard.d.ts +81 -0
- package/lib/typescript/src/components/CardBankAccount/CardBankAccount.d.ts +9 -2
- package/lib/typescript/src/components/CheckboxItem/CheckboxItem.d.ts +18 -2
- 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/LottieIntroBlock/LottieIntroBlock.d.ts +58 -0
- 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/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 +7 -1
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +1 -3
- package/src/components/AccountCard/AccountCard.tsx +376 -0
- package/src/components/AppBar/AppBar.tsx +25 -14
- package/src/components/CardBankAccount/CardBankAccount.tsx +29 -3
- package/src/components/CheckboxItem/CheckboxItem.tsx +65 -30
- 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/LottieIntroBlock/LottieIntroBlock.tsx +202 -0
- 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/Text/Text.tsx +24 -3
- package/src/components/Tooltip/Tooltip.tsx +50 -25
- package/src/components/index.ts +15 -1
- package/src/design-tokens/Coin Variables-variables-full.json +1 -1
- package/src/icons/registry.ts +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { default as AccountCard, type AccountCardProps, type AccountCardState } from './AccountCard/AccountCard';
|
|
1
2
|
export { default as ActionFooter, type ActionFooterProps } from './ActionFooter/ActionFooter';
|
|
2
3
|
export { default as AppBar } from './AppBar/AppBar';
|
|
3
4
|
export { default as Avatar, type AvatarProps } from './Avatar/Avatar';
|
|
@@ -21,6 +22,8 @@ export { default as CardInsight, type CardInsightProps } from './CardInsight/Car
|
|
|
21
22
|
export { default as Disclaimer } from './Disclaimer/Disclaimer';
|
|
22
23
|
export { default as Divider, type DividerProps, type DividerDirection } from './Divider/Divider';
|
|
23
24
|
export { default as Drawer } from './Drawer/Drawer';
|
|
25
|
+
export { default as Dropdown, DropdownItem, type DropdownProps, type DropdownItemProps } from './Dropdown/Dropdown';
|
|
26
|
+
export { default as DropdownInput, type DropdownInputProps, type DropdownInputOption, type DropdownInputOptionValue } from './DropdownInput/DropdownInput';
|
|
24
27
|
export { default as CardCTA, type CardCTAProps, type CardCTAType } from './CardCTA/CardCTA';
|
|
25
28
|
export { default as DebitCard, type DebitCardProps } from './DebitCard/DebitCard';
|
|
26
29
|
export { default as FilterBar } from './FilterBar/FilterBar';
|
|
@@ -43,6 +46,7 @@ export { default as LazyList } from './LazyList/LazyList';
|
|
|
43
46
|
export { default as LinearMeter, type LinearMeterProps } from './LinearMeter/LinearMeter';
|
|
44
47
|
export { default as LinearProgress, type LinearProgressProps } from './LinearProgress/LinearProgress';
|
|
45
48
|
export { default as ListGroup } from './ListGroup/ListGroup';
|
|
49
|
+
export { default as LottieIntroBlock, type LottieIntroBlockProps } from './LottieIntroBlock/LottieIntroBlock';
|
|
46
50
|
export { default as ListItem } from './ListItem/ListItem';
|
|
47
51
|
export { default as MediaCard, type MediaCardProps } from './MediaCard/MediaCard';
|
|
48
52
|
export { default as MerchantProfile, type MerchantProfileProps } from './MerchantProfile/MerchantProfile';
|
|
@@ -61,7 +65,7 @@ export { StepLabel } from './Stepper/StepLabel';
|
|
|
61
65
|
export { default as TextInput } from './TextInput/TextInput';
|
|
62
66
|
export { default as StatusHero, type StatusHeroProps } from './StatusHero/StatusHero';
|
|
63
67
|
export { default as ThreadHero, type ThreadHeroProps } from './ThreadHero/ThreadHero';
|
|
64
|
-
export { Tooltip } from './Tooltip/Tooltip';
|
|
68
|
+
export { Tooltip, TooltipTrigger, TooltipContent, type TooltipProps, type TooltipTriggerProps, type TooltipContentProps, type Placement as TooltipPlacement, } from './Tooltip/Tooltip';
|
|
65
69
|
export { default as TransactionDetails } from './TransactionDetails/TransactionDetails';
|
|
66
70
|
export { default as TransactionStatus } from './TransactionStatus/TransactionStatus';
|
|
67
71
|
export { default as TransactionBubble, type TransactionBubbleProps } from './TransactionBubble/TransactionBubble';
|
|
@@ -98,8 +102,10 @@ export { default as Toast, type ToastProps } from './Toast/Toast';
|
|
|
98
102
|
export { default as ToastProvider, type ToastProviderProps } from './Toast/ToastProvider';
|
|
99
103
|
export { useToast, addToast, closeToast, closeAll, type ToastOptions, type ToastEntry, type ToastPlacement } from './Toast/useToast';
|
|
100
104
|
export { default as AmountInput, type AmountInputProps } from './AmountInput/AmountInput';
|
|
105
|
+
export { default as PageHero, type PageHeroProps } from './PageHero/PageHero';
|
|
101
106
|
export { default as Popup, type PopupProps, type PopupRef } from './Popup/Popup';
|
|
102
107
|
export { default as PortfolioHero, type PortfolioHeroProps } from './PortfolioHero/PortfolioHero';
|
|
108
|
+
export { default as PoweredByLabel, type PoweredByLabelProps } from './PoweredByLabel/PoweredByLabel';
|
|
103
109
|
export { default as ProductLabel, type ProductLabelProps } from './ProductLabel/ProductLabel';
|
|
104
110
|
export { default as ProductOverview, type ProductOverviewProps, type ProductOverviewStat } from './ProductOverview/ProductOverview';
|
|
105
111
|
export { default as ProgressBadge, type ProgressBadgeProps } from './ProgressBadge/ProgressBadge';
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Auto-generated from SVG files in src/icons/
|
|
5
5
|
* DO NOT EDIT MANUALLY - Run "npm run icons:generate" to regenerate
|
|
6
6
|
*
|
|
7
|
-
* Generated: 2026-05-
|
|
7
|
+
* Generated: 2026-05-19T16:28:08.570Z
|
|
8
8
|
*/
|
|
9
9
|
export declare const iconRegistry: Record<string, {
|
|
10
10
|
path: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jfs-components",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.74",
|
|
4
4
|
"description": "React Native Jio Finance Components Library",
|
|
5
5
|
"author": "sunshuaiqi@gmail.com",
|
|
6
6
|
"license": "MIT",
|
|
@@ -36,8 +36,6 @@
|
|
|
36
36
|
"storybook-web": "storybook dev -p 6006",
|
|
37
37
|
"build:storybook-web": "storybook build",
|
|
38
38
|
"deploy:storybook": "vercel --prod",
|
|
39
|
-
"example": "yarn --cwd example start",
|
|
40
|
-
"example:install": "yarn --cwd example install",
|
|
41
39
|
"postinstall": "patch-package",
|
|
42
40
|
"icons:generate": "node scripts/generate-icon-registry.js",
|
|
43
41
|
"prepare": "bob build",
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import React, { useCallback, useMemo, useRef } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Image as RNImage,
|
|
4
|
+
Platform,
|
|
5
|
+
Pressable,
|
|
6
|
+
Text,
|
|
7
|
+
View,
|
|
8
|
+
type ImageSourcePropType,
|
|
9
|
+
type ImageStyle,
|
|
10
|
+
type PressableStateCallbackType,
|
|
11
|
+
type StyleProp,
|
|
12
|
+
type TextStyle,
|
|
13
|
+
type ViewStyle,
|
|
14
|
+
} from 'react-native'
|
|
15
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
16
|
+
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
|
|
17
|
+
import Icon from '../../icons/Icon'
|
|
18
|
+
|
|
19
|
+
const IS_IOS = Platform.OS === 'ios'
|
|
20
|
+
const PRESS_DELAY = IS_IOS ? 130 : 0
|
|
21
|
+
const DEFAULT_IMAGE_RATIO = 125 / 82
|
|
22
|
+
|
|
23
|
+
const pressedOverlayStyle: ViewStyle = { opacity: 0.7 }
|
|
24
|
+
|
|
25
|
+
// Default modes for the "Add" placeholder icon (`iconButton/*` tokens).
|
|
26
|
+
// Caller-supplied `modes` are merged on top so every key here can be overridden.
|
|
27
|
+
const ADD_ICON_DEFAULT_MODES: Readonly<Record<string, any>> = Object.freeze({
|
|
28
|
+
AppearanceBrand: 'Secondary',
|
|
29
|
+
Emphasis: 'Low',
|
|
30
|
+
"Button / Size": "M",
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
export type AccountCardState = 'connected' | 'add'
|
|
34
|
+
|
|
35
|
+
export type AccountCardProps = {
|
|
36
|
+
/**
|
|
37
|
+
* Visual variant of the card.
|
|
38
|
+
* - `'connected'` (default): renders a card preview image, a `title` and a
|
|
39
|
+
* `subtitle` below it (e.g. account name + masked number).
|
|
40
|
+
* - `'add'`: renders an empty placeholder field with a centred `+` icon
|
|
41
|
+
* and only the `title`. Use this for the "Add new account" affordance
|
|
42
|
+
* that pairs with a list of connected `AccountCard`s.
|
|
43
|
+
*/
|
|
44
|
+
state?: AccountCardState
|
|
45
|
+
/** Title rendered below the image / placeholder. */
|
|
46
|
+
title?: string
|
|
47
|
+
/**
|
|
48
|
+
* Subtitle rendered below the title (e.g. masked account number).
|
|
49
|
+
* Only displayed when `state === 'connected'`.
|
|
50
|
+
*/
|
|
51
|
+
subtitle?: string
|
|
52
|
+
/**
|
|
53
|
+
* Card preview image. Only used when `state === 'connected'` and
|
|
54
|
+
* `cardSlot` is not provided. Accepts a URL string or any
|
|
55
|
+
* `ImageSourcePropType` (e.g. `require('./card.png')`).
|
|
56
|
+
*/
|
|
57
|
+
imageSource?: ImageSourcePropType | string
|
|
58
|
+
/**
|
|
59
|
+
* Aspect ratio (width / height) of the image / placeholder area.
|
|
60
|
+
* Defaults to the Figma reference ratio (`125 / 82` ≈ `1.524`).
|
|
61
|
+
*/
|
|
62
|
+
imageRatio?: number
|
|
63
|
+
/**
|
|
64
|
+
* Override the entire image / placeholder area with a custom node
|
|
65
|
+
* (e.g. a smaller `DebitCard`, a brand logo, a remote image).
|
|
66
|
+
* Receives `modes` automatically through `cloneChildrenWithModes`.
|
|
67
|
+
*/
|
|
68
|
+
cardSlot?: React.ReactNode
|
|
69
|
+
/**
|
|
70
|
+
* Icon name shown inside the "Add" placeholder. Defaults to `'ic_add'`.
|
|
71
|
+
* Only used when `state === 'add'`.
|
|
72
|
+
*/
|
|
73
|
+
addIcon?: string
|
|
74
|
+
/**
|
|
75
|
+
* Press handler. Applied to the whole card so the entire surface is a
|
|
76
|
+
* single touch target — particularly important for the `'add'` state,
|
|
77
|
+
* which behaves like a button.
|
|
78
|
+
*/
|
|
79
|
+
onPress?: () => void
|
|
80
|
+
/** Disable interaction (also dims the card). */
|
|
81
|
+
disabled?: boolean
|
|
82
|
+
/** Design token modes (e.g. `{ 'Color Mode': 'Light' }`). */
|
|
83
|
+
modes?: Record<string, any>
|
|
84
|
+
/** Container style override. */
|
|
85
|
+
style?: StyleProp<ViewStyle>
|
|
86
|
+
/** Accessibility label (defaults to `title`). */
|
|
87
|
+
accessibilityLabel?: string
|
|
88
|
+
/** Accessibility hint forwarded to the underlying `Pressable`. */
|
|
89
|
+
accessibilityHint?: string
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const toNumber = (value: unknown, fallback: number): number => {
|
|
93
|
+
if (typeof value === 'number') return Number.isFinite(value) ? value : fallback
|
|
94
|
+
if (typeof value === 'string') {
|
|
95
|
+
const parsed = Number(value)
|
|
96
|
+
return Number.isFinite(parsed) ? parsed : fallback
|
|
97
|
+
}
|
|
98
|
+
return fallback
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const toFontWeight = (
|
|
102
|
+
value: unknown,
|
|
103
|
+
fallback: TextStyle['fontWeight']
|
|
104
|
+
): TextStyle['fontWeight'] => {
|
|
105
|
+
if (typeof value === 'number') return String(value) as TextStyle['fontWeight']
|
|
106
|
+
if (typeof value === 'string') return value as TextStyle['fontWeight']
|
|
107
|
+
return fallback
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const normalizeImageSource = (
|
|
111
|
+
src: AccountCardProps['imageSource']
|
|
112
|
+
): ImageSourcePropType | undefined => {
|
|
113
|
+
if (src == null) return undefined
|
|
114
|
+
if (typeof src === 'string') return { uri: src }
|
|
115
|
+
return src
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* `AccountCard` — a compact card preview used to represent a linked
|
|
120
|
+
* financial account in lists / grids.
|
|
121
|
+
*
|
|
122
|
+
* Two visual states are supported via the `state` prop:
|
|
123
|
+
*
|
|
124
|
+
* 1. `'connected'` (default): renders a small card-art preview, a bold
|
|
125
|
+
* `title` and a regular-weight `subtitle` (e.g. masked account number).
|
|
126
|
+
* 2. `'add'`: renders a soft-tinted placeholder field with a centred `+`
|
|
127
|
+
* icon and the `title` underneath, intended as the "add new account"
|
|
128
|
+
* entry-point at the end of a list of connected accounts.
|
|
129
|
+
*
|
|
130
|
+
* All values resolve through the `accountCard/*` design tokens with
|
|
131
|
+
* sensible Figma defaults so the card renders correctly out of the box.
|
|
132
|
+
*
|
|
133
|
+
* @component
|
|
134
|
+
* @param {AccountCardProps} props
|
|
135
|
+
*/
|
|
136
|
+
function AccountCard({
|
|
137
|
+
state = 'connected',
|
|
138
|
+
title = 'Personal account',
|
|
139
|
+
subtitle = '**** 5651',
|
|
140
|
+
imageSource,
|
|
141
|
+
imageRatio = DEFAULT_IMAGE_RATIO,
|
|
142
|
+
cardSlot,
|
|
143
|
+
addIcon = 'ic_add',
|
|
144
|
+
onPress,
|
|
145
|
+
disabled = false,
|
|
146
|
+
modes = EMPTY_MODES,
|
|
147
|
+
style,
|
|
148
|
+
accessibilityLabel,
|
|
149
|
+
accessibilityHint,
|
|
150
|
+
}: AccountCardProps) {
|
|
151
|
+
const iconModes = useMemo(
|
|
152
|
+
() =>
|
|
153
|
+
modes === EMPTY_MODES
|
|
154
|
+
? ADD_ICON_DEFAULT_MODES
|
|
155
|
+
: { ...ADD_ICON_DEFAULT_MODES, ...modes },
|
|
156
|
+
[modes],
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
// ---- Tokens ---------------------------------------------------------
|
|
160
|
+
const gap = toNumber(getVariableByName('accountCard/gap', modes), 8)
|
|
161
|
+
const textWrapGap = toNumber(getVariableByName('accountCard/textWrap/gap', modes), 0)
|
|
162
|
+
|
|
163
|
+
const titleColor =
|
|
164
|
+
(getVariableByName('accountCard/title/foreground', modes) as string | null) ?? '#0d0d0f'
|
|
165
|
+
const titleFontFamily =
|
|
166
|
+
(getVariableByName('accountCard/title/fontFamily', modes) as string | null) ?? 'JioType Var'
|
|
167
|
+
const titleFontSize = toNumber(getVariableByName('accountCard/title/fontSize', modes), 12)
|
|
168
|
+
const titleLineHeight = toNumber(getVariableByName('accountCard/title/lineHeight', modes), 16)
|
|
169
|
+
const titleFontWeight = toFontWeight(
|
|
170
|
+
getVariableByName('accountCard/title/fontWeight', modes),
|
|
171
|
+
'700'
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
const subtitleColor =
|
|
175
|
+
(getVariableByName('accountCard/subtitle/foreground', modes) as string | null) ?? '#24262b'
|
|
176
|
+
const subtitleFontFamily =
|
|
177
|
+
(getVariableByName('accountCard/subtitle/fontFamily', modes) as string | null) ??
|
|
178
|
+
'JioType Var'
|
|
179
|
+
const subtitleFontSize = toNumber(
|
|
180
|
+
getVariableByName('accountCard/subtitle/fontSize', modes),
|
|
181
|
+
12
|
|
182
|
+
)
|
|
183
|
+
const subtitleLineHeight = toNumber(
|
|
184
|
+
getVariableByName('accountCard/subtitle/lineHeight', modes),
|
|
185
|
+
16
|
|
186
|
+
)
|
|
187
|
+
const subtitleFontWeight = toFontWeight(
|
|
188
|
+
getVariableByName('accountCard/subtitle/fontWeight', modes),
|
|
189
|
+
'400'
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
const addFieldRadius = toNumber(
|
|
193
|
+
getVariableByName('accountCard/addItemField/radius', modes),
|
|
194
|
+
8
|
|
195
|
+
)
|
|
196
|
+
const addFieldBg =
|
|
197
|
+
(getVariableByName('accountCard/addItemField/bg', modes) as string | null) ?? '#ede8ff'
|
|
198
|
+
|
|
199
|
+
const addIconColor =
|
|
200
|
+
(getVariableByName('iconButton/icon/color', iconModes) as string | null) ?? '#5d00b5'
|
|
201
|
+
const addIconSize = toNumber(getVariableByName('iconButton/icon/size', iconModes), 16)
|
|
202
|
+
|
|
203
|
+
// ---- Styles ---------------------------------------------------------
|
|
204
|
+
const titleStyle: TextStyle = {
|
|
205
|
+
color: titleColor,
|
|
206
|
+
fontFamily: titleFontFamily,
|
|
207
|
+
fontSize: titleFontSize,
|
|
208
|
+
lineHeight: titleLineHeight,
|
|
209
|
+
fontWeight: titleFontWeight,
|
|
210
|
+
width: '100%',
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const subtitleStyle: TextStyle = {
|
|
214
|
+
color: subtitleColor,
|
|
215
|
+
fontFamily: subtitleFontFamily,
|
|
216
|
+
fontSize: subtitleFontSize,
|
|
217
|
+
lineHeight: subtitleLineHeight,
|
|
218
|
+
fontWeight: subtitleFontWeight,
|
|
219
|
+
width: '100%',
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const imageBoxStyle: ViewStyle = {
|
|
223
|
+
width: '100%',
|
|
224
|
+
aspectRatio: imageRatio,
|
|
225
|
+
overflow: 'hidden',
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// RN's `<Image>` accepts `ImageStyle`, which has a narrower `overflow`
|
|
229
|
+
// union than `ViewStyle`. Build the image-only style separately so the
|
|
230
|
+
// shared box dimensions can be reused without a cast.
|
|
231
|
+
const imageStyle: ImageStyle = {
|
|
232
|
+
width: '100%',
|
|
233
|
+
aspectRatio: imageRatio,
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const addFieldStyle: ViewStyle = {
|
|
237
|
+
...imageBoxStyle,
|
|
238
|
+
backgroundColor: addFieldBg,
|
|
239
|
+
borderRadius: addFieldRadius,
|
|
240
|
+
alignItems: 'center',
|
|
241
|
+
justifyContent: 'center',
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ---- Image / placeholder area --------------------------------------
|
|
245
|
+
const renderCardArea = (): React.ReactNode => {
|
|
246
|
+
if (cardSlot !== undefined && cardSlot !== null) {
|
|
247
|
+
const processed = cloneChildrenWithModes(cardSlot, modes)
|
|
248
|
+
return (
|
|
249
|
+
<View style={imageBoxStyle} pointerEvents="box-none">
|
|
250
|
+
{processed.length === 1 ? processed[0] : processed}
|
|
251
|
+
</View>
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (state === 'add') {
|
|
256
|
+
return (
|
|
257
|
+
<View style={addFieldStyle} accessibilityElementsHidden importantForAccessibility="no">
|
|
258
|
+
<Icon name={addIcon} size={addIconSize} color={addIconColor} />
|
|
259
|
+
</View>
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const normalized = normalizeImageSource(imageSource)
|
|
264
|
+
if (normalized) {
|
|
265
|
+
return (
|
|
266
|
+
<RNImage
|
|
267
|
+
source={normalized}
|
|
268
|
+
style={imageStyle}
|
|
269
|
+
resizeMode="cover"
|
|
270
|
+
accessibilityElementsHidden
|
|
271
|
+
importantForAccessibility="no"
|
|
272
|
+
/>
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return <View style={imageBoxStyle} />
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// ---- Pressable wiring ----------------------------------------------
|
|
280
|
+
// Keep React state out of the press path. `Pressable`'s style callback
|
|
281
|
+
// applies the pressed visual without a re-render.
|
|
282
|
+
const userHandlersRef = useRef<{
|
|
283
|
+
onPressIn?: (e: any) => void
|
|
284
|
+
onPressOut?: (e: any) => void
|
|
285
|
+
}>({})
|
|
286
|
+
|
|
287
|
+
const handlePressIn = useCallback((e: any) => {
|
|
288
|
+
userHandlersRef.current.onPressIn?.(e)
|
|
289
|
+
}, [])
|
|
290
|
+
const handlePressOut = useCallback((e: any) => {
|
|
291
|
+
userHandlersRef.current.onPressOut?.(e)
|
|
292
|
+
}, [])
|
|
293
|
+
|
|
294
|
+
const containerStyle: ViewStyle = useMemo(
|
|
295
|
+
() => ({
|
|
296
|
+
width: '100%',
|
|
297
|
+
flexDirection: 'column',
|
|
298
|
+
alignItems: 'flex-start',
|
|
299
|
+
gap,
|
|
300
|
+
opacity: disabled ? 0.5 : 1,
|
|
301
|
+
}),
|
|
302
|
+
[gap, disabled]
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
const pressableStyle = useCallback(
|
|
306
|
+
({ pressed }: PressableStateCallbackType): StyleProp<ViewStyle> => [
|
|
307
|
+
containerStyle,
|
|
308
|
+
style,
|
|
309
|
+
pressed && !disabled && onPress ? pressedOverlayStyle : null,
|
|
310
|
+
],
|
|
311
|
+
[containerStyle, style, disabled, onPress]
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
const showSubtitle = state === 'connected' && subtitle != null && subtitle !== ''
|
|
315
|
+
|
|
316
|
+
const a11yRole = onPress ? 'button' : undefined
|
|
317
|
+
const a11yLabel = accessibilityLabel ?? title
|
|
318
|
+
|
|
319
|
+
const content = (
|
|
320
|
+
<>
|
|
321
|
+
{renderCardArea()}
|
|
322
|
+
{(title != null && title !== '') || showSubtitle ? (
|
|
323
|
+
<View
|
|
324
|
+
style={{
|
|
325
|
+
width: '100%',
|
|
326
|
+
flexDirection: 'column',
|
|
327
|
+
alignItems: 'flex-start',
|
|
328
|
+
gap: textWrapGap,
|
|
329
|
+
}}
|
|
330
|
+
>
|
|
331
|
+
{title != null && title !== '' ? (
|
|
332
|
+
<Text style={titleStyle} numberOfLines={1}>
|
|
333
|
+
{title}
|
|
334
|
+
</Text>
|
|
335
|
+
) : null}
|
|
336
|
+
{showSubtitle ? (
|
|
337
|
+
<Text style={subtitleStyle} numberOfLines={1}>
|
|
338
|
+
{subtitle}
|
|
339
|
+
</Text>
|
|
340
|
+
) : null}
|
|
341
|
+
</View>
|
|
342
|
+
) : null}
|
|
343
|
+
</>
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
if (!onPress) {
|
|
347
|
+
return (
|
|
348
|
+
<View
|
|
349
|
+
accessibilityLabel={a11yLabel}
|
|
350
|
+
accessibilityHint={accessibilityHint}
|
|
351
|
+
style={[containerStyle, style]}
|
|
352
|
+
>
|
|
353
|
+
{content}
|
|
354
|
+
</View>
|
|
355
|
+
)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return (
|
|
359
|
+
<Pressable
|
|
360
|
+
accessibilityRole={a11yRole}
|
|
361
|
+
accessibilityLabel={a11yLabel}
|
|
362
|
+
accessibilityHint={accessibilityHint}
|
|
363
|
+
accessibilityState={{ disabled }}
|
|
364
|
+
onPress={disabled ? undefined : onPress}
|
|
365
|
+
disabled={disabled}
|
|
366
|
+
onPressIn={handlePressIn}
|
|
367
|
+
onPressOut={handlePressOut}
|
|
368
|
+
unstable_pressDelay={PRESS_DELAY}
|
|
369
|
+
style={pressableStyle}
|
|
370
|
+
>
|
|
371
|
+
{content}
|
|
372
|
+
</Pressable>
|
|
373
|
+
)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export default React.memo(AccountCard)
|
|
@@ -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
|
>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React, { useMemo } from 'react'
|
|
2
2
|
import {
|
|
3
3
|
View,
|
|
4
4
|
Text,
|
|
@@ -66,7 +66,14 @@ export type CardBankAccountProps = {
|
|
|
66
66
|
* `false`/`null` to hide it entirely.
|
|
67
67
|
*/
|
|
68
68
|
footer?: React.ReactNode | false | null
|
|
69
|
-
/**
|
|
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
|
+
*/
|
|
70
77
|
modes?: Record<string, any>
|
|
71
78
|
/** Container style override. */
|
|
72
79
|
style?: StyleProp<ViewStyle>
|
|
@@ -80,6 +87,14 @@ const DEFAULT_ITEMS: CardBankAccountItem[] = [
|
|
|
80
87
|
{ label: 'Last updated', value: 'Korem ipsum' },
|
|
81
88
|
]
|
|
82
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
|
+
|
|
83
98
|
const toNumber = (value: unknown, fallback: number): number => {
|
|
84
99
|
if (typeof value === 'number') return Number.isFinite(value) ? value : fallback
|
|
85
100
|
if (typeof value === 'string') {
|
|
@@ -119,10 +134,21 @@ function CardBankAccount({
|
|
|
119
134
|
buttonLabel = 'Button',
|
|
120
135
|
onButtonPress,
|
|
121
136
|
footer,
|
|
122
|
-
modes = EMPTY_MODES,
|
|
137
|
+
modes: propModes = EMPTY_MODES,
|
|
123
138
|
style,
|
|
124
139
|
accessibilityLabel,
|
|
125
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
|
+
|
|
126
152
|
const background =
|
|
127
153
|
(getVariableByName('bankAccountCard/background', modes) as string | null) ?? '#ffffff'
|
|
128
154
|
const radius = toNumber(getVariableByName('bankAccountCard/radius', modes), 16)
|