jfs-components 0.0.78 → 0.0.84
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 +11 -0
- package/lib/commonjs/components/AppBar/AppBar.js +56 -6
- package/lib/commonjs/components/Attached/Attached.js +183 -0
- package/lib/commonjs/components/Card/Card.js +25 -2
- package/lib/commonjs/components/Checkbox/Checkbox.js +18 -2
- package/lib/commonjs/components/Drawer/Drawer.js +6 -1
- package/lib/commonjs/components/DropdownInput/DropdownInput.js +30 -6
- package/lib/commonjs/components/ExpandableCheckbox/ExpandableCheckbox.js +17 -11
- package/lib/commonjs/components/FormField/FormField.js +1 -14
- package/lib/commonjs/components/FullscreenModal/FullscreenModal.js +9 -7
- package/lib/commonjs/components/ListItem/ListItem.js +26 -24
- package/lib/commonjs/components/MessageField/MessageField.js +1 -13
- package/lib/commonjs/components/PaymentFeedback/PaymentFeedback.js +12 -9
- package/lib/commonjs/components/PlanComparisonCard/PlanComparisonCard.js +237 -0
- package/lib/commonjs/components/Slot/Slot.js +73 -0
- package/lib/commonjs/components/Spinner/Spinner.js +217 -0
- package/lib/commonjs/components/TextInput/TextInput.js +33 -18
- package/lib/commonjs/components/index.js +28 -0
- package/lib/commonjs/icons/components/IconArrowdown.js +19 -0
- package/lib/commonjs/icons/components/IconArrowup.js +19 -0
- package/lib/commonjs/icons/components/IconChevrondowncircle.js +19 -0
- package/lib/commonjs/icons/components/IconChevronleftcircle.js +19 -0
- package/lib/commonjs/icons/components/IconChevronrightcircle.js +19 -0
- package/lib/commonjs/icons/components/IconChevronupcircle.js +19 -0
- package/lib/commonjs/icons/components/IconOsnavback.js +19 -0
- package/lib/commonjs/icons/components/IconOsnavcenter.js +19 -0
- package/lib/commonjs/icons/components/IconOsnavhome.js +19 -0
- package/lib/commonjs/icons/components/IconOsnavtask.js +19 -0
- package/lib/commonjs/icons/components/IconSignin.js +19 -0
- package/lib/commonjs/icons/components/IconSignout.js +19 -0
- package/lib/commonjs/icons/components/index.js +132 -0
- package/lib/commonjs/icons/registry.js +2 -2
- package/lib/module/components/AppBar/AppBar.js +56 -6
- package/lib/module/components/Attached/Attached.js +178 -0
- package/lib/module/components/Card/Card.js +25 -2
- package/lib/module/components/Checkbox/Checkbox.js +18 -2
- package/lib/module/components/Drawer/Drawer.js +6 -1
- package/lib/module/components/DropdownInput/DropdownInput.js +30 -6
- package/lib/module/components/ExpandableCheckbox/ExpandableCheckbox.js +17 -11
- package/lib/module/components/FormField/FormField.js +3 -16
- package/lib/module/components/FullscreenModal/FullscreenModal.js +9 -7
- package/lib/module/components/ListItem/ListItem.js +26 -24
- package/lib/module/components/MessageField/MessageField.js +3 -15
- package/lib/module/components/PaymentFeedback/PaymentFeedback.js +13 -9
- package/lib/module/components/PlanComparisonCard/PlanComparisonCard.js +234 -0
- package/lib/module/components/Slot/Slot.js +68 -0
- package/lib/module/components/Spinner/Spinner.js +212 -0
- package/lib/module/components/TextInput/TextInput.js +34 -19
- package/lib/module/components/index.js +4 -0
- package/lib/module/icons/components/IconArrowdown.js +12 -0
- package/lib/module/icons/components/IconArrowup.js +12 -0
- package/lib/module/icons/components/IconChevrondowncircle.js +12 -0
- package/lib/module/icons/components/IconChevronleftcircle.js +12 -0
- package/lib/module/icons/components/IconChevronrightcircle.js +12 -0
- package/lib/module/icons/components/IconChevronupcircle.js +12 -0
- package/lib/module/icons/components/IconOsnavback.js +12 -0
- package/lib/module/icons/components/IconOsnavcenter.js +12 -0
- package/lib/module/icons/components/IconOsnavhome.js +12 -0
- package/lib/module/icons/components/IconOsnavtask.js +12 -0
- package/lib/module/icons/components/IconSignin.js +12 -0
- package/lib/module/icons/components/IconSignout.js +12 -0
- package/lib/module/icons/components/index.js +12 -0
- package/lib/module/icons/registry.js +2 -2
- package/lib/typescript/src/components/AppBar/AppBar.d.ts +12 -1
- package/lib/typescript/src/components/Attached/Attached.d.ts +64 -0
- package/lib/typescript/src/components/Card/Card.d.ts +9 -2
- package/lib/typescript/src/components/DropdownInput/DropdownInput.d.ts +3 -2
- package/lib/typescript/src/components/ListItem/ListItem.d.ts +16 -6
- package/lib/typescript/src/components/PaymentFeedback/PaymentFeedback.d.ts +5 -1
- package/lib/typescript/src/components/PlanComparisonCard/PlanComparisonCard.d.ts +66 -0
- package/lib/typescript/src/components/Slot/Slot.d.ts +52 -0
- package/lib/typescript/src/components/Spinner/Spinner.d.ts +45 -0
- package/lib/typescript/src/components/index.d.ts +4 -0
- package/lib/typescript/src/icons/components/IconArrowdown.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconArrowup.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconChevrondowncircle.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconChevronleftcircle.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconChevronrightcircle.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconChevronupcircle.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconOsnavback.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconOsnavcenter.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconOsnavhome.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconOsnavtask.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconSignin.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconSignout.d.ts +3 -0
- package/lib/typescript/src/icons/components/index.d.ts +12 -0
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +3 -2
- package/src/components/AppBar/AppBar.tsx +79 -12
- package/src/components/Attached/Attached.tsx +237 -0
- package/src/components/Card/Card.tsx +28 -1
- package/src/components/Checkbox/Checkbox.tsx +14 -2
- package/src/components/Drawer/Drawer.tsx +4 -0
- package/src/components/DropdownInput/DropdownInput.tsx +54 -20
- package/src/components/ExpandableCheckbox/ExpandableCheckbox.tsx +13 -9
- package/src/components/FormField/FormField.tsx +3 -19
- package/src/components/FullscreenModal/FullscreenModal.tsx +6 -3
- package/src/components/ListItem/ListItem.tsx +42 -25
- package/src/components/MessageField/MessageField.tsx +3 -18
- package/src/components/PaymentFeedback/PaymentFeedback.tsx +15 -8
- package/src/components/PlanComparisonCard/PlanComparisonCard.tsx +316 -0
- package/src/components/Slot/Slot.tsx +91 -0
- package/src/components/Spinner/Spinner.tsx +273 -0
- package/src/components/TextInput/TextInput.tsx +37 -19
- package/src/components/index.ts +4 -0
- package/src/icons/components/IconArrowdown.tsx +11 -0
- package/src/icons/components/IconArrowup.tsx +11 -0
- package/src/icons/components/IconChevrondowncircle.tsx +11 -0
- package/src/icons/components/IconChevronleftcircle.tsx +11 -0
- package/src/icons/components/IconChevronrightcircle.tsx +11 -0
- package/src/icons/components/IconChevronupcircle.tsx +11 -0
- package/src/icons/components/IconOsnavback.tsx +11 -0
- package/src/icons/components/IconOsnavcenter.tsx +11 -0
- package/src/icons/components/IconOsnavhome.tsx +11 -0
- package/src/icons/components/IconOsnavtask.tsx +11 -0
- package/src/icons/components/IconSignin.tsx +11 -0
- package/src/icons/components/IconSignout.tsx +11 -0
- package/src/icons/components/index.ts +12 -0
- package/src/icons/registry.ts +49 -1
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import React, { useCallback, useMemo, useRef } from 'react';
|
|
4
4
|
import { View, Text, Pressable, Platform } from 'react-native';
|
|
5
5
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
6
|
-
import IconCapsule from '../IconCapsule/IconCapsule';
|
|
7
6
|
import NavArrow from '../NavArrow/NavArrow';
|
|
8
7
|
import { usePressableWebSupport } from '../../utils/web-platform-utils';
|
|
9
8
|
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils';
|
|
@@ -15,9 +14,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
15
14
|
const IS_IOS = Platform.OS === 'ios';
|
|
16
15
|
const PRESS_DELAY = IS_IOS ? 130 : 0;
|
|
17
16
|
|
|
18
|
-
// Forced modes for the
|
|
19
|
-
// overridden by external modes. Frozen so identity is stable across
|
|
20
|
-
|
|
17
|
+
// Forced modes for the leading/trailing slots — `Context: 'ListItem'` can
|
|
18
|
+
// never be overridden by external modes. Frozen so identity is stable across
|
|
19
|
+
// renders. Applied to both slots so they cascade modes identically.
|
|
20
|
+
const SLOT_FORCED_MODES = Object.freeze({
|
|
21
21
|
Context: 'ListItem'
|
|
22
22
|
});
|
|
23
23
|
|
|
@@ -32,7 +32,7 @@ const pressedOverlayStyle = {
|
|
|
32
32
|
// ---------------------------------------------------------------------------
|
|
33
33
|
|
|
34
34
|
function resolveListItemTokens(modes) {
|
|
35
|
-
// Modes used to cascade into slot children (leading / supportSlot /
|
|
35
|
+
// Modes used to cascade into slot children (leading / supportSlot / trailing).
|
|
36
36
|
// We do NOT inject an `AppearanceBrand` default here: slot content such as
|
|
37
37
|
// Buttons or Badges carry their own intended appearance, so forcing one onto
|
|
38
38
|
// them would be surprising.
|
|
@@ -131,9 +131,11 @@ const verticalSupportTextOverride = {
|
|
|
131
131
|
* - **design-token driven styling** via `getVariableByName` and `modes`
|
|
132
132
|
*
|
|
133
133
|
* Wherever the Figma layer name contains "Slot", this component exposes a
|
|
134
|
-
* dedicated React "slot" prop
|
|
134
|
+
* dedicated React "slot" prop. The leading and trailing edges share a
|
|
135
|
+
* symmetric `leading` / `trailing` slot API:
|
|
136
|
+
* - Slot "leading" → `leading`
|
|
135
137
|
* - Slot "support text" → `supportSlot`
|
|
136
|
-
* - Slot "
|
|
138
|
+
* - Slot "trailing" → `trailing`
|
|
137
139
|
*
|
|
138
140
|
* @component
|
|
139
141
|
* @param {Object} props
|
|
@@ -141,9 +143,9 @@ const verticalSupportTextOverride = {
|
|
|
141
143
|
* @param {string} [props.title='Title'] - Primary title used in the horizontal layout.
|
|
142
144
|
* @param {string} [props.supportText='Support Text'] - Support text used in both layouts when `supportSlot` is not provided.
|
|
143
145
|
* @param {boolean} [props.showSupportText=true] - Toggles rendering of the support text in Horizontal layout.
|
|
144
|
-
* @param {React.ReactNode} [props.leading] - Optional leading
|
|
146
|
+
* @param {React.ReactNode|null} [props.leading] - Optional leading slot. Omitted or `null` renders nothing.
|
|
145
147
|
* @param {React.ReactNode} [props.supportSlot] - Optional custom slot used instead of the default support text block.
|
|
146
|
-
* @param {React.ReactNode} [props.
|
|
148
|
+
* @param {React.ReactNode} [props.trailing] - Optional trailing slot (Figma Slot "trailing"). Horizontal layout only.
|
|
147
149
|
* @param {boolean} [props.navArrow=true] - Whether to show NavArrow on the far right (Horizontal layout only).
|
|
148
150
|
* @param {Object} [props.modes={}] - Modes object passed to `getVariableByName` for all design tokens.
|
|
149
151
|
* @param {Function} [props.onPress] - When provided, the entire item becomes pressable (navigation variant).
|
|
@@ -172,6 +174,7 @@ function ListItemImpl({
|
|
|
172
174
|
showSupportText = true,
|
|
173
175
|
leading,
|
|
174
176
|
supportSlot,
|
|
177
|
+
trailing,
|
|
175
178
|
endSlot,
|
|
176
179
|
navArrow = true,
|
|
177
180
|
modes = EMPTY_MODES,
|
|
@@ -209,13 +212,9 @@ function ListItemImpl({
|
|
|
209
212
|
// Process leading slot to pass modes to children. Memoized on
|
|
210
213
|
// (leading, resolvedModes) so a parent re-render doesn't re-walk the tree.
|
|
211
214
|
const leadingElement = useMemo(() => {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
modes: tokens.resolvedModes,
|
|
216
|
-
accessibilityLabel: undefined
|
|
217
|
-
});
|
|
218
|
-
}
|
|
215
|
+
if (leading == null) return null;
|
|
216
|
+
const processed = cloneChildrenWithModes(React.Children.toArray(leading), tokens.resolvedModes, SLOT_FORCED_MODES);
|
|
217
|
+
if (processed.length === 0) return null;
|
|
219
218
|
return processed.length === 1 ? processed[0] : processed;
|
|
220
219
|
}, [leading, tokens.resolvedModes]);
|
|
221
220
|
const processedSupportSlot = useMemo(() => {
|
|
@@ -223,11 +222,14 @@ function ListItemImpl({
|
|
|
223
222
|
const processed = cloneChildrenWithModes(React.Children.toArray(supportSlot), tokens.resolvedModes);
|
|
224
223
|
return processed.length === 1 ? processed[0] : processed;
|
|
225
224
|
}, [supportSlot, tokens.resolvedModes]);
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
225
|
+
|
|
226
|
+
// `trailing` wins; `endSlot` is the deprecated alias kept for back-compat.
|
|
227
|
+
const trailingContent = trailing ?? endSlot;
|
|
228
|
+
const processedTrailing = useMemo(() => {
|
|
229
|
+
if (!trailingContent) return null;
|
|
230
|
+
const processed = cloneChildrenWithModes(React.Children.toArray(trailingContent), tokens.resolvedModes, SLOT_FORCED_MODES);
|
|
229
231
|
return processed.length === 1 ? processed[0] : processed;
|
|
230
|
-
}, [
|
|
232
|
+
}, [trailingContent, tokens.resolvedModes]);
|
|
231
233
|
const renderSupportContent = () => {
|
|
232
234
|
if (processedSupportSlot) return processedSupportSlot;
|
|
233
235
|
|
|
@@ -262,7 +264,7 @@ function ListItemImpl({
|
|
|
262
264
|
if (layout === 'Horizontal') {
|
|
263
265
|
const innerContent = /*#__PURE__*/_jsxs(View, {
|
|
264
266
|
style: innerContentStyleArray,
|
|
265
|
-
children: [leadingElement, /*#__PURE__*/_jsxs(View, {
|
|
267
|
+
children: [leadingElement ?? null, /*#__PURE__*/_jsxs(View, {
|
|
266
268
|
style: {
|
|
267
269
|
flex: 1,
|
|
268
270
|
minWidth: 1,
|
|
@@ -273,9 +275,9 @@ function ListItemImpl({
|
|
|
273
275
|
numberOfLines: 1,
|
|
274
276
|
children: title
|
|
275
277
|
}), showSupportText && renderSupportContent()]
|
|
276
|
-
}),
|
|
278
|
+
}), processedTrailing ? /*#__PURE__*/_jsx(View, {
|
|
277
279
|
style: tokens.trailingWrapperStyle,
|
|
278
|
-
children:
|
|
280
|
+
children: processedTrailing
|
|
279
281
|
}) : null, navArrow && /*#__PURE__*/_jsx(NavArrow, {
|
|
280
282
|
direction: "Forward",
|
|
281
283
|
modes: tokens.resolvedModes
|
|
@@ -309,7 +311,7 @@ function ListItemImpl({
|
|
|
309
311
|
// Vertical layout — icon on top, support text/slot below
|
|
310
312
|
const verticalContent = /*#__PURE__*/_jsxs(View, {
|
|
311
313
|
style: verticalContentStyleArray,
|
|
312
|
-
children: [leadingElement, renderSupportContent()]
|
|
314
|
+
children: [leadingElement ?? null, renderSupportContent()]
|
|
313
315
|
});
|
|
314
316
|
if (onPress) {
|
|
315
317
|
return /*#__PURE__*/_jsx(Pressable, {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import React, { useCallback, useMemo,
|
|
4
|
-
import { View, Text,
|
|
3
|
+
import React, { useCallback, useMemo, useState } from 'react';
|
|
4
|
+
import { View, Text, TextInput as RNTextInput } from 'react-native';
|
|
5
5
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
6
6
|
import { useTokens } from '../../design-tokens/JFSThemeProvider';
|
|
7
7
|
import { EMPTY_MODES } from '../../utils/react-utils';
|
|
@@ -140,15 +140,6 @@ function MessageField({
|
|
|
140
140
|
const currentValue = isControlled ? value : uncontrolledValue;
|
|
141
141
|
const [isFocused, setIsFocused] = useState(false);
|
|
142
142
|
const interactive = !isDisabled && !isReadOnly;
|
|
143
|
-
|
|
144
|
-
// Ref to the native textarea so tapping anywhere in the (padded) textarea
|
|
145
|
-
// container focuses it on the FIRST tap, fixing the Android "two taps to
|
|
146
|
-
// open the keyboard" issue.
|
|
147
|
-
const inputRef = useRef(null);
|
|
148
|
-
const focusInput = useCallback(() => {
|
|
149
|
-
if (!interactive) return;
|
|
150
|
-
inputRef.current?.focus();
|
|
151
|
-
}, [interactive]);
|
|
152
143
|
const {
|
|
153
144
|
modes: globalModes
|
|
154
145
|
} = useTokens();
|
|
@@ -283,12 +274,9 @@ function MessageField({
|
|
|
283
274
|
style: requiredIndicatorStyle,
|
|
284
275
|
children: " *"
|
|
285
276
|
})]
|
|
286
|
-
}), /*#__PURE__*/_jsx(
|
|
277
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
287
278
|
style: [textareaContainerStyle, textareaStyle],
|
|
288
|
-
onPress: focusInput,
|
|
289
|
-
accessible: false,
|
|
290
279
|
children: /*#__PURE__*/_jsx(RNTextInput, {
|
|
291
|
-
ref: inputRef,
|
|
292
280
|
multiline: true,
|
|
293
281
|
value: currentValue,
|
|
294
282
|
onChangeText: handleChangeText,
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import React
|
|
3
|
+
import React from 'react';
|
|
4
4
|
import { View, Text } from 'react-native';
|
|
5
5
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
6
6
|
import { useTokens } from '../../design-tokens/JFSThemeProvider';
|
|
7
7
|
import IconCapsule from '../IconCapsule/IconCapsule';
|
|
8
|
-
import { EMPTY_MODES } from '../../utils/react-utils';
|
|
8
|
+
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils';
|
|
9
9
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
10
10
|
export default function PaymentFeedback({
|
|
11
11
|
title = '₹50,000',
|
|
12
12
|
subtitle = 'Payment successful',
|
|
13
|
-
body
|
|
13
|
+
body,
|
|
14
14
|
details = '18 March 2025, 4:15 pm\nTransaction ID: TXN121466784',
|
|
15
15
|
showDetails = true,
|
|
16
16
|
iconName = 'ic_confirm',
|
|
@@ -97,17 +97,21 @@ export default function PaymentFeedback({
|
|
|
97
97
|
fontWeight: String(detailsFontWeight),
|
|
98
98
|
textAlign: 'center'
|
|
99
99
|
};
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
|
|
101
|
+
// Cascade modes into a custom media slot (per the modes-cascade convention);
|
|
102
|
+
// any modes the consumer set on the slot child still take precedence.
|
|
103
|
+
const mediaContent = renderMedia != null ? cloneChildrenWithModes(renderMedia, modes) : null;
|
|
103
104
|
const defaultMedia = /*#__PURE__*/_jsx(IconCapsule, {
|
|
104
|
-
iconName: iconName
|
|
105
|
+
iconName: iconName
|
|
106
|
+
// `positive` is the default; consumers override the capsule color by
|
|
107
|
+
// passing `AppearanceSystem` (or any other mode) via the `modes` prop.
|
|
108
|
+
,
|
|
105
109
|
modes: {
|
|
110
|
+
AppearanceSystem: 'positive',
|
|
106
111
|
...modes,
|
|
107
112
|
'Icon Capsule Size': 'L',
|
|
108
113
|
Emphasis: 'High',
|
|
109
|
-
'Semantic Intent': 'System'
|
|
110
|
-
AppearanceSystem: 'positive'
|
|
114
|
+
'Semantic Intent': 'System'
|
|
111
115
|
}
|
|
112
116
|
});
|
|
113
117
|
const detailLines = details?.split('\n') ?? [];
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { View, Text, Pressable, Platform } from 'react-native';
|
|
5
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
6
|
+
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils';
|
|
7
|
+
import Icon from '../../icons/Icon';
|
|
8
|
+
|
|
9
|
+
/** Figma grid: label column 1.8fr, each plan column 1fr. */
|
|
10
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
|
+
const LABEL_COLUMN_FR = 1.8;
|
|
12
|
+
const PLAN_COLUMN_FR = 1;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A single plan column header (the label column has no header of its own).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Value rendered inside a plan cell.
|
|
20
|
+
* - `string` / `number` → rendered as value text.
|
|
21
|
+
* - `false` → renders the muted "not available" cross icon.
|
|
22
|
+
* - any React node → rendered as-is (e.g. a `Badge`, `MoneyValue`, icon…).
|
|
23
|
+
* - `null` / `undefined` / `true` → empty cell.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const DEFAULT_COLUMNS = [{
|
|
27
|
+
label: 'Your plan'
|
|
28
|
+
}, {
|
|
29
|
+
label: 'JioFinance+',
|
|
30
|
+
brand: true
|
|
31
|
+
}];
|
|
32
|
+
const DEFAULT_ROWS = [{
|
|
33
|
+
label: 'JioPoints multiplier',
|
|
34
|
+
values: ['1x', '1.25x']
|
|
35
|
+
}, {
|
|
36
|
+
label: 'Cashback',
|
|
37
|
+
showInfo: true,
|
|
38
|
+
values: [false, 'Upto ₹5000']
|
|
39
|
+
}, {
|
|
40
|
+
label: 'Bonus JioGold',
|
|
41
|
+
showInfo: true,
|
|
42
|
+
values: [false, '1%']
|
|
43
|
+
}];
|
|
44
|
+
|
|
45
|
+
/** Keeps every text layer on a single line. */
|
|
46
|
+
const NO_WRAP_TEXT = {
|
|
47
|
+
flexShrink: 0,
|
|
48
|
+
...(Platform.OS === 'web' ? {
|
|
49
|
+
whiteSpace: 'nowrap'
|
|
50
|
+
} : {})
|
|
51
|
+
};
|
|
52
|
+
const labelColumnStyle = {
|
|
53
|
+
flex: LABEL_COLUMN_FR,
|
|
54
|
+
minWidth: 0
|
|
55
|
+
};
|
|
56
|
+
const planColumnStyle = {
|
|
57
|
+
flex: PLAN_COLUMN_FR,
|
|
58
|
+
minWidth: 0,
|
|
59
|
+
alignItems: 'center'
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* PlanComparisonCard renders a compact comparison table that pits the user's
|
|
64
|
+
* current plan against one or more alternative plans across a set of feature
|
|
65
|
+
* rows. Implementation of Figma node `4498:2968` (`PlanComparisonCard`).
|
|
66
|
+
*
|
|
67
|
+
* Columns use a 1.8fr / 1fr flex ratio (label vs plan), matching the Figma grid.
|
|
68
|
+
*
|
|
69
|
+
* @component
|
|
70
|
+
*/
|
|
71
|
+
function PlanComparisonCard({
|
|
72
|
+
columns = DEFAULT_COLUMNS,
|
|
73
|
+
rows = DEFAULT_ROWS,
|
|
74
|
+
modes = EMPTY_MODES,
|
|
75
|
+
style
|
|
76
|
+
}) {
|
|
77
|
+
const gap = getVariableByName('planComparisonCard/gap', modes) ?? 16;
|
|
78
|
+
const headerFg = getVariableByName('planComparisonCard/header/fg', modes) ?? '#ffffff';
|
|
79
|
+
const headerBrandFg = getVariableByName('planComparisonCard/header/brand/fg', modes) ?? '#cea15a';
|
|
80
|
+
const headerFontSize = getVariableByName('planComparisonCard/header/fontSize', modes) ?? 14;
|
|
81
|
+
const headerFontFamily = getVariableByName('planComparisonCard/header/fontFamily', modes) ?? 'JioType Var';
|
|
82
|
+
const headerLineHeight = getVariableByName('planComparisonCard/header/lineHeight', modes) ?? 18;
|
|
83
|
+
const headerFontWeight = getVariableByName('planComparisonCard/header/fontWeight', modes) ?? '500';
|
|
84
|
+
const tableBackground = getVariableByName('planComparisonCard/tableRow/background', modes) ?? '#141414';
|
|
85
|
+
const tableRadius = getVariableByName('planComparisonCard/tableRow/radius', modes) ?? 16;
|
|
86
|
+
const tableBorderSize = getVariableByName('planComparisonCard/tableRow/border/size', modes) ?? 1;
|
|
87
|
+
const tableBorderColor = getVariableByName('planComparisonCard/tableRow/border/color', modes) ?? '#1e1a14';
|
|
88
|
+
const cellPadding = getVariableByName('planComparisonCard/tableCell/padding', modes) ?? 12;
|
|
89
|
+
const cellGap = getVariableByName('planComparisonCard/tableCell/gap', modes) ?? 2;
|
|
90
|
+
const cellMinHeight = getVariableByName('planComparisonCard/tableCell/height', modes) ?? 46;
|
|
91
|
+
const cellBorderSize = getVariableByName('planComparisonCard/tableCell/border/size', modes) ?? 1;
|
|
92
|
+
const cellBorderColor = getVariableByName('planComparisonCard/tableCell/border/color', modes) ?? '#1e1a14';
|
|
93
|
+
const labelColor = getVariableByName('planComparisonCard/tableCell/label/color', modes) ?? '#ffffff';
|
|
94
|
+
const labelDisabledColor = getVariableByName('planComparisonCard/tableCell/label/disabled/color', modes) ?? '#91949c';
|
|
95
|
+
const labelFontSize = getVariableByName('planComparisonCard/tableCell/label/fontSize', modes) ?? 12;
|
|
96
|
+
const labelFontFamily = getVariableByName('planComparisonCard/tableCell/label/fontFamily', modes) ?? 'JioType Var';
|
|
97
|
+
const labelLineHeight = getVariableByName('planComparisonCard/tableCell/label/lineHeight', modes) ?? 16;
|
|
98
|
+
const labelFontWeight = getVariableByName('planComparisonCard/tableCell/label/fontWeight', modes) ?? '400';
|
|
99
|
+
const valueColor = getVariableByName('planComparisonCard/tableCell/value/color', modes) ?? '#ffffff';
|
|
100
|
+
const valueFontSize = getVariableByName('planComparisonCard/tableCell/value/fontSize', modes) ?? 12;
|
|
101
|
+
const valueFontFamily = getVariableByName('planComparisonCard/tableCell/value/fontFamily', modes) ?? 'JioType Var';
|
|
102
|
+
const valueLineHeight = getVariableByName('planComparisonCard/tableCell/value/lineHeight', modes) ?? 16;
|
|
103
|
+
const valueFontWeight = getVariableByName('planComparisonCard/tableCell/value/fontWeight', modes) ?? '500';
|
|
104
|
+
const iconColor = getVariableByName('planComparisonCard/icon/color', modes) ?? '#ffffff';
|
|
105
|
+
const iconSize = getVariableByName('planComparisonCard/icon/size', modes) ?? 16;
|
|
106
|
+
const toWeight = w => typeof w === 'number' ? `${w}` : w;
|
|
107
|
+
const headerTextStyle = {
|
|
108
|
+
...NO_WRAP_TEXT,
|
|
109
|
+
fontFamily: headerFontFamily,
|
|
110
|
+
fontSize: headerFontSize,
|
|
111
|
+
lineHeight: headerLineHeight,
|
|
112
|
+
fontWeight: toWeight(headerFontWeight),
|
|
113
|
+
textAlign: 'center'
|
|
114
|
+
};
|
|
115
|
+
const labelTextStyle = {
|
|
116
|
+
...NO_WRAP_TEXT,
|
|
117
|
+
color: labelColor,
|
|
118
|
+
fontFamily: labelFontFamily,
|
|
119
|
+
fontSize: labelFontSize,
|
|
120
|
+
lineHeight: labelLineHeight,
|
|
121
|
+
fontWeight: toWeight(labelFontWeight)
|
|
122
|
+
};
|
|
123
|
+
const valueTextStyle = {
|
|
124
|
+
...NO_WRAP_TEXT,
|
|
125
|
+
color: valueColor,
|
|
126
|
+
fontFamily: valueFontFamily,
|
|
127
|
+
fontSize: valueFontSize,
|
|
128
|
+
lineHeight: valueLineHeight,
|
|
129
|
+
fontWeight: toWeight(valueFontWeight),
|
|
130
|
+
textAlign: 'center'
|
|
131
|
+
};
|
|
132
|
+
const rowStyle = {
|
|
133
|
+
flexDirection: 'row',
|
|
134
|
+
width: '100%'
|
|
135
|
+
};
|
|
136
|
+
const labelCellStyle = {
|
|
137
|
+
flexDirection: 'row',
|
|
138
|
+
alignItems: 'center',
|
|
139
|
+
gap: cellGap,
|
|
140
|
+
padding: cellPadding,
|
|
141
|
+
minHeight: cellMinHeight
|
|
142
|
+
};
|
|
143
|
+
const valueCellStyle = {
|
|
144
|
+
flexDirection: 'row',
|
|
145
|
+
alignItems: 'center',
|
|
146
|
+
justifyContent: 'center',
|
|
147
|
+
padding: cellPadding,
|
|
148
|
+
minHeight: cellMinHeight,
|
|
149
|
+
width: '100%'
|
|
150
|
+
};
|
|
151
|
+
const renderValue = (value, cellKey) => {
|
|
152
|
+
if (value === false) {
|
|
153
|
+
return /*#__PURE__*/_jsx(Icon, {
|
|
154
|
+
name: "ic_close",
|
|
155
|
+
size: iconSize,
|
|
156
|
+
color: labelDisabledColor
|
|
157
|
+
}, cellKey);
|
|
158
|
+
}
|
|
159
|
+
if (value === null || value === undefined || value === true) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
163
|
+
return /*#__PURE__*/_jsx(Text, {
|
|
164
|
+
style: valueTextStyle,
|
|
165
|
+
children: value
|
|
166
|
+
}, cellKey);
|
|
167
|
+
}
|
|
168
|
+
return cloneChildrenWithModes(value, modes);
|
|
169
|
+
};
|
|
170
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
171
|
+
style: [{
|
|
172
|
+
gap,
|
|
173
|
+
width: '100%'
|
|
174
|
+
}, style],
|
|
175
|
+
children: [/*#__PURE__*/_jsxs(View, {
|
|
176
|
+
style: rowStyle,
|
|
177
|
+
children: [/*#__PURE__*/_jsx(View, {
|
|
178
|
+
style: labelColumnStyle
|
|
179
|
+
}), columns.map((column, index) => /*#__PURE__*/_jsx(View, {
|
|
180
|
+
style: planColumnStyle,
|
|
181
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
182
|
+
style: [headerTextStyle, {
|
|
183
|
+
color: column.brand ? headerBrandFg : headerFg
|
|
184
|
+
}],
|
|
185
|
+
children: column.label
|
|
186
|
+
})
|
|
187
|
+
}, column.label ?? index))]
|
|
188
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
189
|
+
style: {
|
|
190
|
+
width: '100%',
|
|
191
|
+
backgroundColor: tableBackground,
|
|
192
|
+
borderWidth: tableBorderSize,
|
|
193
|
+
borderColor: tableBorderColor,
|
|
194
|
+
borderRadius: tableRadius,
|
|
195
|
+
overflow: 'hidden'
|
|
196
|
+
},
|
|
197
|
+
children: rows.map((row, rowIndex) => {
|
|
198
|
+
const isLast = rowIndex === rows.length - 1;
|
|
199
|
+
const showInfo = row.showInfo || row.onInfoPress != null;
|
|
200
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
201
|
+
style: [rowStyle, {
|
|
202
|
+
borderBottomWidth: isLast ? 0 : cellBorderSize,
|
|
203
|
+
borderBottomColor: cellBorderColor
|
|
204
|
+
}],
|
|
205
|
+
children: [/*#__PURE__*/_jsxs(View, {
|
|
206
|
+
style: [labelColumnStyle, labelCellStyle],
|
|
207
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
208
|
+
style: labelTextStyle,
|
|
209
|
+
children: row.label
|
|
210
|
+
}), showInfo && (row.onInfoPress ? /*#__PURE__*/_jsx(Pressable, {
|
|
211
|
+
onPress: row.onInfoPress,
|
|
212
|
+
accessibilityRole: "button",
|
|
213
|
+
accessibilityLabel: `More information about ${row.label}`,
|
|
214
|
+
hitSlop: 8,
|
|
215
|
+
children: /*#__PURE__*/_jsx(Icon, {
|
|
216
|
+
name: "ic_info",
|
|
217
|
+
size: iconSize,
|
|
218
|
+
color: iconColor
|
|
219
|
+
})
|
|
220
|
+
}) : /*#__PURE__*/_jsx(Icon, {
|
|
221
|
+
name: "ic_info",
|
|
222
|
+
size: iconSize,
|
|
223
|
+
color: iconColor
|
|
224
|
+
}))]
|
|
225
|
+
}), columns.map((column, colIndex) => /*#__PURE__*/_jsx(View, {
|
|
226
|
+
style: [planColumnStyle, valueCellStyle],
|
|
227
|
+
children: renderValue(row.values?.[colIndex], `${rowIndex}-${colIndex}`)
|
|
228
|
+
}, column.label ?? colIndex))]
|
|
229
|
+
}, row.key ?? `${row.label}-${rowIndex}`);
|
|
230
|
+
})
|
|
231
|
+
})]
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
export default PlanComparisonCard;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React, { useMemo } from 'react';
|
|
4
|
+
import { View } from 'react-native';
|
|
5
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
6
|
+
import { useTokens } from '../../design-tokens/JFSThemeProvider';
|
|
7
|
+
import { cloneChildrenWithModes, EMPTY_MODES } from '../../utils/react-utils';
|
|
8
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
9
|
+
/**
|
|
10
|
+
* Slot — a token-driven layout container for grouped slot content.
|
|
11
|
+
*
|
|
12
|
+
* Use `Slot` instead of a raw `View` when you need a vertical or horizontal
|
|
13
|
+
* stack with design-token gap spacing and automatic `modes` propagation to
|
|
14
|
+
* children. Typical usage is nesting a column of actions inside a
|
|
15
|
+
* direction-locked parent such as `ActionFooter`:
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```tsx
|
|
19
|
+
* <ActionFooter modes={modes}>
|
|
20
|
+
* <Slot layoutDirection="vertical" modes={modes}>
|
|
21
|
+
* <Button label="Continue" modes={primaryModes} />
|
|
22
|
+
* <Disclaimer disclaimer="Terms apply." modes={modes} />
|
|
23
|
+
* </Slot>
|
|
24
|
+
* </ActionFooter>
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
function Slot({
|
|
28
|
+
children,
|
|
29
|
+
layoutDirection = 'vertical',
|
|
30
|
+
alignCrossAxis,
|
|
31
|
+
justifyMainAxis,
|
|
32
|
+
modes: propModes = EMPTY_MODES,
|
|
33
|
+
style,
|
|
34
|
+
...rest
|
|
35
|
+
}) {
|
|
36
|
+
const {
|
|
37
|
+
modes: globalModes
|
|
38
|
+
} = useTokens();
|
|
39
|
+
const modes = useMemo(() => ({
|
|
40
|
+
...globalModes,
|
|
41
|
+
...propModes
|
|
42
|
+
}), [globalModes, propModes]);
|
|
43
|
+
const {
|
|
44
|
+
containerStyle,
|
|
45
|
+
processedChildren
|
|
46
|
+
} = useMemo(() => {
|
|
47
|
+
const gap = getVariableByName('slot/gap', modes) ?? 8;
|
|
48
|
+
const isHorizontal = layoutDirection === 'horizontal';
|
|
49
|
+
const container = {
|
|
50
|
+
flexDirection: isHorizontal ? 'row' : 'column',
|
|
51
|
+
alignItems: alignCrossAxis ?? (isHorizontal ? 'flex-start' : 'stretch'),
|
|
52
|
+
justifyContent: justifyMainAxis ?? (isHorizontal ? 'center' : undefined),
|
|
53
|
+
alignSelf: 'stretch',
|
|
54
|
+
gap
|
|
55
|
+
};
|
|
56
|
+
const processed = children ? cloneChildrenWithModes(children, modes) : null;
|
|
57
|
+
return {
|
|
58
|
+
containerStyle: container,
|
|
59
|
+
processedChildren: processed
|
|
60
|
+
};
|
|
61
|
+
}, [children, modes, layoutDirection, alignCrossAxis, justifyMainAxis]);
|
|
62
|
+
return /*#__PURE__*/_jsx(View, {
|
|
63
|
+
style: [containerStyle, style],
|
|
64
|
+
...rest,
|
|
65
|
+
children: processedChildren
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
export default /*#__PURE__*/React.memo(Slot);
|