jfs-components 0.1.34 → 0.1.36
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 +14 -0
- package/D2C.md +82 -87
- package/lib/commonjs/components/LottiePlayer/LottiePlayer.js +5 -5
- package/lib/commonjs/components/LottiePlayer/LottiePlayer.web.js +2 -2
- package/lib/commonjs/components/PageHero/PageHero.js +73 -76
- package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +42204 -1
- package/lib/commonjs/design-tokens/figma-modes.generated.js +52 -37
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/module/components/LottiePlayer/LottiePlayer.js +5 -5
- package/lib/module/components/LottiePlayer/LottiePlayer.web.js +2 -2
- package/lib/module/components/PageHero/PageHero.js +73 -76
- package/lib/module/design-tokens/Coin Variables-variables-full.json +42204 -1
- package/lib/module/design-tokens/figma-modes.generated.js +52 -37
- package/lib/module/icons/registry.js +1 -1
- package/lib/typescript/src/components/LottiePlayer/LottiePlayer.d.ts +6 -6
- package/lib/typescript/src/components/PageHero/PageHero.d.ts +33 -25
- package/lib/typescript/src/design-tokens/figma-modes.generated.d.ts +14 -1
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +3 -1
- package/scripts/extract-figma-modes.js +150 -121
- package/src/components/LottiePlayer/LottiePlayer.tsx +8 -8
- package/src/components/LottiePlayer/LottiePlayer.web.tsx +2 -2
- package/src/components/PageHero/PageHero.tsx +142 -106
- package/src/design-tokens/Coin Variables-variables-full.json +42204 -1
- package/src/design-tokens/figma-modes.generated.ts +52 -37
- package/src/icons/registry.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,20 @@ 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.1.34] - 2026-07-21
|
|
8
|
+
|
|
9
|
+
- `jfs-extract-modes` now automatically loads `FIGMA_ACCESS_TOKEN` from the
|
|
10
|
+
consuming project's root `.env` without overriding a token exported in the
|
|
11
|
+
shell. `dotenv` is now an explicit runtime dependency.
|
|
12
|
+
- Simplified `D2C.md` for use as a Cursor slash command: clearer input handling,
|
|
13
|
+
ancestor-mode inheritance (`inheritedFromAncestors` + path merges), accurate
|
|
14
|
+
`unresolved` reasons, verify-before-invent component APIs, and practical
|
|
15
|
+
visual QA (fix implementation diffs; report environment-only diffs).
|
|
16
|
+
- Added focused tests for `.env` precedence and extracted mode diagnostics.
|
|
17
|
+
- Ignore local `.env` / `.env.*` secrets in git (keep `.env.example` allowed).
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
7
21
|
## [0.1.33] - 2026-07-20
|
|
8
22
|
|
|
9
23
|
- **Use [`D2C.md`](./D2C.md) for Design-to-Code.** This is the playbook we want agents and consumers to follow when implementing a Figma screen with `jfs-components`. It ships in the npm package (see `package.json` `files`) so it is available at `node_modules/jfs-components/D2C.md` after install. The workflow is mandatory end-to-end: Figma MCP (`get_design_context` + `get_screenshot`) → mode extraction via `npx jfs-extract-modes` (REST API; Dev seat / file-read token is enough) → build with exact modes → visual QA against the Figma screenshot. Point Cursor rules / agent instructions at this file rather than inventing a parallel D2C process.
|
package/D2C.md
CHANGED
|
@@ -1,118 +1,113 @@
|
|
|
1
|
-
# D2C — Design to Code
|
|
1
|
+
# D2C — Design to Code with jfs-components
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
order. Do not skip steps and do not improvise substitutes for them.
|
|
3
|
+
Implement the supplied Figma design using the installed `jfs-components`
|
|
4
|
+
package. Follow all four stages in order.
|
|
6
5
|
|
|
7
|
-
##
|
|
6
|
+
## Input
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
`<a>:<b>`).
|
|
8
|
+
Use the Figma URL supplied after `/D2C` or in the user's latest message. If no
|
|
9
|
+
Figma URL was supplied, ask for one before doing anything else.
|
|
12
10
|
|
|
13
|
-
1.
|
|
14
|
-
structure, layout, props, variants, text content, and Code Connect hints.
|
|
15
|
-
2. Call `get_screenshot` for the same node and keep the image. This is the
|
|
16
|
-
visual ground truth you will QA against in Step 4.
|
|
11
|
+
## 1. Read the design
|
|
17
12
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
1. Call the Figma MCP `get_design_context` for the linked node.
|
|
14
|
+
2. Call `get_screenshot` for the same node. Keep it as the visual reference for
|
|
15
|
+
final QA.
|
|
16
|
+
3. Follow Code Connect mappings, design annotations, and provided assets. If
|
|
17
|
+
Figma supplies an image or SVG URL, use it directly; do not create a
|
|
18
|
+
placeholder or install an icon package.
|
|
22
19
|
|
|
23
|
-
|
|
20
|
+
If the Figma MCP is unavailable or cannot read the node, report the blocker and
|
|
21
|
+
stop. Do not recreate the design from the URL or from assumptions.
|
|
24
22
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
## 2. Extract and resolve explicit modes
|
|
24
|
+
|
|
25
|
+
From the consuming project's root, run:
|
|
28
26
|
|
|
29
27
|
```bash
|
|
30
|
-
|
|
31
|
-
npx jfs-extract-modes "<the pasted Figma URL>" --out modes.json
|
|
28
|
+
npx jfs-extract-modes "<figma-url>" --out modes.json
|
|
32
29
|
```
|
|
33
30
|
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
The CLI automatically loads `FIGMA_ACCESS_TOKEN` from the project-root `.env`.
|
|
32
|
+
An already exported shell value takes precedence. The token only needs the
|
|
33
|
+
Figma `file_content:read` scope. Never print, log, or ask the user to paste the
|
|
34
|
+
token into chat.
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
If the token is missing, stop and direct the user to `[Open .env](.env)` and
|
|
37
|
+
add `FIGMA_ACCESS_TOKEN=` if needed. To create a token: in Figma's file browser,
|
|
38
|
+
open the account menu → **Settings** → **Security** → **Personal access
|
|
39
|
+
tokens** → **Generate new token**, choose an expiration, grant only
|
|
40
|
+
`file_content:read`, generate it, and copy it immediately into `.env`. Figma
|
|
41
|
+
shows the value only once. Use the
|
|
42
|
+
[official PAT documentation](https://developers.figma.com/docs/rest-api/personal-access-tokens/)
|
|
43
|
+
if the UI has changed. Never create or overwrite a token value yourself.
|
|
40
44
|
|
|
41
|
-
|
|
45
|
+
If the bin is unavailable in a nonstandard monorepo, run:
|
|
42
46
|
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
"modes": [
|
|
46
|
-
{ "layer": "Icon Button", "nodeId": "1:10638",
|
|
47
|
-
"path": "CC / Unsub > AppBar > Slot 2 > Icon Button",
|
|
48
|
-
"modes": { "Button / Size": "S", "Emphasis": "Low", "AppearanceBrand": "Neutral" } }
|
|
49
|
-
]
|
|
50
|
-
}
|
|
47
|
+
```bash
|
|
48
|
+
node node_modules/jfs-components/scripts/extract-figma-modes.js "<figma-url>" --out modes.json
|
|
51
49
|
```
|
|
52
50
|
|
|
53
|
-
|
|
51
|
+
Resolve the effective modes for each represented component as follows:
|
|
54
52
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
screen cannot be built faithfully. Do not guess missing modes.
|
|
53
|
+
1. Start with `inheritedFromAncestors`, when present.
|
|
54
|
+
2. Find every `modes` entry whose `path` is the component's path or an ancestor
|
|
55
|
+
of it.
|
|
56
|
+
3. Merge those entries from root to leaf; deeper entries override inherited
|
|
57
|
+
values.
|
|
58
|
+
4. Match by layer name and position, using `nodeId` to disambiguate duplicate
|
|
59
|
+
names.
|
|
63
60
|
|
|
64
|
-
|
|
61
|
+
If `unresolved` is present, report every item and its exact `reason`, then stop
|
|
62
|
+
before implementation because mode fidelity cannot be guaranteed:
|
|
65
63
|
|
|
66
|
-
|
|
67
|
-
library
|
|
64
|
+
- `local collection` means the design uses a file-local collection that the
|
|
65
|
+
packaged library data cannot resolve.
|
|
66
|
+
- `key not in variables JSON` or `mode not in collection` means the packaged
|
|
67
|
+
design-token JSON is out of date.
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
collection name → mode name, on exactly the layer (or subtree) the extractor
|
|
71
|
-
reported. There is zero tolerance for inconsistency here:
|
|
69
|
+
Do not guess or silently omit an unresolved mode.
|
|
72
70
|
|
|
73
|
-
|
|
74
|
-
`Emphasis: Low`).
|
|
75
|
-
- Do not drop modes that seem redundant — if the design sets
|
|
76
|
-
`AppearanceBrand: Neutral` on one Icon Button and `Appearance: Primary` on
|
|
77
|
-
another, those are different and intentional.
|
|
78
|
-
- Do not apply a child's modes to its parent or vice versa.
|
|
71
|
+
## 3. Implement
|
|
79
72
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
handling.
|
|
73
|
+
Before editing, inspect the installed package's exports/types and the consuming
|
|
74
|
+
app's existing screens. Reuse its file placement, navigation, styling, and
|
|
75
|
+
asset conventions. Never invent a `jfs-components` component or prop.
|
|
84
76
|
|
|
85
|
-
|
|
77
|
+
- Prefer mapped `jfs-components` over rebuilding them from primitives.
|
|
78
|
+
- Apply extracted collection/mode pairs exactly through each component's
|
|
79
|
+
`modes` prop.
|
|
80
|
+
- Put a mode on the closest code component that represents the reported Figma
|
|
81
|
+
layer or subtree. Preserve parent inheritance and child overrides; never
|
|
82
|
+
flatten a child override into its parent.
|
|
83
|
+
- Do not substitute similar-looking modes, remove apparently redundant modes,
|
|
84
|
+
or move modes between layers.
|
|
85
|
+
- Match Figma content, order, visibility, variants, spacing, and assets.
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
Run the consuming app's existing typecheck, tests, or build command relevant to
|
|
88
|
+
the changed screen and fix implementation errors.
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
screen, and take a screenshot (`mobile_take_screenshot`).
|
|
92
|
-
- **Web deployment:** use the browser tooling — navigate to the deployed/dev
|
|
93
|
-
URL and take a screenshot.
|
|
90
|
+
## 4. Render and visually verify
|
|
94
91
|
|
|
95
|
-
|
|
92
|
+
Render the implemented screen on the target platform and capture it with the
|
|
93
|
+
available mobile or browser tooling. Compare it with the Figma screenshot for:
|
|
96
94
|
|
|
97
|
-
1.
|
|
98
|
-
2.
|
|
99
|
-
3.
|
|
100
|
-
4.
|
|
101
|
-
5.
|
|
95
|
+
1. component size, variant, and emphasis;
|
|
96
|
+
2. color and appearance;
|
|
97
|
+
3. spacing, padding, alignment, and gaps;
|
|
98
|
+
4. text, wrapping, and truncation; and
|
|
99
|
+
5. icons, imagery, and layer visibility.
|
|
102
100
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
101
|
+
Fix every implementation-caused difference and capture the screen again.
|
|
102
|
+
Platform or environment differences that cannot be fixed in code, such as a
|
|
103
|
+
missing system font or renderer-specific antialiasing, must be identified
|
|
104
|
+
explicitly rather than hidden.
|
|
107
105
|
|
|
108
|
-
|
|
106
|
+
If no suitable render or screenshot tooling is available, do not claim visual
|
|
107
|
+
completion. Report what remains to be checked and give the exact manual run and
|
|
108
|
+
comparison steps.
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
|---|---|---|
|
|
112
|
-
| Design context + screenshot | Figma MCP | No |
|
|
113
|
-
| Mode extraction | `npx jfs-extract-modes` | No |
|
|
114
|
-
| Build | jfs-components | — |
|
|
115
|
-
| Visual QA loop | Mobile MCP or browser | No |
|
|
110
|
+
## Completion report
|
|
116
111
|
|
|
117
|
-
Report
|
|
118
|
-
|
|
112
|
+
Report the files changed, the effective modes applied to each component, the
|
|
113
|
+
validation commands and results, and the final visual comparison status.
|
|
@@ -27,8 +27,8 @@ function resolveSize(size, modes) {
|
|
|
27
27
|
height: size
|
|
28
28
|
};
|
|
29
29
|
if (size && typeof size === 'object') return size;
|
|
30
|
-
const width = Number((0, _figmaVariablesResolver.getVariableByName)('
|
|
31
|
-
const height = Number((0, _figmaVariablesResolver.getVariableByName)('
|
|
30
|
+
const width = Number((0, _figmaVariablesResolver.getVariableByName)('video/width', modes)) || DEFAULT_SIZE;
|
|
31
|
+
const height = Number((0, _figmaVariablesResolver.getVariableByName)('video/height', modes)) || DEFAULT_SIZE;
|
|
32
32
|
return {
|
|
33
33
|
width,
|
|
34
34
|
height
|
|
@@ -55,8 +55,8 @@ function resolveSize(size, modes) {
|
|
|
55
55
|
* webpack via platform extensions — same pattern as `MediaCard/GlassFill`.
|
|
56
56
|
*
|
|
57
57
|
* Token-driven sizing: when `size` is omitted, `LottiePlayer` reads
|
|
58
|
-
* `
|
|
59
|
-
* animation matches the surrounding component's `
|
|
58
|
+
* `video/width` and `video/height` from the Figma variables resolver, so the
|
|
59
|
+
* animation matches the surrounding component's `Video / Output` mode
|
|
60
60
|
* automatically. This is the same sizing contract `PageHero` and
|
|
61
61
|
* `LottieIntroBlock` use for their `media` slots.
|
|
62
62
|
*
|
|
@@ -67,7 +67,7 @@ function resolveSize(size, modes) {
|
|
|
67
67
|
*
|
|
68
68
|
* <LottiePlayer source={animation} /> // 117 × 117 (default)
|
|
69
69
|
* <LottiePlayer source={animation} size={70} /> // 70 × 70
|
|
70
|
-
* <LottiePlayer source={animation} modes={{ '
|
|
70
|
+
* <LottiePlayer source={animation} modes={{ 'Video / Output': 'S' }} /> // 20 × 20
|
|
71
71
|
* <PageHero media={<LottiePlayer source={animation} />} />
|
|
72
72
|
* ```
|
|
73
73
|
*/
|
|
@@ -18,8 +18,8 @@ function resolveSize(size, modes) {
|
|
|
18
18
|
height: size
|
|
19
19
|
};
|
|
20
20
|
if (size && typeof size === 'object') return size;
|
|
21
|
-
const width = Number((0, _figmaVariablesResolver.getVariableByName)('
|
|
22
|
-
const height = Number((0, _figmaVariablesResolver.getVariableByName)('
|
|
21
|
+
const width = Number((0, _figmaVariablesResolver.getVariableByName)('video/width', modes)) || DEFAULT_SIZE;
|
|
22
|
+
const height = Number((0, _figmaVariablesResolver.getVariableByName)('video/height', modes)) || DEFAULT_SIZE;
|
|
23
23
|
return {
|
|
24
24
|
width,
|
|
25
25
|
height
|
|
@@ -13,16 +13,24 @@ var _reactUtils = require("../../utils/react-utils");
|
|
|
13
13
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
14
14
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
15
15
|
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); }
|
|
16
|
+
// Figma media frame is 117×117 ("Video / Output" L). Used when the token
|
|
17
|
+
// collection is missing from a consumer's variables JSON so the media slot
|
|
18
|
+
// never collapses to 0.
|
|
19
|
+
const MEDIA_FALLBACK = 117;
|
|
16
20
|
/**
|
|
17
21
|
* PageHero displays a centered hero block typically used at the top of a page
|
|
18
22
|
* or feature screen. It contains an optional media slot (illustration / image
|
|
19
23
|
* / Lottie / SVG / video — consumer's choice), an eyebrow line, a large
|
|
20
|
-
* headline, an optional supporting line (e.g. price
|
|
21
|
-
* optional action button.
|
|
24
|
+
* headline, an optional supporting line (e.g. price), an optional body line
|
|
25
|
+
* (e.g. timeline), and an optional action button.
|
|
22
26
|
*
|
|
23
27
|
* All visual values are resolved from Figma design tokens via
|
|
24
|
-
* `getVariableByName
|
|
25
|
-
*
|
|
28
|
+
* `getVariableByName` (Figma node 4453:2348 — "Page Hero"). Text colors react
|
|
29
|
+
* to the `Page type` (`MainPage | SubPage | JioPlus`) and `Color Mode`
|
|
30
|
+
* (`Light | Dark`) collections; type scale reacts to `PageHero Size`
|
|
31
|
+
* (`M | S`); the media box reacts to `Video / Output` (`L | M | S`). Slots
|
|
32
|
+
* cascade the active `modes` to their children through
|
|
33
|
+
* `cloneChildrenWithModes`.
|
|
26
34
|
*
|
|
27
35
|
* @component
|
|
28
36
|
* @example
|
|
@@ -33,21 +41,19 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
|
|
|
33
41
|
* supportingText="₹999/year · ₹0 until 2027"
|
|
34
42
|
* buttonLabel="Renew for free"
|
|
35
43
|
* onButtonPress={() => navigate('Upgrade')}
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* imageSource={require('./assets/upgrade.png')}
|
|
39
|
-
* width={117}
|
|
40
|
-
* height={117}
|
|
41
|
-
* />
|
|
42
|
-
* }
|
|
44
|
+
* modes={{ 'Page type': 'JioPlus' }}
|
|
45
|
+
* media={<LottiePlayer source={animation} />}
|
|
43
46
|
* />
|
|
44
47
|
* ```
|
|
45
48
|
*/
|
|
46
49
|
function PageHero({
|
|
47
50
|
eyebrow = 'Upgrade to JioFinance+',
|
|
51
|
+
showEyebrow = true,
|
|
48
52
|
headline = 'Resume earning cashback, extra points, and 1% gold',
|
|
49
53
|
supportingText = '₹999/year · ₹0 until 2027',
|
|
50
54
|
showSupportingText = true,
|
|
55
|
+
body,
|
|
56
|
+
showBody = true,
|
|
51
57
|
buttonLabel = 'Renew for free',
|
|
52
58
|
onButtonPress,
|
|
53
59
|
showButton = true,
|
|
@@ -65,34 +71,49 @@ function PageHero({
|
|
|
65
71
|
...propModes
|
|
66
72
|
}), [globalModes, propModes]);
|
|
67
73
|
const gap = Number((0, _figmaVariablesResolver.getVariableByName)('PageHero/gap', modes));
|
|
68
|
-
const paddingHorizontal = Number((0, _figmaVariablesResolver.getVariableByName)('PageHero/padding/horizontal', modes));
|
|
69
74
|
const textWrapGap = Number((0, _figmaVariablesResolver.getVariableByName)('PageHero/textWrap/gap', modes));
|
|
70
75
|
|
|
71
|
-
// Media slot box —
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
const mediaWidth = Number((0, _figmaVariablesResolver.getVariableByName)('
|
|
75
|
-
const mediaHeight = Number((0, _figmaVariablesResolver.getVariableByName)('
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
76
|
+
// Media slot box — Figma frame 4540:7845 ("Video"), sized by the
|
|
77
|
+
// `Video / Output` collection (L=117, M=70, S=20). Falls back to 117 when
|
|
78
|
+
// the collection is absent so the slot never collapses to 0×0.
|
|
79
|
+
const mediaWidth = Number((0, _figmaVariablesResolver.getVariableByName)('video/width', modes)) || MEDIA_FALLBACK;
|
|
80
|
+
const mediaHeight = Number((0, _figmaVariablesResolver.getVariableByName)('video/height', modes)) || MEDIA_FALLBACK;
|
|
81
|
+
const eyebrowStyle = (0, _react.useMemo)(() => ({
|
|
82
|
+
color: (0, _figmaVariablesResolver.getVariableByName)('PageHero/eyebrow/color', modes),
|
|
83
|
+
fontFamily: (0, _figmaVariablesResolver.getVariableByName)('PageHero/eyebrow/fontFamily', modes),
|
|
84
|
+
fontSize: Number((0, _figmaVariablesResolver.getVariableByName)('PageHero/eyebrow/fontSize', modes)),
|
|
85
|
+
fontWeight: String((0, _figmaVariablesResolver.getVariableByName)('PageHero/eyebrow/fontWeight', modes)),
|
|
86
|
+
lineHeight: Number((0, _figmaVariablesResolver.getVariableByName)('PageHero/eyebrow/lineHeight', modes)),
|
|
87
|
+
textAlign: 'center'
|
|
88
|
+
}), [modes]);
|
|
89
|
+
const headlineStyle = (0, _react.useMemo)(() => ({
|
|
90
|
+
color: (0, _figmaVariablesResolver.getVariableByName)('PageHero/headline/color', modes),
|
|
91
|
+
fontFamily: (0, _figmaVariablesResolver.getVariableByName)('PageHero/headline/fontFamily', modes),
|
|
92
|
+
fontSize: Number((0, _figmaVariablesResolver.getVariableByName)('PageHero/headline/fontSize', modes)),
|
|
93
|
+
fontWeight: String((0, _figmaVariablesResolver.getVariableByName)('PageHero/headline/fontWeight', modes)),
|
|
94
|
+
lineHeight: Number((0, _figmaVariablesResolver.getVariableByName)('PageHero/headline/lineHeight', modes)),
|
|
95
|
+
textAlign: 'center',
|
|
96
|
+
width: '100%'
|
|
97
|
+
}), [modes]);
|
|
98
|
+
const supportingTextStyle = (0, _react.useMemo)(() => ({
|
|
99
|
+
color: (0, _figmaVariablesResolver.getVariableByName)('PageHero/supportingText/color', modes),
|
|
100
|
+
fontFamily: (0, _figmaVariablesResolver.getVariableByName)('PageHero/supportingText/fontFamily', modes),
|
|
101
|
+
fontSize: Number((0, _figmaVariablesResolver.getVariableByName)('PageHero/supportingText/fontSize', modes)),
|
|
102
|
+
fontWeight: String((0, _figmaVariablesResolver.getVariableByName)('PageHero/supportingText/fontWeight', modes)),
|
|
103
|
+
lineHeight: Number((0, _figmaVariablesResolver.getVariableByName)('PageHero/supportingText/lineHeight', modes)),
|
|
104
|
+
textAlign: 'center'
|
|
105
|
+
}), [modes]);
|
|
106
|
+
const bodyStyle = (0, _react.useMemo)(() => ({
|
|
107
|
+
color: (0, _figmaVariablesResolver.getVariableByName)('PageHero/body/color', modes),
|
|
108
|
+
fontFamily: (0, _figmaVariablesResolver.getVariableByName)('PageHero/body/fontFamily', modes),
|
|
109
|
+
fontSize: Number((0, _figmaVariablesResolver.getVariableByName)('PageHero/body/fontSize', modes)),
|
|
110
|
+
fontWeight: String((0, _figmaVariablesResolver.getVariableByName)('PageHero/body/fontWeight', modes)),
|
|
111
|
+
lineHeight: Number((0, _figmaVariablesResolver.getVariableByName)('PageHero/body/lineHeight', modes)),
|
|
112
|
+
textAlign: 'center'
|
|
113
|
+
}), [modes]);
|
|
92
114
|
const containerStyle = {
|
|
93
115
|
flexDirection: 'column',
|
|
94
116
|
alignItems: 'center',
|
|
95
|
-
paddingHorizontal,
|
|
96
117
|
gap,
|
|
97
118
|
width: '100%'
|
|
98
119
|
};
|
|
@@ -102,34 +123,6 @@ function PageHero({
|
|
|
102
123
|
gap: textWrapGap,
|
|
103
124
|
width: '100%'
|
|
104
125
|
};
|
|
105
|
-
const eyebrowStyle = {
|
|
106
|
-
color: eyebrowColor,
|
|
107
|
-
fontFamily: eyebrowFontFamily,
|
|
108
|
-
fontSize: eyebrowFontSize,
|
|
109
|
-
fontWeight: String(eyebrowFontWeight),
|
|
110
|
-
lineHeight: eyebrowLineHeight,
|
|
111
|
-
textAlign: 'center'
|
|
112
|
-
};
|
|
113
|
-
const headlineStyle = {
|
|
114
|
-
color: headlineColor,
|
|
115
|
-
fontFamily: headlineFontFamily,
|
|
116
|
-
fontSize: headlineFontSize,
|
|
117
|
-
fontWeight: String(headlineFontWeight),
|
|
118
|
-
lineHeight: headlineLineHeight,
|
|
119
|
-
textAlign: 'center',
|
|
120
|
-
width: '100%'
|
|
121
|
-
};
|
|
122
|
-
const supportingTextStyle = {
|
|
123
|
-
color: '#ffffff',
|
|
124
|
-
fontFamily: 'System',
|
|
125
|
-
fontSize: 12,
|
|
126
|
-
fontWeight: '500',
|
|
127
|
-
lineHeight: supportingTextLineHeight,
|
|
128
|
-
textAlign: 'center'
|
|
129
|
-
};
|
|
130
|
-
const buttonWrapStyle = {
|
|
131
|
-
width: '100%'
|
|
132
|
-
};
|
|
133
126
|
const buttonContent = (0, _react.useMemo)(() => {
|
|
134
127
|
if (buttonSlot !== undefined && buttonSlot !== null) {
|
|
135
128
|
return (0, _reactUtils.cloneChildrenWithModes)(buttonSlot, modes);
|
|
@@ -141,20 +134,16 @@ function PageHero({
|
|
|
141
134
|
label: buttonLabel,
|
|
142
135
|
onPress: onButtonPress,
|
|
143
136
|
modes: modes,
|
|
144
|
-
style:
|
|
137
|
+
style: FULL_WIDTH
|
|
145
138
|
});
|
|
146
|
-
// buttonWrapStyle is a literal object created above; it is intentionally
|
|
147
|
-
// omitted from deps because its identity changes on every render but its
|
|
148
|
-
// shape never does.
|
|
149
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
150
139
|
}, [buttonSlot, showButton, buttonLabel, onButtonPress, modes]);
|
|
151
140
|
|
|
152
141
|
// Sized container for the media slot. Always rendered when `media` is
|
|
153
|
-
// provided, so the slot has a predictable box (
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
// `clipsContent` so a slightly oversized illustration doesn't break
|
|
157
|
-
//
|
|
142
|
+
// provided, so the slot has a predictable box (Figma frame 4540:7845 —
|
|
143
|
+
// 117×117 at `Video / Output = L`) even if the inner element omits its own
|
|
144
|
+
// width/height. `overflow: 'hidden'` mirrors the Figma frame's
|
|
145
|
+
// `clipsContent` so a slightly oversized illustration doesn't break the
|
|
146
|
+
// centered layout.
|
|
158
147
|
const mediaContent = (0, _react.useMemo)(() => {
|
|
159
148
|
if (media === undefined || media === null) return null;
|
|
160
149
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
@@ -168,22 +157,30 @@ function PageHero({
|
|
|
168
157
|
children: (0, _reactUtils.cloneChildrenWithModes)(media, modes)
|
|
169
158
|
});
|
|
170
159
|
}, [media, mediaWidth, mediaHeight, modes]);
|
|
160
|
+
const hasEyebrow = showEyebrow && !!eyebrow;
|
|
161
|
+
const hasHeadline = !!headline;
|
|
171
162
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
172
163
|
style: [containerStyle, style],
|
|
173
164
|
testID: testID,
|
|
174
|
-
children: [mediaContent, /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
165
|
+
children: [mediaContent, hasEyebrow || hasHeadline ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
175
166
|
style: textWrapStyle,
|
|
176
|
-
children: [
|
|
167
|
+
children: [hasEyebrow ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
177
168
|
style: eyebrowStyle,
|
|
178
169
|
children: eyebrow
|
|
179
|
-
}) : null,
|
|
170
|
+
}) : null, hasHeadline ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
180
171
|
style: headlineStyle,
|
|
181
172
|
children: headline
|
|
182
173
|
}) : null]
|
|
183
|
-
}), showSupportingText && supportingText ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
174
|
+
}) : null, showSupportingText && supportingText ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
184
175
|
style: supportingTextStyle,
|
|
185
176
|
children: supportingText
|
|
177
|
+
}) : null, showBody && body ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
178
|
+
style: bodyStyle,
|
|
179
|
+
children: body
|
|
186
180
|
}) : null, buttonContent]
|
|
187
181
|
});
|
|
188
182
|
}
|
|
183
|
+
const FULL_WIDTH = {
|
|
184
|
+
width: '100%'
|
|
185
|
+
};
|
|
189
186
|
var _default = exports.default = PageHero;
|