jfs-components 0.0.69 → 0.0.71

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 (56) hide show
  1. package/CHANGELOG.md +20 -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/CircularProgressBar/CircularProgressBar.js +147 -0
  5. package/lib/commonjs/components/CircularProgressBarDoted/CircularProgressBarDoted.js +258 -0
  6. package/lib/commonjs/components/CircularRating/CircularRating.js +161 -0
  7. package/lib/commonjs/components/Gauge/Gauge.js +223 -0
  8. package/lib/commonjs/components/ListGroup/ListGroup.js +3 -1
  9. package/lib/commonjs/components/MediaCard/GlassFill.js +62 -0
  10. package/lib/commonjs/components/MediaCard/GlassFill.web.js +48 -0
  11. package/lib/commonjs/components/MediaCard/MediaCard.js +28 -31
  12. package/lib/commonjs/components/Nudge/Nudge.js +179 -87
  13. package/lib/commonjs/components/index.js +35 -0
  14. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
  15. package/lib/commonjs/icons/registry.js +1 -1
  16. package/lib/module/components/CardAdvisory/CardAdvisory.js +197 -0
  17. package/lib/module/components/CardCTA/CardCTA.js +199 -17
  18. package/lib/module/components/CircularProgressBar/CircularProgressBar.js +141 -0
  19. package/lib/module/components/CircularProgressBarDoted/CircularProgressBarDoted.js +253 -0
  20. package/lib/module/components/CircularRating/CircularRating.js +155 -0
  21. package/lib/module/components/Gauge/Gauge.js +217 -0
  22. package/lib/module/components/ListGroup/ListGroup.js +3 -1
  23. package/lib/module/components/MediaCard/GlassFill.js +57 -0
  24. package/lib/module/components/MediaCard/GlassFill.web.js +43 -0
  25. package/lib/module/components/MediaCard/MediaCard.js +29 -32
  26. package/lib/module/components/Nudge/Nudge.js +178 -87
  27. package/lib/module/components/index.js +5 -0
  28. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
  29. package/lib/module/icons/registry.js +1 -1
  30. package/lib/typescript/src/components/CardAdvisory/CardAdvisory.d.ts +49 -0
  31. package/lib/typescript/src/components/CardCTA/CardCTA.d.ts +16 -1
  32. package/lib/typescript/src/components/CircularProgressBar/CircularProgressBar.d.ts +27 -0
  33. package/lib/typescript/src/components/CircularProgressBarDoted/CircularProgressBarDoted.d.ts +48 -0
  34. package/lib/typescript/src/components/CircularRating/CircularRating.d.ts +49 -0
  35. package/lib/typescript/src/components/Gauge/Gauge.d.ts +53 -0
  36. package/lib/typescript/src/components/MediaCard/GlassFill.d.ts +47 -0
  37. package/lib/typescript/src/components/MediaCard/GlassFill.web.d.ts +20 -0
  38. package/lib/typescript/src/components/MediaCard/MediaCard.d.ts +17 -13
  39. package/lib/typescript/src/components/Nudge/Nudge.d.ts +14 -11
  40. package/lib/typescript/src/components/index.d.ts +6 -1
  41. package/lib/typescript/src/icons/registry.d.ts +1 -1
  42. package/package.json +3 -2
  43. package/src/components/CardAdvisory/CardAdvisory.tsx +283 -0
  44. package/src/components/CardCTA/CardCTA.tsx +236 -13
  45. package/src/components/CircularProgressBar/CircularProgressBar.tsx +190 -0
  46. package/src/components/CircularProgressBarDoted/CircularProgressBarDoted.tsx +357 -0
  47. package/src/components/CircularRating/CircularRating.tsx +241 -0
  48. package/src/components/Gauge/Gauge.tsx +303 -0
  49. package/src/components/ListGroup/ListGroup.tsx +3 -1
  50. package/src/components/MediaCard/GlassFill.tsx +89 -0
  51. package/src/components/MediaCard/GlassFill.web.tsx +53 -0
  52. package/src/components/MediaCard/MediaCard.tsx +29 -48
  53. package/src/components/Nudge/Nudge.tsx +222 -82
  54. package/src/components/index.ts +6 -1
  55. package/src/design-tokens/Coin Variables-variables-full.json +1 -1
  56. package/src/icons/registry.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,26 @@ 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.70] - 2026-04-23
