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.
Files changed (63) hide show
  1. package/CHANGELOG.md +23 -6
  2. package/lib/commonjs/components/AccountCard/AccountCard.js +247 -0
  3. package/lib/commonjs/components/AppBar/AppBar.js +17 -11
  4. package/lib/commonjs/components/CardBankAccount/CardBankAccount.js +18 -2
  5. package/lib/commonjs/components/CheckboxItem/CheckboxItem.js +40 -25
  6. package/lib/commonjs/components/Dropdown/Dropdown.js +214 -0
  7. package/lib/commonjs/components/DropdownInput/DropdownInput.js +542 -0
  8. package/lib/commonjs/components/FormField/FormField.js +328 -178
  9. package/lib/commonjs/components/LottieIntroBlock/LottieIntroBlock.js +150 -0
  10. package/lib/commonjs/components/PageHero/PageHero.js +153 -0
  11. package/lib/commonjs/components/PoweredByLabel/PoweredByLabel.js +135 -0
  12. package/lib/commonjs/components/PoweredByLabel/finvu.png +0 -0
  13. package/lib/commonjs/components/Text/Text.js +9 -2
  14. package/lib/commonjs/components/Tooltip/Tooltip.js +34 -27
  15. package/lib/commonjs/components/index.js +60 -0
  16. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
  17. package/lib/commonjs/icons/registry.js +1 -1
  18. package/lib/module/components/AccountCard/AccountCard.js +241 -0
  19. package/lib/module/components/AppBar/AppBar.js +17 -11
  20. package/lib/module/components/CardBankAccount/CardBankAccount.js +17 -2
  21. package/lib/module/components/CheckboxItem/CheckboxItem.js +41 -26
  22. package/lib/module/components/Dropdown/Dropdown.js +206 -0
  23. package/lib/module/components/DropdownInput/DropdownInput.js +536 -0
  24. package/lib/module/components/FormField/FormField.js +330 -180
  25. package/lib/module/components/LottieIntroBlock/LottieIntroBlock.js +144 -0
  26. package/lib/module/components/PageHero/PageHero.js +147 -0
  27. package/lib/module/components/PoweredByLabel/PoweredByLabel.js +130 -0
  28. package/lib/module/components/PoweredByLabel/finvu.png +0 -0
  29. package/lib/module/components/Text/Text.js +9 -2
  30. package/lib/module/components/Tooltip/Tooltip.js +34 -27
  31. package/lib/module/components/index.js +7 -1
  32. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
  33. package/lib/module/icons/registry.js +1 -1
  34. package/lib/typescript/src/components/AccountCard/AccountCard.d.ts +81 -0
  35. package/lib/typescript/src/components/CardBankAccount/CardBankAccount.d.ts +9 -2
  36. package/lib/typescript/src/components/CheckboxItem/CheckboxItem.d.ts +18 -2
  37. package/lib/typescript/src/components/Dropdown/Dropdown.d.ts +62 -0
  38. package/lib/typescript/src/components/DropdownInput/DropdownInput.d.ts +107 -0
  39. package/lib/typescript/src/components/FormField/FormField.d.ts +76 -19
  40. package/lib/typescript/src/components/LottieIntroBlock/LottieIntroBlock.d.ts +58 -0
  41. package/lib/typescript/src/components/PageHero/PageHero.d.ts +53 -0
  42. package/lib/typescript/src/components/PoweredByLabel/PoweredByLabel.d.ts +70 -0
  43. package/lib/typescript/src/components/Text/Text.d.ts +12 -2
  44. package/lib/typescript/src/components/Tooltip/Tooltip.d.ts +13 -2
  45. package/lib/typescript/src/components/index.d.ts +7 -1
  46. package/lib/typescript/src/icons/registry.d.ts +1 -1
  47. package/package.json +1 -3
  48. package/src/components/AccountCard/AccountCard.tsx +376 -0
  49. package/src/components/AppBar/AppBar.tsx +25 -14
  50. package/src/components/CardBankAccount/CardBankAccount.tsx +29 -3
  51. package/src/components/CheckboxItem/CheckboxItem.tsx +65 -30
  52. package/src/components/Dropdown/Dropdown.tsx +331 -0
  53. package/src/components/DropdownInput/DropdownInput.tsx +819 -0
  54. package/src/components/FormField/FormField.tsx +542 -215
  55. package/src/components/LottieIntroBlock/LottieIntroBlock.tsx +202 -0
  56. package/src/components/PageHero/PageHero.tsx +200 -0
  57. package/src/components/PoweredByLabel/PoweredByLabel.tsx +221 -0
  58. package/src/components/PoweredByLabel/finvu.png +0 -0
  59. package/src/components/Text/Text.tsx +24 -3
  60. package/src/components/Tooltip/Tooltip.tsx +50 -25
  61. package/src/components/index.ts +15 -1
  62. package/src/design-tokens/Coin Variables-variables-full.json +1 -1
  63. 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-15T16:18:56.427Z
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.73",
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: 'space-between',
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={[containerStyle, 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
- {/* Middle Section (Absolute centered often, or flex? Figma shows "Slot 'Middle'" inside a wrapper that is absolute center) */}
176
- {/* Figma: "absolute flex h-full items-center justify-center left-1/2 ... translate-x-[-50%]" */}
177
- {/* We should only render this wrapper if there IS middle content, to avoid z-index blocking hits. */}
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
- position: 'absolute',
182
- left: 0,
183
- right: 0,
184
- top: 0,
185
- bottom: 0,
196
+ flex: 1,
197
+ minWidth: 0,
186
198
  alignItems: 'center',
187
199
  justifyContent: 'center',
188
- zIndex: -1, // Behind actions if overlap? Or should be on top?
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
- /** Design token modes for theming (e.g. `{ 'Color Mode': 'Light' }`). */
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)