jfs-components 0.0.70 → 0.0.72

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 (66) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/lib/commonjs/components/CardAdvisory/CardAdvisory.js +203 -0
  3. package/lib/commonjs/components/CardCTA/CardCTA.js +198 -16
  4. package/lib/commonjs/components/CardFinancialCondition/CardFinancialCondition.js +213 -0
  5. package/lib/commonjs/components/Carousel/Carousel.js +9 -7
  6. package/lib/commonjs/components/CircularProgressBar/CircularProgressBar.js +147 -0
  7. package/lib/commonjs/components/CircularProgressBarDoted/CircularProgressBarDoted.js +258 -0
  8. package/lib/commonjs/components/CircularRating/CircularRating.js +161 -0
  9. package/lib/commonjs/components/Gauge/Gauge.js +223 -0
  10. package/lib/commonjs/components/HoldingsCard/HoldingsCard.js +2 -2
  11. package/lib/commonjs/components/InstitutionBadge/InstitutionBadge.js +132 -0
  12. package/lib/commonjs/components/ListGroup/ListGroup.js +3 -1
  13. package/lib/commonjs/components/Nudge/Nudge.js +179 -87
  14. package/lib/commonjs/components/Radio/Radio.js +194 -0
  15. package/lib/commonjs/components/RadioButton/RadioButton.js +21 -188
  16. package/lib/commonjs/components/index.js +56 -0
  17. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
  18. package/lib/commonjs/icons/registry.js +1 -1
  19. package/lib/module/components/CardAdvisory/CardAdvisory.js +197 -0
  20. package/lib/module/components/CardCTA/CardCTA.js +199 -17
  21. package/lib/module/components/CardFinancialCondition/CardFinancialCondition.js +207 -0
  22. package/lib/module/components/Carousel/Carousel.js +9 -7
  23. package/lib/module/components/CircularProgressBar/CircularProgressBar.js +141 -0
  24. package/lib/module/components/CircularProgressBarDoted/CircularProgressBarDoted.js +253 -0
  25. package/lib/module/components/CircularRating/CircularRating.js +155 -0
  26. package/lib/module/components/Gauge/Gauge.js +217 -0
  27. package/lib/module/components/HoldingsCard/HoldingsCard.js +2 -2
  28. package/lib/module/components/InstitutionBadge/InstitutionBadge.js +127 -0
  29. package/lib/module/components/ListGroup/ListGroup.js +3 -1
  30. package/lib/module/components/Nudge/Nudge.js +178 -87
  31. package/lib/module/components/Radio/Radio.js +188 -0
  32. package/lib/module/components/RadioButton/RadioButton.js +20 -185
  33. package/lib/module/components/index.js +12 -0
  34. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
  35. package/lib/module/icons/registry.js +1 -1
  36. package/lib/typescript/src/components/CardAdvisory/CardAdvisory.d.ts +49 -0
  37. package/lib/typescript/src/components/CardCTA/CardCTA.d.ts +16 -1
  38. package/lib/typescript/src/components/CardFinancialCondition/CardFinancialCondition.d.ts +50 -0
  39. package/lib/typescript/src/components/CircularProgressBar/CircularProgressBar.d.ts +27 -0
  40. package/lib/typescript/src/components/CircularProgressBarDoted/CircularProgressBarDoted.d.ts +48 -0
  41. package/lib/typescript/src/components/CircularRating/CircularRating.d.ts +49 -0
  42. package/lib/typescript/src/components/Gauge/Gauge.d.ts +53 -0
  43. package/lib/typescript/src/components/InstitutionBadge/InstitutionBadge.d.ts +30 -0
  44. package/lib/typescript/src/components/Nudge/Nudge.d.ts +14 -11
  45. package/lib/typescript/src/components/Radio/Radio.d.ts +30 -0
  46. package/lib/typescript/src/components/RadioButton/RadioButton.d.ts +20 -28
  47. package/lib/typescript/src/components/index.d.ts +13 -1
  48. package/lib/typescript/src/icons/registry.d.ts +1 -1
  49. package/package.json +1 -1
  50. package/src/components/CardAdvisory/CardAdvisory.tsx +283 -0
  51. package/src/components/CardCTA/CardCTA.tsx +236 -13
  52. package/src/components/CardFinancialCondition/CardFinancialCondition.tsx +366 -0
  53. package/src/components/Carousel/Carousel.tsx +14 -6
  54. package/src/components/CircularProgressBar/CircularProgressBar.tsx +190 -0
  55. package/src/components/CircularProgressBarDoted/CircularProgressBarDoted.tsx +357 -0
  56. package/src/components/CircularRating/CircularRating.tsx +241 -0
  57. package/src/components/Gauge/Gauge.tsx +303 -0
  58. package/src/components/HoldingsCard/HoldingsCard.tsx +2 -2
  59. package/src/components/InstitutionBadge/InstitutionBadge.tsx +216 -0
  60. package/src/components/ListGroup/ListGroup.tsx +3 -1
  61. package/src/components/Nudge/Nudge.tsx +222 -82
  62. package/src/components/Radio/Radio.tsx +227 -0
  63. package/src/components/RadioButton/RadioButton.tsx +23 -225
  64. package/src/components/index.ts +13 -1
  65. package/src/design-tokens/Coin Variables-variables-full.json +1 -1
  66. package/src/icons/registry.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,55 @@ All notable changes to this project are documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
