jfs-components 0.0.74 → 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 (146) hide show
  1. package/CHANGELOG.md +109 -0
  2. package/lib/commonjs/components/Accordion/Accordion.js +55 -55
  3. package/lib/commonjs/components/ActionFooter/ActionFooter.js +193 -82
  4. package/lib/commonjs/components/Avatar/Avatar.js +20 -0
  5. package/lib/commonjs/components/Badge/Badge.js +23 -0
  6. package/lib/commonjs/components/Button/Button.js +37 -0
  7. package/lib/commonjs/components/Checkbox/Checkbox.js +21 -9
  8. package/lib/commonjs/components/DropdownInput/DropdownInput.js +30 -16
  9. package/lib/commonjs/components/ExpandableCheckbox/ExpandableCheckbox.js +167 -0
  10. package/lib/commonjs/components/FormField/FormField.js +14 -1
  11. package/lib/commonjs/components/FullscreenModal/FullscreenModal.js +355 -0
  12. package/lib/commonjs/components/IconButton/IconButton.js +20 -0
  13. package/lib/commonjs/components/Image/Image.js +26 -1
  14. package/lib/commonjs/components/ListItem/ListItem.js +25 -10
  15. package/lib/commonjs/components/LottiePlayer/LottiePlayer.js +116 -0
  16. package/lib/commonjs/components/LottiePlayer/LottiePlayer.web.js +82 -0
  17. package/lib/commonjs/components/LottiePlayer/loadNativeLottieView.js +74 -0
  18. package/lib/commonjs/components/LottiePlayer/loadWebLottieView.js +50 -0
  19. package/lib/commonjs/components/MessageField/MessageField.js +318 -0
  20. package/lib/commonjs/components/NavArrow/NavArrow.js +58 -17
  21. package/lib/commonjs/components/PageHero/PageHero.js +41 -5
  22. package/lib/commonjs/components/RechargeCard/RechargeCard.js +32 -17
  23. package/lib/commonjs/components/Stepper/Step.js +47 -60
  24. package/lib/commonjs/components/Stepper/StepLabel.js +40 -10
  25. package/lib/commonjs/components/Stepper/Stepper.js +15 -17
  26. package/lib/commonjs/components/SuggestiveSearch/SuggestiveSearch.js +487 -0
  27. package/lib/commonjs/components/Text/Text.js +31 -1
  28. package/lib/commonjs/components/TextInput/TextInput.js +16 -1
  29. package/lib/commonjs/components/Title/Title.js +10 -2
  30. package/lib/commonjs/components/index.js +35 -0
  31. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
  32. package/lib/commonjs/icons/Icon.js +16 -0
  33. package/lib/commonjs/icons/registry.js +1 -1
  34. package/lib/commonjs/index.js +12 -0
  35. package/lib/commonjs/skeleton/Skeleton.js +234 -0
  36. package/lib/commonjs/skeleton/SkeletonGroup.js +140 -0
  37. package/lib/commonjs/skeleton/index.js +58 -0
  38. package/lib/commonjs/skeleton/shimmer-tokens.js +189 -0
  39. package/lib/commonjs/skeleton/useReducedMotion.js +64 -0
  40. package/lib/module/components/Accordion/Accordion.js +56 -56
  41. package/lib/module/components/ActionFooter/ActionFooter.js +193 -83
  42. package/lib/module/components/Avatar/Avatar.js +19 -0
  43. package/lib/module/components/Badge/Badge.js +23 -0
  44. package/lib/module/components/Button/Button.js +37 -0
  45. package/lib/module/components/Checkbox/Checkbox.js +22 -10
  46. package/lib/module/components/DropdownInput/DropdownInput.js +30 -16
  47. package/lib/module/components/ExpandableCheckbox/ExpandableCheckbox.js +161 -0
  48. package/lib/module/components/FormField/FormField.js +16 -3
  49. package/lib/module/components/FullscreenModal/FullscreenModal.js +350 -0
  50. package/lib/module/components/IconButton/IconButton.js +20 -0
  51. package/lib/module/components/Image/Image.js +25 -1
  52. package/lib/module/components/ListItem/ListItem.js +25 -10
  53. package/lib/module/components/LottiePlayer/LottiePlayer.js +111 -0
  54. package/lib/module/components/LottiePlayer/LottiePlayer.web.js +77 -0
  55. package/lib/module/components/LottiePlayer/loadNativeLottieView.js +69 -0
  56. package/lib/module/components/LottiePlayer/loadWebLottieView.js +45 -0
  57. package/lib/module/components/MessageField/MessageField.js +313 -0
  58. package/lib/module/components/NavArrow/NavArrow.js +59 -18
  59. package/lib/module/components/PageHero/PageHero.js +41 -5
  60. package/lib/module/components/RechargeCard/RechargeCard.js +33 -17
  61. package/lib/module/components/Stepper/Step.js +48 -61
  62. package/lib/module/components/Stepper/StepLabel.js +40 -10
  63. package/lib/module/components/Stepper/Stepper.js +15 -17
  64. package/lib/module/components/SuggestiveSearch/SuggestiveSearch.js +481 -0
  65. package/lib/module/components/Text/Text.js +31 -1
  66. package/lib/module/components/TextInput/TextInput.js +17 -2
  67. package/lib/module/components/Title/Title.js +10 -2
  68. package/lib/module/components/index.js +5 -0
  69. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
  70. package/lib/module/icons/Icon.js +16 -0
  71. package/lib/module/icons/registry.js +1 -1
  72. package/lib/module/index.js +2 -1
  73. package/lib/module/skeleton/Skeleton.js +229 -0
  74. package/lib/module/skeleton/SkeletonGroup.js +133 -0
  75. package/lib/module/skeleton/index.js +6 -0
  76. package/lib/module/skeleton/shimmer-tokens.js +181 -0
  77. package/lib/module/skeleton/useReducedMotion.js +61 -0
  78. package/lib/typescript/src/components/Accordion/Accordion.d.ts +14 -20
  79. package/lib/typescript/src/components/ActionFooter/ActionFooter.d.ts +26 -21
  80. package/lib/typescript/src/components/Avatar/Avatar.d.ts +7 -1
  81. package/lib/typescript/src/components/Badge/Badge.d.ts +7 -1
  82. package/lib/typescript/src/components/Button/Button.d.ts +8 -1
  83. package/lib/typescript/src/components/ExpandableCheckbox/ExpandableCheckbox.d.ts +63 -0
  84. package/lib/typescript/src/components/FullscreenModal/FullscreenModal.d.ts +99 -0
  85. package/lib/typescript/src/components/IconButton/IconButton.d.ts +7 -1
  86. package/lib/typescript/src/components/Image/Image.d.ts +8 -1
  87. package/lib/typescript/src/components/LottiePlayer/LottiePlayer.d.ts +85 -0
  88. package/lib/typescript/src/components/LottiePlayer/LottiePlayer.web.d.ts +28 -0
  89. package/lib/typescript/src/components/LottiePlayer/loadNativeLottieView.d.ts +11 -0
  90. package/lib/typescript/src/components/LottiePlayer/loadWebLottieView.d.ts +11 -0
  91. package/lib/typescript/src/components/MessageField/MessageField.d.ts +81 -0
  92. package/lib/typescript/src/components/NavArrow/NavArrow.d.ts +10 -5
  93. package/lib/typescript/src/components/PageHero/PageHero.d.ts +31 -5
  94. package/lib/typescript/src/components/Stepper/Step.d.ts +4 -1
  95. package/lib/typescript/src/components/Stepper/StepLabel.d.ts +4 -1
  96. package/lib/typescript/src/components/Stepper/Stepper.d.ts +3 -1
  97. package/lib/typescript/src/components/SuggestiveSearch/SuggestiveSearch.d.ts +123 -0
  98. package/lib/typescript/src/components/Text/Text.d.ts +20 -1
  99. package/lib/typescript/src/components/index.d.ts +8 -3
  100. package/lib/typescript/src/icons/Icon.d.ts +7 -1
  101. package/lib/typescript/src/icons/registry.d.ts +1 -1
  102. package/lib/typescript/src/index.d.ts +1 -0
  103. package/lib/typescript/src/skeleton/Skeleton.d.ts +60 -0
  104. package/lib/typescript/src/skeleton/SkeletonGroup.d.ts +78 -0
  105. package/lib/typescript/src/skeleton/index.d.ts +5 -0
  106. package/lib/typescript/src/skeleton/shimmer-tokens.d.ts +160 -0
  107. package/lib/typescript/src/skeleton/useReducedMotion.d.ts +15 -0
  108. package/package.json +11 -1
  109. package/src/components/Accordion/Accordion.tsx +113 -73
  110. package/src/components/ActionFooter/ActionFooter.tsx +210 -92
  111. package/src/components/Avatar/Avatar.tsx +26 -0
  112. package/src/components/Badge/Badge.tsx +27 -0
  113. package/src/components/Button/Button.tsx +40 -0
  114. package/src/components/Checkbox/Checkbox.tsx +22 -9
  115. package/src/components/DropdownInput/DropdownInput.tsx +67 -39
  116. package/src/components/ExpandableCheckbox/ExpandableCheckbox.tsx +237 -0
  117. package/src/components/FormField/FormField.tsx +19 -3
  118. package/src/components/FullscreenModal/FullscreenModal.tsx +414 -0
  119. package/src/components/IconButton/IconButton.tsx +27 -0
  120. package/src/components/Image/Image.tsx +25 -0
  121. package/src/components/ListItem/ListItem.tsx +21 -10
  122. package/src/components/LottiePlayer/LottiePlayer.tsx +145 -0
  123. package/src/components/LottiePlayer/LottiePlayer.web.tsx +94 -0
  124. package/src/components/LottiePlayer/loadNativeLottieView.tsx +87 -0
  125. package/src/components/LottiePlayer/loadWebLottieView.tsx +64 -0
  126. package/src/components/MessageField/MessageField.tsx +543 -0
  127. package/src/components/NavArrow/NavArrow.tsx +81 -17
  128. package/src/components/PageHero/PageHero.tsx +61 -4
  129. package/src/components/RechargeCard/RechargeCard.tsx +32 -24
  130. package/src/components/Stepper/Step.tsx +52 -51
  131. package/src/components/Stepper/StepLabel.tsx +46 -9
  132. package/src/components/Stepper/Stepper.tsx +20 -15
  133. package/src/components/SuggestiveSearch/SuggestiveSearch.tsx +756 -0
  134. package/src/components/Text/Text.tsx +54 -0
  135. package/src/components/TextInput/TextInput.tsx +14 -1
  136. package/src/components/Title/Title.tsx +13 -2
  137. package/src/components/index.ts +8 -3
  138. package/src/design-tokens/Coin Variables-variables-full.json +1 -1
  139. package/src/icons/Icon.tsx +17 -0
  140. package/src/icons/registry.ts +1 -1
  141. package/src/index.ts +1 -0
  142. package/src/skeleton/Skeleton.tsx +298 -0
  143. package/src/skeleton/SkeletonGroup.tsx +193 -0
  144. package/src/skeleton/index.ts +10 -0
  145. package/src/skeleton/shimmer-tokens.ts +221 -0
  146. package/src/skeleton/useReducedMotion.ts +72 -0
