jfs-components 0.1.8 → 0.1.12

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 (238) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/lib/commonjs/components/Accordion/Accordion.js +2 -1
  3. package/lib/commonjs/components/AccordionCheckbox/AccordionCheckbox.js +4 -3
  4. package/lib/commonjs/components/AccountCard/AccountCard.js +4 -3
  5. package/lib/commonjs/components/AllocationComparisonChart/AllocationComparisonChart.js +9 -6
  6. package/lib/commonjs/components/AreaLineChart/AreaLineChart.js +11 -7
  7. package/lib/commonjs/components/Badge/Badge.js +47 -8
  8. package/lib/commonjs/components/BrandChip/BrandChip.js +3 -3
  9. package/lib/commonjs/components/Button/Button.js +2 -1
  10. package/lib/commonjs/components/CardAdvisory/CardAdvisory.js +2 -1
  11. package/lib/commonjs/components/CardBankAccount/CardBankAccount.js +3 -2
  12. package/lib/commonjs/components/CardCTA/CardCTA.js +197 -27
  13. package/lib/commonjs/components/CardInsight/CardInsight.js +5 -1
  14. package/lib/commonjs/components/CircularProgressBar/CircularProgressBar.js +7 -3
  15. package/lib/commonjs/components/CircularProgressBarDoted/CircularProgressBarDoted.js +3 -2
  16. package/lib/commonjs/components/CircularRating/CircularRating.js +2 -1
  17. package/lib/commonjs/components/ClusterBubble/ClusterBubble.js +3 -2
  18. package/lib/commonjs/components/CompareTable/CompareTable.js +72 -43
  19. package/lib/commonjs/components/CoverageBarComparison/CoverageBarComparison.js +3 -2
  20. package/lib/commonjs/components/DonutChart/DonutChart.js +4 -3
  21. package/lib/commonjs/components/Dropdown/Dropdown.js +3 -2
  22. package/lib/commonjs/components/DropdownInput/DropdownInput.js +3 -2
  23. package/lib/commonjs/components/ExpandableCheckbox/ExpandableCheckbox.js +2 -1
  24. package/lib/commonjs/components/FormField/FormField.js +42 -2
  25. package/lib/commonjs/components/Gauge/Gauge.js +1 -1
  26. package/lib/commonjs/components/HoldingsCard/HoldingsCard.js +3 -2
  27. package/lib/commonjs/components/InstitutionBadge/InstitutionBadge.js +2 -1
  28. package/lib/commonjs/components/ListGroup/ListGroup.js +2 -1
  29. package/lib/commonjs/components/ListItem/ListItem.js +3 -2
  30. package/lib/commonjs/components/MediaCard/MediaCard.js +4 -2
  31. package/lib/commonjs/components/MetricLegendItem/MetricLegendItem.js +3 -2
  32. package/lib/commonjs/components/MonthlyStatusGrid/MonthlyStatusGrid.js +6 -3
  33. package/lib/commonjs/components/PoweredByLabel/PoweredByLabel.js +3 -2
  34. package/lib/commonjs/components/ProductMerchandisingCard/ProductMerchandisingCard.js +107 -33
  35. package/lib/commonjs/components/ProductOverview/ProductOverview.js +4 -3
  36. package/lib/commonjs/components/ProgressBadge/ProgressBadge.js +2 -2
  37. package/lib/commonjs/components/ProjectionMarker/ProjectionMarker.js +3 -2
  38. package/lib/commonjs/components/Section/Section.js +3 -2
  39. package/lib/commonjs/components/Slider/Slider.js +3 -2
  40. package/lib/commonjs/components/Stack/Stack.js +86 -0
  41. package/lib/commonjs/components/SuggestiveSearch/SuggestiveSearch.js +10 -1
  42. package/lib/commonjs/components/SummaryTile/SummaryTile.js +3 -2
  43. package/lib/commonjs/components/Table/Table.js +324 -0
  44. package/lib/commonjs/components/Text/Text.js +2 -1
  45. package/lib/commonjs/components/TextInput/TextInput.js +9 -1
  46. package/lib/commonjs/components/TextSegment/TextSegment.js +2 -1
  47. package/lib/commonjs/components/ThreadHero/ThreadHero.js +4 -5
  48. package/lib/commonjs/components/Title/Title.js +2 -1
  49. package/lib/commonjs/components/Toast/Toast.js +3 -2
  50. package/lib/commonjs/components/TransactionBubble/TransactionBubble.js +2 -1
  51. package/lib/commonjs/components/TransactionStatus/TransactionStatus.js +3 -2
  52. package/lib/commonjs/components/UpiHandle/UpiHandle.js +2 -2
  53. package/lib/commonjs/components/index.js +44 -0
  54. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
  55. package/lib/commonjs/design-tokens/figma-modes.generated.js +1 -0
  56. package/lib/commonjs/icons/registry.js +1 -1
  57. package/lib/commonjs/{components/ProductMerchandisingCard → utils/GlassFill}/GlassFill.js +11 -3
  58. package/lib/commonjs/{components/ProductMerchandisingCard → utils/GlassFill}/GlassFill.web.js +8 -4
  59. package/lib/commonjs/utils/react-utils.js +56 -0
  60. package/lib/commonjs/utils/web-platform-utils.js +59 -0
  61. package/lib/module/components/Accordion/Accordion.js +3 -2
  62. package/lib/module/components/AccordionCheckbox/AccordionCheckbox.js +5 -4
  63. package/lib/module/components/AccountCard/AccountCard.js +5 -4
  64. package/lib/module/components/AllocationComparisonChart/AllocationComparisonChart.js +10 -7
  65. package/lib/module/components/AreaLineChart/AreaLineChart.js +12 -8
  66. package/lib/module/components/Badge/Badge.js +49 -10
  67. package/lib/module/components/BrandChip/BrandChip.js +4 -4
  68. package/lib/module/components/Button/Button.js +3 -2
  69. package/lib/module/components/CardAdvisory/CardAdvisory.js +3 -2
  70. package/lib/module/components/CardBankAccount/CardBankAccount.js +4 -3
  71. package/lib/module/components/CardCTA/CardCTA.js +198 -28
  72. package/lib/module/components/CardInsight/CardInsight.js +5 -1
  73. package/lib/module/components/CircularProgressBar/CircularProgressBar.js +8 -4
  74. package/lib/module/components/CircularProgressBarDoted/CircularProgressBarDoted.js +4 -3
  75. package/lib/module/components/CircularRating/CircularRating.js +3 -2
  76. package/lib/module/components/ClusterBubble/ClusterBubble.js +4 -3
  77. package/lib/module/components/CompareTable/CompareTable.js +73 -44
  78. package/lib/module/components/CoverageBarComparison/CoverageBarComparison.js +4 -3
  79. package/lib/module/components/DonutChart/DonutChart.js +5 -4
  80. package/lib/module/components/Dropdown/Dropdown.js +4 -3
  81. package/lib/module/components/DropdownInput/DropdownInput.js +4 -3
  82. package/lib/module/components/ExpandableCheckbox/ExpandableCheckbox.js +2 -1
  83. package/lib/module/components/FormField/FormField.js +45 -5
  84. package/lib/module/components/Gauge/Gauge.js +1 -1
  85. package/lib/module/components/HoldingsCard/HoldingsCard.js +4 -3
  86. package/lib/module/components/InstitutionBadge/InstitutionBadge.js +3 -2
  87. package/lib/module/components/ListGroup/ListGroup.js +3 -2
  88. package/lib/module/components/ListItem/ListItem.js +4 -3
  89. package/lib/module/components/MediaCard/MediaCard.js +4 -2
  90. package/lib/module/components/MetricLegendItem/MetricLegendItem.js +4 -3
  91. package/lib/module/components/MonthlyStatusGrid/MonthlyStatusGrid.js +7 -4
  92. package/lib/module/components/PoweredByLabel/PoweredByLabel.js +4 -3
  93. package/lib/module/components/ProductMerchandisingCard/ProductMerchandisingCard.js +109 -35
  94. package/lib/module/components/ProductOverview/ProductOverview.js +5 -4
  95. package/lib/module/components/ProgressBadge/ProgressBadge.js +3 -3
  96. package/lib/module/components/ProjectionMarker/ProjectionMarker.js +4 -3
  97. package/lib/module/components/Section/Section.js +4 -3
  98. package/lib/module/components/Slider/Slider.js +4 -3
  99. package/lib/module/components/Stack/Stack.js +81 -0
  100. package/lib/module/components/SuggestiveSearch/SuggestiveSearch.js +10 -1
  101. package/lib/module/components/SummaryTile/SummaryTile.js +4 -3
  102. package/lib/module/components/Table/Table.js +315 -0
  103. package/lib/module/components/Text/Text.js +3 -2
  104. package/lib/module/components/TextInput/TextInput.js +9 -1
  105. package/lib/module/components/TextSegment/TextSegment.js +3 -2
  106. package/lib/module/components/ThreadHero/ThreadHero.js +5 -6
  107. package/lib/module/components/Title/Title.js +3 -2
  108. package/lib/module/components/Toast/Toast.js +4 -3
  109. package/lib/module/components/TransactionBubble/TransactionBubble.js +3 -2
  110. package/lib/module/components/TransactionStatus/TransactionStatus.js +4 -3
  111. package/lib/module/components/UpiHandle/UpiHandle.js +3 -3
  112. package/lib/module/components/index.js +2 -0
  113. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
  114. package/lib/module/design-tokens/figma-modes.generated.js +1 -0
  115. package/lib/module/icons/registry.js +1 -1
  116. package/lib/module/{components/ProductMerchandisingCard → utils/GlassFill}/GlassFill.js +11 -3
  117. package/lib/module/{components/ProductMerchandisingCard → utils/GlassFill}/GlassFill.web.js +8 -4
  118. package/lib/module/utils/react-utils.js +54 -0
  119. package/lib/module/utils/web-platform-utils.js +58 -0
  120. package/lib/typescript/src/components/Accordion/Accordion.d.ts +7 -1
  121. package/lib/typescript/src/components/AccordionCheckbox/AccordionCheckbox.d.ts +7 -1
  122. package/lib/typescript/src/components/AccountCard/AccountCard.d.ts +7 -1
  123. package/lib/typescript/src/components/AllocationComparisonChart/AllocationComparisonChart.d.ts +7 -1
  124. package/lib/typescript/src/components/AreaLineChart/AreaLineChart.d.ts +8 -1
  125. package/lib/typescript/src/components/Badge/Badge.d.ts +32 -2
  126. package/lib/typescript/src/components/BrandChip/BrandChip.d.ts +7 -1
  127. package/lib/typescript/src/components/Button/Button.d.ts +7 -1
  128. package/lib/typescript/src/components/CardAdvisory/CardAdvisory.d.ts +7 -1
  129. package/lib/typescript/src/components/CardBankAccount/CardBankAccount.d.ts +7 -1
  130. package/lib/typescript/src/components/CardCTA/CardCTA.d.ts +27 -5
  131. package/lib/typescript/src/components/CircularProgressBar/CircularProgressBar.d.ts +7 -1
  132. package/lib/typescript/src/components/CircularProgressBarDoted/CircularProgressBarDoted.d.ts +7 -1
  133. package/lib/typescript/src/components/CircularRating/CircularRating.d.ts +7 -1
  134. package/lib/typescript/src/components/ClusterBubble/ClusterBubble.d.ts +7 -1
  135. package/lib/typescript/src/components/CompareTable/CompareTable.d.ts +7 -1
  136. package/lib/typescript/src/components/CoverageBarComparison/CoverageBarComparison.d.ts +7 -1
  137. package/lib/typescript/src/components/DonutChart/DonutChart.d.ts +7 -1
  138. package/lib/typescript/src/components/Dropdown/Dropdown.d.ts +7 -1
  139. package/lib/typescript/src/components/DropdownInput/DropdownInput.d.ts +6 -0
  140. package/lib/typescript/src/components/ExpandableCheckbox/ExpandableCheckbox.d.ts +7 -1
  141. package/lib/typescript/src/components/HoldingsCard/HoldingsCard.d.ts +7 -1
  142. package/lib/typescript/src/components/InstitutionBadge/InstitutionBadge.d.ts +7 -1
  143. package/lib/typescript/src/components/ListGroup/ListGroup.d.ts +7 -1
  144. package/lib/typescript/src/components/ListItem/ListItem.d.ts +7 -1
  145. package/lib/typescript/src/components/MetricLegendItem/MetricLegendItem.d.ts +7 -1
  146. package/lib/typescript/src/components/MonthlyStatusGrid/MonthlyStatusGrid.d.ts +14 -2
  147. package/lib/typescript/src/components/PoweredByLabel/PoweredByLabel.d.ts +7 -1
  148. package/lib/typescript/src/components/ProductMerchandisingCard/ProductMerchandisingCard.d.ts +7 -1
  149. package/lib/typescript/src/components/ProductOverview/ProductOverview.d.ts +7 -1
  150. package/lib/typescript/src/components/ProgressBadge/ProgressBadge.d.ts +7 -1
  151. package/lib/typescript/src/components/ProjectionMarker/ProjectionMarker.d.ts +7 -1
  152. package/lib/typescript/src/components/Section/Section.d.ts +7 -1
  153. package/lib/typescript/src/components/Slider/Slider.d.ts +6 -0
  154. package/lib/typescript/src/components/Stack/Stack.d.ts +81 -0
  155. package/lib/typescript/src/components/SummaryTile/SummaryTile.d.ts +7 -1
  156. package/lib/typescript/src/components/Table/Table.d.ts +139 -0
  157. package/lib/typescript/src/components/Text/Text.d.ts +7 -1
  158. package/lib/typescript/src/components/TextSegment/TextSegment.d.ts +7 -1
  159. package/lib/typescript/src/components/ThreadHero/ThreadHero.d.ts +7 -1
  160. package/lib/typescript/src/components/Title/Title.d.ts +7 -1
  161. package/lib/typescript/src/components/Toast/Toast.d.ts +7 -1
  162. package/lib/typescript/src/components/TransactionBubble/TransactionBubble.d.ts +7 -1
  163. package/lib/typescript/src/components/TransactionStatus/TransactionStatus.d.ts +7 -1
  164. package/lib/typescript/src/components/UpiHandle/UpiHandle.d.ts +7 -1
  165. package/lib/typescript/src/components/index.d.ts +4 -2
  166. package/lib/typescript/src/icons/registry.d.ts +1 -1
  167. package/lib/typescript/src/{components/ProductMerchandisingCard → utils/GlassFill}/GlassFill.d.ts +31 -3
  168. package/lib/typescript/src/{components/ProductMerchandisingCard → utils/GlassFill}/GlassFill.web.d.ts +18 -2
  169. package/lib/typescript/src/utils/react-utils.d.ts +37 -0
  170. package/lib/typescript/src/utils/web-platform-utils.d.ts +36 -0
  171. package/package.json +1 -1
  172. package/src/components/Accordion/Accordion.tsx +9 -2
  173. package/src/components/AccordionCheckbox/AccordionCheckbox.tsx +10 -3
  174. package/src/components/AccountCard/AccountCard.tsx +10 -3
  175. package/src/components/AllocationComparisonChart/AllocationComparisonChart.tsx +14 -4
  176. package/src/components/AreaLineChart/AreaLineChart.tsx +15 -5
  177. package/src/components/Badge/Badge.tsx +78 -8
  178. package/src/components/BrandChip/BrandChip.tsx +9 -3
  179. package/src/components/Button/Button.tsx +9 -2
  180. package/src/components/CardAdvisory/CardAdvisory.tsx +9 -2
  181. package/src/components/CardBankAccount/CardBankAccount.tsx +9 -2
  182. package/src/components/CardCTA/CardCTA.tsx +214 -41
  183. package/src/components/CardInsight/CardInsight.tsx +7 -1
  184. package/src/components/CircularProgressBar/CircularProgressBar.tsx +14 -4
  185. package/src/components/CircularProgressBarDoted/CircularProgressBarDoted.tsx +10 -3
  186. package/src/components/CircularRating/CircularRating.tsx +9 -2
  187. package/src/components/ClusterBubble/ClusterBubble.tsx +9 -2
  188. package/src/components/CompareTable/CompareTable.tsx +98 -50
  189. package/src/components/CoverageBarComparison/CoverageBarComparison.tsx +9 -2
  190. package/src/components/DonutChart/DonutChart.tsx +10 -3
  191. package/src/components/Dropdown/Dropdown.tsx +9 -2
  192. package/src/components/DropdownInput/DropdownInput.tsx +9 -2
  193. package/src/components/ExpandableCheckbox/ExpandableCheckbox.tsx +8 -1
  194. package/src/components/FormField/FormField.tsx +49 -4
  195. package/src/components/Gauge/Gauge.tsx +1 -1
  196. package/src/components/HoldingsCard/HoldingsCard.tsx +9 -2
  197. package/src/components/InstitutionBadge/InstitutionBadge.tsx +9 -2
  198. package/src/components/ListGroup/ListGroup.tsx +9 -2
  199. package/src/components/ListItem/ListItem.tsx +10 -3
  200. package/src/components/MediaCard/MediaCard.tsx +2 -2
  201. package/src/components/MetricLegendItem/MetricLegendItem.tsx +9 -2
  202. package/src/components/MonthlyStatusGrid/MonthlyStatusGrid.tsx +17 -2
  203. package/src/components/PoweredByLabel/PoweredByLabel.tsx +9 -2
  204. package/src/components/ProductMerchandisingCard/ProductMerchandisingCard.tsx +104 -37
  205. package/src/components/ProductOverview/ProductOverview.tsx +10 -3
  206. package/src/components/ProgressBadge/ProgressBadge.tsx +9 -3
  207. package/src/components/ProjectionMarker/ProjectionMarker.tsx +9 -2
  208. package/src/components/Section/Section.tsx +10 -3
  209. package/src/components/Slider/Slider.tsx +9 -2
  210. package/src/components/Stack/Stack.tsx +131 -0
  211. package/src/components/SuggestiveSearch/SuggestiveSearch.tsx +10 -1
  212. package/src/components/SummaryTile/SummaryTile.tsx +9 -2
  213. package/src/components/Table/Table.tsx +449 -0
  214. package/src/components/Text/Text.tsx +9 -2
  215. package/src/components/TextInput/TextInput.tsx +7 -1
  216. package/src/components/TextSegment/TextSegment.tsx +9 -2
  217. package/src/components/ThreadHero/ThreadHero.tsx +10 -3
  218. package/src/components/Title/Title.tsx +9 -2
  219. package/src/components/Toast/Toast.tsx +9 -2
  220. package/src/components/TransactionBubble/TransactionBubble.tsx +9 -2
  221. package/src/components/TransactionStatus/TransactionStatus.tsx +9 -2
  222. package/src/components/UpiHandle/UpiHandle.tsx +9 -3
  223. package/src/components/index.ts +23 -2
  224. package/src/design-tokens/Coin Variables-variables-full.json +1 -1
  225. package/src/design-tokens/figma-modes.generated.ts +1 -0
  226. package/src/icons/registry.ts +1 -1
  227. package/src/{components/ProductMerchandisingCard → utils/GlassFill}/GlassFill.tsx +32 -3
  228. package/src/{components/ProductMerchandisingCard → utils/GlassFill}/GlassFill.web.tsx +25 -4
  229. package/src/utils/react-utils.ts +61 -0
  230. package/src/utils/web-platform-utils.ts +68 -0
  231. package/lib/commonjs/components/MediaCard/GlassFill.js +0 -62
  232. package/lib/commonjs/components/MediaCard/GlassFill.web.js +0 -48
  233. package/lib/module/components/MediaCard/GlassFill.js +0 -57
  234. package/lib/module/components/MediaCard/GlassFill.web.js +0 -43
  235. package/lib/typescript/src/components/MediaCard/GlassFill.d.ts +0 -47
  236. package/lib/typescript/src/components/MediaCard/GlassFill.web.d.ts +0 -20
  237. package/src/components/MediaCard/GlassFill.tsx +0 -89
  238. package/src/components/MediaCard/GlassFill.web.tsx +0 -53