+ ## [0.0.72] - 2026-05-04
8
+
9
+ High-level summary (one line per point):
10
+
11
+ - **`RadioButton` renamed to `Radio`** — naming aligned with the rest of the library; the old `RadioButton` import is preserved as a deprecated alias so consumer code keeps working without changes.
12
+ - **`Carousel` pagination is now fully token-driven** — dot size, active width, and active/inactive colors come from design tokens (`carousel/pagination/indicator/{size,activeWidth,activeColor,inactiveColor}`) instead of hardcoded values, with token names normalized to camelCase.
13
+ - **`Carousel` outer container height is now controlled by a token** — new `carousel/maxHeight` token enforces the Figma-aligned max height of the carousel viewport.
14
+ - **`HoldingsCard` migrated to `Radio`** — internal-only swap, no API change for consumers.
15
+
16
+ ### Changed
17
+
18
+ - `Carousel.Pagination` now reads `carousel/pagination/indicator/size`, `carousel/pagination/indicator/activeWidth`, `carousel/pagination/indicator/activeColor`, and `carousel/pagination/indicator/inactiveColor` from design tokens. Previous lowercase names (`activecolor`, `inactivecolor`) and hardcoded dot dimensions (6×6 / 16×6) are replaced.
19
+ - `Carousel` outer container respects a new `carousel/maxHeight` token (defaults to `280`) applied as `maxHeight` on the outer view.
20
+ - `HoldingsCard` now imports the new `Radio` component instead of `RadioButton` (no behavioral change).
21
+
22
+ ### Added
23
+
24
+ - New `Radio` component (`src/components/Radio/Radio.tsx`) with its own stories and MDX docs. Exported from the package barrel as `Radio` / `RadioProps`.
25
+
26
+ ### Deprecated
27
+
28
+ - `RadioButton` is deprecated. It is kept as a thin re-export of `Radio` for backward compatibility and will be removed in a future major release. Migrate to `import { Radio } from 'jfs-components'`.
29
+
30
+ ---
31
+
32
+ ## [0.0.71] - 2026-05-04
33
+
34
+ ### Added
35
+
36
+ - **`CardAdvisory`** — new card component with stories and MDX docs.
37
+ - **`CardFinancialCondition`** — new card component with stories and MDX docs.
38
+ - **`CircularProgressBar`** — new circular progress component with stories and MDX docs.
39
+ - **`CircularProgressBarDoted`** — new dotted variant of the circular progress component with stories and MDX docs.
40
+ - **`CircularRating`** — new circular rating display component with stories and MDX docs.
41
+ - **`Gauge`** — new gauge component with stories and MDX docs.
42
+ - **`InstitutionBadge`** — new institution badge component with stories and MDX docs.
43
+ - All new components are exported from the package barrel with their public types.
44
+
45
+ ### Changed
46
+
47
+ - **`Nudge`** — significant rewrite (props, layout, and token usage refreshed); story file simplified.
48
+ - **`CardCTA`** — expanded API and visual variants; story coverage extended.
49
+ - **`MediaCard`** — minor doc/story refresh; no breaking changes.
50
+ - **`ListGroup`** — small adjustments to internal layout; no API change.
51
+ - Refreshed component token metadata and the icon registry; updated `Coin Variables` design-token JSON.
52
+ - Storybook plumbing: added `.storybook/storybook-test-mock.js` and registered it in `.storybook/main.js` to stabilize Storybook test runs.
53
+
54
+ ---
55
+
7
56
  ## [0.0.70] - 2026-04-23
