jfs-components 0.1.2 → 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 (287) hide show
  1. package/CHANGELOG.md +45 -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/AmountInput/AmountInput.js +8 -5
  7. package/lib/commonjs/components/AreaLineChart/AreaLineChart.js +11 -7
  8. package/lib/commonjs/components/Badge/Badge.js +47 -8
  9. package/lib/commonjs/components/BenefitCard/BenefitCard.js +231 -0
  10. package/lib/commonjs/components/BrandChip/BrandChip.js +3 -3
  11. package/lib/commonjs/components/Button/Button.js +2 -1
  12. package/lib/commonjs/components/CardAdvisory/CardAdvisory.js +2 -1
  13. package/lib/commonjs/components/CardBankAccount/CardBankAccount.js +3 -2
  14. package/lib/commonjs/components/CardCTA/CardCTA.js +197 -27
  15. package/lib/commonjs/components/CardInsight/CardInsight.js +5 -1
  16. package/lib/commonjs/components/CcCard/CcCard.js +470 -0
  17. package/lib/commonjs/components/Checkbox/Checkbox.js +4 -3
  18. package/lib/commonjs/components/CheckboxItem/CheckboxItem.js +4 -3
  19. package/lib/commonjs/components/CircularProgressBar/CircularProgressBar.js +7 -3
  20. package/lib/commonjs/components/CircularProgressBarDoted/CircularProgressBarDoted.js +3 -2
  21. package/lib/commonjs/components/CircularRating/CircularRating.js +2 -1
  22. package/lib/commonjs/components/ClusterBubble/ClusterBubble.js +3 -2
  23. package/lib/commonjs/components/CompareTable/CompareTable.js +401 -0
  24. package/lib/commonjs/components/ComparisonBar/ComparisonBar.js +266 -0
  25. package/lib/commonjs/components/CoverageBarComparison/CoverageBarComparison.js +3 -2
  26. package/lib/commonjs/components/DonutChart/DonutChart.js +4 -3
  27. package/lib/commonjs/components/Dropdown/Dropdown.js +3 -2
  28. package/lib/commonjs/components/DropdownInput/DropdownInput.js +38 -5
  29. package/lib/commonjs/components/ExpandableCheckbox/ExpandableCheckbox.js +2 -1
  30. package/lib/commonjs/components/FormField/FormField.js +45 -4
  31. package/lib/commonjs/components/Gauge/Gauge.js +1 -1
  32. package/lib/commonjs/components/HoldingsCard/HoldingsCard.js +3 -2
  33. package/lib/commonjs/components/InputSearch/InputSearch.js +6 -4
  34. package/lib/commonjs/components/InstitutionBadge/InstitutionBadge.js +2 -1
  35. package/lib/commonjs/components/ListGroup/ListGroup.js +2 -1
  36. package/lib/commonjs/components/ListItem/ListItem.js +3 -2
  37. package/lib/commonjs/components/MediaCard/MediaCard.js +4 -2
  38. package/lib/commonjs/components/MetricLegendItem/MetricLegendItem.js +3 -2
  39. package/lib/commonjs/components/MonthlyStatusGrid/MonthlyStatusGrid.js +6 -3
  40. package/lib/commonjs/components/NoteInput/NoteInput.js +6 -5
  41. package/lib/commonjs/components/PdpCcCard/PdpCcCard.js +273 -0
  42. package/lib/commonjs/components/PoweredByLabel/PoweredByLabel.js +3 -2
  43. package/lib/commonjs/components/ProductMerchandisingCard/ProductMerchandisingCard.js +427 -0
  44. package/lib/commonjs/components/ProductOverview/ProductOverview.js +4 -3
  45. package/lib/commonjs/components/ProgressBadge/ProgressBadge.js +2 -2
  46. package/lib/commonjs/components/ProjectionMarker/ProjectionMarker.js +162 -0
  47. package/lib/commonjs/components/Radio/Radio.js +5 -5
  48. package/lib/commonjs/components/Section/Section.js +3 -2
  49. package/lib/commonjs/components/Slider/Slider.js +474 -0
  50. package/lib/commonjs/components/Stack/Stack.js +86 -0
  51. package/lib/commonjs/components/SuggestiveSearch/SuggestiveSearch.js +10 -1
  52. package/lib/commonjs/components/SummaryTile/SummaryTile.js +3 -2
  53. package/lib/commonjs/components/Table/Table.js +324 -0
  54. package/lib/commonjs/components/Text/Text.js +2 -1
  55. package/lib/commonjs/components/TextInput/TextInput.js +22 -9
  56. package/lib/commonjs/components/TextSegment/TextSegment.js +119 -0
  57. package/lib/commonjs/components/ThreadHero/ThreadHero.js +4 -5
  58. package/lib/commonjs/components/Title/Title.js +2 -1
  59. package/lib/commonjs/components/Toast/Toast.js +3 -2
  60. package/lib/commonjs/components/TransactionBubble/TransactionBubble.js +2 -1
  61. package/lib/commonjs/components/TransactionStatus/TransactionStatus.js +3 -2
  62. package/lib/commonjs/components/UpiHandle/UpiHandle.js +2 -2
  63. package/lib/commonjs/components/index.js +107 -0
  64. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
  65. package/lib/commonjs/design-tokens/figma-modes.generated.js +39 -9
  66. package/lib/commonjs/icons/registry.js +1 -1
  67. package/lib/commonjs/utils/GlassFill/GlassFill.js +271 -0
  68. package/lib/commonjs/utils/GlassFill/GlassFill.web.js +120 -0
  69. package/lib/commonjs/utils/react-utils.js +78 -0
  70. package/lib/commonjs/utils/web-platform-utils.js +59 -0
  71. package/lib/module/components/Accordion/Accordion.js +3 -2
  72. package/lib/module/components/AccordionCheckbox/AccordionCheckbox.js +5 -4
  73. package/lib/module/components/AccountCard/AccountCard.js +5 -4
  74. package/lib/module/components/AllocationComparisonChart/AllocationComparisonChart.js +10 -7
  75. package/lib/module/components/AmountInput/AmountInput.js +6 -4
  76. package/lib/module/components/AreaLineChart/AreaLineChart.js +12 -8
  77. package/lib/module/components/Badge/Badge.js +49 -10
  78. package/lib/module/components/BenefitCard/BenefitCard.js +225 -0
  79. package/lib/module/components/BrandChip/BrandChip.js +4 -4
  80. package/lib/module/components/Button/Button.js +3 -2
  81. package/lib/module/components/CardAdvisory/CardAdvisory.js +3 -2
  82. package/lib/module/components/CardBankAccount/CardBankAccount.js +4 -3
  83. package/lib/module/components/CardCTA/CardCTA.js +198 -28
  84. package/lib/module/components/CardInsight/CardInsight.js +5 -1
  85. package/lib/module/components/CcCard/CcCard.js +464 -0
  86. package/lib/module/components/Checkbox/Checkbox.js +5 -4
  87. package/lib/module/components/CheckboxItem/CheckboxItem.js +5 -4
  88. package/lib/module/components/CircularProgressBar/CircularProgressBar.js +8 -4
  89. package/lib/module/components/CircularProgressBarDoted/CircularProgressBarDoted.js +4 -3
  90. package/lib/module/components/CircularRating/CircularRating.js +3 -2
  91. package/lib/module/components/ClusterBubble/ClusterBubble.js +4 -3
  92. package/lib/module/components/CompareTable/CompareTable.js +396 -0
  93. package/lib/module/components/ComparisonBar/ComparisonBar.js +260 -0
  94. package/lib/module/components/CoverageBarComparison/CoverageBarComparison.js +4 -3
  95. package/lib/module/components/DonutChart/DonutChart.js +5 -4
  96. package/lib/module/components/Dropdown/Dropdown.js +4 -3
  97. package/lib/module/components/DropdownInput/DropdownInput.js +40 -7
  98. package/lib/module/components/ExpandableCheckbox/ExpandableCheckbox.js +2 -1
  99. package/lib/module/components/FormField/FormField.js +48 -7
  100. package/lib/module/components/Gauge/Gauge.js +1 -1
  101. package/lib/module/components/HoldingsCard/HoldingsCard.js +4 -3
  102. package/lib/module/components/InputSearch/InputSearch.js +6 -4
  103. package/lib/module/components/InstitutionBadge/InstitutionBadge.js +3 -2
  104. package/lib/module/components/ListGroup/ListGroup.js +3 -2
  105. package/lib/module/components/ListItem/ListItem.js +4 -3
  106. package/lib/module/components/MediaCard/MediaCard.js +4 -2
  107. package/lib/module/components/MetricLegendItem/MetricLegendItem.js +4 -3
  108. package/lib/module/components/MonthlyStatusGrid/MonthlyStatusGrid.js +7 -4
  109. package/lib/module/components/NoteInput/NoteInput.js +7 -6
  110. package/lib/module/components/PdpCcCard/PdpCcCard.js +267 -0
  111. package/lib/module/components/PoweredByLabel/PoweredByLabel.js +4 -3
  112. package/lib/module/components/ProductMerchandisingCard/ProductMerchandisingCard.js +421 -0
  113. package/lib/module/components/ProductOverview/ProductOverview.js +5 -4
  114. package/lib/module/components/ProgressBadge/ProgressBadge.js +3 -3
  115. package/lib/module/components/ProjectionMarker/ProjectionMarker.js +157 -0
  116. package/lib/module/components/Radio/Radio.js +5 -4
  117. package/lib/module/components/Section/Section.js +4 -3
  118. package/lib/module/components/Slider/Slider.js +469 -0
  119. package/lib/module/components/Stack/Stack.js +81 -0
  120. package/lib/module/components/SuggestiveSearch/SuggestiveSearch.js +10 -1
  121. package/lib/module/components/SummaryTile/SummaryTile.js +4 -3
  122. package/lib/module/components/Table/Table.js +315 -0
  123. package/lib/module/components/Text/Text.js +3 -2
  124. package/lib/module/components/TextInput/TextInput.js +24 -11
  125. package/lib/module/components/TextSegment/TextSegment.js +114 -0
  126. package/lib/module/components/ThreadHero/ThreadHero.js +5 -6
  127. package/lib/module/components/Title/Title.js +3 -2
  128. package/lib/module/components/Toast/Toast.js +4 -3
  129. package/lib/module/components/TransactionBubble/TransactionBubble.js +3 -2
  130. package/lib/module/components/TransactionStatus/TransactionStatus.js +4 -3
  131. package/lib/module/components/UpiHandle/UpiHandle.js +3 -3
  132. package/lib/module/components/index.js +11 -0
  133. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
  134. package/lib/module/design-tokens/figma-modes.generated.js +39 -9
  135. package/lib/module/icons/registry.js +1 -1
  136. package/lib/module/utils/GlassFill/GlassFill.js +265 -0
  137. package/lib/module/utils/GlassFill/GlassFill.web.js +115 -0
  138. package/lib/module/utils/react-utils.js +75 -0
  139. package/lib/module/utils/web-platform-utils.js +58 -0
  140. package/lib/typescript/src/components/Accordion/Accordion.d.ts +7 -1
  141. package/lib/typescript/src/components/AccordionCheckbox/AccordionCheckbox.d.ts +7 -1
  142. package/lib/typescript/src/components/AccountCard/AccountCard.d.ts +7 -1
  143. package/lib/typescript/src/components/AllocationComparisonChart/AllocationComparisonChart.d.ts +7 -1
  144. package/lib/typescript/src/components/AmountInput/AmountInput.d.ts +3 -2
  145. package/lib/typescript/src/components/AreaLineChart/AreaLineChart.d.ts +8 -1
  146. package/lib/typescript/src/components/Badge/Badge.d.ts +32 -2
  147. package/lib/typescript/src/components/BenefitCard/BenefitCard.d.ts +93 -0
  148. package/lib/typescript/src/components/BrandChip/BrandChip.d.ts +7 -1
  149. package/lib/typescript/src/components/Button/Button.d.ts +7 -1
  150. package/lib/typescript/src/components/CardAdvisory/CardAdvisory.d.ts +7 -1
  151. package/lib/typescript/src/components/CardBankAccount/CardBankAccount.d.ts +7 -1
  152. package/lib/typescript/src/components/CardCTA/CardCTA.d.ts +27 -5
  153. package/lib/typescript/src/components/CcCard/CcCard.d.ts +137 -0
  154. package/lib/typescript/src/components/Checkbox/Checkbox.d.ts +3 -2
  155. package/lib/typescript/src/components/CheckboxItem/CheckboxItem.d.ts +2 -2
  156. package/lib/typescript/src/components/CircularProgressBar/CircularProgressBar.d.ts +7 -1
  157. package/lib/typescript/src/components/CircularProgressBarDoted/CircularProgressBarDoted.d.ts +7 -1
  158. package/lib/typescript/src/components/CircularRating/CircularRating.d.ts +7 -1
  159. package/lib/typescript/src/components/ClusterBubble/ClusterBubble.d.ts +7 -1
  160. package/lib/typescript/src/components/CompareTable/CompareTable.d.ts +94 -0
  161. package/lib/typescript/src/components/ComparisonBar/ComparisonBar.d.ts +118 -0
  162. package/lib/typescript/src/components/CoverageBarComparison/CoverageBarComparison.d.ts +7 -1
  163. package/lib/typescript/src/components/DonutChart/DonutChart.d.ts +7 -1
  164. package/lib/typescript/src/components/Dropdown/Dropdown.d.ts +7 -1
  165. package/lib/typescript/src/components/DropdownInput/DropdownInput.d.ts +26 -1
  166. package/lib/typescript/src/components/ExpandableCheckbox/ExpandableCheckbox.d.ts +7 -1
  167. package/lib/typescript/src/components/FormField/FormField.d.ts +2 -2
  168. package/lib/typescript/src/components/HoldingsCard/HoldingsCard.d.ts +7 -1
  169. package/lib/typescript/src/components/InputSearch/InputSearch.d.ts +23 -2
  170. package/lib/typescript/src/components/InstitutionBadge/InstitutionBadge.d.ts +7 -1
  171. package/lib/typescript/src/components/ListGroup/ListGroup.d.ts +7 -1
  172. package/lib/typescript/src/components/ListItem/ListItem.d.ts +7 -1
  173. package/lib/typescript/src/components/MetricLegendItem/MetricLegendItem.d.ts +7 -1
  174. package/lib/typescript/src/components/MonthlyStatusGrid/MonthlyStatusGrid.d.ts +14 -2
  175. package/lib/typescript/src/components/NoteInput/NoteInput.d.ts +19 -2
  176. package/lib/typescript/src/components/PdpCcCard/PdpCcCard.d.ts +84 -0
  177. package/lib/typescript/src/components/PoweredByLabel/PoweredByLabel.d.ts +7 -1
  178. package/lib/typescript/src/components/ProductMerchandisingCard/ProductMerchandisingCard.d.ts +87 -0
  179. package/lib/typescript/src/components/ProductOverview/ProductOverview.d.ts +7 -1
  180. package/lib/typescript/src/components/ProgressBadge/ProgressBadge.d.ts +7 -1
  181. package/lib/typescript/src/components/ProjectionMarker/ProjectionMarker.d.ts +88 -0
  182. package/lib/typescript/src/components/Radio/Radio.d.ts +3 -2
  183. package/lib/typescript/src/components/RadioButton/RadioButton.d.ts +2 -2
  184. package/lib/typescript/src/components/Section/Section.d.ts +7 -1
  185. package/lib/typescript/src/components/Slider/Slider.d.ts +105 -0
  186. package/lib/typescript/src/components/Stack/Stack.d.ts +81 -0
  187. package/lib/typescript/src/components/SummaryTile/SummaryTile.d.ts +7 -1
  188. package/lib/typescript/src/components/Table/Table.d.ts +139 -0
  189. package/lib/typescript/src/components/Text/Text.d.ts +7 -1
  190. package/lib/typescript/src/components/TextInput/TextInput.d.ts +9 -29
  191. package/lib/typescript/src/components/TextSegment/TextSegment.d.ts +106 -0
  192. package/lib/typescript/src/components/ThreadHero/ThreadHero.d.ts +7 -1
  193. package/lib/typescript/src/components/Title/Title.d.ts +7 -1
  194. package/lib/typescript/src/components/Toast/Toast.d.ts +7 -1
  195. package/lib/typescript/src/components/TransactionBubble/TransactionBubble.d.ts +7 -1
  196. package/lib/typescript/src/components/TransactionStatus/TransactionStatus.d.ts +7 -1
  197. package/lib/typescript/src/components/UpiHandle/UpiHandle.d.ts +7 -1
  198. package/lib/typescript/src/components/index.d.ts +14 -3
  199. package/lib/typescript/src/design-tokens/figma-modes.generated.d.ts +22 -2
  200. package/lib/typescript/src/icons/registry.d.ts +1 -1
  201. package/lib/typescript/src/utils/GlassFill/GlassFill.d.ts +84 -0
  202. package/lib/typescript/src/utils/GlassFill/GlassFill.web.d.ts +43 -0
  203. package/lib/typescript/src/utils/react-utils.d.ts +47 -0
  204. package/lib/typescript/src/utils/web-platform-utils.d.ts +36 -0
  205. package/package.json +2 -1
  206. package/src/components/Accordion/Accordion.tsx +9 -2
  207. package/src/components/AccordionCheckbox/AccordionCheckbox.tsx +10 -3
  208. package/src/components/AccountCard/AccountCard.tsx +10 -3
  209. package/src/components/AllocationComparisonChart/AllocationComparisonChart.tsx +14 -4
  210. package/src/components/AmountInput/AmountInput.tsx +7 -5
  211. package/src/components/AreaLineChart/AreaLineChart.tsx +15 -5
  212. package/src/components/Badge/Badge.tsx +78 -8
  213. package/src/components/BenefitCard/BenefitCard.tsx +309 -0
  214. package/src/components/BrandChip/BrandChip.tsx +9 -3
  215. package/src/components/Button/Button.tsx +9 -2
  216. package/src/components/CardAdvisory/CardAdvisory.tsx +9 -2
  217. package/src/components/CardBankAccount/CardBankAccount.tsx +9 -2
  218. package/src/components/CardCTA/CardCTA.tsx +214 -41
  219. package/src/components/CardInsight/CardInsight.tsx +7 -1
  220. package/src/components/CcCard/CcCard.tsx +598 -0
  221. package/src/components/Checkbox/Checkbox.tsx +5 -4
  222. package/src/components/CheckboxItem/CheckboxItem.tsx +5 -4
  223. package/src/components/CircularProgressBar/CircularProgressBar.tsx +14 -4
  224. package/src/components/CircularProgressBarDoted/CircularProgressBarDoted.tsx +10 -3
  225. package/src/components/CircularRating/CircularRating.tsx +9 -2
  226. package/src/components/ClusterBubble/ClusterBubble.tsx +9 -2
  227. package/src/components/CompareTable/CompareTable.tsx +525 -0
  228. package/src/components/ComparisonBar/ComparisonBar.tsx +356 -0
  229. package/src/components/CoverageBarComparison/CoverageBarComparison.tsx +9 -2
  230. package/src/components/DonutChart/DonutChart.tsx +10 -3
  231. package/src/components/Dropdown/Dropdown.tsx +9 -2
  232. package/src/components/DropdownInput/DropdownInput.tsx +64 -5
  233. package/src/components/ExpandableCheckbox/ExpandableCheckbox.tsx +8 -1
  234. package/src/components/FormField/FormField.tsx +52 -6
  235. package/src/components/Gauge/Gauge.tsx +1 -1
  236. package/src/components/HoldingsCard/HoldingsCard.tsx +9 -2
  237. package/src/components/InputSearch/InputSearch.tsx +8 -5
  238. package/src/components/InstitutionBadge/InstitutionBadge.tsx +9 -2
  239. package/src/components/ListGroup/ListGroup.tsx +9 -2
  240. package/src/components/ListItem/ListItem.tsx +10 -3
  241. package/src/components/MediaCard/MediaCard.tsx +2 -2
  242. package/src/components/MetricLegendItem/MetricLegendItem.tsx +9 -2
  243. package/src/components/MonthlyStatusGrid/MonthlyStatusGrid.tsx +17 -2
  244. package/src/components/NoteInput/NoteInput.tsx +8 -6
  245. package/src/components/PdpCcCard/PdpCcCard.tsx +356 -0
  246. package/src/components/PoweredByLabel/PoweredByLabel.tsx +9 -2
  247. package/src/components/ProductMerchandisingCard/ProductMerchandisingCard.tsx +490 -0
  248. package/src/components/ProductOverview/ProductOverview.tsx +10 -3
  249. package/src/components/ProgressBadge/ProgressBadge.tsx +9 -3
  250. package/src/components/ProjectionMarker/ProjectionMarker.tsx +284 -0
  251. package/src/components/Radio/Radio.tsx +5 -4
  252. package/src/components/Section/Section.tsx +10 -3
  253. package/src/components/Slider/Slider.tsx +635 -0
  254. package/src/components/Stack/Stack.tsx +131 -0
  255. package/src/components/SuggestiveSearch/SuggestiveSearch.tsx +10 -1
  256. package/src/components/SummaryTile/SummaryTile.tsx +9 -2
  257. package/src/components/Table/Table.tsx +449 -0
  258. package/src/components/Text/Text.tsx +9 -2
  259. package/src/components/TextInput/TextInput.tsx +22 -12
  260. package/src/components/TextSegment/TextSegment.tsx +173 -0
  261. package/src/components/ThreadHero/ThreadHero.tsx +10 -3
  262. package/src/components/Title/Title.tsx +9 -2
  263. package/src/components/Toast/Toast.tsx +9 -2
  264. package/src/components/TransactionBubble/TransactionBubble.tsx +9 -2
  265. package/src/components/TransactionStatus/TransactionStatus.tsx +9 -2
  266. package/src/components/UpiHandle/UpiHandle.tsx +9 -3
  267. package/src/components/index.ts +33 -3
  268. package/src/design-tokens/Coin Variables-variables-full.json +1 -1
  269. package/src/design-tokens/figma-modes.generated.ts +39 -9
  270. package/src/icons/registry.ts +1 -1
  271. package/src/utils/GlassFill/GlassFill.tsx +305 -0
  272. package/src/utils/GlassFill/GlassFill.web.tsx +148 -0
  273. package/src/utils/react-utils.ts +84 -0
  274. package/src/utils/web-platform-utils.ts +68 -0
  275. package/lib/commonjs/components/MediaCard/GlassFill.js +0 -62
  276. package/lib/commonjs/components/MediaCard/GlassFill.web.js +0 -48
  277. package/lib/module/components/MediaCard/GlassFill.js +0 -57
  278. package/lib/module/components/MediaCard/GlassFill.web.js +0 -43
  279. package/lib/typescript/scripts/extract-component-tokens.d.ts +0 -9
  280. package/lib/typescript/scripts/generate-component-docs.d.ts +0 -9
  281. package/lib/typescript/scripts/generate-icon-registry.d.ts +0 -3
  282. package/lib/typescript/scripts/generate-mode-types.d.ts +0 -2
  283. package/lib/typescript/scripts/retype-modes.d.cts +0 -2
  284. package/lib/typescript/src/components/MediaCard/GlassFill.d.ts +0 -47
  285. package/lib/typescript/src/components/MediaCard/GlassFill.web.d.ts +0 -20
  286. package/src/components/MediaCard/GlassFill.tsx +0 -89
  287. package/src/components/MediaCard/GlassFill.web.tsx +0 -53
