jfs-components 0.1.51 → 0.1.54

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 (90) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/lib/commonjs/components/ActionFooter/ActionFooter.js +319 -135
  3. package/lib/commonjs/components/Badge/Badge.js +20 -1
  4. package/lib/commonjs/components/Button/Button.js +139 -47
  5. package/lib/commonjs/components/ButtonGroup/ButtonGroup.js +1 -1
  6. package/lib/commonjs/components/Card/Card.js +151 -112
  7. package/lib/commonjs/components/Card/assets/gold-logo-mark.png +0 -0
  8. package/lib/commonjs/components/CategoryCard/CategoryCard.js +97 -63
  9. package/lib/commonjs/components/CategoryCard/assets/sample.png +0 -0
  10. package/lib/commonjs/components/CheckboxItem/CheckboxItem.js +59 -10
  11. package/lib/commonjs/components/ExpandableCheckbox/ExpandableCheckbox.js +132 -49
  12. package/lib/commonjs/components/HelloJioInput/HelloJioInput.js +56 -14
  13. package/lib/commonjs/components/IconCapsule/IconCapsule.js +1 -1
  14. package/lib/commonjs/components/MetricData/MetricData.js +26 -13
  15. package/lib/commonjs/components/MoneyValue/MoneyValue.js +1 -1
  16. package/lib/commonjs/components/Nudge/Nudge.js +1 -1
  17. package/lib/commonjs/components/PdpCcCard/PdpCcCard.js +47 -91
  18. package/lib/commonjs/components/PlanComparisonCard/PlanComparisonCard.js +52 -45
  19. package/lib/commonjs/components/PortfolioHero/PortfolioHero.js +1 -1
  20. package/lib/commonjs/components/Slider/Slider.js +3 -3
  21. package/lib/commonjs/components/TextInput/TextInput.js +26 -8
  22. package/lib/commonjs/components/Tooltip/Tooltip.js +3 -5
  23. package/lib/commonjs/components/index.js +7 -1
  24. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
  25. package/lib/commonjs/design-tokens/figma-modes.generated.js +13 -13
  26. package/lib/commonjs/icons/registry.js +1 -1
  27. package/lib/module/components/ActionFooter/ActionFooter.js +322 -138
  28. package/lib/module/components/Badge/Badge.js +20 -1
  29. package/lib/module/components/Button/Button.js +140 -48
  30. package/lib/module/components/ButtonGroup/ButtonGroup.js +1 -1
  31. package/lib/module/components/Card/Card.js +152 -114
  32. package/lib/module/components/Card/assets/gold-logo-mark.png +0 -0
  33. package/lib/module/components/CategoryCard/CategoryCard.js +97 -63
  34. package/lib/module/components/CategoryCard/assets/sample.png +0 -0
  35. package/lib/module/components/CheckboxItem/CheckboxItem.js +59 -10
  36. package/lib/module/components/ExpandableCheckbox/ExpandableCheckbox.js +135 -52
  37. package/lib/module/components/HelloJioInput/HelloJioInput.js +56 -14
  38. package/lib/module/components/IconCapsule/IconCapsule.js +1 -1
  39. package/lib/module/components/MetricData/MetricData.js +26 -14
  40. package/lib/module/components/MoneyValue/MoneyValue.js +1 -1
  41. package/lib/module/components/Nudge/Nudge.js +1 -1
  42. package/lib/module/components/PdpCcCard/PdpCcCard.js +48 -92
  43. package/lib/module/components/PlanComparisonCard/PlanComparisonCard.js +52 -45
  44. package/lib/module/components/PortfolioHero/PortfolioHero.js +1 -1
  45. package/lib/module/components/Slider/Slider.js +3 -3
  46. package/lib/module/components/TextInput/TextInput.js +27 -9
  47. package/lib/module/components/Tooltip/Tooltip.js +3 -5
  48. package/lib/module/components/index.js +1 -1
  49. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
  50. package/lib/module/design-tokens/figma-modes.generated.js +13 -13
  51. package/lib/module/icons/registry.js +1 -1
  52. package/lib/typescript/src/components/ActionFooter/ActionFooter.d.ts +50 -21
  53. package/lib/typescript/src/components/Button/Button.d.ts +24 -1
  54. package/lib/typescript/src/components/ButtonGroup/ButtonGroup.d.ts +2 -2
  55. package/lib/typescript/src/components/Card/Card.d.ts +54 -25
  56. package/lib/typescript/src/components/CategoryCard/CategoryCard.d.ts +29 -12
  57. package/lib/typescript/src/components/CheckboxItem/CheckboxItem.d.ts +27 -9
  58. package/lib/typescript/src/components/ExpandableCheckbox/ExpandableCheckbox.d.ts +37 -12
  59. package/lib/typescript/src/components/IconCapsule/IconCapsule.d.ts +1 -1
  60. package/lib/typescript/src/components/MetricData/MetricData.d.ts +7 -5
  61. package/lib/typescript/src/components/PdpCcCard/PdpCcCard.d.ts +16 -6
  62. package/lib/typescript/src/components/PlanComparisonCard/PlanComparisonCard.d.ts +9 -2
  63. package/lib/typescript/src/components/index.d.ts +2 -2
  64. package/lib/typescript/src/icons/registry.d.ts +1 -1
  65. package/package.json +1 -1
  66. package/src/components/ActionFooter/ActionFooter.tsx +375 -141
  67. package/src/components/Badge/Badge.tsx +21 -1
  68. package/src/components/Button/Button.tsx +192 -44
  69. package/src/components/ButtonGroup/ButtonGroup.tsx +2 -2
  70. package/src/components/Card/Card.tsx +370 -212
  71. package/src/components/Card/assets/gold-logo-mark.png +0 -0
  72. package/src/components/CategoryCard/CategoryCard.tsx +130 -84
  73. package/src/components/CategoryCard/assets/sample.png +0 -0
  74. package/src/components/CheckboxItem/CheckboxItem.tsx +72 -12
  75. package/src/components/ExpandableCheckbox/ExpandableCheckbox.tsx +186 -67
  76. package/src/components/HelloJioInput/HelloJioInput.tsx +58 -17
  77. package/src/components/IconCapsule/IconCapsule.tsx +1 -1
  78. package/src/components/MetricData/MetricData.tsx +39 -22
  79. package/src/components/MoneyValue/MoneyValue.tsx +1 -1
  80. package/src/components/Nudge/Nudge.tsx +1 -1
  81. package/src/components/PdpCcCard/PdpCcCard.tsx +41 -92
  82. package/src/components/PlanComparisonCard/PlanComparisonCard.tsx +73 -58
  83. package/src/components/PortfolioHero/PortfolioHero.tsx +1 -1
  84. package/src/components/Slider/Slider.tsx +3 -3
  85. package/src/components/TextInput/TextInput.tsx +26 -9
  86. package/src/components/Tooltip/Tooltip.tsx +3 -5
  87. package/src/components/index.ts +9 -2
  88. package/src/design-tokens/Coin Variables-variables-full.json +1 -1
  89. package/src/design-tokens/figma-modes.generated.ts +13 -13
  90. package/src/icons/registry.ts +1 -1