8
57
 
9
58
  ### Fixed
@@ -0,0 +1,203 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _react = _interopRequireWildcard(require("react"));
8
+ var _reactNative = require("react-native");
9
+ var _figmaVariablesResolver = require("../../design-tokens/figma-variables-resolver");
10
+ var _JFSThemeProvider = require("../../design-tokens/JFSThemeProvider");
11
+ var _Icon = _interopRequireDefault(require("../../icons/Icon"));
12
+ var _reactUtils = require("../../utils/react-utils");
13
+ var _CircularProgressBar = _interopRequireDefault(require("../CircularProgressBar/CircularProgressBar"));
14
+ var _Nudge = _interopRequireDefault(require("../Nudge/Nudge"));
15
+ var _jsxRuntime = require("react/jsx-runtime");
16
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
17
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
18
+ const toNumber = (value, fallback) => {
19
+ if (typeof value === 'number') {
20
+ return Number.isFinite(value) ? value : fallback;
21
+ }
22
+ if (typeof value === 'string') {
23
+ const parsed = Number(value);
24
+ return Number.isFinite(parsed) ? parsed : fallback;
25
+ }
26
+ return fallback;
27
+ };
28
+ const toFontWeight = (value, fallback) => {
29
+ if (typeof value === 'number') {
30
+ return String(value);
31
+ }
32
+ if (typeof value === 'string') {
33
+ return value;
34
+ }
35
+ return fallback;
36
+ };
37
+ function resolveCardAdvisoryTokens(modes) {
38
+ const width = toNumber((0, _figmaVariablesResolver.getVariableByName)('cardAdvisory/width', modes), 360);
39
+ const gap = toNumber((0, _figmaVariablesResolver.getVariableByName)('cardAdvisory/gap', modes), 16);
40
+ const paddingHorizontal = toNumber((0, _figmaVariablesResolver.getVariableByName)('cardAdvisory/padding/horizontal', modes), 0);
41
+ const paddingVertical = toNumber((0, _figmaVariablesResolver.getVariableByName)('cardAdvisory/padding/vertical', modes), 0);
42
+ const radius = toNumber((0, _figmaVariablesResolver.getVariableByName)('cardAdvisory/radius', modes), 0);
43
+ const background = (0, _figmaVariablesResolver.getVariableByName)('cardAdvisory/background', modes) || '#ffffff';
44
+ const mainContentGap = toNumber((0, _figmaVariablesResolver.getVariableByName)('cardAdvisory/mainContent/gap', modes), 16);
45
+ const contentGap = toNumber((0, _figmaVariablesResolver.getVariableByName)('cardAdvisory/content/gap', modes), 6);
46
+ const headerGap = toNumber((0, _figmaVariablesResolver.getVariableByName)('cardAdvisory/header/gap', modes), 8);
47
+ const titleColor = (0, _figmaVariablesResolver.getVariableByName)('cardAdvisory/title/foreground', modes) || '#0d0d0f';
48
+ const titleFontSize = toNumber((0, _figmaVariablesResolver.getVariableByName)('cardAdvisory/title/fontSize', modes), 26);
49
+ const titleFontFamily = (0, _figmaVariablesResolver.getVariableByName)('cardAdvisory/title/fontFamily', modes) || 'JioType Var';
50
+ const titleLineHeight = toNumber((0, _figmaVariablesResolver.getVariableByName)('cardAdvisory/title/lineHeight', modes), 26);
51
+ const titleFontWeight = toFontWeight((0, _figmaVariablesResolver.getVariableByName)('cardAdvisory/title/fontWeight', modes), '900');
52
+ const titleDescenderAllowance = Math.ceil(titleFontSize * 0.16);
53
+ const descriptionColor = (0, _figmaVariablesResolver.getVariableByName)('cardAdvisory/description/foreground', modes) || '#24262b';
54
+ const descriptionFontSize = toNumber((0, _figmaVariablesResolver.getVariableByName)('cardAdvisory/description/fontSize', modes), 12);
55
+ const descriptionFontFamily = (0, _figmaVariablesResolver.getVariableByName)('cardAdvisory/description/fontFamily', modes) || 'JioType Var';
56
+ const descriptionLineHeight = toNumber((0, _figmaVariablesResolver.getVariableByName)('cardAdvisory/description/lineHeight', modes), 16);
57
+ const descriptionFontWeight = toFontWeight((0, _figmaVariablesResolver.getVariableByName)('cardAdvisory/description/fontWeight', modes), '500');
58
+ return {
59
+ containerStyle: {
60
+ alignItems: 'flex-start',
61
+ backgroundColor: background,
62
+ borderRadius: radius,
63
+ gap,
64
+ overflow: 'hidden',
65
+ paddingHorizontal,
66
+ paddingVertical,
67
+ width
68
+ },
69
+ mainContentStyle: {
70
+ alignItems: 'flex-start',
71
+ flexDirection: 'row',
72
+ gap: mainContentGap,
73
+ width: '100%'
74
+ },
75
+ contentStyle: {
76
+ alignItems: 'flex-start',
77
+ flex: 1,
78
+ gap: contentGap,
79
+ minWidth: 1
80
+ },
81
+ headerStyle: {
82
+ alignItems: 'center',
83
+ flexDirection: 'row',
84
+ gap: headerGap,
85
+ width: '100%'
86
+ },
87
+ titleStyle: {
88
+ color: titleColor,
89
+ fontFamily: titleFontFamily,
90
+ fontSize: titleFontSize,
91
+ fontWeight: titleFontWeight,
92
+ lineHeight: titleLineHeight,
93
+ marginBottom: -titleDescenderAllowance,
94
+ paddingBottom: titleDescenderAllowance
95
+ },
96
+ descriptionStyle: {
97
+ color: descriptionColor,
98
+ fontFamily: descriptionFontFamily,
99
+ fontSize: descriptionFontSize,
100
+ fontWeight: descriptionFontWeight,
101
+ lineHeight: descriptionLineHeight,
102
+ width: '100%'
103
+ },
104
+ iconColor: (0, _figmaVariablesResolver.getVariableByName)('cardAdvisory/icon/color', modes) || '#1a1c1f',
105
+ iconSize: toNumber((0, _figmaVariablesResolver.getVariableByName)('cardAdvisory/icon/size', modes), 18)
106
+ };
107
+ }
108
+ function CardAdvisory({
109
+ title = 'Spending',
110
+ description = 'Track your spending habits and stay within your budget.',
111
+ value = 70,
112
+ valueLabel,
113
+ showInfoIcon = true,
114
+ showNudge = true,
115
+ nudgeBody = 'Data confidence is low, add more accounts for better insights.',
116
+ nudgeButtonLabel = 'Button',
117
+ onPressNudgeButton,
118
+ titleEndSlot,
119
+ progressSlot,
120
+ nudgeSlot,
121
+ modes: propModes = _reactUtils.EMPTY_MODES,
122
+ style,
123
+ mainContentStyle,
124
+ titleStyle,
125
+ descriptionStyle,
126
+ progressStyle,
127
+ nudgeStyle,
128
+ accessibilityLabel,
129
+ ...rest
130
+ }) {
131
+ const {
132
+ modes: globalModes
133
+ } = (0, _JFSThemeProvider.useTokens)();
134
+ const modes = (0, _react.useMemo)(() => globalModes === _reactUtils.EMPTY_MODES && propModes === _reactUtils.EMPTY_MODES ? _reactUtils.EMPTY_MODES : {
135
+ ...globalModes,
136
+ ...propModes
137
+ }, [globalModes, propModes]);
138
+ const tokens = (0, _react.useMemo)(() => resolveCardAdvisoryTokens(modes), [modes]);
139
+ const processedTitleEndSlot = (0, _react.useMemo)(() => {
140
+ if (!titleEndSlot) return null;
141
+ const processed = (0, _reactUtils.cloneChildrenWithModes)(_react.default.Children.toArray(titleEndSlot), modes);
142
+ return processed.length === 1 ? processed[0] : processed;
143
+ }, [titleEndSlot, modes]);
144
+ const processedProgressSlot = (0, _react.useMemo)(() => {
145
+ if (!progressSlot) return null;
146
+ const processed = (0, _reactUtils.cloneChildrenWithModes)(_react.default.Children.toArray(progressSlot), modes);
147
+ return processed.length === 1 ? processed[0] : processed;
148
+ }, [progressSlot, modes]);
149
+ const processedNudgeSlot = (0, _react.useMemo)(() => {
150
+ if (!nudgeSlot) return null;
151
+ const processed = (0, _reactUtils.cloneChildrenWithModes)(_react.default.Children.toArray(nudgeSlot), modes);
152
+ return processed.length === 1 ? processed[0] : processed;
153
+ }, [nudgeSlot, modes]);
154
+ const defaultAccessibilityLabel = accessibilityLabel ?? `${title}. ${description}. ${Math.round(value)} out of 100. ${nudgeBody}`;
155
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
156
+ accessibilityLabel: defaultAccessibilityLabel,
157
+ style: [tokens.containerStyle, style],
158
+ ...rest,
159
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
160
+ style: [tokens.mainContentStyle, mainContentStyle],
161
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
162
+ style: tokens.contentStyle,
163
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
164
+ style: tokens.headerStyle,
165
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
166
+ numberOfLines: 1,
167
+ style: [tokens.titleStyle, titleStyle],
168
+ children: title
169
+ }), processedTitleEndSlot || (showInfoIcon ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_Icon.default, {
170
+ name: "ic_info",
171
+ size: tokens.iconSize,
172
+ color: tokens.iconColor,
173
+ accessibilityElementsHidden: true,
174
+ importantForAccessibility: "no"
175
+ }) : null)]
176
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
177
+ style: [tokens.descriptionStyle, descriptionStyle],
178
+ children: description
179
+ })]
180
+ }), processedProgressSlot || /*#__PURE__*/(0, _jsxRuntime.jsx)(_CircularProgressBar.default, {
181
+ state: "Active",
182
+ value: value,
183
+ modes: modes,
184
+ style: progressStyle,
185
+ ...(valueLabel ? {
186
+ valueLabel
187
+ } : {})
188
+ })]
189
+ }), showNudge ? processedNudgeSlot || /*#__PURE__*/(0, _jsxRuntime.jsx)(_Nudge.default, {
190
+ type: "inline-compact",
191
+ body: nudgeBody,
192
+ buttonLabel: nudgeButtonLabel,
193
+ modes: modes,
194
+ style: [{
195
+ width: '100%'
196
+ }, nudgeStyle],
197
+ ...(onPressNudgeButton ? {
198
+ onPressButton: onPressNudgeButton
199
+ } : {})
200
+ }) : null]
201
+ });
202
+ }
203
+ var _default = exports.default = /*#__PURE__*/_react.default.memo(CardAdvisory);
@@ -11,17 +11,39 @@ var _JFSThemeProvider = require("../../design-tokens/JFSThemeProvider");
11
11
  var _reactUtils = require("../../utils/react-utils");
