jfs-components 0.1.25 → 0.1.30

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 (97) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/lib/commonjs/components/AutoplayControl/AutoplayControl.js +66 -0
  3. package/lib/commonjs/components/Carousel/Carousel.js +112 -19
  4. package/lib/commonjs/components/CompareTable/CompareTable.js +191 -25
  5. package/lib/commonjs/components/ContentSheet/ContentSheet.js +104 -10
  6. package/lib/commonjs/components/FormUpload/Additem.js +168 -0
  7. package/lib/commonjs/components/FormUpload/FormUpload.js +198 -0
  8. package/lib/commonjs/components/Grid/Grid.js +206 -0
  9. package/lib/commonjs/components/MoneyValue/MoneyValue.js +179 -54
  10. package/lib/commonjs/components/NumberPagination/NumberPagination.js +144 -0
  11. package/lib/commonjs/components/PdpCcCard/PdpCcCard.js +7 -1
  12. package/lib/commonjs/components/ProfileCard/ProfileCard.js +208 -0
  13. package/lib/commonjs/components/Table/Table.js +27 -7
  14. package/lib/commonjs/components/UpiHandle/UpiHandle.js +23 -11
  15. package/lib/commonjs/components/index.js +42 -0
  16. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
  17. package/lib/commonjs/design-tokens/figma-modes.generated.js +21 -10
  18. package/lib/commonjs/icons/components/IconBrokenimage.js +19 -0
  19. package/lib/commonjs/icons/components/IconImage.js +19 -0
  20. package/lib/commonjs/icons/components/IconMic.js +19 -0
  21. package/lib/commonjs/icons/components/IconMicoff.js +19 -0
  22. package/lib/commonjs/icons/components/IconPause.js +19 -0
  23. package/lib/commonjs/icons/components/IconPlay.js +19 -0
  24. package/lib/commonjs/icons/components/index.js +66 -0
  25. package/lib/commonjs/icons/registry.js +2 -2
  26. package/lib/module/components/AutoplayControl/AutoplayControl.js +60 -0
  27. package/lib/module/components/Carousel/Carousel.js +111 -19
  28. package/lib/module/components/CompareTable/CompareTable.js +191 -26
  29. package/lib/module/components/ContentSheet/ContentSheet.js +108 -14
  30. package/lib/module/components/FormUpload/Additem.js +162 -0
  31. package/lib/module/components/FormUpload/FormUpload.js +192 -0
  32. package/lib/module/components/Grid/Grid.js +201 -0
  33. package/lib/module/components/MoneyValue/MoneyValue.js +182 -57
  34. package/lib/module/components/NumberPagination/NumberPagination.js +138 -0
  35. package/lib/module/components/PdpCcCard/PdpCcCard.js +7 -1
  36. package/lib/module/components/ProfileCard/ProfileCard.js +202 -0
  37. package/lib/module/components/Table/Table.js +27 -7
  38. package/lib/module/components/UpiHandle/UpiHandle.js +23 -11
  39. package/lib/module/components/index.js +7 -1
  40. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
  41. package/lib/module/design-tokens/figma-modes.generated.js +21 -10
  42. package/lib/module/icons/components/IconBrokenimage.js +12 -0
  43. package/lib/module/icons/components/IconImage.js +12 -0
  44. package/lib/module/icons/components/IconMic.js +12 -0
  45. package/lib/module/icons/components/IconMicoff.js +12 -0
  46. package/lib/module/icons/components/IconPause.js +12 -0
  47. package/lib/module/icons/components/IconPlay.js +12 -0
  48. package/lib/module/icons/components/index.js +6 -0
  49. package/lib/module/icons/registry.js +2 -2
  50. package/lib/typescript/src/components/AutoplayControl/AutoplayControl.d.ts +15 -0
  51. package/lib/typescript/src/components/Carousel/Carousel.d.ts +8 -1
  52. package/lib/typescript/src/components/CompareTable/CompareTable.d.ts +21 -1
  53. package/lib/typescript/src/components/ContentSheet/ContentSheet.d.ts +20 -1
  54. package/lib/typescript/src/components/FormUpload/Additem.d.ts +96 -0
  55. package/lib/typescript/src/components/FormUpload/FormUpload.d.ts +85 -0
  56. package/lib/typescript/src/components/Grid/Grid.d.ts +87 -0
  57. package/lib/typescript/src/components/MoneyValue/MoneyValue.d.ts +10 -1
  58. package/lib/typescript/src/components/NumberPagination/NumberPagination.d.ts +21 -0
  59. package/lib/typescript/src/components/PdpCcCard/PdpCcCard.d.ts +25 -1
  60. package/lib/typescript/src/components/ProfileCard/ProfileCard.d.ts +89 -0
  61. package/lib/typescript/src/components/Table/Table.d.ts +9 -1
  62. package/lib/typescript/src/components/UpiHandle/UpiHandle.d.ts +7 -1
  63. package/lib/typescript/src/components/index.d.ts +34 -28
  64. package/lib/typescript/src/design-tokens/figma-modes.generated.d.ts +10 -4
  65. package/lib/typescript/src/icons/components/IconBrokenimage.d.ts +3 -0
  66. package/lib/typescript/src/icons/components/IconImage.d.ts +3 -0
  67. package/lib/typescript/src/icons/components/IconMic.d.ts +3 -0
  68. package/lib/typescript/src/icons/components/IconMicoff.d.ts +3 -0
  69. package/lib/typescript/src/icons/components/IconPause.d.ts +3 -0
  70. package/lib/typescript/src/icons/components/IconPlay.d.ts +3 -0
  71. package/lib/typescript/src/icons/components/index.d.ts +6 -0
  72. package/lib/typescript/src/icons/registry.d.ts +1 -1
  73. package/package.json +1 -1
  74. package/src/components/AutoplayControl/AutoplayControl.tsx +77 -0
  75. package/src/components/Carousel/Carousel.tsx +133 -23
  76. package/src/components/CompareTable/CompareTable.tsx +224 -30
  77. package/src/components/ContentSheet/ContentSheet.tsx +138 -8
  78. package/src/components/FormUpload/Additem.tsx +262 -0
  79. package/src/components/FormUpload/FormUpload.tsx +313 -0
  80. package/src/components/Grid/Grid.tsx +368 -0
  81. package/src/components/MoneyValue/MoneyValue.tsx +216 -65
  82. package/src/components/NumberPagination/NumberPagination.tsx +170 -0
  83. package/src/components/PdpCcCard/PdpCcCard.tsx +36 -1
  84. package/src/components/ProfileCard/ProfileCard.tsx +268 -0
  85. package/src/components/Table/Table.tsx +29 -4
  86. package/src/components/UpiHandle/UpiHandle.tsx +35 -24
  87. package/src/components/index.ts +329 -182
  88. package/src/design-tokens/Coin Variables-variables-full.json +1 -1
  89. package/src/design-tokens/figma-modes.generated.ts +21 -10
  90. package/src/icons/components/IconBrokenimage.tsx +11 -0
  91. package/src/icons/components/IconImage.tsx +11 -0
  92. package/src/icons/components/IconMic.tsx +11 -0
  93. package/src/icons/components/IconMicoff.tsx +11 -0
  94. package/src/icons/components/IconPause.tsx +11 -0
  95. package/src/icons/components/IconPlay.tsx +11 -0
  96. package/src/icons/components/index.ts +6 -0
  97. package/src/icons/registry.ts +25 -1
