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
|
@@ -62,6 +62,8 @@ function Accordion({
|
|
|
62
62
|
accessibilityState,
|
|
63
63
|
webAccessibilityProps,
|
|
64
64
|
disableTruncation,
|
|
65
|
+
showHeader = true,
|
|
66
|
+
showContent = true,
|
|
65
67
|
...rest
|
|
66
68
|
}) {
|
|
67
69
|
const [internalExpanded, setInternalExpanded] = useState(defaultExpanded);
|
|
@@ -99,8 +101,11 @@ function Accordion({
|
|
|
99
101
|
const contentPaddingTop = getVariableByName('accordion/content/padding/top', resolvedModes) ?? 8;
|
|
100
102
|
const contentPaddingBottom = getVariableByName('accordion/content/padding/bottom', resolvedModes) ?? 8;
|
|
101
103
|
const borderColor = getVariableByName('accordion/border/color', resolvedModes) ?? '#e6e6e6';
|
|
104
|
+
|
|
105
|
+
// Header-only sticky pieces drop the divider while expanded so the paired
|
|
106
|
+
// content Accordion owns the section border.
|
|
102
107
|
const containerStyle = {
|
|
103
|
-
borderBottomWidth: 1,
|
|
108
|
+
borderBottomWidth: !showContent && isExpanded ? 0 : 1,
|
|
104
109
|
borderBottomColor: borderColor
|
|
105
110
|
};
|
|
106
111
|
const headerStyle = {
|
|
@@ -137,10 +142,18 @@ function Accordion({
|
|
|
137
142
|
webAccessibilityProps
|
|
138
143
|
});
|
|
139
144
|
const processedChildren = children ? cloneChildrenWithModes(React.Children.toArray(children), resolvedModes) : null;
|
|
145
|
+
|
|
146
|
+
// Content-only + collapsed: nothing to render (header lives in the sticky peer).
|
|
147
|
+
if (!showHeader && !isExpanded) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
if (!showHeader && !showContent) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
140
153
|
return /*#__PURE__*/_jsxs(View, {
|
|
141
154
|
style: [containerStyle, style],
|
|
142
155
|
...rest,
|
|
143
|
-
children: [/*#__PURE__*/_jsxs(Pressable, {
|
|
156
|
+
children: [showHeader && /*#__PURE__*/_jsxs(Pressable, {
|
|
144
157
|
accessibilityRole: "button",
|
|
145
158
|
accessibilityLabel: undefined,
|
|
146
159
|
accessibilityHint: accessibilityHint || (isExpanded ? 'Collapse accordion' : 'Expand accordion'),
|
|
@@ -170,7 +183,7 @@ function Accordion({
|
|
|
170
183
|
accessibilityElementsHidden: true,
|
|
171
184
|
importantForAccessibility: "no"
|
|
172
185
|
})]
|
|
173
|
-
}), isExpanded && processedChildren && /*#__PURE__*/_jsx(View, {
|
|
186
|
+
}), showContent && isExpanded && processedChildren && /*#__PURE__*/_jsx(View, {
|
|
174
187
|
style: contentStyle,
|
|
175
188
|
children: processedChildren
|
|
176
189
|
})]
|
|
@@ -1,20 +1,94 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import React from 'react';
|
|
3
|
+
import React, { useMemo } from 'react';
|
|
4
4
|
import { View, Pressable } from 'react-native';
|
|
5
|
+
import Svg, { Path, Defs, LinearGradient, Stop, ClipPath, Rect, G } from 'react-native-svg';
|
|
5
6
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
6
7
|
import { useTokens } from '../../design-tokens/JFSThemeProvider';
|
|
7
8
|
import NavArrow from '../NavArrow/NavArrow';
|
|
8
9
|
import { cloneChildrenWithModes, EMPTY_MODES } from '../../utils/react-utils';
|
|
9
10
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
10
|
-
// SubPage "slot wrap" geometry
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// item so it fills / shrinks responsively within that box.
|
|
11
|
+
// SubPage "slot wrap" geometry from Figma (node 449:7876). The middle slot is
|
|
12
|
+
// an absolutely-centered fixed-size box; its inner content (node 3991:4125)
|
|
13
|
+
// fills / shrinks responsively within that box.
|
|
14
14
|
const SUBPAGE_MIDDLE_DEFAULT_WIDTH = 192;
|
|
15
15
|
const SUBPAGE_MIDDLE_HEIGHT = 32;
|
|
16
16
|
const SUBPAGE_MIDDLE_PADDING_HORIZONTAL = 21;
|
|
17
|
-
|
|
17
|
+
const JIODOT_FALLBACK_SIZE = 32;
|
|
18
|
+
/**
|
|
19
|
+
* Figma "jiodot" mark (node 1117:1908). Opt-in via `leadingSlot` — MainPage
|
|
20
|
+
* does NOT render this by default so existing consumers stay empty-leading.
|
|
21
|
+
*/
|
|
22
|
+
export function JioDot({
|
|
23
|
+
size,
|
|
24
|
+
modes = EMPTY_MODES
|
|
25
|
+
}) {
|
|
26
|
+
const resolvedSize = size ?? (Number(getVariableByName('appBar/mainPage/jiodot/size', modes)) || JIODOT_FALLBACK_SIZE);
|
|
27
|
+
return /*#__PURE__*/_jsxs(Svg, {
|
|
28
|
+
width: resolvedSize,
|
|
29
|
+
height: resolvedSize,
|
|
30
|
+
viewBox: "0 0 32 32",
|
|
31
|
+
fill: "none",
|
|
32
|
+
accessibilityRole: "image",
|
|
33
|
+
accessibilityLabel: "Jio",
|
|
34
|
+
children: [/*#__PURE__*/_jsxs(G, {
|
|
35
|
+
clipPath: "url(#jiodotClip)",
|
|
36
|
+
children: [/*#__PURE__*/_jsx(Path, {
|
|
37
|
+
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",
|
|
38
|
+
fill: "url(#jiodotGrad)"
|
|
39
|
+
}), /*#__PURE__*/_jsx(Path, {
|
|
40
|
+
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",
|
|
41
|
+
fill: "white"
|
|
42
|
+
})]
|
|
43
|
+
}), /*#__PURE__*/_jsxs(Defs, {
|
|
44
|
+
children: [/*#__PURE__*/_jsxs(LinearGradient, {
|
|
45
|
+
id: "jiodotGrad",
|
|
46
|
+
x1: "11.085",
|
|
47
|
+
y1: "9.25442",
|
|
48
|
+
x2: "22.3469",
|
|
49
|
+
y2: "15.7638",
|
|
50
|
+
gradientUnits: "userSpaceOnUse",
|
|
51
|
+
children: [/*#__PURE__*/_jsx(Stop, {
|
|
52
|
+
stopColor: "#FBDCAF"
|
|
53
|
+
}), /*#__PURE__*/_jsx(Stop, {
|
|
54
|
+
offset: "1",
|
|
55
|
+
stopColor: "#AD8444"
|
|
56
|
+
})]
|
|
57
|
+
}), /*#__PURE__*/_jsx(ClipPath, {
|
|
58
|
+
id: "jiodotClip",
|
|
59
|
+
children: /*#__PURE__*/_jsx(Rect, {
|
|
60
|
+
width: "32",
|
|
61
|
+
height: "32",
|
|
62
|
+
fill: "white"
|
|
63
|
+
})
|
|
64
|
+
})]
|
|
65
|
+
})]
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* AppBar — top navigation bar for MainPage and SubPage layouts
|
|
70
|
+
* (Figma node 1070:18571).
|
|
71
|
+
*
|
|
72
|
+
* Visual values resolve from design tokens via `getVariableByName`. Slots
|
|
73
|
+
* cascade `modes` to children through `cloneChildrenWithModes`.
|
|
74
|
+
*
|
|
75
|
+
* @component
|
|
76
|
+
* @example
|
|
77
|
+
* ```tsx
|
|
78
|
+
* <AppBar
|
|
79
|
+
* type="MainPage"
|
|
80
|
+
* leadingSlot={<JioDot />}
|
|
81
|
+
* actionsSlot={
|
|
82
|
+
* <>
|
|
83
|
+
* <IconButton iconName="ic_add" />
|
|
84
|
+
* <IconButton iconName="ic_add" />
|
|
85
|
+
* <Avatar />
|
|
86
|
+
* </>
|
|
87
|
+
* }
|
|
88
|
+
* />
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
function AppBar({
|
|
18
92
|
type = 'MainPage',
|
|
19
93
|
leadingSlot,
|
|
20
94
|
middleSlot,
|
|
@@ -30,118 +104,70 @@ export default function AppBar({
|
|
|
30
104
|
const {
|
|
31
105
|
modes: globalModes
|
|
32
106
|
} = useTokens();
|
|
33
|
-
const modes = {
|
|
107
|
+
const modes = useMemo(() => ({
|
|
34
108
|
Context2: 'AppBar',
|
|
35
109
|
...globalModes,
|
|
36
110
|
...propModes
|
|
37
|
-
};
|
|
111
|
+
}), [globalModes, propModes]);
|
|
38
112
|
const isMain = type === 'MainPage';
|
|
39
113
|
const isSub = type === 'SubPage';
|
|
40
|
-
|
|
41
|
-
// --- Tokens ---
|
|
42
|
-
// Construct token keys based on type. keys must be camelCase as per instruction.
|
|
43
|
-
// We use explicit string literals so that the extraction script can find them.
|
|
44
114
|
const paddingHorizontal = isMain ? getVariableByName('appBar/mainPage/padding/horizontal', modes) : getVariableByName('appBar/subPage/padding/horizontal', modes);
|
|
45
115
|
const paddingVertical = isMain ? getVariableByName('appBar/mainPage/padding/vertical', modes) : getVariableByName('appBar/subPage/padding/vertical', modes);
|
|
46
116
|
const backgroundColor = isMain ? getVariableByName('appBar/mainPage/background', modes) : getVariableByName('appBar/subPage/background', modes);
|
|
47
117
|
const actionsGap = isMain ? getVariableByName('appBar/mainPage/actions/gap', modes) : getVariableByName('appBar/subPage/actions/gap', modes);
|
|
48
|
-
|
|
49
|
-
// Layout styles
|
|
50
118
|
const containerStyle = {
|
|
51
119
|
flexDirection: 'row',
|
|
52
120
|
alignItems: 'center',
|
|
53
|
-
// No `justifyContent` here: with the inline middle slot using `flex: 1`
|
|
54
|
-
// the three sections lay out naturally (leading | middle | actions).
|
|
55
|
-
// When middleSlot is absent we fall back to `space-between` at the wrapper
|
|
56
|
-
// level so leading & actions still anchor to the edges.
|
|
57
121
|
paddingHorizontal: paddingHorizontal ?? 16,
|
|
58
122
|
paddingVertical: paddingVertical ?? (isMain ? 16 : 10),
|
|
59
123
|
backgroundColor: backgroundColor ?? '#FFFFFF'
|
|
60
|
-
// MainPage: h=68 (16 top/bot padding? 36 height content?)
|
|
61
|
-
// SubPage: h=52
|
|
62
124
|
};
|
|
63
125
|
|
|
64
|
-
//
|
|
65
|
-
// SubPage default
|
|
66
|
-
// MainPage default: Jio Logo (We'll use a placeholder or basic text if no asset,
|
|
67
|
-
// but looking at "AppBarHome" before, it had "ic_hellojio". Let's stick to generic defaults or empty if not provided,
|
|
68
|
-
// but usually a component library provides sensible defaults.)
|
|
69
|
-
|
|
70
|
-
// Actually, let's implement the specific defaults mentioned in the plan/analysis
|
|
71
|
-
// SubPage: NavArrow props (icon, etc).
|
|
72
|
-
|
|
126
|
+
// MainPage: no default leading (backward compatible — consumers opt in via
|
|
127
|
+
// leadingSlot, e.g. <JioDot />). SubPage: default back NavArrow.
|
|
73
128
|
let defaultLeading = null;
|
|
74
|
-
if (leadingSlot === undefined) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
});
|
|
95
|
-
} else {
|
|
96
|
-
// MainPage default leading: often the Brand Logo.
|
|
97
|
-
// We'll leave it empty or render a placeholder if desired, but standard AppBar often lets consumer pass logo.
|
|
98
|
-
// Previous code had `leadingIconName='ic_hellojio'` for Home.
|
|
99
|
-
// We will render nothing by default for MainPage leading to keep it flexible, OR a specialized Logo component if we had one.
|
|
100
|
-
// For now, let's assume consumer passes it for MainPage, or we leave empty.
|
|
101
|
-
// Wait, Figma screenshot shows "Jio" logo.
|
|
102
|
-
// The user said: "mostly token namings are 'camelCase'. Maximize existing component usage."
|
|
103
|
-
// I'll leave defaultLeading as null for MainPage unless I have a "BrandLogo" component.
|
|
104
|
-
}
|
|
129
|
+
if (leadingSlot === undefined && isSub) {
|
|
130
|
+
defaultLeading = /*#__PURE__*/_jsx(Pressable, {
|
|
131
|
+
...(onLeadingPress ? {
|
|
132
|
+
onPress: onLeadingPress
|
|
133
|
+
} : {}),
|
|
134
|
+
hitSlop: 8,
|
|
135
|
+
accessibilityRole: "button",
|
|
136
|
+
accessibilityLabel: undefined,
|
|
137
|
+
style: ({
|
|
138
|
+
pressed
|
|
139
|
+
}) => ({
|
|
140
|
+
opacity: pressed ? 0.7 : 1,
|
|
141
|
+
alignItems: 'center',
|
|
142
|
+
justifyContent: 'center'
|
|
143
|
+
}),
|
|
144
|
+
children: /*#__PURE__*/_jsx(NavArrow, {
|
|
145
|
+
direction: "Back",
|
|
146
|
+
modes: modes
|
|
147
|
+
})
|
|
148
|
+
});
|
|
105
149
|
}
|
|
106
|
-
|
|
107
|
-
// --- Process Slots ---
|
|
108
150
|
const processedLeading = leadingSlot ? cloneChildrenWithModes([leadingSlot], modes)[0] : defaultLeading;
|
|
109
151
|
const processedMiddle = middleSlot ? cloneChildrenWithModes(React.Children.toArray(middleSlot), modes) : null;
|
|
110
|
-
|
|
111
|
-
// Actions Gap wrapper
|
|
112
|
-
// The Figma has "Actions" slot with a gap.
|
|
113
152
|
const actionsStyle = {
|
|
114
153
|
flexDirection: 'row',
|
|
115
154
|
alignItems: 'center',
|
|
116
155
|
gap: actionsGap ?? 0
|
|
117
156
|
};
|
|
118
|
-
const processedActions = actionsSlot ?
|
|
119
|
-
style: actionsStyle,
|
|
120
|
-
children: cloneChildrenWithModes(React.Children.toArray(actionsSlot), modes)
|
|
121
|
-
}) : null;
|
|
157
|
+
const processedActions = actionsSlot ? cloneChildrenWithModes(React.Children.toArray(actionsSlot), modes) : null;
|
|
122
158
|
|
|
123
|
-
// SubPage
|
|
124
|
-
// "slot wrap"), so it never participates in the row flow. Only MainPage
|
|
125
|
-
// keeps the legacy in-flow middle slot.
|
|
159
|
+
// SubPage middle is absolute (Figma "slot wrap"); MainPage middle stays in-flow.
|
|
126
160
|
const hasInFlowMiddle = isMain && !!processedMiddle;
|
|
127
161
|
|
|
128
|
-
// With an in-flow middle
|
|
129
|
-
//
|
|
130
|
-
// other cases we pin leading & actions to the outer edges with
|
|
131
|
-
// `space-between`; the SubPage middle floats above, centered.
|
|
162
|
+
// With an in-flow middle the flex:1 middle absorbs leftover space. Otherwise
|
|
163
|
+
// pin leading & actions to the edges; SubPage middle floats centered above.
|
|
132
164
|
const wrapperStyle = {
|
|
133
165
|
...containerStyle,
|
|
134
166
|
justifyContent: hasInFlowMiddle ? 'flex-start' : 'space-between'
|
|
135
167
|
};
|
|
136
168
|
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
// Android resolves the percentage against an intermediate, content-driven
|
|
140
|
-
// parent height and lands a few px too high), the overlay STRETCHES to fill
|
|
141
|
-
// the whole bar (`top/bottom/left/right: 0`) and centers its content with
|
|
142
|
-
// flexbox. This uses the exact same full-height vertical reference as the
|
|
143
|
-
// leading/actions row (`alignItems: 'center'`), so the middle content always
|
|
144
|
-
// sits on the same line as the back arrow and end slot on every platform.
|
|
169
|
+
// Overlay stretches to the full bar and centers content with flexbox so the
|
|
170
|
+
// middle aligns with leading/actions on every platform.
|
|
145
171
|
const subPageMiddleOverlayStyle = {
|
|
146
172
|
position: 'absolute',
|
|
147
173
|
top: 0,
|
|
@@ -152,10 +178,6 @@ export default function AppBar({
|
|
|
152
178
|
alignItems: 'center',
|
|
153
179
|
justifyContent: 'center'
|
|
154
180
|
};
|
|
155
|
-
|
|
156
|
-
// Fixed-width clipping box (mirrors the Figma "slot wrap" geometry): keeps the
|
|
157
|
-
// middle content from bleeding under the leading/actions slots while staying
|
|
158
|
-
// centered within the overlay above.
|
|
159
181
|
const subPageMiddleBoxStyle = {
|
|
160
182
|
width: middleSlotWidth,
|
|
161
183
|
height: SUBPAGE_MIDDLE_HEIGHT,
|
|
@@ -179,7 +201,7 @@ export default function AppBar({
|
|
|
179
201
|
alignItems: 'center'
|
|
180
202
|
},
|
|
181
203
|
children: processedLeading
|
|
182
|
-
}), hasInFlowMiddle
|
|
204
|
+
}), hasInFlowMiddle ? /*#__PURE__*/_jsx(View, {
|
|
183
205
|
style: {
|
|
184
206
|
flex: 1,
|
|
185
207
|
minWidth: 0,
|
|
@@ -189,10 +211,10 @@ export default function AppBar({
|
|
|
189
211
|
},
|
|
190
212
|
pointerEvents: "box-none",
|
|
191
213
|
children: processedMiddle
|
|
192
|
-
}), /*#__PURE__*/_jsx(View, {
|
|
214
|
+
}) : null, /*#__PURE__*/_jsx(View, {
|
|
193
215
|
style: actionsStyle,
|
|
194
216
|
children: processedActions
|
|
195
|
-
}), isSub && processedMiddle
|
|
217
|
+
}), isSub && processedMiddle ? /*#__PURE__*/_jsx(View, {
|
|
196
218
|
style: subPageMiddleOverlayStyle,
|
|
197
219
|
pointerEvents: "box-none",
|
|
198
220
|
children: /*#__PURE__*/_jsx(View, {
|
|
@@ -211,6 +233,7 @@ export default function AppBar({
|
|
|
211
233
|
children: processedMiddle
|
|
212
234
|
})
|
|
213
235
|
})
|
|
214
|
-
})]
|
|
236
|
+
}) : null]
|
|
215
237
|
});
|
|
216
|
-
}
|
|
238
|
+
}
|
|
239
|
+
export default AppBar;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import React, { useCallback, useRef } from 'react';
|
|
3
|
+
import React, { useCallback, useRef, useState } from 'react';
|
|
4
4
|
import { View, Text, Pressable, Platform, ScrollView } from 'react-native';
|
|
5
5
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
6
6
|
import { EMPTY_MODES, cloneChildrenWithModes, resolveTruncation } from '../../utils/react-utils';
|
|
@@ -108,7 +108,9 @@ function CompareTable({
|
|
|
108
108
|
style,
|
|
109
109
|
disableTruncation,
|
|
110
110
|
columnWidth,
|
|
111
|
-
|
|
111
|
+
stickyCards = true,
|
|
112
|
+
stickyAccordionTitles = true,
|
|
113
|
+
stickyHeaders = false,
|
|
112
114
|
stickyHeaderLabel = true
|
|
113
115
|
}) {
|
|
114
116
|
// --- selection card tokens ------------------------------------------------
|
|
@@ -123,11 +125,10 @@ function CompareTable({
|
|
|
123
125
|
const cardLabelFontFamily = getVariableByName('selectionCard/label/fontfamily', modes) ?? 'JioType Var';
|
|
124
126
|
const cardLabelFontWeight = getVariableByName('selectionCard/label/fontweight', modes) ?? 700;
|
|
125
127
|
const imageRadius = getVariableByName('image/radius', modes) ?? 8;
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
const cardRadius = getVariableByName('selectionCard/radius', modes) ?? 0;
|
|
128
|
+
// Selection cards are square — no corner radius (Figma compare-table
|
|
129
|
+
// treatment). Ignore `selectionCard/radius` so a non-zero token value
|
|
130
|
+
// cannot round the card edges.
|
|
131
|
+
const cardRadius = 0;
|
|
131
132
|
|
|
132
133
|
// --- add-a-card icon button tokens ---------------------------------------
|
|
133
134
|
// padding/size/border come from the shared iconButton tokens; the filled
|
|
@@ -186,6 +187,16 @@ function CompareTable({
|
|
|
186
187
|
* scrollTo echoes from synced peers are ignored, eliminating jitter. */
|
|
187
188
|
const draggingSource = useRef(null);
|
|
188
189
|
const lastSyncX = useRef(0);
|
|
190
|
+
|
|
191
|
+
// Controlled expand state for sticky accordion titles (header + body are
|
|
192
|
+
// split across two Accordion instances that must stay in sync).
|
|
193
|
+
const [expandedByIndex, setExpandedByIndex] = useState(() => {
|
|
194
|
+
const initial = {};
|
|
195
|
+
sections.forEach((section, index) => {
|
|
196
|
+
initial[index] = section.defaultExpanded ?? index === 0;
|
|
197
|
+
});
|
|
198
|
+
return initial;
|
|
199
|
+
});
|
|
189
200
|
const syncOthers = useCallback((source, x) => {
|
|
190
201
|
if (source !== 'cards' && cardsScrollRef.current) {
|
|
191
202
|
cardsScrollRef.current.scrollTo({
|
|
@@ -294,6 +305,10 @@ function CompareTable({
|
|
|
294
305
|
minHeight: 147,
|
|
295
306
|
backgroundColor: cardBg,
|
|
296
307
|
borderRadius: cardRadius,
|
|
308
|
+
borderTopLeftRadius: cardRadius,
|
|
309
|
+
borderTopRightRadius: cardRadius,
|
|
310
|
+
borderBottomLeftRadius: cardRadius,
|
|
311
|
+
borderBottomRightRadius: cardRadius,
|
|
297
312
|
alignItems: 'center',
|
|
298
313
|
justifyContent: 'center',
|
|
299
314
|
gap: cardGap,
|
|
@@ -507,6 +522,7 @@ function CompareTable({
|
|
|
507
522
|
backgroundColor: cardBg,
|
|
508
523
|
borderWidth: 1,
|
|
509
524
|
borderColor: '#e8e8e8',
|
|
525
|
+
borderRadius: 0,
|
|
510
526
|
overflow: 'hidden'
|
|
511
527
|
};
|
|
512
528
|
|
|
@@ -535,14 +551,18 @@ function CompareTable({
|
|
|
535
551
|
}
|
|
536
552
|
|
|
537
553
|
// --- fixed-width scroll mode: chained horizontal sync + sticky cards -----
|
|
538
|
-
//
|
|
539
|
-
//
|
|
540
|
-
//
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
554
|
+
// Sticky cards are pinned *outside* the vertical ScrollView (sibling above
|
|
555
|
+
// it) so they cannot scroll away — `stickyHeaderIndices` is unreliable for
|
|
556
|
+
// a nested horizontal ScrollView on Fabric. Sticky accordion titles /
|
|
557
|
+
// table headers still use stickyHeaderIndices inside the scroll surface
|
|
558
|
+
// and therefore stick directly under the pinned cards.
|
|
559
|
+
const renderCardsRow = () => /*#__PURE__*/_jsx(View, {
|
|
560
|
+
style: {
|
|
561
|
+
width: '100%',
|
|
562
|
+
backgroundColor: cardBg,
|
|
563
|
+
zIndex: 2
|
|
564
|
+
},
|
|
565
|
+
children: /*#__PURE__*/_jsx(ScrollView, {
|
|
546
566
|
ref: cardsScrollRef,
|
|
547
567
|
horizontal: true,
|
|
548
568
|
showsHorizontalScrollIndicator: false,
|
|
@@ -558,11 +578,51 @@ function CompareTable({
|
|
|
558
578
|
},
|
|
559
579
|
children: [columns.map(renderCard), showAddCard && renderAddCard()]
|
|
560
580
|
})
|
|
561
|
-
}
|
|
581
|
+
})
|
|
582
|
+
}, "cards-row");
|
|
583
|
+
const scrollChildren = [];
|
|
584
|
+
const stickyIndices = [];
|
|
585
|
+
|
|
586
|
+
// Non-sticky cards scroll away with the sections (inside the ScrollView).
|
|
587
|
+
if (showCardsRow && !stickyCards) {
|
|
588
|
+
scrollChildren.push(renderCardsRow());
|
|
562
589
|
}
|
|
563
590
|
sections.forEach((section, index) => {
|
|
564
591
|
const sectionKey = section.key ?? section.title ?? index;
|
|
565
592
|
const headerContent = renderHeaderContent(section);
|
|
593
|
+
const expanded = expandedByIndex[index] ?? section.defaultExpanded ?? index === 0;
|
|
594
|
+
const onExpandedChange = next => {
|
|
595
|
+
setExpandedByIndex(prev => ({
|
|
596
|
+
...prev,
|
|
597
|
+
[index]: next
|
|
598
|
+
}));
|
|
599
|
+
};
|
|
600
|
+
const inlineHeader = !stickyHeaders && headerContent != null ? stickyHeaderLabel ? headerContent : /*#__PURE__*/_jsx(ScrollView, {
|
|
601
|
+
horizontal: true,
|
|
602
|
+
showsHorizontalScrollIndicator: false,
|
|
603
|
+
children: /*#__PURE__*/_jsx(View, {
|
|
604
|
+
style: {
|
|
605
|
+
width: contentWidth
|
|
606
|
+
},
|
|
607
|
+
children: headerContent
|
|
608
|
+
})
|
|
609
|
+
}) : null;
|
|
610
|
+
if (stickyAccordionTitles) {
|
|
611
|
+
stickyIndices.push(scrollChildren.length);
|
|
612
|
+
scrollChildren.push(/*#__PURE__*/_jsx(View, {
|
|
613
|
+
style: {
|
|
614
|
+
width: '100%',
|
|
615
|
+
backgroundColor: cardBg
|
|
616
|
+
},
|
|
617
|
+
children: /*#__PURE__*/_jsx(Accordion, {
|
|
618
|
+
title: section.title,
|
|
619
|
+
expanded: expanded,
|
|
620
|
+
onExpandedChange: onExpandedChange,
|
|
621
|
+
modes: modes,
|
|
622
|
+
showContent: false
|
|
623
|
+
})
|
|
624
|
+
}, `sticky-accordion-title-${sectionKey}`));
|
|
625
|
+
}
|
|
566
626
|
if (stickyHeaders && headerContent != null) {
|
|
567
627
|
stickyIndices.push(scrollChildren.length);
|
|
568
628
|
scrollChildren.push(/*#__PURE__*/_jsx(View, {
|
|
@@ -581,37 +641,42 @@ function CompareTable({
|
|
|
581
641
|
})
|
|
582
642
|
}, `sticky-header-${sectionKey}`));
|
|
583
643
|
}
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
children:
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
644
|
+
if (stickyAccordionTitles) {
|
|
645
|
+
scrollChildren.push(/*#__PURE__*/_jsxs(Accordion, {
|
|
646
|
+
title: section.title,
|
|
647
|
+
expanded: expanded,
|
|
648
|
+
onExpandedChange: onExpandedChange,
|
|
649
|
+
modes: modes,
|
|
650
|
+
showHeader: false,
|
|
651
|
+
children: [inlineHeader, renderBodyContent(section, index)]
|
|
652
|
+
}, sectionKey));
|
|
653
|
+
} else {
|
|
654
|
+
scrollChildren.push(/*#__PURE__*/_jsxs(Accordion, {
|
|
655
|
+
title: section.title,
|
|
656
|
+
defaultExpanded: section.defaultExpanded ?? index === 0,
|
|
657
|
+
modes: modes,
|
|
658
|
+
children: [inlineHeader, renderBodyContent(section, index)]
|
|
659
|
+
}, sectionKey));
|
|
660
|
+
}
|
|
599
661
|
});
|
|
600
|
-
return /*#__PURE__*/
|
|
662
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
601
663
|
style: [outerStyle, style, {
|
|
602
664
|
flexDirection: 'column',
|
|
603
665
|
flex: 1
|
|
604
666
|
}],
|
|
605
|
-
children: /*#__PURE__*/_jsx(ScrollView, {
|
|
667
|
+
children: [showCardsRow && stickyCards ? renderCardsRow() : null, /*#__PURE__*/_jsx(ScrollView, {
|
|
606
668
|
style: {
|
|
607
669
|
width: '100%',
|
|
608
670
|
flex: 1
|
|
609
671
|
},
|
|
672
|
+
contentContainerStyle: {
|
|
673
|
+
flexGrow: 1
|
|
674
|
+
},
|
|
610
675
|
stickyHeaderIndices: stickyIndices,
|
|
611
676
|
showsVerticalScrollIndicator: false,
|
|
612
677
|
directionalLockEnabled: true,
|
|
613
678
|
children: scrollChildren
|
|
614
|
-
})
|
|
679
|
+
})]
|
|
615
680
|
});
|
|
616
681
|
}
|
|
617
682
|
export default CompareTable;
|
|
@@ -327,6 +327,9 @@ const FormField = /*#__PURE__*/forwardRef(function FormField({
|
|
|
327
327
|
fontWeight: tokens.inputFontWeight,
|
|
328
328
|
padding: 0,
|
|
329
329
|
margin: 0,
|
|
330
|
+
// Match the caret to the tokenized input text color on web (native uses
|
|
331
|
+
// cursorColor / selectionColor on the TextInput below).
|
|
332
|
+
caretColor: tokens.inputTextColor,
|
|
330
333
|
// Remove the default web focus ring; the input row's border acts as the
|
|
331
334
|
// focus indicator via the FormField States cascade.
|
|
332
335
|
outlineStyle: 'none',
|
|
@@ -389,7 +392,12 @@ const FormField = /*#__PURE__*/forwardRef(function FormField({
|
|
|
389
392
|
onBlur: handleBlur,
|
|
390
393
|
onSubmitEditing: onSubmitEditing,
|
|
391
394
|
placeholder: placeholder ?? '',
|
|
392
|
-
placeholderTextColor: placeholderColor
|
|
395
|
+
placeholderTextColor: placeholderColor
|
|
396
|
+
// Kill platform accent cursors (Android Material green, iOS system
|
|
397
|
+
// blue) — match NoteInput/MoneyValue and use the tokenized text color.
|
|
398
|
+
,
|
|
399
|
+
cursorColor: tokens.inputTextColor,
|
|
400
|
+
selectionColor: tokens.inputTextColor,
|
|
393
401
|
editable: interactive,
|
|
394
402
|
maxLength: maxLength,
|
|
395
403
|
autoFocus: autoFocus,
|
|
@@ -213,23 +213,14 @@ function FullscreenModal({
|
|
|
213
213
|
}), [globalModes, propModes]);
|
|
214
214
|
const rootGap = Number(getVariableByName('fullScreenModal/gap', modes));
|
|
215
215
|
|
|
216
|
-
// Safe-area
|
|
217
|
-
//
|
|
218
|
-
//
|
|
219
|
-
//
|
|
220
|
-
// SafeAreaProvider — every inset is 0, so the layout is unchanged.
|
|
216
|
+
// Safe-area inset so the floating close button clears the status bar / notch.
|
|
217
|
+
// Footer padding stays at the design-token value from ActionFooter — no
|
|
218
|
+
// extra inset is layered on top (the token already accounts for bottom chrome).
|
|
219
|
+
// On web — and anywhere without a SafeAreaProvider — insets.top is 0.
|
|
221
220
|
const insets = useSafeAreaInsets();
|
|
222
221
|
const closeButtonInsetStyle = useMemo(() => ({
|
|
223
222
|
top: 12 + insets.top + closeOffsetY
|
|
224
223
|
}), [insets.top, closeOffsetY]);
|
|
225
|
-
// Extend (not replace) the footer's token bottom padding by the bottom inset
|
|
226
|
-
// so the action button never sits under the system navigation area.
|
|
227
|
-
const footerInsetStyle = useMemo(() => {
|
|
228
|
-
const base = Number(getVariableByName('actionFooter/padding/bottom', modes));
|
|
229
|
-
return {
|
|
230
|
-
paddingBottom: base + insets.bottom
|
|
231
|
-
};
|
|
232
|
-
}, [modes, insets.bottom]);
|
|
233
224
|
|
|
234
225
|
// Drives the background's parallax-free sync with the scroll. The hero media
|
|
235
226
|
// lives at the ROOT (so it is never clipped to the content height and sits
|
|
@@ -390,7 +381,6 @@ function FullscreenModal({
|
|
|
390
381
|
})
|
|
391
382
|
}), footerContent ? /*#__PURE__*/_jsx(ActionFooter, {
|
|
392
383
|
modes: modes,
|
|
393
|
-
style: footerInsetStyle,
|
|
394
384
|
children: footerContent
|
|
395
385
|
}) : null, showClose ? /*#__PURE__*/_jsx(IconButton, {
|
|
396
386
|
iconName: "ic_close",
|
|
@@ -51,7 +51,15 @@ const STATE_FALLBACKS = {
|
|
|
51
51
|
iconColor: '#24262b'
|
|
52
52
|
},
|
|
53
53
|
IdleJioPlus: {
|
|
54
|
-
|
|
54
|
+
// Glass fill. In Figma the IdleJioPlus surface resolves
|
|
55
|
+
// `helloJioInput/background` under the `Page type = JioPlus` variable mode,
|
|
56
|
+
// which yields a TRANSLUCENT light fill (not the opaque `#f5f5f5` the
|
|
57
|
+
// MainPage mode gives). Painted over the backdrop blur, an opaque value
|
|
58
|
+
// would read as a solid bar and hide the blur entirely — so, matching the
|
|
59
|
+
// translucent glass fallbacks the sibling GlassFill consumers use
|
|
60
|
+
// (FavoriteToggle `#ffffff33`, NumberPagination `rgba(141,141,141,0.4)`),
|
|
61
|
+
// this fallback carries alpha so the frost tints the blurred content.
|
|
62
|
+
background: '#f5f5f566',
|
|
55
63
|
borderColor: '#f5f5f5',
|
|
56
64
|
labelColor: '#545961',
|
|
57
65
|
iconColor: '#545961'
|
|
@@ -88,7 +96,11 @@ function resolveHelloJioInputTokens(modes, visualState) {
|
|
|
88
96
|
// Glass keeps the container transparent so GlassFill shows through.
|
|
89
97
|
backgroundColor: useGlass ? 'transparent' : background,
|
|
90
98
|
borderColor,
|
|
91
|
-
|
|
99
|
+
// The Figma IdleJioPlus variant is a borderless frosted pill — it has no
|
|
100
|
+
// `border/color`/`strokeWidth` tokens. Drawing the Idle border color here
|
|
101
|
+
// would leave a faint light ring the design doesn't have, so drop the
|
|
102
|
+
// border entirely in the glass state.
|
|
103
|
+
borderWidth: useGlass ? 0 : strokeWidth,
|
|
92
104
|
borderStyle: 'solid',
|
|
93
105
|
borderRadius,
|
|
94
106
|
paddingLeft,
|
|
@@ -195,11 +207,15 @@ const HelloJioInput = /*#__PURE__*/forwardRef(function HelloJioInput({
|
|
|
195
207
|
setIsFocused(false);
|
|
196
208
|
onBlur?.(e);
|
|
197
209
|
}, [onBlur]);
|
|
198
|
-
|
|
210
|
+
|
|
211
|
+
// Symmetric with `trailing`: an explicit `null` hides the leading icon
|
|
212
|
+
// (the design's `startSlot={false}`); `undefined` falls back to the
|
|
213
|
+
// default brand icon.
|
|
214
|
+
const leadingElement = leading === undefined ? leadingIconName ? /*#__PURE__*/_jsx(Icon, {
|
|
199
215
|
name: leadingIconName,
|
|
200
216
|
size: tokens.iconSize,
|
|
201
217
|
color: tokens.iconColor
|
|
202
|
-
}) : null
|
|
218
|
+
}) : null : leading;
|
|
203
219
|
const processedLeading = leadingElement ? cloneChildrenWithModes(React.Children.toArray(leadingElement), modes) : null;
|
|
204
220
|
const defaultTrailing = /*#__PURE__*/_jsx(IconButton, {
|
|
205
221
|
iconName: sendIconName,
|