jfs-components 0.0.77 → 0.0.79

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 (87) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/lib/commonjs/components/Accordion/Accordion.js +55 -55
  3. package/lib/commonjs/components/ActionFooter/ActionFooter.js +48 -2
  4. package/lib/commonjs/components/Attached/Attached.js +144 -0
  5. package/lib/commonjs/components/Card/Card.js +25 -2
  6. package/lib/commonjs/components/Checkbox/Checkbox.js +21 -9
  7. package/lib/commonjs/components/DropdownInput/DropdownInput.js +30 -16
  8. package/lib/commonjs/components/ExpandableCheckbox/ExpandableCheckbox.js +167 -0
  9. package/lib/commonjs/components/FormField/FormField.js +14 -1
  10. package/lib/commonjs/components/FullscreenModal/FullscreenModal.js +353 -0
  11. package/lib/commonjs/components/ListItem/ListItem.js +46 -24
  12. package/lib/commonjs/components/MessageField/MessageField.js +318 -0
  13. package/lib/commonjs/components/NavArrow/NavArrow.js +58 -17
  14. package/lib/commonjs/components/PlanComparisonCard/PlanComparisonCard.js +328 -0
  15. package/lib/commonjs/components/Slot/Slot.js +73 -0
  16. package/lib/commonjs/components/Stepper/Step.js +47 -60
  17. package/lib/commonjs/components/Stepper/StepLabel.js +40 -10
  18. package/lib/commonjs/components/Stepper/Stepper.js +15 -17
  19. package/lib/commonjs/components/SuggestiveSearch/SuggestiveSearch.js +487 -0
  20. package/lib/commonjs/components/TextInput/TextInput.js +16 -1
  21. package/lib/commonjs/components/Title/Title.js +10 -2
  22. package/lib/commonjs/components/index.js +49 -0
  23. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
  24. package/lib/commonjs/icons/registry.js +1 -1
  25. package/lib/module/components/Accordion/Accordion.js +56 -56
  26. package/lib/module/components/ActionFooter/ActionFooter.js +50 -4
  27. package/lib/module/components/Attached/Attached.js +139 -0
  28. package/lib/module/components/Card/Card.js +25 -2
  29. package/lib/module/components/Checkbox/Checkbox.js +22 -10
  30. package/lib/module/components/DropdownInput/DropdownInput.js +30 -16
  31. package/lib/module/components/ExpandableCheckbox/ExpandableCheckbox.js +161 -0
  32. package/lib/module/components/FormField/FormField.js +16 -3
  33. package/lib/module/components/FullscreenModal/FullscreenModal.js +348 -0
  34. package/lib/module/components/ListItem/ListItem.js +46 -24
  35. package/lib/module/components/MessageField/MessageField.js +313 -0
  36. package/lib/module/components/NavArrow/NavArrow.js +59 -18
  37. package/lib/module/components/PlanComparisonCard/PlanComparisonCard.js +322 -0
  38. package/lib/module/components/Slot/Slot.js +68 -0
  39. package/lib/module/components/Stepper/Step.js +48 -61
  40. package/lib/module/components/Stepper/StepLabel.js +40 -10
  41. package/lib/module/components/Stepper/Stepper.js +15 -17
  42. package/lib/module/components/SuggestiveSearch/SuggestiveSearch.js +481 -0
  43. package/lib/module/components/TextInput/TextInput.js +17 -2
  44. package/lib/module/components/Title/Title.js +10 -2
  45. package/lib/module/components/index.js +7 -0
  46. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
  47. package/lib/module/icons/registry.js +1 -1
  48. package/lib/typescript/src/components/Accordion/Accordion.d.ts +14 -20
  49. package/lib/typescript/src/components/Attached/Attached.d.ts +61 -0
  50. package/lib/typescript/src/components/Card/Card.d.ts +9 -2
  51. package/lib/typescript/src/components/ExpandableCheckbox/ExpandableCheckbox.d.ts +63 -0
  52. package/lib/typescript/src/components/FullscreenModal/FullscreenModal.d.ts +99 -0
  53. package/lib/typescript/src/components/ListItem/ListItem.d.ts +15 -5
  54. package/lib/typescript/src/components/MessageField/MessageField.d.ts +81 -0
  55. package/lib/typescript/src/components/NavArrow/NavArrow.d.ts +10 -5
  56. package/lib/typescript/src/components/PlanComparisonCard/PlanComparisonCard.d.ts +64 -0
  57. package/lib/typescript/src/components/Slot/Slot.d.ts +52 -0
  58. package/lib/typescript/src/components/Stepper/Step.d.ts +4 -1
  59. package/lib/typescript/src/components/Stepper/StepLabel.d.ts +4 -1
  60. package/lib/typescript/src/components/Stepper/Stepper.d.ts +3 -1
  61. package/lib/typescript/src/components/SuggestiveSearch/SuggestiveSearch.d.ts +123 -0
  62. package/lib/typescript/src/components/index.d.ts +10 -3
  63. package/lib/typescript/src/icons/registry.d.ts +1 -1
  64. package/package.json +1 -1
  65. package/src/components/Accordion/Accordion.tsx +113 -73
  66. package/src/components/ActionFooter/ActionFooter.tsx +56 -4
  67. package/src/components/Attached/Attached.tsx +181 -0
  68. package/src/components/Card/Card.tsx +28 -1
  69. package/src/components/Checkbox/Checkbox.tsx +22 -9
  70. package/src/components/DropdownInput/DropdownInput.tsx +67 -39
  71. package/src/components/ExpandableCheckbox/ExpandableCheckbox.tsx +237 -0
  72. package/src/components/FormField/FormField.tsx +19 -3
  73. package/src/components/FullscreenModal/FullscreenModal.tsx +414 -0
  74. package/src/components/ListItem/ListItem.tsx +55 -25
  75. package/src/components/MessageField/MessageField.tsx +543 -0
  76. package/src/components/NavArrow/NavArrow.tsx +81 -17
  77. package/src/components/PlanComparisonCard/PlanComparisonCard.tsx +426 -0
  78. package/src/components/Slot/Slot.tsx +91 -0
  79. package/src/components/Stepper/Step.tsx +52 -51
  80. package/src/components/Stepper/StepLabel.tsx +46 -9
  81. package/src/components/Stepper/Stepper.tsx +20 -15
  82. package/src/components/SuggestiveSearch/SuggestiveSearch.tsx +756 -0
  83. package/src/components/TextInput/TextInput.tsx +14 -1
  84. package/src/components/Title/Title.tsx +13 -2
  85. package/src/components/index.ts +10 -3
  86. package/src/design-tokens/Coin Variables-variables-full.json +1 -1
  87. package/src/icons/registry.ts +1 -1