@@ -13,6 +13,18 @@ var _reactUtils = require("../../utils/react-utils");
13
13
  var _jsxRuntime = require("react/jsx-runtime");
14
14
  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); }
15
15
  const IS_WEB = _reactNative.Platform.OS === 'web';
16
+
17
+ // Spring config — mirrors the Drawer's bottom pop-up spring so the two
18
+ // bottom-sheet surfaces share the exact same entrance feel (slight bounce,
19
+ // snappy). Keep in sync with `Drawer.tsx` SPRING_CONFIG.
20
+ const SPRING_CONFIG = {
21
+ damping: 32,
22
+ stiffness: 300,
23
+ mass: 1,
24
+ overshootClamping: false,
25
+ restDisplacementThreshold: 0.1,
26
+ restSpeedThreshold: 0.1
27
+ };
16
28
  function resolveContentSheetStyle(modes) {
17
29
  const backgroundColor = (0, _figmaVariablesResolver.getVariableByName)('contentSheet/bg', modes);
18
30
  const gap = (0, _figmaVariablesResolver.getVariableByName)('contentSheet/gap', modes);
@@ -21,6 +33,18 @@ function resolveContentSheetStyle(modes) {
21
33
  const paddingBottom = (0, _figmaVariablesResolver.getVariableByName)('contentSheet/padding/bottom', modes);
22
34
  const radiusTop = (0, _figmaVariablesResolver.getVariableByName)('contentSheet/padding/radius/top', modes);
23
35
  const radiusBottom = (0, _figmaVariablesResolver.getVariableByName)('contentSheet/padding/radius/bottom', modes);
36
+
37
+ // Title tokens mirror the Drawer title so the two bottom-sheet surfaces stay
38
+ // visually aligned.
39
+ const titleColor = (0, _figmaVariablesResolver.getVariableByName)('drawer/title/color', modes);
40
+ const titleSize = (0, _figmaVariablesResolver.getVariableByName)('drawer/title/fontSize', modes);
41
+ const titleLineHeight = (0, _figmaVariablesResolver.getVariableByName)('drawer/title/lineHeight', modes);
42
+ const titleFontFamily = (0, _figmaVariablesResolver.getVariableByName)('drawer/title/fontFamily', modes);
43
+ const titleFontWeightRaw = (0, _figmaVariablesResolver.getVariableByName)('drawer/title/fontWeight', modes);
44
+ const titleFontWeight = typeof titleFontWeightRaw === 'number' ? titleFontWeightRaw.toString() : titleFontWeightRaw;
45
+ const titleWrapPaddingTop = (0, _figmaVariablesResolver.getVariableByName)('drawer/titleWrap/padding/top', modes);
46
+ const titleWrapPaddingBottom = (0, _figmaVariablesResolver.getVariableByName)('drawer/titleWrap/padding/bottom', modes);
47
+ const titleWrapPaddingHorizontal = (0, _figmaVariablesResolver.getVariableByName)('drawer/titleWrap/padding/horizontal', modes);
24
48
  return {
25
49
  container: {
26
50
  backgroundColor,
@@ -36,7 +60,21 @@ function resolveContentSheetStyle(modes) {
36
60
  alignItems: 'stretch',
37
61
  overflow: 'hidden'
38
62
  },
39
- paddingBottom
63
+ paddingBottom,
64
+ headerStyle: {
65
+ paddingTop: titleWrapPaddingTop,
66
+ paddingBottom: titleWrapPaddingBottom,
67
+ paddingHorizontal: titleWrapPaddingHorizontal,
68
+ alignItems: 'center'
69
+ },
70
+ titleStyle: {
71
+ color: titleColor,
72
+ fontSize: titleSize,
73
+ lineHeight: titleLineHeight,
74
+ fontFamily: titleFontFamily,
75
+ fontWeight: titleFontWeight,
76
+ textAlign: 'center'
77
+ }
40
78
  };
41
79
  }
42
80
  const pinnedStyle = {
@@ -65,10 +103,12 @@ const pinnedStyle = {
65
103
  function ContentSheet({
66
104
  children,
67
105
  modes = _reactUtils.EMPTY_MODES,
106
+ title,
68
107
  avoidKeyboard = true,
69
108
  keyboardSpacing = 0,
70
109
  safeAreaBottom = true,
71
110
  pinToBottom = true,
111
+ visible = true,
72
112
  style,
73
113
  ...rest
74
114
  }) {
@@ -84,46 +124,100 @@ function ContentSheet({
84
124
  paddingBottom
85
125
  }, style], [pinToBottom, resolved.container, paddingBottom, style]);
86
126
 
127
+ // Entrance / exit "pop up from bottom" animation — mirrors the Drawer. The
128
+ // sheet is bottom-anchored, so a positive `translateY` equal to the window
129
+ // height parks it fully below the bottom edge. Springing it back to `0`
130
+ // produces the same bottom pop-up the Drawer uses. The shared value is
131
+ // initialised off-screen so the very first mount pops in (no flash).
132
+ const {
133
+ height: windowHeight
134
+ } = (0, _reactNative.useWindowDimensions)();
135
+ const entrance = (0, _reactNativeReanimated.useSharedValue)(windowHeight);
136
+ (0, _react.useEffect)(() => {
137
+ entrance.value = (0, _reactNativeReanimated.withSpring)(visible ? 0 : windowHeight, SPRING_CONFIG);
138
+ }, [visible, windowHeight, entrance]);
139
+
87
140
  // Switching between the keyboard-aware and plain implementation is keyed off
88
141
  // `avoidKeyboard` (and platform). Because they are distinct component types,
89
142
  // React remounts on toggle, so the conditional `useAnimatedKeyboard` hook
90
- // inside `KeyboardAwareSheet` always runs in a stable hook order.
143
+ // inside `KeyboardAwareSheet` always runs in a stable hook order. Both
144
+ // branches receive the `entrance` shared value so the bottom pop-up plays in
145
+ // every configuration.
146
+ const header = title ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
147
+ style: resolved.headerStyle,
148
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
149
+ style: resolved.titleStyle,
150
+ children: title
151
+ })
152
+ }) : null;
91
153
  if (avoidKeyboard && !IS_WEB) {
92
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(KeyboardAwareSheet, {
154
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(KeyboardAwareSheet, {
93
155
  style: containerStyle,
94
156
  spacing: keyboardSpacing,
157
+ entrance: entrance,
95
158
  ...rest,
96
- children: processedChildren
159
+ children: [header, processedChildren]
97
160
  });
98
161
  }
99
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
162
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(AnimatedSheet, {
100
163
  style: containerStyle,
164
+ entrance: entrance,
101
165
  ...rest,
102
- children: processedChildren
166
+ children: [header, processedChildren]
103
167
  });
104
168
  }
105
169
  /**
106
170
  * Native-only wrapper that lifts the sheet by the live keyboard height. The
107
171
  * translation is computed on the UI thread from `useAnimatedKeyboard`, so the
108
- * sheet tracks the keyboard frame-for-frame without any JS work.
172
+ * sheet tracks the keyboard frame-for-frame without any JS work. The keyboard
173
+ * offset is summed with the entrance `translateY` (the bottom pop-up) inside a
174
+ * single `useAnimatedStyle` so both motions stay on the UI thread and never
175
+ * fight each other.
109
176
  */
110
177
  function KeyboardAwareSheet({
111
178
  style,
112
179
  spacing,
180
+ entrance,
113
181
  children,
114
182
  ...rest
115
183
  }) {
116
184
  const keyboard = (0, _reactNativeReanimated.useAnimatedKeyboard)();
117
- const keyboardStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => {
185
+ const animatedStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => {
118
186
  const height = keyboard.height.value;
187
+ const keyboardOffset = height > 0 ? -(height + spacing) : 0;
188
+ return {
189
+ transform: [{
190
+ translateY: entrance.value + keyboardOffset
191
+ }]
192
+ };
193
+ });
194
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeReanimated.default.View, {
195
+ style: [style, animatedStyle],
196
+ ...rest,
197
+ children: children
198
+ });
199
+ }
200
+
201
+ /**
202
+ * Entrance-only animated sheet (web, or when keyboard avoidance is disabled).
203
+ * Applies the same bottom pop-up `translateY` as `KeyboardAwareSheet`, minus
204
+ * the keyboard offset.
205
+ */
206
+ function AnimatedSheet({
207
+ style,
208
+ entrance,
209
+ children,
210
+ ...rest
211
+ }) {
212
+ const animatedStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => {
119
213
  return {
120
214
  transform: [{
121
- translateY: height > 0 ? -(height + spacing) : 0
215
+ translateY: entrance.value
122
216
  }]
123
217
  };
124
218
  });
