jfs-components 0.1.34 → 0.1.35
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/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 +1 -1
- 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
|
@@ -23,10 +23,10 @@ export type LottiePlayerProps = {
|
|
|
23
23
|
* Override the rendered size. Pass a number for a square box, or
|
|
24
24
|
* `{ width, height }` for non-square.
|
|
25
25
|
*
|
|
26
|
-
* When omitted, size is resolved from the `
|
|
27
|
-
* design tokens (default `117 × 117`). The `
|
|
26
|
+
* When omitted, size is resolved from the `video/width` and `video/height`
|
|
27
|
+
* design tokens (default `117 × 117`). The `Video / Output` collection
|
|
28
28
|
* exposes `L | M | S` modes (117 / 70 / 20) — pass
|
|
29
|
-
* `modes={{ '
|
|
29
|
+
* `modes={{ 'Video / Output': 'M' }}` to render at 70×70, etc.
|
|
30
30
|
*/
|
|
31
31
|
size?: number | {
|
|
32
32
|
width: number;
|
|
@@ -64,8 +64,8 @@ export type LottiePlayerProps = {
|
|
|
64
64
|
* webpack via platform extensions — same pattern as `MediaCard/GlassFill`.
|
|
65
65
|
*
|
|
66
66
|
* Token-driven sizing: when `size` is omitted, `LottiePlayer` reads
|
|
67
|
-
* `
|
|
68
|
-
* animation matches the surrounding component's `
|
|
67
|
+
* `video/width` and `video/height` from the Figma variables resolver, so the
|
|
68
|
+
* animation matches the surrounding component's `Video / Output` mode
|
|
69
69
|
* automatically. This is the same sizing contract `PageHero` and
|
|
70
70
|
* `LottieIntroBlock` use for their `media` slots.
|
|
71
71
|
*
|
|
@@ -76,7 +76,7 @@ export type LottiePlayerProps = {
|
|
|
76
76
|
*
|
|
77
77
|
* <LottiePlayer source={animation} /> // 117 × 117 (default)
|
|
78
78
|
* <LottiePlayer source={animation} size={70} /> // 70 × 70
|
|
79
|
-
* <LottiePlayer source={animation} modes={{ '
|
|
79
|
+
* <LottiePlayer source={animation} modes={{ 'Video / Output': 'S' }} /> // 20 × 20
|
|
80
80
|
* <PageHero media={<LottiePlayer source={animation} />} />
|
|
81
81
|
* ```
|
|
82
82
|
*/
|
|
@@ -4,12 +4,18 @@ import type { Modes } from '../../design-tokens';
|
|
|
4
4
|
export type PageHeroProps = {
|
|
5
5
|
/** Small eyebrow text shown above the headline. */
|
|
6
6
|
eyebrow?: string;
|
|
7
|
+
/** Whether to render the eyebrow line. */
|
|
8
|
+
showEyebrow?: boolean;
|
|
7
9
|
/** Main headline text. Centered and bold. */
|
|
8
10
|
headline?: string;
|
|
9
|
-
/**
|
|
11
|
+
/** Supporting text shown directly below the headline (e.g. price). */
|
|
10
12
|
supportingText?: string;
|
|
11
13
|
/** Whether to render the supporting text. */
|
|
12
14
|
showSupportingText?: boolean;
|
|
15
|
+
/** Optional second supporting line shown below `supportingText`. */
|
|
16
|
+
body?: string;
|
|
17
|
+
/** Whether to render the body line. */
|
|
18
|
+
showBody?: boolean;
|
|
13
19
|
/** Label for the default action button. Ignored when `buttonSlot` is provided. */
|
|
14
20
|
buttonLabel?: string;
|
|
15
21
|
/** Press handler for the default action button. Ignored when `buttonSlot` is provided. */
|
|
@@ -17,9 +23,10 @@ export type PageHeroProps = {
|
|
|
17
23
|
/** Whether to render the default action button. Ignored when `buttonSlot` is provided. */
|
|
18
24
|
showButton?: boolean;
|
|
19
25
|
/**
|
|
20
|
-
* Optional slot to fully override the action button.
|
|
21
|
-
* When provided, `showButton`,
|
|
22
|
-
* `
|
|
26
|
+
* Optional slot to fully override the action button area (e.g. a
|
|
27
|
+
* `ButtonGroup` with several buttons). When provided, `showButton`,
|
|
28
|
+
* `buttonLabel`, and `onButtonPress` are ignored. `modes` are automatically
|
|
29
|
+
* cascaded into this slot.
|
|
23
30
|
*/
|
|
24
31
|
buttonSlot?: React.ReactNode;
|
|
25
32
|
/**
|
|
@@ -27,16 +34,18 @@ export type PageHeroProps = {
|
|
|
27
34
|
*
|
|
28
35
|
* Intentionally typed as `React.ReactNode` so the consumer can bring any
|
|
29
36
|
* renderer they like — `<Image />`, `<IconCapsule />`, a Lottie player
|
|
30
|
-
* (`
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
37
|
+
* (`LottiePlayer` / `lottie-react-native`), an `<SvgXml />` from
|
|
38
|
+
* `react-native-svg`, a `<Video />` from `react-native-video`, a gradient
|
|
39
|
+
* view, or a custom illustration. The library deliberately does NOT wrap
|
|
40
|
+
* Lottie / video runtimes (they require native modules + pod autolinking on
|
|
41
|
+
* every consumer's app), so PageHero just allocates a token-sized container
|
|
42
|
+
* and lets the slot render whatever it wants.
|
|
36
43
|
*
|
|
37
|
-
* The slot is rendered inside a `width × height` box driven by
|
|
38
|
-
* `
|
|
39
|
-
*
|
|
44
|
+
* The slot is rendered inside a `width × height` box driven by the
|
|
45
|
+
* `video/width` and `video/height` tokens from the `Video / Output`
|
|
46
|
+
* collection (`L | M | S` → 117 / 70 / 20; default `L` = 117×117).
|
|
47
|
+
* `modes` are automatically cascaded into the slot via
|
|
48
|
+
* `cloneChildrenWithModes`.
|
|
40
49
|
*/
|
|
41
50
|
media?: React.ReactNode;
|
|
42
51
|
/** Mode configuration for design-token theming. */
|
|
@@ -49,12 +58,16 @@ export type PageHeroProps = {
|
|
|
49
58
|
* PageHero displays a centered hero block typically used at the top of a page
|
|
50
59
|
* or feature screen. It contains an optional media slot (illustration / image
|
|
51
60
|
* / Lottie / SVG / video — consumer's choice), an eyebrow line, a large
|
|
52
|
-
* headline, an optional supporting line (e.g. price
|
|
53
|
-
* optional action button.
|
|
61
|
+
* headline, an optional supporting line (e.g. price), an optional body line
|
|
62
|
+
* (e.g. timeline), and an optional action button.
|
|
54
63
|
*
|
|
55
64
|
* All visual values are resolved from Figma design tokens via
|
|
56
|
-
* `getVariableByName
|
|
57
|
-
*
|
|
65
|
+
* `getVariableByName` (Figma node 4453:2348 — "Page Hero"). Text colors react
|
|
66
|
+
* to the `Page type` (`MainPage | SubPage | JioPlus`) and `Color Mode`
|
|
67
|
+
* (`Light | Dark`) collections; type scale reacts to `PageHero Size`
|
|
68
|
+
* (`M | S`); the media box reacts to `Video / Output` (`L | M | S`). Slots
|
|
69
|
+
* cascade the active `modes` to their children through
|
|
70
|
+
* `cloneChildrenWithModes`.
|
|
58
71
|
*
|
|
59
72
|
* @component
|
|
60
73
|
* @example
|
|
@@ -65,16 +78,11 @@ export type PageHeroProps = {
|
|
|
65
78
|
* supportingText="₹999/year · ₹0 until 2027"
|
|
66
79
|
* buttonLabel="Renew for free"
|
|
67
80
|
* onButtonPress={() => navigate('Upgrade')}
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* imageSource={require('./assets/upgrade.png')}
|
|
71
|
-
* width={117}
|
|
72
|
-
* height={117}
|
|
73
|
-
* />
|
|
74
|
-
* }
|
|
81
|
+
* modes={{ 'Page type': 'JioPlus' }}
|
|
82
|
+
* media={<LottiePlayer source={animation} />}
|
|
75
83
|
* />
|
|
76
84
|
* ```
|
|
77
85
|
*/
|
|
78
|
-
declare function PageHero({ eyebrow, headline, supportingText, showSupportingText, buttonLabel, onButtonPress, showButton, buttonSlot, media, modes: propModes, style, testID, }: PageHeroProps): import("react/jsx-runtime").JSX.Element;
|
|
86
|
+
declare function PageHero({ eyebrow, showEyebrow, headline, supportingText, showSupportingText, body, showBody, buttonLabel, onButtonPress, showButton, buttonSlot, media, modes: propModes, style, testID, }: PageHeroProps): import("react/jsx-runtime").JSX.Element;
|
|
79
87
|
export default PageHero;
|
|
80
88
|
//# sourceMappingURL=PageHero.d.ts.map
|
|
@@ -33,6 +33,7 @@ export declare const FIGMA_MODES: {
|
|
|
33
33
|
readonly "BankAccountCard / Output": readonly ["Default"];
|
|
34
34
|
readonly "Benefit Card": readonly ["Default"];
|
|
35
35
|
readonly Blur: readonly ["Default"];
|
|
36
|
+
readonly "Border Boolean": readonly ["False", "True"];
|
|
36
37
|
readonly "bottomNav / Output": readonly ["Default"];
|
|
37
38
|
readonly "BottomNavItem / Output": readonly ["Default"];
|
|
38
39
|
readonly "BottomNavItem / State": readonly ["Idle", "Active"];
|
|
@@ -47,6 +48,8 @@ export declare const FIGMA_MODES: {
|
|
|
47
48
|
readonly "Card / Output": readonly ["Default"];
|
|
48
49
|
readonly "Card Apperance": readonly ["Default", "Plain"];
|
|
49
50
|
readonly "Card Feedback / Output": readonly ["Default"];
|
|
51
|
+
readonly "Card Tab / output": readonly ["Default"];
|
|
52
|
+
readonly "Card Tab State": readonly ["Default", "Active"];
|
|
50
53
|
readonly "Card.Media": readonly ["Default"];
|
|
51
54
|
readonly "Card/CTA / Output": readonly ["Default"];
|
|
52
55
|
readonly "Card/Insight": readonly ["Default"];
|
|
@@ -112,6 +115,9 @@ export declare const FIGMA_MODES: {
|
|
|
112
115
|
readonly "Emphasis / DataViz": readonly ["High", "Medium", "Low"];
|
|
113
116
|
readonly "EmptyState / Output": readonly ["Default"];
|
|
114
117
|
readonly "ExpandableCheckbox / Output": readonly ["Default"];
|
|
118
|
+
readonly "Favorite Toggle Color": readonly ["Default", "Active"];
|
|
119
|
+
readonly "Favorite Toggle Size": readonly ["S", "M", "L"];
|
|
120
|
+
readonly "Favorite Toggle/ output": readonly ["Default"];
|
|
115
121
|
readonly "Feedback / Output": readonly ["Default"];
|
|
116
122
|
readonly "filterBar / Output": readonly ["Default"];
|
|
117
123
|
readonly "FinancialConditionCard / Output": readonly ["Default"];
|
|
@@ -122,9 +128,13 @@ export declare const FIGMA_MODES: {
|
|
|
122
128
|
readonly Gap: readonly ["S", "M", "L", "None"];
|
|
123
129
|
readonly "Gauge / Output": readonly ["Default"];
|
|
124
130
|
readonly "Grid / Output": readonly ["Default"];
|
|
131
|
+
readonly "Grid Gap": readonly ["S", "M"];
|
|
125
132
|
readonly "Handle Boolean": readonly ["True", "False"];
|
|
126
133
|
readonly "Heading / Output": readonly ["Default"];
|
|
127
134
|
readonly "Heading text": readonly ["Default", "Slot"];
|
|
135
|
+
readonly "hello Jio / Output": readonly ["Default"];
|
|
136
|
+
readonly "Hello Jio Input Size": readonly ["M", "L"];
|
|
137
|
+
readonly "Hello Jio Input State": readonly ["Idle", "Active"];
|
|
128
138
|
readonly "HeroSection / Output": readonly ["Default"];
|
|
129
139
|
readonly "Holdings card / Output": readonly ["Default"];
|
|
130
140
|
readonly "HStack / Output": readonly ["Default"];
|
|
@@ -175,6 +185,7 @@ export declare const FIGMA_MODES: {
|
|
|
175
185
|
readonly "Page Hero / Output": readonly ["Default"];
|
|
176
186
|
readonly "Page type": readonly ["MainPage", "SubPage", "JioPlus"];
|
|
177
187
|
readonly "PageHero / Output": readonly ["Default"];
|
|
188
|
+
readonly "PageHero Size": readonly ["M", "S"];
|
|
178
189
|
readonly "PaymnetFeedback / Output": readonly ["Default"];
|
|
179
190
|
readonly "PDP cc card": readonly ["Default"];
|
|
180
191
|
readonly peekOffset: readonly ["Medium", "Small", "Large", "None"];
|
|
@@ -196,6 +207,7 @@ export declare const FIGMA_MODES: {
|
|
|
196
207
|
readonly "RangeTrack / Output": readonly ["Default"];
|
|
197
208
|
readonly "Rating / Output": readonly ["Default"];
|
|
198
209
|
readonly "Rating Star / Output": readonly ["Default"];
|
|
210
|
+
readonly "Rating Star Size": readonly ["S", "M", "L"];
|
|
199
211
|
readonly "Rating Star State": readonly ["idle", "selected"];
|
|
200
212
|
readonly "RechargeCard / Output": readonly ["Default"];
|
|
201
213
|
readonly rotfl: readonly ["Default"];
|
|
@@ -240,7 +252,7 @@ export declare const FIGMA_MODES: {
|
|
|
240
252
|
readonly "Tabs / Output": readonly ["Default"];
|
|
241
253
|
readonly TestimonialsCard: readonly ["Default"];
|
|
242
254
|
readonly "Text / Output": readonly ["Default"];
|
|
243
|
-
readonly "Text Appearance": readonly ["Neutral", "Primary", "Secondary", "Tertiary"];
|
|
255
|
+
readonly "Text Appearance": readonly ["Neutral", "Primary", "Secondary", "Tertiary", "Success", "Warning", "Error"];
|
|
244
256
|
readonly "Text Sizes": readonly ["Medium", "Small", "Large"];
|
|
245
257
|
readonly "textInput / Output": readonly ["Default"];
|
|
246
258
|
readonly "TextSegment / Output": readonly ["Default"];
|
|
@@ -259,6 +271,7 @@ export declare const FIGMA_MODES: {
|
|
|
259
271
|
readonly "TransationDetails / Output": readonly ["Default"];
|
|
260
272
|
readonly "UPI Handle / Output": readonly ["Default"];
|
|
261
273
|
readonly "UPI Handle Image": readonly ["True", "False"];
|
|
274
|
+
readonly "valueBack metric": readonly ["Default"];
|
|
262
275
|
readonly "ValueBar / Output": readonly ["Default"];
|
|
263
276
|
readonly "Video / Output": readonly ["L", "M", "S"];
|
|
264
277
|
readonly "VStack / Output": readonly ["Default"];
|
|
@@ -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-07-
|
|
7
|
+
* Generated: 2026-07-20T12:46:13.819Z
|
|
8
8
|
*/
|
|
9
9
|
export declare const iconRegistry: Record<string, {
|
|
10
10
|
path: string;
|
package/package.json
CHANGED
|
@@ -29,10 +29,10 @@ export type LottiePlayerProps = {
|
|
|
29
29
|
* Override the rendered size. Pass a number for a square box, or
|
|
30
30
|
* `{ width, height }` for non-square.
|
|
31
31
|
*
|
|
32
|
-
* When omitted, size is resolved from the `
|
|
33
|
-
* design tokens (default `117 × 117`). The `
|
|
32
|
+
* When omitted, size is resolved from the `video/width` and `video/height`
|
|
33
|
+
* design tokens (default `117 × 117`). The `Video / Output` collection
|
|
34
34
|
* exposes `L | M | S` modes (117 / 70 / 20) — pass
|
|
35
|
-
* `modes={{ '
|
|
35
|
+
* `modes={{ 'Video / Output': 'M' }}` to render at 70×70, etc.
|
|
36
36
|
*/
|
|
37
37
|
size?: number | { width: number; height: number }
|
|
38
38
|
/** Play the animation on mount. Defaults to `true`. */
|
|
@@ -57,9 +57,9 @@ function resolveSize(
|
|
|
57
57
|
if (typeof size === 'number') return { width: size, height: size }
|
|
58
58
|
if (size && typeof size === 'object') return size
|
|
59
59
|
const width =
|
|
60
|
-
Number(getVariableByName('
|
|
60
|
+
Number(getVariableByName('video/width', modes)) || DEFAULT_SIZE
|
|
61
61
|
const height =
|
|
62
|
-
Number(getVariableByName('
|
|
62
|
+
Number(getVariableByName('video/height', modes)) || DEFAULT_SIZE
|
|
63
63
|
return { width, height }
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -83,8 +83,8 @@ function resolveSize(
|
|
|
83
83
|
* webpack via platform extensions — same pattern as `MediaCard/GlassFill`.
|
|
84
84
|
*
|
|
85
85
|
* Token-driven sizing: when `size` is omitted, `LottiePlayer` reads
|
|
86
|
-
* `
|
|
87
|
-
* animation matches the surrounding component's `
|
|
86
|
+
* `video/width` and `video/height` from the Figma variables resolver, so the
|
|
87
|
+
* animation matches the surrounding component's `Video / Output` mode
|
|
88
88
|
* automatically. This is the same sizing contract `PageHero` and
|
|
89
89
|
* `LottieIntroBlock` use for their `media` slots.
|
|
90
90
|
*
|
|
@@ -95,7 +95,7 @@ function resolveSize(
|
|
|
95
95
|
*
|
|
96
96
|
* <LottiePlayer source={animation} /> // 117 × 117 (default)
|
|
97
97
|
* <LottiePlayer source={animation} size={70} /> // 70 × 70
|
|
98
|
-
* <LottiePlayer source={animation} modes={{ '
|
|
98
|
+
* <LottiePlayer source={animation} modes={{ 'Video / Output': 'S' }} /> // 20 × 20
|
|
99
99
|
* <PageHero media={<LottiePlayer source={animation} />} />
|
|
100
100
|
* ```
|
|
101
101
|
*/
|
|
@@ -28,9 +28,9 @@ function resolveSize(
|
|
|
28
28
|
if (typeof size === 'number') return { width: size, height: size }
|
|
29
29
|
if (size && typeof size === 'object') return size
|
|
30
30
|
const width =
|
|
31
|
-
Number(getVariableByName('
|
|
31
|
+
Number(getVariableByName('video/width', modes)) || DEFAULT_SIZE
|
|
32
32
|
const height =
|
|
33
|
-
Number(getVariableByName('
|
|
33
|
+
Number(getVariableByName('video/height', modes)) || DEFAULT_SIZE
|
|
34
34
|
return { width, height }
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -12,15 +12,26 @@ import Button from '../Button/Button'
|
|
|
12
12
|
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
|
|
13
13
|
import type { Modes } from '../../design-tokens'
|
|
14
14
|
|
|
15
|
+
// Figma media frame is 117×117 ("Video / Output" L). Used when the token
|
|
16
|
+
// collection is missing from a consumer's variables JSON so the media slot
|
|
17
|
+
// never collapses to 0.
|
|
18
|
+
const MEDIA_FALLBACK = 117
|
|
19
|
+
|
|
15
20
|
export type PageHeroProps = {
|
|
16
21
|
/** Small eyebrow text shown above the headline. */
|
|
17
22
|
eyebrow?: string
|
|
23
|
+
/** Whether to render the eyebrow line. */
|
|
24
|
+
showEyebrow?: boolean
|
|
18
25
|
/** Main headline text. Centered and bold. */
|
|
19
26
|
headline?: string
|
|
20
|
-
/**
|
|
27
|
+
/** Supporting text shown directly below the headline (e.g. price). */
|
|
21
28
|
supportingText?: string
|
|
22
29
|
/** Whether to render the supporting text. */
|
|
23
30
|
showSupportingText?: boolean
|
|
31
|
+
/** Optional second supporting line shown below `supportingText`. */
|
|
32
|
+
body?: string
|
|
33
|
+
/** Whether to render the body line. */
|
|
34
|
+
showBody?: boolean
|
|
24
35
|
/** Label for the default action button. Ignored when `buttonSlot` is provided. */
|
|
25
36
|
buttonLabel?: string
|
|
26
37
|
/** Press handler for the default action button. Ignored when `buttonSlot` is provided. */
|
|
@@ -28,9 +39,10 @@ export type PageHeroProps = {
|
|
|
28
39
|
/** Whether to render the default action button. Ignored when `buttonSlot` is provided. */
|
|
29
40
|
showButton?: boolean
|
|
30
41
|
/**
|
|
31
|
-
* Optional slot to fully override the action button.
|
|
32
|
-
* When provided, `showButton`,
|
|
33
|
-
* `
|
|
42
|
+
* Optional slot to fully override the action button area (e.g. a
|
|
43
|
+
* `ButtonGroup` with several buttons). When provided, `showButton`,
|
|
44
|
+
* `buttonLabel`, and `onButtonPress` are ignored. `modes` are automatically
|
|
45
|
+
* cascaded into this slot.
|
|
34
46
|
*/
|
|
35
47
|
buttonSlot?: React.ReactNode
|
|
36
48
|
/**
|
|
@@ -38,16 +50,18 @@ export type PageHeroProps = {
|
|
|
38
50
|
*
|
|
39
51
|
* Intentionally typed as `React.ReactNode` so the consumer can bring any
|
|
40
52
|
* renderer they like — `<Image />`, `<IconCapsule />`, a Lottie player
|
|
41
|
-
* (`
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
53
|
+
* (`LottiePlayer` / `lottie-react-native`), an `<SvgXml />` from
|
|
54
|
+
* `react-native-svg`, a `<Video />` from `react-native-video`, a gradient
|
|
55
|
+
* view, or a custom illustration. The library deliberately does NOT wrap
|
|
56
|
+
* Lottie / video runtimes (they require native modules + pod autolinking on
|
|
57
|
+
* every consumer's app), so PageHero just allocates a token-sized container
|
|
58
|
+
* and lets the slot render whatever it wants.
|
|
47
59
|
*
|
|
48
|
-
* The slot is rendered inside a `width × height` box driven by
|
|
49
|
-
* `
|
|
50
|
-
*
|
|
60
|
+
* The slot is rendered inside a `width × height` box driven by the
|
|
61
|
+
* `video/width` and `video/height` tokens from the `Video / Output`
|
|
62
|
+
* collection (`L | M | S` → 117 / 70 / 20; default `L` = 117×117).
|
|
63
|
+
* `modes` are automatically cascaded into the slot via
|
|
64
|
+
* `cloneChildrenWithModes`.
|
|
51
65
|
*/
|
|
52
66
|
media?: React.ReactNode
|
|
53
67
|
/** Mode configuration for design-token theming. */
|
|
@@ -61,12 +75,16 @@ export type PageHeroProps = {
|
|
|
61
75
|
* PageHero displays a centered hero block typically used at the top of a page
|
|
62
76
|
* or feature screen. It contains an optional media slot (illustration / image
|
|
63
77
|
* / Lottie / SVG / video — consumer's choice), an eyebrow line, a large
|
|
64
|
-
* headline, an optional supporting line (e.g. price
|
|
65
|
-
* optional action button.
|
|
78
|
+
* headline, an optional supporting line (e.g. price), an optional body line
|
|
79
|
+
* (e.g. timeline), and an optional action button.
|
|
66
80
|
*
|
|
67
81
|
* All visual values are resolved from Figma design tokens via
|
|
68
|
-
* `getVariableByName
|
|
69
|
-
*
|
|
82
|
+
* `getVariableByName` (Figma node 4453:2348 — "Page Hero"). Text colors react
|
|
83
|
+
* to the `Page type` (`MainPage | SubPage | JioPlus`) and `Color Mode`
|
|
84
|
+
* (`Light | Dark`) collections; type scale reacts to `PageHero Size`
|
|
85
|
+
* (`M | S`); the media box reacts to `Video / Output` (`L | M | S`). Slots
|
|
86
|
+
* cascade the active `modes` to their children through
|
|
87
|
+
* `cloneChildrenWithModes`.
|
|
70
88
|
*
|
|
71
89
|
* @component
|
|
72
90
|
* @example
|
|
@@ -77,21 +95,19 @@ export type PageHeroProps = {
|
|
|
77
95
|
* supportingText="₹999/year · ₹0 until 2027"
|
|
78
96
|
* buttonLabel="Renew for free"
|
|
79
97
|
* onButtonPress={() => navigate('Upgrade')}
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* imageSource={require('./assets/upgrade.png')}
|
|
83
|
-
* width={117}
|
|
84
|
-
* height={117}
|
|
85
|
-
* />
|
|
86
|
-
* }
|
|
98
|
+
* modes={{ 'Page type': 'JioPlus' }}
|
|
99
|
+
* media={<LottiePlayer source={animation} />}
|
|
87
100
|
* />
|
|
88
101
|
* ```
|
|
89
102
|
*/
|
|
90
103
|
function PageHero({
|
|
91
104
|
eyebrow = 'Upgrade to JioFinance+',
|
|
105
|
+
showEyebrow = true,
|
|
92
106
|
headline = 'Resume earning cashback, extra points, and 1% gold',
|
|
93
107
|
supportingText = '₹999/year · ₹0 until 2027',
|
|
94
108
|
showSupportingText = true,
|
|
109
|
+
body,
|
|
110
|
+
showBody = true,
|
|
95
111
|
buttonLabel = 'Renew for free',
|
|
96
112
|
onButtonPress,
|
|
97
113
|
showButton = true,
|
|
@@ -108,51 +124,99 @@ function PageHero({
|
|
|
108
124
|
)
|
|
109
125
|
|
|
110
126
|
const gap = Number(getVariableByName('PageHero/gap', modes))
|
|
111
|
-
const
|
|
112
|
-
Number(getVariableByName('PageHero/padding/horizontal', modes))
|
|
127
|
+
const textWrapGap = Number(getVariableByName('PageHero/textWrap/gap', modes))
|
|
113
128
|
|
|
114
|
-
|
|
115
|
-
|
|
129
|
+
// Media slot box — Figma frame 4540:7845 ("Video"), sized by the
|
|
130
|
+
// `Video / Output` collection (L=117, M=70, S=20). Falls back to 117 when
|
|
131
|
+
// the collection is absent so the slot never collapses to 0×0.
|
|
132
|
+
const mediaWidth =
|
|
133
|
+
Number(getVariableByName('video/width', modes)) || MEDIA_FALLBACK
|
|
134
|
+
const mediaHeight =
|
|
135
|
+
Number(getVariableByName('video/height', modes)) || MEDIA_FALLBACK
|
|
116
136
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
137
|
+
const eyebrowStyle = useMemo<TextStyle>(
|
|
138
|
+
() => ({
|
|
139
|
+
color: getVariableByName('PageHero/eyebrow/color', modes) as string,
|
|
140
|
+
fontFamily: getVariableByName(
|
|
141
|
+
'PageHero/eyebrow/fontFamily',
|
|
142
|
+
modes
|
|
143
|
+
) as string,
|
|
144
|
+
fontSize: Number(getVariableByName('PageHero/eyebrow/fontSize', modes)),
|
|
145
|
+
fontWeight: String(
|
|
146
|
+
getVariableByName('PageHero/eyebrow/fontWeight', modes)
|
|
147
|
+
) as TextStyle['fontWeight'],
|
|
148
|
+
lineHeight: Number(
|
|
149
|
+
getVariableByName('PageHero/eyebrow/lineHeight', modes)
|
|
150
|
+
),
|
|
151
|
+
textAlign: 'center',
|
|
152
|
+
}),
|
|
153
|
+
[modes]
|
|
154
|
+
)
|
|
122
155
|
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
156
|
+
const headlineStyle = useMemo<TextStyle>(
|
|
157
|
+
() => ({
|
|
158
|
+
color: getVariableByName('PageHero/headline/color', modes) as string,
|
|
159
|
+
fontFamily: getVariableByName(
|
|
160
|
+
'PageHero/headline/fontFamily',
|
|
161
|
+
modes
|
|
162
|
+
) as string,
|
|
163
|
+
fontSize: Number(getVariableByName('PageHero/headline/fontSize', modes)),
|
|
164
|
+
fontWeight: String(
|
|
165
|
+
getVariableByName('PageHero/headline/fontWeight', modes)
|
|
166
|
+
) as TextStyle['fontWeight'],
|
|
167
|
+
lineHeight: Number(
|
|
168
|
+
getVariableByName('PageHero/headline/lineHeight', modes)
|
|
169
|
+
),
|
|
170
|
+
textAlign: 'center',
|
|
171
|
+
width: '100%',
|
|
172
|
+
}),
|
|
173
|
+
[modes]
|
|
174
|
+
)
|
|
133
175
|
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
176
|
+
const supportingTextStyle = useMemo<TextStyle>(
|
|
177
|
+
() => ({
|
|
178
|
+
color: getVariableByName(
|
|
179
|
+
'PageHero/supportingText/color',
|
|
180
|
+
modes
|
|
181
|
+
) as string,
|
|
182
|
+
fontFamily: getVariableByName(
|
|
183
|
+
'PageHero/supportingText/fontFamily',
|
|
184
|
+
modes
|
|
185
|
+
) as string,
|
|
186
|
+
fontSize: Number(
|
|
187
|
+
getVariableByName('PageHero/supportingText/fontSize', modes)
|
|
188
|
+
),
|
|
189
|
+
fontWeight: String(
|
|
190
|
+
getVariableByName('PageHero/supportingText/fontWeight', modes)
|
|
191
|
+
) as TextStyle['fontWeight'],
|
|
192
|
+
lineHeight: Number(
|
|
193
|
+
getVariableByName('PageHero/supportingText/lineHeight', modes)
|
|
194
|
+
),
|
|
195
|
+
textAlign: 'center',
|
|
196
|
+
}),
|
|
197
|
+
[modes]
|
|
198
|
+
)
|
|
144
199
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
200
|
+
const bodyStyle = useMemo<TextStyle>(
|
|
201
|
+
() => ({
|
|
202
|
+
color: getVariableByName('PageHero/body/color', modes) as string,
|
|
203
|
+
fontFamily: getVariableByName(
|
|
204
|
+
'PageHero/body/fontFamily',
|
|
205
|
+
modes
|
|
206
|
+
) as string,
|
|
207
|
+
fontSize: Number(getVariableByName('PageHero/body/fontSize', modes)),
|
|
208
|
+
fontWeight: String(
|
|
209
|
+
getVariableByName('PageHero/body/fontWeight', modes)
|
|
210
|
+
) as TextStyle['fontWeight'],
|
|
211
|
+
lineHeight: Number(getVariableByName('PageHero/body/lineHeight', modes)),
|
|
212
|
+
textAlign: 'center',
|
|
213
|
+
}),
|
|
214
|
+
[modes]
|
|
215
|
+
)
|
|
151
216
|
|
|
152
217
|
const containerStyle: ViewStyle = {
|
|
153
218
|
flexDirection: 'column',
|
|
154
219
|
alignItems: 'center',
|
|
155
|
-
paddingHorizontal,
|
|
156
220
|
gap,
|
|
157
221
|
width: '100%',
|
|
158
222
|
}
|
|
@@ -164,38 +228,6 @@ function PageHero({
|
|
|
164
228
|
width: '100%',
|
|
165
229
|
}
|
|
166
230
|
|
|
167
|
-
const eyebrowStyle: TextStyle = {
|
|
168
|
-
color: eyebrowColor as string,
|
|
169
|
-
fontFamily: eyebrowFontFamily as string,
|
|
170
|
-
fontSize: eyebrowFontSize,
|
|
171
|
-
fontWeight: String(eyebrowFontWeight) as TextStyle['fontWeight'],
|
|
172
|
-
lineHeight: eyebrowLineHeight,
|
|
173
|
-
textAlign: 'center',
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const headlineStyle: TextStyle = {
|
|
177
|
-
color: headlineColor as string,
|
|
178
|
-
fontFamily: headlineFontFamily as string,
|
|
179
|
-
fontSize: headlineFontSize,
|
|
180
|
-
fontWeight: String(headlineFontWeight) as TextStyle['fontWeight'],
|
|
181
|
-
lineHeight: headlineLineHeight,
|
|
182
|
-
textAlign: 'center',
|
|
183
|
-
width: '100%',
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const supportingTextStyle: TextStyle = {
|
|
187
|
-
color: '#ffffff',
|
|
188
|
-
fontFamily: 'System',
|
|
189
|
-
fontSize: 12,
|
|
190
|
-
fontWeight: '500',
|
|
191
|
-
lineHeight: supportingTextLineHeight,
|
|
192
|
-
textAlign: 'center',
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const buttonWrapStyle: ViewStyle = {
|
|
196
|
-
width: '100%',
|
|
197
|
-
}
|
|
198
|
-
|
|
199
231
|
const buttonContent = useMemo<React.ReactNode>(() => {
|
|
200
232
|
if (buttonSlot !== undefined && buttonSlot !== null) {
|
|
201
233
|
return cloneChildrenWithModes(buttonSlot, modes)
|
|
@@ -208,21 +240,17 @@ function PageHero({
|
|
|
208
240
|
label={buttonLabel}
|
|
209
241
|
onPress={onButtonPress}
|
|
210
242
|
modes={modes}
|
|
211
|
-
style={
|
|
243
|
+
style={FULL_WIDTH}
|
|
212
244
|
/>
|
|
213
245
|
)
|
|
214
|
-
// buttonWrapStyle is a literal object created above; it is intentionally
|
|
215
|
-
// omitted from deps because its identity changes on every render but its
|
|
216
|
-
// shape never does.
|
|
217
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
218
246
|
}, [buttonSlot, showButton, buttonLabel, onButtonPress, modes])
|
|
219
247
|
|
|
220
248
|
// Sized container for the media slot. Always rendered when `media` is
|
|
221
|
-
// provided, so the slot has a predictable box (
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
// `clipsContent` so a slightly oversized illustration doesn't break
|
|
225
|
-
//
|
|
249
|
+
// provided, so the slot has a predictable box (Figma frame 4540:7845 —
|
|
250
|
+
// 117×117 at `Video / Output = L`) even if the inner element omits its own
|
|
251
|
+
// width/height. `overflow: 'hidden'` mirrors the Figma frame's
|
|
252
|
+
// `clipsContent` so a slightly oversized illustration doesn't break the
|
|
253
|
+
// centered layout.
|
|
226
254
|
const mediaContent = useMemo<React.ReactNode>(() => {
|
|
227
255
|
if (media === undefined || media === null) return null
|
|
228
256
|
return (
|
|
@@ -240,19 +268,27 @@ function PageHero({
|
|
|
240
268
|
)
|
|
241
269
|
}, [media, mediaWidth, mediaHeight, modes])
|
|
242
270
|
|
|
271
|
+
const hasEyebrow = showEyebrow && !!eyebrow
|
|
272
|
+
const hasHeadline = !!headline
|
|
273
|
+
|
|
243
274
|
return (
|
|
244
275
|
<View style={[containerStyle, style]} testID={testID}>
|
|
245
276
|
{mediaContent}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
277
|
+
{hasEyebrow || hasHeadline ? (
|
|
278
|
+
<View style={textWrapStyle}>
|
|
279
|
+
{hasEyebrow ? <Text style={eyebrowStyle}>{eyebrow}</Text> : null}
|
|
280
|
+
{hasHeadline ? <Text style={headlineStyle}>{headline}</Text> : null}
|
|
281
|
+
</View>
|
|
282
|
+
) : null}
|
|
250
283
|
{showSupportingText && supportingText ? (
|
|
251
284
|
<Text style={supportingTextStyle}>{supportingText}</Text>
|
|
252
285
|
) : null}
|
|
286
|
+
{showBody && body ? <Text style={bodyStyle}>{body}</Text> : null}
|
|
253
287
|
{buttonContent}
|
|
254
288
|
</View>
|
|
255
289
|
)
|
|
256
290
|
}
|
|
257
291
|
|
|
292
|
+
const FULL_WIDTH: ViewStyle = { width: '100%' }
|
|
293
|
+
|
|
258
294
|
export default PageHero
|