jfs-components 0.0.73 → 0.0.77
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 +115 -6
- package/lib/commonjs/components/AccountCard/AccountCard.js +247 -0
- package/lib/commonjs/components/ActionFooter/ActionFooter.js +147 -82
- package/lib/commonjs/components/AppBar/AppBar.js +17 -11
- package/lib/commonjs/components/Avatar/Avatar.js +20 -0
- package/lib/commonjs/components/Badge/Badge.js +23 -0
- package/lib/commonjs/components/Button/Button.js +37 -0
- 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/IconButton/IconButton.js +20 -0
- package/lib/commonjs/components/Image/Image.js +26 -1
- package/lib/commonjs/components/LottieIntroBlock/LottieIntroBlock.js +150 -0
- package/lib/commonjs/components/LottiePlayer/LottiePlayer.js +116 -0
- package/lib/commonjs/components/LottiePlayer/LottiePlayer.web.js +82 -0
- package/lib/commonjs/components/LottiePlayer/loadNativeLottieView.js +74 -0
- package/lib/commonjs/components/LottiePlayer/loadWebLottieView.js +50 -0
- package/lib/commonjs/components/PageHero/PageHero.js +189 -0
- package/lib/commonjs/components/PoweredByLabel/PoweredByLabel.js +135 -0
- package/lib/commonjs/components/PoweredByLabel/finvu.png +0 -0
- package/lib/commonjs/components/RechargeCard/RechargeCard.js +32 -17
- package/lib/commonjs/components/Text/Text.js +40 -3
- package/lib/commonjs/components/Tooltip/Tooltip.js +34 -27
- package/lib/commonjs/components/index.js +67 -0
- package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/commonjs/icons/Icon.js +16 -0
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/commonjs/index.js +12 -0
- package/lib/commonjs/skeleton/Skeleton.js +234 -0
- package/lib/commonjs/skeleton/SkeletonGroup.js +140 -0
- package/lib/commonjs/skeleton/index.js +58 -0
- package/lib/commonjs/skeleton/shimmer-tokens.js +189 -0
- package/lib/commonjs/skeleton/useReducedMotion.js +64 -0
- package/lib/module/components/AccountCard/AccountCard.js +241 -0
- package/lib/module/components/ActionFooter/ActionFooter.js +146 -82
- package/lib/module/components/AppBar/AppBar.js +17 -11
- package/lib/module/components/Avatar/Avatar.js +19 -0
- package/lib/module/components/Badge/Badge.js +23 -0
- package/lib/module/components/Button/Button.js +37 -0
- 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/IconButton/IconButton.js +20 -0
- package/lib/module/components/Image/Image.js +25 -1
- package/lib/module/components/LottieIntroBlock/LottieIntroBlock.js +144 -0
- package/lib/module/components/LottiePlayer/LottiePlayer.js +111 -0
- package/lib/module/components/LottiePlayer/LottiePlayer.web.js +77 -0
- package/lib/module/components/LottiePlayer/loadNativeLottieView.js +69 -0
- package/lib/module/components/LottiePlayer/loadWebLottieView.js +45 -0
- package/lib/module/components/PageHero/PageHero.js +183 -0
- package/lib/module/components/PoweredByLabel/PoweredByLabel.js +130 -0
- package/lib/module/components/PoweredByLabel/finvu.png +0 -0
- package/lib/module/components/RechargeCard/RechargeCard.js +33 -17
- package/lib/module/components/Text/Text.js +40 -3
- package/lib/module/components/Tooltip/Tooltip.js +34 -27
- package/lib/module/components/index.js +8 -1
- package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/module/icons/Icon.js +16 -0
- package/lib/module/icons/registry.js +1 -1
- package/lib/module/index.js +2 -1
- package/lib/module/skeleton/Skeleton.js +229 -0
- package/lib/module/skeleton/SkeletonGroup.js +133 -0
- package/lib/module/skeleton/index.js +6 -0
- package/lib/module/skeleton/shimmer-tokens.js +181 -0
- package/lib/module/skeleton/useReducedMotion.js +61 -0
- package/lib/typescript/src/components/AccountCard/AccountCard.d.ts +81 -0
- package/lib/typescript/src/components/ActionFooter/ActionFooter.d.ts +26 -21
- package/lib/typescript/src/components/Avatar/Avatar.d.ts +7 -1
- package/lib/typescript/src/components/Badge/Badge.d.ts +7 -1
- package/lib/typescript/src/components/Button/Button.d.ts +8 -1
- 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/IconButton/IconButton.d.ts +7 -1
- package/lib/typescript/src/components/Image/Image.d.ts +8 -1
- package/lib/typescript/src/components/LottieIntroBlock/LottieIntroBlock.d.ts +58 -0
- package/lib/typescript/src/components/LottiePlayer/LottiePlayer.d.ts +85 -0
- package/lib/typescript/src/components/LottiePlayer/LottiePlayer.web.d.ts +28 -0
- package/lib/typescript/src/components/LottiePlayer/loadNativeLottieView.d.ts +11 -0
- package/lib/typescript/src/components/LottiePlayer/loadWebLottieView.d.ts +11 -0
- package/lib/typescript/src/components/PageHero/PageHero.d.ts +79 -0
- package/lib/typescript/src/components/PoweredByLabel/PoweredByLabel.d.ts +70 -0
- package/lib/typescript/src/components/Text/Text.d.ts +31 -2
- package/lib/typescript/src/components/Tooltip/Tooltip.d.ts +13 -2
- package/lib/typescript/src/components/index.d.ts +8 -1
- package/lib/typescript/src/icons/Icon.d.ts +7 -1
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/skeleton/Skeleton.d.ts +60 -0
- package/lib/typescript/src/skeleton/SkeletonGroup.d.ts +78 -0
- package/lib/typescript/src/skeleton/index.d.ts +5 -0
- package/lib/typescript/src/skeleton/shimmer-tokens.d.ts +160 -0
- package/lib/typescript/src/skeleton/useReducedMotion.d.ts +15 -0
- package/package.json +11 -3
- package/src/components/AccountCard/AccountCard.tsx +376 -0
- package/src/components/ActionFooter/ActionFooter.tsx +152 -86
- package/src/components/AppBar/AppBar.tsx +25 -14
- package/src/components/Avatar/Avatar.tsx +26 -0
- package/src/components/Badge/Badge.tsx +27 -0
- package/src/components/Button/Button.tsx +40 -0
- 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/IconButton/IconButton.tsx +27 -0
- package/src/components/Image/Image.tsx +25 -0
- package/src/components/LottieIntroBlock/LottieIntroBlock.tsx +202 -0
- package/src/components/LottiePlayer/LottiePlayer.tsx +145 -0
- package/src/components/LottiePlayer/LottiePlayer.web.tsx +94 -0
- package/src/components/LottiePlayer/loadNativeLottieView.tsx +87 -0
- package/src/components/LottiePlayer/loadWebLottieView.tsx +64 -0
- package/src/components/PageHero/PageHero.tsx +257 -0
- package/src/components/PoweredByLabel/PoweredByLabel.tsx +221 -0
- package/src/components/PoweredByLabel/finvu.png +0 -0
- package/src/components/RechargeCard/RechargeCard.tsx +32 -24
- package/src/components/Text/Text.tsx +78 -3
- package/src/components/Tooltip/Tooltip.tsx +50 -25
- package/src/components/index.ts +16 -1
- package/src/design-tokens/Coin Variables-variables-full.json +1 -1
- package/src/icons/Icon.tsx +17 -0
- package/src/icons/registry.ts +1 -1
- package/src/index.ts +1 -0
- package/src/skeleton/Skeleton.tsx +298 -0
- package/src/skeleton/SkeletonGroup.tsx +193 -0
- package/src/skeleton/index.ts +10 -0
- package/src/skeleton/shimmer-tokens.ts +221 -0
- package/src/skeleton/useReducedMotion.ts +72 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,14 +4,123 @@ All notable changes to this project are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
6
|
|
|
7
|
+
## [0.0.77] - 2026-05-25
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **Skeleton / shimmer loading system** — new `src/skeleton/` module exported from the package root. One context provider (`<SkeletonGroup loading>`) flips an entire subtree into shape-preserving placeholders. The following primitives auto-skeletonize when inside an active group: `Text`, `Image`, `Badge`, `Button`, `IconButton`, `Avatar`, and `Icon`. Each one also accepts a per-instance `loading?: boolean` override. The atomic `<Skeleton kind="text|image|badge|other" width height />` block is available for non-primitive layouts. Honours `prefers-reduced-motion` automatically; one Reanimated clock per group; gradient angle locked to 135° on any aspect ratio.
|
|
12
|
+
- **`LottiePlayer`** — new component that renders Lottie animations using `lottie-react-native` (native) and `lottie-react` (web) via platform extensions (`LottiePlayer.web.tsx`). Both libraries are declared as **optional peer dependencies**, so installing `jfs-components` does not pull them in. Sizing is token-driven via `media/width` / `media/height` (`Media / Output` mode → `L | M | S` = `117 / 70 / 20`) — same contract `PageHero` and `LottieIntroBlock` already use for their media slots.
|
|
13
|
+
- New on-device dev-client scaffold under `example/` (Expo dev client + Storybook + Playground host), with its native deps pinned independently from the library.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- `ActionFooter` rewritten (~240 lines): `IconButton` children keep their intrinsic square size, every other child is auto-stretched to share the remaining horizontal space; modes now propagate to all slot children via `cloneChildrenWithModes`.
|
|
18
|
+
- `PageHero` API expansion (+65 lines): improved Figma alignment, new examples in stories/MDX.
|
|
19
|
+
- `RechargeCard` re-aligned to Figma node `2235:937`: container now draws the `rechargeCard/strokeWidth` + `rechargeCard/stroke/color` border, fallback colors for title/spec/disclaimer reset to `#000000`, `minWidth` falls back to `312`, and background fallback is now `#ffffff`. Hardcoded label/style overrides removed — colors come purely from tokens.
|
|
20
|
+
- `RechargeCard` now ships sensible slot mode defaults (overridable via the consumer's `modes` prop):
|
|
21
|
+
- inner `MoneyValue` defaults to `Context3: 'Balance & Cards'` (36 px / 900-weight Figma scale).
|
|
22
|
+
- inner `ButtonGroup` defaults to `AppearanceBrand: 'Secondary'`, `Button / Size: 'S'`, `Emphasis: 'High'`. The previous typo (`Appearance.Brand`) is fixed to match the actual `AppearanceBrand` collection, and defaults are spread *before* `modes` so any key can be overridden externally.
|
|
23
|
+
- Figma/token sync pass across `ActionFooter`, `DropdownInput`, `FormField`, `HStack`, `InputSearch`, `Nudge`, `Section`, `Stepper/StepLabel`, `SupportText/SupportTextIcon`, `VStack`, plus refreshed Coin Variables tokens, `.token-metadata.json`, and icons registry.
|
|
24
|
+
- Storybook config: babel-transpile `storybook-assets/` alongside `src/` so Lottie + platform-extension demo files resolve cleanly; `tsconfig.build.json` now excludes `storybook-assets/`.
|
|
25
|
+
|
|
26
|
+
### Library usage notes
|
|
27
|
+
|
|
28
|
+
#### Lottie animations
|
|
29
|
+
|
|
30
|
+
`LottiePlayer` is the only Lottie surface — `lottie-react-native` / `lottie-react` are optional peers and must be installed by the consumer:
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
# React Native (iOS / Android)
|
|
34
|
+
npm install lottie-react-native
|
|
35
|
+
cd ios && pod install
|
|
36
|
+
|
|
37
|
+
# Web (or react-native-web)
|
|
38
|
+
npm install lottie-react
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Then pass a **parsed JSON object** (URI sources are intentionally not supported — web players need data pre-parsed):
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import { LottiePlayer, PageHero, LottieIntroBlock } from 'jfs-components'
|
|
45
|
+
import animation from './assets/loader.json'
|
|
46
|
+
|
|
47
|
+
<LottiePlayer source={animation} /> // 117×117 (default)
|
|
48
|
+
<LottiePlayer source={animation} size={70} /> // explicit size
|
|
49
|
+
<LottiePlayer source={animation} modes={{ 'Media / Output': 'S' }} /> // 20×20 via token
|
|
50
|
+
<PageHero media={<LottiePlayer source={animation} />} /> // any media slot
|
|
51
|
+
<LottieIntroBlock mediaSlot={<LottiePlayer source={animation} />} />
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
#### Skeleton / shimmer loading
|
|
55
|
+
|
|
56
|
+
Wrap any subtree in `<SkeletonGroup loading={!data}>` — **no other changes to the screen are required**. All instrumented JFS primitives inside (`Text`, `Image`, `Badge`, `Button`, `IconButton`, `Avatar`, `Icon`) automatically swap themselves for a shape-preserving shimmer placeholder while `loading` is `true`, and revert to their real content when it flips to `false`. Layout never jumps because the placeholder reuses the same measured box as the real element.
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import { SkeletonGroup, Card, Image, Text, Badge, Button } from 'jfs-components'
|
|
60
|
+
|
|
61
|
+
function Profile({ user }) {
|
|
62
|
+
return (
|
|
63
|
+
<SkeletonGroup loading={!user}>
|
|
64
|
+
<Card media={<Image imageSource={user?.cover} ratio={16 / 9} />}>
|
|
65
|
+
<Card.Title>{user?.name}</Card.Title>
|
|
66
|
+
<Card.SupportText>{user?.role}</Card.SupportText>
|
|
67
|
+
<Badge label={user?.tier ?? 'Member'} />
|
|
68
|
+
<Button label="Follow" />
|
|
69
|
+
</Card>
|
|
70
|
+
</SkeletonGroup>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Two escape hatches when the auto-instrumentation isn't enough:
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
// 1) Bind a single primitive to its own load event, ignoring the group:
|
|
79
|
+
<Image imageSource={uri} ratio={16/9} loading={!loaded} onLoad={() => setLoaded(true)} />
|
|
80
|
+
|
|
81
|
+
// 2) Drop in atomic <Skeleton/> blocks for layouts not built on JFS primitives.
|
|
82
|
+
// Inside an active <SkeletonGroup> they shimmer with the same clock; outside one they render nothing.
|
|
83
|
+
import { Skeleton } from 'jfs-components'
|
|
84
|
+
<SkeletonGroup loading>
|
|
85
|
+
<View style={{ flexDirection: 'row', gap: 12 }}>
|
|
86
|
+
<Skeleton kind="other" width={48} height={48} />
|
|
87
|
+
<View style={{ flex: 1, gap: 6 }}>
|
|
88
|
+
<Skeleton kind="text" width="80%" height={16} />
|
|
89
|
+
<Skeleton kind="text" width="60%" height={14} />
|
|
90
|
+
</View>
|
|
91
|
+
</View>
|
|
92
|
+
</SkeletonGroup>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Reduced motion is auto-detected from the OS (`AccessibilityInfo` on native, `prefers-reduced-motion` on web); override via `<SkeletonGroup reducedMotion>` for testing or forced behaviour.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## [0.0.74] - 2026-05-19
|
|
100
|
+
|
|
101
|
+
- Added new account and onboarding components: `AccountCard` (connected/add variants), `PageHero`, `LottieIntroBlock`, and `PoweredByLabel`.
|
|
102
|
+
- Added new selection primitives: `Dropdown` + `DropdownItem` (compound API) and `DropdownInput` (options-based form control).
|
|
103
|
+
- Added Storybook coverage for the existing `Icon` primitive (no API change).
|
|
104
|
+
- Major `FormField` rewrite — re-bound directly to React Native `TextInput` (no longer wraps `TextInput` component), gained `number` / `phone` / `url` types, `Form` context integration, and improved Figma/token alignment.
|
|
105
|
+
- Compound `Tooltip` upgrade — `TooltipContent` now acts as a vertical slot with `gap` and `alignItems` props, and exports `TooltipPlacement` / trigger / content types from the package barrel.
|
|
106
|
+
- `Text` now accepts JSX `children` (drop-in `<Text>label</Text>` works) in addition to the `text` prop; `AppBar` middle slot lays out via `flex: 1` with `space-between` fallback when absent.
|
|
107
|
+
- `CheckboxItem` gained a `control` prop (`leading` / `trailing`) that swaps the checkbox and the end slot to either edge of the row.
|
|
108
|
+
- `CardBankAccount` ships sensible default `Button / Size` + Appearance modes so the footer button matches Figma out of the box.
|
|
109
|
+
- Figma/token sync pass across `FormField`, `CardBankAccount`, `CardFinancialCondition`, `CheckboxItem`, `CircularProgressBar`, `CoverageBarComparison`, `DonutChart`, `DonutChartSummary`, `Gauge`, `LinearProgress`, `MetricLegendItem`, `MonthlyStatusGrid`, `Nudge`, `RangeTrack`, `SavingsGoalSummary`, and `SegmentedTrack`; refreshed Coin Variables tokens, `.token-metadata.json`, and icons registry.
|
|
110
|
+
- Repo cleanup: removed the legacy `example/` app, `.expo/` artifacts, and the `example` / `example:install` scripts; updated `tsconfig.build.json` and `scripts/generate-component-docs.js`; added `.tgz` and `.expo/` to `.gitignore`.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
7
114
|
## [0.0.73] - 2026-05-15
|
|
8
115
|
|
|
9
|
-
- Added new
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
116
|
+
- Added new chart, progress, and coverage components: `DonutChart`, `DonutChartSummary`, `LinearProgress`, `SavingsGoalSummary`, `CoverageRing`, `CoverageBarComparison`, `MonthlyStatusGrid`, `MetricLegendItem`, `RangeTrack`, `SegmentedTrack`, `StatGroup`, `StrengthIndicator`, and `SummaryTile`.
|
|
117
|
+
- Added new form/selection primitives: `AccordionCheckbox`, `CheckboxGroup`, `CheckboxItem`, and `Radio` (renamed from `RadioButton`; the old name is kept as a deprecated re-export).
|
|
118
|
+
- Added new card and badge components: `CardBankAccount`, `CardInsight`, `ProductOverview`, and `BrandChip`.
|
|
119
|
+
- Major `OTP` overhaul (new API, stories, and MDX) and `LinearMeter` refactor; `Carousel` pagination/maxHeight refresh and `HoldingsCard` migration to `Radio`.
|
|
120
|
+
- Enhanced `CircularProgressBar` with an in-ring `supportText` caption (driven by `circularProgressBar/supportText/*` tokens) and a centered text stack; reworked `StatItem` API and stories.
|
|
121
|
+
- Token surface refresh: added `XS` to `Button / Size` (surfaced on `Button`, `CardCTA`, `IconButton`); split `LinearProgress` / `SavingsGoalSummary` `Appearance / DataViz` into `Appearance Type` + `AppearanceBrand` + `AppearanceSystem` and renamed `Emphasis / DataViz` → `Emphasis`; added `Auto` status + `FormField States` to `SupportText`, `SupportTextIcon`, `InputSearch`; added `Error` state and `Color Mode` to `FormField`; added `Page type` (MainPage / SubPage) to `Title`.
|
|
122
|
+
- New `number-utils` (+ tests) exported from `src/utils`; refreshed icons registry, Coin Variables tokens, and `.token-metadata.json`.
|
|
123
|
+
- Expanded Storybook and MDX coverage for all new and updated components, plus docs for `HStack`, `VStack`, and `Section`.
|
|
15
124
|
|
|
16
125
|
---
|
|
17
126
|
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
8
|
+
var _reactNative = require("react-native");
|
|
9
|
+
var _figmaVariablesResolver = require("../../design-tokens/figma-variables-resolver");
|
|
10
|
+
var _reactUtils = require("../../utils/react-utils");
|
|
11
|
+
var _Icon = _interopRequireDefault(require("../../icons/Icon"));
|
|
12
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
13
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
14
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
15
|
+
const IS_IOS = _reactNative.Platform.OS === 'ios';
|
|
16
|
+
const PRESS_DELAY = IS_IOS ? 130 : 0;
|
|
17
|
+
const DEFAULT_IMAGE_RATIO = 125 / 82;
|
|
18
|
+
const pressedOverlayStyle = {
|
|
19
|
+
opacity: 0.7
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Default modes for the "Add" placeholder icon (`iconButton/*` tokens).
|
|
23
|
+
// Caller-supplied `modes` are merged on top so every key here can be overridden.
|
|
24
|
+
const ADD_ICON_DEFAULT_MODES = Object.freeze({
|
|
25
|
+
AppearanceBrand: 'Secondary',
|
|
26
|
+
Emphasis: 'Low',
|
|
27
|
+
"Button / Size": "M"
|
|
28
|
+
});
|
|
29
|
+
const toNumber = (value, fallback) => {
|
|
30
|
+
if (typeof value === 'number') return Number.isFinite(value) ? value : fallback;
|
|
31
|
+
if (typeof value === 'string') {
|
|
32
|
+
const parsed = Number(value);
|
|
33
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
34
|
+
}
|
|
35
|
+
return fallback;
|
|
36
|
+
};
|
|
37
|
+
const toFontWeight = (value, fallback) => {
|
|
38
|
+
if (typeof value === 'number') return String(value);
|
|
39
|
+
if (typeof value === 'string') return value;
|
|
40
|
+
return fallback;
|
|
41
|
+
};
|
|
42
|
+
const normalizeImageSource = src => {
|
|
43
|
+
if (src == null) return undefined;
|
|
44
|
+
if (typeof src === 'string') return {
|
|
45
|
+
uri: src
|
|
46
|
+
};
|
|
47
|
+
return src;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* `AccountCard` — a compact card preview used to represent a linked
|
|
52
|
+
* financial account in lists / grids.
|
|
53
|
+
*
|
|
54
|
+
* Two visual states are supported via the `state` prop:
|
|
55
|
+
*
|
|
56
|
+
* 1. `'connected'` (default): renders a small card-art preview, a bold
|
|
57
|
+
* `title` and a regular-weight `subtitle` (e.g. masked account number).
|
|
58
|
+
* 2. `'add'`: renders a soft-tinted placeholder field with a centred `+`
|
|
59
|
+
* icon and the `title` underneath, intended as the "add new account"
|
|
60
|
+
* entry-point at the end of a list of connected accounts.
|
|
61
|
+
*
|
|
62
|
+
* All values resolve through the `accountCard/*` design tokens with
|
|
63
|
+
* sensible Figma defaults so the card renders correctly out of the box.
|
|
64
|
+
*
|
|
65
|
+
* @component
|
|
66
|
+
* @param {AccountCardProps} props
|
|
67
|
+
*/
|
|
68
|
+
function AccountCard({
|
|
69
|
+
state = 'connected',
|
|
70
|
+
title = 'Personal account',
|
|
71
|
+
subtitle = '**** 5651',
|
|
72
|
+
imageSource,
|
|
73
|
+
imageRatio = DEFAULT_IMAGE_RATIO,
|
|
74
|
+
cardSlot,
|
|
75
|
+
addIcon = 'ic_add',
|
|
76
|
+
onPress,
|
|
77
|
+
disabled = false,
|
|
78
|
+
modes = _reactUtils.EMPTY_MODES,
|
|
79
|
+
style,
|
|
80
|
+
accessibilityLabel,
|
|
81
|
+
accessibilityHint
|
|
82
|
+
}) {
|
|
83
|
+
const iconModes = (0, _react.useMemo)(() => modes === _reactUtils.EMPTY_MODES ? ADD_ICON_DEFAULT_MODES : {
|
|
84
|
+
...ADD_ICON_DEFAULT_MODES,
|
|
85
|
+
...modes
|
|
86
|
+
}, [modes]);
|
|
87
|
+
|
|
88
|
+
// ---- Tokens ---------------------------------------------------------
|
|
89
|
+
const gap = toNumber((0, _figmaVariablesResolver.getVariableByName)('accountCard/gap', modes), 8);
|
|
90
|
+
const textWrapGap = toNumber((0, _figmaVariablesResolver.getVariableByName)('accountCard/textWrap/gap', modes), 0);
|
|
91
|
+
const titleColor = (0, _figmaVariablesResolver.getVariableByName)('accountCard/title/foreground', modes) ?? '#0d0d0f';
|
|
92
|
+
const titleFontFamily = (0, _figmaVariablesResolver.getVariableByName)('accountCard/title/fontFamily', modes) ?? 'JioType Var';
|
|
93
|
+
const titleFontSize = toNumber((0, _figmaVariablesResolver.getVariableByName)('accountCard/title/fontSize', modes), 12);
|
|
94
|
+
const titleLineHeight = toNumber((0, _figmaVariablesResolver.getVariableByName)('accountCard/title/lineHeight', modes), 16);
|
|
95
|
+
const titleFontWeight = toFontWeight((0, _figmaVariablesResolver.getVariableByName)('accountCard/title/fontWeight', modes), '700');
|
|
96
|
+
const subtitleColor = (0, _figmaVariablesResolver.getVariableByName)('accountCard/subtitle/foreground', modes) ?? '#24262b';
|
|
97
|
+
const subtitleFontFamily = (0, _figmaVariablesResolver.getVariableByName)('accountCard/subtitle/fontFamily', modes) ?? 'JioType Var';
|
|
98
|
+
const subtitleFontSize = toNumber((0, _figmaVariablesResolver.getVariableByName)('accountCard/subtitle/fontSize', modes), 12);
|
|
99
|
+
const subtitleLineHeight = toNumber((0, _figmaVariablesResolver.getVariableByName)('accountCard/subtitle/lineHeight', modes), 16);
|
|
100
|
+
const subtitleFontWeight = toFontWeight((0, _figmaVariablesResolver.getVariableByName)('accountCard/subtitle/fontWeight', modes), '400');
|
|
101
|
+
const addFieldRadius = toNumber((0, _figmaVariablesResolver.getVariableByName)('accountCard/addItemField/radius', modes), 8);
|
|
102
|
+
const addFieldBg = (0, _figmaVariablesResolver.getVariableByName)('accountCard/addItemField/bg', modes) ?? '#ede8ff';
|
|
103
|
+
const addIconColor = (0, _figmaVariablesResolver.getVariableByName)('iconButton/icon/color', iconModes) ?? '#5d00b5';
|
|
104
|
+
const addIconSize = toNumber((0, _figmaVariablesResolver.getVariableByName)('iconButton/icon/size', iconModes), 16);
|
|
105
|
+
|
|
106
|
+
// ---- Styles ---------------------------------------------------------
|
|
107
|
+
const titleStyle = {
|
|
108
|
+
color: titleColor,
|
|
109
|
+
fontFamily: titleFontFamily,
|
|
110
|
+
fontSize: titleFontSize,
|
|
111
|
+
lineHeight: titleLineHeight,
|
|
112
|
+
fontWeight: titleFontWeight,
|
|
113
|
+
width: '100%'
|
|
114
|
+
};
|
|
115
|
+
const subtitleStyle = {
|
|
116
|
+
color: subtitleColor,
|
|
117
|
+
fontFamily: subtitleFontFamily,
|
|
118
|
+
fontSize: subtitleFontSize,
|
|
119
|
+
lineHeight: subtitleLineHeight,
|
|
120
|
+
fontWeight: subtitleFontWeight,
|
|
121
|
+
width: '100%'
|
|
122
|
+
};
|
|
123
|
+
const imageBoxStyle = {
|
|
124
|
+
width: '100%',
|
|
125
|
+
aspectRatio: imageRatio,
|
|
126
|
+
overflow: 'hidden'
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// RN's `<Image>` accepts `ImageStyle`, which has a narrower `overflow`
|
|
130
|
+
// union than `ViewStyle`. Build the image-only style separately so the
|
|
131
|
+
// shared box dimensions can be reused without a cast.
|
|
132
|
+
const imageStyle = {
|
|
133
|
+
width: '100%',
|
|
134
|
+
aspectRatio: imageRatio
|
|
135
|
+
};
|
|
136
|
+
const addFieldStyle = {
|
|
137
|
+
...imageBoxStyle,
|
|
138
|
+
backgroundColor: addFieldBg,
|
|
139
|
+
borderRadius: addFieldRadius,
|
|
140
|
+
alignItems: 'center',
|
|
141
|
+
justifyContent: 'center'
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// ---- Image / placeholder area --------------------------------------
|
|
145
|
+
const renderCardArea = () => {
|
|
146
|
+
if (cardSlot !== undefined && cardSlot !== null) {
|
|
147
|
+
const processed = (0, _reactUtils.cloneChildrenWithModes)(cardSlot, modes);
|
|
148
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
149
|
+
style: imageBoxStyle,
|
|
150
|
+
pointerEvents: "box-none",
|
|
151
|
+
children: processed.length === 1 ? processed[0] : processed
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
if (state === 'add') {
|
|
155
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
156
|
+
style: addFieldStyle,
|
|
157
|
+
accessibilityElementsHidden: true,
|
|
158
|
+
importantForAccessibility: "no",
|
|
159
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Icon.default, {
|
|
160
|
+
name: addIcon,
|
|
161
|
+
size: addIconSize,
|
|
162
|
+
color: addIconColor
|
|
163
|
+
})
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
const normalized = normalizeImageSource(imageSource);
|
|
167
|
+
if (normalized) {
|
|
168
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, {
|
|
169
|
+
source: normalized,
|
|
170
|
+
style: imageStyle,
|
|
171
|
+
resizeMode: "cover",
|
|
172
|
+
accessibilityElementsHidden: true,
|
|
173
|
+
importantForAccessibility: "no"
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
177
|
+
style: imageBoxStyle
|
|
178
|
+
});
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// ---- Pressable wiring ----------------------------------------------
|
|
182
|
+
// Keep React state out of the press path. `Pressable`'s style callback
|
|
183
|
+
// applies the pressed visual without a re-render.
|
|
184
|
+
const userHandlersRef = (0, _react.useRef)({});
|
|
185
|
+
const handlePressIn = (0, _react.useCallback)(e => {
|
|
186
|
+
userHandlersRef.current.onPressIn?.(e);
|
|
187
|
+
}, []);
|
|
188
|
+
const handlePressOut = (0, _react.useCallback)(e => {
|
|
189
|
+
userHandlersRef.current.onPressOut?.(e);
|
|
190
|
+
}, []);
|
|
191
|
+
const containerStyle = (0, _react.useMemo)(() => ({
|
|
192
|
+
width: '100%',
|
|
193
|
+
flexDirection: 'column',
|
|
194
|
+
alignItems: 'flex-start',
|
|
195
|
+
gap,
|
|
196
|
+
opacity: disabled ? 0.5 : 1
|
|
197
|
+
}), [gap, disabled]);
|
|
198
|
+
const pressableStyle = (0, _react.useCallback)(({
|
|
199
|
+
pressed
|
|
200
|
+
}) => [containerStyle, style, pressed && !disabled && onPress ? pressedOverlayStyle : null], [containerStyle, style, disabled, onPress]);
|
|
201
|
+
const showSubtitle = state === 'connected' && subtitle != null && subtitle !== '';
|
|
202
|
+
const a11yRole = onPress ? 'button' : undefined;
|
|
203
|
+
const a11yLabel = accessibilityLabel ?? title;
|
|
204
|
+
const content = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
205
|
+
children: [renderCardArea(), title != null && title !== '' || showSubtitle ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
206
|
+
style: {
|
|
207
|
+
width: '100%',
|
|
208
|
+
flexDirection: 'column',
|
|
209
|
+
alignItems: 'flex-start',
|
|
210
|
+
gap: textWrapGap
|
|
211
|
+
},
|
|
212
|
+
children: [title != null && title !== '' ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
213
|
+
style: titleStyle,
|
|
214
|
+
numberOfLines: 1,
|
|
215
|
+
children: title
|
|
216
|
+
}) : null, showSubtitle ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
217
|
+
style: subtitleStyle,
|
|
218
|
+
numberOfLines: 1,
|
|
219
|
+
children: subtitle
|
|
220
|
+
}) : null]
|
|
221
|
+
}) : null]
|
|
222
|
+
});
|
|
223
|
+
if (!onPress) {
|
|
224
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
225
|
+
accessibilityLabel: a11yLabel,
|
|
226
|
+
accessibilityHint: accessibilityHint,
|
|
227
|
+
style: [containerStyle, style],
|
|
228
|
+
children: content
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
|
|
232
|
+
accessibilityRole: a11yRole,
|
|
233
|
+
accessibilityLabel: a11yLabel,
|
|
234
|
+
accessibilityHint: accessibilityHint,
|
|
235
|
+
accessibilityState: {
|
|
236
|
+
disabled
|
|
237
|
+
},
|
|
238
|
+
onPress: disabled ? undefined : onPress,
|
|
239
|
+
disabled: disabled,
|
|
240
|
+
onPressIn: handlePressIn,
|
|
241
|
+
onPressOut: handlePressOut,
|
|
242
|
+
unstable_pressDelay: PRESS_DELAY,
|
|
243
|
+
style: pressableStyle,
|
|
244
|
+
children: content
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
var _default = exports.default = /*#__PURE__*/_react.default.memo(AccountCard);
|
|
@@ -4,37 +4,104 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.default = void 0;
|
|
7
|
-
var _react =
|
|
7
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
8
8
|
var _reactNative = require("react-native");
|
|
9
9
|
var _figmaVariablesResolver = require("../../design-tokens/figma-variables-resolver");
|
|
10
10
|
var _reactUtils = require("../../utils/react-utils");
|
|
11
11
|
var _IconButton = _interopRequireDefault(require("../IconButton/IconButton"));
|
|
12
12
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
13
13
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
14
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
15
|
+
const IS_WEB = _reactNative.Platform.OS === 'web';
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Yoga-safe stretch
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
//
|
|
21
|
+
// React Native (Yoga) interprets the `flex: 1` shorthand as
|
|
22
|
+
// { flexGrow: 1, flexShrink: 1, flexBasis: 0 }
|
|
23
|
+
// which is the *equal-share* variant. That is the correct math for what we
|
|
24
|
+
// want here (equal-width action buttons), BUT Yoga has a well-known foot-gun
|
|
25
|
+
// when this child sits inside a parent whose main-axis size hasn't been
|
|
26
|
+
// resolved yet on the first layout pass: the child collapses to 0 and the
|
|
27
|
+
// inner text gets clipped to "" before the parent ever measures.
|
|
28
|
+
//
|
|
29
|
+
// The defensive incantation used elsewhere in this codebase (see
|
|
30
|
+
// `CardCTA.leftWrap` and the `MediaCard.Header` fix in CHANGELOG.md) is to
|
|
31
|
+
// keep the equal-share math but explicitly clamp `minWidth` to 0 so Yoga
|
|
32
|
+
// always allows the child to participate in the shrink algorithm, even when
|
|
33
|
+
// the parent itself is in an undetermined state. Combined with explicit
|
|
34
|
+
// `flexGrow`/`flexShrink`/`flexBasis` (NOT the `flex` shorthand) this
|
|
35
|
+
// renders correctly on iOS, Android, and Web — and crucially never produces
|
|
36
|
+
// the "buttons render as empty pills" failure mode the previous version had
|
|
37
|
+
// on iOS dev clients.
|
|
38
|
+
const STRETCH_STYLE = {
|
|
39
|
+
flexGrow: 1,
|
|
40
|
+
flexShrink: 1,
|
|
41
|
+
flexBasis: 0,
|
|
42
|
+
minWidth: 0
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Platform-specific drop shadow. Web boxShadow can't go through
|
|
46
|
+
// Platform.select (RN's typed surface doesn't include it) so we keep it as a
|
|
47
|
+
// separate constant and append it below.
|
|
48
|
+
const NATIVE_SHADOW = _reactNative.Platform.select({
|
|
49
|
+
ios: {
|
|
50
|
+
shadowColor: '#0c0d10',
|
|
51
|
+
shadowOffset: {
|
|
52
|
+
width: 0,
|
|
53
|
+
height: -12
|
|
54
|
+
},
|
|
55
|
+
shadowOpacity: 0.16,
|
|
56
|
+
shadowRadius: 24
|
|
57
|
+
},
|
|
58
|
+
android: {
|
|
59
|
+
elevation: 16
|
|
60
|
+
},
|
|
61
|
+
default: {}
|
|
62
|
+
});
|
|
63
|
+
const WEB_SHADOW = IS_WEB ? {
|
|
64
|
+
boxShadow: '0px -12px 24px 0px rgba(12, 13, 16, 0.12), 0px -16px 48px 0px rgba(12, 13, 16, 0.16)'
|
|
65
|
+
} : null;
|
|
66
|
+
|
|
67
|
+
// The runtime token a slot child must equal (by reference) to be treated as
|
|
68
|
+
// an IconButton. `IconButton` is exported wrapped in `React.memo`, so the
|
|
69
|
+
// element.type identity comparison works for both `<IconButton />` from the
|
|
70
|
+
// same module and any `React.memo`-wrapped re-export. The fallback check
|
|
71
|
+
// (`type.type === IconButton`) catches one extra layer of `forwardRef` /
|
|
72
|
+
// `memo` wrapping which can happen when consumers re-export the component.
|
|
73
|
+
function isIconButtonElement(element) {
|
|
74
|
+
const t = element.type;
|
|
75
|
+
if (t === _IconButton.default) return true;
|
|
76
|
+
if (t && typeof t === 'object' && t.type === _IconButton.default) return true;
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
14
80
|
/**
|
|
15
|
-
* ActionFooter
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
81
|
+
* ActionFooter — a sticky bottom container for primary screen actions.
|
|
82
|
+
*
|
|
83
|
+
* Layout contract:
|
|
84
|
+
* - The outer container stretches horizontally (`alignSelf: 'stretch'`) so
|
|
85
|
+
* it fills the parent regardless of whether the parent is a flex column,
|
|
86
|
+
* a ScrollView contentContainer, or a plain View.
|
|
87
|
+
* - The inner slot is a single row sized by its tallest child. It does NOT
|
|
88
|
+
* use `flex: 1` — that previously caused the row to collapse to zero on
|
|
89
|
+
* the first Yoga pass on native, taking the button labels with it.
|
|
90
|
+
* - `IconButton` children keep their intrinsic square size.
|
|
91
|
+
* - Every other child is auto-stretched with the Yoga-safe stretch style
|
|
92
|
+
* above so two `<Button>` siblings render at equal width on iOS, Android,
|
|
93
|
+
* and Web.
|
|
94
|
+
*
|
|
95
|
+
* The `modes` prop is automatically pushed down to every slot child via
|
|
96
|
+
* {@link cloneChildrenWithModes}; explicit child-level modes win over the
|
|
97
|
+
* parent's modes.
|
|
98
|
+
*
|
|
30
99
|
* @example
|
|
31
100
|
* ```tsx
|
|
32
|
-
* // Basic usage - modes are automatically passed to all children.
|
|
33
|
-
* // Non-IconButton children (e.g., Button) are auto-stretched to fill.
|
|
34
101
|
* <ActionFooter modes={modes}>
|
|
35
102
|
* <IconButton iconName="ic_split" />
|
|
36
|
-
* <Button label="Request" />
|
|
37
|
-
* <Button label="Pay" />
|
|
103
|
+
* <Button label="Request" modes={{ AppearanceBrand: 'Secondary' }} />
|
|
104
|
+
* <Button label="Pay" modes={{ AppearanceBrand: 'Primary' }} />
|
|
38
105
|
* </ActionFooter>
|
|
39
106
|
* ```
|
|
40
107
|
*/
|
|
@@ -42,76 +109,74 @@ function ActionFooter({
|
|
|
42
109
|
children,
|
|
43
110
|
modes = _reactUtils.EMPTY_MODES,
|
|
44
111
|
style,
|
|
45
|
-
accessibilityLabel
|
|
112
|
+
accessibilityLabel
|
|
46
113
|
}) {
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
const containerStyle = {
|
|
73
|
-
backgroundColor,
|
|
74
|
-
paddingLeft: paddingHorizontal,
|
|
75
|
-
paddingRight: paddingHorizontal,
|
|
76
|
-
paddingTop,
|
|
77
|
-
paddingBottom,
|
|
78
|
-
...shadowStyle
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
// Slot container style for horizontal layout of action items
|
|
82
|
-
const slotStyle = {
|
|
83
|
-
flexDirection: 'row',
|
|
84
|
-
alignItems: 'flex-start',
|
|
85
|
-
gap,
|
|
86
|
-
flex: 1
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
// Web-specific box-shadow
|
|
90
|
-
const webShadow = _reactNative.Platform.OS === 'web' ? {
|
|
91
|
-
boxShadow: '0px -12px 24px 0px rgba(12, 13, 16, 0.12), 0px -16px 48px 0px rgba(12, 13, 16, 0.16)'
|
|
92
|
-
} : {};
|
|
93
|
-
const flatChildren = (0, _reactUtils.flattenChildren)(children);
|
|
94
|
-
const processedChildren = (0, _reactUtils.cloneChildrenWithModes)(flatChildren, modes);
|
|
95
|
-
const enhancedChildren = processedChildren.map((child, index) => {
|
|
96
|
-
if (! /*#__PURE__*/_react.default.isValidElement(child)) return child;
|
|
97
|
-
const element = child;
|
|
98
|
-
const isIconButton = element.type === _IconButton.default;
|
|
99
|
-
const stretchStyle = isIconButton ? undefined : {
|
|
100
|
-
flex: 1
|
|
114
|
+
// All token reads collapsed into a single useMemo keyed on `modes`. With
|
|
115
|
+
// the shared `EMPTY_MODES` default this resolves once for the common path
|
|
116
|
+
// and never re-allocates the container/slot style objects between renders.
|
|
117
|
+
const {
|
|
118
|
+
containerStyle,
|
|
119
|
+
slotStyle
|
|
120
|
+
} = (0, _react.useMemo)(() => {
|
|
121
|
+
const backgroundColor = (0, _figmaVariablesResolver.getVariableByName)('actionFooter/background', modes) ?? '#ffffff';
|
|
122
|
+
const gap = (0, _figmaVariablesResolver.getVariableByName)('actionFooter/gap', modes) ?? 8;
|
|
123
|
+
const paddingHorizontal = (0, _figmaVariablesResolver.getVariableByName)('actionFooter/padding/horizontal', modes) ?? 16;
|
|
124
|
+
const paddingTop = (0, _figmaVariablesResolver.getVariableByName)('actionFooter/padding/top', modes) ?? 10;
|
|
125
|
+
const paddingBottom = (0, _figmaVariablesResolver.getVariableByName)('actionFooter/padding/bottom', modes) ?? 41;
|
|
126
|
+
const container = {
|
|
127
|
+
// `alignSelf: 'stretch'` is the cross-platform way to ask "fill the
|
|
128
|
+
// parent's cross axis" — in the common case (column parent) this gives
|
|
129
|
+
// us full-width without the caller needing to pass `width: '100%'`.
|
|
130
|
+
alignSelf: 'stretch',
|
|
131
|
+
backgroundColor,
|
|
132
|
+
paddingLeft: paddingHorizontal,
|
|
133
|
+
paddingRight: paddingHorizontal,
|
|
134
|
+
paddingTop,
|
|
135
|
+
paddingBottom,
|
|
136
|
+
...NATIVE_SHADOW
|
|
101
137
|
};
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
138
|
+
const slot = {
|
|
139
|
+
flexDirection: 'row',
|
|
140
|
+
// Vertically center the IconButton against the slightly taller Buttons
|
|
141
|
+
// so the row reads as a single optical baseline.
|
|
142
|
+
alignItems: 'center',
|
|
143
|
+
gap
|
|
144
|
+
};
|
|
145
|
+
return {
|
|
146
|
+
containerStyle: container,
|
|
147
|
+
slotStyle: slot
|
|
148
|
+
};
|
|
149
|
+
}, [modes]);
|
|
150
|
+
|
|
151
|
+
// Process children once per (children, modes) tuple:
|
|
152
|
+
// 1. Flatten Fragments so each action is its own keyed sibling.
|
|
153
|
+
// 2. Push `modes` down so callers don't have to thread it manually.
|
|
154
|
+
// 3. Auto-stretch every non-IconButton with the Yoga-safe stretch style.
|
|
155
|
+
//
|
|
156
|
+
// The result identity is stable across re-renders when the inputs don't
|
|
157
|
+
// change, which keeps the `React.memo`-wrapped Button/IconButton children
|
|
158
|
+
// from re-rendering for no reason.
|
|
159
|
+
const enhancedChildren = (0, _react.useMemo)(() => {
|
|
160
|
+
const flat = (0, _reactUtils.flattenChildren)(children);
|
|
161
|
+
const withModes = (0, _reactUtils.cloneChildrenWithModes)(flat, modes);
|
|
162
|
+
return withModes.map((child, index) => {
|
|
163
|
+
if (! /*#__PURE__*/_react.default.isValidElement(child)) return child;
|
|
164
|
+
const element = child;
|
|
165
|
+
if (isIconButtonElement(element)) return element;
|
|
166
|
+
return /*#__PURE__*/_react.default.cloneElement(element, {
|
|
167
|
+
key: element.key ?? `action-footer-item-${index}`,
|
|
168
|
+
style: [STRETCH_STYLE, element.props.style]
|
|
169
|
+
});
|
|
105
170
|
});
|
|
106
|
-
});
|
|
171
|
+
}, [children, modes]);
|
|
107
172
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
108
|
-
style: [containerStyle,
|
|
173
|
+
style: [containerStyle, WEB_SHADOW, style],
|
|
109
174
|
accessibilityRole: "toolbar",
|
|
110
|
-
accessibilityLabel:
|
|
175
|
+
accessibilityLabel: accessibilityLabel,
|
|
111
176
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
112
177
|
style: slotStyle,
|
|
113
178
|
children: enhancedChildren
|
|
114
179
|
})
|
|
115
180
|
});
|
|
116
181
|
}
|
|
117
|
-
var _default = exports.default = ActionFooter;
|
|
182
|
+
var _default = exports.default = /*#__PURE__*/_react.default.memo(ActionFooter);
|