jfs-components 0.0.77 → 0.0.79

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/lib/commonjs/components/Accordion/Accordion.js +55 -55
  3. package/lib/commonjs/components/ActionFooter/ActionFooter.js +48 -2
  4. package/lib/commonjs/components/Attached/Attached.js +144 -0
  5. package/lib/commonjs/components/Card/Card.js +25 -2
  6. package/lib/commonjs/components/Checkbox/Checkbox.js +21 -9
  7. package/lib/commonjs/components/DropdownInput/DropdownInput.js +30 -16
  8. package/lib/commonjs/components/ExpandableCheckbox/ExpandableCheckbox.js +167 -0
  9. package/lib/commonjs/components/FormField/FormField.js +14 -1
  10. package/lib/commonjs/components/FullscreenModal/FullscreenModal.js +353 -0
  11. package/lib/commonjs/components/ListItem/ListItem.js +46 -24
  12. package/lib/commonjs/components/MessageField/MessageField.js +318 -0
  13. package/lib/commonjs/components/NavArrow/NavArrow.js +58 -17
  14. package/lib/commonjs/components/PlanComparisonCard/PlanComparisonCard.js +328 -0
  15. package/lib/commonjs/components/Slot/Slot.js +73 -0
  16. package/lib/commonjs/components/Stepper/Step.js +47 -60
  17. package/lib/commonjs/components/Stepper/StepLabel.js +40 -10
  18. package/lib/commonjs/components/Stepper/Stepper.js +15 -17
  19. package/lib/commonjs/components/SuggestiveSearch/SuggestiveSearch.js +487 -0
  20. package/lib/commonjs/components/TextInput/TextInput.js +16 -1
  21. package/lib/commonjs/components/Title/Title.js +10 -2
  22. package/lib/commonjs/components/index.js +49 -0
  23. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
  24. package/lib/commonjs/icons/registry.js +1 -1
  25. package/lib/module/components/Accordion/Accordion.js +56 -56
  26. package/lib/module/components/ActionFooter/ActionFooter.js +50 -4
  27. package/lib/module/components/Attached/Attached.js +139 -0
  28. package/lib/module/components/Card/Card.js +25 -2
  29. package/lib/module/components/Checkbox/Checkbox.js +22 -10
  30. package/lib/module/components/DropdownInput/DropdownInput.js +30 -16
  31. package/lib/module/components/ExpandableCheckbox/ExpandableCheckbox.js +161 -0
  32. package/lib/module/components/FormField/FormField.js +16 -3
  33. package/lib/module/components/FullscreenModal/FullscreenModal.js +348 -0
  34. package/lib/module/components/ListItem/ListItem.js +46 -24
  35. package/lib/module/components/MessageField/MessageField.js +313 -0
  36. package/lib/module/components/NavArrow/NavArrow.js +59 -18
  37. package/lib/module/components/PlanComparisonCard/PlanComparisonCard.js +322 -0
  38. package/lib/module/components/Slot/Slot.js +68 -0
  39. package/lib/module/components/Stepper/Step.js +48 -61
  40. package/lib/module/components/Stepper/StepLabel.js +40 -10
  41. package/lib/module/components/Stepper/Stepper.js +15 -17
  42. package/lib/module/components/SuggestiveSearch/SuggestiveSearch.js +481 -0
  43. package/lib/module/components/TextInput/TextInput.js +17 -2
  44. package/lib/module/components/Title/Title.js +10 -2
  45. package/lib/module/components/index.js +7 -0
  46. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
  47. package/lib/module/icons/registry.js +1 -1
  48. package/lib/typescript/src/components/Accordion/Accordion.d.ts +14 -20
  49. package/lib/typescript/src/components/Attached/Attached.d.ts +61 -0
  50. package/lib/typescript/src/components/Card/Card.d.ts +9 -2
  51. package/lib/typescript/src/components/ExpandableCheckbox/ExpandableCheckbox.d.ts +63 -0
  52. package/lib/typescript/src/components/FullscreenModal/FullscreenModal.d.ts +99 -0
  53. package/lib/typescript/src/components/ListItem/ListItem.d.ts +15 -5
  54. package/lib/typescript/src/components/MessageField/MessageField.d.ts +81 -0
  55. package/lib/typescript/src/components/NavArrow/NavArrow.d.ts +10 -5
  56. package/lib/typescript/src/components/PlanComparisonCard/PlanComparisonCard.d.ts +64 -0
  57. package/lib/typescript/src/components/Slot/Slot.d.ts +52 -0
  58. package/lib/typescript/src/components/Stepper/Step.d.ts +4 -1
  59. package/lib/typescript/src/components/Stepper/StepLabel.d.ts +4 -1
  60. package/lib/typescript/src/components/Stepper/Stepper.d.ts +3 -1
  61. package/lib/typescript/src/components/SuggestiveSearch/SuggestiveSearch.d.ts +123 -0
  62. package/lib/typescript/src/components/index.d.ts +10 -3
  63. package/lib/typescript/src/icons/registry.d.ts +1 -1
  64. package/package.json +1 -1
  65. package/src/components/Accordion/Accordion.tsx +113 -73
  66. package/src/components/ActionFooter/ActionFooter.tsx +56 -4
  67. package/src/components/Attached/Attached.tsx +181 -0
  68. package/src/components/Card/Card.tsx +28 -1
  69. package/src/components/Checkbox/Checkbox.tsx +22 -9
  70. package/src/components/DropdownInput/DropdownInput.tsx +67 -39
  71. package/src/components/ExpandableCheckbox/ExpandableCheckbox.tsx +237 -0
  72. package/src/components/FormField/FormField.tsx +19 -3
  73. package/src/components/FullscreenModal/FullscreenModal.tsx +414 -0
  74. package/src/components/ListItem/ListItem.tsx +55 -25
  75. package/src/components/MessageField/MessageField.tsx +543 -0
  76. package/src/components/NavArrow/NavArrow.tsx +81 -17
  77. package/src/components/PlanComparisonCard/PlanComparisonCard.tsx +426 -0
  78. package/src/components/Slot/Slot.tsx +91 -0
  79. package/src/components/Stepper/Step.tsx +52 -51
  80. package/src/components/Stepper/StepLabel.tsx +46 -9
  81. package/src/components/Stepper/Stepper.tsx +20 -15
  82. package/src/components/SuggestiveSearch/SuggestiveSearch.tsx +756 -0
  83. package/src/components/TextInput/TextInput.tsx +14 -1
  84. package/src/components/Title/Title.tsx +13 -2
  85. package/src/components/index.ts +10 -3
  86. package/src/design-tokens/Coin Variables-variables-full.json +1 -1
  87. package/src/icons/registry.ts +1 -1