@@ -8,7 +8,10 @@ var _react = _interopRequireWildcard(require("react"));
8
8
  var _reactNative = require("react-native");
9
9
  var _figmaVariablesResolver = require("../../design-tokens/figma-variables-resolver");
10
10
  var _reactUtils = require("../../utils/react-utils");
11
+ var _Skeleton = _interopRequireDefault(require("../../skeleton/Skeleton"));
12
+ var _SkeletonGroup = require("../../skeleton/SkeletonGroup");
11
13
  var _jsxRuntime = require("react/jsx-runtime");
14
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
12
15
  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); }
13
16
  const avatarImage = require('./31595e70c4181263f9971590224b12934b280c9b.png');
14
17
 
@@ -128,11 +131,19 @@ function Avatar({
128
131
  // component intentionally renders `accessibilityLabel={undefined}` on the
129
132
  // wrapper (the inner Text/Image carry the label instead).
130
133
  accessibilityLabel: _accessibilityLabel,
134
+ loading,
131
135
  ...rest
132
136
  }) {
133
137
  const isMonogram = style === 'Monogram';
134
138
  const tokens = (0, _react.useMemo)(() => resolveAvatarTokens(modes, isMonogram), [modes, isMonogram]);
135
139
 
140
+ // Skeleton context — read unconditionally; the actual short-circuit
141
+ // happens AFTER all remaining hooks below.
142
+ const {
143
+ active: groupActive
144
+ } = (0, _SkeletonGroup.useSkeleton)();
145
+ const isLoading = loading ?? groupActive;
146
+
136
147
  // Focus is a sustained visible state — keep mirroring on web; gate the
137
148
  // setter so it never fires on native (where focus events don't fire on
138
149
  // these elements anyway).
@@ -163,6 +174,15 @@ function Avatar({
163
174
  pressed
164
175
  }) => [tokens.containerStyle, pressed ? pressedOverlayStyle : null, isFocused ? focusOverlayStyle : null], [tokens.containerStyle, isFocused]);
