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
@@ -10,8 +10,9 @@ import {
10
10
  type TextStyle,
11
11
  } from 'react-native'
12
12
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
13
- import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
13
+ import { EMPTY_MODES, cloneChildrenWithModes, resolveTruncation } from '../../utils/react-utils'
14
14
  import Accordion from '../Accordion/Accordion'
15
+ import Table from '../Table/Table'
15
16
  import Image from '../Image/Image'
16
17
  import IconButton from '../IconButton/IconButton'
17
18
  import Icon from '../../icons/Icon'
@@ -133,6 +134,12 @@ export type CompareTableProps = {
133
134
  modes?: Modes;
134
135
  /** Override the outer container style. */
135
136
  style?: StyleProp<ViewStyle>;
137
+ /**
138
+ * When `true`, disables text truncation: the trailing ellipsis (`…`) is never
139
+ * shown and any `numberOfLines` clamp is ignored, so the text wraps to as many
140
+ * lines as it needs even when horizontal space is tight.
141
+ */
142
+ disableTruncation?: boolean;
136
143
  };
137
144
 
138
145
  const DEFAULT_COLUMNS: CompareTableColumn[] = [
@@ -173,6 +180,7 @@ function CompareTable({
173
180
  maxColumns = 4,
174
181
  modes = EMPTY_MODES,
175
182
  style,
183
+ disableTruncation,
176
184
  }: CompareTableProps) {
177
185
  // --- selection card tokens ------------------------------------------------
178
186
  const cardBg = (getVariableByName('selectionCard/background/color', modes) as string) ?? '#ffffff'
@@ -186,6 +194,11 @@ function CompareTable({
186
194
  const cardLabelFontFamily = (getVariableByName('selectionCard/label/fontfamily', modes) as string) ?? 'JioType Var'
187
195
  const cardLabelFontWeight = (getVariableByName('selectionCard/label/fontweight', modes) as number | string) ?? 700
188
196
  const imageRadius = (getVariableByName('image/radius', modes) as number) ?? 8
197
+ // `selectionCard/radius` (Figma node 5568:3487) rounds the card corners.
198
+ // The published token store is still out of sync (the variable resolves to
199
+ // `0` in the design), so the read falls back to `0` and will pick up a
200
+ // rounded value automatically once the store syncs.
201
+ const cardRadius = (getVariableByName('selectionCard/radius', modes) as number) ?? 0
189
202
 
190
203
  // --- add-a-card icon button tokens ---------------------------------------
191
204
  // padding/size/border come from the shared iconButton tokens; the filled
@@ -195,6 +208,9 @@ function CompareTable({
195
208
  const addBorderSize = (getVariableByName('iconButton/border/size', modes) as number) ?? 1
196
209
  const addPadding = (getVariableByName('iconButton/padding', modes) as number) ?? 11
197
210
  const addIconSize = (getVariableByName('iconButton/icon/size', modes) as number) ?? 18
211
+ // `iconButton/radius` is the design-token sentinel `9999` ("perfect
212
+ // circle"), mirroring IconButton's own resolution.
213
+ const addRadius = (getVariableByName('iconButton/radius', modes) as number) ?? 9999
198
214
  const addBg = ADD_BUTTON_BG
199
215
  const addIconColor = ADD_BUTTON_ICON_COLOR
200
216
 
@@ -220,6 +236,8 @@ function CompareTable({
220
236
 
221
237
  const columnCount = columns.length
222
238
  const showAddCard = onAddColumn != null && columnCount < maxColumns
239
+ /** Grid columns shared by the selection-card row and every table row. */
240
+ const gridColumnCount = columnCount + (showAddCard ? 1 : 0)
223
241
 
224
242
  const cardLabelStyle: TextStyle = {
225
243
  ...NO_WRAP_TEXT,
@@ -259,6 +277,7 @@ function CompareTable({
259
277
  minWidth: 0,
260
278
  minHeight: 147,
261
279
  backgroundColor: cardBg,
280
+ borderRadius: cardRadius,
262
281
  alignItems: 'center',
263
282
  justifyContent: 'center',
264
283
  gap: cardGap,
@@ -292,7 +311,7 @@ function CompareTable({
292
311
  />
293
312
  )}
294
313
  {column.label != null && (
295
- <Text style={cardLabelStyle} numberOfLines={2}>
314
+ <Text style={cardLabelStyle} {...resolveTruncation(disableTruncation, 2)}>
296
315
  {column.label}
297
316
  </Text>
298
317
  )}
@@ -334,7 +353,7 @@ function CompareTable({
334
353
  style={{
335
354
  width: addPadding * 2 + addIconSize,
336
355
  height: addPadding * 2 + addIconSize,
337
- borderRadius: 9999,
356
+ borderRadius: addRadius,
338
357
  borderWidth: addBorderSize,
339
358
  borderColor: addBorderColor,
340
359
  backgroundColor: addBg,
@@ -350,7 +369,7 @@ function CompareTable({
350
369
  importantForAccessibility="no"
351
370
  />
352
371
  </View>
353
- <Text style={cardLabelStyle} numberOfLines={1}>
372
+ <Text style={cardLabelStyle} {...resolveTruncation(disableTruncation, 1)}>
354
373
  {addColumnLabel}
355
374
  </Text>
356
375
  </Pressable>
@@ -380,61 +399,91 @@ function CompareTable({
380
399
  return cloneChildrenWithModes(value, modes)
381
400
  }
382
401
 
402
+ // Renders a section's comparison grid using the shared `Table` component.
403
+ // CompareTable keeps ownership of token resolution and passes the resolved
404
+ // values as style overrides so the appearance is byte-identical to the
405
+ // previous hand-rolled grid (full outer border + suppressed last-edge
406
+ // dividers), while reusing Table's structure and `modes` cascade.
407
+ //
408
+ // When the "Add a card" tile is visible, each table row appends an empty
409
+ // trailing cell with the same flex weight so data columns stay 1:1 beneath
410
+ // their selection card.
411
+ const renderTableCell = (
412
+ content: React.ReactNode,
413
+ colIndex: number,
414
+ isLastRow: boolean,
415
+ key: React.Key,
416
+ ) => {
417
+ const isLastCol = colIndex === gridColumnCount - 1
418
+ return (
419
+ <Table.Cell
420
+ key={key}
421
+ modes={modes}
422
+ flex={1}
423
+ align="left"
424
+ isLastColumn={isLastCol}
425
+ isLastRow={isLastRow}
426
+ style={{
427
+ backgroundColor: cellBg,
428
+ borderColor: cellBorderColor,
429
+ borderRightWidth: isLastCol ? 0 : cellBorderSize,
430
+ borderBottomWidth: isLastRow ? 0 : cellBorderSize,
431
+ paddingHorizontal: cellPaddingH,
432
+ paddingVertical: cellPaddingV,
433
+ }}
434
+ >
435
+ {content}
436
+ </Table.Cell>
437
+ )
438
+ }
439
+
383
440
  const renderTable = (section: CompareTableSection) => (
384
- <View
441
+ <Table
442
+ modes={modes}
385
443
  style={{
386
444
  width: '100%',
387
445
  borderWidth: cellBorderSize,
388
446
  borderColor: cellBorderColor,
389
- overflow: 'hidden',
390
447
  }}
391
448
  >
392
449
  {section.header != null && (
393
- <View
394
- style={{
395
- backgroundColor: headerBg,
396
- paddingHorizontal: headerPaddingH,
397
- paddingVertical: headerPaddingV,
398
- borderBottomWidth: cellBorderSize,
399
- borderBottomColor: cellBorderColor,
400
- }}
450
+ <Table.Header
451
+ modes={modes}
452
+ style={{ borderBottomWidth: cellBorderSize, borderBottomColor: cellBorderColor }}
401
453
  >
402
- <Text style={headerTextStyle}>{section.header}</Text>
403
- </View>
404
- )}
405
- {section.rows.map((row, rowIndex) => {
406
- const isLastRow = rowIndex === section.rows.length - 1
407
- return (
408
- <View
409
- key={row.key ?? rowIndex}
410
- style={{ flexDirection: 'row', width: '100%' }}
454
+ <Table.HeaderCell
455
+ modes={modes}
456
+ flex={1}
457
+ style={{
458
+ backgroundColor: headerBg,
459
+ paddingHorizontal: headerPaddingH,
460
+ paddingVertical: headerPaddingV,
461
+ }}
411
462
  >
412
- {Array.from({ length: columnCount }).map((_, colIndex) => {
413
- const isLastCol = colIndex === columnCount - 1
414
- return (
415
- <View
416
- key={colIndex}
417
- style={{
418
- flex: 1,
419
- minWidth: 0,
420
- backgroundColor: cellBg,
421
- justifyContent: 'center',
422
- paddingHorizontal: cellPaddingH,
423
- paddingVertical: cellPaddingV,
424
- borderRightWidth: isLastCol ? 0 : cellBorderSize,
425
- borderRightColor: cellBorderColor,
426
- borderBottomWidth: isLastRow ? 0 : cellBorderSize,
427
- borderBottomColor: cellBorderColor,
428
- }}
429
- >
430
- {renderCellContent(row.values?.[colIndex], `${rowIndex}-${colIndex}`)}
431
- </View>
432
- )
433
- })}
434
- </View>
435
- )
436
- })}
437
- </View>
463
+ <Text style={headerTextStyle}>{section.header}</Text>
464
+ </Table.HeaderCell>
465
+ </Table.Header>
466
+ )}
467
+ <Table.Body modes={modes}>
468
+ {section.rows.map((row, rowIndex) => {
469
+ const isLastRow = rowIndex === section.rows.length - 1
470
+ return (
471
+ <Table.Row key={row.key ?? rowIndex} modes={modes}>
472
+ {Array.from({ length: columnCount }).map((_, colIndex) =>
473
+ renderTableCell(
474
+ renderCellContent(row.values?.[colIndex], `${rowIndex}-${colIndex}`),
475
+ colIndex,
476
+ isLastRow,
477
+ colIndex,
478
+ ),
479
+ )}
480
+ {showAddCard &&
481
+ renderTableCell(null, gridColumnCount - 1, isLastRow, '__add_spacer__')}
482
+ </Table.Row>
483
+ )
484
+ })}
485
+ </Table.Body>
486
+ </Table>
438
487
  )
439
488
 
440
489
  return (
@@ -464,7 +513,6 @@ function CompareTable({
464
513
  title={section.title}
465
514
  defaultExpanded={section.defaultExpanded ?? index === 0}
466
515
  modes={modes}
467
- style={{ paddingHorizontal: headerPaddingH }}
468
516
  >
469
517
  {renderTable(section)}
470
518
  </Accordion>
@@ -10,7 +10,7 @@ import {
10
10
  import Svg, { Rect } from 'react-native-svg'
11
11
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
12
12
  import { useTokens } from '../../design-tokens/JFSThemeProvider'
13
- import { EMPTY_MODES } from '../../utils/react-utils'
13
+ import { EMPTY_MODES, resolveTruncation } from '../../utils/react-utils'
14
14
  import MetricLegendItem from '../MetricLegendItem/MetricLegendItem'
15
15
  import type { Modes } from '../../design-tokens'
16
16
 
@@ -97,6 +97,12 @@ export type CoverageBarComparisonProps = {
97
97
  labelStyle?: StyleProp<TextStyle>
98
98
  /** Accessibility label for the entire component. */
99
99
  accessibilityLabel?: string
100
+ /**
101
+ * When `true`, disables text truncation: the trailing ellipsis (`…`) is never
102
+ * shown and any `numberOfLines` clamp is ignored, so the text wraps to as many
103
+ * lines as it needs even when horizontal space is tight.
104
+ */
105
+ disableTruncation?: boolean
100
106
  }
101
107
 
102
108
  /**
@@ -221,6 +227,7 @@ function CoverageBarComparison({
221
227
  legendStyle,
222
228
  labelStyle,
223
229
  accessibilityLabel,
230
+ disableTruncation,
224
231
  }: CoverageBarComparisonProps) {
225
232
  const { modes: globalModes } = useTokens()
226
233
  const modes = React.useMemo(
@@ -331,7 +338,7 @@ function CoverageBarComparison({
331
338
  >
332
339
  {hasLabel ? (
333
340
  <Text
334
- numberOfLines={1}
341
+ {...resolveTruncation(disableTruncation, 1)}
335
342
  style={[computedLabelStyle, labelStyle]}
336
343
  >
337
344
  {original.label}
@@ -10,7 +10,7 @@ import {
10
10
  import Svg, { Circle } from 'react-native-svg'
11
11
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
12
12
  import { useTokens } from '../../design-tokens/JFSThemeProvider'
13
- import { EMPTY_MODES, flattenChildren } from '../../utils/react-utils'
13
+ import { EMPTY_MODES, flattenChildren, resolveTruncation } from '../../utils/react-utils'
14
14
  import type { Modes } from '../../design-tokens'
15
15
 
16
16
  /**
@@ -83,6 +83,12 @@ export type DonutChartProps = {
83
83
  labelStyle?: StyleProp<TextStyle>
84
84
  /** Accessibility label announced for the whole donut chart. */
85
85
  accessibilityLabel?: string
86
+ /**
87
+ * When `true`, disables text truncation: the trailing ellipsis (`…`) is never
88
+ * shown and any `numberOfLines` clamp is ignored, so the text wraps to as many
89
+ * lines as it needs even when horizontal space is tight.
90
+ */
91
+ disableTruncation?: boolean
86
92
  }
87
93
 
88
94
  const DEFAULT_APPEARANCE_CYCLE = [
@@ -222,6 +228,7 @@ function DonutChart({
222
228
  valueStyle,
223
229
  labelStyle,
224
230
  accessibilityLabel,
231
+ disableTruncation,
225
232
  }: DonutChartProps) {
226
233
  const { modes: globalModes } = useTokens()
227
234
  const modes = { ...globalModes, ...propModes }
@@ -388,7 +395,7 @@ function DonutChart({
388
395
  },
389
396
  valueStyle,
390
397
  ]}
391
- numberOfLines={1}
398
+ {...resolveTruncation(disableTruncation, 1)}
392
399
  >
393
400
  {value}
394
401
  </Text>
@@ -406,7 +413,7 @@ function DonutChart({
406
413
  },
407
414
  labelStyle,
408
415
  ]}
409
- numberOfLines={1}
416
+ {...resolveTruncation(disableTruncation, 1)}
410
417
  >
411
418
  {label}
412
419
  </Text>
@@ -13,7 +13,7 @@ import {
13
13
  } from 'react-native'
14
14
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
15
15
  import { useTokens } from '../../design-tokens/JFSThemeProvider'
16
- import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
16
+ import { EMPTY_MODES, cloneChildrenWithModes, resolveTruncation } from '../../utils/react-utils'
17
17
  import Icon from '../../icons/Icon'
18
18
  import type { Modes } from '../../design-tokens'
19
19
 
@@ -57,6 +57,12 @@ export type DropdownItemProps = {
57
57
  accessibilityLabel?: string
58
58
  /** Accessibility hint. */
59
59
  accessibilityHint?: 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
  function useDropdownItemTokens(modes: Modes) {
@@ -128,6 +134,7 @@ export function DropdownItem({
128
134
  labelStyle,
129
135
  accessibilityLabel,
130
136
  accessibilityHint,
137
+ disableTruncation,
131
138
  }: DropdownItemProps) {
132
139
  const { modes: globalModes } = useTokens()
133
140
  const modes = useMemo(
@@ -226,7 +233,7 @@ export function DropdownItem({
226
233
  {children != null ? (
227
234
  children
228
235
  ) : (
229
- <Text style={[textStyle, labelStyle]} numberOfLines={1}>
236
+ <Text style={[textStyle, labelStyle]} {...resolveTruncation(disableTruncation, 1)}>
230
237
  {label}
231
238
  </Text>
232
239
  )}
@@ -24,7 +24,7 @@ import {
24
24
  import { useSafeAreaInsets } from 'react-native-safe-area-context'
25
25
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
26
26
  import { useTokens } from '../../design-tokens/JFSThemeProvider'
27
- import { EMPTY_MODES, flattenChildren } from '../../utils/react-utils'
27
+ import { EMPTY_MODES, flattenChildren, resolveTruncation } from '../../utils/react-utils'
28
28
  import Icon from '../../icons/Icon'
29
29
  import SupportText from '../SupportText/SupportText'
30
30
  import type { SupportTextStatus } from '../SupportText/SupportTextIcon'
@@ -171,6 +171,12 @@ export type DropdownInputProps = {
171
171
  onFocus?: (e: any) => void
172
172
  /** Called when the trigger loses focus (web only). */
173
173
  onBlur?: (e: any) => void
174
+ /**
175
+ * When `true`, disables text truncation: the trailing ellipsis (`…`) is never
176
+ * shown and any `numberOfLines` clamp is ignored, so the text wraps to as many
177
+ * lines as it needs even when horizontal space is tight.
178
+ */
179
+ disableTruncation?: boolean
174
180
  }
175
181
 
176
182
  // ---------------------------------------------------------------------------
@@ -362,6 +368,7 @@ const DropdownInput = forwardRef<DropdownInputHandle, DropdownInputProps>(functi
362
368
  accessibilityHint,
363
369
  onFocus,
364
370
  onBlur,
371
+ disableTruncation,
365
372
  }: DropdownInputProps, ref: React.Ref<DropdownInputHandle>) {
366
373
  // ---------------- Modes ----------------
367
374
  const { modes: globalModes } = useTokens()
@@ -829,7 +836,7 @@ const DropdownInput = forwardRef<DropdownInputHandle, DropdownInputProps>(functi
829
836
  valueTextStyle,
830
837
  !hasValue && { color: placeholderColor },
831
838
  ]}
832
- numberOfLines={1}
839
+ {...resolveTruncation(disableTruncation, 1)}
833
840
  >
834
841
  {displayLabel}
835
842
  </Text>
@@ -35,6 +35,12 @@ export type ExpandableCheckboxProps = {
35
35
  readLessLabel?: string
36
36
  /** Number of lines to show when collapsed. Defaults to `1`. */
37
37
  collapsedLines?: number
38
+ /**
39
+ * When `true`, disables truncation of the collapsed label: the trailing
40
+ * ellipsis (`…`) is never shown and the `collapsedLines` clamp is ignored, so
41
+ * the label wraps fully even when horizontal space is tight.
42
+ */
43
+ disableTruncation?: boolean
38
44
  /** Design token modes for theming (e.g. `{ 'Color Mode': 'Light' }`). */
39
45
  modes?: Modes
40
46
  /** Override outer container styles. */
@@ -96,6 +102,7 @@ function ExpandableCheckbox({
96
102
  readMoreLabel = 'Read more',
97
103
  readLessLabel = 'Read less',
98
104
  collapsedLines = 1,
105
+ disableTruncation = false,
99
106
  modes = EMPTY_MODES,
100
107
  style,
101
108
  labelStyle,
@@ -205,7 +212,7 @@ function ExpandableCheckbox({
205
212
  const buttonLabel = isExpanded ? readLessLabel : readMoreLabel
206
213
 
207
214
  const labelNumberOfLinesProps =
208
- !isExpanded && collapsedLines > 0
215
+ !disableTruncation && !isExpanded && collapsedLines > 0
209
216
  ? { numberOfLines: collapsedLines, ellipsizeMode: 'tail' as const }
210
217
  : null
211
218
 
@@ -1,5 +1,6 @@
1
- import React, { forwardRef, useCallback, useMemo, useState } from 'react'
1
+ import React, { forwardRef, useCallback, useMemo, useRef, useState } from 'react'
2
2
  import {
3
+ Platform,
3
4
  View,
4
5
  Text,
5
6
  TextInput as RNTextInput,
@@ -10,7 +11,8 @@ import {
10
11
  } from 'react-native'
11
12
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
12
13
  import { useTokens } from '../../design-tokens/JFSThemeProvider'
13
- import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
14
+ import { EMPTY_MODES, cloneChildrenWithModes, mergeRefs } from '../../utils/react-utils'
15
+ import { useWebOutsidePress } from '../../utils/web-platform-utils'
14
16
  import SupportText from '../SupportText/SupportText'
15
17
  import type { SupportTextStatus } from '../SupportText/SupportTextIcon'
16
18
  import Icon from '../../icons/Icon'
@@ -347,6 +349,25 @@ const FormField = forwardRef<RNTextInput, FormFieldProps>(function FormField({
347
349
  const [isFocused, setIsFocused] = useState(false)
348
350
  const interactive = !isDisabled && !isReadOnly
349
351
 
352
+ // Internal handles so we can (a) imperatively blur the input and (b) detect
353
+ // outside presses. The forwarded `ref` is merged in below, so the public
354
+ // ref API (focus/blur/clear/isFocused) is unchanged for consumers.
355
+ const inputRef = useRef<RNTextInput>(null)
356
+ const rootRef = useRef<View>(null)
357
+ // Stable merged ref so React doesn't detach/reattach (and momentarily null
358
+ // `inputRef.current`) on every render.
359
+ const mergedInputRef = useMemo(() => mergeRefs(inputRef, ref), [ref])
360
+
361
+ // Web: pressing an INTERACTIVE element outside the field (Button, link,
362
+ // another pressable) often does not move DOM focus, so the native `blur`
363
+ // never fires and the field stays visually "Active". Force a blur on any
364
+ // outside pointer press while focused. No-op on native.
365
+ useWebOutsidePress(
366
+ rootRef,
367
+ useCallback(() => inputRef.current?.blur(), []),
368
+ isFocused,
369
+ )
370
+
350
371
  // FormField States cascade — error > read only/disabled > active (focused) > idle.
351
372
  // Disabled maps to "Read Only" since there is no dedicated disabled mode and
352
373
  // the visual treatment is closest. This is only the DEFAULT — an explicit
@@ -478,7 +499,30 @@ const FormField = forwardRef<RNTextInput, FormFieldProps>(function FormField({
478
499
  color: tokens.inputTextColor,
479
500
  fontFamily: tokens.inputFontFamily,
480
501
  fontSize: tokens.inputFontSize,
481
- lineHeight: tokens.inputLineHeight,
502
+ // ⚠️ DO NOT change to an unconditional `lineHeight: tokens.inputLineHeight`.
503
+ // This has regressed twice — read this before "simplifying" it.
504
+ //
505
+ // `inputLineHeight` comes from the Figma token `formField/input/label`
506
+ // ("16/45"), which is a 16px font inside a 45px ROW, i.e. it is the row
507
+ // HEIGHT, not the text leading. The row height is already applied via
508
+ // `inputRowStyle.minHeight` + `alignItems: 'center'`, so the text never
509
+ // needs this value as its own `lineHeight`.
510
+ //
511
+ // Passing a `lineHeight` (45) that is much larger than `fontSize` (16)
512
+ // onto a single-line RNTextInput breaks ONLY iOS: iOS puts ALL the extra
513
+ // vertical space ABOVE the glyphs, so the text/placeholder sinks to the
514
+ // bottom of the box instead of centering. Android and Web distribute the
515
+ // space evenly and look correct. This is a long-standing iOS native
516
+ // font-metrics bug in UITextField — see facebook/react-native#39145 and
517
+ // #28012; the documented workaround is exactly "drop lineHeight on the
518
+ // single-line input and let a wrapper center it". A single-line UITextField
519
+ // with no lineHeight centers its text natively (see react-native#56774).
520
+ //
521
+ // So: omit lineHeight on iOS (native centering via the row), and keep the
522
+ // token value on Android/Web where it already renders correctly.
523
+ ...(Platform.OS === 'ios'
524
+ ? null
525
+ : { lineHeight: tokens.inputLineHeight }),
482
526
  fontWeight: tokens.inputFontWeight,
483
527
  padding: 0,
484
528
  margin: 0,
@@ -530,6 +574,7 @@ const FormField = forwardRef<RNTextInput, FormFieldProps>(function FormField({
530
574
 
531
575
  return (
532
576
  <View
577
+ ref={rootRef}
533
578
  style={[wrapperStyle, style]}
534
579
  pointerEvents={isDisabled ? 'none' : 'auto'}
535
580
  testID={testID}
@@ -552,7 +597,7 @@ const FormField = forwardRef<RNTextInput, FormFieldProps>(function FormField({
552
597
  </View>
553
598
  )}
554
599
  <RNTextInput
555
- ref={ref}
600
+ ref={mergedInputRef}
556
601
  style={[inputTextStyles, inputTextStyle]}
557
602
  value={value ?? ''}
558
603
  onChangeText={handleChangeText}
@@ -91,7 +91,7 @@ const toFontWeight = (value: unknown, fallback: TextStyle['fontWeight']) => {
91
91
 
92
92
  const getStrokeColor = (style: StyleProp<ViewStyle>, fallback: string) => {
93
93
  const flattened = StyleSheet.flatten(style)
94
- return typeof flattened.backgroundColor === 'string'
94
+ return flattened && typeof flattened.backgroundColor === 'string'
95
95
  ? flattened.backgroundColor
96
96
  : fallback
97
97
  }
@@ -10,7 +10,7 @@ import {
10
10
  } from 'react-native'
11
11
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
12
12
  import Radio from '../Radio/Radio'
13
- import { EMPTY_MODES } from '../../utils/react-utils'
13
+ import { EMPTY_MODES, resolveTruncation } from '../../utils/react-utils'
14
14
  import type { Modes } from '../../design-tokens'
15
15
 
16
16
  export interface HoldingsCardDetailItem {
@@ -39,6 +39,12 @@ export interface HoldingsCardProps {
39
39
  style?: StyleProp<ViewStyle>
40
40
  /** Test ID for testing. */
41
41
  testID?: string
42
+ /**
43
+ * When `true`, disables text truncation: the trailing ellipsis (`…`) is never
44
+ * shown and any `numberOfLines` clamp is ignored, so the text wraps to as many
45
+ * lines as it needs even when horizontal space is tight.
46
+ */
47
+ disableTruncation?: boolean
42
48
  }
43
49
 
44
50
  const GRID_COLUMNS = 3
@@ -62,6 +68,7 @@ export default function HoldingsCard({
62
68
  modes = EMPTY_MODES,
63
69
  style,
64
70
  testID,
71
+ disableTruncation,
65
72
  }: HoldingsCardProps) {
66
73
  // ---- Container tokens ----
67
74
  const containerFill = getVariableByName('holdingsCard/container/fill', modes) || '#fef4e5'
@@ -268,7 +275,7 @@ export default function HoldingsCard({
268
275
  lineHeight: detailValueLineHeight,
269
276
  fontWeight: detailValueFontWeight as any,
270
277
  }}
271
- numberOfLines={1}
278
+ {...resolveTruncation(disableTruncation, 1)}
272
279
  >
273
280
  {item.value}
274
281
  </Text>
@@ -8,7 +8,7 @@ import {
8
8
  } from 'react-native'
9
9
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
10
10
  import { useTokens } from '../../design-tokens/JFSThemeProvider'
11
- import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
11
+ import { EMPTY_MODES, cloneChildrenWithModes, resolveTruncation } from '../../utils/react-utils'
12
12
  import MediaSource, { type UnifiedSource } from '../../utils/MediaSource'
13
13
  import type { Modes } from '../../design-tokens'
14
14
 
@@ -43,6 +43,12 @@ export type InstitutionBadgeProps = InstitutionBadgeBaseProps & {
43
43
  labelStyle?: StyleProp<TextStyle>
44
44
  /** Accessibility label. Defaults to `label`. */
45
45
  accessibilityLabel?: string
46
+ /**
47
+ * When `true`, disables text truncation: the trailing ellipsis (`…`) is never
48
+ * shown and any `numberOfLines` clamp is ignored, so the text wraps to as many
49
+ * lines as it needs even when horizontal space is tight.
50
+ */
51
+ disableTruncation?: boolean
46
52
  }
47
53
 
48
54
  interface InstitutionBadgeTokens {
@@ -155,6 +161,7 @@ function InstitutionBadge({
155
161
  style,
156
162
  labelStyle,
157
163
  accessibilityLabel,
164
+ disableTruncation,
158
165
  ...rest
159
166
  }: InstitutionBadgeProps) {
160
167
  const { modes: globalModes } = useTokens()
@@ -207,7 +214,7 @@ function InstitutionBadge({
207
214
  />
208
215
  </View>
209
216
  )}
210
- <Text style={[tokens.labelStyle, labelStyle]} numberOfLines={1}>
217
+ <Text style={[tokens.labelStyle, labelStyle]} {...resolveTruncation(disableTruncation, 1)}>
211
218
  {label}
212
219
  </Text>
213
220
  </View>
@@ -6,7 +6,7 @@ import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
6
6
  * Helper function to recursively clone children and pass modes prop to components that accept it.
7
7
  * This ensures that all child components in slots receive the modes prop from the parent.
8
8
  */
9
- import { EMPTY_MODES, cloneChildrenWithModes, flattenChildren } from '../../utils/react-utils'
9
+ import { EMPTY_MODES, cloneChildrenWithModes, flattenChildren, resolveTruncation } from '../../utils/react-utils'
10
10
  import type { Modes } from '../../design-tokens'
11
11
 
12
12
  type ListGroupProps = {
@@ -17,6 +17,12 @@ type ListGroupProps = {
17
17
  style?: StyleProp<ViewStyle>;
18
18
  accessibilityLabel?: string;
19
19
  accessibilityHint?: string;
20
+ /**
21
+ * When `true`, disables text truncation: the trailing ellipsis (`…`) is never
22
+ * shown and any `numberOfLines` clamp is ignored, so the text wraps to as many
23
+ * lines as it needs even when horizontal space is tight.
24
+ */
25
+ disableTruncation?: boolean;
20
26
  } & Omit<React.ComponentProps<typeof View>, 'children'>;
21
27
 
22
28
  /**
@@ -52,6 +58,7 @@ function ListGroup({
52
58
  style,
53
59
  accessibilityLabel,
54
60
  accessibilityHint,
61
+ disableTruncation,
55
62
  ...rest
56
63
  }: ListGroupProps) {
57
64
  // Resolve container tokens
@@ -113,7 +120,7 @@ function ListGroup({
113
120
  {label ? (
114
121
  <Text
115
122
  style={[labelStyle, processedSlot && { marginBottom: gap }]}
116
- numberOfLines={1}
123
+ {...resolveTruncation(disableTruncation, 1)}
117
124
  accessibilityElementsHidden={true}
118
125
  importantForAccessibility="no"
119
126
  >