@@ -1,11 +1,9 @@
1
1
  import React, { useMemo } from 'react'
2
2
  import {
3
3
  View,
4
- Text,
5
4
  Pressable,
6
5
  StyleSheet,
7
6
  type ViewStyle,
8
- type TextStyle,
9
7
  type StyleProp,
10
8
  type ImageSourcePropType,
11
9
  } from 'react-native'
@@ -16,19 +14,29 @@ import Divider from '../Divider/Divider'
16
14
  import Button from '../Button/Button'
17
15
  import Image from '../Image/Image'
18
16
  import Icon from '../Icon/Icon'
17
+ import MetricData from '../MetricData/MetricData'
19
18
  import type { Modes } from '../../design-tokens'
20
19
 
21
20
  /**
22
21
  * A single metric column inside the card's stats row: a small title, a bold
23
- * value and an optional muted caption.
22
+ * value (optionally preceded by an icon) and an optional muted caption.
24
23
  */
25
24
  export interface PdpCcCardMetric {
26
25
  /** Small label rendered above the value (e.g. `"Weight"`). */
27
26
  title?: string
28
- /** The prominent value (e.g. `"24K"`). */
29
- value?: string
27
+ /**
28
+ * The prominent value (e.g. `"24K"`). Accepts a string or any node
29
+ * (e.g. a `MoneyValue`) for richer composition.
30
+ */
31
+ value?: string | React.ReactNode
30
32
  /** Optional muted caption rendered below the value. */
31
33
  caption?: string
34
+ /**
35
+ * Optional glyph shown to the left of the value. Pass a registry icon name
36
+ * (e.g. `'ic_card'`) or a custom node. Opt-in — omit to keep the legacy
37
+ * text-only metric column.
38
+ */
39
+ icon?: string | React.ReactNode
32
40
  }
