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
@@ -6,6 +6,8 @@ import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
6
6
  import { usePressableWebSupport } from '../../utils/web-platform-utils';
7
7
  import { EMPTY_MODES } from '../../utils/react-utils';
8
8
  import Icon from '../../icons/Icon';
9
+ import Skeleton from '../../skeleton/Skeleton';
10
+ import { useSkeleton } from '../../skeleton/SkeletonGroup';
9
11
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
12
  // ---------------------------------------------------------------------------
11
13
  // Module-scope constants — never re-allocated per render.
@@ -167,6 +169,7 @@ function ButtonImpl({
167
169
  accessibilityHint,
168
170
  accessibilityState,
169
171
  webAccessibilityProps,
172
+ loading,
170
173
  ...rest
171
174
  }) {
172
175
  // Hover state is web-only in practice; the setter is gated so native taps
@@ -182,6 +185,14 @@ function ButtonImpl({
182
185
  userHandlersRef.current.onHoverOut = rest?.onHoverOut;
183
186
  const tokens = useMemo(() => resolveButtonTokens(modes, disabled), [modes, disabled]);
184
187
 
188
+ // Skeleton context — read unconditionally so React's hook order stays
189
+ // stable. The actual short-circuit return happens AFTER all remaining
190
+ // hooks have been called below.
191
+ const {
192
+ active: groupActive
193
+ } = useSkeleton();
194
+ const isLoading = loading ?? groupActive;
195
+
185
196
  // Active label color: base by default; hover override (web-only) when hovered.
186
197
  // Press color is intentionally NOT applied to the label on native — applying
187
198
  // it would require a React render per touch and re-introduce the flicker.
@@ -259,6 +270,32 @@ function ButtonImpl({
259
270
  console.warn('[Button] Custom content is used without an explicit `accessibilityLabel` or string `label`. ' + 'Screen readers may not announce this button correctly.');
260
271
  }
261
272
  }
273
+ if (isLoading) {
274
+ const {
275
+ container,
276
+ baseLabel,
277
+ iconSize,
278
+ accessoryOffset
279
+ } = tokens;
280
+ const paddingHorizontal = container.paddingHorizontal ?? 20;
281
+ const paddingVertical = container.paddingVertical ?? 12;
282
+ const lineHeight = baseLabel.lineHeight ?? 19;
283
+ const fontSize = baseLabel.fontSize ?? 16;
284
+ const labelText = typeof label === 'string' ? label : 'Button';
285
+ const charWidth = fontSize * 0.55;
286
+ const labelWidth = Math.max(labelText.length, 4) * charWidth;
287
+ const hasAccessory = !!(leading || trailing || icon);
288
+ const accessoryWidth = hasAccessory ? iconSize + accessoryOffset * 2 : 0;
289
+ const skeletonWidth = paddingHorizontal * 2 + labelWidth + accessoryWidth;
290
+ const skeletonHeight = paddingVertical * 2 + lineHeight;
291
+ return /*#__PURE__*/_jsx(Skeleton, {
292
+ kind: "other",
293
+ width: skeletonWidth,
294
+ height: skeletonHeight,
295
+ style: style,
296
+ modes: modes
297
+ });
298
+ }
262
299
  return /*#__PURE__*/_jsxs(Pressable, {
263
300
  accessibilityRole: "button",
264
301
  accessibilityLabel: undefined,
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  import React, { useCallback, useEffect, useRef, useState } from 'react';
4
- import { Pressable, Platform } from 'react-native';
4
+ import { Pressable, Platform, View } 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 { EMPTY_MODES } from '../../utils/react-utils';
@@ -46,6 +46,15 @@ function useFocusVisible() {
46
46
  }
47
47
  };
48
48
  }
49
+
50
+ /** Minimum touch target per iOS HIG / Material accessibility guidance. */
51
+ const MIN_TOUCH_TARGET = 44;
52
+ const touchTargetStyle = {
53
+ minWidth: MIN_TOUCH_TARGET,
54
+ minHeight: MIN_TOUCH_TARGET,
55
+ alignItems: 'center',
56
+ justifyContent: 'center'
57
+ };
49
58
  /**
50
59
  * Checkbox component that maps directly to the Figma design using design tokens.
51
60
  *
@@ -171,7 +180,7 @@ function Checkbox({
171
180
  };
172
181
  const markColor = disabled && isChecked ? disabledActiveMark : selectedMarkColor;
173
182
  return /*#__PURE__*/_jsx(Pressable, {
174
- style: [resolveStyle(), style],
183
+ style: [touchTargetStyle, style],
175
184
  onPress: handlePress,
176
185
  disabled: disabled,
177
186
  onHoverIn: () => setIsHovered(true),
@@ -183,14 +192,17 @@ function Checkbox({
183
192
  disabled
184
193
  },
185
194
  accessibilityLabel: accessibilityLabel,
186
- children: isChecked && /*#__PURE__*/_jsx(Svg, {
187
- width: 12,
188
- height: 9,
189
- viewBox: "0 0 12 9",
190
- fill: "none",
191
- children: /*#__PURE__*/_jsx(Path, {
192
- 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",
193
- fill: markColor
195
+ children: /*#__PURE__*/_jsx(View, {
196
+ style: resolveStyle(),
197
+ children: isChecked && /*#__PURE__*/_jsx(Svg, {
198
+ width: 12,
199
+ height: 9,
200
+ viewBox: "0 0 12 9",
201
+ fill: "none",
202
+ children: /*#__PURE__*/_jsx(Path, {
203
+ 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",
204
+ fill: markColor
205
+ })
194
206
  })
195
207
  })
196
208
  });
@@ -30,25 +30,36 @@ function useChevronTokens(modes) {
30
30
  };
31
31
  }, [modes]);
32
32
  }
33
+ function toNumber(value, fallback) {
34
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
35
+ if (typeof value === 'string') {
36
+ const parsed = parseFloat(value);
37
+ if (Number.isFinite(parsed)) return parsed;
38
+ }
39
+ return fallback;
40
+ }
33
41
  function useFormFieldTokens(modes) {
34
42
  return useMemo(() => {
35
- const labelColor = getVariableByName('formField/label/color', modes) || '#0c0d10';
43
+ const labelColor = getVariableByName('formField/label/color', modes) || '#000000';
36
44
  const labelFontFamily = getVariableByName('formField/label/fontFamily', modes) || 'JioType Var';
37
- const labelFontSize = parseInt(getVariableByName('formField/label/fontSize', modes), 10) || 14;
38
- const labelLineHeight = parseInt(getVariableByName('formField/label/lineHeight', modes), 10) || 17;
45
+ const labelFontSize = toNumber(getVariableByName('formField/label/fontSize', modes), 14);
46
+ const labelLineHeight = toNumber(getVariableByName('formField/label/lineHeight', modes), 17);
39
47
  const labelFontWeight = getVariableByName('formField/label/fontWeight', modes) || '500';
40
- const gap = parseInt(getVariableByName('formField/gap', modes), 10) || 8;
41
- const inputPaddingH = parseInt(getVariableByName('formField/input/padding/horizontal', modes), 10) || 12;
42
- const inputGap = parseInt(getVariableByName('formField/input/gap', modes), 10) || 8;
43
- const inputRadius = parseInt(getVariableByName('formField/input/radius', modes), 10) || 8;
48
+ const gap = toNumber(getVariableByName('formField/gap', modes), 8);
49
+ const inputPaddingH = toNumber(getVariableByName('formField/input/padding/horizontal', modes), 12);
50
+ const inputGap = toNumber(getVariableByName('formField/input/gap', modes), 8);
51
+ const inputRadius = toNumber(getVariableByName('formField/input/radius', modes), 8);
44
52
  const inputBackground = getVariableByName('formField/input/background', modes) || '#ffffff';
45
- const inputFontSize = parseInt(getVariableByName('formField/input/label/fontSize', modes), 10) || 16;
46
- const inputLineHeight = parseInt(getVariableByName('formField/input/label/lineHeight', modes), 10) || 45;
53
+ const inputFontSize = toNumber(getVariableByName('formField/input/label/fontSize', modes), 16);
54
+ const inputLineHeight = toNumber(getVariableByName('formField/input/label/lineHeight', modes), 45);
47
55
  const inputFontFamily = getVariableByName('formField/input/label/fontFamily', modes) || 'JioType Var';
48
56
  const inputFontWeight = getVariableByName('formField/input/label/fontWeight', modes) || '400';
49
57
  const inputTextColor = getVariableByName('states/formField/input/label/color', modes) || getVariableByName('formField/input/label/color', modes) || '#24262b';
50
58
  const inputBorderColor = getVariableByName('states/formField/input/border/color', modes) || getVariableByName('formField/input/border/color', modes) || '#b5b6b7';
51
- const inputBorderSize = parseInt(getVariableByName('formField/input/border/size', modes), 10) || 1;
59
+ // Figma spec: 1.5px. Using parseFloat (via toNumber) preserves the
60
+ // fractional value — parseInt was truncating it to 1, leaving the
61
+ // resolved row height ~1px shorter than the Figma reference.
62
+ const inputBorderSize = toNumber(getVariableByName('formField/input/border/size', modes), 1.5);
52
63
  return {
53
64
  labelColor,
54
65
  labelFontFamily,
@@ -128,7 +139,7 @@ function DropdownInput({
128
139
  supportText,
129
140
  errorMessage,
130
141
  menuMaxHeight = 240,
131
- menuOffset = 4,
142
+ menuOffset = 6,
132
143
  matchTriggerWidth = true,
133
144
  closeOnBackdropPress = true,
134
145
  modes: propModes = EMPTY_MODES,
@@ -334,19 +345,23 @@ function DropdownInput({
334
345
  };
335
346
 
336
347
  // Focus ring uses the resolved input border color from FormField States so
337
- // active/error look consistent with TextInput-based FormField. We also lift
338
- // border weight to 2 when "Active" to read as a focus ring.
348
+ // active/error look consistent with TextInput-based FormField. Only the
349
+ // color changes between states width stays constant to avoid layout
350
+ // shift when opening the menu (a shift would invalidate the measured
351
+ // trigger rect and visually shove the popup).
339
352
  const inputRowStyle = {
340
353
  flexDirection: 'row',
341
354
  alignItems: 'center',
342
355
  backgroundColor: tokens.inputBackground,
343
356
  borderColor: tokens.inputBorderColor,
344
- borderWidth: isOpen ? Math.max(tokens.inputBorderSize, 1) : tokens.inputBorderSize,
357
+ borderWidth: tokens.inputBorderSize,
358
+ borderStyle: 'solid',
345
359
  borderRadius: tokens.inputRadius,
346
360
  paddingHorizontal: tokens.inputPaddingH,
347
361
  paddingVertical: 0,
348
362
  gap: tokens.inputGap,
349
- minHeight: tokens.inputLineHeight
363
+ minHeight: tokens.inputLineHeight,
364
+ width: '100%'
350
365
  };
351
366
  const valueTextStyle = {
352
367
  flex: 1,
@@ -494,7 +509,6 @@ function DropdownInput({
494
509
  transparent: true,
495
510
  animationType: "fade",
496
511
  onRequestClose: closeMenu,
497
- statusBarTranslucent: true,
498
512
  children: /*#__PURE__*/_jsx(Pressable, {
499
513
  style: StyleSheet.absoluteFill,
500
514
  onPress: closeOnBackdropPress ? closeMenu : undefined,
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+
3
+ import React, { useCallback, useMemo, useState } from 'react';
4
+ import { View, Text } from 'react-native';
5
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
+ import { EMPTY_MODES } from '../../utils/react-utils';
7
+ import Checkbox from '../Checkbox/Checkbox';
8
+ import Button from '../Button/Button';
9
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
+ /**
11
+ * Default modes applied to the inner toggle `Button`. These resolve the
12
+ * tertiary-style pill in the Figma reference (small, transparent background,
13
+ * brand purple foreground). Any value supplied via the consumer `modes` prop
14
+ * takes precedence over these defaults.
15
+ */
16
+ const BUTTON_DEFAULT_MODES = {
17
+ 'Button / Size': 'XS',
18
+ AppearanceBrand: 'Secondary',
19
+ Emphasis: 'Low'
20
+ };
21
+
22
+ /**
23
+ * ExpandableCheckbox composes a `Checkbox`, a long-form label and a
24
+ * "Read more" / "Read less" toggle. Mirrors the Figma "Expandable Checkbox"
25
+ * component with two states:
26
+ *
27
+ * - **Idle (collapsed)** — checkbox + truncated label + toggle button arranged
28
+ * in a horizontal row (cross-axis centered).
29
+ * - **Open (expanded)** — checkbox + full multi-line label, with the toggle
30
+ * button right-aligned beneath the row.
31
+ *
32
+ * The checkbox and the toggle button have independent press handlers — pressing
33
+ * the toggle does not affect the checked state, and toggling the checkbox does
34
+ * not collapse / expand the row.
35
+ *
36
+ * @component
37
+ * @param {ExpandableCheckboxProps} props
38
+ *
39
+ * @example
40
+ * ```tsx
41
+ * <ExpandableCheckbox
42
+ * label="By checking this box, I (a) acknowledge and (b) agree to the full terms…"
43
+ * defaultChecked
44
+ * onValueChange={setAccepted}
45
+ * modes={{ 'Color Mode': 'Light' }}
46
+ * />
47
+ * ```
48
+ */
49
+ function ExpandableCheckbox({
50
+ label = '',
51
+ checked: controlledChecked,
52
+ defaultChecked = false,
53
+ onValueChange,
54
+ expanded: controlledExpanded,
55
+ defaultExpanded = false,
56
+ onExpandedChange,
57
+ disabled = false,
58
+ readMoreLabel = 'Read more',
59
+ readLessLabel = 'Read less',
60
+ collapsedLines = 1,
61
+ modes = EMPTY_MODES,
62
+ style,
63
+ labelStyle,
64
+ accessibilityLabel
65
+ }) {
66
+ const isCheckedControlled = controlledChecked !== undefined;
67
+ const [internalChecked, setInternalChecked] = useState(defaultChecked);
68
+ const isChecked = isCheckedControlled ? controlledChecked : internalChecked;
69
+ const isExpandedControlled = controlledExpanded !== undefined;
70
+ const [internalExpanded, setInternalExpanded] = useState(defaultExpanded);
71
+ const isExpanded = isExpandedControlled ? controlledExpanded : internalExpanded;
72
+ const handleToggleChecked = useCallback(next => {
73
+ if (disabled) return;
74
+ if (!isCheckedControlled) setInternalChecked(next);
75
+ onValueChange?.(next);
76
+ }, [disabled, isCheckedControlled, onValueChange]);
77
+ const handleToggleExpanded = useCallback(() => {
78
+ if (disabled) return;
79
+ const next = !isExpanded;
80
+ if (!isExpandedControlled) setInternalExpanded(next);
81
+ onExpandedChange?.(next);
82
+ }, [disabled, isExpanded, isExpandedControlled, onExpandedChange]);
83
+ const gap = getVariableByName('expandableCheckbox/gap', modes) ?? 8;
84
+ const rowGap = getVariableByName('checkboxItem/gap', modes) ?? 8;
85
+ const rowPaddingHorizontal = getVariableByName('checkboxItem/padding/horizontal', modes) ?? 0;
86
+ const rowPaddingVertical = getVariableByName('checkboxItem/padding/vertical', modes) ?? 0;
87
+ const labelColor = getVariableByName('checkboxItem/foreground', modes) ?? '#1a1c1f';
88
+ const labelFontFamily = getVariableByName('checkboxItem/label/fontFamily', modes) ?? 'JioType Var';
89
+ const labelFontSize = getVariableByName('checkboxItem/label/fontSize', modes) ?? 14;
90
+ const labelLineHeight = getVariableByName('checkboxItem/label/lineHeight', modes) ?? 19;
91
+ const labelFontWeightRaw = getVariableByName('checkboxItem/label/fontWeight', modes) ?? 400;
92
+ const labelFontWeight = String(labelFontWeightRaw);
93
+ const containerStyle = useMemo(() => ({
94
+ flexDirection: isExpanded ? 'column' : 'row',
95
+ alignItems: isExpanded ? 'flex-end' : 'center',
96
+ gap,
97
+ width: '100%',
98
+ ...(disabled ? {
99
+ opacity: 0.6
100
+ } : null)
101
+ }), [isExpanded, gap, disabled]);
102
+ const rowStyle = useMemo(() => ({
103
+ flex: isExpanded ? undefined : 1,
104
+ alignSelf: isExpanded ? 'stretch' : 'auto',
105
+ minWidth: 0,
106
+ flexDirection: 'row',
107
+ alignItems: 'flex-start',
108
+ gap: rowGap,
109
+ paddingHorizontal: rowPaddingHorizontal,
110
+ paddingVertical: rowPaddingVertical
111
+ }), [isExpanded, rowGap, rowPaddingHorizontal, rowPaddingVertical]);
112
+ const resolvedLabelStyle = useMemo(() => ({
113
+ flex: 1,
114
+ minWidth: 0,
115
+ color: labelColor,
116
+ fontFamily: labelFontFamily,
117
+ fontSize: labelFontSize,
118
+ lineHeight: labelLineHeight,
119
+ fontWeight: labelFontWeight
120
+ }), [labelColor, labelFontFamily, labelFontSize, labelLineHeight, labelFontWeight]);
121
+ const buttonModes = useMemo(() => ({
122
+ ...BUTTON_DEFAULT_MODES,
123
+ ...modes
124
+ }), [modes]);
125
+ const a11yLabel = accessibilityLabel ?? (typeof label === 'string' ? label : undefined);
126
+ const buttonLabel = isExpanded ? readLessLabel : readMoreLabel;
127
+ const labelNumberOfLinesProps = !isExpanded && collapsedLines > 0 ? {
128
+ numberOfLines: collapsedLines,
129
+ ellipsizeMode: 'tail'
130
+ } : null;
131
+ return /*#__PURE__*/_jsxs(View, {
132
+ style: [containerStyle, style],
133
+ children: [/*#__PURE__*/_jsxs(View, {
134
+ style: rowStyle,
135
+ children: [/*#__PURE__*/_jsx(Checkbox, {
136
+ checked: isChecked,
137
+ disabled: disabled,
138
+ onValueChange: handleToggleChecked,
139
+ modes: modes,
140
+ ...(a11yLabel !== undefined ? {
141
+ accessibilityLabel: a11yLabel
142
+ } : {})
143
+ }), /*#__PURE__*/_jsx(Text, {
144
+ style: [resolvedLabelStyle, labelStyle],
145
+ selectable: false,
146
+ ...(labelNumberOfLinesProps ?? {}),
147
+ children: label
148
+ })]
149
+ }), /*#__PURE__*/_jsx(Button, {
150
+ label: buttonLabel,
151
+ onPress: handleToggleExpanded,
152
+ disabled: disabled,
153
+ modes: buttonModes,
154
+ accessibilityLabel: buttonLabel,
155
+ accessibilityState: {
156
+ expanded: isExpanded
157
+ }
158
+ })]
159
+ });
160
+ }
161
+ export default ExpandableCheckbox;
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
 
3
- import React, { useCallback, useMemo, useState } from 'react';
4
- import { View, Text, TextInput as RNTextInput } from 'react-native';
3
+ import React, { useCallback, useMemo, useRef, useState } from 'react';
4
+ import { View, Text, Pressable, TextInput as RNTextInput } from 'react-native';
5
5
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
6
  import { useTokens } from '../../design-tokens/JFSThemeProvider';
7
7
  import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils';
@@ -202,6 +202,16 @@ function FormField({
202
202
  const [isFocused, setIsFocused] = useState(false);
203
203
  const interactive = !isDisabled && !isReadOnly;
204
204
 
205
+ // Ref to the native input so tapping anywhere in the input row (padding,
206
+ // leading/trailing gutters) focuses it on the FIRST tap — fixing the Android
207
+ // "two taps to open the keyboard" issue caused by the row intercepting the
208
+ // initial touch.
209
+ const inputRef = useRef(null);
210
+ const focusInput = useCallback(() => {
211
+ if (!interactive) return;
212
+ inputRef.current?.focus();
213
+ }, [interactive]);
214
+
205
215
  // FormField States cascade — error > read only/disabled > active (focused) > idle.
206
216
  // Disabled maps to "Read Only" since there is no dedicated disabled mode and
207
217
  // the visual treatment is closest. This is only the DEFAULT — an explicit
@@ -334,13 +344,16 @@ function FormField({
334
344
  style: requiredIndicatorStyle,
335
345
  children: " *"
336
346
  })]
337
- }), /*#__PURE__*/_jsxs(View, {
347
+ }), /*#__PURE__*/_jsxs(Pressable, {
338
348
  style: [inputRowStyle, inputStyle],
349
+ onPress: focusInput,
350
+ accessible: false,
339
351
  children: [processedLeading != null && /*#__PURE__*/_jsx(View, {
340
352
  accessibilityElementsHidden: true,
341
353
  importantForAccessibility: "no",
342
354
  children: processedLeading
343
355
  }), /*#__PURE__*/_jsx(RNTextInput, {
356
+ ref: inputRef,
344
357
  style: [inputTextStyles, inputTextStyle],
345
358
  value: value ?? '',
346
359
  onChangeText: handleChangeText,