@@ -1,5 +1,5 @@
1
1
  import React from 'react'
2
- import { View, Text, type ViewStyle, type TextStyle, type StyleProp } from 'react-native'
2
+ import { View, Text, type ViewStyle, type TextStyle, type StyleProp, type ImageSourcePropType } from 'react-native'
3
3
  import { findVariablesByPattern, getVariableByName } from '../../design-tokens/figma-variables-resolver'
4
4
  import { useTokens } from '../../design-tokens/JFSThemeProvider'
5
5
  import { cloneChildrenWithModes, EMPTY_MODES } from '../../utils/react-utils'
@@ -8,12 +8,24 @@ import Button from '../Button/Button'
8
8
  import Badge from '../Badge/Badge'
9
9
  import ButtonGroup from '../ButtonGroup/ButtonGroup'
10
10
  import IconButton from '../IconButton/IconButton'
11
+ import Avatar from '../Avatar/Avatar'
11
12
  import type { Modes } from '../../design-tokens'
12
13
 
13
- export type CardCTAType = 'cta' | 'rating'
14
+ export type CardCTAType = 'cta' | 'rating' | 'sip'
15
+
16
+ /**
17
+ * Accepted `type` values. The canonical (and historical) values are lowercase
18
+ * (`'cta' | 'rating' | 'sip'`). The Figma variant names are PascalCase
19
+ * (`'CTA' | 'Rating' | 'Sip'`) — we accept those too and normalize internally
20
+ * so callers can pass either casing without a breaking change.
21
+ */
22
+ export type CardCTATypeInput = CardCTAType | 'CTA' | 'Rating' | 'Sip'
14
23
 