@@ -21,9 +21,10 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
21
21
  const IS_IOS = _reactNative.Platform.OS === 'ios';
22
22
  const PRESS_DELAY = IS_IOS ? 130 : 0;
23
23
 
24
- // Forced modes for the endSlot — `Context: 'ListItem'` can never be
25
- // overridden by external modes. Frozen so identity is stable across renders.
26
- const END_SLOT_FORCED_MODES = Object.freeze({
24
+ // Forced modes for the leading/trailing slots — `Context: 'ListItem'` can
25
+ // never be overridden by external modes. Frozen so identity is stable across
26
+ // renders. Applied to both slots so they cascade modes identically.
27
+ const SLOT_FORCED_MODES = Object.freeze({
27
28
  Context: 'ListItem'
28
29
  });
29
30
 
@@ -38,27 +39,42 @@ const pressedOverlayStyle = {
38
39
  // ---------------------------------------------------------------------------
39
40
 
40
41
  function resolveListItemTokens(modes) {
42
+ // Modes used to cascade into slot children (leading / supportSlot / trailing).
43
+ // We do NOT inject an `AppearanceBrand` default here: slot content such as
44
+ // Buttons or Badges carry their own intended appearance, so forcing one onto
45
+ // them would be surprising.
41
46
  const resolvedModes = {
42
47
  ...modes,
43
48
  Context: 'ListItem'
44
49
  };
50
+
51
+ // Modes used to resolve the ListItem's OWN title + support text. Within this
52
+ // component, `AppearanceBrand` only affects `listItem/title/color` and
53
+ // `listItem/supportText/color`, so the text defaults to the "Neutral"
54
+ // appearance (in both Vertical and Horizontal layouts). A caller-supplied
55
+ // `AppearanceBrand` still wins; `Context` is always forced to 'ListItem'.
56
+ const textModes = {
57
+ AppearanceBrand: 'Neutral',
58
+ ...modes,
59
+ Context: 'ListItem'
60
+ };
45
61
  const gap = (0, _figmaVariablesResolver.getVariableByName)('listItem/gap', resolvedModes) ?? 8;
46
62
  const paddingTop = (0, _figmaVariablesResolver.getVariableByName)('listItem/padding/top', resolvedModes) ?? 0;
47
63
  const paddingBottom = (0, _figmaVariablesResolver.getVariableByName)('listItem/padding/bottom', resolvedModes) ?? 0;
48
64
  const paddingLeft = (0, _figmaVariablesResolver.getVariableByName)('listItem/padding/left', resolvedModes) ?? 0;
49
65
  const paddingRight = (0, _figmaVariablesResolver.getVariableByName)('listItem/padding/right', resolvedModes) ?? 0;
50
66
  const textWrapGap = (0, _figmaVariablesResolver.getVariableByName)('listItem/text wrap', resolvedModes) ?? 0;
51
- const titleColor = (0, _figmaVariablesResolver.getVariableByName)('listItem/title/color', resolvedModes) || '#0f0d0a';
52
- const titleFontSize = (0, _figmaVariablesResolver.getVariableByName)('listItem/title/fontSize', resolvedModes) || 14;
53
- const titleLineHeight = (0, _figmaVariablesResolver.getVariableByName)('listItem/title/lineHeight', resolvedModes) || 16;
54
- const titleFontFamily = (0, _figmaVariablesResolver.getVariableByName)('listItem/title/fontFamily', resolvedModes) || 'System';
55
- const titleFontWeightRaw = (0, _figmaVariablesResolver.getVariableByName)('listItem/title/fontWeight', resolvedModes) || 700;
67
+ const titleColor = (0, _figmaVariablesResolver.getVariableByName)('listItem/title/color', textModes) || '#0f0d0a';
68
+ const titleFontSize = (0, _figmaVariablesResolver.getVariableByName)('listItem/title/fontSize', textModes) || 14;
69
+ const titleLineHeight = (0, _figmaVariablesResolver.getVariableByName)('listItem/title/lineHeight', textModes) || 16;
70
+ const titleFontFamily = (0, _figmaVariablesResolver.getVariableByName)('listItem/title/fontFamily', textModes) || 'System';
71
+ const titleFontWeightRaw = (0, _figmaVariablesResolver.getVariableByName)('listItem/title/fontWeight', textModes) || 700;
56
72
  const titleFontWeight = typeof titleFontWeightRaw === 'number' ? titleFontWeightRaw.toString() : titleFontWeightRaw;
57
- const supportColor = (0, _figmaVariablesResolver.getVariableByName)('listItem/supportText/color', resolvedModes) || '#1f1a14';
58
- const supportFontSize = (0, _figmaVariablesResolver.getVariableByName)('listItem/supportText/fontSize', resolvedModes) || 12;
59
- const supportLineHeight = (0, _figmaVariablesResolver.getVariableByName)('listItem/supportText/lineHeight', resolvedModes) || 14;
60
- const supportFontFamily = (0, _figmaVariablesResolver.getVariableByName)('listItem/supportText/fontFamily', resolvedModes) || 'System';
61
- const supportFontWeightRaw = (0, _figmaVariablesResolver.getVariableByName)('listItem/supportText/fontWeight', resolvedModes) || 500;
73
+ const supportColor = (0, _figmaVariablesResolver.getVariableByName)('listItem/supportText/color', textModes) || '#1f1a14';
74
+ const supportFontSize = (0, _figmaVariablesResolver.getVariableByName)('listItem/supportText/fontSize', textModes) || 12;
75
+ const supportLineHeight = (0, _figmaVariablesResolver.getVariableByName)('listItem/supportText/lineHeight', textModes) || 14;
76
+ const supportFontFamily = (0, _figmaVariablesResolver.getVariableByName)('listItem/supportText/fontFamily', textModes) || 'System';
77
+ const supportFontWeightRaw = (0, _figmaVariablesResolver.getVariableByName)('listItem/supportText/fontWeight', textModes) || 500;
62
78
  const supportFontWeight = typeof supportFontWeightRaw === 'number' ? supportFontWeightRaw.toString() : supportFontWeightRaw;
63
79
  return {
64
80
  baseContainerStyle: {
@@ -122,9 +138,11 @@ const verticalSupportTextOverride = {
122
138
  * - **design-token driven styling** via `getVariableByName` and `modes`
123
139
  *
124
140
  * Wherever the Figma layer name contains "Slot", this component exposes a
125
- * dedicated React "slot" prop:
141
+ * dedicated React "slot" prop. The leading and trailing edges share a
142
+ * symmetric `leading` / `trailing` slot API:
143
+ * - Slot "leading" → `leading`
126
144
  * - Slot "support text" → `supportSlot`
127
- * - Slot "end" → `endSlot`
145
+ * - Slot "trailing" → `trailing`
128
146
  *
129
147
  * @component
130
148
  * @param {Object} props
@@ -132,9 +150,9 @@ const verticalSupportTextOverride = {
132
150
  * @param {string} [props.title='Title'] - Primary title used in the horizontal layout.
133
151
  * @param {string} [props.supportText='Support Text'] - Support text used in both layouts when `supportSlot` is not provided.
134
152
  * @param {boolean} [props.showSupportText=true] - Toggles rendering of the support text in Horizontal layout.
135
- * @param {React.ReactNode} [props.leading] - Optional leading element. Defaults to `IconCapsule`.
153
+ * @param {React.ReactNode} [props.leading] - Optional leading slot. Defaults to `IconCapsule`.
136
154
  * @param {React.ReactNode} [props.supportSlot] - Optional custom slot used instead of the default support text block.
137
- * @param {React.ReactNode} [props.endSlot] - Optional custom trailing slot (Figma Slot "end").
155
+ * @param {React.ReactNode} [props.trailing] - Optional trailing slot (Figma Slot "trailing"). Horizontal layout only.
138
156
  * @param {boolean} [props.navArrow=true] - Whether to show NavArrow on the far right (Horizontal layout only).
139
157
  * @param {Object} [props.modes={}] - Modes object passed to `getVariableByName` for all design tokens.
140
158
  * @param {Function} [props.onPress] - When provided, the entire item becomes pressable (navigation variant).
@@ -163,6 +181,7 @@ function ListItemImpl({
163
181
  showSupportText = true,
164
182
  leading,
165
183
  supportSlot,
184
+ trailing,
166
185
  endSlot,
167
186
  navArrow = true,
168
187
  modes = _reactUtils.EMPTY_MODES,
@@ -200,7 +219,7 @@ function ListItemImpl({
200
219
  // Process leading slot to pass modes to children. Memoized on
201
220
  // (leading, resolvedModes) so a parent re-render doesn't re-walk the tree.
202
221
  const leadingElement = (0, _react.useMemo)(() => {
203
- const processed = leading ? (0, _reactUtils.cloneChildrenWithModes)(_react.default.Children.toArray(leading), tokens.resolvedModes) : [];
222
+ const processed = leading ? (0, _reactUtils.cloneChildrenWithModes)(_react.default.Children.toArray(leading), tokens.resolvedModes, SLOT_FORCED_MODES) : [];
204
223
  if (processed.length === 0) {
205
224
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_IconCapsule.default, {
206
225
  modes: tokens.resolvedModes,
@@ -214,11 +233,14 @@ function ListItemImpl({
214
233
  const processed = (0, _reactUtils.cloneChildrenWithModes)(_react.default.Children.toArray(supportSlot), tokens.resolvedModes);
215
234
  return processed.length === 1 ? processed[0] : processed;
216
235
  }, [supportSlot, tokens.resolvedModes]);
217
- const processedEndSlot = (0, _react.useMemo)(() => {
218
- if (!endSlot) return null;
219
- const processed = (0, _reactUtils.cloneChildrenWithModes)(_react.default.Children.toArray(endSlot), tokens.resolvedModes, END_SLOT_FORCED_MODES);
236
+
237
+ // `trailing` wins; `endSlot` is the deprecated alias kept for back-compat.
238
+ const trailingContent = trailing ?? endSlot;
239
+ const processedTrailing = (0, _react.useMemo)(() => {
240
+ if (!trailingContent) return null;
241
+ const processed = (0, _reactUtils.cloneChildrenWithModes)(_react.default.Children.toArray(trailingContent), tokens.resolvedModes, SLOT_FORCED_MODES);
220
242
  return processed.length === 1 ? processed[0] : processed;
221
- }, [endSlot, tokens.resolvedModes]);
243
+ }, [trailingContent, tokens.resolvedModes]);
222
244
  const renderSupportContent = () => {
223
245
  if (processedSupportSlot) return processedSupportSlot;
224
246
 
@@ -264,9 +286,9 @@ function ListItemImpl({
264
286
  numberOfLines: 1,
265
287
  children: title
266
288
  }), showSupportText && renderSupportContent()]
267
- }), processedEndSlot ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
289
+ }), processedTrailing ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
268
290
  style: tokens.trailingWrapperStyle,
269
- children: processedEndSlot
291
+ children: processedTrailing
270
292
  }) : null, navArrow && /*#__PURE__*/(0, _jsxRuntime.jsx)(_NavArrow.default, {
271
293
  direction: "Forward",
272
294
  modes: tokens.resolvedModes
@@ -0,0 +1,318 @@
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 _jsxRuntime = require("react/jsx-runtime");
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
+ // ---------------------------------------------------------------------------
16
+ // Types
17
+ // ---------------------------------------------------------------------------
18
+
19
+ /**
20
+ * Visual state of the textarea. Mirrors the `FormField States` collection so
21
+ * MessageField slots into the same theming pipeline as FormField. The state
22
+ * is always derived from props (`isInvalid`, `isDisabled`, `isReadOnly` and
23
+ * focus) and is locked in `modes['FormField States']` — passing that key in
24
+ * `modes` is intentionally ignored to keep interactive behaviour and visual
25
+ * state in sync.
26
+ */
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Token helpers
30
+ // ---------------------------------------------------------------------------
31
+
32
+ function toNumber(value, fallback) {
33
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
34
+ if (typeof value === 'string') {
35
+ const parsed = parseFloat(value);
36
+ if (Number.isFinite(parsed)) return parsed;
37
+ }
38
+ return fallback;
39
+ }
40
+ function toFontWeight(value, fallback) {
41
+ if (typeof value === 'number') return value.toString();
42
+ if (typeof value === 'string' && value.length > 0) return value;
43
+ return fallback;
44
+ }
45
+ function firstError(error) {
46
+ if (!error) return undefined;
47
+ if (Array.isArray(error)) return error[0];
48
+ return error;
49
+ }
50
+ function useMessageFieldTokens(modes) {
51
+ return (0, _react.useMemo)(() => {
52
+ const wrapperGap = toNumber((0, _figmaVariablesResolver.getVariableByName)('messageField/gap', modes), 8);
53
+ const labelColor = (0, _figmaVariablesResolver.getVariableByName)('messageField/label/foreground', modes) || '#000000';
54
+ const labelFontFamily = (0, _figmaVariablesResolver.getVariableByName)('messageField/label/fontFamily', modes) || 'JioType Var';
55
+ const labelFontSize = toNumber((0, _figmaVariablesResolver.getVariableByName)('messageField/label/fontSize', modes), 14);
56
+ const labelLineHeight = toNumber((0, _figmaVariablesResolver.getVariableByName)('messageField/label/lineHeight', modes), 17);
57
+ const labelFontWeight = toFontWeight((0, _figmaVariablesResolver.getVariableByName)('messageField/label/fontWeight', modes), '500');
58
+ const textareaBackground = (0, _figmaVariablesResolver.getVariableByName)('messageField/textarea/background', modes) || '#ffffff';
59
+ const textareaBorderColor = (0, _figmaVariablesResolver.getVariableByName)('messageField/textarea/border/color', modes) || '#b5b6b7';
60
+ const textareaBorderSize = toNumber((0, _figmaVariablesResolver.getVariableByName)('messageField/textarea/border/size', modes), 1.5);
61
+ const textareaRadius = toNumber((0, _figmaVariablesResolver.getVariableByName)('messageField/textarea/radius', modes), 8);
62
+ const textareaPadding = toNumber((0, _figmaVariablesResolver.getVariableByName)('messageField/textarea/padding', modes), 12);
63
+ const textareaHeight = toNumber((0, _figmaVariablesResolver.getVariableByName)('messageField/textarea/height', modes), 108);
64
+ const textareaGap = toNumber((0, _figmaVariablesResolver.getVariableByName)('messageField/textarea/gap', modes), 0);
65
+
66
+ // `messageField/text/foreground` is the input text color. It also
67
+ // serves as the placeholder color — in mode-aware token sets it
68
+ // resolves to a muted/idle color when empty and shifts darker via
69
+ // the `FormField States` cascade once typed-state tokens land. We
70
+ // never re-route this through another token (e.g. the counter
71
+ // color) because that conflates two semantically distinct tokens.
72
+ const inputTextColor = (0, _figmaVariablesResolver.getVariableByName)('messageField/text/foreground', modes) || '#707275';
73
+ const inputFontFamily = (0, _figmaVariablesResolver.getVariableByName)('messageField/text/fontFamily', modes) || 'JioType Var';
74
+ const inputFontSize = toNumber((0, _figmaVariablesResolver.getVariableByName)('messageField/text/fontSize', modes), 16);
75
+ const inputLineHeight = toNumber((0, _figmaVariablesResolver.getVariableByName)('messageField/text/lineHeight', modes), 21);
76
+ const inputFontWeight = toFontWeight((0, _figmaVariablesResolver.getVariableByName)('messageField/text/fontWeight', modes), '400');
77
+ const counterColor = (0, _figmaVariablesResolver.getVariableByName)('messageField/maxLength/foreground', modes) || '#24262b';
78
+ const counterFontFamily = (0, _figmaVariablesResolver.getVariableByName)('messageField/maxLength/fontFamily', modes) || 'JioType Var';
79
+ const counterFontSize = toNumber((0, _figmaVariablesResolver.getVariableByName)('messageField/maxLength/fontSize', modes), 14);
80
+ const counterLineHeight = toNumber((0, _figmaVariablesResolver.getVariableByName)('messageField/maxLength/lineHeight', modes), 18);
81
+ const counterFontWeight = toFontWeight((0, _figmaVariablesResolver.getVariableByName)('messageField/maxLength/fontWeight', modes), '400');
82
+ return {
83
+ wrapperGap,
84
+ labelColor,
85
+ labelFontFamily,
86
+ labelFontSize,
87
+ labelLineHeight,
88
+ labelFontWeight,
89
+ textareaBackground,
90
+ textareaBorderColor,
91
+ textareaBorderSize,
92
+ textareaRadius,
93
+ textareaPadding,
94
+ textareaHeight,
95
+ textareaGap,
96
+ inputTextColor,
97
+ inputFontFamily,
98
+ inputFontSize,
99
+ inputLineHeight,
100
+ inputFontWeight,
101
+ counterColor,
102
+ counterFontFamily,
103
+ counterFontSize,
104
+ counterLineHeight,
105
+ counterFontWeight
106
+ };
107
+ }, [modes]);
108
+ }
109
+
110
+ // ---------------------------------------------------------------------------
111
+ // Component
112
+ // ---------------------------------------------------------------------------
113
+
114
+ const REQUIRED_INDICATOR_COLOR = '#d93d3d';
115
+ function MessageField({
116
+ label,
117
+ placeholder,
118
+ value,
119
+ defaultValue,
120
+ onChangeText,
121
+ name,
122
+ maxLength,
123
+ showCounter,
124
+ rows,
125
+ isRequired = false,
126
+ isDisabled = false,
127
+ isInvalid = false,
128
+ isReadOnly = false,
129
+ autoFocus = false,
130
+ modes: propModes = _reactUtils.EMPTY_MODES,
131
+ style,
132
+ textareaStyle,
133
+ inputStyle,
134
+ accessibilityLabel,
135
+ accessibilityHint,
136
+ testID,
137
+ onFocus,
138
+ onBlur
139
+ }) {
140
+ const formCtx = (0, _Form.useFormContext)();
141
+ const formError = name && formCtx ? firstError(formCtx.validationErrors[name]) : undefined;
142
+ const resolvedIsInvalid = isInvalid || Boolean(formError);
143
+ const isControlled = value !== undefined;
144
+ const [uncontrolledValue, setUncontrolledValue] = (0, _react.useState)(defaultValue ?? '');
145
+ const currentValue = isControlled ? value : uncontrolledValue;
146
+ const [isFocused, setIsFocused] = (0, _react.useState)(false);
147
+ const interactive = !isDisabled && !isReadOnly;
148
+
149
+ // Ref to the native textarea so tapping anywhere in the (padded) textarea
150
+ // container focuses it on the FIRST tap, fixing the Android "two taps to
151
+ // open the keyboard" issue.
152
+ const inputRef = (0, _react.useRef)(null);
153
+ const focusInput = (0, _react.useCallback)(() => {
154
+ if (!interactive) return;
155
+ inputRef.current?.focus();
156
+ }, [interactive]);
157
+ const {
158
+ modes: globalModes
159
+ } = (0, _JFSThemeProvider.useTokens)();
160
+ const baseModes = (0, _react.useMemo)(() => ({
161
+ ...globalModes,
162
+ ...propModes
163
+ }), [globalModes, propModes]);
164
+
165
+ // FormField States cascade — error > disabled > read only > active (focus)
166
+ // > idle. Always derived from props and locked into the modes object so
167
+ // consumers cannot pass `modes={{ 'FormField States': ... }}` and get out
168
+ // of sync with the component's actual interactive behaviour.
169
+ const stateMode = (0, _react.useMemo)(() => {
170
+ if (resolvedIsInvalid) return 'Error';
171
+ if (isDisabled) return 'Disabled';
172
+ if (isReadOnly) return 'Read Only';
173
+ if (isFocused) return 'Active';
174
+ return 'Idle';
175
+ }, [resolvedIsInvalid, isDisabled, isReadOnly, isFocused]);
176
+ const modes = (0, _react.useMemo)(() => ({
177
+ ...baseModes,
178
+ 'FormField States': stateMode
179
+ }), [baseModes, stateMode]);
180
+ const tokens = useMessageFieldTokens(modes);
181
+
182
+ // ---------- Event handlers ---------------------------------------------
183
+ const handleFocus = (0, _react.useCallback)(e => {
184
+ setIsFocused(true);
185
+ onFocus?.(e);
186
+ }, [onFocus]);
187
+ const handleBlur = (0, _react.useCallback)(e => {
188
+ setIsFocused(false);
189
+ onBlur?.(e);
190
+ }, [onBlur]);
191
+ const handleChangeText = (0, _react.useCallback)(next => {
192
+ if (!isControlled) {
193
+ setUncontrolledValue(next);
194
+ }
195
+ onChangeText?.(next);
196
+ if (name && formCtx) formCtx.onFieldChange(name);
197
+ }, [isControlled, onChangeText, name, formCtx]);
198
+
199
+ // ---------- Derived layout values --------------------------------------
200
+ const computedHeight = (0, _react.useMemo)(() => {
201
+ if (rows && rows > 0) {
202
+ return Math.round(rows * tokens.inputLineHeight + 2 * tokens.textareaPadding);
203
+ }
204
+ return tokens.textareaHeight;
205
+ }, [rows, tokens.inputLineHeight, tokens.textareaPadding, tokens.textareaHeight]);
206
+ const shouldShowCounter = (0, _react.useMemo)(() => {
207
+ if (showCounter === false) return false;
208
+ if (showCounter === true) return true;
209
+ return typeof maxLength === 'number';
210
+ }, [showCounter, maxLength]);
211
+ const counterText = (0, _react.useMemo)(() => {
212
+ const count = currentValue.length;
213
+ if (typeof maxLength === 'number') return `${count}/${maxLength}`;
214
+ return `${count}`;
215
+ }, [currentValue.length, maxLength]);
216
+
217
+ // ---------- Styles -----------------------------------------------------
218
+ const wrapperStyle = (0, _react.useMemo)(() => ({
219
+ gap: tokens.wrapperGap,
220
+ width: '100%'
221
+ }), [tokens.wrapperGap]);
222
+ const labelRowStyle = (0, _react.useMemo)(() => ({
223
+ flexDirection: 'row',
224
+ alignItems: 'baseline'
225
+ }), []);
226
+ const labelTextStyle = (0, _react.useMemo)(() => ({
227
+ color: tokens.labelColor,
228
+ fontFamily: tokens.labelFontFamily,
229
+ fontSize: tokens.labelFontSize,
230
+ lineHeight: tokens.labelLineHeight,
231
+ fontWeight: tokens.labelFontWeight
232
+ }), [tokens.labelColor, tokens.labelFontFamily, tokens.labelFontSize, tokens.labelLineHeight, tokens.labelFontWeight]);
233
+ const requiredIndicatorStyle = (0, _react.useMemo)(() => ({
234
+ ...labelTextStyle,
235
+ color: REQUIRED_INDICATOR_COLOR
236
+ }), [labelTextStyle]);
237
+ const textareaContainerStyle = (0, _react.useMemo)(() => ({
238
+ backgroundColor: tokens.textareaBackground,
239
+ borderColor: tokens.textareaBorderColor,
240
+ borderWidth: tokens.textareaBorderSize,
241
+ borderStyle: 'solid',
242
+ borderRadius: tokens.textareaRadius,
243
+ padding: tokens.textareaPadding,
244
+ height: computedHeight,
245
+ width: '100%',
246
+ overflow: 'hidden',
247
+ // The gap token is for content within the textarea (icons, etc.);
248
+ // we keep it so downstream layouts that pass children align.
249
+ gap: tokens.textareaGap
250
+ }), [tokens.textareaBackground, tokens.textareaBorderColor, tokens.textareaBorderSize, tokens.textareaRadius, tokens.textareaPadding, computedHeight, tokens.textareaGap]);
251
+ const inputTextStyle = (0, _react.useMemo)(() => ({
252
+ flex: 1,
253
+ color: tokens.inputTextColor,
254
+ fontFamily: tokens.inputFontFamily,
255
+ fontSize: tokens.inputFontSize,
256
+ lineHeight: tokens.inputLineHeight,
257
+ fontWeight: tokens.inputFontWeight,
258
+ padding: 0,
259
+ margin: 0,
260
+ textAlignVertical: 'top',
261
+ // Disable the default web focus ring; the textarea border
262
+ // already encodes focus state.
263
+ outlineStyle: 'none',
264
+ outlineWidth: 0,
265
+ outlineColor: 'transparent'
266
+ }), [tokens.inputTextColor, tokens.inputFontFamily, tokens.inputFontSize, tokens.inputLineHeight, tokens.inputFontWeight]);
267
+ const counterTextStyle = (0, _react.useMemo)(() => ({
268
+ color: tokens.counterColor,
269
+ fontFamily: tokens.counterFontFamily,
270
+ fontSize: tokens.counterFontSize,
271
+ lineHeight: tokens.counterLineHeight,
272
+ fontWeight: tokens.counterFontWeight,
273
+ textAlign: 'right',
274
+ width: '100%'
275
+ }), [tokens.counterColor, tokens.counterFontFamily, tokens.counterFontSize, tokens.counterLineHeight, tokens.counterFontWeight]);
276
+ const resolvedA11yLabel = accessibilityLabel || label || placeholder || 'Message field';
277
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
278
+ style: [wrapperStyle, style],
279
+ pointerEvents: isDisabled ? 'none' : 'auto',
280
+ testID: testID,
281
+ accessible: false,
282
+ children: [label != null && label !== '' && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
283
+ style: labelRowStyle,
284
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
285
+ style: labelTextStyle,
286
+ children: label
287
+ }), isRequired && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
288
+ style: requiredIndicatorStyle,
289
+ children: " *"
290
+ })]
291
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
292
+ style: [textareaContainerStyle, textareaStyle],
293
+ onPress: focusInput,
294
+ accessible: false,
295
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
296
+ ref: inputRef,
297
+ multiline: true,
298
+ value: currentValue,
299
+ onChangeText: handleChangeText,
300
+ onFocus: handleFocus,
301
+ onBlur: handleBlur,
302
+ placeholder: placeholder ?? '',
303
+ placeholderTextColor: tokens.inputTextColor,
304
+ editable: interactive,
305
+ maxLength: maxLength,
306
+ autoFocus: autoFocus,
307
+ accessibilityLabel: resolvedA11yLabel,
308
+ accessibilityHint: accessibilityHint,
309
+ style: [inputTextStyle, inputStyle]
310
+ })
311
+ }), shouldShowCounter && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
312
+ style: counterTextStyle,
313
+ accessibilityElementsHidden: true,
314
+ children: counterText
315
+ })]
316
+ });
317
+ }
318
+ var _default = exports.default = MessageField;
@@ -8,9 +8,20 @@ var _react = _interopRequireWildcard(require("react"));
8
8
  var _reactNative = require("react-native");
