jfs-components 0.1.51 → 0.1.55

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 (95) hide show
  1. package/.cursor/D2C-SCREEN.md +2 -1
  2. package/.cursor/commands/D2C.md +2 -1
  3. package/CHANGELOG.md +17 -0
  4. package/lib/commonjs/components/ActionFooter/ActionFooter.js +319 -135
  5. package/lib/commonjs/components/Badge/Badge.js +20 -1
  6. package/lib/commonjs/components/Button/Button.js +139 -47
  7. package/lib/commonjs/components/ButtonGroup/ButtonGroup.js +1 -1
  8. package/lib/commonjs/components/Card/Card.js +151 -112
  9. package/lib/commonjs/components/Card/assets/gold-logo-mark.png +0 -0
  10. package/lib/commonjs/components/CategoryCard/CategoryCard.js +97 -63
  11. package/lib/commonjs/components/CategoryCard/assets/sample.png +0 -0
  12. package/lib/commonjs/components/CheckboxItem/CheckboxItem.js +59 -10
  13. package/lib/commonjs/components/CircularProgressBarDoted/CircularProgressBarDoted.js +1 -1
  14. package/lib/commonjs/components/ExpandableCheckbox/ExpandableCheckbox.js +132 -49
  15. package/lib/commonjs/components/HelloJioInput/HelloJioInput.js +56 -14
  16. package/lib/commonjs/components/IconCapsule/IconCapsule.js +1 -1
  17. package/lib/commonjs/components/MetricData/MetricData.js +26 -13
  18. package/lib/commonjs/components/MoneyValue/MoneyValue.js +1 -1
  19. package/lib/commonjs/components/Nudge/Nudge.js +1 -1
  20. package/lib/commonjs/components/PdpCcCard/PdpCcCard.js +47 -91
  21. package/lib/commonjs/components/PlanComparisonCard/PlanComparisonCard.js +52 -45
  22. package/lib/commonjs/components/PortfolioHero/PortfolioHero.js +1 -1
  23. package/lib/commonjs/components/Slider/Slider.js +3 -3
  24. package/lib/commonjs/components/TextInput/TextInput.js +26 -8
  25. package/lib/commonjs/components/Tooltip/Tooltip.js +3 -5
  26. package/lib/commonjs/components/index.js +7 -1
  27. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
  28. package/lib/commonjs/design-tokens/figma-modes.generated.js +13 -13
  29. package/lib/commonjs/icons/registry.js +1 -1
  30. package/lib/module/components/ActionFooter/ActionFooter.js +322 -138
  31. package/lib/module/components/Badge/Badge.js +20 -1
  32. package/lib/module/components/Button/Button.js +140 -48
  33. package/lib/module/components/ButtonGroup/ButtonGroup.js +1 -1
  34. package/lib/module/components/Card/Card.js +152 -114
  35. package/lib/module/components/Card/assets/gold-logo-mark.png +0 -0
  36. package/lib/module/components/CategoryCard/CategoryCard.js +97 -63
  37. package/lib/module/components/CategoryCard/assets/sample.png +0 -0
  38. package/lib/module/components/CheckboxItem/CheckboxItem.js +59 -10
  39. package/lib/module/components/CircularProgressBarDoted/CircularProgressBarDoted.js +1 -1
  40. package/lib/module/components/ExpandableCheckbox/ExpandableCheckbox.js +135 -52
  41. package/lib/module/components/HelloJioInput/HelloJioInput.js +56 -14
  42. package/lib/module/components/IconCapsule/IconCapsule.js +1 -1
  43. package/lib/module/components/MetricData/MetricData.js +26 -14
  44. package/lib/module/components/MoneyValue/MoneyValue.js +1 -1
  45. package/lib/module/components/Nudge/Nudge.js +1 -1
  46. package/lib/module/components/PdpCcCard/PdpCcCard.js +48 -92
  47. package/lib/module/components/PlanComparisonCard/PlanComparisonCard.js +52 -45
  48. package/lib/module/components/PortfolioHero/PortfolioHero.js +1 -1
  49. package/lib/module/components/Slider/Slider.js +3 -3
  50. package/lib/module/components/TextInput/TextInput.js +27 -9
  51. package/lib/module/components/Tooltip/Tooltip.js +3 -5
  52. package/lib/module/components/index.js +1 -1
  53. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
  54. package/lib/module/design-tokens/figma-modes.generated.js +13 -13
  55. package/lib/module/icons/registry.js +1 -1
  56. package/lib/typescript/src/components/ActionFooter/ActionFooter.d.ts +50 -21
  57. package/lib/typescript/src/components/Button/Button.d.ts +24 -1
  58. package/lib/typescript/src/components/ButtonGroup/ButtonGroup.d.ts +2 -2
  59. package/lib/typescript/src/components/Card/Card.d.ts +54 -25
  60. package/lib/typescript/src/components/CategoryCard/CategoryCard.d.ts +29 -12
  61. package/lib/typescript/src/components/CheckboxItem/CheckboxItem.d.ts +27 -9
  62. package/lib/typescript/src/components/ExpandableCheckbox/ExpandableCheckbox.d.ts +37 -12
  63. package/lib/typescript/src/components/IconCapsule/IconCapsule.d.ts +1 -1
  64. package/lib/typescript/src/components/MetricData/MetricData.d.ts +7 -5
  65. package/lib/typescript/src/components/PdpCcCard/PdpCcCard.d.ts +16 -6
  66. package/lib/typescript/src/components/PlanComparisonCard/PlanComparisonCard.d.ts +9 -2
  67. package/lib/typescript/src/components/index.d.ts +2 -2
  68. package/lib/typescript/src/icons/registry.d.ts +1 -1
  69. package/package.json +1 -1
  70. package/src/components/ActionFooter/ActionFooter.tsx +375 -141
  71. package/src/components/Badge/Badge.tsx +21 -1
  72. package/src/components/Button/Button.tsx +192 -44
  73. package/src/components/ButtonGroup/ButtonGroup.tsx +2 -2
  74. package/src/components/Card/Card.tsx +370 -212
  75. package/src/components/Card/assets/gold-logo-mark.png +0 -0
  76. package/src/components/CategoryCard/CategoryCard.tsx +130 -84
  77. package/src/components/CategoryCard/assets/sample.png +0 -0
  78. package/src/components/CheckboxItem/CheckboxItem.tsx +72 -12
  79. package/src/components/CircularProgressBarDoted/CircularProgressBarDoted.tsx +1 -1
  80. package/src/components/ExpandableCheckbox/ExpandableCheckbox.tsx +186 -67
  81. package/src/components/HelloJioInput/HelloJioInput.tsx +58 -17
  82. package/src/components/IconCapsule/IconCapsule.tsx +1 -1
  83. package/src/components/MetricData/MetricData.tsx +39 -22
  84. package/src/components/MoneyValue/MoneyValue.tsx +1 -1
  85. package/src/components/Nudge/Nudge.tsx +1 -1
  86. package/src/components/PdpCcCard/PdpCcCard.tsx +41 -92
  87. package/src/components/PlanComparisonCard/PlanComparisonCard.tsx +73 -58
  88. package/src/components/PortfolioHero/PortfolioHero.tsx +1 -1
  89. package/src/components/Slider/Slider.tsx +3 -3
  90. package/src/components/TextInput/TextInput.tsx +26 -9
  91. package/src/components/Tooltip/Tooltip.tsx +3 -5
  92. package/src/components/index.ts +9 -2
  93. package/src/design-tokens/Coin Variables-variables-full.json +1 -1
  94. package/src/design-tokens/figma-modes.generated.ts +13 -13
  95. package/src/icons/registry.ts +1 -1
