jfs-components 0.1.36 → 0.1.48
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/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/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/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/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/ValueBackMetric/ValueBackMetric.d.ts +27 -16
- package/lib/typescript/src/components/index.d.ts +1 -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/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/icons/registry.ts +1 -1
|
@@ -33,6 +33,7 @@ const MEDIA_FALLBACK = 117;
|
|
|
33
33
|
* eyebrow="Upgrade to JioFinance+"
|
|
34
34
|
* headline="Resume earning cashback, extra points, and 1% gold"
|
|
35
35
|
* supportingText="₹999/year · ₹0 until 2027"
|
|
36
|
+
* body="₹999/year · ₹0 until 2027"
|
|
36
37
|
* buttonLabel="Renew for free"
|
|
37
38
|
* onButtonPress={() => navigate('Upgrade')}
|
|
38
39
|
* modes={{ 'Page type': 'JioPlus' }}
|
|
@@ -65,6 +66,7 @@ function PageHero({
|
|
|
65
66
|
...propModes
|
|
66
67
|
}), [globalModes, propModes]);
|
|
67
68
|
const gap = Number(getVariableByName('PageHero/gap', modes));
|
|
69
|
+
const paddingHorizontal = Number(getVariableByName('PageHero/padding/horizontal', modes));
|
|
68
70
|
const textWrapGap = Number(getVariableByName('PageHero/textWrap/gap', modes));
|
|
69
71
|
|
|
70
72
|
// Media slot box — Figma frame 4540:7845 ("Video"), sized by the
|
|
@@ -109,6 +111,7 @@ function PageHero({
|
|
|
109
111
|
flexDirection: 'column',
|
|
110
112
|
alignItems: 'center',
|
|
111
113
|
gap,
|
|
114
|
+
paddingHorizontal,
|
|
112
115
|
width: '100%'
|
|
113
116
|
};
|
|
114
117
|
const textWrapStyle = {
|
|
@@ -42,6 +42,7 @@ function ProductMerchandisingCard({
|
|
|
42
42
|
title = 'Title',
|
|
43
43
|
subtitle = 'Subtitle',
|
|
44
44
|
badgeLabel,
|
|
45
|
+
badgeIcon,
|
|
45
46
|
badge,
|
|
46
47
|
showAvatar = true,
|
|
47
48
|
avatarSource,
|
|
@@ -96,29 +97,33 @@ function ProductMerchandisingCard({
|
|
|
96
97
|
}), [modes]);
|
|
97
98
|
const headerBadge = badge ?? (badgeLabel != null ? /*#__PURE__*/_jsx(Badge, {
|
|
98
99
|
label: badgeLabel,
|
|
100
|
+
leading: badgeIcon,
|
|
99
101
|
modes: badgeModes
|
|
100
102
|
}) : null);
|
|
101
103
|
const headerAvatar = showAvatar ? avatar ?? /*#__PURE__*/_jsx(Avatar, {
|
|
102
104
|
style: avatarSource ? 'Image' : avatarMonogram ? 'Monogram' : 'Image',
|
|
103
|
-
|
|
104
|
-
|
|
105
|
+
...(avatarSource != null ? {
|
|
106
|
+
imageSource: avatarSource
|
|
107
|
+
} : {}),
|
|
108
|
+
...(avatarMonogram != null ? {
|
|
109
|
+
monogram: avatarMonogram
|
|
110
|
+
} : {}),
|
|
105
111
|
modes: avatarModes
|
|
106
112
|
}) : null;
|
|
107
113
|
|
|
108
|
-
// The footer "special badge" is
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
// instantiation renders byte-for-byte the same as before.
|
|
114
|
+
// The footer "special badge" is the shared <Badge type="glass">. Overlay +
|
|
115
|
+
// blur come from `badge/glass/*` + `blur/minimal`; box/gap from
|
|
116
|
+
// `productMerchandisingcard/specialbadge/*`; label color/weight from the
|
|
117
|
+
// glass badge tokens (Figma: white / 600). Optional `specialBadgeIcon`
|
|
118
|
+
// maps to Badge's `leading` slot.
|
|
114
119
|
const footerSpecialBadge = specialBadge ?? (specialBadgeLabel != null ? /*#__PURE__*/_jsx(Badge, {
|
|
115
120
|
type: "glass",
|
|
116
121
|
label: specialBadgeLabel,
|
|
117
122
|
leading: specialBadgeIcon,
|
|
118
|
-
glassOverlayColor:
|
|
119
|
-
glassIntensity:
|
|
123
|
+
glassOverlayColor: tokens.specialBadgeBg,
|
|
124
|
+
glassIntensity: tokens.specialBadgeIntensity,
|
|
120
125
|
modes: modes,
|
|
121
|
-
style:
|
|
126
|
+
style: tokens.specialBadge,
|
|
122
127
|
labelStyle: tokens.specialBadgeText,
|
|
123
128
|
...(disableTruncation != null ? {
|
|
124
129
|
disableTruncation
|
|
@@ -136,7 +141,9 @@ function ProductMerchandisingCard({
|
|
|
136
141
|
const footerCta = cta ?? /*#__PURE__*/_jsx(Button, {
|
|
137
142
|
label: ctaLabel,
|
|
138
143
|
modes: ctaModes,
|
|
139
|
-
|
|
144
|
+
...(onCtaPress != null ? {
|
|
145
|
+
onPress: onCtaPress
|
|
146
|
+
} : {})
|
|
140
147
|
});
|
|
141
148
|
const content = /*#__PURE__*/_jsxs(_Fragment, {
|
|
142
149
|
children: [imageSource != null ? /*#__PURE__*/_jsx(Image, {
|
|
@@ -297,12 +304,6 @@ function ProductMerchandisingCard({
|
|
|
297
304
|
// the ramp (the strongest point); the upper part fades to fully clear. Kept
|
|
298
305
|
// intentionally small to match the design's subtle "minimal" blur.
|
|
299
306
|
const BLUR_INTENSITY_FACTOR = 1.0;
|
|
300
|
-
|
|
301
|
-
// Special badge ("frosted" pill) — values are literals in the Figma node
|
|
302
|
-
// (no dedicated tokens): bg rgba(255,245,229,0.15), 5px backdrop blur.
|
|
303
|
-
const SPECIAL_BADGE_BG = 'rgba(255,245,229,0.15)';
|
|
304
|
-
const SPECIAL_BADGE_INTENSITY = 17; // ~5px CSS blur / ~2 native blurAmount
|
|
305
|
-
|
|
306
307
|
function asNum(raw, fallback) {
|
|
307
308
|
const n = typeof raw === 'number' ? raw : parseFloat(raw);
|
|
308
309
|
return Number.isFinite(n) ? n : fallback;
|
|
@@ -322,6 +323,7 @@ function resolveTokens(modes) {
|
|
|
322
323
|
const footerPadH = asNum(getVariableByName('productMerchandisingcard/footer/padding/horizontal', modes), 16);
|
|
323
324
|
const footerPadV = asNum(getVariableByName('productMerchandisingcard/footer/padding/vertical', modes), 16);
|
|
324
325
|
const textGap = asNum(getVariableByName('productMerchandisingcard/footer/text/gap', modes), 2);
|
|
326
|
+
const textWrapMinWidth = asNum(getVariableByName('productMerchandisingcard/footer/textWrap/minWidth', modes), 100);
|
|
325
327
|
const blurRadius = asNum(getVariableByName('blur/minimal', modes), 29);
|
|
326
328
|
const titleColor = asStr(getVariableByName('productMerchandisingcard/footer/title/font/color', modes), '#ffffff');
|
|
327
329
|
const titleSize = asNum(getVariableByName('productMerchandisingcard/footer/title/fontsize', modes), 14);
|
|
@@ -333,7 +335,20 @@ function resolveTokens(modes) {
|
|
|
333
335
|
const subtitleWeight = asStr(getVariableByName('productMerchandisingcard/footer/subtitle/fontweight', modes), '400');
|
|
334
336
|
const sbSize = asNum(getVariableByName('productMerchandisingcard/specialbadge/text/fontsize', modes), 12);
|
|
335
337
|
const sbFamily = asStr(getVariableByName('productMerchandisingcard/specialbadge/text/fontfamily', modes), 'JioType Var');
|
|
336
|
-
|
|
338
|
+
// No dedicated `specialbadge/text/fontweight` in the collection — Figma
|
|
339
|
+
// binds the glass badge label to `badge/label/fontWeight` (600).
|
|
340
|
+
const sbWeight = asStr(getVariableByName('badge/label/fontWeight', modes), '600');
|
|
341
|
+
const sbPadH = asNum(getVariableByName('productMerchandisingcard/specialbadge/padding/horizontal', modes), 6);
|
|
342
|
+
const sbGap = asNum(getVariableByName('productMerchandisingcard/specialbadge/gap', modes), 4);
|
|
343
|
+
const sbPadV = asNum(getVariableByName('badge/padding/vertical', modes), 4);
|
|
344
|
+
const sbRadius = asNum(getVariableByName('badge/radius', modes), 4);
|
|
345
|
+
const sbLabelColor = asStr(getVariableByName('badge/glass/color/label', modes), '#ffffff');
|
|
346
|
+
|
|
347
|
+
// The special badge is a `type="glass"` Badge: its overlay + blur come
|
|
348
|
+
// straight from the shared badge-glass tokens the design references
|
|
349
|
+
// (`badge/glass/color/background` and the `glass/minimal` → `blur/minimal`
|
|
350
|
+
// effect), so it frosts at the same strength as the footer.
|
|
351
|
+
const specialBadgeBg = asStr(getVariableByName('badge/glass/color/background', modes), 'rgba(255,255,255,0.15)');
|
|
337
352
|
|
|
338
353
|
// SVG `<FeGaussianBlur stdDeviation>` is resolution-independent and renders
|
|
339
354
|
// an identical Gaussian on iOS, Android and web — so unlike the old
|
|
@@ -345,6 +360,10 @@ function resolveTokens(modes) {
|
|
|
345
360
|
radius,
|
|
346
361
|
blurIntensity: Math.max(0, Math.min(100, Math.round(blurRadius * BLUR_INTENSITY_FACTOR))),
|
|
347
362
|
imageBlurStdDeviation,
|
|
363
|
+
specialBadgeBg,
|
|
364
|
+
// Same `blur/minimal`-derived intensity the footer glass uses, so the
|
|
365
|
+
// special badge frosts consistently with the surface behind it.
|
|
366
|
+
specialBadgeIntensity: Math.max(0, Math.min(100, Math.round(blurRadius * BLUR_INTENSITY_FACTOR))),
|
|
348
367
|
header: {
|
|
349
368
|
flexDirection: 'row',
|
|
350
369
|
alignItems: 'flex-start',
|
|
@@ -362,6 +381,9 @@ function resolveTokens(modes) {
|
|
|
362
381
|
},
|
|
363
382
|
textWrap: {
|
|
364
383
|
flex: 1,
|
|
384
|
+
// The Figma footer pins the text column to a minimum width so a long
|
|
385
|
+
// CTA can never squeeze the title/subtitle away (`footer/textWrap/minWidth`).
|
|
386
|
+
minWidth: textWrapMinWidth,
|
|
365
387
|
gap: textGap,
|
|
366
388
|
justifyContent: 'center'
|
|
367
389
|
},
|
|
@@ -381,8 +403,16 @@ function resolveTokens(modes) {
|
|
|
381
403
|
lineHeight: Math.round(subtitleSize * 1.2),
|
|
382
404
|
includeFontPadding: false
|
|
383
405
|
},
|
|
406
|
+
// Pin the special-badge box to the card collection tokens so a parent
|
|
407
|
+
// `Badge Size` mode cannot inflate/deflate this pill away from Figma.
|
|
408
|
+
specialBadge: {
|
|
409
|
+
paddingHorizontal: sbPadH,
|
|
410
|
+
paddingVertical: sbPadV,
|
|
411
|
+
borderRadius: sbRadius,
|
|
412
|
+
gap: sbGap
|
|
413
|
+
},
|
|
384
414
|
specialBadgeText: {
|
|
385
|
-
color:
|
|
415
|
+
color: sbLabelColor,
|
|
386
416
|
fontSize: sbSize,
|
|
387
417
|
fontFamily: sbFamily,
|
|
388
418
|
fontWeight: sbWeight,
|
|
@@ -408,16 +438,6 @@ const styles = StyleSheet.create({
|
|
|
408
438
|
// the intended fade strength.
|
|
409
439
|
opacity: 0.9
|
|
410
440
|
},
|
|
411
|
-
// Overrides layered on top of <Badge type="glass">. Badge already supplies
|
|
412
|
-
// the row layout + 4px gap (when a leading icon is present), `overflow:
|
|
413
|
-
// 'hidden'`, `alignSelf: 'flex-start'` and `alignItems: 'center'`; these
|
|
414
|
-
// pin the special badge's literal Figma box (6/2 padding, 4px radius)
|
|
415
|
-
// regardless of the badge size tokens resolved by the active modes.
|
|
416
|
-
specialBadge: {
|
|
417
|
-
paddingHorizontal: 6,
|
|
418
|
-
paddingVertical: 2,
|
|
419
|
-
borderRadius: 4
|
|
420
|
-
},
|
|
421
441
|
pressed: {
|
|
422
442
|
opacity: 0.92
|
|
423
443
|
}
|
|
@@ -1,60 +1,94 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import React from 'react';
|
|
4
|
-
import { View, Text, Pressable } from 'react-native';
|
|
3
|
+
import React, { useMemo } from 'react';
|
|
4
|
+
import { View, Text as RNText, Pressable } from 'react-native';
|
|
5
5
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
6
6
|
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils';
|
|
7
7
|
import Icon from '../Icon/Icon';
|
|
8
|
-
import
|
|
8
|
+
import Text from '../Text/Text';
|
|
9
9
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
10
10
|
const TITLE_LINE_HEIGHT_RATIO = 1.2;
|
|
11
11
|
const VALUE_LINE_HEIGHT_RATIO = 1.2;
|
|
12
12
|
const CAPTION_LINE_HEIGHT_RATIO = 1.5;
|
|
13
|
-
|
|
14
|
-
// The Figma node binds the header-icon size and the per-part font sizes/weights
|
|
15
|
-
// to `valueBackMetric/*` variables, but those tokens are NOT in the committed
|
|
16
|
-
// variables snapshot (`npm run tokens -- find valueBackMetric` → no matches).
|
|
17
|
-
// They also intentionally differ from the `metricdata/*` typography defaults
|
|
18
|
-
// (e.g. `metricdata/value` resolves to 20px/700, whereas this component's value
|
|
19
|
-
// is 16px/500). So the sizes/weights below are encoded as literal constants
|
|
20
|
-
// matching the Figma design exactly. Colours, gaps and padding DO exist as real
|
|
21
|
-
// `metricdata/*` tokens (and match the design), so those resolve through
|
|
22
|
-
// `getVariableByName` and stay themeable.
|
|
23
|
-
const TITLE_FONT_SIZE = 12;
|
|
24
|
-
const TITLE_FONT_WEIGHT = '500';
|
|
25
|
-
const VALUE_FONT_SIZE = 16;
|
|
26
|
-
const VALUE_FONT_WEIGHT = '500';
|
|
27
|
-
const CAPTION_FONT_SIZE = 12;
|
|
28
|
-
const CAPTION_FONT_WEIGHT = '400';
|
|
29
|
-
const FONT_FAMILY = 'JioType Var';
|
|
30
|
-
const CARD_BACKGROUND = '#ffffff';
|
|
31
13
|
const CARD_MIN_HEIGHT = 82;
|
|
32
14
|
const ICON_SIZE = 18;
|
|
15
|
+
const TEXT_WRAP_GAP = 4;
|
|
16
|
+
|
|
33
17
|
// Figma renders the header glyph flush (all `icon/padding/*` tokens are 0); the
|
|
34
|
-
// shared Icon pulls padding from those tokens, so
|
|
35
|
-
// explicitly to keep the icon flush against the title.
|
|
18
|
+
// shared Icon pulls padding from those tokens, so zero it explicitly.
|
|
36
19
|
const ICON_PADDING_RESET = {
|
|
37
20
|
paddingLeft: 0,
|
|
38
21
|
paddingRight: 0,
|
|
39
22
|
paddingTop: 0,
|
|
40
23
|
paddingBottom: 0
|
|
41
24
|
};
|
|
25
|
+
// The `valueBackMetric/*` layout + typography tokens are bound on the Figma
|
|
26
|
+
// node but are NOT yet in the committed variables snapshot
|
|
27
|
+
// (`npm run tokens -- find valueBackMetric` → no matches). Colours and the
|
|
28
|
+
// header-row gap do exist as `metricdata/*` / `icon/*` tokens. Until the
|
|
29
|
+
// export is re-synced, `getVariableByName` returns undefined for the
|
|
30
|
+
// valueBackMetric names and the Figma-node fallbacks below take over.
|
|
31
|
+
function resolveTokens(modes) {
|
|
32
|
+
const titleColor = getVariableByName('metricdata/title/color', modes) ?? '#000000';
|
|
33
|
+
const valueColor = getVariableByName('metricdata/value/color', modes) ?? '#000000';
|
|
34
|
+
const captionColor = getVariableByName('metricdata/caption/color', modes) ?? '#777777';
|
|
35
|
+
const titleFontFamily = getVariableByName('valueBackMetric/title/fontfamily', modes) ?? 'JioType Var';
|
|
36
|
+
const titleFontSize = getVariableByName('valueBackMetric/title/fontsize', modes) ?? 12;
|
|
37
|
+
const titleFontWeight = String(getVariableByName('valueBackMetric/title/fontweight', modes) ?? 500);
|
|
38
|
+
const valueFontFamily = getVariableByName('valueBackMetric/value/fontfamily', modes) ?? 'JioType Var';
|
|
39
|
+
const valueFontSize = getVariableByName('valueBackMetric/value/fontsize', modes) ?? 16;
|
|
40
|
+
// Figma binds the value face to JioType Medium; there is no dedicated
|
|
41
|
+
// valueBackMetric/value/fontweight token yet, so fall back to 500.
|
|
42
|
+
const valueFontWeight = '500';
|
|
43
|
+
const captionFontFamily = getVariableByName('valueBackMetric/caption/fontfamily', modes) ?? 'JioType Var';
|
|
44
|
+
const captionFontSize = getVariableByName('valueBackMetric/caption/fontsize', modes) ?? 12;
|
|
45
|
+
const captionFontWeight = String(getVariableByName('valueBackMetric/caption/fontweight', modes) ?? 400);
|
|
46
|
+
const gap = getVariableByName('valueBackMetric/gap', modes) ?? 4;
|
|
47
|
+
const valueWrapGap = getVariableByName('metricdata/valueWrap/gap', modes) ?? 4;
|
|
48
|
+
// Design padding is 0 — do NOT fall back to metricdata/padding (10).
|
|
49
|
+
const paddingHorizontal = getVariableByName('valueBackMetric/padding/horizontal', modes) ?? 0;
|
|
50
|
+
const paddingVertical = getVariableByName('valueBackMetric/padding/vertical', modes) ?? 0;
|
|
51
|
+
const backgroundColor = getVariableByName('valueBackMetric/background/color', modes) ?? '#ffffff';
|
|
52
|
+
const iconColor = getVariableByName('icon/color', modes) ?? '#ad8444';
|
|
53
|
+
const iconSize = getVariableByName('icon/size', modes) ?? ICON_SIZE;
|
|
54
|
+
return {
|
|
55
|
+
titleColor,
|
|
56
|
+
titleFontFamily,
|
|
57
|
+
titleFontSize,
|
|
58
|
+
titleFontWeight,
|
|
59
|
+
valueColor,
|
|
60
|
+
valueFontFamily,
|
|
61
|
+
valueFontSize,
|
|
62
|
+
valueFontWeight,
|
|
63
|
+
captionColor,
|
|
64
|
+
captionFontFamily,
|
|
65
|
+
captionFontSize,
|
|
66
|
+
captionFontWeight,
|
|
67
|
+
gap,
|
|
68
|
+
valueWrapGap,
|
|
69
|
+
paddingHorizontal,
|
|
70
|
+
paddingVertical,
|
|
71
|
+
backgroundColor,
|
|
72
|
+
iconColor,
|
|
73
|
+
iconSize
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
42
77
|
/**
|
|
43
78
|
* ValueBackMetric — a compact, left-aligned "value back" card.
|
|
44
79
|
*
|
|
45
80
|
* Stacks a branded header row (icon + `title`, e.g. "JioPoints"), a prominent
|
|
46
|
-
* `value`, a muted `caption`, and an optional bottom `linkLabel`
|
|
47
|
-
* `value`, `caption` and
|
|
48
|
-
* (icon + title) always shows.
|
|
81
|
+
* `value`, a muted `caption`, and an optional bottom CTA (`linkLabel` / `link`
|
|
82
|
+
* slot, e.g. "Earn"). `value`, `caption` and the CTA each render only when
|
|
83
|
+
* provided; the header (icon + title) always shows.
|
|
49
84
|
*
|
|
50
|
-
* Colours
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* matching the design.
|
|
85
|
+
* Colours resolve from the `metricdata/*` tokens; the header glyph from the
|
|
86
|
+
* `icon/*` tokens (default 18px gold); the CTA from the shared {@link Text}
|
|
87
|
+
* with `Text Appearance: Secondary` + `Text Sizes: Small` seeded so it renders
|
|
88
|
+
* purple at 12px like the design (caller `modes` win). Layout/typography
|
|
89
|
+
* follow the Figma `valueBackMetric/*` variables — not yet in the committed
|
|
90
|
+
* token snapshot, so they resolve through `getVariableByName` with Figma-node
|
|
91
|
+
* fallbacks.
|
|
58
92
|
*
|
|
59
93
|
* @example
|
|
60
94
|
* ```tsx
|
|
@@ -74,6 +108,7 @@ function ValueBackMetric({
|
|
|
74
108
|
value,
|
|
75
109
|
caption,
|
|
76
110
|
linkLabel,
|
|
111
|
+
link,
|
|
77
112
|
onLinkPress,
|
|
78
113
|
onPress,
|
|
79
114
|
disabled,
|
|
@@ -84,84 +119,83 @@ function ValueBackMetric({
|
|
|
84
119
|
captionStyle,
|
|
85
120
|
accessibilityLabel
|
|
86
121
|
}) {
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
// header icon keeps the default gold `icon/color`. Seed only the link's
|
|
93
|
-
// modes; caller-supplied modes win.
|
|
94
|
-
const linkModes = React.useMemo(() => ({
|
|
122
|
+
const tokens = useMemo(() => resolveTokens(modes), [modes]);
|
|
123
|
+
|
|
124
|
+
// Figma Code-Connects the CTA to Text (not Link): purple, no underline.
|
|
125
|
+
// Seed Secondary + Small so it matches the design; caller modes win.
|
|
126
|
+
const linkModes = useMemo(() => ({
|
|
95
127
|
'Text Appearance': 'Secondary',
|
|
96
128
|
'Text Sizes': 'Small',
|
|
97
129
|
...modes
|
|
98
130
|
}), [modes]);
|
|
99
|
-
|
|
100
|
-
// The header glyph is token-driven: `icon/color` (gold by default, retinted
|
|
101
|
-
// via `AppearanceBrand`) and `icon/size` (18px). Passing them explicitly — as
|
|
102
|
-
// MetricData does — makes the glyph deterministic instead of relying on the
|
|
103
|
-
// shared Icon's internal token lookups.
|
|
104
|
-
const iconColor = getVariableByName('icon/color', modes) ?? '#ad8444';
|
|
105
|
-
const iconSize = getVariableByName('icon/size', modes) ?? ICON_SIZE;
|
|
106
|
-
const gap = getVariableByName('metricdata/gap', modes) ?? 4;
|
|
107
|
-
const valueWrapGap = getVariableByName('metricdata/valueWrap/gap', modes) ?? 4;
|
|
108
|
-
const paddingHorizontal = getVariableByName('metricdata/padding/horizontal', modes) ?? 10;
|
|
109
|
-
const paddingVertical = getVariableByName('metricdata/padding/vertical', modes) ?? 10;
|
|
110
131
|
const containerStyle = {
|
|
111
132
|
flexDirection: 'column',
|
|
112
133
|
alignItems: 'flex-start',
|
|
113
134
|
justifyContent: 'center',
|
|
114
|
-
gap,
|
|
115
|
-
paddingHorizontal,
|
|
116
|
-
paddingVertical,
|
|
135
|
+
gap: tokens.gap,
|
|
136
|
+
paddingHorizontal: tokens.paddingHorizontal,
|
|
137
|
+
paddingVertical: tokens.paddingVertical,
|
|
117
138
|
minHeight: CARD_MIN_HEIGHT,
|
|
118
|
-
backgroundColor:
|
|
139
|
+
backgroundColor: tokens.backgroundColor
|
|
119
140
|
};
|
|
120
141
|
const titleTextStyle = {
|
|
121
|
-
color: titleColor,
|
|
122
|
-
fontFamily:
|
|
123
|
-
fontSize:
|
|
124
|
-
fontWeight:
|
|
125
|
-
lineHeight:
|
|
142
|
+
color: tokens.titleColor,
|
|
143
|
+
fontFamily: tokens.titleFontFamily,
|
|
144
|
+
fontSize: tokens.titleFontSize,
|
|
145
|
+
fontWeight: tokens.titleFontWeight,
|
|
146
|
+
lineHeight: tokens.titleFontSize * TITLE_LINE_HEIGHT_RATIO
|
|
126
147
|
};
|
|
127
148
|
const valueTextStyle = {
|
|
128
|
-
color: valueColor,
|
|
129
|
-
fontFamily:
|
|
130
|
-
fontSize:
|
|
131
|
-
fontWeight:
|
|
132
|
-
lineHeight:
|
|
149
|
+
color: tokens.valueColor,
|
|
150
|
+
fontFamily: tokens.valueFontFamily,
|
|
151
|
+
fontSize: tokens.valueFontSize,
|
|
152
|
+
fontWeight: tokens.valueFontWeight,
|
|
153
|
+
lineHeight: tokens.valueFontSize * VALUE_LINE_HEIGHT_RATIO
|
|
133
154
|
};
|
|
134
155
|
const captionTextStyle = {
|
|
135
|
-
color: captionColor,
|
|
136
|
-
fontFamily:
|
|
137
|
-
fontSize:
|
|
138
|
-
fontWeight:
|
|
139
|
-
lineHeight:
|
|
156
|
+
color: tokens.captionColor,
|
|
157
|
+
fontFamily: tokens.captionFontFamily,
|
|
158
|
+
fontSize: tokens.captionFontSize,
|
|
159
|
+
fontWeight: tokens.captionFontWeight,
|
|
160
|
+
lineHeight: tokens.captionFontSize * CAPTION_LINE_HEIGHT_RATIO
|
|
140
161
|
};
|
|
141
|
-
|
|
142
|
-
// Mirrors the Figma swap slot, whose preferred instances are `Icon` and
|
|
143
|
-
// `Image`. A registry name renders the token-driven `Icon` (18px, gold,
|
|
144
|
-
// flush — all from the `icon/*` tokens); a custom node (`<Icon/>`,
|
|
145
|
-
// `<Image/>`, anything) renders as-is with `modes` cascaded into it, so an
|
|
146
|
-
// Icon instance follows the icon tokens while an Image keeps its own sizing.
|
|
147
162
|
const iconNode = icon == null ? null : typeof icon === 'string' ? /*#__PURE__*/_jsx(Icon, {
|
|
148
163
|
iconName: icon,
|
|
149
|
-
size: iconSize,
|
|
150
|
-
color: iconColor,
|
|
164
|
+
size: tokens.iconSize,
|
|
165
|
+
color: tokens.iconColor,
|
|
151
166
|
modes: modes,
|
|
152
167
|
style: ICON_PADDING_RESET
|
|
153
168
|
}) : cloneChildrenWithModes(icon, modes);
|
|
154
169
|
const hasValue = value !== undefined && value !== null && value !== '';
|
|
155
170
|
const hasCaption = caption !== undefined && caption !== '';
|
|
171
|
+
const hasLinkSlot = link !== undefined && link !== null && link !== false;
|
|
172
|
+
const hasLinkLabel = linkLabel !== undefined && linkLabel !== '';
|
|
156
173
|
const resolvedLabel = accessibilityLabel ?? ([title, typeof value === 'string' ? value : undefined, caption, linkLabel].filter(Boolean).join(', ') || undefined);
|
|
174
|
+
|
|
175
|
+
// Seed Secondary + Small into the CTA slot too, so a bare `<Text text="Earn" />`
|
|
176
|
+
// matches the design without the consumer re-seeding. Child-level modes still win.
|
|
177
|
+
const ctaNode = hasLinkSlot ? cloneChildrenWithModes(link, linkModes) : hasLinkLabel ? onLinkPress ? /*#__PURE__*/_jsx(Pressable, {
|
|
178
|
+
onPress: onLinkPress,
|
|
179
|
+
accessibilityRole: "link",
|
|
180
|
+
accessibilityLabel: linkLabel,
|
|
181
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
182
|
+
text: linkLabel,
|
|
183
|
+
textAlign: "Left",
|
|
184
|
+
modes: linkModes
|
|
185
|
+
})
|
|
186
|
+
}) : /*#__PURE__*/_jsx(Text, {
|
|
187
|
+
text: linkLabel,
|
|
188
|
+
textAlign: "Left",
|
|
189
|
+
modes: linkModes
|
|
190
|
+
}) : null;
|
|
157
191
|
const content = /*#__PURE__*/_jsxs(_Fragment, {
|
|
158
192
|
children: [/*#__PURE__*/_jsxs(View, {
|
|
159
193
|
style: {
|
|
160
194
|
flexDirection: 'row',
|
|
161
195
|
alignItems: 'center',
|
|
162
|
-
gap: valueWrapGap
|
|
196
|
+
gap: tokens.valueWrapGap
|
|
163
197
|
},
|
|
164
|
-
children: [iconNode, title !== undefined && title !== '' ? /*#__PURE__*/_jsx(
|
|
198
|
+
children: [iconNode, title !== undefined && title !== '' ? /*#__PURE__*/_jsx(RNText, {
|
|
165
199
|
style: [titleTextStyle, titleStyle],
|
|
166
200
|
children: title
|
|
167
201
|
}) : null]
|
|
@@ -169,23 +203,16 @@ function ValueBackMetric({
|
|
|
169
203
|
style: {
|
|
170
204
|
flexDirection: 'column',
|
|
171
205
|
alignItems: 'flex-start',
|
|
172
|
-
gap:
|
|
206
|
+
gap: TEXT_WRAP_GAP
|
|
173
207
|
},
|
|
174
|
-
children: [hasValue ? typeof value === 'string' ? /*#__PURE__*/_jsx(
|
|
208
|
+
children: [hasValue ? typeof value === 'string' ? /*#__PURE__*/_jsx(RNText, {
|
|
175
209
|
style: [valueTextStyle, valueStyle],
|
|
176
210
|
children: value
|
|
177
|
-
}) : value : null, hasCaption ? /*#__PURE__*/_jsx(
|
|
211
|
+
}) : value : null, hasCaption ? /*#__PURE__*/_jsx(RNText, {
|
|
178
212
|
style: [captionTextStyle, captionStyle],
|
|
179
213
|
children: caption
|
|
180
214
|
}) : null]
|
|
181
|
-
}) : null,
|
|
182
|
-
text: linkLabel,
|
|
183
|
-
autolayout: "Hug",
|
|
184
|
-
modes: linkModes,
|
|
185
|
-
...(onLinkPress ? {
|
|
186
|
-
onPress: onLinkPress
|
|
187
|
-
} : null)
|
|
188
|
-
}) : null]
|
|
215
|
+
}) : null, ctaNode]
|
|
189
216
|
});
|
|
190
217
|
if (onPress) {
|
|
191
218
|
return /*#__PURE__*/_jsx(Pressable, {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
export { default as AccountCard } from './AccountCard/AccountCard';
|
|
4
4
|
export { default as ActionFooter } from './ActionFooter/ActionFooter';
|
|
5
5
|
export { default as Attached } from './Attached/Attached';
|
|
6
|
-
export { default as AppBar } from './AppBar/AppBar';
|
|
6
|
+
export { default as AppBar, JioDot } from './AppBar/AppBar';
|
|
7
7
|
export { default as Avatar } from './Avatar/Avatar';
|
|
8
8
|
export { default as AvatarGroup } from './AvatarGroup/AvatarGroup';
|
|
9
9
|
export { default as Badge } from './Badge/Badge';
|