9
9
  var _reactNativeSvg = _interopRequireWildcard(require("react-native-svg"));
10
10
  var _figmaVariablesResolver = require("../../design-tokens/figma-variables-resolver");
11
+ var _webPlatformUtils = require("../../utils/web-platform-utils");
11
12
  var _reactUtils = require("../../utils/react-utils");
12
13
  var _jsxRuntime = require("react/jsx-runtime");
13
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
+ /** Minimum touch target per iOS HIG / Material accessibility guidance. */
16
+ const MIN_TOUCH_TARGET = 44;
17
+ const IS_IOS = _reactNative.Platform.OS === 'ios';
18
+ const PRESS_DELAY = IS_IOS ? 130 : 0;
19
+ const touchTargetStyle = {
20
+ minWidth: MIN_TOUCH_TARGET,
21
+ minHeight: MIN_TOUCH_TARGET,
22
+ alignItems: 'center',
23
+ justifyContent: 'center'
24
+ };
14
25
  function resolveNavArrowTokens(modes) {
15
26
  const iconColor = (0, _figmaVariablesResolver.getVariableByName)('navArrow/icon/color', modes) || '#24262b';
16
27
  const widthToken = Number((0, _figmaVariablesResolver.getVariableByName)('navArrow/width', modes)) || 6;
@@ -51,6 +62,8 @@ function NavArrow({
51
62
  modes = _reactUtils.EMPTY_MODES,
52
63
  style,
53
64
  accessibilityLabel,
65
+ onPress,
66
+ disabled = false,
54
67
  ...rest
55
68
  }) {
56
69
  const tokens = (0, _react.useMemo)(() => resolveNavArrowTokens(modes), [modes]);
@@ -64,8 +77,7 @@ function NavArrow({
64
77
  borderRadius: tokens.borderRadius,
65
78
  backgroundColor: tokens.backgroundColor,
66
79
  alignItems: 'center',
67
- justifyContent: 'center',
68
- ...(style || {})
80
+ justifyContent: 'center'
69
81
  };
70
82
  const chevronW = isDown ? tokens.iconHeight : tokens.iconWidth;
71
83
  const chevronH = isDown ? tokens.iconWidth : tokens.iconHeight;
@@ -91,26 +103,55 @@ function NavArrow({
91
103
  svgHeight,
92
104
  points
93
105
  };
94
- }, [tokens, direction, style]);
106
+ }, [tokens, direction]);
95
107
  const defaultAccessibilityLabel = accessibilityLabel || (direction === 'Back' ? 'Go back' : direction === 'Forward' ? 'Go forward' : 'Go down');
