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.
Files changed (56) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/lib/commonjs/components/Accordion/Accordion.js +16 -3
  3. package/lib/commonjs/components/AppBar/AppBar.js +118 -93
  4. package/lib/commonjs/components/CompareTable/CompareTable.js +98 -33
  5. package/lib/commonjs/components/FormField/FormField.js +9 -1
  6. package/lib/commonjs/components/FullscreenModal/FullscreenModal.js +4 -14
  7. package/lib/commonjs/components/HelloJioInput/HelloJioInput.js +20 -4
  8. package/lib/commonjs/components/PageHero/PageHero.js +3 -0
  9. package/lib/commonjs/components/ProductMerchandisingCard/ProductMerchandisingCard.js +50 -30
  10. package/lib/commonjs/components/Section/Section.js +3 -1
  11. package/lib/commonjs/components/ValueBackMetric/ValueBackMetric.js +117 -89
  12. package/lib/commonjs/components/index.js +7 -1
  13. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -42204
  14. package/lib/commonjs/design-tokens/figma-modes.generated.js +8 -7
  15. package/lib/commonjs/icons/registry.js +1 -1
  16. package/lib/module/components/Accordion/Accordion.js +16 -3
  17. package/lib/module/components/AppBar/AppBar.js +116 -93
  18. package/lib/module/components/CompareTable/CompareTable.js +99 -34
  19. package/lib/module/components/FormField/FormField.js +9 -1
  20. package/lib/module/components/FullscreenModal/FullscreenModal.js +4 -14
  21. package/lib/module/components/HelloJioInput/HelloJioInput.js +20 -4
  22. package/lib/module/components/PageHero/PageHero.js +3 -0
  23. package/lib/module/components/ProductMerchandisingCard/ProductMerchandisingCard.js +50 -30
  24. package/lib/module/components/Section/Section.js +3 -1
  25. package/lib/module/components/ValueBackMetric/ValueBackMetric.js +120 -93
  26. package/lib/module/components/index.js +1 -1
  27. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -42204
  28. package/lib/module/design-tokens/figma-modes.generated.js +8 -7
  29. package/lib/module/icons/registry.js +1 -1
  30. package/lib/typescript/src/components/Accordion/Accordion.d.ts +14 -1
  31. package/lib/typescript/src/components/AppBar/AppBar.d.ts +46 -14
  32. package/lib/typescript/src/components/CompareTable/CompareTable.d.ts +29 -12
  33. package/lib/typescript/src/components/HelloJioInput/HelloJioInput.d.ts +6 -4
  34. package/lib/typescript/src/components/PageHero/PageHero.d.ts +1 -0
  35. package/lib/typescript/src/components/ProductMerchandisingCard/ProductMerchandisingCard.d.ts +8 -1
  36. package/lib/typescript/src/components/Section/Section.d.ts +2 -1
  37. package/lib/typescript/src/components/ValueBackMetric/ValueBackMetric.d.ts +27 -16
  38. package/lib/typescript/src/components/index.d.ts +1 -1
  39. package/lib/typescript/src/design-tokens/figma-modes.generated.d.ts +2 -1
  40. package/lib/typescript/src/icons/registry.d.ts +1 -1
  41. package/package.json +1 -1
  42. package/src/components/Accordion/Accordion.tsx +61 -33
  43. package/src/components/AppBar/AppBar.tsx +154 -124
  44. package/src/components/CompareTable/CompareTable.tsx +138 -52
  45. package/src/components/FormField/FormField.tsx +7 -0
  46. package/src/components/FullscreenModal/FullscreenModal.tsx +5 -14
  47. package/src/components/HelloJioInput/HelloJioInput.tsx +31 -12
  48. package/src/components/PageHero/PageHero.tsx +5 -0
  49. package/src/components/ProductMerchandisingCard/ProductMerchandisingCard.tsx +63 -30
  50. package/src/components/Section/Section.tsx +121 -156
  51. package/src/components/ValueBackMetric/ValueBackMetric.tsx +178 -104
  52. package/src/components/index.ts +6 -1
  53. package/src/design-tokens/Coin Variables-variables-full.json +1 -42204
  54. package/src/design-tokens/figma-modes.generated.ts +8 -7
  55. package/src/icons/registry.ts +1 -1
  56. package/D2C.md +0 -113
@@ -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
- imageSource: avatarSource,
104
- monogram: avatarMonogram,
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 now the shared <Badge type="glass">. The
109
- // hardcoded literals that defined its look (warm frosted overlay, gentle
110
- // 5px blur, 6/2 padding, 4px radius, weight-500 white label) are preserved
111
- // 1:1 via the glass overrides + `style`/`labelStyle`, and the optional
112
- // `specialBadgeIcon` maps to Badge's `leading` slot — so every existing
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: SPECIAL_BADGE_BG,
119
- glassIntensity: SPECIAL_BADGE_INTENSITY,
123
+ glassOverlayColor: tokens.specialBadgeBg,
124
+ glassIntensity: tokens.specialBadgeIntensity,
120
125
  modes: modes,
