jfs-components 0.1.28 → 0.1.32

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 (53) hide show
  1. package/CHANGELOG.md +28 -2
  2. package/lib/commonjs/components/CategoryCard/CategoryCard.js +264 -0
  3. package/lib/commonjs/components/CompareTable/CompareTable.js +247 -25
  4. package/lib/commonjs/components/ContentSheet/ContentSheet.js +91 -8
  5. package/lib/commonjs/components/CounterBadge/CounterBadge.js +78 -0
  6. package/lib/commonjs/components/FavoriteToggle/FavoriteToggle.js +180 -0
  7. package/lib/commonjs/components/HelloJioInput/HelloJioInput.js +287 -0
  8. package/lib/commonjs/components/MoneyValue/MoneyValue.js +179 -54
  9. package/lib/commonjs/components/PdpCcCard/PdpCcCard.js +7 -1
  10. package/lib/commonjs/components/Table/Table.js +27 -7
  11. package/lib/commonjs/components/ValueBackMetric/ValueBackMetric.js +219 -0
  12. package/lib/commonjs/components/index.js +35 -0
  13. package/lib/commonjs/design-tokens/figma-modes.generated.js +3 -0
  14. package/lib/commonjs/icons/registry.js +1 -1
  15. package/lib/module/components/CategoryCard/CategoryCard.js +258 -0
  16. package/lib/module/components/CompareTable/CompareTable.js +247 -26
  17. package/lib/module/components/ContentSheet/ContentSheet.js +94 -11
  18. package/lib/module/components/CounterBadge/CounterBadge.js +73 -0
  19. package/lib/module/components/FavoriteToggle/FavoriteToggle.js +174 -0
  20. package/lib/module/components/HelloJioInput/HelloJioInput.js +281 -0
  21. package/lib/module/components/MoneyValue/MoneyValue.js +182 -57
  22. package/lib/module/components/PdpCcCard/PdpCcCard.js +7 -1
  23. package/lib/module/components/Table/Table.js +27 -7
  24. package/lib/module/components/ValueBackMetric/ValueBackMetric.js +214 -0
  25. package/lib/module/components/index.js +6 -1
  26. package/lib/module/design-tokens/figma-modes.generated.js +3 -0
  27. package/lib/module/icons/registry.js +1 -1
  28. package/lib/typescript/src/components/CategoryCard/CategoryCard.d.ts +72 -0
  29. package/lib/typescript/src/components/CompareTable/CompareTable.d.ts +36 -1
  30. package/lib/typescript/src/components/ContentSheet/ContentSheet.d.ts +20 -1
  31. package/lib/typescript/src/components/CounterBadge/CounterBadge.d.ts +19 -0
  32. package/lib/typescript/src/components/FavoriteToggle/FavoriteToggle.d.ts +66 -0
  33. package/lib/typescript/src/components/HelloJioInput/HelloJioInput.d.ts +111 -0
  34. package/lib/typescript/src/components/MoneyValue/MoneyValue.d.ts +10 -1
  35. package/lib/typescript/src/components/PdpCcCard/PdpCcCard.d.ts +25 -1
  36. package/lib/typescript/src/components/Table/Table.d.ts +9 -1
  37. package/lib/typescript/src/components/ValueBackMetric/ValueBackMetric.d.ts +86 -0
  38. package/lib/typescript/src/components/index.d.ts +5 -0
  39. package/lib/typescript/src/icons/registry.d.ts +1 -1
  40. package/package.json +1 -1
  41. package/src/components/CategoryCard/CategoryCard.tsx +404 -0
  42. package/src/components/CompareTable/CompareTable.tsx +324 -30
  43. package/src/components/ContentSheet/ContentSheet.tsx +120 -11
  44. package/src/components/CounterBadge/CounterBadge.tsx +95 -0
  45. package/src/components/FavoriteToggle/FavoriteToggle.tsx +243 -0
  46. package/src/components/HelloJioInput/HelloJioInput.tsx +513 -0
  47. package/src/components/MoneyValue/MoneyValue.tsx +216 -65
  48. package/src/components/PdpCcCard/PdpCcCard.tsx +36 -1
  49. package/src/components/Table/Table.tsx +29 -4
  50. package/src/components/ValueBackMetric/ValueBackMetric.tsx +298 -0
  51. package/src/components/index.ts +5 -0
  52. package/src/design-tokens/figma-modes.generated.ts +3 -0
  53. package/src/icons/registry.ts +1 -1