108
+ const webProps = (0, _webPlatformUtils.usePressableWebSupport)({
109
+ restProps: rest,
110
+ onPress,
111
+ disabled,
112
+ accessibilityLabel: defaultAccessibilityLabel
113
+ });
114
+ const chevron = /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.default, {
115
+ width: computed.svgWidth,
116
+ height: computed.svgHeight,
117
+ viewBox: `0 0 ${computed.svgWidth} ${computed.svgHeight}`,
118
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Polyline, {
119
+ points: computed.points,
120
+ stroke: tokens.iconColor,
121
+ strokeWidth: tokens.strokeWeight,
122
+ strokeLinecap: "round",
123
+ strokeLinejoin: "round",
124
+ fill: "none"
125
+ })
126
+ });
127
+ if (onPress) {
128
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
129
+ onPress: onPress,
130
+ disabled: disabled,
131
+ accessibilityRole: "button",
132
+ accessibilityLabel: defaultAccessibilityLabel,
133
+ accessibilityState: {
134
+ disabled
135
+ },
136
+ unstable_pressDelay: PRESS_DELAY,
137
+ style: ({
138
+ pressed
139
+ }) => [touchTargetStyle, style, pressed && !disabled ? {
140
+ opacity: 0.7
141
+ } : null],
142
+ ...webProps,
143
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
144
+ style: computed.containerStyle,
145
+ children: chevron
146
+ })
147
+ });
148
+ }
96
149
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
97
- style: computed.containerStyle,
150
+ style: [computed.containerStyle, style],
98
151
  accessibilityRole: "image",
99
152
  accessibilityLabel: defaultAccessibilityLabel,
100
153
  ...rest,
101
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.default, {
102
- width: computed.svgWidth,
103
- height: computed.svgHeight,
104
- viewBox: `0 0 ${computed.svgWidth} ${computed.svgHeight}`,
105
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Polyline, {
106
- points: computed.points,
107
- stroke: tokens.iconColor,
108
- strokeWidth: tokens.strokeWeight,
109
- strokeLinecap: "round",
110
- strokeLinejoin: "round",
111
- fill: "none"
112
- })
113
- })
154
+ children: chevron
114
155
  });
115
156
  }
116
157
  var _default = exports.default = /*#__PURE__*/_react.default.memo(NavArrow);