125
219
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeReanimated.default.View, {
126
- style: [style, keyboardStyle],
220
+ style: [style, animatedStyle],
127
221
  ...rest,
128
222
  children: children
129
223
  });
@@ -0,0 +1,168 @@
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 _reactUtils = require("../../utils/react-utils");
12
+ var _Image = _interopRequireDefault(require("../Image/Image"));
13
+ var _IconCapsule = _interopRequireDefault(require("../IconCapsule/IconCapsule"));
14
+ var _jsxRuntime = require("react/jsx-runtime");
15
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
16
+ 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); }
17
+ /**
18
+ * Callback that opens a platform picker and resolves with the selected assets.
19
+ * This is the integration point for libraries like `react-native-image-picker`,
20
+ * `expo-image-picker`, or `react-native-document-picker`.
21
+ */
22
+
23
+ function toNumber(value, fallback) {
24
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
25
+ if (typeof value === 'string') {
26
+ const parsed = parseFloat(value);
27
+ if (Number.isFinite(parsed)) return parsed;
28
+ }
29
+ return fallback;
30
+ }
31
+ function resolveAdditemTokens(modes) {
32
+ const backgroundColor = (0, _figmaVariablesResolver.getVariableByName)('compareCardItem/background', modes);
33
+ const borderRadius = toNumber((0, _figmaVariablesResolver.getVariableByName)('compareCardItem/radius', modes), 8);
34
+ const paddingHorizontal = toNumber((0, _figmaVariablesResolver.getVariableByName)('compareCardItem/padding/horizontal', modes), 6);
35
+ const paddingVertical = toNumber((0, _figmaVariablesResolver.getVariableByName)('compareCardItem/padding/vertical', modes), 8);
36
+ // The Figma design uses a fixed 44 × 44 dp cell. There is no
37
+ // `compareCardItem/size` token in the variables JSON, so we keep the size
38
+ // constant to match the design exactly.
39
+ const size = 44;
40
+ return {
41
+ backgroundColor,
42
+ borderRadius,
43
+ paddingHorizontal,
44
+ paddingVertical,
45
+ size
46
+ };
47
+ }
48
+
49
+ /** Figma defaults for the inner `IconCapsule` icons. Overridable via `modes`. */
50
+ const ADDITEM_DEFAULT_MODES = Object.freeze({
51
+ AppearanceBrand: 'Neutral',
52
+ Emphasis: 'Low',
53
+ 'Icon Capsule Size': 'S'
54
+ });
55
+
56
+ /**
57
+ * `Additem` — a small, reusable upload/preview cell used inside form uploaders.
58
+ *
59
+ * In its default `'empty'` state it shows a centered add/upload icon. In
60
+ * `'preview'` state it renders a thumbnail with a removable overlay. The
61
+ * component is intentionally unopinionated about *how* files are picked: pass a
62
+ * `picker` callback that wraps `react-native-image-picker`,
63
+ * `expo-image-picker`, `react-native-document-picker`, or any custom selector,
64
+ * and the cell will invoke it, surface the result via `onAssetsPicked`, and
65
+ * handle cancellation/errors gracefully.
66
+ */
67
+ function Additem({
68
+ state = 'empty',
69
+ imageSource,
70
+ onPress,
71
+ onRemove,
72
+ picker,
73
+ onAssetsPicked,
74
+ modes: propModes = _reactUtils.EMPTY_MODES,
75
+ accessibilityLabel,
76
+ isDisabled = false,
77
+ testID
78
+ }) {
79
+ const {
80
+ modes: globalModes
81
+ } = (0, _JFSThemeProvider.useTokens)();
82
+ const modes = (0, _react.useMemo)(() => globalModes === _reactUtils.EMPTY_MODES && propModes === _reactUtils.EMPTY_MODES ? ADDITEM_DEFAULT_MODES : {
83
+ ...ADDITEM_DEFAULT_MODES,
84
+ ...globalModes,
85
+ ...propModes
86
+ }, [globalModes, propModes]);
87
+ const tokens = (0, _react.useMemo)(() => resolveAdditemTokens(modes), [modes]);
88
+ const handlePress = _react.default.useCallback(async () => {
89
+ if (isDisabled) return;
90
+ if (state === 'empty' && picker) {
91
+ try {
92
+ const result = await picker();
93
+ if (result.didCancel) return;
94
+ if (result.error) {
95
+ if (__DEV__) {
96
+ console.warn('[Additem] picker error:', result.error);
97
+ }
98
+ return;
99
+ }
100
+ if (result.assets.length > 0) {
101
+ onAssetsPicked?.(result.assets);
102
+ }
103
+ } catch (err) {
104
+ if (__DEV__) {
105
+ console.warn('[Additem] unexpected picker error:', err);
106
+ }
107
+ }
108
+ return;
109
+ }
110
+ onPress?.();
111
+ }, [isDisabled, onPress, onAssetsPicked, picker, state]);
112
+ const handleRemove = _react.default.useCallback(() => {
113
+ if (!isDisabled) {
114
+ onRemove?.();
115
+ }
116
+ }, [isDisabled, onRemove]);
117
+ const resolvedAccessibilityLabel = accessibilityLabel ?? (state === 'empty' ? 'Add attachment' : 'Attachment preview');
118
+ const containerStyle = (0, _react.useMemo)(() => ({
119
+ width: tokens.size,
120
+ height: tokens.size,
121
+ borderRadius: tokens.borderRadius,
122
+ backgroundColor: tokens.backgroundColor,
123
+ paddingHorizontal: tokens.paddingHorizontal,
124
+ paddingVertical: tokens.paddingVertical,
125
+ alignItems: 'center',
126
+ justifyContent: 'center',
127
+ overflow: 'hidden',
128
+ opacity: isDisabled ? 0.5 : 1
129
+ }), [tokens, isDisabled]);
130
+ const isPreview = state === 'preview';
131
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
132
+ onPress: handlePress,
133
+ disabled: isDisabled,
134
+ accessibilityRole: "button",
135
+ accessibilityLabel: resolvedAccessibilityLabel,
136
+ accessibilityState: {
137
+ disabled: isDisabled
138
+ },
139
+ testID: testID,
140
+ style: containerStyle,
141
+ children: isPreview ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
142
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_Image.default, {
143
+ imageSource: imageSource,
144
+ width: "100%",
145
+ height: tokens.size - tokens.paddingVertical * 2,
146
+ resizeMode: "cover",
147
+ borderRadius: tokens.borderRadius - 2
148
+ }), onRemove != null && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
149
+ style: {
150
+ position: 'absolute',
151
+ top: 2,
152
+ right: 2
153
+ },
154
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_IconCapsule.default, {
155
+ iconName: "ic_close",
156
+ modes: modes,
157
+ onTouchEnd: handleRemove,
158
+ accessibilityRole: "button",
159
+ accessibilityLabel: "Remove attachment"
160
+ })
161
+ })]
162
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_IconCapsule.default, {
163
+ iconName: "ic_add",
164
+ modes: modes
165
+ })
166
+ });
167
+ }
168
+ var _default = exports.default = /*#__PURE__*/_react.default.memo(Additem);
@@ -0,0 +1,198 @@
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 _reactUtils = require("../../utils/react-utils");
12
+ var _Form = require("../Form/Form");
13
+ var _SupportText = _interopRequireDefault(require("../SupportText/SupportText"));
14
+ var _Additem = _interopRequireDefault(require("./Additem"));
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
+ function toNumber(value, fallback) {
19
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
20
+ if (typeof value === 'string') {
21
+ const parsed = parseFloat(value);
22
+ if (Number.isFinite(parsed)) return parsed;
23
+ }
24
+ return fallback;
25
+ }
26
+ function toFontWeight(value, fallback) {
27
+ if (typeof value === 'number') return value.toString();
28
+ if (typeof value === 'string' && value.length > 0) return value;
29
+ return fallback;
30
+ }
31
+ function resolveFormUploadTokens(modes) {
32
+ const gap = toNumber((0, _figmaVariablesResolver.getVariableByName)('formField/input/gap', modes), 8);
33
+ const rowGap = toNumber((0, _figmaVariablesResolver.getVariableByName)('formField/gap', modes), 8);
34
+ const labelColor = (0, _figmaVariablesResolver.getVariableByName)('formField/label/color', modes);
35
+ const labelFontFamily = (0, _figmaVariablesResolver.getVariableByName)('formField/label/fontFamily', modes);
36
+ const labelFontSize = toNumber((0, _figmaVariablesResolver.getVariableByName)('formField/label/fontSize', modes), 14);
37
+ const labelLineHeight = toNumber((0, _figmaVariablesResolver.getVariableByName)('formField/label/lineHeight', modes), 17);
38
+ const labelFontWeight = toFontWeight((0, _figmaVariablesResolver.getVariableByName)('formField/label/fontWeight', modes), '500');
39
+ return {
40
+ gap,
41
+ rowGap,
42
+ labelColor,
43
+ labelFontFamily,
44
+ labelFontSize,
45
+ labelLineHeight,
46
+ labelFontWeight
47
+ };
48
+ }
49
+ function firstError(error) {
50
+ if (!error) return undefined;
51
+ if (Array.isArray(error)) return error[0];
52
+ return error;
53
+ }
54
+
55
+ /** Figma defaults for the remove `IconCapsule` on filled preview cells. Overridable via `modes`. */
56
+ const FORM_UPLOAD_PREVIEW_ICON_CAPSULE_MODES = {
57
+ AppearanceBrand: 'Neutral',
58
+ Emphasis: 'Medium',
59
+ 'Icon Capsule Size': 'XS'
60
+ };
61
+
62
+ /**
63
+ * `FormUpload` — a form-aware upload row built from reusable `Additem` cells.
64
+ *
65
+ * The component mirrors the Figma "FormUpload" design: a label, a horizontal
66
+ * row of upload/preview cells, and support text. It integrates with the
67
+ * existing `<Form>` context so server-side validation errors automatically
68
+ * show below the row, and it clears those errors when attachments change.
69
+ *
70
+ * Upload behaviour is provided through the `picker` prop so the library stays
71
+ * free of native image-picker dependencies. Pass any picker that returns the
72
+ * `AdditemPickerResult` shape and the empty cell becomes a real upload trigger
73
+ * on both iOS and Android.
74
+ */
75
+ function FormUpload({
76
+ label,
77
+ supportText,
78
+ name,
79
+ attachments = [],
80
+ maxCount,
81
+ onAttachmentsChange,
82
+ picker,
83
+ errorMessage,
84
+ isInvalid = false,
85
+ isDisabled = false,
86
+ children,
87
+ modes: propModes = _reactUtils.EMPTY_MODES,
88
+ style,
89
+ rowStyle,
90
+ accessibilityLabel,
91
+ testID
92
+ }) {
93
+ const formCtx = (0, _Form.useFormContext)();
94
+ const formError = name && formCtx ? firstError(formCtx.validationErrors[name]) : undefined;
95
+ const resolvedIsInvalid = isInvalid || Boolean(formError);
96
+ const resolvedErrorMessage = errorMessage ?? formError;
97
+ const {
98
+ modes: globalModes
99
+ } = (0, _JFSThemeProvider.useTokens)();
100
+ const baseModes = (0, _react.useMemo)(() => globalModes === _reactUtils.EMPTY_MODES && propModes === _reactUtils.EMPTY_MODES ? _reactUtils.EMPTY_MODES : {
101
+ ...globalModes,
102
+ ...propModes
103
+ }, [globalModes, propModes]);
104
+ const modes = (0, _react.useMemo)(() => ({
105
+ ...baseModes,
106
+ 'FormField States': resolvedIsInvalid ? 'Error' : 'Idle',
107
+ Status: resolvedIsInvalid ? 'Error' : 'Auto'
108
+ }), [baseModes, resolvedIsInvalid]);
109
+
110
+ // Empty cells rely on `Additem`'s built-in Neutral / Low / S defaults for the
111
+ // add icon. Preview cells use the tighter remove-icon treatment from Figma.
112
+ const previewAdditemModes = (0, _react.useMemo)(() => ({
113
+ ...FORM_UPLOAD_PREVIEW_ICON_CAPSULE_MODES,
114
+ ...modes
115
+ }), [modes]);
116
+ const tokens = (0, _react.useMemo)(() => resolveFormUploadTokens(modes), [modes]);
117
+ const handleAssetsPicked = (0, _react.useCallback)(assets => {
118
+ if (isDisabled) return;
119
+ const next = [...attachments, ...assets];
120
+ if (maxCount != null && next.length > maxCount) {
121
+ next.length = maxCount;
122
+ }
123
+ onAttachmentsChange?.(next);
124
+ if (name && formCtx) {
125
+ formCtx.onFieldChange(name);
126
+ }
127
+ }, [attachments, maxCount, onAttachmentsChange, isDisabled, name, formCtx]);
128
+ const handleRemove = (0, _react.useCallback)(index => {
129
+ if (isDisabled) return;
130
+ const next = attachments.filter((_, i) => i !== index);
131
+ onAttachmentsChange?.(next);
132
+ if (name && formCtx) {
133
+ formCtx.onFieldChange(name);
134
+ }
135
+ }, [attachments, onAttachmentsChange, isDisabled, name, formCtx]);
136
+ const containerStyle = (0, _react.useMemo)(() => ({
137
+ gap: tokens.gap,
138
+ opacity: isDisabled ? 0.5 : 1
139
+ }), [tokens.gap, isDisabled]);
140
+ const rowStyleResolved = (0, _react.useMemo)(() => ({
141
+ flexDirection: 'row',
142
+ flexWrap: 'wrap',
143
+ gap: tokens.rowGap,
144
+ alignItems: 'flex-start'
145
+ }), [tokens.rowGap]);
146
+ const labelStyle = (0, _react.useMemo)(() => ({
147
+ color: tokens.labelColor,
148
+ fontFamily: tokens.labelFontFamily,
149
+ fontSize: tokens.labelFontSize,
150
+ lineHeight: tokens.labelLineHeight,
151
+ fontWeight: tokens.labelFontWeight
152
+ }), [tokens]);
153
+ const resolvedA11yLabel = accessibilityLabel ?? label ?? 'Attachment upload';
154
+ const canAddMore = maxCount == null || attachments.length < maxCount;
155
+ const renderContent = () => {
156
+ if (children) {
157
+ return (0, _reactUtils.cloneChildrenWithModes)(children, modes);
158
+ }
159
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
160
+ children: [attachments.map((attachment, index) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_Additem.default, {
161
+ state: "preview",
162
+ imageSource: attachment.uri,
163
+ onRemove: () => handleRemove(index),
164
+ modes: previewAdditemModes,
165
+ isDisabled: isDisabled,
166
+ accessibilityLabel: attachment.name ?? `Attachment ${index + 1}`,
167
+ testID: `${testID}-item-${index}`
168
+ }, `${attachment.uri}-${index}`)), canAddMore && /*#__PURE__*/(0, _jsxRuntime.jsx)(_Additem.default, {
169
+ state: "empty",
170
+ picker: picker,
171
+ onAssetsPicked: handleAssetsPicked,
172
+ modes: modes,
173
+ isDisabled: isDisabled,
174
+ testID: `${testID}-add`
175
+ })]
176
+ });
177
+ };
178
+ const supportLabel = resolvedIsInvalid && resolvedErrorMessage ? resolvedErrorMessage : supportText;
179
+ const supportStatus = resolvedIsInvalid ? 'Error' : 'Neutral';
180
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
181
+ style: [containerStyle, style],
182
+ accessibilityRole: "none",
183
+ accessibilityLabel: resolvedA11yLabel,
184
+ testID: testID,
185
+ children: [label != null && label !== '' && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
186
+ style: labelStyle,
187
+ children: label
188
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
189
+ style: [rowStyleResolved, rowStyle],
190
+ children: renderContent()
191
+ }), supportLabel != null && supportLabel !== '' && /*#__PURE__*/(0, _jsxRuntime.jsx)(_SupportText.default, {
192
+ label: supportLabel,
193
+ status: supportStatus,
194
+ modes: modes
195
+ })]
196
+ });
197
+ }
198
+ var _default = exports.default = /*#__PURE__*/_react.default.memo(FormUpload);