@@ -0,0 +1,356 @@
1
+ import React, { useMemo } from 'react'
2
+ import {
3
+ Pressable,
4
+ View,
5
+ type ImageSourcePropType,
6
+ type StyleProp,
7
+ type ViewStyle,
8
+ } from 'react-native'
9
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
10
+ import { useTokens } from '../../design-tokens/JFSThemeProvider'
11
+ import { EMPTY_MODES } from '../../utils/react-utils'
12
+ import { usePressableWebSupport } from '../../utils/web-platform-utils'
13
+ import Button from '../Button/Button'
14
+ import Image from '../Image/Image'
15
+ import IconCapsule from '../IconCapsule/IconCapsule'
16
+ import type { Modes } from '../../design-tokens'
17
+
18
+ /**
19
+ * A single slot in the {@link ComparisonBar}. Each item is either empty (the
20
+ * "Add" state — a tappable `+` capsule) or filled with an image (the
21
+ * "Image Added" state — the image plus a dismiss capsule in the corner).
22
+ *
23
+ * The presence of {@link ComparisonBarItem.imageSource} is what toggles the
24
+ * state: provide a source to show the image, leave it `undefined`/`null` to
25
+ * show the empty add slot. This keeps the component fully controlled — the
26
+ * `ComparisonBar` never owns the image state itself, so the consumer decides
27
+ * (e.g. after opening a file/asset picker) when and how an item flips between
28
+ * the two states.
29
+ */
30
+ export type ComparisonBarItem = {
31
+ /**
32
+ * Stable identifier for this slot. Returned to callbacks so the consumer
33
+ * can target the exact item that was interacted with. Falls back to the
34
+ * array index when omitted.
35
+ */
36
+ id?: string | number
37
+ /**
38
+ * Image to render in the slot. When provided the slot renders in the
39
+ * "Image Added" state; when omitted/`null` it renders the empty "Add" state.
40
+ * Accepts the same shapes as the library `Image` component (remote URL
41
+ * string, `{ uri }`, or a `require()`d asset).
42
+ */
43
+ imageSource?: ImageSourcePropType | string | null
44
+ /** Accessibility label for the slot. Defaults to a generic add/remove label. */
45
+ accessibilityLabel?: string
46
+ }
47
+
48
+ export type ComparisonBarProps = {
49
+ /**
50
+ * The slots rendered before the Compare button. Each entry controls its own
51
+ * add/image state via {@link ComparisonBarItem.imageSource}.
52
+ */
53
+ items: ComparisonBarItem[]
54
+ /**
55
+ * Fired when an empty (Add) slot is tapped. The consumer is expected to react
56
+ * by opening whatever picker is appropriate and then updating that item's
57
+ * `imageSource` to flip it into the "Image Added" state — the component does
58
+ * not know how images are sourced. Receives the item's `id` (or index when no
59
+ * id was supplied) and the slot index.
60
+ */
61
+ onItemPress?: (id: ComparisonBarItem['id'], index: number) => void
62
+ /**
63
+ * Fired when a filled slot is tapped. For better mobile ergonomics the
64
+ * *entire* filled slot is the remove target (the dismiss capsule is just a
65
+ * visual affordance), so a fingertip anywhere on the item triggers this. The
66
+ * consumer is expected to clear that item's `imageSource` to return it to the
67
+ * "Add" state. Receives the item's `id` (or index) and the slot index.
68
+ */
69
+ onItemRemove?: (id: ComparisonBarItem['id'], index: number) => void
70
+ /** Fired when the Compare button is pressed. */
71
+ onCompare?: () => void
72
+ /** Label for the trailing action button. Defaults to `"Compare"`. */
73
+ compareLabel?: string
74
+ /**
75
+ * Explicitly controls the Compare button's *functional* disabled state — a
76
+ * truly disabled button is non-interactive (its `onPress` never fires), not
77
+ * just dimmed. Note that `modes` only affects appearance, so dimming the
78
+ * button via tokens does NOT stop taps; that is what this prop is for.
79
+ *
80
+ * When provided (a boolean), it always wins over
81
+ * {@link ComparisonBarProps.disableCompareWhenEmpty}. Leave it `undefined`
82
+ * to fall back to the auto behavior.
83
+ */
84
+ compareDisabled?: boolean
85
+ /**
86
+ * When `true` (default) the Compare button is automatically (and truly)
87
+ * disabled while no slot has an image — there is nothing to compare yet. Set
88
+ * to `false` to keep it tappable even when empty. Ignored when
89
+ * {@link ComparisonBarProps.compareDisabled} is set explicitly.
90
+ */
91
+ disableCompareWhenEmpty?: boolean
92
+ /** Mode configuration passed to the token resolver. */
93
+ modes?: Modes
94
+ /** Style overrides for the outer floating card. */
95
+ style?: StyleProp<ViewStyle>
96
+ }
97
+
98
+ const ITEM_WIDTH = 45
99
+ const ITEM_HEIGHT = 44
100
+
101
+ interface ComparisonBarTokens {
102
+ card: ViewStyle
103
+ item: ViewStyle
104
+ itemImageState: ViewStyle
105
+ imageRadius: number
106
+ }
107
+
108
+ function resolveTokens(modes: Modes): ComparisonBarTokens {
109
+ const cardGap = (getVariableByName('compareFloatCard/gap', modes) ?? 12) as number
110
+ const cardPadH = (getVariableByName('compareFloatCard/padding/horizontal', modes) ?? 12) as number
111
+ const cardPadV = (getVariableByName('compareFloatCard/padding/vertical', modes) ?? 10) as number
112
+ const cardRadius = (getVariableByName('compareFloatCard/radius', modes) ?? 12) as number
113
+ const cardBackground = (getVariableByName('compareFloatCard/background', modes) ?? '#ffffff') as string
114
+ const cardBorderColor = (getVariableByName('compareFloatCard/border/color', modes) ?? '#f5f5f5') as string
115
+
116
+ const itemPadH = (getVariableByName('compareCardItem/padding/horizontal', modes) ?? 6) as number
117
+ const itemPadV = (getVariableByName('compareCardItem/padding/vertical', modes) ?? 8) as number
118
+ const itemRadius = (getVariableByName('compareCardItem/radius', modes) ?? 8) as number
119
+ const itemBackground = (getVariableByName('compareCardItem/background', modes) ?? '#ebebed') as string
120
+
121
+ const imageRadius = (getVariableByName('image/radius', modes) ?? 8) as number
122
+
123
+ return {
124
+ card: {
125
+ flexDirection: 'row',
126
+ alignItems: 'center',
127
+ gap: cardGap,
128
+ paddingHorizontal: cardPadH,
129
+ paddingVertical: cardPadV,
130
+ borderRadius: cardRadius,
131
+ borderWidth: 1,
132
+ borderColor: cardBorderColor,
133
+ backgroundColor: cardBackground,
134
+ alignSelf: 'flex-start',
135
+ },
136
+ item: {
137
+ width: ITEM_WIDTH,
138
+ height: ITEM_HEIGHT,
139
+ borderRadius: itemRadius,
140
+ backgroundColor: itemBackground,
141
+ paddingHorizontal: itemPadH,
142
+ paddingVertical: itemPadV,
143
+ alignItems: 'center',
144
+ justifyContent: 'center',
145
+ },
146
+ itemImageState: {
147
+ paddingHorizontal: itemPadH,
148
+ paddingVertical: itemPadV,
149
+ },
150
+ imageRadius,
151
+ }
152
+ }
153
+
154
+ type AdditemProps = {
155
+ item: ComparisonBarItem
156
+ index: number
157
+ tokens: ComparisonBarTokens
158
+ addCapsuleModes: Modes
159
+ closeCapsuleModes: Modes
160
+ onPress?: ComparisonBarProps['onItemPress']
161
+ onRemove?: ComparisonBarProps['onItemRemove']
162
+ }
163
+
164
+ /**
165
+ * Internal slot renderer for {@link ComparisonBar}. Intentionally NOT exported
166
+ * — it is meaningless outside of a `ComparisonBar` (its layout, sizing and
167
+ * remove affordance all assume the surrounding card) and is kept private so
168
+ * the public surface stays a single, cohesive component.
169
+ */
170
+ function Additem({ item, index, tokens, addCapsuleModes, closeCapsuleModes, onPress, onRemove }: AdditemProps) {
171
+ const hasImage = item.imageSource != null && item.imageSource !== ''
172
+ const id = item.id ?? index
173
+
174
+ const addWebProps = usePressableWebSupport({
175
+ restProps: {},
176
+ onPress: () => onPress?.(id, index),
177
+ disabled: false,
178
+ accessibilityLabel: item.accessibilityLabel ?? 'Add item to comparison',
179
+ })
180
+
181
+ const removeWebProps = usePressableWebSupport({
182
+ restProps: {},
183
+ onPress: () => onRemove?.(id, index),
184
+ disabled: false,
185
+ accessibilityLabel: item.accessibilityLabel ?? 'Remove item from comparison',
186
+ })
187
+
188
+ if (!hasImage) {
189
+ return (
190
+ <Pressable
191
+ style={tokens.item}
192
+ accessibilityRole="button"
193
+ accessibilityLabel={item.accessibilityLabel ?? 'Add item to comparison'}
194
+ onPress={() => onPress?.(id, index)}
195
+ {...addWebProps}
196
+ >
197
+ <IconCapsule iconName="ic_add" modes={addCapsuleModes} style={ADD_CAPSULE_STYLE} />
198
+ </Pressable>
199
+ )
200
+ }
201
+
202
+ // Mobile-first: the entire filled slot is the remove target, not just the
203
+ // tiny close capsule (which a fingertip struggles to hit). The capsule stays
204
+ // purely as a visual affordance and is marked non-interactive so it never
205
+ // intercepts the press from the surrounding Pressable.
206
+ return (
207
+ <Pressable
208
+ style={[tokens.item, tokens.itemImageState]}
209
+ accessibilityRole="button"
210
+ accessibilityLabel={item.accessibilityLabel ?? 'Remove item from comparison'}
211
+ onPress={() => onRemove?.(id, index)}
212
+ {...removeWebProps}
213
+ >
214
+ <Image
215
+ imageSource={item.imageSource as any}
216
+ width="100%"
217
+ height="100%"
218
+ borderRadius={tokens.imageRadius}
219
+ resizeMode="cover"
220
+ accessibilityLabel={item.accessibilityLabel ?? 'Comparison item image'}
221
+ />
222
+ <View style={CLOSE_CAPSULE_WRAPPER_STYLE} pointerEvents="none">
223
+ <IconCapsule iconName="ic_close" modes={closeCapsuleModes} />
224
+ </View>
225
+ </Pressable>
226
+ )
227
+ }
228
+
229
+ // The Add capsule is the transparent IconCapsule variant from Figma (the gray
230
+ // item box is the visible surface); its size/icon come from the resolved
231
+ // `Icon Capsule Size: S` tokens, we only flatten the background/border here.
232
+ const ADD_CAPSULE_STYLE: ViewStyle = {
233
+ backgroundColor: 'transparent',
234
+ borderColor: 'transparent',
235
+ }
236
+
237
+ // Positions the dismiss IconCapsule in the slot's top-right corner. The capsule
238
+ // itself (size/background/icon) is fully token-driven via `closeCapsuleModes`.
239
+ const CLOSE_CAPSULE_WRAPPER_STYLE: ViewStyle = {
240
+ position: 'absolute',
241
+ top: 1,
242
+ right: 1,
243
+ }
244
+
245
+ // Mode overrides applied on top of the consumer's `modes` for each capsule.
246
+ // These mirror the Figma component's IconCapsule variant selections so the
247
+ // sizing and colours come from design tokens instead of magic numbers.
248
+ const ADD_CAPSULE_MODE_OVERRIDES: Modes = { 'Icon Capsule Size': 'XS' }
249
+ const CLOSE_CAPSULE_MODE_OVERRIDES: Modes = {
250
+ AppearanceBrand: 'Neutral',
251
+ Emphasis: 'Medium',
252
+ 'Icon Capsule Size': 'XS',
253
+ }
254
+
255
+ /**
256
+ * ComparisonBar — a floating card that lets a user assemble a set of items to
257
+ * compare, then trigger the comparison.
258
+ *
259
+ * Each slot is fully controlled via its `imageSource`: an empty slot shows a
260
+ * tappable `+` (the "Add" state) and a filled slot shows the image with a
261
+ * dismiss capsule (the "Image Added" state). The component never sources or
262
+ * stores images itself — when an empty slot is pressed it fires `onItemPress`
263
+ * with the item's id/index so the consumer can open whatever picker is
264
+ * appropriate and then update that item's `imageSource` to flip its state.
265
+ * Tapping a filled slot (anywhere on it — a mobile-friendly hit target, with
266
+ * the dismiss capsule as a visual affordance) fires `onItemRemove` so the
267
+ * consumer can clear the source again.
268
+ *
269
+ * @example
270
+ * ```tsx
271
+ * const [items, setItems] = useState<ComparisonBarItem[]>([
272
+ * { id: 'a' }, { id: 'b' }, { id: 'c' }, { id: 'd' },
273
+ * ])
274
+ *
275
+ * <ComparisonBar
276
+ * items={items}
277
+ * onItemPress={async (id) => {
278
+ * const uri = await openImagePicker()
279
+ * setItems(prev => prev.map(it => it.id === id ? { ...it, imageSource: uri } : it))
280
+ * }}
281
+ * onItemRemove={(id) =>
282
+ * setItems(prev => prev.map(it => it.id === id ? { ...it, imageSource: null } : it))
283
+ * }
284
+ * onCompare={runComparison}
285
+ * />
286
+ * ```
287
+ */
288
+ function ComparisonBar({
289
+ items,
290
+ onItemPress,
291
+ onItemRemove,
292
+ onCompare,
293
+ compareLabel = 'Compare',
294
+ compareDisabled,
295
+ disableCompareWhenEmpty = true,
296
+ modes: propModes = EMPTY_MODES,
297
+ style,
298
+ }: ComparisonBarProps) {
299
+ const { modes: globalModes } = useTokens()
300
+ const modes = useMemo(
301
+ () =>
302
+ globalModes === EMPTY_MODES && propModes === EMPTY_MODES
303
+ ? EMPTY_MODES
304
+ : { ...globalModes, ...propModes },
305
+ [globalModes, propModes]
306
+ )
307
+
308
+ const tokens = useMemo(() => resolveTokens(modes), [modes])
309
+
310
+ // Capsule modes = consumer modes + this component's fixed IconCapsule variant
311
+ // selections. Memoized so each `Additem`'s `IconCapsule` keeps a stable
312
+ // `modes` identity and hits the resolver cache.
313
+ const addCapsuleModes = useMemo(
314
+ () => ({ ...modes, ...ADD_CAPSULE_MODE_OVERRIDES }),
315
+ [modes]
316
+ )
317
+ const closeCapsuleModes = useMemo(
318
+ () => ({ ...modes, ...CLOSE_CAPSULE_MODE_OVERRIDES }),
319
+ [modes]
320
+ )
321
+
322
+ // An explicit `compareDisabled` always wins (functional disable — the tap is
323
+ // truly blocked, not merely dimmed). Otherwise auto-disable while no slot has
324
+ // an image, since there is nothing to compare yet.
325
+ const hasAnyImage = useMemo(
326
+ () => items.some((it) => it.imageSource != null && it.imageSource !== ''),
327
+ [items]
328
+ )
329
+ const isCompareDisabled =
330
+ compareDisabled ?? (disableCompareWhenEmpty && !hasAnyImage)
331
+
332
+ return (
333
+ <View style={[tokens.card, style]}>
334
+ {items.map((item, index) => (
335
+ <Additem
336
+ key={item.id ?? index}
337
+ item={item}
338
+ index={index}
339
+ tokens={tokens}
340
+ addCapsuleModes={addCapsuleModes}
341
+ closeCapsuleModes={closeCapsuleModes}
342
+ onPress={onItemPress}
343
+ onRemove={onItemRemove}
344
+ />
345
+ ))}
346
+ <Button
347
+ label={compareLabel}
348
+ modes={modes}
349
+ disabled={isCompareDisabled}
350
+ {...(onCompare !== undefined ? { onPress: onCompare } : {})}
351
+ />
352
+ </View>
353
+ )
354
+ }
355
+
356
+ export default React.memo(ComparisonBar)
@@ -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
  )}
