jfs-components 0.1.36 → 0.1.49
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 +29 -0
- package/lib/commonjs/components/Accordion/Accordion.js +16 -3
- package/lib/commonjs/components/AppBar/AppBar.js +118 -93
- package/lib/commonjs/components/CompareTable/CompareTable.js +98 -33
- package/lib/commonjs/components/FormField/FormField.js +9 -1
- package/lib/commonjs/components/FullscreenModal/FullscreenModal.js +4 -14
- package/lib/commonjs/components/HelloJioInput/HelloJioInput.js +20 -4
- package/lib/commonjs/components/PageHero/PageHero.js +3 -0
- package/lib/commonjs/components/ProductMerchandisingCard/ProductMerchandisingCard.js +50 -30
- package/lib/commonjs/components/Section/Section.js +3 -1
- package/lib/commonjs/components/ValueBackMetric/ValueBackMetric.js +117 -89
- package/lib/commonjs/components/index.js +7 -1
- package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -42204
- package/lib/commonjs/design-tokens/figma-modes.generated.js +8 -7
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/module/components/Accordion/Accordion.js +16 -3
- package/lib/module/components/AppBar/AppBar.js +116 -93
- package/lib/module/components/CompareTable/CompareTable.js +99 -34
- package/lib/module/components/FormField/FormField.js +9 -1
- package/lib/module/components/FullscreenModal/FullscreenModal.js +4 -14
- package/lib/module/components/HelloJioInput/HelloJioInput.js +20 -4
- package/lib/module/components/PageHero/PageHero.js +3 -0
- package/lib/module/components/ProductMerchandisingCard/ProductMerchandisingCard.js +50 -30
- package/lib/module/components/Section/Section.js +3 -1
- package/lib/module/components/ValueBackMetric/ValueBackMetric.js +120 -93
- package/lib/module/components/index.js +1 -1
- package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -42204
- package/lib/module/design-tokens/figma-modes.generated.js +8 -7
- package/lib/module/icons/registry.js +1 -1
- package/lib/typescript/src/components/Accordion/Accordion.d.ts +14 -1
- package/lib/typescript/src/components/AppBar/AppBar.d.ts +46 -14
- package/lib/typescript/src/components/CompareTable/CompareTable.d.ts +29 -12
- package/lib/typescript/src/components/HelloJioInput/HelloJioInput.d.ts +6 -4
- package/lib/typescript/src/components/PageHero/PageHero.d.ts +1 -0
- package/lib/typescript/src/components/ProductMerchandisingCard/ProductMerchandisingCard.d.ts +8 -1
- package/lib/typescript/src/components/Section/Section.d.ts +2 -1
- package/lib/typescript/src/components/ValueBackMetric/ValueBackMetric.d.ts +27 -16
- package/lib/typescript/src/components/index.d.ts +1 -1
- package/lib/typescript/src/design-tokens/figma-modes.generated.d.ts +2 -1
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/Accordion/Accordion.tsx +61 -33
- package/src/components/AppBar/AppBar.tsx +154 -124
- package/src/components/CompareTable/CompareTable.tsx +138 -52
- package/src/components/FormField/FormField.tsx +7 -0
- package/src/components/FullscreenModal/FullscreenModal.tsx +5 -14
- package/src/components/HelloJioInput/HelloJioInput.tsx +31 -12
- package/src/components/PageHero/PageHero.tsx +5 -0
- package/src/components/ProductMerchandisingCard/ProductMerchandisingCard.tsx +63 -30
- package/src/components/Section/Section.tsx +121 -156
- package/src/components/ValueBackMetric/ValueBackMetric.tsx +178 -104
- package/src/components/index.ts +6 -1
- package/src/design-tokens/Coin Variables-variables-full.json +1 -42204
- package/src/design-tokens/figma-modes.generated.ts +8 -7
- package/src/icons/registry.ts +1 -1
- package/D2C.md +0 -113
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,35 @@ 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.48] - 2026-07-23
|
|
8
|
+
|
|
9
|
+
- `CompareTable` (fixed-width scroll mode) — selection cards pin above the scroll surface (`stickyCards`, default `true`); accordion section titles stick under the cards and replace each other as you scroll (`stickyAccordionTitles`, default `true`); gray table-header sticky is now opt-in (`stickyHeaders` default `false`). Selection cards are square (corner radius forced to `0`).
|
|
10
|
+
- `Accordion` — new `showHeader` / `showContent` props so hosts can split sticky titles from bodies in scroll layouts (used by `CompareTable`).
|
|
11
|
+
- `HelloJioInput` — `leading={null}` hides the leading icon; IdleJioPlus is borderless frosted glass so blur shows through.
|
|
12
|
+
- `ProductMerchandisingCard` — new `badgeIcon`; footer special badge fully token-driven; footer text column keeps a min-width so CTAs don’t crush title/subtitle.
|
|
13
|
+
- `ValueBackMetric` — CTA is purple non-underlined `Text` (not `Link`), with optional `link` override slot; layout/typography resolve via `valueBackMetric/*`.
|
|
14
|
+
- Token sync — refreshed Coin Variables / `.token-metadata.json` / icon registry for the above components.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## [0.1.38] - 2026-07-22
|
|
19
|
+
|
|
20
|
+
- `FormField` — caret / cursor / selection color now follows the tokenized input text color (no platform blue/green accent).
|
|
21
|
+
- `FullscreenModal` — sticky footer no longer adds safe-area bottom padding on top of `ActionFooter` tokens (avoids double inset).
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## [0.1.36] - 2026-07-21
|
|
26
|
+
|
|
27
|
+
- `AppBar` — exported `JioDot` brand mark for MainPage `leadingSlot` (no default logo; opt in with `<JioDot />`). Also exports `AppBarProps` / `JioDotProps`.
|
|
28
|
+
- `PageHero` — new `body` / `showBody` (and `showEyebrow`) for a second text line; media sizing moves to `video/*` tokens + `Video / Output` mode (117px fallback).
|
|
29
|
+
- `LottiePlayer` — default size tokens renamed `media/*` → `video/*`; mode key is now `Video / Output` (**breaking** for callers still passing `Media / Output`).
|
|
30
|
+
- Library-wide mode/docs refresh (`figma-modes.generated.ts`, Coin Variables, component MDX).
|
|
31
|
+
|
|
32
|
+
> Note: `0.1.35` was published on npm as an intermediate build; the next documented release is `0.1.36`.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
7
36
|
## [0.1.34] - 2026-07-21
|
|
8
37
|
|
|
9
38
|
- `jfs-extract-modes` now automatically loads `FIGMA_ACCESS_TOKEN` from the
|
|
@@ -68,6 +68,8 @@ function Accordion({
|
|
|
68
68
|
accessibilityState,
|
|
69
69
|
webAccessibilityProps,
|
|
70
70
|
disableTruncation,
|
|
71
|
+
showHeader = true,
|
|
72
|
+
showContent = true,
|
|
71
73
|
...rest
|
|
72
74
|
}) {
|
|
73
75
|
const [internalExpanded, setInternalExpanded] = (0, _react.useState)(defaultExpanded);
|
|
@@ -105,8 +107,11 @@ function Accordion({
|
|
|
105
107
|
const contentPaddingTop = (0, _figmaVariablesResolver.getVariableByName)('accordion/content/padding/top', resolvedModes) ?? 8;
|
|
106
108
|
const contentPaddingBottom = (0, _figmaVariablesResolver.getVariableByName)('accordion/content/padding/bottom', resolvedModes) ?? 8;
|
|
107
109
|
const borderColor = (0, _figmaVariablesResolver.getVariableByName)('accordion/border/color', resolvedModes) ?? '#e6e6e6';
|
|
110
|
+
|
|
111
|
+
// Header-only sticky pieces drop the divider while expanded so the paired
|
|
112
|
+
// content Accordion owns the section border.
|
|
108
113
|
const containerStyle = {
|
|
109
|
-
borderBottomWidth: 1,
|
|
114
|
+
borderBottomWidth: !showContent && isExpanded ? 0 : 1,
|
|
110
115
|
borderBottomColor: borderColor
|
|
111
116
|
};
|
|
112
117
|
const headerStyle = {
|
|
@@ -143,10 +148,18 @@ function Accordion({
|
|
|
143
148
|
webAccessibilityProps
|
|
144
149
|
});
|
|
145
150
|
const processedChildren = children ? (0, _reactUtils.cloneChildrenWithModes)(_react.default.Children.toArray(children), resolvedModes) : null;
|
|
151
|
+
|
|
152
|
+
// Content-only + collapsed: nothing to render (header lives in the sticky peer).
|
|
153
|
+
if (!showHeader && !isExpanded) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
if (!showHeader && !showContent) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
146
159
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
147
160
|
style: [containerStyle, style],
|
|
148
161
|
...rest,
|
|
149
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
|
|
162
|
+
children: [showHeader && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
|
|
150
163
|
accessibilityRole: "button",
|
|
151
164
|
accessibilityLabel: undefined,
|
|
152
165
|
accessibilityHint: accessibilityHint || (isExpanded ? 'Collapse accordion' : 'Expand accordion'),
|
|
@@ -176,7 +189,7 @@ function Accordion({
|
|
|
176
189
|
accessibilityElementsHidden: true,
|
|
177
190
|
importantForAccessibility: "no"
|
|
178
191
|
})]
|
|
179
|
-
}), isExpanded && processedChildren && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
192
|
+
}), showContent && isExpanded && processedChildren && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
180
193
|
style: contentStyle,
|
|
181
194
|
children: processedChildren
|
|
182
195
|
})]
|
|
@@ -3,22 +3,98 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.
|
|
7
|
-
|
|
6
|
+
exports.JioDot = JioDot;
|
|
7
|
+
exports.default = void 0;
|
|
8
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
8
9
|
var _reactNative = require("react-native");
|
|
10
|
+
var _reactNativeSvg = _interopRequireWildcard(require("react-native-svg"));
|
|
9
11
|
var _figmaVariablesResolver = require("../../design-tokens/figma-variables-resolver");
|
|
10
12
|
var _JFSThemeProvider = require("../../design-tokens/JFSThemeProvider");
|
|
11
13
|
var _NavArrow = _interopRequireDefault(require("../NavArrow/NavArrow"));
|
|
12
14
|
var _reactUtils = require("../../utils/react-utils");
|
|
13
15
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
14
16
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
15
|
-
|
|
16
|
-
// (node 449:7876). The middle slot is
|
|
17
|
-
// size; its inner content (node 3991:4125)
|
|
18
|
-
//
|
|
17
|
+
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); }
|
|
18
|
+
// SubPage "slot wrap" geometry from Figma (node 449:7876). The middle slot is
|
|
19
|
+
// an absolutely-centered fixed-size box; its inner content (node 3991:4125)
|
|
20
|
+
// fills / shrinks responsively within that box.
|
|
19
21
|
const SUBPAGE_MIDDLE_DEFAULT_WIDTH = 192;
|
|
20
22
|
const SUBPAGE_MIDDLE_HEIGHT = 32;
|
|
21
23
|
const SUBPAGE_MIDDLE_PADDING_HORIZONTAL = 21;
|
|
24
|
+
const JIODOT_FALLBACK_SIZE = 32;
|
|
25
|
+
/**
|
|
26
|
+
* Figma "jiodot" mark (node 1117:1908). Opt-in via `leadingSlot` — MainPage
|
|
27
|
+
* does NOT render this by default so existing consumers stay empty-leading.
|
|
28
|
+
*/
|
|
29
|
+
function JioDot({
|
|
30
|
+
size,
|
|
31
|
+
modes = _reactUtils.EMPTY_MODES
|
|
32
|
+
}) {
|
|
33
|
+
const resolvedSize = size ?? (Number((0, _figmaVariablesResolver.getVariableByName)('appBar/mainPage/jiodot/size', modes)) || JIODOT_FALLBACK_SIZE);
|
|
34
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeSvg.default, {
|
|
35
|
+
width: resolvedSize,
|
|
36
|
+
height: resolvedSize,
|
|
37
|
+
viewBox: "0 0 32 32",
|
|
38
|
+
fill: "none",
|
|
39
|
+
accessibilityRole: "image",
|
|
40
|
+
accessibilityLabel: "Jio",
|
|
41
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeSvg.G, {
|
|
42
|
+
clipPath: "url(#jiodotClip)",
|
|
43
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Path, {
|
|
44
|
+
d: "M0 16C0 7.16344 7.16344 0 16 0C24.8366 0 32 7.16344 32 16C32 24.8366 24.8366 32 16 32C7.16344 32 0 24.8366 0 16Z",
|
|
45
|
+
fill: "url(#jiodotGrad)"
|
|
46
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Path, {
|
|
47
|
+
d: "M11.3039 9.64982H10.7705C9.75837 9.64982 9.20548 10.2205 9.20548 11.3621V16.8679C9.20548 18.2854 8.72667 18.7829 7.60341 18.7829C6.71985 18.7829 6.00192 18.3959 5.43096 17.6963C5.37555 17.6229 4.21615 18.1749 4.21615 19.5378C4.21615 21.0107 5.59629 21.9132 8.15689 21.9132C11.268 21.9132 12.9077 20.3479 12.9077 16.9233V11.3621C12.9059 10.222 12.3536 9.64982 11.3039 9.64982ZM23.7039 12.3381C20.6837 12.3381 18.6769 14.2531 18.6769 17.1085C18.6769 20.0365 20.6096 21.915 23.6482 21.915C26.6674 21.915 28.6556 20.0365 28.6556 17.1274C28.6573 14.2531 26.6867 12.3381 23.7039 12.3381ZM23.6668 19.206C22.4879 19.206 21.6781 18.3408 21.6781 17.107C21.6781 15.8922 22.5077 15.0264 23.6668 15.0264C24.8259 15.0264 25.6553 15.8922 25.6553 17.1257C25.6556 18.3221 24.8084 19.206 23.6668 19.206ZM16.1252 12.43H15.7563C14.8547 12.43 14.1735 12.8531 14.1735 14.1426V20.0347C14.1735 21.3423 14.8366 21.7476 15.7936 21.7476H16.1622C17.0644 21.7476 17.7086 21.3052 17.7086 20.0347V14.1426C17.7086 12.8169 17.0831 12.43 16.1252 12.43ZM15.9228 8.15885C14.7809 8.15885 14.063 8.80359 14.063 9.81634C14.063 10.8469 14.7999 11.4913 15.9779 11.4913C17.1196 11.4913 17.8378 10.8469 17.8378 9.81634C17.8378 8.78582 17.1012 8.15885 15.9228 8.15885Z",
|
|
48
|
+
fill: "white"
|
|
49
|
+
})]
|
|
50
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeSvg.Defs, {
|
|
51
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeSvg.LinearGradient, {
|
|
52
|
+
id: "jiodotGrad",
|
|
53
|
+
x1: "11.085",
|
|
54
|
+
y1: "9.25442",
|
|
55
|
+
x2: "22.3469",
|
|
56
|
+
y2: "15.7638",
|
|
57
|
+
gradientUnits: "userSpaceOnUse",
|
|
58
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Stop, {
|
|
59
|
+
stopColor: "#FBDCAF"
|
|
60
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Stop, {
|
|
61
|
+
offset: "1",
|
|
62
|
+
stopColor: "#AD8444"
|
|
63
|
+
})]
|
|
64
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.ClipPath, {
|
|
65
|
+
id: "jiodotClip",
|
|
66
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Rect, {
|
|
67
|
+
width: "32",
|
|
68
|
+
height: "32",
|
|
69
|
+
fill: "white"
|
|
70
|
+
})
|
|
71
|
+
})]
|
|
72
|
+
})]
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* AppBar — top navigation bar for MainPage and SubPage layouts
|
|
77
|
+
* (Figma node 1070:18571).
|
|
78
|
+
*
|
|
79
|
+
* Visual values resolve from design tokens via `getVariableByName`. Slots
|
|
80
|
+
* cascade `modes` to children through `cloneChildrenWithModes`.
|
|
81
|
+
*
|
|
82
|
+
* @component
|
|
83
|
+
* @example
|
|
84
|
+
* ```tsx
|
|
85
|
+
* <AppBar
|
|
86
|
+
* type="MainPage"
|
|
87
|
+
* leadingSlot={<JioDot />}
|
|
88
|
+
* actionsSlot={
|
|
89
|
+
* <>
|
|
90
|
+
* <IconButton iconName="ic_add" />
|
|
91
|
+
* <IconButton iconName="ic_add" />
|
|
92
|
+
* <Avatar />
|
|
93
|
+
* </>
|
|
94
|
+
* }
|
|
95
|
+
* />
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
22
98
|
function AppBar({
|
|
23
99
|
type = 'MainPage',
|
|
24
100
|
leadingSlot,
|
|
@@ -35,118 +111,70 @@ function AppBar({
|
|
|
35
111
|
const {
|
|
36
112
|
modes: globalModes
|
|
37
113
|
} = (0, _JFSThemeProvider.useTokens)();
|
|
38
|
-
const modes = {
|
|
114
|
+
const modes = (0, _react.useMemo)(() => ({
|
|
39
115
|
Context2: 'AppBar',
|
|
40
116
|
...globalModes,
|
|
41
117
|
...propModes
|
|
42
|
-
};
|
|
118
|
+
}), [globalModes, propModes]);
|
|
43
119
|
const isMain = type === 'MainPage';
|
|
44
120
|
const isSub = type === 'SubPage';
|
|
45
|
-
|
|
46
|
-
// --- Tokens ---
|
|
47
|
-
// Construct token keys based on type. keys must be camelCase as per instruction.
|
|
48
|
-
// We use explicit string literals so that the extraction script can find them.
|
|
49
121
|
const paddingHorizontal = isMain ? (0, _figmaVariablesResolver.getVariableByName)('appBar/mainPage/padding/horizontal', modes) : (0, _figmaVariablesResolver.getVariableByName)('appBar/subPage/padding/horizontal', modes);
|
|
50
122
|
const paddingVertical = isMain ? (0, _figmaVariablesResolver.getVariableByName)('appBar/mainPage/padding/vertical', modes) : (0, _figmaVariablesResolver.getVariableByName)('appBar/subPage/padding/vertical', modes);
|
|
51
123
|
const backgroundColor = isMain ? (0, _figmaVariablesResolver.getVariableByName)('appBar/mainPage/background', modes) : (0, _figmaVariablesResolver.getVariableByName)('appBar/subPage/background', modes);
|
|
52
124
|
const actionsGap = isMain ? (0, _figmaVariablesResolver.getVariableByName)('appBar/mainPage/actions/gap', modes) : (0, _figmaVariablesResolver.getVariableByName)('appBar/subPage/actions/gap', modes);
|
|
53
|
-
|
|
54
|
-
// Layout styles
|
|
55
125
|
const containerStyle = {
|
|
56
126
|
flexDirection: 'row',
|
|
57
127
|
alignItems: 'center',
|
|
58
|
-
// No `justifyContent` here: with the inline middle slot using `flex: 1`
|
|
59
|
-
// the three sections lay out naturally (leading | middle | actions).
|
|
60
|
-
// When middleSlot is absent we fall back to `space-between` at the wrapper
|
|
61
|
-
// level so leading & actions still anchor to the edges.
|
|
62
128
|
paddingHorizontal: paddingHorizontal ?? 16,
|
|
63
129
|
paddingVertical: paddingVertical ?? (isMain ? 16 : 10),
|
|
64
130
|
backgroundColor: backgroundColor ?? '#FFFFFF'
|
|
65
|
-
// MainPage: h=68 (16 top/bot padding? 36 height content?)
|
|
66
|
-
// SubPage: h=52
|
|
67
131
|
};
|
|
68
132
|
|
|
69
|
-
//
|
|
70
|
-
// SubPage default
|
|
71
|
-
// MainPage default: Jio Logo (We'll use a placeholder or basic text if no asset,
|
|
72
|
-
// but looking at "AppBarHome" before, it had "ic_hellojio". Let's stick to generic defaults or empty if not provided,
|
|
73
|
-
// but usually a component library provides sensible defaults.)
|
|
74
|
-
|
|
75
|
-
// Actually, let's implement the specific defaults mentioned in the plan/analysis
|
|
76
|
-
// SubPage: NavArrow props (icon, etc).
|
|
77
|
-
|
|
133
|
+
// MainPage: no default leading (backward compatible — consumers opt in via
|
|
134
|
+
// leadingSlot, e.g. <JioDot />). SubPage: default back NavArrow.
|
|
78
135
|
let defaultLeading = null;
|
|
79
|
-
if (leadingSlot === undefined) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
});
|
|
100
|
-
} else {
|
|
101
|
-
// MainPage default leading: often the Brand Logo.
|
|
102
|
-
// We'll leave it empty or render a placeholder if desired, but standard AppBar often lets consumer pass logo.
|
|
103
|
-
// Previous code had `leadingIconName='ic_hellojio'` for Home.
|
|
104
|
-
// We will render nothing by default for MainPage leading to keep it flexible, OR a specialized Logo component if we had one.
|
|
105
|
-
// For now, let's assume consumer passes it for MainPage, or we leave empty.
|
|
106
|
-
// Wait, Figma screenshot shows "Jio" logo.
|
|
107
|
-
// The user said: "mostly token namings are 'camelCase'. Maximize existing component usage."
|
|
108
|
-
// I'll leave defaultLeading as null for MainPage unless I have a "BrandLogo" component.
|
|
109
|
-
}
|
|
136
|
+
if (leadingSlot === undefined && isSub) {
|
|
137
|
+
defaultLeading = /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
|
|
138
|
+
...(onLeadingPress ? {
|
|
139
|
+
onPress: onLeadingPress
|
|
140
|
+
} : {}),
|
|
141
|
+
hitSlop: 8,
|
|
142
|
+
accessibilityRole: "button",
|
|
143
|
+
accessibilityLabel: undefined,
|
|
144
|
+
style: ({
|
|
145
|
+
pressed
|
|
146
|
+
}) => ({
|
|
147
|
+
opacity: pressed ? 0.7 : 1,
|
|
148
|
+
alignItems: 'center',
|
|
149
|
+
justifyContent: 'center'
|
|
150
|
+
}),
|
|
151
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_NavArrow.default, {
|
|
152
|
+
direction: "Back",
|
|
153
|
+
modes: modes
|
|
154
|
+
})
|
|
155
|
+
});
|
|
110
156
|
}
|
|
111
|
-
|
|
112
|
-
// --- Process Slots ---
|
|
113
157
|
const processedLeading = leadingSlot ? (0, _reactUtils.cloneChildrenWithModes)([leadingSlot], modes)[0] : defaultLeading;
|
|
114
158
|
const processedMiddle = middleSlot ? (0, _reactUtils.cloneChildrenWithModes)(_react.default.Children.toArray(middleSlot), modes) : null;
|
|
115
|
-
|
|
116
|
-
// Actions Gap wrapper
|
|
117
|
-
// The Figma has "Actions" slot with a gap.
|
|
118
159
|
const actionsStyle = {
|
|
119
160
|
flexDirection: 'row',
|
|
120
161
|
alignItems: 'center',
|
|
121
162
|
gap: actionsGap ?? 0
|
|
122
163
|
};
|
|
123
|
-
const processedActions = actionsSlot ?
|
|
124
|
-
style: actionsStyle,
|
|
125
|
-
children: (0, _reactUtils.cloneChildrenWithModes)(_react.default.Children.toArray(actionsSlot), modes)
|
|
126
|
-
}) : null;
|
|
164
|
+
const processedActions = actionsSlot ? (0, _reactUtils.cloneChildrenWithModes)(_react.default.Children.toArray(actionsSlot), modes) : null;
|
|
127
165
|
|
|
128
|
-
// SubPage
|
|
129
|
-
// "slot wrap"), so it never participates in the row flow. Only MainPage
|
|
130
|
-
// keeps the legacy in-flow middle slot.
|
|
166
|
+
// SubPage middle is absolute (Figma "slot wrap"); MainPage middle stays in-flow.
|
|
131
167
|
const hasInFlowMiddle = isMain && !!processedMiddle;
|
|
132
168
|
|
|
133
|
-
// With an in-flow middle
|
|
134
|
-
//
|
|
135
|
-
// other cases we pin leading & actions to the outer edges with
|
|
136
|
-
// `space-between`; the SubPage middle floats above, centered.
|
|
169
|
+
// With an in-flow middle the flex:1 middle absorbs leftover space. Otherwise
|
|
170
|
+
// pin leading & actions to the edges; SubPage middle floats centered above.
|
|
137
171
|
const wrapperStyle = {
|
|
138
172
|
...containerStyle,
|
|
139
173
|
justifyContent: hasInFlowMiddle ? 'flex-start' : 'space-between'
|
|
140
174
|
};
|
|
141
175
|
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
// Android resolves the percentage against an intermediate, content-driven
|
|
145
|
-
// parent height and lands a few px too high), the overlay STRETCHES to fill
|
|
146
|
-
// the whole bar (`top/bottom/left/right: 0`) and centers its content with
|
|
147
|
-
// flexbox. This uses the exact same full-height vertical reference as the
|
|
148
|
-
// leading/actions row (`alignItems: 'center'`), so the middle content always
|
|
149
|
-
// sits on the same line as the back arrow and end slot on every platform.
|
|
176
|
+
// Overlay stretches to the full bar and centers content with flexbox so the
|
|
177
|
+
// middle aligns with leading/actions on every platform.
|
|
150
178
|
const subPageMiddleOverlayStyle = {
|
|
151
179
|
position: 'absolute',
|
|
152
180
|
top: 0,
|
|
@@ -157,10 +185,6 @@ function AppBar({
|
|
|
157
185
|
alignItems: 'center',
|
|
158
186
|
justifyContent: 'center'
|
|
159
187
|
};
|
|
160
|
-
|
|
161
|
-
// Fixed-width clipping box (mirrors the Figma "slot wrap" geometry): keeps the
|
|
162
|
-
// middle content from bleeding under the leading/actions slots while staying
|
|
163
|
-
// centered within the overlay above.
|
|
164
188
|
const subPageMiddleBoxStyle = {
|
|
165
189
|
width: middleSlotWidth,
|
|
166
190
|
height: SUBPAGE_MIDDLE_HEIGHT,
|
|
@@ -184,7 +208,7 @@ function AppBar({
|
|
|
184
208
|
alignItems: 'center'
|
|
185
209
|
},
|
|
186
210
|
children: processedLeading
|
|
187
|
-
}), hasInFlowMiddle
|
|
211
|
+
}), hasInFlowMiddle ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
188
212
|
style: {
|
|
189
213
|
flex: 1,
|
|
190
214
|
minWidth: 0,
|
|
@@ -194,10 +218,10 @@ function AppBar({
|
|
|
194
218
|
},
|
|
195
219
|
pointerEvents: "box-none",
|
|
196
220
|
children: processedMiddle
|
|
197
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
221
|
+
}) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
198
222
|
style: actionsStyle,
|
|
199
223
|
children: processedActions
|
|
200
|
-
}), isSub && processedMiddle
|
|
224
|
+
}), isSub && processedMiddle ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
201
225
|
style: subPageMiddleOverlayStyle,
|
|
202
226
|
pointerEvents: "box-none",
|
|
203
227
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
@@ -216,6 +240,7 @@ function AppBar({
|
|
|
216
240
|
children: processedMiddle
|
|
217
241
|
})
|
|
218
242
|
})
|
|
219
|
-
})]
|
|
243
|
+
}) : null]
|
|
220
244
|
});
|
|
221
|
-
}
|
|
245
|
+
}
|
|
246
|
+
var _default = exports.default = AppBar;
|
|
@@ -114,7 +114,9 @@ function CompareTable({
|
|
|
114
114
|
style,
|
|
115
115
|
disableTruncation,
|
|
116
116
|
columnWidth,
|
|
117
|
-
|
|
117
|
+
stickyCards = true,
|
|
118
|
+
stickyAccordionTitles = true,
|
|
119
|
+
stickyHeaders = false,
|
|
118
120
|
stickyHeaderLabel = true
|
|
119
121
|
}) {
|
|
120
122
|
// --- selection card tokens ------------------------------------------------
|
|
@@ -129,11 +131,10 @@ function CompareTable({
|
|
|
129
131
|
const cardLabelFontFamily = (0, _figmaVariablesResolver.getVariableByName)('selectionCard/label/fontfamily', modes) ?? 'JioType Var';
|
|
130
132
|
const cardLabelFontWeight = (0, _figmaVariablesResolver.getVariableByName)('selectionCard/label/fontweight', modes) ?? 700;
|
|
131
133
|
const imageRadius = (0, _figmaVariablesResolver.getVariableByName)('image/radius', modes) ?? 8;
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
const cardRadius = (0, _figmaVariablesResolver.getVariableByName)('selectionCard/radius', modes) ?? 0;
|
|
134
|
+
// Selection cards are square — no corner radius (Figma compare-table
|
|
135
|
+
// treatment). Ignore `selectionCard/radius` so a non-zero token value
|
|
136
|
+
// cannot round the card edges.
|
|
137
|
+
const cardRadius = 0;
|
|
137
138
|
|
|
138
139
|
// --- add-a-card icon button tokens ---------------------------------------
|
|
139
140
|
// padding/size/border come from the shared iconButton tokens; the filled
|
|
@@ -192,6 +193,16 @@ function CompareTable({
|
|
|
192
193
|
* scrollTo echoes from synced peers are ignored, eliminating jitter. */
|
|
193
194
|
const draggingSource = (0, _react.useRef)(null);
|
|
194
195
|
const lastSyncX = (0, _react.useRef)(0);
|
|
196
|
+
|
|
197
|
+
// Controlled expand state for sticky accordion titles (header + body are
|
|
198
|
+
// split across two Accordion instances that must stay in sync).
|
|
199
|
+
const [expandedByIndex, setExpandedByIndex] = (0, _react.useState)(() => {
|
|
200
|
+
const initial = {};
|
|
201
|
+
sections.forEach((section, index) => {
|
|
202
|
+
initial[index] = section.defaultExpanded ?? index === 0;
|
|
203
|
+
});
|
|
204
|
+
return initial;
|
|
205
|
+
});
|
|
195
206
|
const syncOthers = (0, _react.useCallback)((source, x) => {
|
|
196
207
|
if (source !== 'cards' && cardsScrollRef.current) {
|
|
197
208
|
cardsScrollRef.current.scrollTo({
|
|
@@ -300,6 +311,10 @@ function CompareTable({
|
|
|
300
311
|
minHeight: 147,
|
|
301
312
|
backgroundColor: cardBg,
|
|
302
313
|
borderRadius: cardRadius,
|
|
314
|
+
borderTopLeftRadius: cardRadius,
|
|
315
|
+
borderTopRightRadius: cardRadius,
|
|
316
|
+
borderBottomLeftRadius: cardRadius,
|
|
317
|
+
borderBottomRightRadius: cardRadius,
|
|
303
318
|
alignItems: 'center',
|
|
304
319
|
justifyContent: 'center',
|
|
305
320
|
gap: cardGap,
|
|
@@ -513,6 +528,7 @@ function CompareTable({
|
|
|
513
528
|
backgroundColor: cardBg,
|
|
514
529
|
borderWidth: 1,
|
|
515
530
|
borderColor: '#e8e8e8',
|
|
531
|
+
borderRadius: 0,
|
|
516
532
|
overflow: 'hidden'
|
|
517
533
|
};
|
|
518
534
|
|
|
@@ -541,14 +557,18 @@ function CompareTable({
|
|
|
541
557
|
}
|
|
542
558
|
|
|
543
559
|
// --- fixed-width scroll mode: chained horizontal sync + sticky cards -----
|
|
544
|
-
//
|
|
545
|
-
//
|
|
546
|
-
//
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
560
|
+
// Sticky cards are pinned *outside* the vertical ScrollView (sibling above
|
|
561
|
+
// it) so they cannot scroll away — `stickyHeaderIndices` is unreliable for
|
|
562
|
+
// a nested horizontal ScrollView on Fabric. Sticky accordion titles /
|
|
563
|
+
// table headers still use stickyHeaderIndices inside the scroll surface
|
|
564
|
+
// and therefore stick directly under the pinned cards.
|
|
565
|
+
const renderCardsRow = () => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
566
|
+
style: {
|
|
567
|
+
width: '100%',
|
|
568
|
+
backgroundColor: cardBg,
|
|
569
|
+
zIndex: 2
|
|
570
|
+
},
|
|
571
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
|
|
552
572
|
ref: cardsScrollRef,
|
|
553
573
|
horizontal: true,
|
|
554
574
|
showsHorizontalScrollIndicator: false,
|
|
@@ -564,11 +584,51 @@ function CompareTable({
|
|
|
564
584
|
},
|
|
565
585
|
children: [columns.map(renderCard), showAddCard && renderAddCard()]
|
|
566
586
|
})
|
|
567
|
-
}
|
|
587
|
+
})
|
|
588
|
+
}, "cards-row");
|
|
589
|
+
const scrollChildren = [];
|
|
590
|
+
const stickyIndices = [];
|
|
591
|
+
|
|
592
|
+
// Non-sticky cards scroll away with the sections (inside the ScrollView).
|
|
593
|
+
if (showCardsRow && !stickyCards) {
|
|
594
|
+
scrollChildren.push(renderCardsRow());
|
|
568
595
|
}
|
|
569
596
|
sections.forEach((section, index) => {
|
|
570
597
|
const sectionKey = section.key ?? section.title ?? index;
|
|
571
598
|
const headerContent = renderHeaderContent(section);
|
|
599
|
+
const expanded = expandedByIndex[index] ?? section.defaultExpanded ?? index === 0;
|
|
600
|
+
const onExpandedChange = next => {
|
|
601
|
+
setExpandedByIndex(prev => ({
|
|
602
|
+
...prev,
|
|
603
|
+
[index]: next
|
|
604
|
+
}));
|
|
605
|
+
};
|
|
606
|
+
const inlineHeader = !stickyHeaders && headerContent != null ? stickyHeaderLabel ? headerContent : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
|
|
607
|
+
horizontal: true,
|
|
608
|
+
showsHorizontalScrollIndicator: false,
|
|
609
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
610
|
+
style: {
|
|
611
|
+
width: contentWidth
|
|
612
|
+
},
|
|
613
|
+
children: headerContent
|
|
614
|
+
})
|
|
615
|
+
}) : null;
|
|
616
|
+
if (stickyAccordionTitles) {
|
|
617
|
+
stickyIndices.push(scrollChildren.length);
|
|
618
|
+
scrollChildren.push(/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
619
|
+
style: {
|
|
620
|
+
width: '100%',
|
|
621
|
+
backgroundColor: cardBg
|
|
622
|
+
},
|
|
623
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Accordion.default, {
|
|
624
|
+
title: section.title,
|
|
625
|
+
expanded: expanded,
|
|
626
|
+
onExpandedChange: onExpandedChange,
|
|
627
|
+
modes: modes,
|
|
628
|
+
showContent: false
|
|
629
|
+
})
|
|
630
|
+
}, `sticky-accordion-title-${sectionKey}`));
|
|
631
|
+
}
|
|
572
632
|
if (stickyHeaders && headerContent != null) {
|
|
573
633
|
stickyIndices.push(scrollChildren.length);
|
|
574
634
|
scrollChildren.push(/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
@@ -587,37 +647,42 @@ function CompareTable({
|
|
|
587
647
|
})
|
|
588
648
|
}, `sticky-header-${sectionKey}`));
|
|
589
649
|
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
children:
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
650
|
+
if (stickyAccordionTitles) {
|
|
651
|
+
scrollChildren.push(/*#__PURE__*/(0, _jsxRuntime.jsxs)(_Accordion.default, {
|
|
652
|
+
title: section.title,
|
|
653
|
+
expanded: expanded,
|
|
654
|
+
onExpandedChange: onExpandedChange,
|
|
655
|
+
modes: modes,
|
|
656
|
+
showHeader: false,
|
|
657
|
+
children: [inlineHeader, renderBodyContent(section, index)]
|
|
658
|
+
}, sectionKey));
|
|
659
|
+
} else {
|
|
660
|
+
scrollChildren.push(/*#__PURE__*/(0, _jsxRuntime.jsxs)(_Accordion.default, {
|
|
661
|
+
title: section.title,
|
|
662
|
+
defaultExpanded: section.defaultExpanded ?? index === 0,
|
|
663
|
+
modes: modes,
|
|
664
|
+
children: [inlineHeader, renderBodyContent(section, index)]
|
|
665
|
+
}, sectionKey));
|
|
666
|
+
}
|
|
605
667
|
});
|
|
606
|
-
return /*#__PURE__*/(0, _jsxRuntime.
|
|
668
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
607
669
|
style: [outerStyle, style, {
|
|
608
670
|
flexDirection: 'column',
|
|
609
671
|
flex: 1
|
|
610
672
|
}],
|
|
611
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
|
|
673
|
+
children: [showCardsRow && stickyCards ? renderCardsRow() : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
|
|
612
674
|
style: {
|
|
613
675
|
width: '100%',
|
|
614
676
|
flex: 1
|
|
615
677
|
},
|
|
678
|
+
contentContainerStyle: {
|
|
679
|
+
flexGrow: 1
|
|
680
|
+
},
|
|
616
681
|
stickyHeaderIndices: stickyIndices,
|
|
617
682
|
showsVerticalScrollIndicator: false,
|
|
618
683
|
directionalLockEnabled: true,
|
|
619
684
|
children: scrollChildren
|
|
620
|
-
})
|
|
685
|
+
})]
|
|
621
686
|
});
|
|
622
687
|
}
|
|
623
688
|
var _default = exports.default = CompareTable;
|
|
@@ -333,6 +333,9 @@ const FormField = /*#__PURE__*/(0, _react.forwardRef)(function FormField({
|
|
|
333
333
|
fontWeight: tokens.inputFontWeight,
|
|
334
334
|
padding: 0,
|
|
335
335
|
margin: 0,
|
|
336
|
+
// Match the caret to the tokenized input text color on web (native uses
|
|
337
|
+
// cursorColor / selectionColor on the TextInput below).
|
|
338
|
+
caretColor: tokens.inputTextColor,
|
|
336
339
|
// Remove the default web focus ring; the input row's border acts as the
|
|
337
340
|
// focus indicator via the FormField States cascade.
|
|
338
341
|
outlineStyle: 'none',
|
|
@@ -395,7 +398,12 @@ const FormField = /*#__PURE__*/(0, _react.forwardRef)(function FormField({
|
|
|
395
398
|
onBlur: handleBlur,
|
|
396
399
|
onSubmitEditing: onSubmitEditing,
|
|
397
400
|
placeholder: placeholder ?? '',
|
|
398
|
-
placeholderTextColor: placeholderColor
|
|
401
|
+
placeholderTextColor: placeholderColor
|
|
402
|
+
// Kill platform accent cursors (Android Material green, iOS system
|
|
403
|
+
// blue) — match NoteInput/MoneyValue and use the tokenized text color.
|
|
404
|
+
,
|
|
405
|
+
cursorColor: tokens.inputTextColor,
|
|
406
|
+
selectionColor: tokens.inputTextColor,
|
|
399
407
|
editable: interactive,
|
|
400
408
|
maxLength: maxLength,
|
|
401
409
|
autoFocus: autoFocus,
|
|
@@ -219,23 +219,14 @@ function FullscreenModal({
|
|
|
219
219
|
}), [globalModes, propModes]);
|
|
220
220
|
const rootGap = Number((0, _figmaVariablesResolver.getVariableByName)('fullScreenModal/gap', modes));
|
|
221
221
|
|
|
222
|
-
// Safe-area
|
|
223
|
-
//
|
|
224
|
-
//
|
|
225
|
-
//
|
|
226
|
-
// SafeAreaProvider — every inset is 0, so the layout is unchanged.
|
|
222
|
+
// Safe-area inset so the floating close button clears the status bar / notch.
|
|
223
|
+
// Footer padding stays at the design-token value from ActionFooter — no
|
|
224
|
+
// extra inset is layered on top (the token already accounts for bottom chrome).
|
|
225
|
+
// On web — and anywhere without a SafeAreaProvider — insets.top is 0.
|
|
227
226
|
const insets = (0, _reactNativeSafeAreaContext.useSafeAreaInsets)();
|
|
228
227
|
const closeButtonInsetStyle = (0, _react.useMemo)(() => ({
|
|
229
228
|
top: 12 + insets.top + closeOffsetY
|
|
230
229
|
}), [insets.top, closeOffsetY]);
|
|
231
|
-
// Extend (not replace) the footer's token bottom padding by the bottom inset
|
|
232
|
-
// so the action button never sits under the system navigation area.
|
|
233
|
-
const footerInsetStyle = (0, _react.useMemo)(() => {
|
|
234
|
-
const base = Number((0, _figmaVariablesResolver.getVariableByName)('actionFooter/padding/bottom', modes));
|
|
235
|
-
return {
|
|
236
|
-
paddingBottom: base + insets.bottom
|
|
237
|
-
};
|
|
238
|
-
}, [modes, insets.bottom]);
|
|
239
230
|
|
|
240
231
|
// Drives the background's parallax-free sync with the scroll. The hero media
|
|
241
232
|
// lives at the ROOT (so it is never clipped to the content height and sits
|
|
@@ -396,7 +387,6 @@ function FullscreenModal({
|
|
|
396
387
|
})
|
|
397
388
|
}), footerContent ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_ActionFooter.default, {
|
|
398
389
|
modes: modes,
|
|
399
|
-
style: footerInsetStyle,
|
|
400
390
|
children: footerContent
|
|
401
391
|
}) : null, showClose ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_IconButton.default, {
|
|
402
392
|
iconName: "ic_close",
|