165
176
  const staticContainerStyle = (0, _react.useMemo)(() => [tokens.containerStyle, isFocused ? focusOverlayStyle : null], [tokens.containerStyle, isFocused]);
177
+ if (isLoading) {
178
+ const size = tokens.containerStyle.width;
179
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_Skeleton.default, {
180
+ kind: "other",
181
+ width: size,
182
+ height: size,
183
+ modes: modes
184
+ });
185
+ }
166
186
 
167
187
  // The inner content varies; everything else (wrapper, handlers, style) is shared.
168
188
  const innerContent = isMonogram ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
@@ -8,6 +8,8 @@ var _react = _interopRequireDefault(require("react"));
8
8
  var _reactNative = require("react-native");
9
9
  var _figmaVariablesResolver = require("../../design-tokens/figma-variables-resolver");
10
10
  var _reactUtils = require("../../utils/react-utils");
11
+ var _Skeleton = _interopRequireDefault(require("../../skeleton/Skeleton"));
12
+ var _SkeletonGroup = require("../../skeleton/SkeletonGroup");
11
13
  var _jsxRuntime = require("react/jsx-runtime");
12
14
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
13
15
  function Badge({
@@ -17,6 +19,7 @@ function Badge({
17
19
  accessibilityLabel,
18
20
  style,
19
21
  labelStyle,
22
+ loading,
20
23
  ...rest
21
24
  }) {
22
25
  // Resolve token values (fall back to sensible defaults)
@@ -29,6 +32,26 @@ function Badge({
29
32
  const paddingVertical = Number((0, _figmaVariablesResolver.getVariableByName)('badge/padding/vertical', modes)) || 4;
30
33
  const borderRadius = Number((0, _figmaVariablesResolver.getVariableByName)('badge/radius', modes)) || 4;
31
34
  const lineHeight = Number((0, _figmaVariablesResolver.getVariableByName)('badge/label/lineHeight', modes)) || Math.round(fontSize * 1.2);
35
+
36
+ // Skeleton short-circuit. Size derived from the same tokens the loaded
37
+ // badge would use so the placeholder occupies the same box.
38
+ const {
39
+ active: groupActive
40
+ } = (0, _SkeletonGroup.useSkeleton)();
41
+ const isLoading = loading ?? groupActive;
42
+ if (isLoading) {
43
+ const charWidth = fontSize * 0.55;
44
+ const labelWidth = Math.max(label.length, 3) * charWidth;
45
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_Skeleton.default, {
46
+ kind: "badge",
47
+ width: paddingHorizontal * 2 + labelWidth,
48
+ height: paddingVertical * 2 + lineHeight,
49
+ style: {
50
+ alignSelf: 'flex-start'
51
+ },
52
+ modes: modes
53
+ });
54
+ }
32
55
  const Container = onPress ? _reactNative.Pressable : _reactNative.View;
33
56
  const containerStyle = {
34
57
  backgroundColor,
@@ -10,6 +10,8 @@ var _figmaVariablesResolver = require("../../design-tokens/figma-variables-resol
10
10
  var _webPlatformUtils = require("../../utils/web-platform-utils");
11
11
  var _reactUtils = require("../../utils/react-utils");
12
12
  var _Icon = _interopRequireDefault(require("../../icons/Icon"));
13
+ var _Skeleton = _interopRequireDefault(require("../../skeleton/Skeleton"));
14
+ var _SkeletonGroup = require("../../skeleton/SkeletonGroup");
13
15
  var _jsxRuntime = require("react/jsx-runtime");
14
16
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
15
17
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
@@ -173,6 +175,7 @@ function ButtonImpl({
173
175
  accessibilityHint,
174
176
  accessibilityState,
175
177
  webAccessibilityProps,
178
+ loading,
176
179
  ...rest
177
180
  }) {
178
181
  // Hover state is web-only in practice; the setter is gated so native taps
@@ -188,6 +191,14 @@ function ButtonImpl({
188
191
  userHandlersRef.current.onHoverOut = rest?.onHoverOut;
189
192
  const tokens = (0, _react.useMemo)(() => resolveButtonTokens(modes, disabled), [modes, disabled]);
190
193
 
194
+ // Skeleton context — read unconditionally so React's hook order stays
195
+ // stable. The actual short-circuit return happens AFTER all remaining
196
+ // hooks have been called below.
197
+ const {
198
+ active: groupActive
199
+ } = (0, _SkeletonGroup.useSkeleton)();
200
+ const isLoading = loading ?? groupActive;
201
+
191
202
  // Active label color: base by default; hover override (web-only) when hovered.
192
203
  // Press color is intentionally NOT applied to the label on native — applying
193
204
  // it would require a React render per touch and re-introduce the flicker.
@@ -265,6 +276,32 @@ function ButtonImpl({
265
276
  console.warn('[Button] Custom content is used without an explicit `accessibilityLabel` or string `label`. ' + 'Screen readers may not announce this button correctly.');
266
277
  }
267
278
  }
279
+ if (isLoading) {
280
+ const {
281
+ container,
282
+ baseLabel,
283
+ iconSize,
284
+ accessoryOffset
285
+ } = tokens;
286
+ const paddingHorizontal = container.paddingHorizontal ?? 20;
287
+ const paddingVertical = container.paddingVertical ?? 12;
288
+ const lineHeight = baseLabel.lineHeight ?? 19;
289
+ const fontSize = baseLabel.fontSize ?? 16;
290
+ const labelText = typeof label === 'string' ? label : 'Button';
291
+ const charWidth = fontSize * 0.55;
292
+ const labelWidth = Math.max(labelText.length, 4) * charWidth;
293
+ const hasAccessory = !!(leading || trailing || icon);
294
+ const accessoryWidth = hasAccessory ? iconSize + accessoryOffset * 2 : 0;
295
+ const skeletonWidth = paddingHorizontal * 2 + labelWidth + accessoryWidth;
296
+ const skeletonHeight = paddingVertical * 2 + lineHeight;
297
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_Skeleton.default, {
298
+ kind: "other",
299
+ width: skeletonWidth,
300
+ height: skeletonHeight,
301
+ style: style,
302
+ modes: modes
303
+ });
304
+ }
268
305
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
269
306
  accessibilityRole: "button",
270
307
  accessibilityLabel: undefined,
@@ -49,6 +49,15 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
49
49
  }
50
50
  };
51
51
  }
52
+
53
+ /** Minimum touch target per iOS HIG / Material accessibility guidance. */
54
+ const MIN_TOUCH_TARGET = 44;
55
+ const touchTargetStyle = {
56
+ minWidth: MIN_TOUCH_TARGET,
57
+ minHeight: MIN_TOUCH_TARGET,
58
+ alignItems: 'center',
59
+ justifyContent: 'center'
60
+ };
52
61
  /**
53
62
  * Checkbox component that maps directly to the Figma design using design tokens.
54
63
  *
@@ -174,7 +183,7 @@ function Checkbox({
174
183
  };
175
184
  const markColor = disabled && isChecked ? disabledActiveMark : selectedMarkColor;
176
185
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
177
- style: [resolveStyle(), style],
186
+ style: [touchTargetStyle, style],
178
187
  onPress: handlePress,
179
188
  disabled: disabled,
180
189
  onHoverIn: () => setIsHovered(true),
@@ -186,14 +195,17 @@ function Checkbox({
186
195
  disabled
187
196
  },
188
197
  accessibilityLabel: accessibilityLabel,
189
- children: isChecked && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.default, {
190
- width: 12,
191
- height: 9,
192
- viewBox: "0 0 12 9",
193
- fill: "none",
194
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Path, {
195
- d: "M4.00091 8.66939C3.91321 8.6699 3.82628 8.65309 3.74509 8.61991C3.6639 8.58673 3.59006 8.53785 3.52779 8.47606L0.195972 5.14273C0.0704931 5.01719 -1.86978e-09 4.84693 0 4.66939C1.86978e-09 4.49186 0.0704931 4.3216 0.195972 4.19606C0.321451 4.07053 0.491636 4 0.66909 4C0.846544 4 1.01673 4.07053 1.14221 4.19606L4.00091 7.06273L10.8578 0.196061C10.9833 0.0705253 11.1535 0 11.3309 0C11.5084 0 11.6785 0.0705253 11.804 0.196061C11.9295 0.321597 12 0.49186 12 0.669394C12 0.846929 11.9295 1.01719 11.804 1.14273L4.47403 8.47606C4.41176 8.53785 4.33792 8.58673 4.25673 8.61991C4.17554 8.65309 4.08861 8.6699 4.00091 8.66939Z",
196
- fill: markColor
198
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
199
+ style: resolveStyle(),
200
+ children: isChecked && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.default, {
201
+ width: 12,
202
+ height: 9,
203
+ viewBox: "0 0 12 9",
204
+ fill: "none",
205
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Path, {
206
+ d: "M4.00091 8.66939C3.91321 8.6699 3.82628 8.65309 3.74509 8.61991C3.6639 8.58673 3.59006 8.53785 3.52779 8.47606L0.195972 5.14273C0.0704931 5.01719 -1.86978e-09 4.84693 0 4.66939C1.86978e-09 4.49186 0.0704931 4.3216 0.195972 4.19606C0.321451 4.07053 0.491636 4 0.66909 4C0.846544 4 1.01673 4.07053 1.14221 4.19606L4.00091 7.06273L10.8578 0.196061C10.9833 0.0705253 11.1535 0 11.3309 0C11.5084 0 11.6785 0.0705253 11.804 0.196061C11.9295 0.321597 12 0.49186 12 0.669394C12 0.846929 11.9295 1.01719 11.804 1.14273L4.47403 8.47606C4.41176 8.53785 4.33792 8.58673 4.25673 8.61991C4.17554 8.65309 4.08861 8.6699 4.00091 8.66939Z",
207
+ fill: markColor
208
+ })
197
209
  })
198
210
  })
199
211
  });
@@ -36,25 +36,36 @@ function useChevronTokens(modes) {
36
36
  };
37
37
  }, [modes]);
38
38
  }
39
+ function toNumber(value, fallback) {
40
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
41
+ if (typeof value === 'string') {
42
+ const parsed = parseFloat(value);
43
+ if (Number.isFinite(parsed)) return parsed;
44
+ }
45
+ return fallback;
46
+ }
39
47
  function useFormFieldTokens(modes) {
40
48
  return (0, _react.useMemo)(() => {
41
- const labelColor = (0, _figmaVariablesResolver.getVariableByName)('formField/label/color', modes) || '#0c0d10';
49
+ const labelColor = (0, _figmaVariablesResolver.getVariableByName)('formField/label/color', modes) || '#000000';
42
50
  const labelFontFamily = (0, _figmaVariablesResolver.getVariableByName)('formField/label/fontFamily', modes) || 'JioType Var';
43
- const labelFontSize = parseInt((0, _figmaVariablesResolver.getVariableByName)('formField/label/fontSize', modes), 10) || 14;
44
- const labelLineHeight = parseInt((0, _figmaVariablesResolver.getVariableByName)('formField/label/lineHeight', modes), 10) || 17;
51
+ const labelFontSize = toNumber((0, _figmaVariablesResolver.getVariableByName)('formField/label/fontSize', modes), 14);
52
+ const labelLineHeight = toNumber((0, _figmaVariablesResolver.getVariableByName)('formField/label/lineHeight', modes), 17);
45
53
  const labelFontWeight = (0, _figmaVariablesResolver.getVariableByName)('formField/label/fontWeight', modes) || '500';
46
- const gap = parseInt((0, _figmaVariablesResolver.getVariableByName)('formField/gap', modes), 10) || 8;
47
- const inputPaddingH = parseInt((0, _figmaVariablesResolver.getVariableByName)('formField/input/padding/horizontal', modes), 10) || 12;
48
- const inputGap = parseInt((0, _figmaVariablesResolver.getVariableByName)('formField/input/gap', modes), 10) || 8;
49
- const inputRadius = parseInt((0, _figmaVariablesResolver.getVariableByName)('formField/input/radius', modes), 10) || 8;
54
+ const gap = toNumber((0, _figmaVariablesResolver.getVariableByName)('formField/gap', modes), 8);
55
+ const inputPaddingH = toNumber((0, _figmaVariablesResolver.getVariableByName)('formField/input/padding/horizontal', modes), 12);
56
+ const inputGap = toNumber((0, _figmaVariablesResolver.getVariableByName)('formField/input/gap', modes), 8);
57
+ const inputRadius = toNumber((0, _figmaVariablesResolver.getVariableByName)('formField/input/radius', modes), 8);
50
58
  const inputBackground = (0, _figmaVariablesResolver.getVariableByName)('formField/input/background', modes) || '#ffffff';
51
- const inputFontSize = parseInt((0, _figmaVariablesResolver.getVariableByName)('formField/input/label/fontSize', modes), 10) || 16;
52
- const inputLineHeight = parseInt((0, _figmaVariablesResolver.getVariableByName)('formField/input/label/lineHeight', modes), 10) || 45;
59
+ const inputFontSize = toNumber((0, _figmaVariablesResolver.getVariableByName)('formField/input/label/fontSize', modes), 16);
60
+ const inputLineHeight = toNumber((0, _figmaVariablesResolver.getVariableByName)('formField/input/label/lineHeight', modes), 45);
53
61
  const inputFontFamily = (0, _figmaVariablesResolver.getVariableByName)('formField/input/label/fontFamily', modes) || 'JioType Var';
54
62
  const inputFontWeight = (0, _figmaVariablesResolver.getVariableByName)('formField/input/label/fontWeight', modes) || '400';
55
63
  const inputTextColor = (0, _figmaVariablesResolver.getVariableByName)('states/formField/input/label/color', modes) || (0, _figmaVariablesResolver.getVariableByName)('formField/input/label/color', modes) || '#24262b';
56
64
  const inputBorderColor = (0, _figmaVariablesResolver.getVariableByName)('states/formField/input/border/color', modes) || (0, _figmaVariablesResolver.getVariableByName)('formField/input/border/color', modes) || '#b5b6b7';
57
- const inputBorderSize = parseInt((0, _figmaVariablesResolver.getVariableByName)('formField/input/border/size', modes), 10) || 1;
65
+ // Figma spec: 1.5px. Using parseFloat (via toNumber) preserves the
66
+ // fractional value — parseInt was truncating it to 1, leaving the
67
+ // resolved row height ~1px shorter than the Figma reference.
68
+ const inputBorderSize = toNumber((0, _figmaVariablesResolver.getVariableByName)('formField/input/border/size', modes), 1.5);
58
69
  return {
59
70
  labelColor,
60
71
  labelFontFamily,
@@ -134,7 +145,7 @@ function DropdownInput({
134
145
  supportText,
135
146
  errorMessage,
136
147
  menuMaxHeight = 240,
137
- menuOffset = 4,
148
+ menuOffset = 6,
138
149
  matchTriggerWidth = true,
139
150
  closeOnBackdropPress = true,
140
151
  modes: propModes = _reactUtils.EMPTY_MODES,
@@ -340,19 +351,23 @@ function DropdownInput({
340
351
  };
341
352
 
342
353
  // Focus ring uses the resolved input border color from FormField States so
343
- // active/error look consistent with TextInput-based FormField. We also lift
344
- // border weight to 2 when "Active" to read as a focus ring.
354
+ // active/error look consistent with TextInput-based FormField. Only the
355
+ // color changes between states width stays constant to avoid layout
356
+ // shift when opening the menu (a shift would invalidate the measured
357
+ // trigger rect and visually shove the popup).
345
358
  const inputRowStyle = {
346
359
  flexDirection: 'row',
347
360
  alignItems: 'center',
348
361
  backgroundColor: tokens.inputBackground,
349
362
  borderColor: tokens.inputBorderColor,
350
- borderWidth: isOpen ? Math.max(tokens.inputBorderSize, 1) : tokens.inputBorderSize,
363
+ borderWidth: tokens.inputBorderSize,
364
+ borderStyle: 'solid',
351
365
  borderRadius: tokens.inputRadius,
352
366
  paddingHorizontal: tokens.inputPaddingH,
353
367
  paddingVertical: 0,
354
368
  gap: tokens.inputGap,
355
- minHeight: tokens.inputLineHeight
369
+ minHeight: tokens.inputLineHeight,
370
+ width: '100%'
356
371
  };
357
372
  const valueTextStyle = {
358
373
  flex: 1,
@@ -500,7 +515,6 @@ function DropdownInput({
500
515
  transparent: true,
501
516
  animationType: "fade",
502
517
  onRequestClose: closeMenu,
503
- statusBarTranslucent: true,
504
518
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
505
519
  style: _reactNative.StyleSheet.absoluteFill,
506
520
  onPress: closeOnBackdropPress ? closeMenu : undefined,
@@ -0,0 +1,167 @@
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 _reactUtils = require("../../utils/react-utils");
11
+ var _Checkbox = _interopRequireDefault(require("../Checkbox/Checkbox"));
12
+ var _Button = _interopRequireDefault(require("../Button/Button"));
13
+ var _jsxRuntime = require("react/jsx-runtime");
14
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
15
+ 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); }
16
+ /**
17
+ * Default modes applied to the inner toggle `Button`. These resolve the
18
+ * tertiary-style pill in the Figma reference (small, transparent background,
19
+ * brand purple foreground). Any value supplied via the consumer `modes` prop
20
+ * takes precedence over these defaults.
21
+ */
22
+ const BUTTON_DEFAULT_MODES = {
23
+ 'Button / Size': 'XS',
24
+ AppearanceBrand: 'Secondary',
25
+ Emphasis: 'Low'
26
+ };
27
+
28
+ /**
29
+ * ExpandableCheckbox composes a `Checkbox`, a long-form label and a
30
+ * "Read more" / "Read less" toggle. Mirrors the Figma "Expandable Checkbox"
31
+ * component with two states:
32
+ *
33
+ * - **Idle (collapsed)** — checkbox + truncated label + toggle button arranged
34
+ * in a horizontal row (cross-axis centered).
35
+ * - **Open (expanded)** — checkbox + full multi-line label, with the toggle
36
+ * button right-aligned beneath the row.
37
+ *
38
+ * The checkbox and the toggle button have independent press handlers — pressing
39
+ * the toggle does not affect the checked state, and toggling the checkbox does
40
+ * not collapse / expand the row.
41
+ *
42
+ * @component
43
+ * @param {ExpandableCheckboxProps} props
44
+ *
45
+ * @example
46
+ * ```tsx
47
+ * <ExpandableCheckbox
48
+ * label="By checking this box, I (a) acknowledge and (b) agree to the full terms…"
49
+ * defaultChecked
50
+ * onValueChange={setAccepted}
51
+ * modes={{ 'Color Mode': 'Light' }}
52
+ * />
53
+ * ```
54
+ */
55
+ function ExpandableCheckbox({
56
+ label = '',
57
+ checked: controlledChecked,
58
+ defaultChecked = false,
59
+ onValueChange,
60
+ expanded: controlledExpanded,
61
+ defaultExpanded = false,
62
+ onExpandedChange,
63
+ disabled = false,
64
+ readMoreLabel = 'Read more',
65
+ readLessLabel = 'Read less',
66
+ collapsedLines = 1,
67
+ modes = _reactUtils.EMPTY_MODES,
68
+ style,
69
+ labelStyle,
70
+ accessibilityLabel
71
+ }) {
72
+ const isCheckedControlled = controlledChecked !== undefined;
73
+ const [internalChecked, setInternalChecked] = (0, _react.useState)(defaultChecked);
74
+ const isChecked = isCheckedControlled ? controlledChecked : internalChecked;
75
+ const isExpandedControlled = controlledExpanded !== undefined;
76
+ const [internalExpanded, setInternalExpanded] = (0, _react.useState)(defaultExpanded);
77
+ const isExpanded = isExpandedControlled ? controlledExpanded : internalExpanded;
78
+ const handleToggleChecked = (0, _react.useCallback)(next => {
79
+ if (disabled) return;
80
+ if (!isCheckedControlled) setInternalChecked(next);
81
+ onValueChange?.(next);
82
+ }, [disabled, isCheckedControlled, onValueChange]);
83
+ const handleToggleExpanded = (0, _react.useCallback)(() => {
84
+ if (disabled) return;
85
+ const next = !isExpanded;
86
+ if (!isExpandedControlled) setInternalExpanded(next);
87
+ onExpandedChange?.(next);
88
+ }, [disabled, isExpanded, isExpandedControlled, onExpandedChange]);
89
+ const gap = (0, _figmaVariablesResolver.getVariableByName)('expandableCheckbox/gap', modes) ?? 8;
90
+ const rowGap = (0, _figmaVariablesResolver.getVariableByName)('checkboxItem/gap', modes) ?? 8;
91
+ const rowPaddingHorizontal = (0, _figmaVariablesResolver.getVariableByName)('checkboxItem/padding/horizontal', modes) ?? 0;
92
+ const rowPaddingVertical = (0, _figmaVariablesResolver.getVariableByName)('checkboxItem/padding/vertical', modes) ?? 0;
93
+ const labelColor = (0, _figmaVariablesResolver.getVariableByName)('checkboxItem/foreground', modes) ?? '#1a1c1f';
94
+ const labelFontFamily = (0, _figmaVariablesResolver.getVariableByName)('checkboxItem/label/fontFamily', modes) ?? 'JioType Var';
95
+ const labelFontSize = (0, _figmaVariablesResolver.getVariableByName)('checkboxItem/label/fontSize', modes) ?? 14;
96
+ const labelLineHeight = (0, _figmaVariablesResolver.getVariableByName)('checkboxItem/label/lineHeight', modes) ?? 19;
97
+ const labelFontWeightRaw = (0, _figmaVariablesResolver.getVariableByName)('checkboxItem/label/fontWeight', modes) ?? 400;
98
+ const labelFontWeight = String(labelFontWeightRaw);
99
+ const containerStyle = (0, _react.useMemo)(() => ({
100
+ flexDirection: isExpanded ? 'column' : 'row',
101
+ alignItems: isExpanded ? 'flex-end' : 'center',
102
+ gap,
103
+ width: '100%',
104
+ ...(disabled ? {
105
+ opacity: 0.6
106
+ } : null)
107
+ }), [isExpanded, gap, disabled]);
108
+ const rowStyle = (0, _react.useMemo)(() => ({
109
+ flex: isExpanded ? undefined : 1,
110
+ alignSelf: isExpanded ? 'stretch' : 'auto',
111
+ minWidth: 0,
112
+ flexDirection: 'row',
113
+ alignItems: 'flex-start',
114
+ gap: rowGap,
115
+ paddingHorizontal: rowPaddingHorizontal,
116
+ paddingVertical: rowPaddingVertical
117
+ }), [isExpanded, rowGap, rowPaddingHorizontal, rowPaddingVertical]);
118
+ const resolvedLabelStyle = (0, _react.useMemo)(() => ({
119
+ flex: 1,
120
+ minWidth: 0,
121
+ color: labelColor,
122
+ fontFamily: labelFontFamily,
123
+ fontSize: labelFontSize,
124
+ lineHeight: labelLineHeight,
125
+ fontWeight: labelFontWeight
126
+ }), [labelColor, labelFontFamily, labelFontSize, labelLineHeight, labelFontWeight]);
127
+ const buttonModes = (0, _react.useMemo)(() => ({
128
+ ...BUTTON_DEFAULT_MODES,
129
+ ...modes
130
+ }), [modes]);
131
+ const a11yLabel = accessibilityLabel ?? (typeof label === 'string' ? label : undefined);
132
+ const buttonLabel = isExpanded ? readLessLabel : readMoreLabel;
133
+ const labelNumberOfLinesProps = !isExpanded && collapsedLines > 0 ? {
134
+ numberOfLines: collapsedLines,
135
+ ellipsizeMode: 'tail'
136
+ } : null;
137
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
138
+ style: [containerStyle, style],
139
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
140
+ style: rowStyle,
141
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_Checkbox.default, {
142
+ checked: isChecked,
143
+ disabled: disabled,
144
+ onValueChange: handleToggleChecked,
145
+ modes: modes,
146
+ ...(a11yLabel !== undefined ? {
147
+ accessibilityLabel: a11yLabel
148
+ } : {})
149
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
150
+ style: [resolvedLabelStyle, labelStyle],
151
+ selectable: false,
152
+ ...(labelNumberOfLinesProps ?? {}),
153
+ children: label
154
+ })]
155
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_Button.default, {
156
+ label: buttonLabel,
157
+ onPress: handleToggleExpanded,
158
+ disabled: disabled,
159
+ modes: buttonModes,
160
+ accessibilityLabel: buttonLabel,
161
+ accessibilityState: {
162
+ expanded: isExpanded
163
+ }
164
+ })]
165
+ });
166
+ }
167
+ var _default = exports.default = ExpandableCheckbox;
@@ -208,6 +208,16 @@ function FormField({
208
208
  const [isFocused, setIsFocused] = (0, _react.useState)(false);
209
209
  const interactive = !isDisabled && !isReadOnly;
210
210
 
211
+ // Ref to the native input so tapping anywhere in the input row (padding,
212
+ // leading/trailing gutters) focuses it on the FIRST tap — fixing the Android
213
+ // "two taps to open the keyboard" issue caused by the row intercepting the
214
+ // initial touch.
215
+ const inputRef = (0, _react.useRef)(null);
216
+ const focusInput = (0, _react.useCallback)(() => {
217
+ if (!interactive) return;
218
+ inputRef.current?.focus();
219
+ }, [interactive]);
220
+
211
221
  // FormField States cascade — error > read only/disabled > active (focused) > idle.
212
222
  // Disabled maps to "Read Only" since there is no dedicated disabled mode and
213
223
  // the visual treatment is closest. This is only the DEFAULT — an explicit
@@ -340,13 +350,16 @@ function FormField({
340
350
  style: requiredIndicatorStyle,
341
351
  children: " *"
342
352
  })]
343
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
353
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
344
354
  style: [inputRowStyle, inputStyle],
355
+ onPress: focusInput,
356
+ accessible: false,
345
357
  children: [processedLeading != null && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
346
358
  accessibilityElementsHidden: true,
347
359
  importantForAccessibility: "no",
348
360
  children: processedLeading
349
361
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
362
+ ref: inputRef,
350
363
  style: [inputTextStyles, inputTextStyle],
351
364
  value: value ?? '',
352
365
  onChangeText: handleChangeText,