12
12
  var _IconCapsule = _interopRequireDefault(require("../IconCapsule/IconCapsule"));
13
13
  var _Button = _interopRequireDefault(require("../Button/Button"));
14
+ var _Badge = _interopRequireDefault(require("../Badge/Badge"));
15
+ var _ButtonGroup = _interopRequireDefault(require("../ButtonGroup/ButtonGroup"));
16
+ var _IconButton = _interopRequireDefault(require("../IconButton/IconButton"));
14
17
  var _jsxRuntime = require("react/jsx-runtime");
15
18
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
19
+ const optionalTokenAvailability = new Map();
20
+ function getOptionalVariableByName(name, modes, fallback) {
21
+ let isAvailable = optionalTokenAvailability.get(name);
22
+ if (isAvailable === undefined) {
23
+ isAvailable = (0, _figmaVariablesResolver.findVariablesByPattern)(name).some(variable => variable.name === name);
24
+ optionalTokenAvailability.set(name, isAvailable);
25
+ }
26
+ if (!isAvailable) {
27
+ return fallback;
28
+ }
29
+ return (0, _figmaVariablesResolver.getVariableByName)(name, modes) ?? fallback;
30
+ }
16
31
  function CardCTA({
32
+ type = 'cta',
17
33
  title = 'If you have 1 line',
18
34
  body = 'Then you can have up to 3 lines in the subtext as well. This is for demonstration.',
19
35
  iconName = 'ic_upi_number',
20
- buttonLabel = 'Button',
36
+ buttonLabel,
21
37
  onPressButton,
38
+ ratingLabel = '+28 Rating',
39
+ showRatingActions = true,
40
+ onPressLike,
41
+ onPressDislike,
22
42
  modes: propModes = _reactUtils.EMPTY_MODES,
23
43
  iconSlot,
24
44
  buttonSlot,
45
+ ratingBadgeSlot,
46
+ ratingActionsSlot,
25
47
  style
26
48
  }) {
27
49
  const {
@@ -31,6 +53,7 @@ function CardCTA({
31
53
  ...globalModes,
32
54
  ...propModes
33
55
  };
56
+ const isRating = type === 'rating';
34
57
  const background = (0, _figmaVariablesResolver.getVariableByName)('cardCTA/background', modes) || '#ffffff';
35
58
  const radius = (0, _figmaVariablesResolver.getVariableByName)('cardCTA/radius', modes) || 12;
36
59
  const borderSize = (0, _figmaVariablesResolver.getVariableByName)('cardCTA/border/size', modes) || 1;
@@ -53,13 +76,48 @@ function CardCTA({
53
76
  const bodyLineHeight = (0, _figmaVariablesResolver.getVariableByName)('cardCTA/body/lineHeight', modes) || 12;
54
77
  const bodyFontWeightRaw = (0, _figmaVariablesResolver.getVariableByName)('cardCTA/body/fontWeight', modes) || 400;
55
78
  const bodyFontWeight = typeof bodyFontWeightRaw === 'number' ? bodyFontWeightRaw.toString() : bodyFontWeightRaw;
79
+ const ratingContentGap = getOptionalVariableByName('cardCTA/rating/content/gap', modes, 12);
80
+ const ratingContentPaddingH = getOptionalVariableByName('cardCTA/rating/content/padding/horizontal', modes, 16);
81
+ const ratingContentPaddingV = getOptionalVariableByName('cardCTA/rating/content/padding/vertical', modes, 12);
82
+ const ratingFooterPaddingH = getOptionalVariableByName('cardCTA/rating/footer/horizontal', modes, 16);
83
+ const ratingFooterPaddingTop = getOptionalVariableByName('cardCTA/rating/footer/top', modes, 0);
84
+ const ratingFooterPaddingBottom = getOptionalVariableByName('cardCTA/rating/footer/bottom', modes, 12);
85
+ const buttonModes = {
86
+ ...modes,
87
+ AppearanceBrand: 'Secondary',
88
+ 'Button / Size': 'S'
89
+ };
90
+ const iconButtonModes = {
91
+ 'Button / Size': 'S',
92
+ 'Emphasis': 'Low',
93
+ 'AppearanceBrand': 'Neutral',
94
+ ...modes
95
+ };
96
+ const effectiveButtonLabel = buttonLabel ?? (isRating ? 'Save' : 'Button');
97
+ const nonWrappingButtonLabel = effectiveButtonLabel.replace(/\s/g, '\u00A0');
98
+ const [measuredButtonLabelWidth, setMeasuredButtonLabelWidth] = _react.default.useState(null);
99
+ const buttonPaddingH = (0, _figmaVariablesResolver.getVariableByName)('button/padding/horizontal', buttonModes) || 20;
100
+ const buttonBorderSize = (0, _figmaVariablesResolver.getVariableByName)('button/border/size', buttonModes) ?? 1;
101
+ const measuredButtonWidth = measuredButtonLabelWidth === null ? undefined : Math.ceil(measuredButtonLabelWidth + buttonPaddingH * 2 + buttonBorderSize * 2);
102
+ const handleButtonLabelTextLayout = _react.default.useCallback(event => {
103
+ const lines = event?.nativeEvent?.lines;
104
+ if (!Array.isArray(lines) || lines.length === 0) return;
105
+ const nextWidth = Math.ceil(lines.reduce((sum, line) => sum + (typeof line?.width === 'number' ? line.width : 0), 0));
106
+ if (nextWidth <= 0) return;
107
+ setMeasuredButtonLabelWidth(currentWidth => {
108
+ if (currentWidth !== null && Math.abs(currentWidth - nextWidth) < 1) {
109
+ return currentWidth;
110
+ }
111
+ return nextWidth;
112
+ });
113
+ }, []);
56
114
  const containerStyle = {
57
115
  backgroundColor: background,
58
116
  borderRadius: radius,
59
117
  borderWidth: borderSize,
60
118
  borderColor,
61
- flexDirection: 'row',
62
- overflow: 'hidden'
119
+ flexDirection: isRating ? 'column' : 'row',
120
+ overflow: 'visible'
63
121
  };
64
122
 
65
123
  // NOTE: `minWidth: 0` + explicit `flexShrink: 1` are required on native.
@@ -76,7 +134,9 @@ function CardCTA({
76
134
  paddingVertical: leftPaddingV,
77
135
  gap: leftGap,
78
136
  alignItems: 'flex-start',
79
- justifyContent: 'center'
137
+ justifyContent: 'center',
138
+ overflow: 'visible',
139
+ zIndex: 1
80
140
  };
81
141
 
82
142
  // NOTE: rightWrap must NOT shrink on native. On Android (Yoga), the default
@@ -105,6 +165,29 @@ function CardCTA({
105
165
  alignSelf: 'stretch',
106
166
  minWidth: 0
107
167
  };
168
+
169
+ // Keep text shrink/wrap behavior on the left column, but let the CTA keep
170
+ // its own intrinsic width. On native, Yoga otherwise measures the Button
171
+ // with the left column's available width and the single-line label
172
+ // truncates even when the Button itself has no width/maxWidth constraint.
173
+ const buttonWrapStyle = {
174
+ alignSelf: 'flex-start',
175
+ flexGrow: 0,
176
+ flexShrink: 0,
177
+ flexBasis: 'auto',
178
+ overflow: 'visible',
179
+ zIndex: 1
180
+ };
181
+ const buttonStyle = {
182
+ alignSelf: 'flex-start',
183
+ flexGrow: 0,
184
+ flexShrink: 0,
185
+ flexBasis: 'auto',
186
+ overflow: 'visible',
187
+ ...(measuredButtonWidth !== undefined ? {
188
+ width: measuredButtonWidth
189
+ } : {})
190
+ };
108
191
  const titleStyle = {
109
192
  color: titleColor,
110
193
  fontFamily: titleFontFamily,
@@ -119,6 +202,104 @@ function CardCTA({
119
202
  lineHeight: bodyLineHeight,
120
203
  fontWeight: bodyFontWeight
121
204
  };
205
+ const ratingContentStyle = {
206
+ paddingHorizontal: ratingContentPaddingH,
207
+ paddingVertical: ratingContentPaddingV,
208
+ gap: ratingContentGap,
209
+ alignItems: 'flex-start'
210
+ };
211
+ const ratingFooterStyle = {
212
+ flexDirection: 'row',
213
+ alignItems: 'flex-start',
214
+ justifyContent: 'space-between',
215
+ paddingHorizontal: ratingFooterPaddingH,
216
+ paddingTop: ratingFooterPaddingTop,
217
+ paddingBottom: ratingFooterPaddingBottom,
218
+ overflow: 'visible'
219
+ };
220
+ const buttonLabelStyle = {
221
+ flexGrow: 0,
222
+ flexShrink: 0,
223
+ flexWrap: 'nowrap'
224
+ };
225
+
226
+ // Keep the rating CTA on an overflow-visible, non-shrinking path. The
227
+ // footer's row width stays fixed by the card, but Yoga must not use that
228
+ // width to shrink or clip the button label.
229
+ const ratingButtonWrapStyle = {
230
+ flexGrow: 0,
231
+ flexShrink: 0,
232
+ flexBasis: 'auto',
233
+ alignItems: 'flex-start',
234
+ overflow: 'visible'
235
+ };
236
+ const ratingButtonStyle = {
237
+ alignSelf: 'flex-start',
238
+ flexGrow: 0,
239
+ flexShrink: 0,
240
+ flexBasis: 'auto',
241
+ overflow: 'visible',
242
+ ...(measuredButtonWidth !== undefined ? {
243
+ width: measuredButtonWidth
244
+ } : {})
245
+ };
246
+ const ratingButtonLabelStyle = {
247
+ flexGrow: 0,
248
+ flexShrink: 0,
249
+ flexWrap: 'nowrap'
250
+ };
251
+ if (isRating) {
252
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
253
+ style: [containerStyle, style],
254
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
255
+ style: ratingContentStyle,
256
+ children: [ratingBadgeSlot ? (0, _reactUtils.cloneChildrenWithModes)(ratingBadgeSlot, modes) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_Badge.default, {
257
+ label: ratingLabel,
258
+ modes: modes
259
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
260
+ style: textWrapStyle,
261
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
262
+ style: titleStyle,
263
+ children: title
264
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
265
+ style: bodyStyle,
266
+ children: body
267
+ })]
268
+ })]
269
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
270
+ style: ratingFooterStyle,
271
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
272
+ style: ratingButtonWrapStyle,
273
+ children: buttonSlot ? (0, _reactUtils.cloneChildrenWithModes)(buttonSlot, buttonModes) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_Button.default, {
274
+ label: effectiveButtonLabel,
275
+ onPress: onPressButton || (() => {}),
276
+ modes: buttonModes,
277
+ style: ratingButtonStyle,
278
+ renderContent: labelStyles => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
279
+ onTextLayout: handleButtonLabelTextLayout,
280
+ style: [labelStyles, ratingButtonLabelStyle],
281
+ children: nonWrappingButtonLabel
282
+ })
283
+ })
284
+ }), showRatingActions ? ratingActionsSlot ? (0, _reactUtils.cloneChildrenWithModes)(ratingActionsSlot, iconButtonModes) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_ButtonGroup.default, {
285
+ modes: iconButtonModes,
286
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_IconButton.default, {
287
+ iconName: "ic_like",
288
+ accessibilityLabel: "Like",
289
+ ...(onPressLike ? {
290
+ onPress: onPressLike
291
+ } : {})
292
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_IconButton.default, {
293
+ iconName: "ic_dislike",
294
+ accessibilityLabel: "Dislike",
295
+ ...(onPressDislike ? {
296
+ onPress: onPressDislike
297
+ } : {})
298
+ })]
299
+ }) : null]
300
+ })]
301
+ });
302
+ }
122
303
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
123
304
  style: [containerStyle, style],
