jfs-components 0.1.23 → 0.1.28

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 (99) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/lib/commonjs/components/AutoplayControl/AutoplayControl.js +66 -0
  3. package/lib/commonjs/components/CardCTA/CardCTA.js +32 -25
  4. package/lib/commonjs/components/Carousel/Carousel.js +112 -19
  5. package/lib/commonjs/components/ChipSelect/ChipSelect.js +2 -1
  6. package/lib/commonjs/components/ContentSheet/ContentSheet.js +39 -5
  7. package/lib/commonjs/components/FormUpload/Additem.js +168 -0
  8. package/lib/commonjs/components/FormUpload/FormUpload.js +198 -0
  9. package/lib/commonjs/components/Grid/Grid.js +206 -0
  10. package/lib/commonjs/components/ListItem/ListItem.js +11 -4
  11. package/lib/commonjs/components/MetricData/MetricData.js +132 -0
  12. package/lib/commonjs/components/NumberPagination/NumberPagination.js +144 -0
  13. package/lib/commonjs/components/ProfileCard/ProfileCard.js +208 -0
  14. package/lib/commonjs/components/Rating/Rating.js +137 -0
  15. package/lib/commonjs/components/UpiHandle/UpiHandle.js +23 -11
  16. package/lib/commonjs/components/index.js +42 -0
  17. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
  18. package/lib/commonjs/design-tokens/figma-modes.generated.js +23 -10
  19. package/lib/commonjs/icons/components/IconBrokenimage.js +19 -0
  20. package/lib/commonjs/icons/components/IconImage.js +19 -0
  21. package/lib/commonjs/icons/components/IconMic.js +19 -0
  22. package/lib/commonjs/icons/components/IconMicoff.js +19 -0
  23. package/lib/commonjs/icons/components/IconPause.js +19 -0
  24. package/lib/commonjs/icons/components/IconPlay.js +19 -0
  25. package/lib/commonjs/icons/components/index.js +66 -0
  26. package/lib/commonjs/icons/registry.js +2 -2
  27. package/lib/module/components/AutoplayControl/AutoplayControl.js +60 -0
  28. package/lib/module/components/CardCTA/CardCTA.js +32 -25
  29. package/lib/module/components/Carousel/Carousel.js +111 -19
  30. package/lib/module/components/ChipSelect/ChipSelect.js +2 -1
  31. package/lib/module/components/ContentSheet/ContentSheet.js +41 -7
  32. package/lib/module/components/FormUpload/Additem.js +162 -0
  33. package/lib/module/components/FormUpload/FormUpload.js +192 -0
  34. package/lib/module/components/Grid/Grid.js +201 -0
  35. package/lib/module/components/ListItem/ListItem.js +11 -4
  36. package/lib/module/components/MetricData/MetricData.js +127 -0
  37. package/lib/module/components/NumberPagination/NumberPagination.js +138 -0
  38. package/lib/module/components/ProfileCard/ProfileCard.js +202 -0
  39. package/lib/module/components/Rating/Rating.js +132 -0
  40. package/lib/module/components/UpiHandle/UpiHandle.js +23 -11
  41. package/lib/module/components/index.js +7 -1
  42. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
  43. package/lib/module/design-tokens/figma-modes.generated.js +23 -10
  44. package/lib/module/icons/components/IconBrokenimage.js +12 -0
  45. package/lib/module/icons/components/IconImage.js +12 -0
  46. package/lib/module/icons/components/IconMic.js +12 -0
  47. package/lib/module/icons/components/IconMicoff.js +12 -0
  48. package/lib/module/icons/components/IconPause.js +12 -0
  49. package/lib/module/icons/components/IconPlay.js +12 -0
  50. package/lib/module/icons/components/index.js +6 -0
  51. package/lib/module/icons/registry.js +2 -2
  52. package/lib/typescript/src/components/AutoplayControl/AutoplayControl.d.ts +15 -0
  53. package/lib/typescript/src/components/Carousel/Carousel.d.ts +8 -1
  54. package/lib/typescript/src/components/ChipSelect/ChipSelect.d.ts +6 -1
  55. package/lib/typescript/src/components/ContentSheet/ContentSheet.d.ts +7 -1
  56. package/lib/typescript/src/components/FormUpload/Additem.d.ts +96 -0
  57. package/lib/typescript/src/components/FormUpload/FormUpload.d.ts +85 -0
  58. package/lib/typescript/src/components/Grid/Grid.d.ts +87 -0
  59. package/lib/typescript/src/components/MetricData/MetricData.d.ts +53 -0
  60. package/lib/typescript/src/components/NumberPagination/NumberPagination.d.ts +21 -0
  61. package/lib/typescript/src/components/ProfileCard/ProfileCard.d.ts +89 -0
  62. package/lib/typescript/src/components/Rating/Rating.d.ts +30 -0
  63. package/lib/typescript/src/components/UpiHandle/UpiHandle.d.ts +7 -1
  64. package/lib/typescript/src/components/index.d.ts +34 -28
  65. package/lib/typescript/src/design-tokens/figma-modes.generated.d.ts +10 -4
  66. package/lib/typescript/src/icons/components/IconBrokenimage.d.ts +3 -0
  67. package/lib/typescript/src/icons/components/IconImage.d.ts +3 -0
  68. package/lib/typescript/src/icons/components/IconMic.d.ts +3 -0
  69. package/lib/typescript/src/icons/components/IconMicoff.d.ts +3 -0
  70. package/lib/typescript/src/icons/components/IconPause.d.ts +3 -0
  71. package/lib/typescript/src/icons/components/IconPlay.d.ts +3 -0
  72. package/lib/typescript/src/icons/components/index.d.ts +6 -0
  73. package/lib/typescript/src/icons/registry.d.ts +1 -1
  74. package/package.json +1 -1
  75. package/src/components/AutoplayControl/AutoplayControl.tsx +77 -0
  76. package/src/components/CardCTA/CardCTA.tsx +30 -15
  77. package/src/components/Carousel/Carousel.tsx +133 -23
  78. package/src/components/ChipSelect/ChipSelect.tsx +7 -1
  79. package/src/components/ContentSheet/ContentSheet.tsx +50 -0
  80. package/src/components/FormUpload/Additem.tsx +262 -0
  81. package/src/components/FormUpload/FormUpload.tsx +313 -0
  82. package/src/components/Grid/Grid.tsx +368 -0
  83. package/src/components/ListItem/ListItem.tsx +11 -4
  84. package/src/components/MetricData/MetricData.tsx +185 -0
  85. package/src/components/NumberPagination/NumberPagination.tsx +170 -0
  86. package/src/components/ProfileCard/ProfileCard.tsx +268 -0
  87. package/src/components/Rating/Rating.tsx +174 -0
  88. package/src/components/UpiHandle/UpiHandle.tsx +35 -24
  89. package/src/components/index.ts +329 -182
  90. package/src/design-tokens/Coin Variables-variables-full.json +1 -1
  91. package/src/design-tokens/figma-modes.generated.ts +23 -10
  92. package/src/icons/components/IconBrokenimage.tsx +11 -0
  93. package/src/icons/components/IconImage.tsx +11 -0
  94. package/src/icons/components/IconMic.tsx +11 -0
  95. package/src/icons/components/IconMicoff.tsx +11 -0
  96. package/src/icons/components/IconPause.tsx +11 -0
  97. package/src/icons/components/IconPlay.tsx +11 -0
  98. package/src/icons/components/index.ts +6 -0
  99. package/src/icons/registry.ts +25 -1