8
+
9
+ ### Fixed
10
+
11
+ - **`MediaCard.Footer` blur on bare React Native:** `0.0.69` shipped the glass footer using `expo-blur`, which silently required consumers to integrate Expo Modules autolinking (`use_expo_modules!` in `Podfile`, `ExpoModulesPackage` on Android). Bare React Native apps that just installed the library and ran `pod install` hit two failures: a runtime "`Cannot read property 'BlurView' of undefined`" red box on Android and an iOS Xcode build failure after `pod install`. **Replaced `expo-blur` with [`@react-native-community/blur`](https://github.com/Kureev/react-native-blur)** — a regular React Native native module handled by standard autolinking. No Expo runtime required.
12
+
13
+ ### Changed
14
+
15
+ - **`MediaCard.Footer` glass implementation:** Native blur now lives in a small platform-split helper, `MediaCard/GlassFill.tsx` (iOS + Android via the community blur module) and `MediaCard/GlassFill.web.tsx` (`backdrop-filter` via inline style). Metro picks the correct file per platform, so the web bundle never imports the native-only blur module. The Footer's API and design-token contract (`blur/minimal/background`, `blur/minimal`, `Contrast Context` mode) are unchanged.
16
+
17
+ ### Removed
18
+
19
+ - **`expo-blur`** is no longer a runtime dependency.
20
+
21
+ ### Dependencies
22
+
23
+ - **`@react-native-community/blur`** (`>=4.4.0`) added as a **peer dependency**. Consumers must install it once: `npm install @react-native-community/blur && cd ios && pod install`. On Expo, use `npx expo install @react-native-community/blur` and prebuild.
24
+
25
+ ---
26
+
7
27
  ## [0.0.69] - 2026-04-22
8
28
 
9
29
  ### Changed
@@ -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), 16);
41
+ const paddingVertical = toNumber((0, _figmaVariablesResolver.getVariableByName)('cardAdvisory/padding/vertical', modes), 12);
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,
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _react = _interopRequireDefault(require("react"));
8
+ var _reactNative = require("react-native");
9
+ var _reactNativeSvg = _interopRequireWildcard(require("react-native-svg"));
10
+ var _figmaVariablesResolver = require("../../design-tokens/figma-variables-resolver");
11
+ var _JFSThemeProvider = require("../../design-tokens/JFSThemeProvider");
12
+ var _reactUtils = require("../../utils/react-utils");
13
+ var _IconMinus = require("../../icons/components/IconMinus");
14
+ var _jsxRuntime = require("react/jsx-runtime");
15
+ 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); }
16
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
17
+ const STROKE_WIDTH_RATIO = 8 / 60;
18
+ const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
19
+ const toNumber = (value, fallback) => {
20
+ if (typeof value === 'number') {
21
+ return Number.isFinite(value) ? value : fallback;
22
+ }
23
+ if (typeof value === 'string') {
24
+ const parsed = Number(value);
25
+ return Number.isFinite(parsed) ? parsed : fallback;
26
+ }
27
+ return fallback;
28
+ };
29
+ const toFontWeight = (value, fallback) => {
30
+ if (typeof value === 'number') {
31
+ return String(value);
32
+ }
33
+ if (typeof value === 'string') {
34
+ return value;
35
+ }
36
+ return fallback;
37
+ };
38
+ const getStrokeColor = (style, fallback) => {
39
+ const flattened = _reactNative.StyleSheet.flatten(style);
40
+ return typeof flattened.backgroundColor === 'string' ? flattened.backgroundColor : fallback;
41
+ };
42
+ function CircularProgressBar({
43
+ value = 70,
44
+ state = 'Inactive',
45
+ valueLabel,
46
+ modes: propModes = _reactUtils.EMPTY_MODES,
47
+ style,
48
+ trackStyle,
49
+ progressStyle,
50
+ valueStyle,
51
+ accessibilityLabel,
52
+ ...rest
53
+ }) {
54
+ const {
55
+ modes: globalModes
56
+ } = (0, _JFSThemeProvider.useTokens)();
57
+ const modes = {
58
+ ...globalModes,
59
+ ...propModes
60
+ };
61
+ const isActive = state === true || state === 'Active';
62
+ const normalizedValue = clamp(value, 0, 100);
63
+ const size = toNumber((0, _figmaVariablesResolver.getVariableByName)('circularProgressBar/size', modes), 60);
64
+ const strokeWidth = Math.max(1, size * STROKE_WIDTH_RATIO);
65
+ const radius = Math.max(0, (size - strokeWidth) / 2);
66
+ const center = size / 2;
67
+ const circumference = 2 * Math.PI * radius;
68
+ const trackColor = getStrokeColor(trackStyle, (0, _figmaVariablesResolver.getVariableByName)('circularProgressBar/track/color', modes) || '#ebebed');
69
+ const progressColor = getStrokeColor(progressStyle, (0, _figmaVariablesResolver.getVariableByName)('circularProgressBar/progress/color', modes) || '#25ab21');
70
+ const iconColor = (0, _figmaVariablesResolver.getVariableByName)('circularProgressBar/icon/color', modes) || '#666666';
71
+ const iconSize = toNumber((0, _figmaVariablesResolver.getVariableByName)('circularProgressBar/icon/size', modes), 24);
72
+ const foreground = (0, _figmaVariablesResolver.getVariableByName)('circularProgressBar/foreground', modes) || '#0d0d0f';
73
+ const fontSize = toNumber((0, _figmaVariablesResolver.getVariableByName)('circularProgressBar/fontSize', modes), 18);
74
+ const fontFamily = (0, _figmaVariablesResolver.getVariableByName)('circularProgressBar/fontFamily', modes) || 'JioType Var';
75
+ const lineHeight = toNumber((0, _figmaVariablesResolver.getVariableByName)('circularProgressBar/lineHeight', modes), 21);
76
+ const fontWeight = toFontWeight((0, _figmaVariablesResolver.getVariableByName)('circularProgressBar/fontWeight', modes), '700');
77
+ const computedContainerStyle = {
78
+ alignItems: 'center',
79
+ height: size,
80
+ justifyContent: 'center',
81
+ position: 'relative',
82
+ width: size
83
+ };
84
+ const computedValueStyle = {
85
+ color: foreground,
86
+ fontFamily,
87
+ fontSize,
88
+ fontWeight,
89
+ lineHeight,
90
+ position: 'absolute',
91
+ textAlign: 'center'
92
+ };
93
+ const iconStyle = {
94
+ left: (size - iconSize) / 2,
95
+ position: 'absolute',
96
+ top: (size - iconSize) / 2
97
+ };
98
+ const displayValue = valueLabel ?? String(Math.round(normalizedValue));
99
+ const defaultAccessibilityLabel = accessibilityLabel ?? (isActive ? `${displayValue} out of 100` : 'Inactive progress');
100
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
101
+ accessibilityRole: "progressbar",
102
+ accessibilityLabel: defaultAccessibilityLabel,
103
+ accessibilityValue: {
104
+ min: 0,
105
+ max: 100,
106
+ now: normalizedValue
107
+ },
108
+ style: [computedContainerStyle, style],
109
+ ...rest,
110
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeSvg.default, {
111
+ width: size,
112
+ height: size,
113
+ viewBox: `0 0 ${size} ${size}`,
114
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Circle, {
115
+ cx: center,
116
+ cy: center,
117
+ r: radius,
118
+ stroke: trackColor,
119
+ strokeWidth: strokeWidth,
120
+ fill: "none"
121
+ }), isActive ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Circle, {
122
+ cx: center,
123
+ cy: center,
124
+ r: radius,
125
+ stroke: progressColor,
126
+ strokeWidth: strokeWidth,
127
+ strokeLinecap: "round",
128
+ fill: "none",
129
+ strokeDasharray: `${circumference} ${circumference}`,
130
+ strokeDashoffset: circumference * (1 - normalizedValue / 100),
131
+ rotation: "-90",
132
+ originX: center,
133
+ originY: center
134
+ }) : null]
135
+ }), isActive ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
136
+ style: [computedValueStyle, valueStyle],
137
+ children: displayValue
138
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_IconMinus.IconMinus, {
139
+ width: iconSize,
140
+ height: iconSize,
141
+ fill: iconColor,
142
+ color: iconColor,
143
+ style: iconStyle
144
+ })]
145
+ });
146
+ }
147
+ var _default = exports.default = CircularProgressBar;