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.
- package/CHANGELOG.md +28 -0
- package/lib/commonjs/components/Accordion/Accordion.js +55 -55
- package/lib/commonjs/components/ActionFooter/ActionFooter.js +48 -2
- package/lib/commonjs/components/Attached/Attached.js +144 -0
- package/lib/commonjs/components/Card/Card.js +25 -2
- package/lib/commonjs/components/Checkbox/Checkbox.js +21 -9
- package/lib/commonjs/components/DropdownInput/DropdownInput.js +30 -16
- package/lib/commonjs/components/ExpandableCheckbox/ExpandableCheckbox.js +167 -0
- package/lib/commonjs/components/FormField/FormField.js +14 -1
- package/lib/commonjs/components/FullscreenModal/FullscreenModal.js +353 -0
- package/lib/commonjs/components/ListItem/ListItem.js +46 -24
- package/lib/commonjs/components/MessageField/MessageField.js +318 -0
- package/lib/commonjs/components/NavArrow/NavArrow.js +58 -17
- package/lib/commonjs/components/PlanComparisonCard/PlanComparisonCard.js +328 -0
- package/lib/commonjs/components/Slot/Slot.js +73 -0
- package/lib/commonjs/components/Stepper/Step.js +47 -60
- package/lib/commonjs/components/Stepper/StepLabel.js +40 -10
- package/lib/commonjs/components/Stepper/Stepper.js +15 -17
- package/lib/commonjs/components/SuggestiveSearch/SuggestiveSearch.js +487 -0
- package/lib/commonjs/components/TextInput/TextInput.js +16 -1
- package/lib/commonjs/components/Title/Title.js +10 -2
- package/lib/commonjs/components/index.js +49 -0
- package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/module/components/Accordion/Accordion.js +56 -56
- package/lib/module/components/ActionFooter/ActionFooter.js +50 -4
- package/lib/module/components/Attached/Attached.js +139 -0
- package/lib/module/components/Card/Card.js +25 -2
- package/lib/module/components/Checkbox/Checkbox.js +22 -10
- package/lib/module/components/DropdownInput/DropdownInput.js +30 -16
- package/lib/module/components/ExpandableCheckbox/ExpandableCheckbox.js +161 -0
- package/lib/module/components/FormField/FormField.js +16 -3
- package/lib/module/components/FullscreenModal/FullscreenModal.js +348 -0
- package/lib/module/components/ListItem/ListItem.js +46 -24
- package/lib/module/components/MessageField/MessageField.js +313 -0
- package/lib/module/components/NavArrow/NavArrow.js +59 -18
- package/lib/module/components/PlanComparisonCard/PlanComparisonCard.js +322 -0
- package/lib/module/components/Slot/Slot.js +68 -0
- package/lib/module/components/Stepper/Step.js +48 -61
- package/lib/module/components/Stepper/StepLabel.js +40 -10
- package/lib/module/components/Stepper/Stepper.js +15 -17
- package/lib/module/components/SuggestiveSearch/SuggestiveSearch.js +481 -0
- package/lib/module/components/TextInput/TextInput.js +17 -2
- package/lib/module/components/Title/Title.js +10 -2
- package/lib/module/components/index.js +7 -0
- package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/module/icons/registry.js +1 -1
- package/lib/typescript/src/components/Accordion/Accordion.d.ts +14 -20
- package/lib/typescript/src/components/Attached/Attached.d.ts +61 -0
- package/lib/typescript/src/components/Card/Card.d.ts +9 -2
- package/lib/typescript/src/components/ExpandableCheckbox/ExpandableCheckbox.d.ts +63 -0
- package/lib/typescript/src/components/FullscreenModal/FullscreenModal.d.ts +99 -0
- package/lib/typescript/src/components/ListItem/ListItem.d.ts +15 -5
- package/lib/typescript/src/components/MessageField/MessageField.d.ts +81 -0
- package/lib/typescript/src/components/NavArrow/NavArrow.d.ts +10 -5
- package/lib/typescript/src/components/PlanComparisonCard/PlanComparisonCard.d.ts +64 -0
- package/lib/typescript/src/components/Slot/Slot.d.ts +52 -0
- package/lib/typescript/src/components/Stepper/Step.d.ts +4 -1
- package/lib/typescript/src/components/Stepper/StepLabel.d.ts +4 -1
- package/lib/typescript/src/components/Stepper/Stepper.d.ts +3 -1
- package/lib/typescript/src/components/SuggestiveSearch/SuggestiveSearch.d.ts +123 -0
- package/lib/typescript/src/components/index.d.ts +10 -3
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/Accordion/Accordion.tsx +113 -73
- package/src/components/ActionFooter/ActionFooter.tsx +56 -4
- package/src/components/Attached/Attached.tsx +181 -0
- package/src/components/Card/Card.tsx +28 -1
- package/src/components/Checkbox/Checkbox.tsx +22 -9
- package/src/components/DropdownInput/DropdownInput.tsx +67 -39
- package/src/components/ExpandableCheckbox/ExpandableCheckbox.tsx +237 -0
- package/src/components/FormField/FormField.tsx +19 -3
- package/src/components/FullscreenModal/FullscreenModal.tsx +414 -0
- package/src/components/ListItem/ListItem.tsx +55 -25
- package/src/components/MessageField/MessageField.tsx +543 -0
- package/src/components/NavArrow/NavArrow.tsx +81 -17
- package/src/components/PlanComparisonCard/PlanComparisonCard.tsx +426 -0
- package/src/components/Slot/Slot.tsx +91 -0
- package/src/components/Stepper/Step.tsx +52 -51
- package/src/components/Stepper/StepLabel.tsx +46 -9
- package/src/components/Stepper/Stepper.tsx +20 -15
- package/src/components/SuggestiveSearch/SuggestiveSearch.tsx +756 -0
- package/src/components/TextInput/TextInput.tsx +14 -1
- package/src/components/Title/Title.tsx +13 -2
- package/src/components/index.ts +10 -3
- package/src/design-tokens/Coin Variables-variables-full.json +1 -1
- 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
|
|
25
|
-
// overridden by external modes. Frozen so identity is stable across
|
|
26
|
-
|
|
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',
|
|
52
|
-
const titleFontSize = (0, _figmaVariablesResolver.getVariableByName)('listItem/title/fontSize',
|
|
53
|
-
const titleLineHeight = (0, _figmaVariablesResolver.getVariableByName)('listItem/title/lineHeight',
|
|
54
|
-
const titleFontFamily = (0, _figmaVariablesResolver.getVariableByName)('listItem/title/fontFamily',
|
|
55
|
-
const titleFontWeightRaw = (0, _figmaVariablesResolver.getVariableByName)('listItem/title/fontWeight',
|
|
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',
|
|
58
|
-
const supportFontSize = (0, _figmaVariablesResolver.getVariableByName)('listItem/supportText/fontSize',
|
|
59
|
-
const supportLineHeight = (0, _figmaVariablesResolver.getVariableByName)('listItem/supportText/lineHeight',
|
|
60
|
-
const supportFontFamily = (0, _figmaVariablesResolver.getVariableByName)('listItem/supportText/fontFamily',
|
|
61
|
-
const supportFontWeightRaw = (0, _figmaVariablesResolver.getVariableByName)('listItem/supportText/fontWeight',
|
|
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 "
|
|
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
|
|
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.
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
}, [
|
|
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
|
-
}),
|
|
289
|
+
}), processedTrailing ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
268
290
|
style: tokens.trailingWrapperStyle,
|
|
269
|
-
children:
|
|
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
|
|
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:
|
|
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);
|