@@ -15,16 +15,23 @@ var _Image = _interopRequireDefault(require("../Image/Image"));
15
15
  var _jsxRuntime = require("react/jsx-runtime");
16
16
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
17
17
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
18
- const CATEGORY_CARD_STATE = 'Category Card State';
18
+ /** Canonical Figma collection that drives idle vs active chrome. */
19
+ const CARD_TAB_STATE = 'Card Tab State';
20
+ /**
21
+ * Legacy collection name used by earlier CategoryCard builds. Still accepted on
22
+ * `modes` so existing callers keep resolving the correct Active/Default tokens.
23
+ */
24
+ const LEGACY_CARD_TAB_STATE = 'Category Card State';
19
25
  const BADGE_OVERLAP = 11;
20
26
  const DEFAULT_CARD_WIDTH = 60;
27
+ const DEFAULT_IMAGE_ASSET = require('./assets/sample.png');
21
28
 
22
29
  // Default modes from the Figma CategoryCard component. Overridable via `modes`.
23
30
  const CATEGORY_CARD_DEFAULT_MODES = Object.freeze({
24
31
  AppearanceBrand: 'Tertiary',
25
32
  'Badge Size': 'Small',
26
33
  Context4: 'Badge',
27
- "Radius": "None"
34
+ Radius: 'None'
28
35
  });