@@ -0,0 +1,287 @@
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 _GlassFill = _interopRequireDefault(require("../../utils/GlassFill/GlassFill"));
14
+ var _IconButton = _interopRequireDefault(require("../IconButton/IconButton"));
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 IS_WEB = _reactNative.Platform.OS === 'web';
19
+ const STATE_COLLECTION = 'HelloJioInput State';
20
+
21
+ /** Default IconButton modes matching the Figma send control. */
22
+ const DEFAULT_SEND_BUTTON_MODES = Object.freeze({
23
+ AppearanceBrand: 'Primary',
24
+ 'Button / Size': 'S'
25
+ });
26
+ const toNumber = (value, fallback) => {
27
+ if (typeof value === 'number') {
28
+ return Number.isFinite(value) ? value : fallback;
29
+ }
30
+ if (typeof value === 'string') {
31
+ const parsed = Number(value);
32
+ return Number.isFinite(parsed) ? parsed : fallback;
33
+ }
34
+ return fallback;
35
+ };
36
+ const toFontWeight = (value, fallback) => {
37
+ if (typeof value === 'number') return String(value);
38
+ if (typeof value === 'string') return value;
39
+ return fallback;
40
+ };
41
+ /**
42
+ * Fallback values from the Figma `HelloJioInput` component when local token
43
+ * JSON has not been synced yet. Mode-driven `getVariableByName` wins when
44
+ * tokens resolve.
45
+ */
46
+ const STATE_FALLBACKS = {
47
+ Idle: {
48
+ background: '#f5f5f5',
49
+ borderColor: '#f5f5f5',
50
+ labelColor: '#545961',
51
+ iconColor: '#545961'
52
+ },
53
+ Active: {
54
+ background: '#ffffff',
55
+ borderColor: '#b5b5b5',
56
+ labelColor: '#24262b',
57
+ iconColor: '#24262b'
58
+ },
59
+ IdleJioPlus: {
60
+ background: '#f5f5f5',
61
+ borderColor: '#f5f5f5',
62
+ labelColor: '#545961',
63
+ iconColor: '#545961'
64
+ }
65
+ };
66
+ function resolveHelloJioInputTokens(modes, visualState) {
67
+ const fallback = STATE_FALLBACKS[visualState];
68
+ const background = (0, _figmaVariablesResolver.getVariableByName)('helloJioInput/background', modes) ?? fallback.background;
69
+ const borderColor = (0, _figmaVariablesResolver.getVariableByName)('helloJioInput/border/color', modes) ?? fallback.borderColor;
70
+ const strokeWidth = toNumber((0, _figmaVariablesResolver.getVariableByName)('helloJioInput/strokeWidth', modes), 1);
71
+ const gap = toNumber((0, _figmaVariablesResolver.getVariableByName)('helloJioInput/gap', modes), 8);
72
+ const paddingLeft = toNumber((0, _figmaVariablesResolver.getVariableByName)('helloJioInput/padding/left', modes), 12);
73
+ const paddingRight = toNumber((0, _figmaVariablesResolver.getVariableByName)('helloJioInput/padding/right', modes), 4);
74
+ const paddingTop = toNumber((0, _figmaVariablesResolver.getVariableByName)('helloJioInput/padding/top', modes), 4);
75
+ const paddingBottom = toNumber((0, _figmaVariablesResolver.getVariableByName)('helloJioInput/padding/bottom', modes), 4);
76
+ const height = toNumber((0, _figmaVariablesResolver.getVariableByName)('helloJioInput/height', modes), 36);
77
+ const radiusRaw = toNumber((0, _figmaVariablesResolver.getVariableByName)('helloJioInput/radius', modes), 99999);
78
+ const borderRadius = radiusRaw >= 9999 ? 99999 : radiusRaw;
79
+ const labelColor = (0, _figmaVariablesResolver.getVariableByName)('helloJioInput/label/color', modes) ?? fallback.labelColor;
80
+ const fontSize = toNumber((0, _figmaVariablesResolver.getVariableByName)('helloJioInput/fontSize', modes), 14);
81
+ const lineHeight = toNumber((0, _figmaVariablesResolver.getVariableByName)('helloJioInput/lineHeight', modes), 18);
82
+ const fontFamily = (0, _figmaVariablesResolver.getVariableByName)('helloJioInput/fontFamily', modes) ?? 'JioType Var';
83
+ const fontWeight = toFontWeight((0, _figmaVariablesResolver.getVariableByName)('helloJioInput/fontWeight', modes), '400');
84
+ const iconColor = (0, _figmaVariablesResolver.getVariableByName)('helloJioInput/icon/color', modes) ?? fallback.iconColor;
85
+ const iconSize = toNumber((0, _figmaVariablesResolver.getVariableByName)('textInput/icon/size', modes) ?? (0, _figmaVariablesResolver.getVariableByName)('helloJioInput/icon/size', modes), 18);
86
+ const blurMinimal = toNumber((0, _figmaVariablesResolver.getVariableByName)('blur/minimal', modes), 29);
87
+ const blurIntensity = Math.max(0, Math.min(100, Math.round(blurMinimal)));
88
+ const useGlass = visualState === 'IdleJioPlus';
89
+ return {
90
+ containerStyle: {
91
+ position: 'relative',
92
+ flexDirection: 'row',
93
+ alignItems: 'center',
94
+ // Glass keeps the container transparent so GlassFill shows through.
95
+ backgroundColor: useGlass ? 'transparent' : background,
96
+ borderColor,
97
+ borderWidth: strokeWidth,
98
+ borderStyle: 'solid',
99
+ borderRadius,
100
+ paddingLeft,
101
+ paddingRight,
102
+ paddingTop,
103
+ paddingBottom,
104
+ gap,
105
+ height,
106
+ overflow: 'hidden',
107
+ width: '100%'
108
+ },
109
+ inputStyle: {
110
+ flex: 1,
111
+ color: labelColor,
112
+ fontSize,
113
+ fontFamily,
114
+ fontWeight,
115
+ // iOS single-line TextInput + explicit lineHeight sits below center —
116
+ // same quirk TextInput works around. Omit on iOS.
117
+ ...(_reactNative.Platform.OS === 'ios' ? null : {
118
+ lineHeight
119
+ }),
120
+ padding: 0,
121
+ margin: 0,
122
+ minHeight: lineHeight,
123
+ outlineStyle: 'none',
124
+ outlineWidth: 0,
125
+ outlineColor: 'transparent'
126
+ },
127
+ iconColor,
128
+ iconSize,
129
+ blurIntensity,
130
+ useGlass
131
+ };
132
+ }
133
+
134
+ /**
135
+ * `HelloJioInput` is the pill-shaped HelloJio prompt field: leading brand
136
+ * icon, single-line text input, and a gold send `IconButton` on the right.
137
+ *
138
+ * Visual state is derived automatically:
139
+ * - **Idle** — gray fill, gray label (default)
140
+ * - **Active** — white fill + border while focused
141
+ * - **IdleJioPlus** — frosted glass when `jioPlus` and unfocused
142
+ *
143
+ * @component
144
+ */
145
+ const HelloJioInput = /*#__PURE__*/(0, _react.forwardRef)(function HelloJioInput({
146
+ placeholder = 'Ask me anything',
147
+ value,
148
+ defaultValue = '',
149
+ onChangeText,
150
+ onSubmit,
151
+ leadingIconName = 'ic_hellojio',
152
+ leading,
153
+ trailing,
154
+ sendIconName = 'ic_send_message',
155
+ jioPlus = false,
156
+ disabled = false,
157
+ modes: propModes = _reactUtils.EMPTY_MODES,
158
+ style,
159
+ inputStyle,
160
+ accessibilityLabel,
161
+ accessibilityHint,
162
+ testID,
163
+ onFocus,
164
+ onBlur,
165
+ returnKeyType = 'send',
166
+ ...rest
167
+ }, ref) {
168
+ const {
169
+ modes: globalModes
170
+ } = (0, _JFSThemeProvider.useTokens)();
171
+ const inputRef = (0, _react.useRef)(null);
172
+ const [isFocused, setIsFocused] = (0, _react.useState)(false);
173
+ const [isHovered, setIsHovered] = (0, _react.useState)(false);
174
+ const [uncontrolledValue, setUncontrolledValue] = (0, _react.useState)(defaultValue);
175
+ const isControlled = value !== undefined;
176
+ const currentValue = isControlled ? value : uncontrolledValue;
177
+ const visualState = isFocused ? 'Active' : jioPlus ? 'IdleJioPlus' : 'Idle';
178
+ const modes = (0, _react.useMemo)(() => ({
179
+ ...globalModes,
180
+ ...propModes,
181
+ [STATE_COLLECTION]: visualState
182
+ }), [globalModes, propModes, visualState]);
183
+ const sendButtonModes = (0, _react.useMemo)(() => ({
184
+ ...DEFAULT_SEND_BUTTON_MODES,
185
+ ...modes
186
+ }), [modes]);
187
+ const tokens = (0, _react.useMemo)(() => resolveHelloJioInputTokens(modes, visualState), [modes, visualState]);
188
+ const handleChangeText = (0, _react.useCallback)(text => {
189
+ if (!isControlled) setUncontrolledValue(text);
190
+ onChangeText?.(text);
191
+ }, [isControlled, onChangeText]);
192
+ const handleSubmit = (0, _react.useCallback)(() => {
193
+ if (disabled) return;
194
+ onSubmit?.(currentValue);
195
+ }, [disabled, onSubmit, currentValue]);
196
+ const handleFocus = (0, _react.useCallback)(e => {
197
+ setIsFocused(true);
198
+ onFocus?.(e);
199
+ }, [onFocus]);
200
+ const handleBlur = (0, _react.useCallback)(e => {
201
+ setIsFocused(false);
202
+ onBlur?.(e);
203
+ }, [onBlur]);
204
+ const leadingElement = leading ?? (leadingIconName ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_Icon.default, {
205
+ name: leadingIconName,
206
+ size: tokens.iconSize,
207
+ color: tokens.iconColor
208
+ }) : null);
209
+ const processedLeading = leadingElement ? (0, _reactUtils.cloneChildrenWithModes)(_react.default.Children.toArray(leadingElement), modes) : null;
210
+ const defaultTrailing = /*#__PURE__*/(0, _jsxRuntime.jsx)(_IconButton.default, {
211
+ iconName: sendIconName,
212
+ modes: sendButtonModes,
213
+ onPress: handleSubmit,
214
+ disabled: disabled,
215
+ accessibilityLabel: "Send"
216
+ });
217
+ const trailingElement = trailing === undefined ? defaultTrailing : trailing === null ? null : trailing;
218
+ const processedTrailing = trailingElement ? (0, _reactUtils.cloneChildrenWithModes)(_react.default.Children.toArray(trailingElement), modes) : null;
219
+ const a11yLabel = accessibilityLabel ?? (placeholder || 'HelloJio input');
220
+ const hoverStyle = isHovered && !disabled && !isFocused ? {
221
+ opacity: 0.95
222
+ } : {};
223
+ const containerStyleArray = [tokens.containerStyle, hoverStyle, disabled ? {
224
+ opacity: 0.5
225
+ } : null, style];
226
+ const glassOverlay = (0, _figmaVariablesResolver.getVariableByName)('helloJioInput/background', modes) ?? STATE_FALLBACKS.IdleJioPlus.background;
227
+ const inner = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
228
+ children: [tokens.useGlass ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_GlassFill.default, {
229
+ tint: "light",
230
+ intensity: tokens.blurIntensity,
231
+ overlayColor: glassOverlay,
232
+ androidTintWash: false
233
+ }) : null, processedLeading ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
234
+ accessibilityElementsHidden: true,
235
+ importantForAccessibility: "no",
236
+ style: SLOT_CENTER,
237
+ children: processedLeading
238
+ }) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
239
+ ref: (0, _reactUtils.mergeRefs)(inputRef, ref),
240
+ accessibilityLabel: a11yLabel,
241
+ accessibilityHint: accessibilityHint,
242
+ placeholder: isFocused ? '' : placeholder,
243
+ placeholderTextColor: tokens.inputStyle.color,
244
+ value: currentValue,
245
+ onChangeText: handleChangeText,
246
+ onFocus: handleFocus,
247
+ onBlur: handleBlur,
248
+ onSubmitEditing: handleSubmit,
249
+ returnKeyType: returnKeyType,
250
+ editable: !disabled,
251
+ style: [tokens.inputStyle, inputStyle],
252
+ testID: testID,
253
+ ...rest
254
+ }), processedTrailing ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View
255
+ // Keep the send button in the a11y tree — it has its own label.
256
+ , {
257
+ style: SLOT_CENTER,
258
+ children: processedTrailing
259
+ }) : null]
260
+ });
261
+
262
+ // Same Android focus rule as TextInput: never wrap native TextInput in
263
+ // Pressable on native (steals the first tap). Web keeps Pressable for
264
+ // hover + click-anywhere-to-focus.
265
+ if (IS_WEB) {
266
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
267
+ style: containerStyleArray,
268
+ onHoverIn: () => setIsHovered(true),
269
+ onHoverOut: () => setIsHovered(false),
270
+ onPress: () => {
271
+ if (!disabled) inputRef.current?.focus();
272
+ },
273
+ accessible: false,
274
+ disabled: disabled,
275
+ children: inner
276
+ });
277
+ }
278
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
279
+ style: containerStyleArray,
280
+ children: inner
281
+ });
282
+ });
283
+ const SLOT_CENTER = {
284
+ justifyContent: 'center',
285
+ alignItems: 'center'
286
+ };
287
+ var _default = exports.default = /*#__PURE__*/_react.default.memo(HelloJioInput);
@@ -61,6 +61,8 @@ function MoneyValue({
61
61
  negative,
62
62
  focused = false,
63
63
  hidden = false,
64
+ editable = false,
65
+ onValueChange,
64
66
  modes = _reactUtils.EMPTY_MODES,
65
67
  style,
66
68
  valueStyle,
@@ -72,24 +74,51 @@ function MoneyValue({
72
74
  ...rest
73
75
  }) {
74
76
  // Auto-detect negative from value and compute display value
75
- const {
76
- isNegative,
77
- displayValue
78
- } = (0, _react.useMemo)(() => {
79
- const stringValue = String(value);
80
- const trimmed = stringValue.trim();
81
- const valueIsNegative = trimmed.startsWith('-') || typeof value === 'number' && value < 0;
77
+ const stringValue = String(value);
78
+ const trimmed = stringValue.trim();
79
+ const valueIsNegative = trimmed.startsWith('-') || typeof value === 'number' && value < 0;
82
80
 
83
- // Strip leading minus sign for display (we show it separately)
84
- const absoluteValue = trimmed.startsWith('-') ? trimmed.slice(1) : trimmed;
81
+ // Strip leading minus sign for display (we show it separately)
82
+ const absoluteValue = trimmed.startsWith('-') ? trimmed.slice(1) : trimmed;
85
83
 
86
- // Use explicit negative prop if provided, otherwise use auto-detected
87
- const showNegative = negative !== undefined ? negative : valueIsNegative;
88
- return {
89
- isNegative: hidden ? false : showNegative,
90
- displayValue: hidden ? '•••' : absoluteValue
91
- };
92
- }, [value, negative, hidden]);
84
+ // Use explicit negative prop if provided, otherwise use auto-detected
85
+ const showNegative = negative !== undefined ? negative : valueIsNegative;
86
+ const isNegative = hidden ? false : showNegative;
87
+ const displayValue = hidden ? '•••' : absoluteValue;
88
+
89
+ // Inline editing state
90
+ const [isEditing, setIsEditing] = (0, _react.useState)(false);
91
+ const [editBuffer, setEditBuffer] = (0, _react.useState)('');
92
+ const inputRef = (0, _react.useRef)(null);
93
+ // Always-current snapshot of the display value for callbacks to reference.
94
+ const displayValueRef = (0, _react.useRef)(displayValue);
95
+ displayValueRef.current = displayValue;
96
+ const enterEditMode = (0, _react.useCallback)(() => {
97
+ setIsEditing(true);
98
+ setEditBuffer(displayValueRef.current);
99
+ // Focus the input on next frame so the keyboard opens
100
+ setTimeout(() => inputRef.current?.focus(), 0);
101
+ }, []);
102
+ const commitEdit = (0, _react.useCallback)(() => {
103
+ setIsEditing(false);
104
+ const resolved = editBuffer || '0';
105
+ if (resolved !== displayValueRef.current) {
106
+ onValueChange?.(resolved);
107
+ }
108
+ }, [editBuffer, onValueChange]);
109
+ const handleChangeText = (0, _react.useCallback)(text => {
110
+ // Allow only digits and at most one decimal point
111
+ const filtered = text.replace(/[^0-9.]/g, '');
112
+ const parts = filtered.split('.');
113
+ const clean = parts.length > 2 ? parts[0] + '.' + parts.slice(1).join('') : filtered;
114
+ setEditBuffer(clean);
115
+ }, []);
116
+ const handlePress = (0, _react.useCallback)(event => {
117
+ if (editable) {
118
+ enterEditMode();
119
+ }
120
+ onPress?.(event);
121
+ }, [editable, enterEditMode, onPress]);
93
122
 
94
123
  // Resolve typography and layout tokens from Figma
95
124
  const textColor = (0, _figmaVariablesResolver.getVariableByName)('moneyValue/text/color', modes);
@@ -150,44 +179,140 @@ function MoneyValue({
150
179
  cursorOpacity.setValue(0);
151
180
  }
152
181
  }, [focused, cursorOpacity]);
153
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
154
- style: [containerStyle, style],
155
- accessibilityRole: "text",
156
- accessibilityLabel: undefined,
157
- accessibilityHint: accessibilityHint,
158
- onPress: onPress,
159
- disabled: !onPress,
160
- ...rest,
161
- children: [isNegative && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
162
- style: [currencyTextStyle, negativeSignStyle],
163
- accessibilityElementsHidden: true,
164
- importantForAccessibility: "no",
165
- children: "-"
166
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
167
- style: [currencyTextStyle, currencyStyle],
168
- accessibilityElementsHidden: true,
169
- importantForAccessibility: "no",
170
- children: resolvedCurrency
171
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
172
- style: {
173
- flexDirection: 'row',
174
- alignItems: 'center'
175
- },
176
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
177
- style: [valueTextStyle, valueStyle],
178
- accessibilityElementsHidden: true,
179
- importantForAccessibility: "no",
180
- children: displayValue
181
- }), focused && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, {
182
- style: {
183
- opacity: cursorOpacity,
184
- width: 2,
185
- height: valueFontSize * 1.1,
186
- backgroundColor: textColor,
187
- marginLeft: 2
188
- }
189
- })]
190
- })]
182
+
183
+ // Keyboard avoidance — lift the component when the keyboard would cover it.
184
+ // Follows the same pattern as ActionFooter (uses Keyboard.addListener).
185
+ // Resets when editing ends or the keyboard hides.
186
+ const rootRef = (0, _react.useRef)(null);
187
+ const keyboardLift = (0, _react.useRef)(new _reactNative.Animated.Value(0)).current;
188
+ const rootWindowYRef = (0, _react.useRef)(0);
189
+ const rootMeasuredHeightRef = (0, _react.useRef)(0);
190
+ const measureRoot = (0, _react.useCallback)(() => {
191
+ rootRef.current?.measureInWindow((_x, y, _w, h) => {
192
+ rootWindowYRef.current = y;
193
+ rootMeasuredHeightRef.current = h;
194
+ });
195
+ }, []);
196
+ const handleRootLayout = (0, _react.useCallback)(_e => {
197
+ // measureInWindow gives the component's position relative to the window,
198
+ // which is what we need for keyboard overlap calculation. onLayout fires
199
+ // after the component mounts and whenever its size changes.
200
+ measureRoot();
201
+ }, [measureRoot]);
202
+ (0, _react.useEffect)(() => {
203
+ if (!editable) return;
204
+ const showSub = _reactNative.Keyboard.addListener('keyboardDidShow', e => {
205
+ measureRoot();
206
+ if (!isEditing) return;
207
+ const kbHeight = e.endCoordinates.height;
208
+ const {
209
+ height: screenH
210
+ } = require('react-native').Dimensions.get('window');
211
+ const componentBottom = rootWindowYRef.current + rootMeasuredHeightRef.current;
212
+ const visibleBottom = screenH - kbHeight;
213
+ const overlap = componentBottom - visibleBottom + 12;
214
+ if (overlap > 0) {
215
+ _reactNative.Animated.timing(keyboardLift, {
216
+ toValue: -overlap,
217
+ duration: e.duration ?? 250,
218
+ useNativeDriver: true
219
+ }).start();
220
+ }
221
+ });
222
+ const hideSub = _reactNative.Keyboard.addListener('keyboardDidHide', () => {
223
+ _reactNative.Animated.timing(keyboardLift, {
224
+ toValue: 0,
225
+ duration: 150,
226
+ useNativeDriver: true
227
+ }).start();
228
+ });
229
+ return () => {
230
+ showSub.remove();
231
+ hideSub.remove();
232
+ };
233
+ }, [editable, isEditing, keyboardLift, measureRoot]);
234
+
235
+ // Reset lift when editing ends
236
+ (0, _react.useEffect)(() => {
237
+ if (!isEditing) {
238
+ keyboardLift.setValue(0);
239
+ }
240
+ }, [isEditing, keyboardLift]);
241
+ const handleBlur = (0, _react.useCallback)(() => {
242
+ commitEdit();
243
+ }, [commitEdit]);
244
+ const handleSubmitEditing = (0, _react.useCallback)(() => {
245
+ commitEdit();
246
+ }, [commitEdit]);
247
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, {
248
+ style: {
249
+ transform: [{
250
+ translateY: keyboardLift
251
+ }]
252
+ },
253
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
254
+ ref: rootRef,
255
+ onLayout: handleRootLayout,
256
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
257
+ style: [containerStyle, style],
258
+ accessibilityRole: "text",
259
+ accessibilityLabel: undefined,
260
+ accessibilityHint: accessibilityHint,
261
+ onPress: handlePress,
262
+ disabled: !onPress && !editable,
263
+ ...rest,
264
+ children: [isNegative && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
265
+ style: [currencyTextStyle, negativeSignStyle],
266
+ accessibilityElementsHidden: true,
267
+ importantForAccessibility: "no",
268
+ children: "-"
269
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
270
+ style: [currencyTextStyle, currencyStyle],
271
+ accessibilityElementsHidden: true,
272
+ importantForAccessibility: "no",
273
+ children: resolvedCurrency
274
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
275
+ style: {
276
+ flexDirection: 'row',
277
+ alignItems: 'center'
278
+ },
279
+ children: isEditing ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
280
+ ref: inputRef,
281
+ value: editBuffer,
282
+ onChangeText: handleChangeText,
283
+ onBlur: handleBlur,
284
+ onSubmitEditing: handleSubmitEditing,
285
+ keyboardType: "decimal-pad",
286
+ returnKeyType: "done",
287
+ selectTextOnFocus: true,
288
+ style: [valueTextStyle, valueStyle, {
289
+ padding: 0,
290
+ margin: 0,
291
+ // On web a thin border helps the cursor appear precisely
292
+ ...(_reactNative.Platform.OS === 'web' ? {
293
+ outlineStyle: 'none'
294
+ } : {})
295
+ }],
296
+ accessibilityLabel: accessibilityLabel || 'Edit value'
297
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
298
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
299
+ style: [valueTextStyle, valueStyle],
300
+ accessibilityElementsHidden: true,
301
+ importantForAccessibility: "no",
302
+ children: displayValue
303
+ }), focused && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, {
304
+ style: {
305
+ opacity: cursorOpacity,
306
+ width: 2,
307
+ height: valueFontSize * 1.1,
308
+ backgroundColor: textColor,
309
+ marginLeft: 2
310
+ }
311
+ })]
312
+ })
313
+ })]
314
+ })
315
+ })
191
316
  });
