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.
@@ -22,8 +22,8 @@ function resolveSize(size, modes) {
22
22
  height: size
23
23
  };
24
24
  if (size && typeof size === 'object') return size;
25
- const width = Number(getVariableByName('media/width', modes));
26
- const height = Number(getVariableByName('media/height', modes));
25
+ const width = Number(getVariableByName('video/width', modes)) || DEFAULT_SIZE;
26
+ const height = Number(getVariableByName('video/height', modes)) || DEFAULT_SIZE;
27
27
  return {
28
28
  width,
29
29
  height
@@ -50,8 +50,8 @@ function resolveSize(size, modes) {
50
50
  * webpack via platform extensions — same pattern as `MediaCard/GlassFill`.
51
51
  *
52
52
  * Token-driven sizing: when `size` is omitted, `LottiePlayer` reads
53
- * `media/width` and `media/height` from the Figma variables resolver, so the
54
- * animation matches the surrounding component's `Media / Output` mode
53
+ * `video/width` and `video/height` from the Figma variables resolver, so the
54
+ * animation matches the surrounding component's `Video / Output` mode
55
55
  * automatically. This is the same sizing contract `PageHero` and
56
56
  * `LottieIntroBlock` use for their `media` slots.
57
57
  *
@@ -62,7 +62,7 @@ function resolveSize(size, modes) {
62
62
  *
63
63
  * <LottiePlayer source={animation} /> // 117 × 117 (default)
64
64
  * <LottiePlayer source={animation} size={70} /> // 70 × 70
65
- * <LottiePlayer source={animation} modes={{ 'Media / Output': 'S' }} /> // 20 × 20
65
+ * <LottiePlayer source={animation} modes={{ 'Video / Output': 'S' }} /> // 20 × 20
66
66
  * <PageHero media={<LottiePlayer source={animation} />} />
67
67
  * ```
68
68
  */
@@ -13,8 +13,8 @@ function resolveSize(size, modes) {
13
13
  height: size
14
14
  };
15
15
  if (size && typeof size === 'object') return size;
16
- const width = Number(getVariableByName('media/width', modes));
17
- const height = Number(getVariableByName('media/height', modes));
16
+ const width = Number(getVariableByName('video/width', modes)) || DEFAULT_SIZE;
17
+ const height = Number(getVariableByName('video/height', modes)) || DEFAULT_SIZE;
18
18
  return {
19
19
  width,
20
20
  height
@@ -7,16 +7,24 @@ import { useTokens } from '../../design-tokens/JFSThemeProvider';
7
7
  import Button from '../Button/Button';
8
8
  import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils';
9
9
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
+ // Figma media frame is 117×117 ("Video / Output" L). Used when the token
11
+ // collection is missing from a consumer's variables JSON so the media slot
12
+ // never collapses to 0.
13
+ const MEDIA_FALLBACK = 117;
10
14
  /**
11
15
  * PageHero displays a centered hero block typically used at the top of a page
12
16
  * or feature screen. It contains an optional media slot (illustration / image
13
17
  * / Lottie / SVG / video — consumer's choice), an eyebrow line, a large
14
- * headline, an optional supporting line (e.g. price / timeline), and an
15
- * optional action button.
18
+ * headline, an optional supporting line (e.g. price), an optional body line
19
+ * (e.g. timeline), and an optional action button.
16
20
  *
17
21
  * All visual values are resolved from Figma design tokens via
18
- * `getVariableByName`. Slots cascade the active `modes` to their children
19
- * through `cloneChildrenWithModes`.
22
+ * `getVariableByName` (Figma node 4453:2348 "Page Hero"). Text colors react
23
+ * to the `Page type` (`MainPage | SubPage | JioPlus`) and `Color Mode`
24
+ * (`Light | Dark`) collections; type scale reacts to `PageHero Size`
25
+ * (`M | S`); the media box reacts to `Video / Output` (`L | M | S`). Slots
26
+ * cascade the active `modes` to their children through
27
+ * `cloneChildrenWithModes`.
20
28
  *
21
29
  * @component
22
30
  * @example
@@ -27,21 +35,19 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
27
35
  * supportingText="₹999/year · ₹0 until 2027"
28
36
  * buttonLabel="Renew for free"
29
37
  * onButtonPress={() => navigate('Upgrade')}
30
- * media={
31
- * <Image
32
- * imageSource={require('./assets/upgrade.png')}
33
- * width={117}
34
- * height={117}
35
- * />
36
- * }
38
+ * modes={{ 'Page type': 'JioPlus' }}
39
+ * media={<LottiePlayer source={animation} />}
37
40
  * />
38
41
  * ```
39
42
  */
40
43
  function PageHero({
41
44
  eyebrow = 'Upgrade to JioFinance+',
45
+ showEyebrow = true,
42
46
  headline = 'Resume earning cashback, extra points, and 1% gold',
43
47
  supportingText = '₹999/year · ₹0 until 2027',
44
48
  showSupportingText = true,
49
+ body,
50
+ showBody = true,
45
51
  buttonLabel = 'Renew for free',
46
52
  onButtonPress,
47
53
  showButton = true,
@@ -59,34 +65,49 @@ function PageHero({
59
65
  ...propModes
60
66
  }), [globalModes, propModes]);
61
67
  const gap = Number(getVariableByName('PageHero/gap', modes));
62
- const paddingHorizontal = Number(getVariableByName('PageHero/padding/horizontal', modes));
63
68
  const textWrapGap = Number(getVariableByName('PageHero/textWrap/gap', modes));
64
69
 
65
- // Media slot box — matches the 117×117 frame in Figma (node 4540:7845).
66
- // Tokens fall back to 117 when not defined in the variables collection,
67
- // so the layout stays stable on consumers that haven't tokenized this yet.
68
- const mediaWidth = Number(getVariableByName('media/width', modes));
69
- const mediaHeight = Number(getVariableByName('media/height', modes));
70
- const eyebrowColor = getVariableByName('PageHero/eyebrow/color', modes);
71
- const eyebrowFontFamily = getVariableByName('PageHero/eyebrow/fontFamily', modes);
72
- const eyebrowFontSize = Number(getVariableByName('PageHero/eyebrow/fontSize', modes));
73
- const eyebrowFontWeight = getVariableByName('PageHero/eyebrow/fontWeight', modes);
74
- const eyebrowLineHeight = Number(getVariableByName('PageHero/eyebrow/lineHeight', modes));
75
- const headlineColor = getVariableByName('PageHero/headline/color', modes);
76
- const headlineFontFamily = getVariableByName('PageHero/headline/fontFamily', modes);
77
- const headlineFontSize = Number(getVariableByName('PageHero/headline/fontSize', modes));
78
- const headlineFontWeight = getVariableByName('PageHero/headline/fontWeight', modes);
79
- const headlineLineHeight = Number(getVariableByName('PageHero/headline/lineHeight', modes));
80
-
81
- // Only `lineHeight` is tokenized for the supporting text in the Figma source.
82
- // Color, font size and weight are inline literals in the design (12px medium
83
- // white) — we mirror that here so the visual stays faithful when no token
84
- // exists.
85
- const supportingTextLineHeight = Number(getVariableByName('PageHero/supportingText/lineHeight', modes));
70
+ // Media slot box — Figma frame 4540:7845 ("Video"), sized by the
71
+ // `Video / Output` collection (L=117, M=70, S=20). Falls back to 117 when
72
+ // the collection is absent so the slot never collapses to 0×0.
73
+ const mediaWidth = Number(getVariableByName('video/width', modes)) || MEDIA_FALLBACK;
74
+ const mediaHeight = Number(getVariableByName('video/height', modes)) || MEDIA_FALLBACK;
75
+ const eyebrowStyle = useMemo(() => ({
76
+ color: getVariableByName('PageHero/eyebrow/color', modes),
77
+ fontFamily: getVariableByName('PageHero/eyebrow/fontFamily', modes),
78
+ fontSize: Number(getVariableByName('PageHero/eyebrow/fontSize', modes)),
79
+ fontWeight: String(getVariableByName('PageHero/eyebrow/fontWeight', modes)),
80
+ lineHeight: Number(getVariableByName('PageHero/eyebrow/lineHeight', modes)),
81
+ textAlign: 'center'
82
+ }), [modes]);
83
+ const headlineStyle = useMemo(() => ({
84
+ color: getVariableByName('PageHero/headline/color', modes),
85
+ fontFamily: getVariableByName('PageHero/headline/fontFamily', modes),
86
+ fontSize: Number(getVariableByName('PageHero/headline/fontSize', modes)),
87
+ fontWeight: String(getVariableByName('PageHero/headline/fontWeight', modes)),
88
+ lineHeight: Number(getVariableByName('PageHero/headline/lineHeight', modes)),
89
+ textAlign: 'center',
90
+ width: '100%'
91
+ }), [modes]);
92
+ const supportingTextStyle = useMemo(() => ({
93
+ color: getVariableByName('PageHero/supportingText/color', modes),
94
+ fontFamily: getVariableByName('PageHero/supportingText/fontFamily', modes),
95
+ fontSize: Number(getVariableByName('PageHero/supportingText/fontSize', modes)),
96
+ fontWeight: String(getVariableByName('PageHero/supportingText/fontWeight', modes)),
97
+ lineHeight: Number(getVariableByName('PageHero/supportingText/lineHeight', modes)),
98
+ textAlign: 'center'
99
+ }), [modes]);
100
+ const bodyStyle = useMemo(() => ({
101
+ color: getVariableByName('PageHero/body/color', modes),
102
+ fontFamily: getVariableByName('PageHero/body/fontFamily', modes),
103
+ fontSize: Number(getVariableByName('PageHero/body/fontSize', modes)),
104
+ fontWeight: String(getVariableByName('PageHero/body/fontWeight', modes)),
105
+ lineHeight: Number(getVariableByName('PageHero/body/lineHeight', modes)),
106
+ textAlign: 'center'
107
+ }), [modes]);
86
108
  const containerStyle = {
87
109
  flexDirection: 'column',
88
110
  alignItems: 'center',
89
- paddingHorizontal,
90
111
  gap,
91
112
  width: '100%'
92
113
  };
@@ -96,34 +117,6 @@ function PageHero({
96
117
  gap: textWrapGap,
97
118
  width: '100%'
98
119
  };
99
- const eyebrowStyle = {
100
- color: eyebrowColor,
101
- fontFamily: eyebrowFontFamily,
102
- fontSize: eyebrowFontSize,
103
- fontWeight: String(eyebrowFontWeight),
104
- lineHeight: eyebrowLineHeight,
105
- textAlign: 'center'
106
- };
107
- const headlineStyle = {
108
- color: headlineColor,
109
- fontFamily: headlineFontFamily,
110
- fontSize: headlineFontSize,
111
- fontWeight: String(headlineFontWeight),
112
- lineHeight: headlineLineHeight,
113
- textAlign: 'center',
114
- width: '100%'
115
- };
116
- const supportingTextStyle = {
117
- color: '#ffffff',
118
- fontFamily: 'System',
119
- fontSize: 12,
120
- fontWeight: '500',
121
- lineHeight: supportingTextLineHeight,
122
- textAlign: 'center'
123
- };
124
- const buttonWrapStyle = {
125
- width: '100%'
126
- };
127
120
  const buttonContent = useMemo(() => {
128
121
  if (buttonSlot !== undefined && buttonSlot !== null) {
129
122
  return cloneChildrenWithModes(buttonSlot, modes);
@@ -135,20 +128,16 @@ function PageHero({
135
128
  label: buttonLabel,
136
129
  onPress: onButtonPress,
137
130
  modes: modes,
138
- style: buttonWrapStyle
131
+ style: FULL_WIDTH
139
132
  });
140
- // buttonWrapStyle is a literal object created above; it is intentionally
141
- // omitted from deps because its identity changes on every render but its
142
- // shape never does.
143
- // eslint-disable-next-line react-hooks/exhaustive-deps
144
133
  }, [buttonSlot, showButton, buttonLabel, onButtonPress, modes]);
145
134
 
146
135
  // Sized container for the media slot. Always rendered when `media` is
147
- // provided, so the slot has a predictable box (matches Figma frame
148
- // 4540:7845 — 117×117 by default) even if the inner element omits its
149
- // own width/height. `overflow: 'hidden'` mirrors the Figma frame's
150
- // `clipsContent` so a slightly oversized illustration doesn't break
151
- // the centered layout.
136
+ // provided, so the slot has a predictable box (Figma frame 4540:7845 —
137
+ // 117×117 at `Video / Output = L`) even if the inner element omits its own
138
+ // width/height. `overflow: 'hidden'` mirrors the Figma frame's
139
+ // `clipsContent` so a slightly oversized illustration doesn't break the
140
+ // centered layout.
152
141
  const mediaContent = useMemo(() => {
153
142
  if (media === undefined || media === null) return null;
154
143
  return /*#__PURE__*/_jsx(View, {
@@ -162,22 +151,30 @@ function PageHero({
162
151
  children: cloneChildrenWithModes(media, modes)
163
152
  });
164
153
  }, [media, mediaWidth, mediaHeight, modes]);
154
+ const hasEyebrow = showEyebrow && !!eyebrow;
155
+ const hasHeadline = !!headline;
165
156
  return /*#__PURE__*/_jsxs(View, {
166
157
  style: [containerStyle, style],
167
158
  testID: testID,
168
- children: [mediaContent, /*#__PURE__*/_jsxs(View, {
159
+ children: [mediaContent, hasEyebrow || hasHeadline ? /*#__PURE__*/_jsxs(View, {
169
160
  style: textWrapStyle,
170
- children: [eyebrow ? /*#__PURE__*/_jsx(Text, {
161
+ children: [hasEyebrow ? /*#__PURE__*/_jsx(Text, {
171
162
  style: eyebrowStyle,
172
163
  children: eyebrow
173
- }) : null, headline ? /*#__PURE__*/_jsx(Text, {
164
+ }) : null, hasHeadline ? /*#__PURE__*/_jsx(Text, {
174
165
  style: headlineStyle,
175
166
  children: headline
176
167
  }) : null]
177
- }), showSupportingText && supportingText ? /*#__PURE__*/_jsx(Text, {
168
+ }) : null, showSupportingText && supportingText ? /*#__PURE__*/_jsx(Text, {
178
169
  style: supportingTextStyle,
179
170
  children: supportingText
171
+ }) : null, showBody && body ? /*#__PURE__*/_jsx(Text, {
172
+ style: bodyStyle,
173
+ children: body
180
174
  }) : null, buttonContent]
181
175
  });
182
176
  }
177
+ const FULL_WIDTH = {
178
+ width: '100%'
179
+ };
183
180
  export default PageHero;