124
305
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
@@ -132,18 +313,19 @@ function CardCTA({
132
313
  style: bodyStyle,
133
314
  children: body
134
315
  })]
135
- }), buttonSlot ? (0, _reactUtils.cloneChildrenWithModes)(buttonSlot, {
136
- ...modes,
137
- AppearanceBrand: 'Secondary',
138
- 'Button / Size': 'S'
139
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_Button.default, {
140
- label: buttonLabel,
141
- onPress: onPressButton || (() => {}),
142
- modes: {
143
- ...modes,
144
- AppearanceBrand: 'Secondary',
145
- 'Button / Size': 'S'
146
- }
316
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
317
+ style: buttonWrapStyle,
318
+ children: buttonSlot ? (0, _reactUtils.cloneChildrenWithModes)(buttonSlot, buttonModes) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_Button.default, {
319
+ label: effectiveButtonLabel,
320
+ onPress: onPressButton || (() => {}),
321
+ modes: buttonModes,
322
+ style: buttonStyle,
323
+ renderContent: labelStyles => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
324
+ onTextLayout: handleButtonLabelTextLayout,
325
+ style: [labelStyles, buttonLabelStyle],
326
+ children: nonWrappingButtonLabel
327
+ })
328
+ })
147
329
  })]
148
330
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
149
331
  style: rightWrapStyle,