@@ -1,6 +1,8 @@
1
1
  import React, {
2
+ forwardRef,
2
3
  useCallback,
3
4
  useEffect,
5
+ useImperativeHandle,
4
6
  useMemo,
5
7
  useRef,
6
8
  useState,
@@ -22,7 +24,7 @@ import {
22
24
  import { useSafeAreaInsets } from 'react-native-safe-area-context'
23
25
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
24
26
  import { useTokens } from '../../design-tokens/JFSThemeProvider'
25
- import { EMPTY_MODES, flattenChildren } from '../../utils/react-utils'
27
+ import { EMPTY_MODES, flattenChildren, resolveTruncation } from '../../utils/react-utils'
26
28
  import Icon from '../../icons/Icon'
27
29
  import SupportText from '../SupportText/SupportText'
28
30
  import type { SupportTextStatus } from '../SupportText/SupportTextIcon'
@@ -52,6 +54,28 @@ export type DropdownInputOption = {
52
54
 
53
55
  type Rect = { x: number; y: number; width: number; height: number }
54
56
 
57
+ /**
58
+ * Imperative handle exposed via `ref`. Lets the consumer drive the dropdown
59
+ * from outside — e.g. close it when an Android hardware/system button is
60
+ * pressed, or open it programmatically from a sibling control.
61
+ */
62
+ export type DropdownInputHandle = {
63
+ /** Opens the options menu (no-op when disabled / read-only). */
64
+ open: () => void
65
+ /** Closes the options menu. */
66
+ close: () => void
67
+ /** Toggles the options menu open/closed. */
68
+ toggle: () => void
69
+ /** Moves focus to the trigger (web). */
70
+ focus: () => void
71
+ /** Removes focus from the trigger (web). */
72
+ blur: () => void
73
+ /** Measures the trigger in window coordinates. */
74
+ measureInWindow: (
75
+ callback: (x: number, y: number, width: number, height: number) => void
76
+ ) => void
77
+ }
78
+
55
79
  export type DropdownInputProps = {
56
80
  /** Label rendered above the input. */
57
81
  label?: string
@@ -147,6 +171,12 @@ export type DropdownInputProps = {
147
171
  onFocus?: (e: any) => void
148
172
  /** Called when the trigger loses focus (web only). */
149
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
150
180
  }
151
181
 
152
182
  // ---------------------------------------------------------------------------
@@ -307,7 +337,7 @@ function collectOptionsFromChildren(
307
337
  // Component
308
338
  // ---------------------------------------------------------------------------
309
339
 
310
- function DropdownInput({
340
+ const DropdownInput = forwardRef<DropdownInputHandle, DropdownInputProps>(function DropdownInput({
311
341
  label,
312
342
  placeholder = 'Select an option',
313
343
  items,
@@ -338,7 +368,8 @@ function DropdownInput({
338
368
  accessibilityHint,
339
369
  onFocus,
340
370
  onBlur,
341
- }: DropdownInputProps) {
371
+ disableTruncation,
372
+ }: DropdownInputProps, ref: React.Ref<DropdownInputHandle>) {
342
373
  // ---------------- Modes ----------------
343
374
  const { modes: globalModes } = useTokens()
344
375
  const baseModes = useMemo(
@@ -497,6 +528,34 @@ function DropdownInput({
497
528
  [measure]
498
529
  )
499
530
 
531
+ // ---------------- Imperative handle ----------------
532
+ useImperativeHandle(
533
+ ref,
534
+ () => ({
535
+ open: () => {
536
+ if (isDisabled || isReadOnly) return
537
+ measure()
538
+ setOpenState(true)
539
+ },
540
+ close: closeMenu,
541
+ toggle: () => {
542
+ if (isDisabled || isReadOnly) return
543
+ measure()
544
+ toggleMenu()
545
+ },
546
+ focus: () => {
547
+ ;(triggerRef.current as unknown as { focus?: () => void })?.focus?.()
548
+ },
549
+ blur: () => {
550
+ ;(triggerRef.current as unknown as { blur?: () => void })?.blur?.()
551
+ },
552
+ measureInWindow: (callback) => {
553
+ triggerRef.current?.measureInWindow(callback)
554
+ },
555
+ }),
556
+ [isDisabled, isReadOnly, measure, setOpenState, closeMenu, toggleMenu]
557
+ )
558
+
500
559
  // ---------------- Popup positioning ----------------
501
560
  const [menuSize, setMenuSize] = useState<{
502
561
  width: number
@@ -777,7 +836,7 @@ function DropdownInput({
777
836
  valueTextStyle,
778
837
  !hasValue && { color: placeholderColor },
779
838
  ]}
780
- numberOfLines={1}
839
+ {...resolveTruncation(disableTruncation, 1)}
781
840
  >
782
841
  {displayLabel}
783
842
  </Text>
@@ -863,7 +922,7 @@ function DropdownInput({
863
922
  </Modal>
864
923
  </View>
865
924
  )
866
- }
925
+ })
867
926
 
868
927
  const webNoOutline: any = {
869
928
  outlineStyle: 'none',
@@ -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