15
24
  const optionalTokenAvailability = new Map<string, boolean>()
16
25
 
26
+ const toWeight = (w: number | string): TextStyle['fontWeight'] =>
27
+ (typeof w === 'number' ? `${w}` : w) as TextStyle['fontWeight']
28
+
17
29
  function getOptionalVariableByName<T>(name: string, modes: Modes, fallback: T): T {
18
30
  let isAvailable = optionalTokenAvailability.get(name)
19
31
 
@@ -30,8 +42,13 @@ function getOptionalVariableByName<T>(name: string, modes: Modes, fallback: T):
30
42
  }
31
43
 
32
44
  export type CardCTAProps = {
33
- /** Visual layout variant */
34
- type?: CardCTAType;
45
+ /**
46
+ * Visual layout variant. `'cta'` (default) is the horizontal CTA card,
47
+ * `'rating'` is the rating card, and `'sip'` is the avatar-led card with a
48
+ * headline/description/subtitle footer. PascalCase Figma names
49
+ * (`'CTA' | 'Rating' | 'Sip'`) are also accepted.
50
+ */
51
+ type?: CardCTATypeInput;
35
52
  /** Title text */
36
53
  title?: string;
37
54
  /** Body / subtitle text */
@@ -50,6 +67,14 @@ export type CardCTAProps = {
50
67
  onPressLike?: () => void;
51
68
  /** Callback for the default dislike action */
52
69
  onPressDislike?: () => void;
70
+ /** [sip] Headline text shown above the description row */
71
+ headline?: string;
72
+ /** [sip] Description text (left of subtitle) */
73
+ description?: string;
74
+ /** [sip] Subtitle text (right of description) */
75
+ subtitle?: string;
76
+ /** [sip] Image source for the default Avatar */
77
+ avatarSource?: ImageSourcePropType | string;
53
78
  /** Mode configuration for design token resolution */
54
79
  modes?: Modes;
55
80
  /** Slot: replaces the default icon area (right side) */
@@ -60,6 +85,8 @@ export type CardCTAProps = {
60
85
  ratingBadgeSlot?: React.ReactNode;
61
86
  /** Slot: replaces the default like/dislike action group */
62
87
  ratingActionsSlot?: React.ReactNode;
88
+ /** [sip] Slot: replaces the default Avatar */
89
+ avatarSlot?: React.ReactNode;
63
90
  /** Container style overrides */
64
91
  style?: StyleProp<ViewStyle>;
65
92
  };
@@ -75,16 +102,24 @@ function CardCTA({
75
102
  showRatingActions = true,
76
103
  onPressLike,
77
104
  onPressDislike,
105
+ headline = 'Headline',
106
+ description = 'Description',
107
+ subtitle = 'Subtitle',
108
+ avatarSource,
78
109
  modes: propModes = EMPTY_MODES,
79
110
  iconSlot,
80
111
  buttonSlot,
81
112
  ratingBadgeSlot,
82
113
  ratingActionsSlot,
114
+ avatarSlot,
83
115
  style,
84
116
  }: CardCTAProps) {
85
117
  const { modes: globalModes } = useTokens()
86
118
  const modes = { ...globalModes, ...propModes }
87
- const isRating = type === 'rating'
119
+ // Accept both lowercase (canonical) and Figma PascalCase variant names.
120
+ const normalizedType = (typeof type === 'string' ? type.toLowerCase() : 'cta') as CardCTAType
121
+ const isRating = normalizedType === 'rating'
122
+ const isSip = normalizedType === 'sip'
88
123
 
89
124
  const background = getVariableByName('cardCTA/background', modes) || '#ffffff'
90
125
  const radius = getVariableByName('cardCTA/radius', modes) || 12
@@ -103,14 +138,14 @@ function CardCTA({
103
138
  const titleColor = getVariableByName('cardCTA/title/color', modes) || '#0c0d10'
104
139
  const titleFontFamily = getVariableByName('cardCTA/title/fontFamily', modes) || 'JioType Var'
105
140
  const titleFontSize = getVariableByName('cardCTA/title/fontSize', modes) || 16
106
- const titleLineHeight = getVariableByName('cardCTA/title/lineHeight', modes) || 17.6
141
+ const titleLineHeight = getVariableByName('cardCTA/title/lineHeight', modes) || 18
107
142
  const titleFontWeightRaw = getVariableByName('cardCTA/title/fontWeight', modes) || 700
108
143
  const titleFontWeight = typeof titleFontWeightRaw === 'number' ? titleFontWeightRaw.toString() : titleFontWeightRaw
109
144
 
110
145
  const bodyColor = getVariableByName('cardCTA/body/color', modes) || '#13002d'
111
146
  const bodyFontFamily = getVariableByName('cardCTA/body/fontFamily', modes) || 'JioType Var'
112
- const bodyFontSize = getVariableByName('cardCTA/body/fontSize', modes) || 10
113
- const bodyLineHeight = getVariableByName('cardCTA/body/lineHeight', modes) || 12
147
+ const bodyFontSize = getVariableByName('cardCTA/body/fontSize', modes) || 14
148
+ const bodyLineHeight = getVariableByName('cardCTA/body/lineHeight', modes) || 17
114
149
  const bodyFontWeightRaw = getVariableByName('cardCTA/body/fontWeight', modes) || 400
115
150
  const bodyFontWeight = typeof bodyFontWeightRaw === 'number' ? bodyFontWeightRaw.toString() : bodyFontWeightRaw
116
151
 
@@ -122,6 +157,34 @@ function CardCTA({
122
157
  const ratingFooterPaddingTop = getOptionalVariableByName('cardCTA/rating/footer/top', modes, 0)
123
158
  const ratingFooterPaddingBottom = getOptionalVariableByName('cardCTA/rating/footer/bottom', modes, 12)
124
159
 
160
+ // Sip variant footer text block (Headline / Description / Subtitle). These
161
+ // `ccCard/*` tokens were introduced for the Sip layout — resolve them
162
+ // optionally so older token snapshots that lack them fall back gracefully.
163
+ const sipFooterGap = getOptionalVariableByName('ccCard/footer/textWrap/gap', modes, 4)
164
+
165
+ const sipHeadlineColor = getOptionalVariableByName('ccCard/headline/color', modes, '#707275')
166
+ const sipHeadlineFontFamily = getOptionalVariableByName('ccCard/headline/fontFamily', modes, 'JioType Var')
167
+ const sipHeadlineFontSize = getOptionalVariableByName('ccCard/headline/fontSize', modes, 10)
168
+ const sipHeadlineLineHeight = getOptionalVariableByName('ccCard/headline/lineHeight', modes, 13)
169
+ const sipHeadlineFontWeightRaw = getOptionalVariableByName<string | number>('ccCard/headline/fontWeight', modes, 500)
170
+ const sipHeadlineFontWeight = typeof sipHeadlineFontWeightRaw === 'number' ? sipHeadlineFontWeightRaw.toString() : sipHeadlineFontWeightRaw
171
+
172
+ const sipDescriptionColor = getOptionalVariableByName('ccCard/title/color', modes, '#081007')
173
+ const sipDescriptionFontFamily = getOptionalVariableByName('ccCard/title/fontFamily', modes, 'JioType Var')
174
+ const sipDescriptionFontSize = getOptionalVariableByName('ccCard/title/fontSize', modes, 14)
175
+ const sipDescriptionLineHeight = getOptionalVariableByName('ccCard/title/lineHeight', modes, 15)
176
+ const sipDescriptionFontWeightRaw = getOptionalVariableByName<string | number>('ccCard/title/fontWeight', modes, 700)
177
+ const sipDescriptionFontWeight = typeof sipDescriptionFontWeightRaw === 'number' ? sipDescriptionFontWeightRaw.toString() : sipDescriptionFontWeightRaw
178
+
179
+ const sipSubtitleColor = getOptionalVariableByName('ccCard/subtitle/color', modes, '#707275')
180
+ const sipSubtitleFontFamily = getOptionalVariableByName('ccCard/subtitle/fontFamily', modes, 'JioType Var')
181
+ const sipSubtitleFontSize = getOptionalVariableByName('ccCard/subtitle/fontSize', modes, 10)
182
+ const sipSubtitleLineHeight = getOptionalVariableByName('ccCard/subtitle/lineHeight', modes, 13)
183
+ const sipSubtitleFontWeightRaw = getOptionalVariableByName<string | number>('ccCard/subtitle/fontWeight', modes, 500)
184
+ const sipSubtitleFontWeight = typeof sipSubtitleFontWeightRaw === 'number' ? sipSubtitleFontWeightRaw.toString() : sipSubtitleFontWeightRaw
185
+
186
+ const avatarModes = { ...modes, 'Avatar Size': 'L' }
187
+
125
188
  const buttonModes = {...modes, AppearanceBrand: 'Secondary', 'Button / Size': 'S'}
126
189
  const iconButtonModes = {'Button / Size': 'S', 'Emphasis': 'Low', 'AppearanceBrand':'Neutral',...modes }
127
190
  const effectiveButtonLabel = buttonLabel ?? (isRating ? 'Save' : 'Button')
@@ -130,33 +193,72 @@ function CardCTA({
130
193
 
131
194
  const buttonPaddingH = getVariableByName('button/padding/horizontal', buttonModes) || 20
132
195
  const buttonBorderSize = getVariableByName('button/border/size', buttonModes) ?? 1
196
+ // Small safety pad so sub-pixel rounding between the measure pass and the
197
+ // rendered glyph run can never clip the final character.
133
198
  const measuredButtonWidth = measuredButtonLabelWidth === null
134
199
  ? undefined
135
- : Math.ceil(measuredButtonLabelWidth + (buttonPaddingH as number) * 2 + (buttonBorderSize as number) * 2)
136
-
137
- const handleButtonLabelTextLayout = React.useCallback((event: any) => {
138
- const lines = event?.nativeEvent?.lines
139
- if (!Array.isArray(lines) || lines.length === 0) return
140
-
141
- const nextWidth = Math.ceil(
142
- lines.reduce((sum, line) => sum + (typeof line?.width === 'number' ? line.width : 0), 0)
200
+ : Math.ceil(measuredButtonLabelWidth + (buttonPaddingH as number) * 2 + (buttonBorderSize as number) * 2 + 2)
201
+
202
+ // ROOT-CAUSE FIX for the Android "jumping button" feedback loop + clipping.
203
+ //
204
+ // Previously the *visible* label measured itself via `onTextLayout` and we
205
+ // fed that width straight back onto the same Button. Because the button is
206
+ // width-constrained by its parent, Android clamps the overflowing text and
207
+ // even character-breaks a single word ("Save" -> "Sav"/"e"); the re-applied
208
+ // width re-triggers layout, producing an endless ~60fps width oscillation.
209
+ //
210
+ // We now measure an *off-screen* copy of the label, laid out at an
211
+ // effectively unbounded width, so its reported single-line width is
212
+ // intrinsic and constant regardless of the button's applied width — the
213
+ // measurement can no longer depend on the value it produces, so the loop is
214
+ // impossible. Crucially the measurer reuses the Button's own resolved label
215
+ // styles (passed into `renderContent`), so the measured font matches the
216
+ // rendered font exactly and the width is never an under-estimate. The label
217
+ // is always a single line that overflows rather than wraps or truncates.
218
+ const handleMeasureTextLayout = React.useCallback((event: any) => {
219
+ const line = event?.nativeEvent?.lines?.[0]
220
+ const width = line && typeof line.width === 'number' ? line.width : 0
221
+ if (width <= 0) return
222
+
223
+ setMeasuredButtonLabelWidth((currentWidth) =>
224
+ currentWidth !== null && Math.abs(currentWidth - width) < 0.5
225
+ ? currentWidth
226
+ : Math.ceil(width)
143
227
  )
144
- if (nextWidth <= 0) return
145
-
146
- setMeasuredButtonLabelWidth((currentWidth) => {
147
- if (currentWidth !== null && Math.abs(currentWidth - nextWidth) < 1) {
148
- return currentWidth
149
- }
150
- return nextWidth
151
- })
152
228
  }, [])
153
229
 
230
+ // Off-screen measurer wrapper: absolute + opacity 0 + pointerEvents none =>
231
+ // out of flow, invisible, never intercepts touches, zero layout impact.
232
+ const measurerWrapStyle: ViewStyle = { position: 'absolute', left: 0, top: 0, opacity: 0 }
233
+
234
+ // Renders the visible single-line label (never wraps, never ellipsizes) plus
235
+ // the co-located off-screen measurer that shares the Button's exact label
236
+ // styles. `width: 9999` forces the measurer onto one line so `onTextLayout`
237
+ // reports the true intrinsic width.
238
+ const renderButtonContent = (labelExtraStyle: TextStyle) => (labelStyles: StyleProp<TextStyle>) => (
239
+ <>
240
+ <Text numberOfLines={1} ellipsizeMode="clip" style={[labelStyles, labelExtraStyle]}>
241
+ {nonWrappingButtonLabel}
242
+ </Text>
243
+ <View
244
+ pointerEvents="none"
245
+ accessibilityElementsHidden
246
+ importantForAccessibility="no"
247
+ style={measurerWrapStyle}
248
+ >
249
+ <Text style={[labelStyles, { width: 9999 }]} onTextLayout={handleMeasureTextLayout}>
250
+ {nonWrappingButtonLabel}
251
+ </Text>
252
+ </View>
253
+ </>
254
+ )
255
+
154
256
  const containerStyle: ViewStyle = {
155
257
  backgroundColor: background,
156
258
  borderRadius: radius,
157
259
  borderWidth: borderSize,
158
260
  borderColor,
159
- flexDirection: isRating ? 'column' : 'row',
261
+ flexDirection: isRating || isSip ? 'column' : 'row',
160
262
  overflow: 'visible',
161
263
  }
162
264
 
@@ -245,6 +347,53 @@ function CardCTA({
245
347
  fontWeight: bodyFontWeight,
246
348
  }
247
349
 
350
+ const sipWrapStyle: ViewStyle = {
351
+ width: '100%',
352
+ paddingHorizontal: leftPaddingH,
353
+ paddingVertical: leftPaddingV,
354
+ gap: leftGap,
355
+ alignItems: 'flex-start',
356
+ justifyContent: 'flex-start',
357
+ overflow: 'visible',
358
+ }
359
+
360
+ const sipFooterTextWrapStyle: ViewStyle = {
361
+ gap: sipFooterGap,
362
+ alignItems: 'flex-start',
363
+ alignSelf: 'stretch',
364
+ minWidth: 0,
365
+ }
366
+
367
+ const sipDescriptionWrapStyle: ViewStyle = {
368
+ flexDirection: 'row',
369
+ gap: sipFooterGap,
370
+ alignItems: 'flex-end',
371
+ }
372
+
373
+ const sipHeadlineStyle: TextStyle = {
374
+ color: sipHeadlineColor,
375
+ fontFamily: sipHeadlineFontFamily,
376
+ fontSize: sipHeadlineFontSize,
377
+ lineHeight: sipHeadlineLineHeight,
378
+ fontWeight: toWeight(sipHeadlineFontWeightRaw),
379
+ }
380
+
381
+ const sipDescriptionStyle: TextStyle = {
382
+ color: sipDescriptionColor,
383
+ fontFamily: sipDescriptionFontFamily,
384
+ fontSize: sipDescriptionFontSize,
385
+ lineHeight: sipDescriptionLineHeight,
386
+ fontWeight: toWeight(sipDescriptionFontWeightRaw),
387
+ }
388
+
389
+ const sipSubtitleStyle: TextStyle = {
390
+ color: sipSubtitleColor,
391
+ fontFamily: sipSubtitleFontFamily,
392
+ fontSize: sipSubtitleFontSize,
393
+ lineHeight: sipSubtitleLineHeight,
394
+ fontWeight: toWeight(sipSubtitleFontWeightRaw),
395
+ }
396
+
248
397
  const ratingContentStyle: ViewStyle = {
249
398
  paddingHorizontal: ratingContentPaddingH,
250
399
  paddingVertical: ratingContentPaddingV,
@@ -294,6 +443,44 @@ function CardCTA({
294
443
  flexWrap: 'nowrap',
295
444
  }
296
445
 
446
+ if (isSip) {
447
+ return (
448
+ <View style={[containerStyle, style]}>
449
+ <View style={sipWrapStyle}>
450
+ {avatarSlot ? (
451
+ cloneChildrenWithModes(avatarSlot, avatarModes)
452
+ ) : (
453
+ <Avatar style="Image" imageSource={avatarSource} modes={avatarModes} />
454
+ )}
455
+ <View style={textWrapStyle}>
456
+ <Text style={titleStyle}>{title}</Text>
457
+ <Text style={bodyStyle}>{body}</Text>
458
+ </View>
459
+ <View style={sipFooterTextWrapStyle}>
460
+ <Text style={sipHeadlineStyle}>{headline}</Text>
461
+ <View style={sipDescriptionWrapStyle}>
462
+ <Text style={sipDescriptionStyle}>{description}</Text>
463
+ <Text style={sipSubtitleStyle}>{subtitle}</Text>
464
+ </View>
465
+ </View>
466
+ <View style={buttonWrapStyle}>
467
+ {buttonSlot ? (
468
+ cloneChildrenWithModes(buttonSlot, buttonModes)
469
+ ) : (
470
+ <Button
471
+ label={effectiveButtonLabel}
472
+ onPress={onPressButton || (() => {})}
473
+ modes={buttonModes}
474
+ style={buttonStyle}
475
+ renderContent={renderButtonContent(buttonLabelStyle)}
476
+ />
477
+ )}
478
+ </View>
479
+ </View>
480
+ </View>
481
+ )
482
+ }
483
+
297
484
  if (isRating) {
298
485
  return (
299
486
  <View style={[containerStyle, style]}>
@@ -318,14 +505,7 @@ function CardCTA({
318
505
  onPress={onPressButton || (() => {})}
319
506
  modes={buttonModes}
320
507
  style={ratingButtonStyle}
321
- renderContent={(labelStyles) => (
322
- <Text
323
- onTextLayout={handleButtonLabelTextLayout}
324
- style={[labelStyles, ratingButtonLabelStyle]}
325
- >
326
- {nonWrappingButtonLabel}
327
- </Text>
328
- )}
508
+ renderContent={renderButtonContent(ratingButtonLabelStyle)}
329
509
  />
330
510
  )}
331
511
  </View>
@@ -368,14 +548,7 @@ function CardCTA({
368
548
  onPress={onPressButton || (() => {})}
369
549
  modes={buttonModes}
370
550
  style={buttonStyle}
371
- renderContent={(labelStyles) => (
372
- <Text
373
- onTextLayout={handleButtonLabelTextLayout}
374
- style={[labelStyles, buttonLabelStyle]}
375
- >
376
- {nonWrappingButtonLabel}
377
- </Text>
378
- )}
551
+ renderContent={renderButtonContent(buttonLabelStyle)}
379
552
  />
380
553
  )}
381
554
  </View>
@@ -71,7 +71,11 @@ function CardInsight({
71
71
  const background =
72
72
  (getVariableByName('cardInsight/background', modes) as string | null) ?? '#ffffff'
73
73
  const radius =
74
- (getVariableByName('cardInsight/radius', modes) as number | null) ?? 16
74
+ (getVariableByName('cardInsight/radius', modes) as number | null) ?? 12
75
+ const borderWidth =
76
+ (getVariableByName('cardInsight/borderWidth', modes) as number | null) ?? 1
77
+ const borderColor =
78
+ (getVariableByName('cardInsight/border/color', modes) as string | null) ?? '#ffffff'
75
79
  const paddingHorizontal =
76
80
  (getVariableByName('cardInsight/padding/horizontal', modes) as number | null) ?? 12
77
81
  const paddingVertical =
@@ -164,6 +168,8 @@ function CardInsight({
164
168
  width,
165
169
  backgroundColor: background,
166
170
  borderRadius: radius,
171
+ borderWidth,
172
+ borderColor,
167
173
  paddingHorizontal,
168
174
  paddingVertical,
169
175
  gap,
@@ -3,7 +3,7 @@ import { StyleSheet, Text, View, type StyleProp, type TextStyle, type ViewStyle
3
3
  import Svg, { Circle } from 'react-native-svg'
4
4
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
5
5
  import { useTokens } from '../../design-tokens/JFSThemeProvider'
6
- import { EMPTY_MODES } from '../../utils/react-utils'
6
+ import { EMPTY_MODES, resolveTruncation } from '../../utils/react-utils'
7
7
  import { IconMinus } from '../../icons/components/IconMinus'
8
8
  import type { Modes } from '../../design-tokens'
9
9
 
@@ -40,6 +40,12 @@ export type CircularProgressBarProps = CircularProgressBarBaseProps & {
40
40
  supportTextStyle?: StyleProp<TextStyle>
41
41
  /** Accessibility label for the whole progress component. */
42
42
  accessibilityLabel?: string
43
+ /**
44
+ * When `true`, disables text truncation: the trailing ellipsis (`…`) is never
45
+ * shown and any `numberOfLines` clamp is ignored, so the text wraps to as many
46
+ * lines as it needs even when horizontal space is tight.
47
+ */
48
+ disableTruncation?: boolean
43
49
  }
44
50
 
45
51
  const STROKE_WIDTH_RATIO = 8 / 60
@@ -72,8 +78,11 @@ const toFontWeight = (value: unknown, fallback: TextStyle['fontWeight']) => {
72
78
  }
73
79
 
74
80
  const getStrokeColor = (style: StyleProp<ViewStyle>, fallback: string) => {
81
+ // `StyleSheet.flatten(undefined)` returns `undefined`, so guard before
82
+ // reading `backgroundColor` — otherwise omitting the optional style prop
83
+ // crashes with "Cannot read property 'backgroundColor' of undefined".
75
84
  const flattened = StyleSheet.flatten(style)
76
- return typeof flattened.backgroundColor === 'string'
85
+ return typeof flattened?.backgroundColor === 'string'
77
86
  ? flattened.backgroundColor
78
87
  : fallback
79
88
  }
@@ -90,6 +99,7 @@ function CircularProgressBar({
90
99
  valueStyle,
91
100
  supportTextStyle,
92
101
  accessibilityLabel,
102
+ disableTruncation,
93
103
  ...rest
94
104
  }: CircularProgressBarProps) {
95
105
  const { modes: globalModes } = useTokens()
@@ -232,11 +242,11 @@ function CircularProgressBar({
232
242
  {isActive ? (
233
243
  <View style={textStackStyle} pointerEvents="none">
234
244
  {hasSupportText ? (
235
- <Text style={[computedSupportTextStyle, supportTextStyle]} numberOfLines={1}>
245
+ <Text style={[computedSupportTextStyle, supportTextStyle]} {...resolveTruncation(disableTruncation, 1)}>
236
246
  {supportText}
237
247
  </Text>
238
248
  ) : null}
239
- <Text style={[computedValueStyle, valueStyle]} numberOfLines={1}>
249
+ <Text style={[computedValueStyle, valueStyle]} {...resolveTruncation(disableTruncation, 1)}>
240
250
  {displayValue}
241
251
  </Text>
242
252
  </View>
@@ -11,7 +11,7 @@ import {
11
11
  } from 'react-native'
12
12
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
13
13
  import { useTokens } from '../../design-tokens/JFSThemeProvider'
14
- import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
14
+ import { EMPTY_MODES, cloneChildrenWithModes, resolveTruncation } from '../../utils/react-utils'
15
15
  import { IconChevronright } from '../../icons/components/IconChevronright'
16
16
  import type { Modes } from '../../design-tokens'
17
17
 
@@ -58,6 +58,12 @@ export type CircularProgressBarDotedProps = CircularProgressBarDotedBaseProps &
58
58
  tierLabelStyle?: StyleProp<TextStyle>
59
59
  /** Accessibility label for the whole progress component. */
60
60
  accessibilityLabel?: string
61
+ /**
62
+ * When `true`, disables text truncation: the trailing ellipsis (`…`) is never
63
+ * shown and any `numberOfLines` clamp is ignored, so the text wraps to as many
64
+ * lines as it needs even when horizontal space is tight.
65
+ */
66
+ disableTruncation?: boolean
61
67
  }
62
68
 
63
69
  const DEFAULT_DOT_COUNT = 24
@@ -92,7 +98,7 @@ const toFontWeight = (value: unknown, fallback: TextStyle['fontWeight']) => {
92
98
 
93
99
  const getBackgroundColor = (style: StyleProp<ViewStyle>, fallback: string) => {
94
100
  const flattened = StyleSheet.flatten(style)
95
- return typeof flattened.backgroundColor === 'string'
101
+ return flattened && typeof flattened.backgroundColor === 'string'
96
102
  ? flattened.backgroundColor
97
103
  : fallback
98
104
  }
@@ -118,6 +124,7 @@ function CircularProgressBarDoted({
118
124
  scoreLabelStyle,
119
125
  tierLabelStyle,
120
126
  accessibilityLabel,
127
+ disableTruncation,
121
128
  onLayout,
122
129
  ...rest
123
130
  }: CircularProgressBarDotedProps) {
@@ -277,7 +284,7 @@ function CircularProgressBarDoted({
277
284
 
278
285
  const trendContent = (
279
286
  <>
280
- <Text numberOfLines={1} style={[computedTierLabelStyle, tierLabelStyle]}>
287
+ <Text {...resolveTruncation(disableTruncation, 1)} style={[computedTierLabelStyle, tierLabelStyle]}>
281
288
  {tierLabel}
282
289
  </Text>
283
290
  {showChevron ? (
@@ -9,7 +9,7 @@ import {
9
9
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
10
10
  import { useTokens } from '../../design-tokens/JFSThemeProvider'
11
11
  import Icon from '../../icons/Icon'
12
- import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
12
+ import { EMPTY_MODES, cloneChildrenWithModes, resolveTruncation } from '../../utils/react-utils'
13
13
  import CircularProgressBarDoted from '../CircularProgressBarDoted/CircularProgressBarDoted'
14
14
  import Nudge from '../Nudge/Nudge'
15
15
  import type { Modes } from '../../design-tokens'
@@ -57,6 +57,12 @@ export type CircularRatingProps = CircularRatingBaseProps & {
57
57
  nudgeStyle?: StyleProp<ViewStyle>
58
58
  /** Accessibility label for the whole component. */
59
59
  accessibilityLabel?: string
60
+ /**
61
+ * When `true`, disables text truncation: the trailing ellipsis (`…`) is never
62
+ * shown and any `numberOfLines` clamp is ignored, so the text wraps to as many
63
+ * lines as it needs even when horizontal space is tight.
64
+ */
65
+ disableTruncation?: boolean
60
66
  }
61
67
 
62
68
  interface CircularRatingTokens {
@@ -161,6 +167,7 @@ function CircularRating({
161
167
  footerTextStyle,
162
168
  nudgeStyle,
163
169
  accessibilityLabel,
170
+ disableTruncation,
164
171
  ...rest
165
172
  }: CircularRatingProps) {
166
173
  const { modes: globalModes } = useTokens()
@@ -207,7 +214,7 @@ function CircularRating({
207
214
  <View style={[tokens.footerStyle, footerStyle]}>
208
215
  {processedFooterSlot || (
209
216
  <>
210
- <Text numberOfLines={1} style={[tokens.footerTextStyle, footerTextStyle]}>
217
+ <Text {...resolveTruncation(disableTruncation, 1)} style={[tokens.footerTextStyle, footerTextStyle]}>
211
218
  {footerText}
212
219
  </Text>
213
220
  {showFooterIcon ? (
@@ -11,7 +11,7 @@ import {
11
11
  } from 'react-native'
12
12
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
13
13
  import { useTokens } from '../../design-tokens/JFSThemeProvider'
14
- import { EMPTY_MODES } from '../../utils/react-utils'
14
+ import { EMPTY_MODES, resolveTruncation } from '../../utils/react-utils'
15
15
  import type { Modes } from '../../design-tokens'
16
16
 
17
17
  /** Where the value/label text sits relative to the circle. */
@@ -76,6 +76,12 @@ export type ClusterBubbleProps = {
76
76
  modes?: Modes
77
77
  /** Accessibility label. Defaults to a `value + label` composite. */
78
78
  accessibilityLabel?: string
79
+ /**
80
+ * When `true`, disables text truncation: the trailing ellipsis (`…`) is never
81
+ * shown and any `numberOfLines` clamp is ignored, so the text wraps to as many
82
+ * lines as it needs even when horizontal space is tight.
83
+ */
84
+ disableTruncation?: boolean
79
85
  }
80
86
 
81
87
  const DEFAULT_FILL = '#5d00b5'
@@ -149,6 +155,7 @@ function ClusterBubble({
149
155
  style,
150
156
  modes: propModes = EMPTY_MODES,
151
157
  accessibilityLabel,
158
+ disableTruncation,
152
159
  }: ClusterBubbleProps) {
153
160
  const { modes: globalModes } = useTokens()
154
161
  const modes = useMemo(() => ({ ...globalModes, ...propModes }), [globalModes, propModes])
@@ -207,7 +214,7 @@ function ClusterBubble({
207
214
  if (node === undefined || node === null || node === false) return null
208
215
  if (typeof node === 'string' || typeof node === 'number') {
209
216
  return (
210
- <Text style={[baseStyle, override]} numberOfLines={2}>
217
+ <Text style={[baseStyle, override]} {...resolveTruncation(disableTruncation, 2)}>
211
218
  {node}
212
219
  </Text>
213
220
  )