121
- style: styles.specialBadge,
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
- onPress: onCtaPress
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
- const sbWeight = asStr(getVariableByName('productMerchandisingcard/specialbadge/text/fontweight', modes), '500');
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: '#ffffff',
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
  }
@@ -540,6 +540,7 @@ function SectionSlot({
540
540
  * @param {Object} props
541
541
  * @param {React.ReactNode} [props.navSlot] - Optional custom slot for navigation items (Figma Slot "Nav wrap")
542
542
  * @param {React.ReactNode} [props.upiSlot] - Optional custom slot for UPI handles (Figma Slot "UPI wrap")
543
+ * @param {boolean} [props.upi=true] - Toggles the UPI wrap (Figma boolean property "upi")
543
544
  * @param {Object} [props.modes={}] - Modes object passed to `getVariableByName` for all design tokens
544
545
  * @param {Object} [props.style] - Optional container style overrides
545
546
  * @param {string} [props.accessibilityLabel] - Accessibility label for the section
@@ -552,6 +553,7 @@ const sectionBentoUpiRowStyle = {
552
553
  function SectionBento({
553
554
  navSlot,
554
555
  upiSlot,
556
+ upi = true,
555
557
  modes = EMPTY_MODES,
556
558
  style,
557
559
  // Same rationale as Section: accepted on the type but unused internally.
@@ -642,7 +644,7 @@ function SectionBento({
642
644
  animateExtrasFromIndex: collapsedCount,
643
645
  animateContainerLayout: true
644
646
  } : null)
645
- }), processedUpiSlot && /*#__PURE__*/_jsx(View, {
647
+ }), upi && processedUpiSlot && /*#__PURE__*/_jsx(View, {
646
648
  style: sectionBentoUpiRowStyle,
647
649
  children: processedUpiSlot
648
650
  })]
@@ -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 Link from '../Link/Link';
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 mirror MetricData and zero it
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` (e.g. "Earn").
47
- * `value`, `caption` and `linkLabel` each render only when provided; the header
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, gaps and padding resolve from the `metricdata/*` tokens; the header
51
- * glyph from the `icon/*` tokens (default 18px gold); the link entirely from
52
- * the `link/*` + `text/foreground` tokens via the shared {@link Link}, with
53
- * `Text Appearance: Secondary` and `Text Sizes: Small` seeded so it renders
54
- * purple at 12px like the design (caller `modes` win). The per-part font
55
- * sizes/weights come from the Figma `valueBackMetric/*` variables, which are
56
- * not yet in the committed token snapshot, so they are encoded as constants
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 titleColor = getVariableByName('metricdata/title/color', modes) ?? '#000000';
88
- const valueColor = getVariableByName('metricdata/value/color', modes) ?? '#000000';
89
- const captionColor = getVariableByName('metricdata/caption/color', modes) ?? '#777777';
90
- // The design's link is purple (`text/foreground` under `Text Appearance:
91
- // Secondary`) at 12px (`link/fontSize` under `Text Sizes: Small`), while the
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: CARD_BACKGROUND
139
+ backgroundColor: tokens.backgroundColor
119
140
  };
120
141
  const titleTextStyle = {
121
- color: titleColor,
122
- fontFamily: FONT_FAMILY,
123
- fontSize: TITLE_FONT_SIZE,
124
- fontWeight: TITLE_FONT_WEIGHT,
125
- lineHeight: TITLE_FONT_SIZE * TITLE_LINE_HEIGHT_RATIO
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: FONT_FAMILY,
130
- fontSize: VALUE_FONT_SIZE,
131
- fontWeight: VALUE_FONT_WEIGHT,
132
- lineHeight: VALUE_FONT_SIZE * VALUE_LINE_HEIGHT_RATIO
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: FONT_FAMILY,
137
- fontSize: CAPTION_FONT_SIZE,
138
- fontWeight: CAPTION_FONT_WEIGHT,
139
- lineHeight: CAPTION_FONT_SIZE * CAPTION_LINE_HEIGHT_RATIO
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(Text, {
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: 4
206
+ gap: TEXT_WRAP_GAP
173
207
  },
174
- children: [hasValue ? typeof value === 'string' ? /*#__PURE__*/_jsx(Text, {
208
+ children: [hasValue ? typeof value === 'string' ? /*#__PURE__*/_jsx(RNText, {
175
209
  style: [valueTextStyle, valueStyle],
176
210
  children: value
177
- }) : value : null, hasCaption ? /*#__PURE__*/_jsx(Text, {
211
+ }) : value : null, hasCaption ? /*#__PURE__*/_jsx(RNText, {
178
212
  style: [captionTextStyle, captionStyle],
179
213
  children: caption
180
214
  }) : null]
181
- }) : null, linkLabel ? /*#__PURE__*/_jsx(Link, {
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';