192
317
  }
193
318
  var _default = exports.default = MoneyValue;
@@ -45,6 +45,9 @@ function PdpCcCard({
45
45
  media,
46
46
  title = 'Title',
47
47
  subtitle = 'Subtitle',
48
+ numberOfLines,
49
+ disableTruncation,
50
+ singleLine,
48
51
  metrics = DEFAULT_METRICS,
49
52
  buttonLabel = 'button',
50
53
  buttonIcon = 'ic_add_circle',
@@ -96,7 +99,10 @@ function PdpCcCard({
96
99
  title: title,
97
100
  subtitle: subtitle,
98
101
  textAlign: "Center",
99
- modes: modes
102
+ modes: modes,
103
+ numberOfLines: numberOfLines,
104
+ disableTruncation: disableTruncation,
105
+ singleLine: singleLine
100
106
  }), metrics.length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
101
107
  style: styles.metricsRow,
102
108
  children: metrics.map((metric, index) => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_react.default.Fragment, {
@@ -9,12 +9,22 @@ exports.TableHeader = TableHeader;
9
9
  exports.TableHeaderCell = TableHeaderCell;
10
10
  exports.TableRow = TableRow;
11
11
  exports.default = void 0;
12
- var _react = _interopRequireDefault(require("react"));
12
+ var _react = _interopRequireWildcard(require("react"));
13
13
  var _reactNative = require("react-native");
14
14
  var _figmaVariablesResolver = require("../../design-tokens/figma-variables-resolver");
15
15
  var _reactUtils = require("../../utils/react-utils");
16
16
  var _jsxRuntime = require("react/jsx-runtime");
17
- 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
+ /**
19
+ * Carries the root `Table`'s `columnWidth` down to every cell so that, when it
20
+ * is set, all columns (header + body, data-driven or composed) share the same
21
+ * fixed width without each cell having to repeat the prop. Cells still honour
22
+ * an explicit `width` prop over this context value. `undefined` (the default,
23
+ * when no `Table` ancestor is present, or `columnWidth` is unset) leaves cells
24
+ * to flex — preserving the original behaviour.
25
+ */
26
+ const TableContext = /*#__PURE__*/(0, _react.createContext)(undefined);
27
+
18
28
  /* -------------------------------------------------------------------------------------------------
19
29
  * Shared types & token resolution
20
30
  * -----------------------------------------------------------------------------------------------*/
@@ -98,6 +108,10 @@ function TableCell({
98
108
  style
99
109
  }) {
100
110
  const t = resolveTableTokens(modes);
111
+ // A cell's width resolves in priority order: an explicit `width` prop, then
112
+ // the root Table's `columnWidth` (via context), then fall back to flex.
113
+ const ctxColumnWidth = (0, _react.useContext)(TableContext);
114
+ const effectiveWidth = width ?? ctxColumnWidth;
101
115
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
102
116
  style: [{
103
117
  backgroundColor: t.cellBg,
@@ -109,8 +123,8 @@ function TableCell({
109
123
  borderColor: t.borderColor,
110
124
  borderBottomWidth: isLastRow ? 0 : t.borderSize,
111
125
  borderRightWidth: isLastColumn ? 0 : t.borderSize,
112
- ...(width != null ? {
113
- width
126
+ ...(effectiveWidth != null ? {
127
+ width: effectiveWidth
114
128
  } : {
115
129
  flex,
116
130
  minWidth: 0
@@ -138,6 +152,8 @@ function TableHeaderCell({
138
152
  style
139
153
  }) {
140
154
  const t = resolveTableTokens(modes);
155
+ const ctxColumnWidth = (0, _react.useContext)(TableContext);
156
+ const effectiveWidth = width ?? ctxColumnWidth;
141
157
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
142
158
  style: [{
143
159
  backgroundColor: t.headerBg,
@@ -146,8 +162,8 @@ function TableHeaderCell({
146
162
  justifyContent: alignToJustify(align),
147
163
  paddingHorizontal: t.headerPaddingH,
148
164
  paddingVertical: t.headerPaddingV,
149
- ...(width != null ? {
150
- width
165
+ ...(effectiveWidth != null ? {
166
+ width: effectiveWidth
151
167
  } : {
152
168
  flex,
153
169
  minWidth: 0
@@ -262,6 +278,7 @@ function Table({
262
278
  getRowKey,
263
279
  onRowPress,
264
280
  scrollable = false,
281
+ columnWidth,
265
282
  accessibilityLabel,
266
283
  modes = _reactUtils.EMPTY_MODES,
267
284
  style
@@ -305,7 +322,10 @@ function Table({
305
322
  }, !scrollable && {
306
323
  width: '100%'
307
324
  }, style],
308
- children: body
325
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(TableContext.Provider, {
326
+ value: columnWidth,
327
+ children: body
328
+ })
309
329
  });
310
330
  if (scrollable) {
311
331
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {