jfs-components 0.0.77 → 0.0.78

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 (70) hide show
  1. package/CHANGELOG.md +17 -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/Checkbox/Checkbox.js +21 -9
  5. package/lib/commonjs/components/DropdownInput/DropdownInput.js +30 -16
  6. package/lib/commonjs/components/ExpandableCheckbox/ExpandableCheckbox.js +167 -0
  7. package/lib/commonjs/components/FormField/FormField.js +14 -1
  8. package/lib/commonjs/components/FullscreenModal/FullscreenModal.js +355 -0
  9. package/lib/commonjs/components/ListItem/ListItem.js +25 -10
  10. package/lib/commonjs/components/MessageField/MessageField.js +318 -0
  11. package/lib/commonjs/components/NavArrow/NavArrow.js +58 -17
  12. package/lib/commonjs/components/Stepper/Step.js +47 -60
  13. package/lib/commonjs/components/Stepper/StepLabel.js +40 -10
  14. package/lib/commonjs/components/Stepper/Stepper.js +15 -17
  15. package/lib/commonjs/components/SuggestiveSearch/SuggestiveSearch.js +487 -0
  16. package/lib/commonjs/components/TextInput/TextInput.js +16 -1
  17. package/lib/commonjs/components/Title/Title.js +10 -2
  18. package/lib/commonjs/components/index.js +28 -0
  19. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
  20. package/lib/commonjs/icons/registry.js +1 -1
  21. package/lib/module/components/Accordion/Accordion.js +56 -56
  22. package/lib/module/components/ActionFooter/ActionFooter.js +50 -4
  23. package/lib/module/components/Checkbox/Checkbox.js +22 -10
  24. package/lib/module/components/DropdownInput/DropdownInput.js +30 -16
  25. package/lib/module/components/ExpandableCheckbox/ExpandableCheckbox.js +161 -0
  26. package/lib/module/components/FormField/FormField.js +16 -3
  27. package/lib/module/components/FullscreenModal/FullscreenModal.js +350 -0
  28. package/lib/module/components/ListItem/ListItem.js +25 -10
  29. package/lib/module/components/MessageField/MessageField.js +313 -0
  30. package/lib/module/components/NavArrow/NavArrow.js +59 -18
  31. package/lib/module/components/Stepper/Step.js +48 -61
  32. package/lib/module/components/Stepper/StepLabel.js +40 -10
  33. package/lib/module/components/Stepper/Stepper.js +15 -17
  34. package/lib/module/components/SuggestiveSearch/SuggestiveSearch.js +481 -0
  35. package/lib/module/components/TextInput/TextInput.js +17 -2
  36. package/lib/module/components/Title/Title.js +10 -2
  37. package/lib/module/components/index.js +4 -0
  38. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
  39. package/lib/module/icons/registry.js +1 -1
  40. package/lib/typescript/src/components/Accordion/Accordion.d.ts +14 -20
  41. package/lib/typescript/src/components/ExpandableCheckbox/ExpandableCheckbox.d.ts +63 -0
  42. package/lib/typescript/src/components/FullscreenModal/FullscreenModal.d.ts +99 -0
  43. package/lib/typescript/src/components/MessageField/MessageField.d.ts +81 -0
  44. package/lib/typescript/src/components/NavArrow/NavArrow.d.ts +10 -5
  45. package/lib/typescript/src/components/Stepper/Step.d.ts +4 -1
  46. package/lib/typescript/src/components/Stepper/StepLabel.d.ts +4 -1
  47. package/lib/typescript/src/components/Stepper/Stepper.d.ts +3 -1
  48. package/lib/typescript/src/components/SuggestiveSearch/SuggestiveSearch.d.ts +123 -0
  49. package/lib/typescript/src/components/index.d.ts +7 -3
  50. package/lib/typescript/src/icons/registry.d.ts +1 -1
  51. package/package.json +1 -1
  52. package/src/components/Accordion/Accordion.tsx +113 -73
  53. package/src/components/ActionFooter/ActionFooter.tsx +56 -4
  54. package/src/components/Checkbox/Checkbox.tsx +22 -9
  55. package/src/components/DropdownInput/DropdownInput.tsx +67 -39
  56. package/src/components/ExpandableCheckbox/ExpandableCheckbox.tsx +237 -0
  57. package/src/components/FormField/FormField.tsx +19 -3
  58. package/src/components/FullscreenModal/FullscreenModal.tsx +414 -0
  59. package/src/components/ListItem/ListItem.tsx +21 -10
  60. package/src/components/MessageField/MessageField.tsx +543 -0
  61. package/src/components/NavArrow/NavArrow.tsx +81 -17
  62. package/src/components/Stepper/Step.tsx +52 -51
  63. package/src/components/Stepper/StepLabel.tsx +46 -9
  64. package/src/components/Stepper/Stepper.tsx +20 -15
  65. package/src/components/SuggestiveSearch/SuggestiveSearch.tsx +756 -0
  66. package/src/components/TextInput/TextInput.tsx +14 -1
  67. package/src/components/Title/Title.tsx +13 -2
  68. package/src/components/index.ts +7 -3
  69. package/src/design-tokens/Coin Variables-variables-full.json +1 -1
  70. package/src/icons/registry.ts +1 -1
@@ -0,0 +1,313 @@
1
+ "use strict";
2
+
3
+ import React, { useCallback, useMemo, useRef, useState } from 'react';
4
+ import { View, Text, Pressable, TextInput as RNTextInput } from 'react-native';
5
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
+ import { useTokens } from '../../design-tokens/JFSThemeProvider';
7
+ import { EMPTY_MODES } from '../../utils/react-utils';
8
+ import { useFormContext } from '../Form/Form';
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Types
12
+ // ---------------------------------------------------------------------------
13
+
14
+ /**
15
+ * Visual state of the textarea. Mirrors the `FormField States` collection so
16
+ * MessageField slots into the same theming pipeline as FormField. The state
17
+ * is always derived from props (`isInvalid`, `isDisabled`, `isReadOnly` and
18
+ * focus) and is locked in `modes['FormField States']` — passing that key in
19
+ * `modes` is intentionally ignored to keep interactive behaviour and visual
20
+ * state in sync.
21
+ */
22
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
23
+ // ---------------------------------------------------------------------------
24
+ // Token helpers
25
+ // ---------------------------------------------------------------------------
26
+
27
+ function toNumber(value, fallback) {
28
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
29
+ if (typeof value === 'string') {
30
+ const parsed = parseFloat(value);
31
+ if (Number.isFinite(parsed)) return parsed;
32
+ }
33
+ return fallback;
34
+ }
35
+ function toFontWeight(value, fallback) {
36
+ if (typeof value === 'number') return value.toString();
37
+ if (typeof value === 'string' && value.length > 0) return value;
38
+ return fallback;
39
+ }
40
+ function firstError(error) {
41
+ if (!error) return undefined;
42
+ if (Array.isArray(error)) return error[0];
43
+ return error;
44
+ }
45
+ function useMessageFieldTokens(modes) {
46
+ return useMemo(() => {
47
+ const wrapperGap = toNumber(getVariableByName('messageField/gap', modes), 8);
48
+ const labelColor = getVariableByName('messageField/label/foreground', modes) || '#000000';
49
+ const labelFontFamily = getVariableByName('messageField/label/fontFamily', modes) || 'JioType Var';
50
+ const labelFontSize = toNumber(getVariableByName('messageField/label/fontSize', modes), 14);
51
+ const labelLineHeight = toNumber(getVariableByName('messageField/label/lineHeight', modes), 17);
52
+ const labelFontWeight = toFontWeight(getVariableByName('messageField/label/fontWeight', modes), '500');
53
+ const textareaBackground = getVariableByName('messageField/textarea/background', modes) || '#ffffff';
54
+ const textareaBorderColor = getVariableByName('messageField/textarea/border/color', modes) || '#b5b6b7';
55
+ const textareaBorderSize = toNumber(getVariableByName('messageField/textarea/border/size', modes), 1.5);
56
+ const textareaRadius = toNumber(getVariableByName('messageField/textarea/radius', modes), 8);
57
+ const textareaPadding = toNumber(getVariableByName('messageField/textarea/padding', modes), 12);
58
+ const textareaHeight = toNumber(getVariableByName('messageField/textarea/height', modes), 108);
59
+ const textareaGap = toNumber(getVariableByName('messageField/textarea/gap', modes), 0);
60
+
61
+ // `messageField/text/foreground` is the input text color. It also
62
+ // serves as the placeholder color — in mode-aware token sets it
63
+ // resolves to a muted/idle color when empty and shifts darker via
64
+ // the `FormField States` cascade once typed-state tokens land. We
65
+ // never re-route this through another token (e.g. the counter
66
+ // color) because that conflates two semantically distinct tokens.
67
+ const inputTextColor = getVariableByName('messageField/text/foreground', modes) || '#707275';
68
+ const inputFontFamily = getVariableByName('messageField/text/fontFamily', modes) || 'JioType Var';
69
+ const inputFontSize = toNumber(getVariableByName('messageField/text/fontSize', modes), 16);
70
+ const inputLineHeight = toNumber(getVariableByName('messageField/text/lineHeight', modes), 21);
71
+ const inputFontWeight = toFontWeight(getVariableByName('messageField/text/fontWeight', modes), '400');
72
+ const counterColor = getVariableByName('messageField/maxLength/foreground', modes) || '#24262b';
73
+ const counterFontFamily = getVariableByName('messageField/maxLength/fontFamily', modes) || 'JioType Var';
74
+ const counterFontSize = toNumber(getVariableByName('messageField/maxLength/fontSize', modes), 14);
75
+ const counterLineHeight = toNumber(getVariableByName('messageField/maxLength/lineHeight', modes), 18);
76
+ const counterFontWeight = toFontWeight(getVariableByName('messageField/maxLength/fontWeight', modes), '400');
77
+ return {
78
+ wrapperGap,
79
+ labelColor,
80
+ labelFontFamily,
81
+ labelFontSize,
82
+ labelLineHeight,
83
+ labelFontWeight,
84
+ textareaBackground,
85
+ textareaBorderColor,
86
+ textareaBorderSize,
87
+ textareaRadius,
88
+ textareaPadding,
89
+ textareaHeight,
90
+ textareaGap,
91
+ inputTextColor,
92
+ inputFontFamily,
93
+ inputFontSize,
94
+ inputLineHeight,
95
+ inputFontWeight,
96
+ counterColor,
97
+ counterFontFamily,
98
+ counterFontSize,
99
+ counterLineHeight,
100
+ counterFontWeight
101
+ };
102
+ }, [modes]);
103
+ }
104
+
105
+ // ---------------------------------------------------------------------------
106
+ // Component
107
+ // ---------------------------------------------------------------------------
108
+
109
+ const REQUIRED_INDICATOR_COLOR = '#d93d3d';
110
+ function MessageField({
111
+ label,
112
+ placeholder,
113
+ value,
114
+ defaultValue,
115
+ onChangeText,
116
+ name,
117
+ maxLength,
118
+ showCounter,
119
+ rows,
120
+ isRequired = false,
121
+ isDisabled = false,
122
+ isInvalid = false,
123
+ isReadOnly = false,
124
+ autoFocus = false,
125
+ modes: propModes = EMPTY_MODES,
126
+ style,
127
+ textareaStyle,
128
+ inputStyle,
129
+ accessibilityLabel,
130
+ accessibilityHint,
131
+ testID,
132
+ onFocus,
133
+ onBlur
134
+ }) {
135
+ const formCtx = useFormContext();
136
+ const formError = name && formCtx ? firstError(formCtx.validationErrors[name]) : undefined;
137
+ const resolvedIsInvalid = isInvalid || Boolean(formError);
138
+ const isControlled = value !== undefined;
139
+ const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue ?? '');
140
+ const currentValue = isControlled ? value : uncontrolledValue;
141
+ const [isFocused, setIsFocused] = useState(false);
142
+ const interactive = !isDisabled && !isReadOnly;
143
+
144
+ // Ref to the native textarea so tapping anywhere in the (padded) textarea
145
+ // container focuses it on the FIRST tap, fixing the Android "two taps to
146
+ // open the keyboard" issue.
147
+ const inputRef = useRef(null);
148
+ const focusInput = useCallback(() => {
149
+ if (!interactive) return;
150
+ inputRef.current?.focus();
151
+ }, [interactive]);
152
+ const {
153
+ modes: globalModes
154
+ } = useTokens();
155
+ const baseModes = useMemo(() => ({
156
+ ...globalModes,
157
+ ...propModes
158
+ }), [globalModes, propModes]);
159
+
160
+ // FormField States cascade — error > disabled > read only > active (focus)
161
+ // > idle. Always derived from props and locked into the modes object so
162
+ // consumers cannot pass `modes={{ 'FormField States': ... }}` and get out
163
+ // of sync with the component's actual interactive behaviour.
164
+ const stateMode = useMemo(() => {
165
+ if (resolvedIsInvalid) return 'Error';
166
+ if (isDisabled) return 'Disabled';
167
+ if (isReadOnly) return 'Read Only';
168
+ if (isFocused) return 'Active';
169
+ return 'Idle';
170
+ }, [resolvedIsInvalid, isDisabled, isReadOnly, isFocused]);
171
+ const modes = useMemo(() => ({
172
+ ...baseModes,
173
+ 'FormField States': stateMode
174
+ }), [baseModes, stateMode]);
175
+ const tokens = useMessageFieldTokens(modes);
176
+
177
+ // ---------- Event handlers ---------------------------------------------
178
+ const handleFocus = useCallback(e => {
179
+ setIsFocused(true);
180
+ onFocus?.(e);
181
+ }, [onFocus]);
182
+ const handleBlur = useCallback(e => {
183
+ setIsFocused(false);
184
+ onBlur?.(e);
185
+ }, [onBlur]);
186
+ const handleChangeText = useCallback(next => {
187
+ if (!isControlled) {
188
+ setUncontrolledValue(next);
189
+ }
190
+ onChangeText?.(next);
191
+ if (name && formCtx) formCtx.onFieldChange(name);
192
+ }, [isControlled, onChangeText, name, formCtx]);
193
+
194
+ // ---------- Derived layout values --------------------------------------
195
+ const computedHeight = useMemo(() => {
196
+ if (rows && rows > 0) {
197
+ return Math.round(rows * tokens.inputLineHeight + 2 * tokens.textareaPadding);
198
+ }
199
+ return tokens.textareaHeight;
200
+ }, [rows, tokens.inputLineHeight, tokens.textareaPadding, tokens.textareaHeight]);
201
+ const shouldShowCounter = useMemo(() => {
202
+ if (showCounter === false) return false;
203
+ if (showCounter === true) return true;
204
+ return typeof maxLength === 'number';
205
+ }, [showCounter, maxLength]);
206
+ const counterText = useMemo(() => {
207
+ const count = currentValue.length;
208
+ if (typeof maxLength === 'number') return `${count}/${maxLength}`;
209
+ return `${count}`;
210
+ }, [currentValue.length, maxLength]);
211
+
212
+ // ---------- Styles -----------------------------------------------------
213
+ const wrapperStyle = useMemo(() => ({
214
+ gap: tokens.wrapperGap,
215
+ width: '100%'
216
+ }), [tokens.wrapperGap]);
217
+ const labelRowStyle = useMemo(() => ({
218
+ flexDirection: 'row',
219
+ alignItems: 'baseline'
220
+ }), []);
221
+ const labelTextStyle = useMemo(() => ({
222
+ color: tokens.labelColor,
223
+ fontFamily: tokens.labelFontFamily,
224
+ fontSize: tokens.labelFontSize,
225
+ lineHeight: tokens.labelLineHeight,
226
+ fontWeight: tokens.labelFontWeight
227
+ }), [tokens.labelColor, tokens.labelFontFamily, tokens.labelFontSize, tokens.labelLineHeight, tokens.labelFontWeight]);
228
+ const requiredIndicatorStyle = useMemo(() => ({
229
+ ...labelTextStyle,
230
+ color: REQUIRED_INDICATOR_COLOR
231
+ }), [labelTextStyle]);
232
+ const textareaContainerStyle = useMemo(() => ({
233
+ backgroundColor: tokens.textareaBackground,
234
+ borderColor: tokens.textareaBorderColor,
235
+ borderWidth: tokens.textareaBorderSize,
236
+ borderStyle: 'solid',
237
+ borderRadius: tokens.textareaRadius,
238
+ padding: tokens.textareaPadding,
239
+ height: computedHeight,
240
+ width: '100%',
241
+ overflow: 'hidden',
242
+ // The gap token is for content within the textarea (icons, etc.);
243
+ // we keep it so downstream layouts that pass children align.
244
+ gap: tokens.textareaGap
245
+ }), [tokens.textareaBackground, tokens.textareaBorderColor, tokens.textareaBorderSize, tokens.textareaRadius, tokens.textareaPadding, computedHeight, tokens.textareaGap]);
246
+ const inputTextStyle = useMemo(() => ({
247
+ flex: 1,
248
+ color: tokens.inputTextColor,
249
+ fontFamily: tokens.inputFontFamily,
250
+ fontSize: tokens.inputFontSize,
251
+ lineHeight: tokens.inputLineHeight,
252
+ fontWeight: tokens.inputFontWeight,
253
+ padding: 0,
254
+ margin: 0,
255
+ textAlignVertical: 'top',
256
+ // Disable the default web focus ring; the textarea border
257
+ // already encodes focus state.
258
+ outlineStyle: 'none',
259
+ outlineWidth: 0,
260
+ outlineColor: 'transparent'
261
+ }), [tokens.inputTextColor, tokens.inputFontFamily, tokens.inputFontSize, tokens.inputLineHeight, tokens.inputFontWeight]);
262
+ const counterTextStyle = useMemo(() => ({
263
+ color: tokens.counterColor,
264
+ fontFamily: tokens.counterFontFamily,
265
+ fontSize: tokens.counterFontSize,
266
+ lineHeight: tokens.counterLineHeight,
267
+ fontWeight: tokens.counterFontWeight,
268
+ textAlign: 'right',
269
+ width: '100%'
270
+ }), [tokens.counterColor, tokens.counterFontFamily, tokens.counterFontSize, tokens.counterLineHeight, tokens.counterFontWeight]);
271
+ const resolvedA11yLabel = accessibilityLabel || label || placeholder || 'Message field';
272
+ return /*#__PURE__*/_jsxs(View, {
273
+ style: [wrapperStyle, style],
274
+ pointerEvents: isDisabled ? 'none' : 'auto',
275
+ testID: testID,
276
+ accessible: false,
277
+ children: [label != null && label !== '' && /*#__PURE__*/_jsxs(View, {
278
+ style: labelRowStyle,
279
+ children: [/*#__PURE__*/_jsx(Text, {
280
+ style: labelTextStyle,
281
+ children: label
282
+ }), isRequired && /*#__PURE__*/_jsx(Text, {
283
+ style: requiredIndicatorStyle,
284
+ children: " *"
285
+ })]
286
+ }), /*#__PURE__*/_jsx(Pressable, {
287
+ style: [textareaContainerStyle, textareaStyle],
288
+ onPress: focusInput,
289
+ accessible: false,
290
+ children: /*#__PURE__*/_jsx(RNTextInput, {
291
+ ref: inputRef,
292
+ multiline: true,
293
+ value: currentValue,
294
+ onChangeText: handleChangeText,
295
+ onFocus: handleFocus,
296
+ onBlur: handleBlur,
297
+ placeholder: placeholder ?? '',
298
+ placeholderTextColor: tokens.inputTextColor,
299
+ editable: interactive,
300
+ maxLength: maxLength,
301
+ autoFocus: autoFocus,
302
+ accessibilityLabel: resolvedA11yLabel,
303
+ accessibilityHint: accessibilityHint,
304
+ style: [inputTextStyle, inputStyle]
305
+ })
306
+ }), shouldShowCounter && /*#__PURE__*/_jsx(Text, {
307
+ style: counterTextStyle,
308
+ accessibilityElementsHidden: true,
309
+ children: counterText
310
+ })]
311
+ });
312
+ }
313
+ export default MessageField;
@@ -1,11 +1,22 @@
1
1
  "use strict";
2
2
 
3
3
  import React, { useMemo } from 'react';
4
- import { View } from 'react-native';
4
+ import { Platform, Pressable, View } from 'react-native';
5
5
  import Svg, { Polyline } from 'react-native-svg';
6
6
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
7
+ import { usePressableWebSupport } from '../../utils/web-platform-utils';
7
8
  import { EMPTY_MODES } from '../../utils/react-utils';
8
9
  import { jsx as _jsx } from "react/jsx-runtime";
10
+ /** Minimum touch target per iOS HIG / Material accessibility guidance. */
11
+ const MIN_TOUCH_TARGET = 44;
12
+ const IS_IOS = Platform.OS === 'ios';
13
+ const PRESS_DELAY = IS_IOS ? 130 : 0;
14
+ const touchTargetStyle = {
15
+ minWidth: MIN_TOUCH_TARGET,
16
+ minHeight: MIN_TOUCH_TARGET,
17
+ alignItems: 'center',
18
+ justifyContent: 'center'
19
+ };
9
20
  function resolveNavArrowTokens(modes) {
10
21
  const iconColor = getVariableByName('navArrow/icon/color', modes) || '#24262b';
11
22
  const widthToken = Number(getVariableByName('navArrow/width', modes)) || 6;
@@ -46,6 +57,8 @@ function NavArrow({
46
57
  modes = EMPTY_MODES,
47
58
  style,
48
59
  accessibilityLabel,
60
+ onPress,
61
+ disabled = false,
49
62
  ...rest
50
63
  }) {
51
64
  const tokens = useMemo(() => resolveNavArrowTokens(modes), [modes]);
@@ -59,8 +72,7 @@ function NavArrow({
59
72
  borderRadius: tokens.borderRadius,
60
73
  backgroundColor: tokens.backgroundColor,
61
74
  alignItems: 'center',
62
- justifyContent: 'center',
63
- ...(style || {})
75
+ justifyContent: 'center'
64
76
  };
65
77
  const chevronW = isDown ? tokens.iconHeight : tokens.iconWidth;
66
78
  const chevronH = isDown ? tokens.iconWidth : tokens.iconHeight;
@@ -86,26 +98,55 @@ function NavArrow({
86
98
  svgHeight,
87
99
  points
88
100
  };
89
- }, [tokens, direction, style]);
101
+ }, [tokens, direction]);
90
102
  const defaultAccessibilityLabel = accessibilityLabel || (direction === 'Back' ? 'Go back' : direction === 'Forward' ? 'Go forward' : 'Go down');
103
+ const webProps = usePressableWebSupport({
104
+ restProps: rest,
105
+ onPress,
106
+ disabled,
107
+ accessibilityLabel: defaultAccessibilityLabel
108
+ });
109
+ const chevron = /*#__PURE__*/_jsx(Svg, {
110
+ width: computed.svgWidth,
111
+ height: computed.svgHeight,
112
+ viewBox: `0 0 ${computed.svgWidth} ${computed.svgHeight}`,
113
+ children: /*#__PURE__*/_jsx(Polyline, {
114
+ points: computed.points,
115
+ stroke: tokens.iconColor,
116
+ strokeWidth: tokens.strokeWeight,
117
+ strokeLinecap: "round",
118
+ strokeLinejoin: "round",
119
+ fill: "none"
120
+ })
121
+ });
122
+ if (onPress) {
123
+ return /*#__PURE__*/_jsx(Pressable, {
124
+ onPress: onPress,
125
+ disabled: disabled,
126
+ accessibilityRole: "button",
127
+ accessibilityLabel: defaultAccessibilityLabel,
128
+ accessibilityState: {
129
+ disabled
130
+ },
131
+ unstable_pressDelay: PRESS_DELAY,
132
+ style: ({
133
+ pressed
134
+ }) => [touchTargetStyle, style, pressed && !disabled ? {
135
+ opacity: 0.7
136
+ } : null],
137
+ ...webProps,
138
+ children: /*#__PURE__*/_jsx(View, {
139
+ style: computed.containerStyle,
140
+ children: chevron
141
+ })
142
+ });
143
+ }
91
144
  return /*#__PURE__*/_jsx(View, {
92
- style: computed.containerStyle,
145
+ style: [computed.containerStyle, style],
93
146
  accessibilityRole: "image",
94
147
  accessibilityLabel: defaultAccessibilityLabel,
95
148
  ...rest,
96
- children: /*#__PURE__*/_jsx(Svg, {
97
- width: computed.svgWidth,
98
- height: computed.svgHeight,
99
- viewBox: `0 0 ${computed.svgWidth} ${computed.svgHeight}`,
100
- children: /*#__PURE__*/_jsx(Polyline, {
101
- points: computed.points,
102
- stroke: tokens.iconColor,
103
- strokeWidth: tokens.strokeWeight,
104
- strokeLinecap: "round",
105
- strokeLinejoin: "round",
106
- fill: "none"
107
- })
108
- })
149
+ children: chevron
109
150
  });
110
151
  }
111
152
  export default /*#__PURE__*/React.memo(NavArrow);
@@ -5,56 +5,52 @@ import { View, Text } from 'react-native';
5
5
  import Svg, { Path } from 'react-native-svg';
6
6
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
7
7
  import { StepLabel } from './StepLabel';
8
- import { EMPTY_MODES } from '../../utils/react-utils';
8
+ import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils';
9
9
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
10
  export function Step({
11
11
  children,
12
12
  modes = EMPTY_MODES,
13
13
  style,
14
14
  index = 0,
15
+ showLine = true,
15
16
  connectorStyle,
16
17
  title,
17
18
  supportingText,
19
+ metaText,
18
20
  subtitle = true,
21
+ meta = true,
19
22
  status = 'number'
20
23
  }) {
21
- // Container styles
22
24
  const minHeight = Number(getVariableByName('steperItem/minHeight', modes)) || 52;
23
- const gap = Number(getVariableByName('steperItem/gap', modes)) || 8;
24
-
25
- // Indicator dimensions and styles
26
- const indicatorSize = Number(getVariableByName('stepIndicator/size', modes)) || 24;
25
+ const gap = Number(getVariableByName('steperItem/gap', modes)) || 16;
26
+ const indicatorSize = Number(getVariableByName('stepIndicator/size', modes)) || 36;
27
27
  const indicatorRadius = Number(getVariableByName('stepIndicator/radius', modes)) || 9999;
28
- const indicatorBg = getVariableByName('stepIndicator/background', modes) || '#5c00b5';
29
- const indicatorBorderColor = getVariableByName('stepIndicator/border/color', modes) || '#5c00b5';
28
+ const indicatorBg = getVariableByName('stepIndicator/background', modes) || '#5d00b5';
29
+ const indicatorBorderColor = getVariableByName('stepIndicator/border/color', modes) || '#5d00b5';
30
30
  const indicatorBorderSize = Number(getVariableByName('stepIndicator/border/size', modes)) || 1;
31
31
  const iconColor = getVariableByName('stepIndicator/icon/color', modes) || '#ffffff';
32
-
33
- // Label styles (for number)
32
+ const stepStatusSize = Number(getVariableByName('stepStatus/size', modes)) || 18;
34
33
  const labelFontSize = Number(getVariableByName('stepIndicator/label/fontSize', modes)) || 12;
35
34
  const labelFontFamily = getVariableByName('stepIndicator/label/fontFamily', modes) || undefined;
36
35
  const labelLineHeight = Number(getVariableByName('stepIndicator/label/lineHeight', modes)) || 14;
37
36
  const labelFontWeight = getVariableByName('stepIndicator/label/fontWeight', modes) || '500';
38
-
39
- // Vertical line styles
40
37
  const lineSize = Number(getVariableByName('steperItem/lineSize', modes)) || 2;
41
- const lineColor = getVariableByName('steperItem/line', modes) || '#5c00b5';
38
+ const lineColor = getVariableByName('steperItem/line', modes) || '#5d00b5';
42
39
  const badgeWrapGap = Number(getVariableByName('steperItem/badgeWrap/gap', modes)) || 2;
43
40
  const containerStyle = {
44
41
  flexDirection: 'row',
45
42
  minHeight,
43
+ gap,
46
44
  ...style
47
45
  };
48
-
49
- // FIX: This wrapper should NOT expand. It should be exact width of the indicator.
50
46
  const indicatorWrapperStyle = {
51
47
  flexDirection: 'column',
52
48
  alignItems: 'center',
53
49
  width: indicatorSize,
54
50
  flexGrow: 0,
55
- // Do NOT grow
56
51
  flexShrink: 0,
57
- gap: badgeWrapGap
52
+ gap: badgeWrapGap,
53
+ alignSelf: 'stretch'
58
54
  };
59
55
  const indicatorStyle = {
60
56
  width: indicatorSize,
@@ -67,6 +63,12 @@ export function Step({
67
63
  justifyContent: 'center',
68
64
  overflow: 'hidden'
69
65
  };
66
+ const stepStatusContainerStyle = {
67
+ width: stepStatusSize,
68
+ height: stepStatusSize,
69
+ alignItems: 'center',
70
+ justifyContent: 'center'
71
+ };
70
72
  const labelStyle = {
71
73
  color: iconColor,
72
74
  fontSize: labelFontSize,
@@ -75,24 +77,19 @@ export function Step({
75
77
  lineHeight: labelLineHeight,
76
78
  textAlign: 'center'
77
79
  };
78
-
79
- // Combine base line style with injected connectorStyle
80
80
  const lineStyle = {
81
81
  width: lineSize,
82
82
  backgroundColor: lineColor,
83
83
  flexGrow: 1,
84
- // Line should take up remaining vertical space in this column
85
- ...connectorStyle // Allow overriding display, color, etc.
84
+ minHeight: 1,
85
+ ...connectorStyle
86
86
  };
87
-
88
- // Helper for icons
89
87
  const renderIcon = () => {
90
88
  switch (status) {
91
89
  case 'complete':
92
- // Checkmark
93
90
  return /*#__PURE__*/_jsx(Svg, {
94
- width: 12,
95
- height: 12,
91
+ width: stepStatusSize,
92
+ height: stepStatusSize,
96
93
  viewBox: "0 0 24 24",
97
94
  fill: "none",
98
95
  children: /*#__PURE__*/_jsx(Path, {
@@ -104,10 +101,9 @@ export function Step({
104
101
  })
105
102
  });
106
103
  case 'error':
107
- // X mark
108
104
  return /*#__PURE__*/_jsx(Svg, {
109
- width: 12,
110
- height: 12,
105
+ width: stepStatusSize,
106
+ height: stepStatusSize,
111
107
  viewBox: "0 0 24 24",
112
108
  fill: "none",
113
109
  children: /*#__PURE__*/_jsx(Path, {
@@ -119,10 +115,9 @@ export function Step({
119
115
  })
120
116
  });
121
117
  case 'warning':
122
- // Exclamation / Triangle
123
118
  return /*#__PURE__*/_jsx(Svg, {
124
- width: 12,
125
- height: 12,
119
+ width: stepStatusSize,
120
+ height: stepStatusSize,
126
121
  viewBox: "0 0 24 24",
127
122
  fill: "none",
128
123
  children: /*#__PURE__*/_jsx(Path, {
@@ -141,46 +136,38 @@ export function Step({
141
136
  });
142
137
  }
143
138
  };
144
-
145
- // Clone children to pass modes if they are StepLabel
146
- const childrenWithProps = React.Children.map(children, child => {
147
- if (/*#__PURE__*/React.isValidElement(child)) {
148
- return /*#__PURE__*/React.cloneElement(child, {
149
- modes
150
- });
151
- }
152
- return child;
153
- });
154
-
155
- // Determine if we have content to pad
156
- // Default padding bottom to 16 if line is shown, but also respect connectorStyle display
157
- // If display is none, we probably don't want the padding bottom either.
158
- const isLineHidden = connectorStyle?.display === 'none';
159
- const contentPaddingBottom = isLineHidden ? 0 : 16;
139
+ const hasSlotChildren = React.Children.count(children) > 0;
160
140
  return /*#__PURE__*/_jsxs(View, {
161
141
  style: containerStyle,
162
142
  children: [/*#__PURE__*/_jsxs(View, {
163
143
  style: indicatorWrapperStyle,
164
144
  children: [/*#__PURE__*/_jsx(View, {
165
145
  style: indicatorStyle,
166
- children: renderIcon()
167
- }), /*#__PURE__*/_jsx(View, {
146
+ children: /*#__PURE__*/_jsx(View, {
147
+ style: stepStatusContainerStyle,
148
+ children: renderIcon()
149
+ })
150
+ }), showLine ? /*#__PURE__*/_jsx(View, {
168
151
  style: lineStyle
169
- })]
170
- }), /*#__PURE__*/_jsx(View, {
171
- style: {
172
- width: gap
173
- }
152
+ }) : null]
174
153
  }), /*#__PURE__*/_jsx(View, {
175
154
  style: {
176
- flex: 1,
177
- paddingBottom: contentPaddingBottom
155
+ flex: 1
178
156
  },
179
- children: title ? /*#__PURE__*/_jsx(StepLabel, {
180
- title: title,
181
- supportingText: subtitle ? supportingText || undefined : undefined,
157
+ children: hasSlotChildren ? cloneChildrenWithModes(children, modes) : /*#__PURE__*/_jsx(StepLabel, {
158
+ ...(title !== undefined ? {
159
+ title
160
+ } : {}),
161
+ ...(supportingText !== undefined ? {
162
+ supportingText
163
+ } : {}),
164
+ ...(metaText !== undefined ? {
165
+ metaText
166
+ } : {}),
167
+ subtitle: subtitle,
168
+ meta: meta,
182
169
  modes: modes
183
- }) : childrenWithProps
170
+ })
184
171
  })]
185
172
  });
186
173
  }