@@ -0,0 +1,167 @@
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 _Checkbox = _interopRequireDefault(require("../Checkbox/Checkbox"));
12
+ var _Button = _interopRequireDefault(require("../Button/Button"));
13
+ var _jsxRuntime = require("react/jsx-runtime");
14
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
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
+ /**
17
+ * Default modes applied to the inner toggle `Button`. These resolve the
18
+ * tertiary-style pill in the Figma reference (small, transparent background,
19
+ * brand purple foreground). Any value supplied via the consumer `modes` prop
20
+ * takes precedence over these defaults.
21
+ */
22
+ const BUTTON_DEFAULT_MODES = {
23
+ 'Button / Size': 'XS',
24
+ AppearanceBrand: 'Secondary',
25
+ Emphasis: 'Low'
26
+ };
27
+
28
+ /**
29
+ * ExpandableCheckbox composes a `Checkbox`, a long-form label and a
30
+ * "Read more" / "Read less" toggle. Mirrors the Figma "Expandable Checkbox"
31
+ * component with two states:
32
+ *
33
+ * - **Idle (collapsed)** — checkbox + truncated label + toggle button arranged
34
+ * in a horizontal row (cross-axis centered).
35
+ * - **Open (expanded)** — checkbox + full multi-line label, with the toggle
36
+ * button right-aligned beneath the row.
37
+ *
38
+ * The checkbox and the toggle button have independent press handlers — pressing
39
+ * the toggle does not affect the checked state, and toggling the checkbox does
40
+ * not collapse / expand the row.
41
+ *
42
+ * @component
43
+ * @param {ExpandableCheckboxProps} props
44
+ *
45
+ * @example
46
+ * ```tsx
47
+ * <ExpandableCheckbox
48
+ * label="By checking this box, I (a) acknowledge and (b) agree to the full terms…"
49
+ * defaultChecked
50
+ * onValueChange={setAccepted}
51
+ * modes={{ 'Color Mode': 'Light' }}
52
+ * />
53
+ * ```
54
+ */
55
+ function ExpandableCheckbox({
56
+ label = '',
57
+ checked: controlledChecked,
58
+ defaultChecked = false,
59
+ onValueChange,
60
+ expanded: controlledExpanded,
61
+ defaultExpanded = false,
62
+ onExpandedChange,
63
+ disabled = false,
64
+ readMoreLabel = 'Read more',
65
+ readLessLabel = 'Read less',
66
+ collapsedLines = 1,
67
+ modes = _reactUtils.EMPTY_MODES,
68
+ style,
69
+ labelStyle,
70
+ accessibilityLabel
71
+ }) {
72
+ const isCheckedControlled = controlledChecked !== undefined;
73
+ const [internalChecked, setInternalChecked] = (0, _react.useState)(defaultChecked);
74
+ const isChecked = isCheckedControlled ? controlledChecked : internalChecked;
75
+ const isExpandedControlled = controlledExpanded !== undefined;
76
+ const [internalExpanded, setInternalExpanded] = (0, _react.useState)(defaultExpanded);
77
+ const isExpanded = isExpandedControlled ? controlledExpanded : internalExpanded;
78
+ const handleToggleChecked = (0, _react.useCallback)(next => {
79
+ if (disabled) return;
80
+ if (!isCheckedControlled) setInternalChecked(next);
81
+ onValueChange?.(next);
82
+ }, [disabled, isCheckedControlled, onValueChange]);
83
+ const handleToggleExpanded = (0, _react.useCallback)(() => {
84
+ if (disabled) return;
85
+ const next = !isExpanded;
86
+ if (!isExpandedControlled) setInternalExpanded(next);
87
+ onExpandedChange?.(next);
88
+ }, [disabled, isExpanded, isExpandedControlled, onExpandedChange]);
89
+ const gap = (0, _figmaVariablesResolver.getVariableByName)('expandableCheckbox/gap', modes) ?? 8;
90
+ const rowGap = (0, _figmaVariablesResolver.getVariableByName)('checkboxItem/gap', modes) ?? 8;
91
+ const rowPaddingHorizontal = (0, _figmaVariablesResolver.getVariableByName)('checkboxItem/padding/horizontal', modes) ?? 0;
92
+ const rowPaddingVertical = (0, _figmaVariablesResolver.getVariableByName)('checkboxItem/padding/vertical', modes) ?? 0;
93
+ const labelColor = (0, _figmaVariablesResolver.getVariableByName)('checkboxItem/foreground', modes) ?? '#1a1c1f';
94
+ const labelFontFamily = (0, _figmaVariablesResolver.getVariableByName)('checkboxItem/label/fontFamily', modes) ?? 'JioType Var';
95
+ const labelFontSize = (0, _figmaVariablesResolver.getVariableByName)('checkboxItem/label/fontSize', modes) ?? 14;
96
+ const labelLineHeight = (0, _figmaVariablesResolver.getVariableByName)('checkboxItem/label/lineHeight', modes) ?? 19;
97
+ const labelFontWeightRaw = (0, _figmaVariablesResolver.getVariableByName)('checkboxItem/label/fontWeight', modes) ?? 400;
98
+ const labelFontWeight = String(labelFontWeightRaw);
99
+ const containerStyle = (0, _react.useMemo)(() => ({
100
+ flexDirection: isExpanded ? 'column' : 'row',
101
+ alignItems: isExpanded ? 'flex-end' : 'center',
102
+ gap,
103
+ width: '100%',
104
+ ...(disabled ? {
105
+ opacity: 0.6
106
+ } : null)
107
+ }), [isExpanded, gap, disabled]);
108
+ const rowStyle = (0, _react.useMemo)(() => ({
109
+ flex: isExpanded ? undefined : 1,
110
+ alignSelf: isExpanded ? 'stretch' : 'auto',
111
+ minWidth: 0,
112
+ flexDirection: 'row',
113
+ alignItems: 'flex-start',
114
+ gap: rowGap,
115
+ paddingHorizontal: rowPaddingHorizontal,
116
+ paddingVertical: rowPaddingVertical
117
+ }), [isExpanded, rowGap, rowPaddingHorizontal, rowPaddingVertical]);
118
+ const resolvedLabelStyle = (0, _react.useMemo)(() => ({
119
+ flex: 1,
120
+ minWidth: 0,
121
+ color: labelColor,
122
+ fontFamily: labelFontFamily,
123
+ fontSize: labelFontSize,
124
+ lineHeight: labelLineHeight,
125
+ fontWeight: labelFontWeight
126
+ }), [labelColor, labelFontFamily, labelFontSize, labelLineHeight, labelFontWeight]);
127
+ const buttonModes = (0, _react.useMemo)(() => ({
128
+ ...BUTTON_DEFAULT_MODES,
129
+ ...modes
130
+ }), [modes]);
131
+ const a11yLabel = accessibilityLabel ?? (typeof label === 'string' ? label : undefined);
132
+ const buttonLabel = isExpanded ? readLessLabel : readMoreLabel;
133
+ const labelNumberOfLinesProps = !isExpanded && collapsedLines > 0 ? {
134
+ numberOfLines: collapsedLines,
135
+ ellipsizeMode: 'tail'
136
+ } : null;
137
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
138
+ style: [containerStyle, style],
139
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
140
+ style: rowStyle,
141
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_Checkbox.default, {
142
+ checked: isChecked,
143
+ disabled: disabled,
144
+ onValueChange: handleToggleChecked,
145
+ modes: modes,
146
+ ...(a11yLabel !== undefined ? {
147
+ accessibilityLabel: a11yLabel
148
+ } : {})
149
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
150
+ style: [resolvedLabelStyle, labelStyle],
151
+ selectable: false,
152
+ ...(labelNumberOfLinesProps ?? {}),
153
+ children: label
154
+ })]
155
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_Button.default, {
156
+ label: buttonLabel,
157
+ onPress: handleToggleExpanded,
158
+ disabled: disabled,
159
+ modes: buttonModes,
160
+ accessibilityLabel: buttonLabel,
161
+ accessibilityState: {
162
+ expanded: isExpanded
163
+ }
164
+ })]
165
+ });
166
+ }
167
+ var _default = exports.default = ExpandableCheckbox;
@@ -208,6 +208,16 @@ function FormField({
208
208
  const [isFocused, setIsFocused] = (0, _react.useState)(false);
209
209
  const interactive = !isDisabled && !isReadOnly;
210
210
 
211
+ // Ref to the native input so tapping anywhere in the input row (padding,
212
+ // leading/trailing gutters) focuses it on the FIRST tap — fixing the Android
213
+ // "two taps to open the keyboard" issue caused by the row intercepting the
214
+ // initial touch.
215
+ const inputRef = (0, _react.useRef)(null);
216
+ const focusInput = (0, _react.useCallback)(() => {
217
+ if (!interactive) return;
218
+ inputRef.current?.focus();
219
+ }, [interactive]);
220
+
211
221
  // FormField States cascade — error > read only/disabled > active (focused) > idle.
212
222
  // Disabled maps to "Read Only" since there is no dedicated disabled mode and
213
223
  // the visual treatment is closest. This is only the DEFAULT — an explicit
@@ -340,13 +350,16 @@ function FormField({
340
350
  style: requiredIndicatorStyle,
341
351
  children: " *"
342
352
  })]
343
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
353
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
344
354
  style: [inputRowStyle, inputStyle],
355
+ onPress: focusInput,
356
+ accessible: false,
345
357
  children: [processedLeading != null && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
346
358
  accessibilityElementsHidden: true,
347
359
  importantForAccessibility: "no",
348
360
  children: processedLeading
349
361
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
362
+ ref: inputRef,
350
363
  style: [inputTextStyles, inputTextStyle],
351
364
  value: value ?? '',
352
365
  onChangeText: handleChangeText,
@@ -0,0 +1,353 @@
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 _reactNativeReanimated = _interopRequireWildcard(require("react-native-reanimated"));
10
+ var _figmaVariablesResolver = require("../../design-tokens/figma-variables-resolver");
11
+ var _JFSThemeProvider = require("../../design-tokens/JFSThemeProvider");
12
+ var _reactUtils = require("../../utils/react-utils");
13
+ var _Button = _interopRequireDefault(require("../Button/Button"));
14
+ var _Disclaimer = _interopRequireDefault(require("../Disclaimer/Disclaimer"));
15
+ var _IconButton = _interopRequireDefault(require("../IconButton/IconButton"));
16
+ var _ActionFooter = _interopRequireDefault(require("../ActionFooter/ActionFooter"));
17
+ var _Slot = _interopRequireDefault(require("../Slot/Slot"));
18
+ var _jsxRuntime = require("react/jsx-runtime");
19
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
20
+ 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); }
21
+ // ---------------------------------------------------------------------------
22
+ // Forced modes
23
+ //
24
+ // `FullscreenModal` always themes itself with the `context5: 'Fullscreen Modal'`
25
+ // collection mode. This is what flips the section / list-item / hero text
26
+ // tokens to their white-on-dark values (see the Figma "Fullscreen Modal"
27
+ // context). It is intentionally NON-overridable: callers can pass any other
28
+ // modes (Color Mode, AppearanceBrand, …) but never context5. The frozen
29
+ // object keeps its identity stable so the token resolver's per-modes cache
30
+ // stays hot, and so `cloneChildrenWithModes` can use it as the
31
+ // always-wins `forcedModes` argument.
32
+ // ---------------------------------------------------------------------------
33
+ const FULLSCREEN_MODAL_FORCED_MODES = Object.freeze({
34
+ context5: 'Fullscreen Modal'
35
+ });
36
+
37
+ // Reanimated-driven ScrollView so the parallax handler runs on the UI thread.
38
+ // Module scope so the wrapped component identity is stable across renders.
39
+ const AnimatedScrollView = _reactNativeReanimated.default.createAnimatedComponent(_reactNative.ScrollView);
40
+
41
+ // Parallax tuning. The hero collapses by HEIGHT only as the user scrolls up —
42
+ // its full width is preserved and the media keeps a fixed aspect ratio (it is
43
+ // cropped, never scaled or squished, like a `cover` background). When no
44
+ // explicit `heroMinHeight` is given, the hero collapses to this fraction of
45
+ // its resting height.
46
+ const HERO_MIN_HEIGHT_RATIO = 0.45;
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // Hero text — the eyebrow / headline / supporting / price block. Built inline
50
+ // (rather than reusing <PageHero>) so we can render BOTH a supporting
51
+ // paragraph AND a price line with the exact PageHero token gaps, and overlay
52
+ // it on the parallax media without PageHero's media/button scaffolding.
53
+ // ---------------------------------------------------------------------------
54
+
55
+ function HeroText({
56
+ eyebrow,
57
+ headline,
58
+ supportingText,
59
+ priceText,
60
+ modes
61
+ }) {
62
+ const styles = (0, _react.useMemo)(() => {
63
+ const gap = Number((0, _figmaVariablesResolver.getVariableByName)('PageHero/gap', modes)) || 16;
64
+ const textWrapGap = Number((0, _figmaVariablesResolver.getVariableByName)('PageHero/textWrap/gap', modes)) || 8;
65
+ const eyebrowStyle = {
66
+ color: (0, _figmaVariablesResolver.getVariableByName)('PageHero/eyebrow/color', modes) || '#ffffff',
67
+ fontFamily: (0, _figmaVariablesResolver.getVariableByName)('PageHero/eyebrow/fontFamily', modes) || 'System',
68
+ fontSize: Number((0, _figmaVariablesResolver.getVariableByName)('PageHero/eyebrow/fontSize', modes)) || 18,
69
+ fontWeight: String((0, _figmaVariablesResolver.getVariableByName)('PageHero/eyebrow/fontWeight', modes) || 700),
70
+ lineHeight: Number((0, _figmaVariablesResolver.getVariableByName)('PageHero/eyebrow/lineHeight', modes)) || 20,
71
+ textAlign: 'center'
72
+ };
73
+ const headlineStyle = {
74
+ color: (0, _figmaVariablesResolver.getVariableByName)('PageHero/headline/color', modes) || '#ffffff',
75
+ fontFamily: (0, _figmaVariablesResolver.getVariableByName)('PageHero/headline/fontFamily', modes) || 'System',
76
+ fontSize: Number((0, _figmaVariablesResolver.getVariableByName)('PageHero/headline/fontSize', modes)) || 29,
77
+ fontWeight: String((0, _figmaVariablesResolver.getVariableByName)('PageHero/headline/fontWeight', modes) || 900),
78
+ lineHeight: Number((0, _figmaVariablesResolver.getVariableByName)('PageHero/headline/lineHeight', modes)) || 29,
79
+ textAlign: 'center',
80
+ width: '100%'
81
+ };
82
+ const supportingTextStyle = {
83
+ color: (0, _figmaVariablesResolver.getVariableByName)('PageHero/supportingText/color', modes) || '#ffffff',
84
+ fontFamily: (0, _figmaVariablesResolver.getVariableByName)('PageHero/supportingText/fontFamily', modes) || 'System',
85
+ fontSize: Number((0, _figmaVariablesResolver.getVariableByName)('PageHero/supportingText/fontSize', modes)) || 12,
86
+ fontWeight: String((0, _figmaVariablesResolver.getVariableByName)('PageHero/supportingText/fontWeight', modes) || 500),
87
+ lineHeight: Number((0, _figmaVariablesResolver.getVariableByName)('PageHero/supportingText/lineHeight', modes)) || 16,
88
+ textAlign: 'center'
89
+ };
90
+ const priceTextStyle = {
91
+ color: (0, _figmaVariablesResolver.getVariableByName)('PageHero/body/color', modes) || '#ffffff',
92
+ fontFamily: (0, _figmaVariablesResolver.getVariableByName)('PageHero/body/fontFamily', modes) || 'System',
93
+ fontSize: Number((0, _figmaVariablesResolver.getVariableByName)('PageHero/body/fontSize', modes)) || 12,
94
+ fontWeight: String((0, _figmaVariablesResolver.getVariableByName)('PageHero/body/fontWeight', modes) || 500),
95
+ lineHeight: Number((0, _figmaVariablesResolver.getVariableByName)('PageHero/body/lineHeight', modes)) || 16,
96
+ textAlign: 'center'
97
+ };
98
+ return {
99
+ container: {
100
+ width: '100%',
101
+ alignItems: 'center',
102
+ gap
103
+ },
104
+ textWrap: {
105
+ width: '100%',
106
+ alignItems: 'center',
107
+ gap: textWrapGap
108
+ },
109
+ eyebrowStyle,
110
+ headlineStyle,
111
+ supportingTextStyle,
112
+ priceTextStyle
113
+ };
114
+ }, [modes]);
115
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
116
+ style: styles.container,
117
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
118
+ style: styles.textWrap,
119
+ children: [eyebrow ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
120
+ style: styles.eyebrowStyle,
121
+ children: eyebrow
122
+ }) : null, headline ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
123
+ style: styles.headlineStyle,
124
+ children: headline
125
+ }) : null]
126
+ }), supportingText ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
127
+ style: styles.supportingTextStyle,
128
+ children: supportingText
129
+ }) : null, priceText ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
130
+ style: styles.priceTextStyle,
131
+ children: priceText
132
+ }) : null]
133
+ });
134
+ }
135
+
136
+ /**
137
+ * FullscreenModal — a full-screen takeover surface with a parallax media hero,
138
+ * a scrollable body, a floating close button, and a sticky `ActionFooter`.
139
+ *
140
+ * The component always themes itself with `context5: 'Fullscreen Modal'`
141
+ * (non-overridable) so every nested component (Section, ListItem, Button,
142
+ * Disclaimer, …) resolves the white-on-dark "fullscreen modal" token values.
143
+ * That mode is cascaded into `children`, the footer, and the hero text via
144
+ * `cloneChildrenWithModes` / the merged `modes` object.
145
+ *
146
+ * ### Parallax
147
+ * As the user scrolls up, the hero collapses by **height only** (from
148
+ * `heroHeight` to `heroMinHeight`) — its **full width is always preserved**.
149
+ * The `heroMedia` is pinned to the top at a fixed size and `cover`-cropped by
150
+ * the collapsing clip, so it keeps a perfect aspect ratio the whole time
151
+ * (never scaled or squished). Because it collapses slower than the content
152
+ * scrolls, the media lags behind for the parallax depth cue. Disable with
153
+ * `parallax={false}`.
154
+ *
155
+ * @component
156
+ * @example
157
+ * ```tsx
158
+ * <FullscreenModal
159
+ * eyebrow="Upgrade to JioFinance+"
160
+ * headline="Get more from your money."
161
+ * supportingText="JioFinance+ is your upgraded financial experience…"
162
+ * priceText="₹999/year · ₹0 until 2027"
163
+ * heroMedia={<LottiePlayer source={hero} size={{ width: 360, height: 420 }} />}
164
+ * primaryActionLabel="Upgrade for free"
165
+ * disclaimer="By upgrading, we'll check your eligibility with Experian."
166
+ * onPrimaryAction={() => upgrade()}
167
+ * onClose={() => navigation.goBack()}
168
+ * >
169
+ * <Section title="Key Benefits" slotDirection="column" slot={…} />
170
+ * <Section title="Compare plans" slotDirection="column" slot={…} />
171
+ * </FullscreenModal>
172
+ * ```
173
+ */
174
+ function FullscreenModal({
175
+ eyebrow = 'Upgrade to JioFinance+',
176
+ headline = 'Get more from your money.',
177
+ supportingText = 'JioFinance+ is your upgraded financial experience, designed to work harder in the background so your money works smarter in real life.',
178
+ priceText = '₹999/year · ₹0 until 2027',
179
+ heroMedia,
180
+ heroHeight = 420,
181
+ heroMinHeight,
182
+ parallax = true,
183
+ showClose = true,
184
+ onClose,
185
+ closeAccessibilityLabel = 'Close',
186
+ footer,
187
+ primaryActionLabel = 'Upgrade for free',
188
+ onPrimaryAction,
189
+ disclaimer = "By upgrading, we'll check your eligibility with Experian.",
190
+ backgroundColor = '#0f0d0a',
191
+ children,
192
+ modes: propModes = _reactUtils.EMPTY_MODES,
193
+ style,
194
+ contentContainerStyle,
195
+ testID
196
+ }) {
197
+ const {
198
+ modes: globalModes
199
+ } = (0, _JFSThemeProvider.useTokens)();
200
+
201
+ // context5 is appended last so it always wins, regardless of what the
202
+ // caller (or the global theme) passes.
203
+ const modes = (0, _react.useMemo)(() => ({
204
+ ...globalModes,
205
+ ...propModes,
206
+ ...FULLSCREEN_MODAL_FORCED_MODES
207
+ }), [globalModes, propModes]);
208
+ const rootGap = Number((0, _figmaVariablesResolver.getVariableByName)('fullScreenModal/gap', modes)) || 16;
209
+ const minHeight = heroMinHeight ?? Math.round(heroHeight * HERO_MIN_HEIGHT_RATIO);
210
+ const scrollY = (0, _reactNativeReanimated.useSharedValue)(0);
211
+ const onScroll = (0, _reactNativeReanimated.useAnimatedScrollHandler)(event => {
212
+ scrollY.value = event.contentOffset.y;
213
+ });
214
+
215
+ // Collapse the hero by HEIGHT only as the user scrolls up. The clip's width
216
+ // never changes and the media inside is pinned full-size at the top, so the
217
+ // art is cropped (cover) rather than scaled or narrowed — it keeps a perfect
218
+ // aspect ratio the whole time. Pull-down (negative offset) is clamped, so the
219
+ // hero never grows past its resting height.
220
+ const heroAnimatedStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => {
221
+ const height = (0, _reactNativeReanimated.interpolate)(scrollY.value, [0, heroHeight], [heroHeight, minHeight], _reactNativeReanimated.Extrapolation.CLAMP);
222
+ return {
223
+ height
224
+ };
225
+ });
226
+ const processedHeroMedia = (0, _react.useMemo)(() => heroMedia ? (0, _reactUtils.cloneChildrenWithModes)(heroMedia, modes, FULLSCREEN_MODAL_FORCED_MODES) : null, [heroMedia, modes]);
227
+ const processedChildren = (0, _react.useMemo)(() => children ? (0, _reactUtils.cloneChildrenWithModes)(children, modes, FULLSCREEN_MODAL_FORCED_MODES) : null, [children, modes]);
228
+
229
+ // The clip is full-width and top-pinned; its height is what animates. Width
230
+ // is intentionally never animated.
231
+ const heroClipBaseStyle = (0, _react.useMemo)(() => ({
232
+ position: 'absolute',
233
+ top: 0,
234
+ left: 0,
235
+ right: 0,
236
+ overflow: 'hidden'
237
+ }), []);
238
+
239
+ // The media sits at a fixed full-size box pinned to the top of the clip, so
240
+ // the collapsing clip crops it from the bottom (cover) instead of resizing
241
+ // it. Full width, fixed height — a perfect, constant aspect ratio.
242
+ const heroMediaWrapStyle = (0, _react.useMemo)(() => ({
243
+ position: 'absolute',
244
+ top: 0,
245
+ left: 0,
246
+ right: 0,
247
+ height: heroHeight,
248
+ alignItems: 'stretch'
249
+ }), [heroHeight]);
250
+ const heroTextRegionStyle = (0, _react.useMemo)(() => ({
251
+ height: heroHeight,
252
+ justifyContent: 'flex-end',
253
+ paddingHorizontal: 16,
254
+ paddingBottom: 16
255
+ }), [heroHeight]);
256
+ const bodyStyle = (0, _react.useMemo)(() => [{
257
+ backgroundColor,
258
+ gap: rootGap,
259
+ paddingTop: rootGap,
260
+ paddingBottom: 24
261
+ }, contentContainerStyle], [backgroundColor, rootGap, contentContainerStyle]);
262
+ const heroClip = /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeReanimated.default.View, {
263
+ style: [heroClipBaseStyle, parallax ? heroAnimatedStyle : {
264
+ height: heroHeight
265
+ }],
266
+ pointerEvents: "none",
267
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
268
+ style: heroMediaWrapStyle,
269
+ children: processedHeroMedia
270
+ })
271
+ });
272
+
273
+ // Footer: a fully custom node, or the default Button + Disclaimer column.
274
+ let footerContent = null;
275
+ if (footer) {
276
+ footerContent = footer;
277
+ } else if (primaryActionLabel) {
278
+ footerContent = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_Slot.default, {
279
+ layoutDirection: "vertical",
280
+ modes: modes,
281
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_Button.default, {
282
+ label: primaryActionLabel,
283
+ modes: modes,
284
+ style: fullWidthStyle,
285
+ ...(onPrimaryAction ? {
286
+ onPress: onPrimaryAction
287
+ } : {})
288
+ }), disclaimer ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_Disclaimer.default, {
289
+ disclaimer: disclaimer,
290
+ modes: modes
291
+ }) : null]
292
+ });
293
+ }
294
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
295
+ style: [rootStyle, {
296
+ backgroundColor
297
+ }, style],
298
+ testID: testID,
299
+ children: [processedHeroMedia ? heroClip : null, /*#__PURE__*/(0, _jsxRuntime.jsxs)(AnimatedScrollView, {
300
+ style: scrollViewStyle,
301
+ contentContainerStyle: scrollContentStyle,
302
+ showsVerticalScrollIndicator: false,
303
+ onScroll: onScroll,
304
+ scrollEventThrottle: 16,
305
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
306
+ style: heroTextRegionStyle,
307
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(HeroText, {
308
+ eyebrow: eyebrow,
309
+ headline: headline,
310
+ supportingText: supportingText,
311
+ priceText: priceText,
312
+ modes: modes
313
+ })
314
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
315
+ style: bodyStyle,
316
+ children: processedChildren
317
+ })]
318
+ }), footerContent ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_ActionFooter.default, {
319
+ modes: modes,
320
+ children: footerContent
321
+ }) : null, showClose ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_IconButton.default, {
322
+ iconName: "ic_close",
323
+ modes: modes,
324
+ accessibilityLabel: closeAccessibilityLabel,
325
+ style: closeButtonStyle,
326
+ ...(onClose ? {
327
+ onPress: onClose
328
+ } : {})
329
+ }) : null]
330
+ });
331
+ }
332
+
333
+ // Module-scope style constants — never re-allocated per render.
334
+ const rootStyle = {
335
+ flex: 1,
336
+ width: '100%',
337
+ position: 'relative'
338
+ };
339
+ const scrollViewStyle = {
340
+ flex: 1
341
+ };
342
+ const scrollContentStyle = {
343
+ flexGrow: 1
344
+ };
345
+ const fullWidthStyle = {
346
+ width: '100%'
347
+ };
348
+ const closeButtonStyle = {
349
+ position: 'absolute',
350
+ top: 12,
351
+ right: 12
352
+ };
353
+ var _default = exports.default = FullscreenModal;