29
36
  const toNumber = (value, fallback) => {
30
37
  if (typeof value === 'number') {
@@ -41,32 +48,54 @@ const toFontWeight = (value, fallback) => {
41
48
  if (typeof value === 'string') return value;
42
49
  return fallback;
43
50
  };
44
- function resolveCategoryCardTokens(modes, active) {
45
- const idleModes = {
46
- ...modes,
47
- [CATEGORY_CARD_STATE]: 'Idle'
48
- };
49
- const activeModes = {
50
- ...modes,
51
- [CATEGORY_CARD_STATE]: 'Active'
52
- };
53
- const resolvedModes = active ? activeModes : idleModes;
54
- const gap = toNumber((0, _figmaVariablesResolver.getVariableByName)('cardTab/gap', resolvedModes), 6);
55
- const paddingHorizontal = toNumber((0, _figmaVariablesResolver.getVariableByName)('cardTab/padding/horizontal', resolvedModes), 8);
56
- const paddingVertical = toNumber((0, _figmaVariablesResolver.getVariableByName)('cardTab/padding/vertical', resolvedModes), 8);
57
- const radius = toNumber((0, _figmaVariablesResolver.getVariableByName)('cardTab/radius', resolvedModes), 8);
58
- const strokeWidth = toNumber((0, _figmaVariablesResolver.getVariableByName)('cardTab/strokeWidth', resolvedModes), 1);
59
- const strokeColor = (0, _figmaVariablesResolver.getVariableByName)('cardTab/stroke/color', resolvedModes) ?? '#f5f5f5';
60
- const idleBackground = (0, _figmaVariablesResolver.getVariableByName)('cardTab/background/color', idleModes) ?? '#ffffff';
61
- const activeBackground = (0, _figmaVariablesResolver.getVariableByName)('cardTab/background/color', activeModes) ?? '#f5f5f5';
62
- const backgroundColor = active ? activeBackground : idleBackground;
63
- const imageSize = toNumber((0, _figmaVariablesResolver.getVariableByName)('cardTab/image/width', resolvedModes), 36);
64
- const imageRadius = toNumber((0, _figmaVariablesResolver.getVariableByName)('image/radius', resolvedModes), 0);
65
- const fontFamily = (0, _figmaVariablesResolver.getVariableByName)('label/fontFamily', resolvedModes) ?? 'JioType Var';
66
- const fontSize = toNumber((0, _figmaVariablesResolver.getVariableByName)('label/fontSize', resolvedModes), 12);
67
- const lineHeight = toNumber((0, _figmaVariablesResolver.getVariableByName)('label/lineHeight', resolvedModes), Math.round(fontSize * 1.3));
68
- const fontWeight = toFontWeight((0, _figmaVariablesResolver.getVariableByName)('label/fontWeight', resolvedModes), '500');
69
- const labelColor = (0, _figmaVariablesResolver.getVariableByName)('cardTab/label/color', resolvedModes) ?? '#000000';
51
+
52
+ /**
53
+ * Maps any accepted state token (`Default` / `Active`, plus legacy `Idle`) onto
54
+ * the canonical `Card Tab State` mode names used by the variable resolver.
55
+ */
56
+ const normalizeCardTabState = value => {
57
+ if (value === 'Active') return 'Active';
58
+ if (value === 'Default' || value === 'Idle') return 'Default';
59
+ return undefined;
60
+ };
61
+
62
+ /**
63
+ * Resolves whether the card should render in the Active (gold stroke) state.
64
+ *
65
+ * Precedence (backward compatible):
66
+ * 1. Explicit `active` boolean prop (existing public API)
67
+ * 2. `modes['Card Tab State']` / legacy `modes['Category Card State']`
68
+ * 3. Default → idle (`Default`)
69
+ */
70
+ const resolveIsActive = (active, propModes) => {
71
+ if (typeof active === 'boolean') return active;
72
+ const fromModes = normalizeCardTabState(propModes[CARD_TAB_STATE] ?? propModes[LEGACY_CARD_TAB_STATE]);
73
+ return fromModes === 'Active';
74
+ };
75
+ function resolveCategoryCardTokens(modes) {
76
+ const gap = toNumber((0, _figmaVariablesResolver.getVariableByName)('cardTab/gap', modes), 6);
77
+ const paddingHorizontal = toNumber((0, _figmaVariablesResolver.getVariableByName)('cardTab/padding/horizontal', modes), 8);
78
+ const paddingVertical = toNumber((0, _figmaVariablesResolver.getVariableByName)('cardTab/padding/vertical', modes), 8);
79
+ const radius = toNumber((0, _figmaVariablesResolver.getVariableByName)('cardTab/radius', modes), 8);
80
+ const strokeWidth = toNumber((0, _figmaVariablesResolver.getVariableByName)('cardTab/strokeWidth', modes), 1);
81
+ // Active → gold (`#cea15a`); Default → grey (`#f5f5f5`). Both resolve through
82
+ // `Card Tab State` on `cardTab/stroke/color`.
83
+ const strokeColor = (0, _figmaVariablesResolver.getVariableByName)('cardTab/stroke/color', modes) ?? '#f5f5f5';
84
+
85
+ // Both Idle and Active use a white surface — selection is stroke-only.
86
+ const backgroundColor = (0, _figmaVariablesResolver.getVariableByName)('cardTab/background/color', modes) ?? '#ffffff';
87
+ const imageWidth = toNumber((0, _figmaVariablesResolver.getVariableByName)('cardTab/image/width', modes), 36);
88
+ const imageHeight = toNumber((0, _figmaVariablesResolver.getVariableByName)('cardTab/image/height', modes), imageWidth);
89
+ const imageRadius = toNumber((0, _figmaVariablesResolver.getVariableByName)('image/radius', modes), 0);
90
+
91
+ // Prefer component-scoped `cardTab/label/*` (Figma bindings). Do not fall
92
+ // back to bare `label/fontWeight` / `label/lineHeight` — those currently
93
+ // resolve from an unrelated Donut Chart collection (400 / 15.6).
94
+ const fontFamily = (0, _figmaVariablesResolver.getVariableByName)('cardTab/label/fontFamily', modes) ?? (0, _figmaVariablesResolver.getVariableByName)('label/fontFamily', modes) ?? 'JioType Var';
95
+ const fontSize = toNumber((0, _figmaVariablesResolver.getVariableByName)('cardTab/label/fontSize', modes) ?? (0, _figmaVariablesResolver.getVariableByName)('label/fontSize', modes), 12);
96
+ const lineHeight = toNumber((0, _figmaVariablesResolver.getVariableByName)('cardTab/label/lineHeight', modes), 16);
97
+ const fontWeight = toFontWeight((0, _figmaVariablesResolver.getVariableByName)('cardTab/label/fontWeight', modes), '500');
98
+ const labelColor = (0, _figmaVariablesResolver.getVariableByName)('cardTab/label/color', modes) ?? '#000000';
70
99
  return {
71
100
  containerStyle: {
72
101
  position: 'relative',
@@ -90,7 +119,8 @@ function resolveCategoryCardTokens(modes, active) {
90
119
  lineHeight,
91
120
  textAlign: 'center'
92
121
  },
93
- imageSize,
122
+ imageWidth,
123
+ imageHeight,
94
124
  imageRadius
95
125
  };
96
126
  }
@@ -107,8 +137,13 @@ const disabledOverlayStyle = {
107
137
  * optionally show an overlapping badge at the top edge. Cards are typically
108
138
  * laid out in a horizontal `ScrollView` or `HStack`.
109
139
  *
110
- * All visual values resolve through the `cardTab/*` design tokens with
111
- * sensible Figma defaults so the card renders correctly out of the box.
140
+ * Selection is communicated with a gold stroke (`Card Tab State = Active`);
141
+ * the surface stays white in both Idle and Active. All visual values resolve
142
+ * through the `cardTab/*` design tokens with sensible Figma defaults so the
143
+ * card renders correctly out of the box.
144
+ *
145
+ * The image area is a real slot (`imageSlot` or `children`) matching the Figma
146
+ * Image instance — omit both to keep the default `Image`.
112
147
  *
113
148
  * @component
114
149
  * @param {CategoryCardProps} props
@@ -117,11 +152,12 @@ function CategoryCard({
117
152
  label = 'Popular',
118
153
  imageSource,
119
154
  imageSlot,
155
+ children,
120
156
  badge,
121
- active = false,
157
+ active: activeProp,
122
158
  width = DEFAULT_CARD_WIDTH,
123
- disableTruncation,
124
- singleLine,
159
+ disableTruncation = true,
160
+ singleLine = true,
125
161
  onPress,
126
162
  disabled = false,
127
163
  modes: propModes = _reactUtils.EMPTY_MODES,
@@ -137,16 +173,21 @@ function CategoryCard({
137
173
  const {
138
174
  modes: globalModes
139
175
  } = (0, _JFSThemeProvider.useTokens)();
176
+ const active = resolveIsActive(activeProp, propModes);
177
+ const cardTabState = active ? 'Active' : 'Default';
140
178
  const modes = (0, _react.useMemo)(() => ({
141
179
  ...globalModes,
142
180
  ...CATEGORY_CARD_DEFAULT_MODES,
143
- ...propModes
144
- }), [globalModes, propModes]);
145
- const resolvedModes = (0, _react.useMemo)(() => ({
146
- ...modes,
147
- [CATEGORY_CARD_STATE]: active ? 'Active' : 'Idle'
148
- }), [modes, active]);
149
- const tokens = (0, _react.useMemo)(() => resolveCategoryCardTokens(resolvedModes, active), [resolvedModes, active]);
181
+ ...propModes,
182
+ // Canonical collection always wins so stroke/background tokens resolve.
183
+ [CARD_TAB_STATE]: cardTabState,
184
+ // Keep the legacy key in sync for any nested consumers still reading it.
185
+ [LEGACY_CARD_TAB_STATE]: active ? 'Active' : 'Idle'
186
+ }), [globalModes, propModes, cardTabState, active]);
187
+ const tokens = (0, _react.useMemo)(() => resolveCategoryCardTokens(modes), [modes]);
188
+
189
+ // Same layout merge order as `Text`: consumer `style` first, then layout so
190
+ // single-line / no-truncation overflow always wins.
150
191
  const {
151
192
  style: labelLayoutStyle,
152
193
  ...labelTruncation
@@ -156,30 +197,22 @@ function CategoryCard({
156
197
  numberOfLines: 1,
157
198
  ellipsizeMode: 'tail'
158
199
  }), [disableTruncation, singleLine]);
200
+ const slotContent = imageSlot ?? children;
159
201
  const processedImageSlot = (0, _react.useMemo)(() => {
160
- if (!imageSlot) return null;
161
- const processed = (0, _reactUtils.cloneChildrenWithModes)(imageSlot, resolvedModes);
202
+ if (slotContent == null) return null;
203
+ const processed = (0, _reactUtils.cloneChildrenWithModes)(slotContent, modes);
162
204
  return processed.length === 1 ? processed[0] : processed;
163
- }, [imageSlot, resolvedModes]);
164
- const imageNode = processedImageSlot ?? (imageSource !== undefined ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_Image.default, {
165
- imageSource: imageSource,
166
- width: tokens.imageSize,
167
- height: tokens.imageSize,
168
- ratio: 1,
169
- resizeMode: "cover",
170
- borderRadius: tokens.imageRadius,
171
- accessibilityElementsHidden: true,
172
- importantForAccessibility: "no"
173
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_Image.default, {
174
- imageSource: "https://www.figma.com/api/mcp/asset/0ee34871-646f-4736-be06-22fb6c0a4eed",
175
- width: tokens.imageSize,
176
- height: tokens.imageSize,
205
+ }, [slotContent, modes]);
206
+ const imageNode = processedImageSlot ?? /*#__PURE__*/(0, _jsxRuntime.jsx)(_Image.default, {
207
+ imageSource: imageSource ?? DEFAULT_IMAGE_ASSET,
208
+ width: tokens.imageWidth,
209
+ height: tokens.imageHeight,
177
210
  ratio: 1,
178
211
  resizeMode: "cover",
179
212
  borderRadius: tokens.imageRadius,
180
213
  accessibilityElementsHidden: true,
181
214
  importantForAccessibility: "no"
182
- }));
215
+ });
183
216
  const a11yLabel = accessibilityLabel ?? (badge ? `${label}, ${badge}` : label);
184
217
  const webProps = (0, _webPlatformUtils.usePressableWebSupport)({
185
218
  restProps: rest,
@@ -193,8 +226,8 @@ function CategoryCard({
193
226
  pointerEvents: "none",
194
227
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Badge.default, {
195
228
  label: badge,
196
- modes: resolvedModes,
197
- style: BADGE_CENTER_STYLE
229
+ modes: modes,
230
+ style: BADGE_STYLE
198
231
  })
199
232
  }) : null;
200
233
 
@@ -225,7 +258,7 @@ function CategoryCard({
225
258
  testID: testID,
226
259
  ...webProps,
227
260
  children: [badgeNode, imageNode, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
228
- style: [tokens.labelStyle, labelWidthStyle, labelLayoutStyle, labelStyle],
261
+ style: [tokens.labelStyle, labelWidthStyle, labelStyle, labelLayoutStyle],
229
262
  ...labelTruncation,
230
263
  accessibilityElementsHidden: true,
231
264
  importantForAccessibility: "no",
@@ -242,8 +275,9 @@ const BADGE_ANCHOR = {
242
275
  justifyContent: 'center',
243
276
  zIndex: 1
244
277
  };
245
- const BADGE_CENTER_STYLE = {
246
- alignSelf: 'center'
278
+ const BADGE_STYLE = {
279
+ alignSelf: 'center',
280
+ maxWidth: 38
247
281
  };
248
282
 
249
283
  /** Lets the label fill the card width so the one-line ellipsis has a bound. */
@@ -13,15 +13,30 @@ var _jsxRuntime = require("react/jsx-runtime");
13
13
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
14
14
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
15
15
  /**
16
- * CheckboxItem composes a `Checkbox`, a label and an optional `endSlot` into a
16
+ * Figma's Checkbox Item label slot lays out `Text` + `Link` (etc.) in a
17
+ * horizontal auto-layout frame with `Slot gap = XS` (2px). Consumers may still
18
+ * override via `modes['Slot gap']`. Applied only when resolving the slot gap —
19
+ * not merged into child `modes` — so nested components are unaffected.
20
+ */
21
+ const LABEL_SLOT_GAP_DEFAULTS = {
22
+ 'Slot gap': 'XS'
23
+ };
24
+ /**
25
+ * CheckboxItem composes a `Checkbox`, a label slot and an optional `endSlot` into a
17
26
  * single horizontal pressable row. Pressing anywhere on the row (outside of the
18
- * `endSlot`) toggles the checkbox, mirroring the typical native form pattern.
27
+ * `endSlot` and nested interactive children such as `Link`) toggles the checkbox,
28
+ * mirroring the typical native form pattern.
19
29
  *
20
30
  * Use the `control` prop to swap the checkbox between the leading (left, default)
21
31
  * and trailing (right) edge of the row. The `endSlot` flips to the opposite edge.
22
32
  *
33
+ * The Figma default composition places `Text` + `Link` in the label slot
34
+ * ("I agree" + "Terms & Conditions"). Pass those as `children` — the slot lays
35
+ * multiple children out in a horizontal row with `slot/gap`.
36
+ *
23
37
  * Mirrors the Figma "Checkbox Item" component and uses the `checkboxItem/*`
24
- * design tokens for typography and spacing.
38
+ * design tokens for row spacing; string labels still resolve `checkboxItem/label/*`
39
+ * for backward-compatible typography.
25
40
  *
26
41
  * @component
27
42
  * @param {CheckboxItemProps} props
@@ -30,13 +45,19 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
30
45
  * ```tsx
31
46
  * const [checked, setChecked] = useState(false)
32
47
  *
33
- * // Recommended: pass the label via the children slot.
48
+ * // Figma default composition Text + Link in the label slot.
34
49
  * <CheckboxItem
35
50
  * checked={checked}
36
51
  * onValueChange={setChecked}
37
52
  * control="leading"
38
53
  * modes={{ 'Color Mode': 'Light' }}
39
54
  * >
55
+ * <Text text="I agree" autolayout="Hug" />
56
+ * <Link text="Terms & Conditions" autolayout="Hug" onPress={openTerms} />
57
+ * </CheckboxItem>
58
+ *
59
+ * // Plain string (still supported).
60
+ * <CheckboxItem checked={checked} onValueChange={setChecked}>
40
61
  * Fixed deposit • 0245
41
62
  * </CheckboxItem>
42
63
  *
@@ -65,8 +86,8 @@ const CheckboxItem = /*#__PURE__*/(0, _react.forwardRef)(function CheckboxItem({
65
86
  // 2. otherwise the legacy `label` prop, whenever it was passed at all —
66
87
  // including an explicit `null`/`false`, which (as before) hides the
67
88
  // label entirely.
68
- // 3. otherwise the Figma placeholder, so the default story still renders
69
- // something meaningful.
89
+ // 3. otherwise the Figma plain-string placeholder, so the default story
90
+ // still renders something meaningful when no slot content is supplied.
70
91
  const hasChildren = children != null && children !== false;
71
92
  const slotContent = hasChildren ? children : label !== undefined ? label : 'Fixed deposit • 0245';
72
93
  const isTrailing = control === 'trailing';
@@ -84,6 +105,11 @@ const CheckboxItem = /*#__PURE__*/(0, _react.forwardRef)(function CheckboxItem({
84
105
  const gap = (0, _figmaVariablesResolver.getVariableByName)('checkboxItem/gap', modes) ?? 8;
85
106
  const paddingHorizontal = (0, _figmaVariablesResolver.getVariableByName)('checkboxItem/padding/horizontal', modes) ?? 0;
86
107
  const paddingVertical = (0, _figmaVariablesResolver.getVariableByName)('checkboxItem/padding/vertical', modes) ?? 0;
108
+ // Label-slot internal gap — Figma uses Slot gap = XS (2). Consumer modes win.
109
+ const labelSlotGap = (0, _figmaVariablesResolver.getVariableByName)('slot/gap', {
110
+ ...LABEL_SLOT_GAP_DEFAULTS,
111
+ ...modes
112
+ }) ?? 2;
87
113
  const labelColor = (0, _figmaVariablesResolver.getVariableByName)('checkboxItem/foreground', modes) ?? '#1a1c1f';
88
114
  const labelFontFamily = (0, _figmaVariablesResolver.getVariableByName)('checkboxItem/label/fontFamily', modes) ?? 'JioType Var';
89
115
  const labelFontSize = (0, _figmaVariablesResolver.getVariableByName)('checkboxItem/label/fontSize', modes) ?? 14;
@@ -94,10 +120,17 @@ const CheckboxItem = /*#__PURE__*/(0, _react.forwardRef)(function CheckboxItem({
94
120
  flexDirection: 'row',
95
121
  alignItems: 'center',
96
122
  gap,
123
+ // Trailing matches Figma: space-between pins the checkbox to the far edge
124
+ // while the label (and optional endSlot) stay on the leading side.
125
+ justifyContent: isTrailing ? 'space-between' : 'flex-start',
97
126
  paddingHorizontal,
98
127
  paddingVertical,
99
128
  width: '100%'
100
129
  };
130
+
131
+ // String labels keep the historical checkboxItem/label/* typography so
132
+ // existing plain-string call-sites do not change visually. `flex: 1` is
133
+ // retained in both control modes so long labels still wrap inside the row.
101
134
  const resolvedLabelStyle = {
102
135
  flex: 1,
103
136
  minWidth: 0,
@@ -124,23 +157,39 @@ const CheckboxItem = /*#__PURE__*/(0, _react.forwardRef)(function CheckboxItem({
124
157
  // correctly). An explicit null/false hides the label entirely (legacy
125
158
  // behaviour of the `label` prop).
126
159
  const isTextSlot = typeof slotContent === 'string' || typeof slotContent === 'number';
160
+ const labelSlotStyle = {
161
+ // Grow to fill remaining row width so long content wraps. Trailing rows
162
+ // also set `justifyContent: 'space-between'` so the checkbox stays pinned
163
+ // to the far edge (Figma Control=Trailing).
164
+ flex: 1,
165
+ minWidth: 0,
166
+ flexDirection: 'row',
167
+ flexWrap: 'wrap',
168
+ alignItems: 'flex-start',
169
+ gap: labelSlotGap
170
+ };
127
171
  const labelNode = slotContent == null || slotContent === false ? null : isTextSlot ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
128
172
  style: [resolvedLabelStyle, labelStyle],
129
173
  selectable: false,
130
174
  children: slotContent
131
175
  }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
132
- style: {
133
- flex: 1,
134
- minWidth: 0
135
- },
176
+ style: labelSlotStyle,
136
177
  children: (0, _reactUtils.cloneChildrenWithModes)(slotContent, modes)
137
178
  });
179
+
180
+ // Capture the end-slot responder so presses on nested actions (e.g. Button)
181
+ // never bubble to the row Pressable and toggle the checkbox.
182
+ const suppressRowToggle = (0, _react.useCallback)(event => {
183
+ event.stopPropagation?.();
184
+ }, []);
138
185
  const endSlotNode = endSlot ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
139
186
  style: {
140
187
  width: endSlotWidth,
141
188
  flexShrink: 0,
142
189
  alignItems: 'stretch'
143
190
  },
191
+ onStartShouldSetResponder: () => true,
192
+ onTouchEnd: suppressRowToggle,
144
193
  children: (0, _reactUtils.cloneChildrenWithModes)(endSlot, modes)
145
194
  }) : null;
146
195
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
@@ -76,7 +76,7 @@ function CircularProgressBarDoted({
76
76
  const activeDots = normalizedValue <= 0 ? 0 : Math.ceil(normalizedValue / 100 * resolvedDotCount);
77
77
  const dotShadowSize = toNumber((0, _figmaVariablesResolver.getVariableByName)('circularProgressBarDoted/dot/shadow/size', modes), 6);
78
78
  const baseDotSize = toNumber((0, _figmaVariablesResolver.getVariableByName)('circularProgressBarDoted/dot/size', modes), 6);
79
- const dotSize = baseDotSize + dotShadowSize;
79
+ const dotSize = baseDotSize + dotShadowSize * 2;
80
80
  const outerDotSize = dotSize;
81
81
  const ringSize = layoutSize;
82
82
  const ringRadius = Math.max(0, (ringSize - outerDotSize) / 2);
@@ -8,8 +8,11 @@ var _react = _interopRequireWildcard(require("react"));
8
8
  var _reactNative = require("react-native");
9
9
  var _figmaVariablesResolver = require("../../design-tokens/figma-variables-resolver");
10
10
  var _reactUtils = require("../../utils/react-utils");
11
- var _Checkbox = _interopRequireDefault(require("../Checkbox/Checkbox"));
11
+ var _CheckboxItem = _interopRequireDefault(require("../CheckboxItem/CheckboxItem"));
12
12
  var _Button = _interopRequireDefault(require("../Button/Button"));
13
+ var _Text = _interopRequireDefault(require("../Text/Text"));
14
+ var _Link = _interopRequireDefault(require("../Link/Link"));
15
+ var _ScrollArea = _interopRequireDefault(require("../ScrollArea/ScrollArea"));
13
16
  var _jsxRuntime = require("react/jsx-runtime");
14
17
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
15
18
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
@@ -25,15 +28,21 @@ const BUTTON_DEFAULT_MODES = {
25
28
  Emphasis: 'Low'
26
29
  };
27
30
 
31
+ /** Figma Idle label-slot itemSpacing. */
32
+ const IDLE_SLOT_GAP = 4;
33
+ /** Figma Open scroll-slot itemSpacing. */
34
+ const OPEN_SLOT_GAP = 2;
35
+
28
36
  /**
29
- * ExpandableCheckbox composes a `Checkbox`, a long-form label and a
30
- * "Read more" / "Read less" toggle. Mirrors the Figma "Expandable Checkbox"
31
- * component with two states:
37
+ * ExpandableCheckbox composes a `CheckboxItem` (checkbox + label slot), optional
38
+ * `Text`/`Link` label content, and a "Read more" / "Read less" toggle. Mirrors
39
+ * the Figma "Expandable Checkbox" component with two states:
32
40
  *
33
- * - **Idle (collapsed)** — checkbox + truncated label + toggle button arranged
34
- * in a horizontal row (cross-axis centered).
35
- * - **Open (expanded)** — checkbox + full multi-line label, with the toggle
36
- * button right-aligned beneath the row.
41
+ * - **Idle (collapsed)** — horizontal row: CheckboxItem (fill) + toggle button
42
+ * (hug), cross-axis centered.
43
+ * - **Open (expanded)** — vertical stack aligned to the end: CheckboxItem with
44
+ * the label slot wrapped in a `ScrollArea` (capped by `checkbox/maxHeight`),
45
+ * then the toggle button right-aligned beneath.
37
46
  *
38
47
  * The checkbox and the toggle button have independent press handlers — pressing
39
48
  * the toggle does not affect the checked state, and toggling the checkbox does
@@ -45,7 +54,9 @@ const BUTTON_DEFAULT_MODES = {
45
54
  * @example
46
55
  * ```tsx
47
56
  * <ExpandableCheckbox
48
- * label="By checking this box, I (a) acknowledge and (b) agree to the full terms…"
57
+ * label="I agree"
58
+ * linkLabel="Terms & Conditions"
59
+ * onLinkPress={openTerms}
49
60
  * defaultChecked
50
61
  * onValueChange={setAccepted}
51
62
  * modes={{ 'Color Mode': 'Light' }}
@@ -54,6 +65,9 @@ const BUTTON_DEFAULT_MODES = {
54
65
  */
55
66
  function ExpandableCheckbox({
56
67
  label = '',
68
+ linkLabel,
69
+ onLinkPress,
70
+ children,
57
71
  checked: controlledChecked,
58
72
  defaultChecked = false,
59
73
  onValueChange,
@@ -87,14 +101,21 @@ function ExpandableCheckbox({
87
101
  if (!isExpandedControlled) setInternalExpanded(next);
88
102
  onExpandedChange?.(next);
89
103
  }, [disabled, isExpanded, isExpandedControlled, onExpandedChange]);
104
+
105
+ // Isolate link presses from the CheckboxItem row Pressable so activating the
106
+ // Terms link never toggles the checkbox.
107
+ const handleLinkPress = (0, _react.useCallback)(event => {
108
+ if (disabled) return;
109
+ // RN web: stop the parent Pressable from also receiving the click.
110
+ event.stopPropagation?.();
111
+ onLinkPress?.(event);
112
+ }, [disabled, onLinkPress]);
90
113
  const gap = (0, _figmaVariablesResolver.getVariableByName)('expandableCheckbox/gap', modes) ?? 8;
91
- const rowGap = (0, _figmaVariablesResolver.getVariableByName)('checkboxItem/gap', modes) ?? 8;
92
- const labelColor = (0, _figmaVariablesResolver.getVariableByName)('checkboxItem/foreground', modes) ?? '#1a1c1f';
93
- const labelFontFamily = (0, _figmaVariablesResolver.getVariableByName)('checkboxItem/label/fontFamily', modes) ?? 'JioType Var';
94
- const labelFontSize = (0, _figmaVariablesResolver.getVariableByName)('checkboxItem/label/fontSize', modes) ?? 14;
95
- const labelLineHeight = (0, _figmaVariablesResolver.getVariableByName)('checkboxItem/label/lineHeight', modes) ?? 19;
96
- const labelFontWeightRaw = (0, _figmaVariablesResolver.getVariableByName)('checkboxItem/label/fontWeight', modes) ?? 400;
97
- const labelFontWeight = String(labelFontWeightRaw);
114
+ const maxHeight = (0, _figmaVariablesResolver.getVariableByName)('checkbox/maxHeight', modes) ?? 450;
115
+ const scrollPaddingLeft = (0, _figmaVariablesResolver.getVariableByName)('scrollArea/padding/left', modes) ?? 0;
116
+ const scrollPaddingTop = (0, _figmaVariablesResolver.getVariableByName)('scrollArea/padding/top', modes) ?? 0;
117
+ const scrollPaddingRight = (0, _figmaVariablesResolver.getVariableByName)('scrollArea/padding/right', modes) ?? 0;
118
+ const scrollPaddingBottom = (0, _figmaVariablesResolver.getVariableByName)('scrollArea/padding/bottom', modes) ?? 0;
98
119
  const containerStyle = (0, _react.useMemo)(() => ({
99
120
  flexDirection: isExpanded ? 'column' : 'row',
100
121
  alignItems: isExpanded ? 'flex-end' : 'center',
@@ -104,30 +125,26 @@ function ExpandableCheckbox({
104
125
  opacity: 0.6
105
126
  } : null)
106
127
  }), [isExpanded, gap, disabled]);
107
- const rowStyle = (0, _react.useMemo)(() => ({
128
+ const itemWrapperStyle = (0, _react.useMemo)(() => ({
108
129
  flex: isExpanded ? undefined : 1,
109
130
  alignSelf: isExpanded ? 'stretch' : 'auto',
110
131
  minWidth: 0,
132
+ width: isExpanded ? '100%' : undefined
133
+ }), [isExpanded]);
134
+ const checkboxItemStyle = (0, _react.useMemo)(() => ({
135
+ // CheckboxItem hard-codes width: '100%'; clear it in the Idle row so the
136
+ // Read more button can sit beside a flex-growing item.
137
+ width: isExpanded ? '100%' : undefined,
138
+ alignItems: isExpanded ? 'flex-start' : 'center'
139
+ }), [isExpanded]);
140
+ const slotRowStyle = (0, _react.useMemo)(() => ({
111
141
  flexDirection: 'row',
142
+ flexWrap: 'wrap',
112
143
  alignItems: isExpanded ? 'flex-start' : 'center',
113
- gap: rowGap
114
- }), [isExpanded, rowGap]);
115
- const resolvedLabelStyle = (0, _react.useMemo)(() => ({
144
+ gap: isExpanded ? OPEN_SLOT_GAP : IDLE_SLOT_GAP,
116
145
  flex: 1,
117
- minWidth: 0,
118
- color: labelColor,
119
- fontFamily: labelFontFamily,
120
- fontSize: labelFontSize,
121
- lineHeight: labelLineHeight,
122
- fontWeight: labelFontWeight,
123
- // Android adds asymmetric font padding and top-aligns the glyph inside
124
- // an inflated line box when `lineHeight` is set. That makes the centered
125
- // checkbox look like it drops below the text. Disabling the extra
126
- // padding + centering the glyph keeps the single-line label optically
127
- // aligned with the checkbox. No-op on iOS / web.
128
- includeFontPadding: false,
129
- textAlignVertical: isExpanded ? 'top' : 'center'
130
- }), [labelColor, labelFontFamily, labelFontSize, labelLineHeight, labelFontWeight, isExpanded]);
146
+ minWidth: 0
147
+ }), [isExpanded]);
131
148
 
132
149
  // Layer component modes first (e.g. Color Mode), then button defaults so
133
150
  // Secondary / XS / Low always win unless a dedicated override prop is added.
@@ -135,30 +152,96 @@ function ExpandableCheckbox({
135
152
  ...modes,
136
153
  ...BUTTON_DEFAULT_MODES
137
154
  }), [modes]);
138
- const a11yLabel = accessibilityLabel ?? (typeof label === 'string' ? label : undefined);
155
+ const a11yLabel = accessibilityLabel ?? (typeof label === 'string' && label.length > 0 ? linkLabel ? `${label} ${linkLabel}` : label : linkLabel);
139
156
  const buttonLabel = isExpanded ? readLessLabel : readMoreLabel;
140
- const labelNumberOfLinesProps = !disableTruncation && !isExpanded && collapsedLines > 0 ? {
141
- numberOfLines: collapsedLines,
142
- ellipsizeMode: 'tail'
143
- } : null;
157
+ const shouldTruncate = !disableTruncation && !isExpanded && collapsedLines > 0;
158
+ const hasChildren = children != null && children !== false;
159
+ const composedLabel = hasChildren ? (0, _reactUtils.cloneChildrenWithModes)(children, modes) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
160
+ style: slotRowStyle,
161
+ children: [label ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_Text.default, {
162
+ text: label,
163
+ modes: modes,
164
+ textAlign: "Left",
165
+ style: labelStyle,
166
+ ...(shouldTruncate ? {
167
+ numberOfLines: collapsedLines
168
+ } : {
169
+ disableTruncation: true
170
+ })
171
+ }) : null, linkLabel ?
172
+ /*#__PURE__*/
173
+ // Capture the touch so the parent CheckboxItem Pressable does not also
174
+ // toggle the checkbox when the Terms link is activated.
175
+ (0, _jsxRuntime.jsx)(_reactNative.View, {
176
+ onStartShouldSetResponder: () => true,
177
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Link.default, {
178
+ text: linkLabel,
179
+ onPress: handleLinkPress,
180
+ disabled: disabled,
181
+ modes: modes,
182
+ textAlign: "Left"
183
+ // Idle: Fill (Figma). Open: Hug (Figma).
184
+ ,
185
+ autolayout: isExpanded || !label ? 'Hug' : 'Fill'
186
+ })
187
+ }) : null]
188
+ });
189
+ const labelSlot = isExpanded ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_ScrollArea.default, {
190
+ direction: "vertical",
191
+ modes: modes,
192
+ showScrollIndicator: false,
193
+ paddingLeft: scrollPaddingLeft,
194
+ paddingTop: scrollPaddingTop,
195
+ paddingRight: scrollPaddingRight,
196
+ paddingBottom: scrollPaddingBottom,
197
+ style: {
198
+ maxHeight,
199
+ width: '100%',
200
+ flexGrow: 1,
201
+ flexShrink: 1
202
+ },
203
+ contentContainerStyle: {
204
+ flexDirection: 'row',
205
+ flexWrap: 'wrap',
206
+ alignItems: 'flex-start',
207
+ gap: OPEN_SLOT_GAP
208
+ },
209
+ children: hasChildren ? composedLabel : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
210
+ children: [label ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_Text.default, {
211
+ text: label,
212
+ modes: modes,
213
+ textAlign: "Left",
214
+ style: labelStyle,
215
+ disableTruncation: true
216
+ }) : null, linkLabel ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
217
+ onStartShouldSetResponder: () => true,
218
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Link.default, {
219
+ text: linkLabel,
220
+ onPress: handleLinkPress,
221
+ disabled: disabled,
222
+ modes: modes,
223
+ textAlign: "Left",
224
+ autolayout: "Hug"
225
+ })
226
+ }) : null]
227
+ })
228
+ }) : composedLabel;
144
229
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
145
230
  style: [containerStyle, style],
146
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
147
- style: rowStyle,
148
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_Checkbox.default, {
231
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
232
+ style: itemWrapperStyle,
233
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_CheckboxItem.default, {
149
234
  checked: isChecked,
150
235
  disabled: disabled,
151
236
  onValueChange: handleToggleChecked,
152
237
  modes: modes,
238
+ control: "leading",
239
+ style: checkboxItemStyle,
153
240
  ...(a11yLabel !== undefined ? {
154
241
  accessibilityLabel: a11yLabel
155
- } : {})
156
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
157
- style: [resolvedLabelStyle, labelStyle],
158
- selectable: false,
159
- ...(labelNumberOfLinesProps ?? {}),
160
- children: label
161
- })]
242
+ } : {}),
243
+ children: labelSlot
244
+ })
162
245
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_Button.default, {
163
246
  label: buttonLabel,
164
247
  onPress: handleToggleExpanded,