33
41
 
34
42
  export interface PdpCcCardProps {
@@ -77,7 +85,7 @@ export interface PdpCcCardProps {
77
85
  /**
78
86
  * The metric columns rendered in the stats row. Vertical dividers are
79
87
  * inserted automatically between adjacent metrics. Defaults to two sample
80
- * metrics.
88
+ * metrics (with leading icons) matching the Figma default.
81
89
  */
82
90
  metrics?: PdpCcCardMetric[]
83
91
  /** CTA button label. Defaults to `"button"`. */
@@ -115,8 +123,9 @@ export interface PdpCcCardProps {
115
123
  * `imageSource` for the default image or `media` for a full slot override.
116
124
  * - **Title** — a centered headline + subtitle rendered through the shared
117
125
  * {@link Title} component (`title/*`, `pageSubtitle/*` tokens).
118
- * - **Metrics** — a row of {@link PdpCcCardMetric} columns (title / value /
119
- * caption) separated by vertical `Divider`s (`metricdata/*` tokens).
126
+ * - **Metrics** — a row of {@link PdpCcCardMetric} columns composed via
127
+ * {@link MetricData} (title / optional icon + value / caption) separated by
128
+ * vertical `Divider`s (`metricdata/*`, `icon/*` tokens).
120
129
  * - **CTA** — a small tonal {@link Button} (`Button / Size: S`,
121
130
  * `AppearanceBrand: Secondary`, `Emphasis: Medium`) with a leading icon.
122
131
  *
@@ -179,12 +188,14 @@ function PdpCcCard({
179
188
  <Button
180
189
  label={buttonLabel}
181
190
  modes={ctaModes}
182
- onPress={onButtonPress}
183
- leading={
184
- buttonIcon ? (
185
- <Icon iconName={buttonIcon} size={tokens.buttonIconSize} color={buttonForeground} />
186
- ) : undefined
187
- }
191
+ {...(onButtonPress !== undefined ? { onPress: onButtonPress } : null)}
192
+ {...(buttonIcon
193
+ ? {
194
+ leading: (
195
+ <Icon iconName={buttonIcon} size={tokens.buttonIconSize} color={buttonForeground} />
196
+ ),
197
+ }
198
+ : null)}
188
199
  />
189
200
  )
190
201
 
@@ -197,9 +208,9 @@ function PdpCcCard({
197
208
  subtitle={subtitle}
198
209
  textAlign="Center"
199
210
  modes={modes}
200
- numberOfLines={numberOfLines}
201
- disableTruncation={disableTruncation}
202
- singleLine={singleLine}
211
+ {...(numberOfLines !== undefined ? { numberOfLines } : null)}
212
+ {...(disableTruncation !== undefined ? { disableTruncation } : null)}
213
+ {...(singleLine !== undefined ? { singleLine } : null)}
203
214
  />
204
215
 
205
216
  {metrics.length > 0 ? (
@@ -209,7 +220,14 @@ function PdpCcCard({
209
220
  {index > 0 ? (
210
221
  <Divider direction="vertical" modes={modes} style={styles.metricDivider} />
211
222
  ) : null}
212
- <Metricdata metric={metric} tokens={tokens} />
223
+ <MetricData
224
+ {...(metric.title !== undefined ? { title: metric.title } : null)}
225
+ {...(metric.value !== undefined ? { value: metric.value } : null)}
226
+ {...(metric.caption !== undefined ? { caption: metric.caption } : null)}
227
+ {...(metric.icon !== undefined ? { icon: metric.icon } : null)}
228
+ modes={modes}
229
+ style={styles.metricCell}
230
+ />
213
231
  </React.Fragment>
214
232
  ))}
215
233
  </View>
@@ -244,20 +262,6 @@ function PdpCcCard({
244
262
  )
245
263
  }
246
264
 
247
- // ---------------------------------------------------------------------------
248
- // Metricdata — internal metric column (Figma node 5352:256)
249
- // ---------------------------------------------------------------------------
250
-
251
- function Metricdata({ metric, tokens }: { metric: PdpCcCardMetric; tokens: ResolvedTokens }) {
252
- return (
253
- <View style={tokens.metric}>
254
- {metric.title != null ? <Text style={tokens.metricTitle}>{metric.title}</Text> : null}
255
- {metric.value != null ? <Text style={tokens.metricValue}>{metric.value}</Text> : null}
256
- {metric.caption != null ? <Text style={tokens.metricCaption}>{metric.caption}</Text> : null}
257
- </View>
258
- )
259
- }
260
-
261
265
  // ---------------------------------------------------------------------------
262
266
  // Tokens / static styles
263
267
  // ---------------------------------------------------------------------------
@@ -266,10 +270,6 @@ interface ResolvedTokens {
266
270
  container: ViewStyle
267
271
  imageRadius: number
268
272
  buttonIconSize: number
269
- metric: ViewStyle
270
- metricTitle: TextStyle
271
- metricValue: TextStyle
272
- metricCaption: TextStyle
273
273
  }
274
274
 
275
275
  function asNum(raw: unknown, fallback: number): number {
@@ -294,25 +294,6 @@ function resolveTokens(modes: Modes): ResolvedTokens {
294
294
  const imageRadius = asNum(getVariableByName('image/radius', modes), 8)
295
295
  const buttonIconSize = asNum(getVariableByName('button/iconSize', modes), 16)
296
296
 
297
- const metricGap = asNum(getVariableByName('metricdata/gap', modes), 4)
298
- const metricPadH = asNum(getVariableByName('metricdata/padding/horizontal', modes), 10)
299
- const metricPadV = asNum(getVariableByName('metricdata/padding/vertical', modes), 10)
300
-
301
- const titleColor = asStr(getVariableByName('metricdata/title/color', modes), '#000000')
302
- const titleSize = asNum(getVariableByName('metricdata/title/fontsize', modes), 12)
303
- const titleFamily = asStr(getVariableByName('metricdata/title/fontfamily', modes), 'JioType Var')
304
- const titleWeight = asStr(getVariableByName('metricdata/title/fontweight', modes), '400')
305
-
306
- const valueColor = asStr(getVariableByName('metricdata/value/color', modes), '#000000')
307
- const valueSize = asNum(getVariableByName('metricdata/value/fontsize', modes), 20)
308
- const valueFamily = asStr(getVariableByName('metricdata/value/fontfamily', modes), 'JioType Var')
309
- const valueWeight = asStr(getVariableByName('metricdata/value/fontweight', modes), '700')
310
-
311
- const captionColor = asStr(getVariableByName('metricdata/caption/color', modes), '#777777')
312
- const captionSize = asNum(getVariableByName('metricdata/caption/fontsize', modes), 12)
313
- const captionFamily = asStr(getVariableByName('metricdata/caption/fontfamily', modes), 'JioType Var')
314
- const captionWeight = asStr(getVariableByName('metricdata/caption/fontweight', modes), '500')
315
-
316
297
  return {
317
298
  container: {
318
299
  backgroundColor: background,
@@ -325,47 +306,12 @@ function resolveTokens(modes: Modes): ResolvedTokens {
325
306
  },
326
307
  imageRadius,
327
308
  buttonIconSize,
328
- metric: {
329
- flex: 1,
330
- gap: metricGap,
331
- paddingHorizontal: metricPadH,
332
- paddingVertical: metricPadV,
333
- alignItems: 'center',
334
- justifyContent: 'center',
335
- },
336
- metricTitle: {
337
- color: titleColor,
338
- fontSize: titleSize,
339
- fontFamily: titleFamily,
340
- fontWeight: titleWeight as TextStyle['fontWeight'],
341
- lineHeight: Math.round(titleSize * 1.2),
342
- textAlign: 'center',
343
- includeFontPadding: false as any,
344
- },
345
- metricValue: {
346
- color: valueColor,
347
- fontSize: valueSize,
348
- fontFamily: valueFamily,
349
- fontWeight: valueWeight as TextStyle['fontWeight'],
350
- lineHeight: Math.round(valueSize * 1.2),
351
- textAlign: 'center',
352
- includeFontPadding: false as any,
353
- },
354
- metricCaption: {
355
- color: captionColor,
356
- fontSize: captionSize,
357
- fontFamily: captionFamily,
358
- fontWeight: captionWeight as TextStyle['fontWeight'],
359
- lineHeight: Math.round(captionSize * 1.2),
360
- textAlign: 'center',
361
- includeFontPadding: false as any,
362
- },
363
309
  }
364
310
  }
365
311
 
366
312
  const DEFAULT_METRICS: PdpCcCardMetric[] = [
367
- { title: 'Title', value: 'Value', caption: 'caption' },
368
- { title: 'Title', value: 'Value', caption: 'caption' },
313
+ { title: 'Title', value: 'Value', caption: 'caption', icon: 'ic_card' },
314
+ { title: 'Title', value: 'Value', caption: 'caption', icon: 'ic_card' },
369
315
  ]
370
316
 
371
317
  const styles = StyleSheet.create({
@@ -379,6 +325,9 @@ const styles = StyleSheet.create({
379
325
  flexDirection: 'row',
380
326
  alignItems: 'stretch',
381
327
  },
328
+ metricCell: {
329
+ flex: 1,
330
+ },
382
331
  metricDivider: {
383
332
  alignSelf: 'center',
384
333
  height: '70%',
@@ -73,6 +73,12 @@ export type PlanComparisonCardProps = {
73
73
  columns?: PlanComparisonColumn[];
74
74
  /** Feature rows compared across the plan columns. */
75
75
  rows?: PlanComparisonRow[];
76
+ /**
77
+ * Slot that replaces the default comparison table (Figma `Slot`). When
78
+ * omitted, the table is built from `columns` + `rows`. Header still
79
+ * renders from `columns`. `modes` are cascaded into slot children.
80
+ */
81
+ children?: React.ReactNode;
76
82
  /** Design token modes for theming (e.g. `{ "Color Mode": "Light" }`). */
77
83
  modes?: Modes;
78
84
  /** Override the outer container style. */
@@ -88,6 +94,7 @@ const DEFAULT_ROWS: PlanComparisonRow[] = [
88
94
  { label: 'JioPoints multiplier', values: ['1x', '1.25x'] },
89
95
  { label: 'Cashback', showInfo: true, values: [false, 'Upto ₹5000'] },
90
96
  { label: 'Bonus JioGold', showInfo: true, values: [false, '1%'] },
97
+ { label: 'Bonus JioSilver', showInfo: true, values: [false, '1%'] },
91
98
  ];
92
99
 
93
100
  /** Keeps every text layer on a single line. */
@@ -112,13 +119,15 @@ const planColumnStyle: ViewStyle = {
112
119
  * current plan against one or more alternative plans across a set of feature
113
120
  * rows. Implementation of Figma node `4498:2968` (`PlanComparisonCard`).
114
121
  *
115
- * Columns use a 1.8fr / 1fr flex ratio (label vs plan), matching the Figma grid.
122
+ * Structure: header (from `columns`) + Slot (default table from `rows`, or
123
+ * custom `children`). Columns use a 1.8fr / 1fr flex ratio (label vs plan).
116
124
  *
117
125
  * @component
118
126
  */
119
127
  function PlanComparisonCard({
120
128
  columns = DEFAULT_COLUMNS,
121
129
  rows = DEFAULT_ROWS,
130
+ children,
122
131
  modes = EMPTY_MODES,
123
132
  style,
124
133
  }: PlanComparisonCardProps) {
@@ -234,6 +243,65 @@ function PlanComparisonCard({
234
243
  return cloneChildrenWithModes(value, modes);
235
244
  };
236
245
 
246
+ const defaultTable = (
247
+ <View
248
+ style={{
249
+ width: '100%',
250
+ backgroundColor: tableBackground,
251
+ borderWidth: tableBorderSize,
252
+ borderColor: tableBorderColor,
253
+ borderRadius: tableRadius,
254
+ overflow: 'hidden',
255
+ }}
256
+ >
257
+ {rows.map((row, rowIndex) => {
258
+ const isLast = rowIndex === rows.length - 1;
259
+ const showInfo = row.showInfo || row.onInfoPress != null;
260
+ return (
261
+ <View
262
+ key={row.key ?? `${row.label}-${rowIndex}`}
263
+ style={[
264
+ rowStyle,
265
+ {
266
+ borderBottomWidth: isLast ? 0 : cellBorderSize,
267
+ borderBottomColor: cellBorderColor,
268
+ },
269
+ ]}
270
+ >
271
+ <View style={[labelColumnStyle, labelCellStyle]}>
272
+ <Text style={labelTextStyle}>{row.label}</Text>
273
+ {showInfo &&
274
+ (row.onInfoPress ? (
275
+ <Pressable
276
+ onPress={row.onInfoPress}
277
+ accessibilityRole="button"
278
+ accessibilityLabel={`More information about ${row.label}`}
279
+ hitSlop={8}
280
+ >
281
+ <Icon name="ic_info" size={iconSize} color={iconColor} />
282
+ </Pressable>
283
+ ) : (
284
+ <Icon name="ic_info" size={iconSize} color={iconColor} />
285
+ ))}
286
+ </View>
287
+
288
+ {columns.map((column, colIndex) => (
289
+ <View
290
+ key={column.label ?? colIndex}
291
+ style={[planColumnStyle, valueCellStyle]}
292
+ >
293
+ {renderValue(
294
+ row.values?.[colIndex],
295
+ `${rowIndex}-${colIndex}`,
296
+ )}
297
+ </View>
298
+ ))}
299
+ </View>
300
+ );
301
+ })}
302
+ </View>
303
+ );
304
+
237
305
  return (
238
306
  <View style={[{ gap, width: '100%' }, style]}>
239
307
  {/* Header row — same 1.8fr / 1fr grid as the table */}
@@ -253,63 +321,10 @@ function PlanComparisonCard({
253
321
  ))}
254
322
  </View>
255
323
 
256
- {/* Table body */}
257
- <View
258
- style={{
259
- width: '100%',
260
- backgroundColor: tableBackground,
261
- borderWidth: tableBorderSize,
262
- borderColor: tableBorderColor,
263
- borderRadius: tableRadius,
264
- overflow: 'hidden',
265
- }}
266
- >
267
- {rows.map((row, rowIndex) => {
268
- const isLast = rowIndex === rows.length - 1;
269
- const showInfo = row.showInfo || row.onInfoPress != null;
270
- return (
271
- <View
272
- key={row.key ?? `${row.label}-${rowIndex}`}
273
- style={[
274
- rowStyle,
275
- {
276
- borderBottomWidth: isLast ? 0 : cellBorderSize,
277
- borderBottomColor: cellBorderColor,
278
- },
279
- ]}
280
- >
281
- <View style={[labelColumnStyle, labelCellStyle]}>
282
- <Text style={labelTextStyle}>{row.label}</Text>
283
- {showInfo &&
284
- (row.onInfoPress ? (
285
- <Pressable
286
- onPress={row.onInfoPress}
287
- accessibilityRole="button"
288
- accessibilityLabel={`More information about ${row.label}`}
289
- hitSlop={8}
290
- >
291
- <Icon name="ic_info" size={iconSize} color={iconColor} />
292
- </Pressable>
293
- ) : (
294
- <Icon name="ic_info" size={iconSize} color={iconColor} />
295
- ))}
296
- </View>
297
-
298
- {columns.map((column, colIndex) => (
299
- <View
300
- key={column.label ?? colIndex}
301
- style={[planColumnStyle, valueCellStyle]}
302
- >
303
- {renderValue(
304
- row.values?.[colIndex],
305
- `${rowIndex}-${colIndex}`,
306
- )}
307
- </View>
308
- ))}
309
- </View>
310
- );
311
- })}
312
- </View>
324
+ {/* Figma Slot — custom children replace the default table */}
325
+ {children != null
326
+ ? cloneChildrenWithModes(children, modes)
327
+ : defaultTable}
313
328
  </View>
314
329
  );
315
330
  }
@@ -34,7 +34,7 @@ function PortfolioHero({
34
34
  imageSource,
35
35
  value = '12,000.11',
36
36
  currency = '₹',
37
- modes = { Context3: 'Balance' },
37
+ modes = { Context3: 'Balance & Cards' },
38
38
  children,
39
39
  style,
40
40
  }: PortfolioHeroProps) {
@@ -547,9 +547,9 @@ function resolveTokens(modes: Modes): ResolvedTokens {
547
547
  const tooltipBackground = asStr(getVariableByName('tooltip/background', modes), '#0f0d0a')
548
548
  const tooltipPaddingH = asNum(getVariableByName('tooltip/padding/horizontal', modes), 12)
549
549
  const tooltipPaddingV = asNum(getVariableByName('tooltip/padding/vertical', modes), 8)
550
- // Not yet in Coin Variables export (Figma still binds bare local radius/maxWidth).
551
- const tooltipRadius = asNum(getVariableByName('tooltip/radius', modes), 8)
552
- const tooltipMaxWidth = asNum(getVariableByName('tooltip/maxWidth', modes), 280)
550
+ // Figma Tooltip binds bare local `radius` / `maxWidth` (not tooltip/*).
551
+ const tooltipRadius = Number(getVariableByName('radius', modes))
552
+ const tooltipMaxWidth = Number(getVariableByName('maxWidth', modes))
553
553
  const tipWidth = asNum(getVariableByName('tooltip/tipItem/width', modes), 16)
554
554
  const tipHeight = asNum(getVariableByName('tooltip/tipItem/height', modes), 8)
555
555
 
@@ -1,5 +1,5 @@
1
1
  import React, { forwardRef, useRef, useState } from 'react'
2
- import { Platform, Pressable, View, TextInput as RNTextInput, type StyleProp, type ViewStyle, type TextInputProps as RNTextInputProps, type TextStyle } from 'react-native'
2
+ import { Platform, Pressable, StyleSheet, View, TextInput as RNTextInput, type StyleProp, type ViewStyle, type TextInputProps as RNTextInputProps, type TextStyle } from 'react-native'
3
3
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
4
4
  import Icon from '../../icons/Icon'
5
5
  import { EMPTY_MODES, cloneChildrenWithModes, mergeRefs } from '../../utils/react-utils'
@@ -178,28 +178,45 @@ const TextInputBase = forwardRef<RNTextInput, TextInputProps>(function TextInput
178
178
  const hoverStyle: ViewStyle = isHovered ? { opacity: 0.95 } : {}
179
179
  const focusContainerStyle: ViewStyle = isFocused ? { borderColor: '#222', borderWidth: 1 } : {}
180
180
 
181
+ // ⚠️ DO NOT change to an unconditional `lineHeight`, and do NOT put
182
+ // `minHeight: lineHeight` on this single-line input on iOS. Same contract as
183
+ // FormField / SuggestiveSearch — read before "simplifying".
184
+ //
185
+ // facebook/react-native#39145 / #28012: on iOS, a single-line RN TextInput
186
+ // (UITextField) with `lineHeight` > font metrics puts ALL the extra space
187
+ // ABOVE the glyphs, so placeholder/value sink. Android/Web distribute evenly.
188
+ // RN's documented workaround (#39145) and #56774: omit `lineHeight` on
189
+ // single-line iOS inputs — UITextField centers natively — and let the
190
+ // wrapper (`alignItems: 'center'` + padding/icon) own vertical layout.
191
+ // Pinning `lineHeight` to `fontSize` is NOT a substitute; it clips custom
192
+ // font metrics and floats typed text above center.
181
193
  const textInputStyle: TextStyle = {
182
194
  flex: 1,
183
195
  color: textColor,
184
196
  fontSize,
185
- // iOS-only quirk: a single-line RNTextInput with an explicit `lineHeight`
186
- // larger than `fontSize` puts all the extra space ABOVE the glyphs, so the
187
- // text/placeholder sits below center (iOS native font-metrics bug — see
188
- // facebook/react-native#39145 / #28012). The container centers via
189
- // `alignItems: 'center'` + `minHeight`, so omit lineHeight on iOS
190
- // (UITextField centers natively) and keep it on Android/Web where it's fine.
191
197
  ...(Platform.OS === 'ios' ? null : { lineHeight }),
192
198
  fontFamily,
193
199
  fontWeight,
194
200
  padding: 0,
195
201
  margin: 0,
196
- minHeight: lineHeight,
197
202
  // Remove focus outline for React Native Web
198
203
  outlineStyle: 'none' as any,
199
204
  outlineWidth: 0,
200
205
  outlineColor: 'transparent',
201
206
  }
202
207
 
208
+ // On iOS, strip any consumer `inputStyle.lineHeight` so it can't re-break
209
+ // the native centering contract above.
210
+ const resolvedInputStyle: StyleProp<TextStyle> =
211
+ Platform.OS === 'ios' && inputStyle != null
212
+ ? (() => {
213
+ const flat = StyleSheet.flatten(inputStyle) as TextStyle | undefined
214
+ if (!flat || flat.lineHeight == null) return inputStyle
215
+ const { lineHeight: _ignoredLineHeight, ...restStyle } = flat
216
+ return restStyle
217
+ })()
218
+ : inputStyle
219
+
203
220
  // Determine leading element: use provided leading, or default to icon from leadingIconName
204
221
  const leadingElement = leading || (
205
222
  <Icon name={leadingIconName} size={iconSize} color={iconColor} />
@@ -266,7 +283,7 @@ const TextInputBase = forwardRef<RNTextInput, TextInputProps>(function TextInput
266
283
  placeholderTextColor={placeholderColor}
267
284
  onFocus={handleFocus}
268
285
  onBlur={handleBlur}
269
- style={[textInputStyle, inputStyle]}
286
+ style={[textInputStyle, resolvedInputStyle]}
270
287
  {...rest}
271
288
  />
272
289
  {processedTrailing && (
@@ -262,14 +262,12 @@ export function TooltipContent({
262
262
  const insets = useSafeAreaInsets()
263
263
  const { width: windowWidth, height: windowHeight } = Dimensions.get('window')
264
264
 
265
- // Tokens
265
+ // Tokens — Figma binds Output/Tooltip `radius` + `maxWidth` (not tooltip/*).
266
266
  const bgColor = (getVariableByName('tooltip/background', modes) as string)
267
267
  const paddingH = Number(getVariableByName('tooltip/padding/horizontal', modes))
268
268
  const paddingV = Number(getVariableByName('tooltip/padding/vertical', modes))
269
- // Figma still binds bare local radius/maxWidth; published names are tooltip/*.
270
- // Not yet in Coin Variables export — fall back to Figma values (8 / 280).
271
- const radius = Number(getVariableByName('tooltip/radius', modes) ?? 8)
272
- const maxWidthToken = Number(getVariableByName('tooltip/maxWidth', modes) ?? 280)
269
+ const radius = Number(getVariableByName('radius', modes))
270
+ const maxWidthToken = Number(getVariableByName('maxWidth', modes))
273
271
  const arrowWidth = Number(getVariableByName('tooltip/tipItem/width', modes))
274
272
  const arrowHeight = Number(getVariableByName('tooltip/tipItem/height', modes))
275
273
  const labelColor = (getVariableByName('tooltip/label/color', modes) as string)
@@ -14,8 +14,15 @@ export { default as BenefitCard, type BenefitCardProps, type BenefitCardItem } f
14
14
  export { default as BottomNav } from './BottomNav/BottomNav'
15
15
  export { default as BottomNavItem } from './BottomNavItem/BottomNavItem'
16
16
  export { default as BrandChip, type BrandChipProps } from './BrandChip/BrandChip'
17
- export { default as Button, type ButtonProps } from './Button/Button'
18
- export { default as Card } from './Card/Card'
17
+ export { default as Button, type ButtonProps, type ButtonType } from './Button/Button'
18
+ export {
19
+ default as Card,
20
+ type CardProps,
21
+ type CardTextProps,
22
+ type CardVariant,
23
+ type CardVariantInput,
24
+ CARD_MEDIA_ASPECT_RATIO,
25
+ } from './Card/Card'
19
26
  export { default as CardAdvisory, type CardAdvisoryProps } from './CardAdvisory/CardAdvisory'
20
27
  export {
21
28
  default as CardBankAccount,