@@ -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);
@@ -0,0 +1,206 @@
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 _jsxRuntime = require("react/jsx-runtime");
13
+ 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); }
14
+ function toNumber(value, fallback) {
15
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
16
+ if (typeof value === 'string') {
17
+ const parsed = parseFloat(value);
18
+ if (Number.isFinite(parsed)) return parsed;
19
+ }
20
+ return fallback;
21
+ }
22
+ function resolveGridTokens(modes) {
23
+ const gapFallback = toNumber((0, _figmaVariablesResolver.getVariableByName)('Grid/gap', modes), 8);
24
+ const columnGap = toNumber((0, _figmaVariablesResolver.getVariableByName)('Grid/column/gap', modes), gapFallback);
25
+ const rowGap = toNumber((0, _figmaVariablesResolver.getVariableByName)('Grid/row/gap', modes), gapFallback);
26
+ const paddingHorizontal = toNumber((0, _figmaVariablesResolver.getVariableByName)('Grid/padding/horizontal', modes), 8);
27
+ const paddingVertical = toNumber((0, _figmaVariablesResolver.getVariableByName)('Grid/padding/vertical', modes), 8);
28
+ const paddingTop = toNumber((0, _figmaVariablesResolver.getVariableByName)('Grid/padding/top', modes), paddingVertical);
29
+ const paddingBottom = toNumber((0, _figmaVariablesResolver.getVariableByName)('Grid/padding/bottom', modes), paddingVertical);
30
+ const paddingLeft = toNumber((0, _figmaVariablesResolver.getVariableByName)('Grid/padding/left', modes), paddingHorizontal);
31
+ const paddingRight = toNumber((0, _figmaVariablesResolver.getVariableByName)('Grid/padding/right', modes), paddingHorizontal);
32
+ const backgroundColor = (0, _figmaVariablesResolver.getVariableByName)('Grid/background/color', modes) ?? 'transparent';
33
+ return {
34
+ backgroundColor,
35
+ columnGap,
36
+ rowGap,
37
+ paddingTop,
38
+ paddingBottom,
39
+ paddingLeft,
40
+ paddingRight
41
+ };
42
+ }
43
+ function normalizeFrs(frs, count) {
44
+ if (!frs || frs.length === 0) {
45
+ return Array.from({
46
+ length: count
47
+ }, () => 1);
48
+ }
49
+ return Array.from({
50
+ length: count
51
+ }, (_, index) => {
52
+ const value = frs[index] ?? frs[frs.length - 1] ?? 1;
53
+ return value > 0 ? value : 1;
54
+ });
55
+ }
56
+ function resolveAlignSelf(alignItems) {
57
+ if (alignItems === 'center') return 'center';
58
+ if (alignItems === 'start') return 'flex-start';
59
+ return 'stretch';
60
+ }
61
+ function resolveRowJustifyContent(justifyContent) {
62
+ if (justifyContent === 'space-between') return 'space-between';
63
+ if (justifyContent === 'start') return 'flex-start';
64
+ return 'flex-start';
65
+ }
66
+ function buildCellStyle(columnFr, justifyContent, alignItems) {
67
+ const alignSelf = resolveAlignSelf(alignItems);
68
+ if (justifyContent === 'space-between' || justifyContent === 'start') {
69
+ return {
70
+ flexGrow: 0,
71
+ flexShrink: 1,
72
+ flexBasis: 'auto',
73
+ minWidth: 0,
74
+ alignSelf
75
+ };
76
+ }
77
+ return {
78
+ flex: columnFr,
79
+ minWidth: 0,
80
+ alignSelf
81
+ };
82
+ }
83
+
84
+ /**
85
+ * `Grid` — a token-driven layout grid for React Native.
86
+ *
87
+ * Mirrors the Figma "Grid" component: a padded container with configurable
88
+ * columns, rows, fractional column tracks (`columnFrs`), and gap tokens.
89
+ * Each slot child maps to one cell — no separate `GridItem` wrapper is needed.
90
+ *
91
+ * @component
92
+ */
93
+ function Grid({
94
+ columns = 1,
95
+ rows,
96
+ columnFrs,
97
+ rowFrs,
98
+ columnGap: columnGapProp,
99
+ rowGap: rowGapProp,
100
+ gap: gapProp,
101
+ padding: paddingProp,
102
+ paddingHorizontal: paddingHorizontalProp,
103
+ paddingVertical: paddingVerticalProp,
104
+ paddingTop: paddingTopProp,
105
+ paddingBottom: paddingBottomProp,
106
+ paddingLeft: paddingLeftProp,
107
+ paddingRight: paddingRightProp,
108
+ justifyContent = 'stretch',
109
+ alignItems = 'stretch',
110
+ children,
111
+ modes: propModes = _reactUtils.EMPTY_MODES,
112
+ style,
113
+ contentStyle,
114
+ cellStyle,
115
+ accessibilityLabel,
116
+ testID
117
+ }) {
118
+ const {
119
+ modes: globalModes
120
+ } = (0, _JFSThemeProvider.useTokens)();
121
+ const modes = (0, _react.useMemo)(() => globalModes === _reactUtils.EMPTY_MODES && propModes === _reactUtils.EMPTY_MODES ? _reactUtils.EMPTY_MODES : {
122
+ ...globalModes,
123
+ ...propModes
124
+ }, [globalModes, propModes]);
125
+ const tokens = (0, _react.useMemo)(() => resolveGridTokens(modes), [modes]);
126
+ const resolvedColumnGap = columnGapProp ?? gapProp ?? tokens.columnGap;
127
+ const resolvedRowGap = rowGapProp ?? gapProp ?? tokens.rowGap;
128
+ const resolvedPadding = (0, _react.useMemo)(() => ({
129
+ paddingTop: paddingTopProp ?? paddingVerticalProp ?? paddingProp ?? tokens.paddingTop,
130
+ paddingBottom: paddingBottomProp ?? paddingVerticalProp ?? paddingProp ?? tokens.paddingBottom,
131
+ paddingLeft: paddingLeftProp ?? paddingHorizontalProp ?? paddingProp ?? tokens.paddingLeft,
132
+ paddingRight: paddingRightProp ?? paddingHorizontalProp ?? paddingProp ?? tokens.paddingRight
133
+ }), [paddingTopProp, paddingBottomProp, paddingLeftProp, paddingRightProp, paddingHorizontalProp, paddingVerticalProp, paddingProp, tokens]);
134
+ const safeColumns = Math.max(1, Math.floor(columns));
135
+ const processedChildren = (0, _react.useMemo)(() => (0, _reactUtils.cloneChildrenWithModes)((0, _reactUtils.flattenChildren)(children), modes), [children, modes]);
136
+ const cellNodes = (0, _react.useMemo)(() => {
137
+ const nodes = [...processedChildren];
138
+ if (rows != null) {
139
+ const targetCount = safeColumns * Math.max(1, Math.floor(rows));
140
+ while (nodes.length < targetCount) {
141
+ nodes.push(null);
142
+ }
143
+ if (nodes.length > targetCount) {
144
+ nodes.length = targetCount;
145
+ }
146
+ }
147
+ return nodes;
148
+ }, [processedChildren, rows, safeColumns]);
149
+ const gridRows = (0, _react.useMemo)(() => {
150
+ const result = [];
151
+ for (let index = 0; index < cellNodes.length; index += safeColumns) {
152
+ result.push(cellNodes.slice(index, index + safeColumns));
153
+ }
154
+ return result;
155
+ }, [cellNodes, safeColumns]);
156
+ const normalizedColumnFrs = (0, _react.useMemo)(() => normalizeFrs(columnFrs, safeColumns), [columnFrs, safeColumns]);
157
+ const normalizedRowFrs = (0, _react.useMemo)(() => rowFrs ? normalizeFrs(rowFrs, gridRows.length) : null, [rowFrs, gridRows.length]);
158
+ const containerStyle = (0, _react.useMemo)(() => ({
159
+ backgroundColor: tokens.backgroundColor,
160
+ ...resolvedPadding,
161
+ width: '100%'
162
+ }), [tokens.backgroundColor, resolvedPadding]);
163
+ const rowsContainerStyle = (0, _react.useMemo)(() => ({
164
+ gap: resolvedRowGap,
165
+ width: '100%'
166
+ }), [resolvedRowGap]);
167
+ const rowStyle = (0, _react.useMemo)(() => ({
168
+ flexDirection: 'row',
169
+ columnGap: resolvedColumnGap,
170
+ justifyContent: resolveRowJustifyContent(justifyContent),
171
+ alignItems: alignItems === 'stretch' ? 'stretch' : alignItems === 'center' ? 'center' : 'flex-start',
172
+ width: '100%'
173
+ }), [resolvedColumnGap, justifyContent, alignItems]);
174
+ const defaultAccessibilityLabel = accessibilityLabel ?? `Grid, ${safeColumns} column${safeColumns === 1 ? '' : 's'}, ${gridRows.length} row${gridRows.length === 1 ? '' : 's'}`;
175
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
176
+ style: [containerStyle, style],
177
+ accessibilityRole: "none",
178
+ accessibilityLabel: defaultAccessibilityLabel,
179
+ testID: testID,
180
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
181
+ style: [rowsContainerStyle, contentStyle],
182
+ children: gridRows.map((row, rowIndex) => {
183
+ const rowFr = normalizedRowFrs?.[rowIndex];
184
+ const spacersNeeded = justifyContent === 'space-between' && row.length < safeColumns ? safeColumns - row.length : 0;
185
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
186
+ style: rowFr != null ? [rowStyle, {
187
+ flexGrow: rowFr
188
+ }] : rowStyle,
189
+ children: [row.map((cell, columnIndex) => {
190
+ const absoluteIndex = rowIndex * safeColumns + columnIndex;
191
+ const cellFlexStyle = buildCellStyle(normalizedColumnFrs[columnIndex] ?? 1, justifyContent, alignItems);
192
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
193
+ style: [cellFlexStyle, cellStyle],
194
+ children: cell
195
+ }, `grid-cell-${absoluteIndex}`);
196
+ }), spacersNeeded > 0 && Array.from({
197
+ length: spacersNeeded
198
+ }, (_, spacerIndex) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
199
+ style: buildCellStyle(normalizedColumnFrs[row.length + spacerIndex] ?? 1, justifyContent, alignItems)
200
+ }, `grid-spacer-${rowIndex}-${spacerIndex}`))]
201
+ }, `grid-row-${rowIndex}`);
202
+ })
203
+ })
204
+ });
205
+ }
206
+ var _default = exports.default = /*#__PURE__*/_react.default.memo(Grid);
@@ -255,13 +255,20 @@ function ListItemImpl({
255
255
  const renderSupportContent = () => {
256
256
  if (processedSupportSlot) return processedSupportSlot;
257
257
 
258
- // Default support text:
259
- // - vertical layout: main text (line-broken on spaces)
260
- // - horizontal layout: secondary line (clamped to 2 lines)
258
+ // Default support text — ONE `<Text>` hard-clamped to 2 lines via
259
+ // `numberOfLines={2}`, the only mechanism React Native actually enforces
260
+ // on iOS/Android (native has no `white-space: nowrap` equivalent, so any
261
+ // style-based trick cannot stop a too-wide word from wrapping).
262
+ //
263
+ // - vertical layout: line-broken on spaces so each word gets its own line
264
+ // ("Split Payments" → "Split" / "Payments"). If a word is still wider
265
+ // than the cell (or there are 3+ words), RN cuts it off at line 2 with
266
+ // a trailing ellipsis instead of ever spilling onto a third line.
267
+ // - horizontal layout: natural wrapping, same 2-line clamp.
261
268
  const displayText = layout === 'Vertical' ? supportText.replace(/ /g, '\n') : supportText;
262
269
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
263
270
  style: layout === 'Vertical' ? [tokens.supportTextStyle, verticalSupportTextOverride] : tokens.supportTextStyle,
264
- ...(0, _reactUtils.resolveTruncation)(disableTruncation, layout === 'Horizontal' ? 2 : undefined),
271
+ ...(0, _reactUtils.resolveTruncation)(disableTruncation, 2, 'tail'),
265
272
  children: displayText
266
273
  });
267
274
  };