jfs-components 0.0.79 → 0.0.85

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 (138) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/lib/commonjs/components/AppBar/AppBar.js +70 -6
  3. package/lib/commonjs/components/AreaLineChart/AreaLineChart.js +866 -0
  4. package/lib/commonjs/components/AreaLineChart/chartMath.js +252 -0
  5. package/lib/commonjs/components/Attached/Attached.js +76 -7
  6. package/lib/commonjs/components/BubbleChart/BubbleChart.js +191 -0
  7. package/lib/commonjs/components/BubbleChart/bubblePacking.js +378 -0
  8. package/lib/commonjs/components/Checkbox/Checkbox.js +18 -2
  9. package/lib/commonjs/components/ClusterBubble/ClusterBubble.js +272 -0
  10. package/lib/commonjs/components/Drawer/Drawer.js +6 -1
  11. package/lib/commonjs/components/DropdownInput/DropdownInput.js +30 -6
  12. package/lib/commonjs/components/ExpandableCheckbox/ExpandableCheckbox.js +17 -11
  13. package/lib/commonjs/components/FormField/FormField.js +1 -14
  14. package/lib/commonjs/components/FullscreenModal/FullscreenModal.js +5 -1
  15. package/lib/commonjs/components/ListItem/ListItem.js +6 -11
  16. package/lib/commonjs/components/MessageField/MessageField.js +1 -13
  17. package/lib/commonjs/components/MetricLegendItem/MetricLegendItem.js +7 -1
  18. package/lib/commonjs/components/PaymentFeedback/PaymentFeedback.js +12 -9
  19. package/lib/commonjs/components/PlanComparisonCard/PlanComparisonCard.js +69 -160
  20. package/lib/commonjs/components/Spinner/Spinner.js +217 -0
  21. package/lib/commonjs/components/TextInput/TextInput.js +33 -18
  22. package/lib/commonjs/components/index.js +34 -0
  23. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
  24. package/lib/commonjs/icons/components/IconArrowdown.js +19 -0
  25. package/lib/commonjs/icons/components/IconArrowup.js +19 -0
  26. package/lib/commonjs/icons/components/IconChevrondowncircle.js +19 -0
  27. package/lib/commonjs/icons/components/IconChevronleftcircle.js +19 -0
  28. package/lib/commonjs/icons/components/IconChevronrightcircle.js +19 -0
  29. package/lib/commonjs/icons/components/IconChevronupcircle.js +19 -0
  30. package/lib/commonjs/icons/components/IconOsnavback.js +19 -0
  31. package/lib/commonjs/icons/components/IconOsnavcenter.js +19 -0
  32. package/lib/commonjs/icons/components/IconOsnavhome.js +19 -0
  33. package/lib/commonjs/icons/components/IconOsnavtask.js +19 -0
  34. package/lib/commonjs/icons/components/IconSignin.js +19 -0
  35. package/lib/commonjs/icons/components/IconSignout.js +19 -0
  36. package/lib/commonjs/icons/components/index.js +132 -0
  37. package/lib/commonjs/icons/registry.js +2 -2
  38. package/lib/module/components/AppBar/AppBar.js +70 -6
  39. package/lib/module/components/AreaLineChart/AreaLineChart.js +859 -0
  40. package/lib/module/components/AreaLineChart/chartMath.js +242 -0
  41. package/lib/module/components/Attached/Attached.js +76 -7
  42. package/lib/module/components/BubbleChart/BubbleChart.js +185 -0
  43. package/lib/module/components/BubbleChart/bubblePacking.js +370 -0
  44. package/lib/module/components/Checkbox/Checkbox.js +18 -2
  45. package/lib/module/components/ClusterBubble/ClusterBubble.js +267 -0
  46. package/lib/module/components/Drawer/Drawer.js +6 -1
  47. package/lib/module/components/DropdownInput/DropdownInput.js +30 -6
  48. package/lib/module/components/ExpandableCheckbox/ExpandableCheckbox.js +17 -11
  49. package/lib/module/components/FormField/FormField.js +3 -16
  50. package/lib/module/components/FullscreenModal/FullscreenModal.js +5 -1
  51. package/lib/module/components/ListItem/ListItem.js +6 -11
  52. package/lib/module/components/MessageField/MessageField.js +3 -15
  53. package/lib/module/components/MetricLegendItem/MetricLegendItem.js +7 -1
  54. package/lib/module/components/PaymentFeedback/PaymentFeedback.js +13 -9
  55. package/lib/module/components/PlanComparisonCard/PlanComparisonCard.js +72 -160
  56. package/lib/module/components/Spinner/Spinner.js +212 -0
  57. package/lib/module/components/TextInput/TextInput.js +34 -19
  58. package/lib/module/components/index.js +4 -0
  59. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
  60. package/lib/module/icons/components/IconArrowdown.js +12 -0
  61. package/lib/module/icons/components/IconArrowup.js +12 -0
  62. package/lib/module/icons/components/IconChevrondowncircle.js +12 -0
  63. package/lib/module/icons/components/IconChevronleftcircle.js +12 -0
  64. package/lib/module/icons/components/IconChevronrightcircle.js +12 -0
  65. package/lib/module/icons/components/IconChevronupcircle.js +12 -0
  66. package/lib/module/icons/components/IconOsnavback.js +12 -0
  67. package/lib/module/icons/components/IconOsnavcenter.js +12 -0
  68. package/lib/module/icons/components/IconOsnavhome.js +12 -0
  69. package/lib/module/icons/components/IconOsnavtask.js +12 -0
  70. package/lib/module/icons/components/IconSignin.js +12 -0
  71. package/lib/module/icons/components/IconSignout.js +12 -0
  72. package/lib/module/icons/components/index.js +12 -0
  73. package/lib/module/icons/registry.js +2 -2
  74. package/lib/typescript/src/components/AppBar/AppBar.d.ts +12 -1
  75. package/lib/typescript/src/components/AreaLineChart/AreaLineChart.d.ts +212 -0
  76. package/lib/typescript/src/components/AreaLineChart/chartMath.d.ts +90 -0
  77. package/lib/typescript/src/components/Attached/Attached.d.ts +19 -16
  78. package/lib/typescript/src/components/BubbleChart/BubbleChart.d.ts +81 -0
  79. package/lib/typescript/src/components/BubbleChart/bubblePacking.d.ts +83 -0
  80. package/lib/typescript/src/components/ClusterBubble/ClusterBubble.d.ts +76 -0
  81. package/lib/typescript/src/components/DropdownInput/DropdownInput.d.ts +3 -2
  82. package/lib/typescript/src/components/ListItem/ListItem.d.ts +3 -3
  83. package/lib/typescript/src/components/MetricLegendItem/MetricLegendItem.d.ts +7 -1
  84. package/lib/typescript/src/components/PaymentFeedback/PaymentFeedback.d.ts +5 -1
  85. package/lib/typescript/src/components/PlanComparisonCard/PlanComparisonCard.d.ts +10 -8
  86. package/lib/typescript/src/components/Spinner/Spinner.d.ts +45 -0
  87. package/lib/typescript/src/components/index.d.ts +4 -0
  88. package/lib/typescript/src/icons/components/IconArrowdown.d.ts +3 -0
  89. package/lib/typescript/src/icons/components/IconArrowup.d.ts +3 -0
  90. package/lib/typescript/src/icons/components/IconChevrondowncircle.d.ts +3 -0
  91. package/lib/typescript/src/icons/components/IconChevronleftcircle.d.ts +3 -0
  92. package/lib/typescript/src/icons/components/IconChevronrightcircle.d.ts +3 -0
  93. package/lib/typescript/src/icons/components/IconChevronupcircle.d.ts +3 -0
  94. package/lib/typescript/src/icons/components/IconOsnavback.d.ts +3 -0
  95. package/lib/typescript/src/icons/components/IconOsnavcenter.d.ts +3 -0
  96. package/lib/typescript/src/icons/components/IconOsnavhome.d.ts +3 -0
  97. package/lib/typescript/src/icons/components/IconOsnavtask.d.ts +3 -0
  98. package/lib/typescript/src/icons/components/IconSignin.d.ts +3 -0
  99. package/lib/typescript/src/icons/components/IconSignout.d.ts +3 -0
  100. package/lib/typescript/src/icons/components/index.d.ts +12 -0
  101. package/lib/typescript/src/icons/registry.d.ts +1 -1
  102. package/package.json +3 -2
  103. package/src/components/AppBar/AppBar.tsx +92 -12
  104. package/src/components/AreaLineChart/AreaLineChart.tsx +1161 -0
  105. package/src/components/AreaLineChart/chartMath.ts +265 -0
  106. package/src/components/Attached/Attached.tsx +94 -7
  107. package/src/components/BubbleChart/BubbleChart.tsx +319 -0
  108. package/src/components/BubbleChart/bubblePacking.ts +397 -0
  109. package/src/components/Checkbox/Checkbox.tsx +14 -2
  110. package/src/components/ClusterBubble/ClusterBubble.tsx +359 -0
  111. package/src/components/Drawer/Drawer.tsx +4 -0
  112. package/src/components/DropdownInput/DropdownInput.tsx +54 -20
  113. package/src/components/ExpandableCheckbox/ExpandableCheckbox.tsx +13 -9
  114. package/src/components/FormField/FormField.tsx +3 -19
  115. package/src/components/FullscreenModal/FullscreenModal.tsx +3 -0
  116. package/src/components/ListItem/ListItem.tsx +14 -16
  117. package/src/components/MessageField/MessageField.tsx +3 -18
  118. package/src/components/MetricLegendItem/MetricLegendItem.tsx +20 -6
  119. package/src/components/PaymentFeedback/PaymentFeedback.tsx +15 -8
  120. package/src/components/PlanComparisonCard/PlanComparisonCard.tsx +82 -192
  121. package/src/components/Spinner/Spinner.tsx +273 -0
  122. package/src/components/TextInput/TextInput.tsx +37 -19
  123. package/src/components/index.ts +4 -0
  124. package/src/design-tokens/Coin Variables-variables-full.json +1 -1
  125. package/src/icons/components/IconArrowdown.tsx +11 -0
  126. package/src/icons/components/IconArrowup.tsx +11 -0
  127. package/src/icons/components/IconChevrondowncircle.tsx +11 -0
  128. package/src/icons/components/IconChevronleftcircle.tsx +11 -0
  129. package/src/icons/components/IconChevronrightcircle.tsx +11 -0
  130. package/src/icons/components/IconChevronupcircle.tsx +11 -0
  131. package/src/icons/components/IconOsnavback.tsx +11 -0
  132. package/src/icons/components/IconOsnavcenter.tsx +11 -0
  133. package/src/icons/components/IconOsnavhome.tsx +11 -0
  134. package/src/icons/components/IconOsnavtask.tsx +11 -0
  135. package/src/icons/components/IconSignin.tsx +11 -0
  136. package/src/icons/components/IconSignout.tsx +11 -0
  137. package/src/icons/components/index.ts +12 -0
  138. package/src/icons/registry.ts +49 -1
@@ -139,7 +139,7 @@ function DropdownInput({
139
139
  supportText,
140
140
  errorMessage,
141
141
  menuMaxHeight = 240,
142
- menuOffset = 6,
142
+ menuOffset,
143
143
  matchTriggerWidth = true,
144
144
  closeOnBackdropPress = true,
145
145
  modes: propModes = EMPTY_MODES,
@@ -202,10 +202,29 @@ function DropdownInput({
202
202
  const tokens = useFormFieldTokens(modes);
203
203
  const chevron = useChevronTokens(modes);
204
204
 
205
+ // Gap between the input and the popup. Falls back to the `formField/gap`
206
+ // token so the menu's offset matches the field's own internal spacing.
207
+ const effectiveMenuOffset = menuOffset ?? tokens.gap;
208
+
205
209
  // ---------------- Layout / measurement ----------------
206
210
  const triggerRef = useRef(null);
207
211
  const [triggerRect, setTriggerRect] = useState(null);
208
212
  const insets = useSafeAreaInsets();
213
+
214
+ // Android coordinate-space bridge.
215
+ //
216
+ // The popup lives inside a `statusBarTranslucent` Modal, whose window is
217
+ // laid out from the PHYSICAL top of the screen (behind the status bar).
218
+ // The trigger, however, is rendered inside the app's content area (Expo
219
+ // Router / react-native-screens under edge-to-edge), so its
220
+ // `measureInWindow` Y is relative to the content area — it does NOT include
221
+ // the status bar height. Feeding that Y straight into the Modal would place
222
+ // the popup one status-bar-height too high, landing it on top of the input.
223
+ //
224
+ // Adding `insets.top` converts the trigger's content-relative Y into the
225
+ // Modal's full-screen coordinate space. iOS/web share a single coordinate
226
+ // space for the Modal and the trigger, so no shift is needed there.
227
+ const windowTopOffset = Platform.OS === 'android' ? insets.top : 0;
209
228
  const measure = useCallback(() => {
210
229
  if (!triggerRef.current) return;
211
230
  triggerRef.current.measureInWindow((x, y, width, height) => {
@@ -271,7 +290,7 @@ function DropdownInput({
271
290
  const spaceBelow = windowHeight - (triggerRect.y + triggerRect.height) - insets.bottom;
272
291
  const spaceAbove = triggerRect.y - insets.top;
273
292
  const desiredHeight = Math.min(menuSize?.height ?? menuMaxHeight, menuMaxHeight);
274
- const needed = desiredHeight + menuOffset + 8;
293
+ const needed = desiredHeight + effectiveMenuOffset + 8;
275
294
  if (placement === 'top') {
276
295
  return spaceAbove >= needed || spaceAbove >= spaceBelow ? 'top' : 'bottom';
277
296
  }
@@ -279,7 +298,7 @@ function DropdownInput({
279
298
  return spaceBelow >= needed || spaceBelow >= spaceAbove ? 'bottom' : 'top';
280
299
  }
281
300
  return spaceBelow >= needed || spaceBelow >= spaceAbove ? 'bottom' : 'top';
282
- }, [triggerRect, placement, windowHeight, menuSize?.height, menuMaxHeight, menuOffset, insets.top, insets.bottom]);
301
+ }, [triggerRect, placement, windowHeight, menuSize?.height, menuMaxHeight, effectiveMenuOffset, insets.top, insets.bottom]);
283
302
  const popupStyle = useMemo(() => {
284
303
  if (!triggerRect) {
285
304
  return {
@@ -298,15 +317,18 @@ function DropdownInput({
298
317
  const minLeft = insets.left + screenPadding;
299
318
  if (leftPos > maxLeft) leftPos = maxLeft;
300
319
  if (leftPos < minLeft) leftPos = minLeft;
320
+
321
+ // Trigger top expressed in the Modal's (full-screen) coordinate space.
322
+ const triggerTop = triggerRect.y + windowTopOffset;
301
323
  let topPos;
302
324
  if (computedPlacement === 'top') {
303
325
  const desiredHeight = menuSize?.height ?? menuMaxHeight;
304
- topPos = triggerRect.y - desiredHeight - menuOffset;
326
+ topPos = triggerTop - desiredHeight - effectiveMenuOffset;
305
327
  if (topPos < insets.top + screenPadding) {
306
328
  topPos = insets.top + screenPadding;
307
329
  }
308
330
  } else {
309
- topPos = triggerRect.y + triggerRect.height + menuOffset;
331
+ topPos = triggerTop + triggerRect.height + effectiveMenuOffset;
310
332
  }
311
333
  const style = {
312
334
  position: 'absolute',
@@ -318,7 +340,7 @@ function DropdownInput({
318
340
  // the wrong place. menuSize becomes truthy after the first layout.
319
341
  if (menuSize == null) style.opacity = 0;
320
342
  return style;
321
- }, [triggerRect, computedPlacement, menuSize, menuOffset, menuMaxHeight, matchTriggerWidth, windowWidth, insets.top, insets.left, insets.right]);
343
+ }, [triggerRect, computedPlacement, menuSize, effectiveMenuOffset, windowTopOffset, menuMaxHeight, matchTriggerWidth, windowWidth, insets.top, insets.left, insets.right]);
322
344
 
323
345
  // Reset menu size when closing so the next open re-measures (handles items
324
346
  // changing while the menu was closed).
@@ -507,6 +529,8 @@ function DropdownInput({
507
529
  }), /*#__PURE__*/_jsx(Modal, {
508
530
  visible: isOpen,
509
531
  transparent: true,
532
+ statusBarTranslucent: true,
533
+ navigationBarTranslucent: true,
510
534
  animationType: "fade",
511
535
  onRequestClose: closeMenu,
512
536
  children: /*#__PURE__*/_jsx(Pressable, {
@@ -82,8 +82,6 @@ function ExpandableCheckbox({
82
82
  }, [disabled, isExpanded, isExpandedControlled, onExpandedChange]);
83
83
  const gap = getVariableByName('expandableCheckbox/gap', modes) ?? 8;
84
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
85
  const labelColor = getVariableByName('checkboxItem/foreground', modes) ?? '#1a1c1f';
88
86
  const labelFontFamily = getVariableByName('checkboxItem/label/fontFamily', modes) ?? 'JioType Var';
89
87
  const labelFontSize = getVariableByName('checkboxItem/label/fontSize', modes) ?? 14;
@@ -104,11 +102,9 @@ function ExpandableCheckbox({
104
102
  alignSelf: isExpanded ? 'stretch' : 'auto',
105
103
  minWidth: 0,
106
104
  flexDirection: 'row',
107
- alignItems: 'flex-start',
108
- gap: rowGap,
109
- paddingHorizontal: rowPaddingHorizontal,
110
- paddingVertical: rowPaddingVertical
111
- }), [isExpanded, rowGap, rowPaddingHorizontal, rowPaddingVertical]);
105
+ alignItems: isExpanded ? 'flex-start' : 'center',
106
+ gap: rowGap
107
+ }), [isExpanded, rowGap]);
112
108
  const resolvedLabelStyle = useMemo(() => ({
113
109
  flex: 1,
114
110
  minWidth: 0,
@@ -116,11 +112,21 @@ function ExpandableCheckbox({
116
112
  fontFamily: labelFontFamily,
117
113
  fontSize: labelFontSize,
118
114
  lineHeight: labelLineHeight,
119
- fontWeight: labelFontWeight
120
- }), [labelColor, labelFontFamily, labelFontSize, labelLineHeight, labelFontWeight]);
115
+ fontWeight: labelFontWeight,
116
+ // Android adds asymmetric font padding and top-aligns the glyph inside
117
+ // an inflated line box when `lineHeight` is set. That makes the centered
118
+ // checkbox look like it drops below the text. Disabling the extra
119
+ // padding + centering the glyph keeps the single-line label optically
120
+ // aligned with the checkbox. No-op on iOS / web.
121
+ includeFontPadding: false,
122
+ textAlignVertical: isExpanded ? 'top' : 'center'
123
+ }), [labelColor, labelFontFamily, labelFontSize, labelLineHeight, labelFontWeight, isExpanded]);
124
+
125
+ // Layer component modes first (e.g. Color Mode), then button defaults so
126
+ // Secondary / XS / Low always win unless a dedicated override prop is added.
121
127
  const buttonModes = useMemo(() => ({
122
- ...BUTTON_DEFAULT_MODES,
123
- ...modes
128
+ ...modes,
129
+ ...BUTTON_DEFAULT_MODES
124
130
  }), [modes]);
125
131
  const a11yLabel = accessibilityLabel ?? (typeof label === 'string' ? label : undefined);
126
132
  const buttonLabel = isExpanded ? readLessLabel : readMoreLabel;
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
 
3
- import React, { useCallback, useMemo, useRef, useState } from 'react';
4
- import { View, Text, Pressable, TextInput as RNTextInput } from 'react-native';
3
+ import React, { useCallback, useMemo, useState } from 'react';
4
+ import { View, Text, 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,16 +202,6 @@ 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
-
215
205
  // FormField States cascade — error > read only/disabled > active (focused) > idle.
216
206
  // Disabled maps to "Read Only" since there is no dedicated disabled mode and
217
207
  // the visual treatment is closest. This is only the DEFAULT — an explicit
@@ -344,16 +334,13 @@ function FormField({
344
334
  style: requiredIndicatorStyle,
345
335
  children: " *"
346
336
  })]
347
- }), /*#__PURE__*/_jsxs(Pressable, {
337
+ }), /*#__PURE__*/_jsxs(View, {
348
338
  style: [inputRowStyle, inputStyle],
349
- onPress: focusInput,
350
- accessible: false,
351
339
  children: [processedLeading != null && /*#__PURE__*/_jsx(View, {
352
340
  accessibilityElementsHidden: true,
353
341
  importantForAccessibility: "no",
354
342
  children: processedLeading
355
343
  }), /*#__PURE__*/_jsx(RNTextInput, {
356
- ref: inputRef,
357
344
  style: [inputTextStyles, inputTextStyle],
358
345
  value: value ?? '',
359
346
  onChangeText: handleChangeText,
@@ -296,7 +296,11 @@ function FullscreenModal({
296
296
  contentContainerStyle: scrollContentStyle,
297
297
  showsVerticalScrollIndicator: false,
298
298
  onScroll: onScroll,
299
- scrollEventThrottle: 16,
299
+ scrollEventThrottle: 16
300
+ // Tap an input in the body and it focuses on the FIRST tap, even when
301
+ // the keyboard is already open (default 'never' eats that tap).
302
+ ,
303
+ keyboardShouldPersistTaps: "handled",
300
304
  children: [/*#__PURE__*/_jsx(View, {
301
305
  style: heroTextRegionStyle,
302
306
  children: /*#__PURE__*/_jsx(HeroText, {
@@ -3,7 +3,6 @@
3
3
  import React, { useCallback, useMemo, useRef } from 'react';
4
4
  import { View, Text, Pressable, Platform } from 'react-native';
5
5
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
- import IconCapsule from '../IconCapsule/IconCapsule';
7
6
  import NavArrow from '../NavArrow/NavArrow';
8
7
  import { usePressableWebSupport } from '../../utils/web-platform-utils';
9
8
  import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils';
@@ -144,7 +143,7 @@ const verticalSupportTextOverride = {
144
143
  * @param {string} [props.title='Title'] - Primary title used in the horizontal layout.
145
144
  * @param {string} [props.supportText='Support Text'] - Support text used in both layouts when `supportSlot` is not provided.
146
145
  * @param {boolean} [props.showSupportText=true] - Toggles rendering of the support text in Horizontal layout.
147
- * @param {React.ReactNode} [props.leading] - Optional leading slot. Defaults to `IconCapsule`.
146
+ * @param {React.ReactNode|null} [props.leading] - Optional leading slot. Omitted or `null` renders nothing.
148
147
  * @param {React.ReactNode} [props.supportSlot] - Optional custom slot used instead of the default support text block.
149
148
  * @param {React.ReactNode} [props.trailing] - Optional trailing slot (Figma Slot "trailing"). Horizontal layout only.
150
149
  * @param {boolean} [props.navArrow=true] - Whether to show NavArrow on the far right (Horizontal layout only).
@@ -213,13 +212,9 @@ function ListItemImpl({
213
212
  // Process leading slot to pass modes to children. Memoized on
214
213
  // (leading, resolvedModes) so a parent re-render doesn't re-walk the tree.
215
214
  const leadingElement = useMemo(() => {
216
- const processed = leading ? cloneChildrenWithModes(React.Children.toArray(leading), tokens.resolvedModes, SLOT_FORCED_MODES) : [];
217
- if (processed.length === 0) {
218
- return /*#__PURE__*/_jsx(IconCapsule, {
219
- modes: tokens.resolvedModes,
220
- accessibilityLabel: undefined
221
- });
222
- }
215
+ if (leading == null) return null;
216
+ const processed = cloneChildrenWithModes(React.Children.toArray(leading), tokens.resolvedModes, SLOT_FORCED_MODES);
217
+ if (processed.length === 0) return null;
223
218
  return processed.length === 1 ? processed[0] : processed;
224
219
  }, [leading, tokens.resolvedModes]);
225
220
  const processedSupportSlot = useMemo(() => {
@@ -269,7 +264,7 @@ function ListItemImpl({
269
264
  if (layout === 'Horizontal') {
270
265
  const innerContent = /*#__PURE__*/_jsxs(View, {
271
266
  style: innerContentStyleArray,
272
- children: [leadingElement, /*#__PURE__*/_jsxs(View, {
267
+ children: [leadingElement ?? null, /*#__PURE__*/_jsxs(View, {
273
268
  style: {
274
269
  flex: 1,
275
270
  minWidth: 1,
@@ -316,7 +311,7 @@ function ListItemImpl({
316
311
  // Vertical layout — icon on top, support text/slot below
317
312
  const verticalContent = /*#__PURE__*/_jsxs(View, {
318
313
  style: verticalContentStyleArray,
319
- children: [leadingElement, renderSupportContent()]
314
+ children: [leadingElement ?? null, renderSupportContent()]
320
315
  });
321
316
  if (onPress) {
322
317
  return /*#__PURE__*/_jsx(Pressable, {
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
 
3
- import React, { useCallback, useMemo, useRef, useState } from 'react';
4
- import { View, Text, Pressable, TextInput as RNTextInput } from 'react-native';
3
+ import React, { useCallback, useMemo, useState } from 'react';
4
+ import { View, Text, 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 } from '../../utils/react-utils';
@@ -140,15 +140,6 @@ function MessageField({
140
140
  const currentValue = isControlled ? value : uncontrolledValue;
141
141
  const [isFocused, setIsFocused] = useState(false);
142
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
143
  const {
153
144
  modes: globalModes
154
145
  } = useTokens();
@@ -283,12 +274,9 @@ function MessageField({
283
274
  style: requiredIndicatorStyle,
284
275
  children: " *"
285
276
  })]
286
- }), /*#__PURE__*/_jsx(Pressable, {
277
+ }), /*#__PURE__*/_jsx(View, {
287
278
  style: [textareaContainerStyle, textareaStyle],
288
- onPress: focusInput,
289
- accessible: false,
290
279
  children: /*#__PURE__*/_jsx(RNTextInput, {
291
- ref: inputRef,
292
280
  multiline: true,
293
281
  value: currentValue,
294
282
  onChangeText: handleChangeText,
@@ -17,6 +17,7 @@ function MetricLegendItem({
17
17
  label = 'Current (4 months)',
18
18
  value,
19
19
  indicatorColor,
20
+ indicatorShape = 'dot',
20
21
  modes = EMPTY_MODES,
21
22
  style,
22
23
  indicatorStyle,
@@ -49,7 +50,12 @@ function MetricLegendItem({
49
50
  }, style],
50
51
  accessibilityRole: "text",
51
52
  children: [/*#__PURE__*/_jsx(View, {
52
- style: [{
53
+ style: [indicatorShape === 'line' ? {
54
+ width: indicatorSize * 2,
55
+ height: Math.max(2, Math.round(indicatorSize / 4)),
56
+ borderRadius: indicatorRadius,
57
+ backgroundColor: indicatorBg
58
+ } : {
53
59
  width: indicatorSize,
54
60
  height: indicatorSize,
55
61
  borderRadius: indicatorRadius,
@@ -1,16 +1,16 @@
1
1
  "use strict";
2
2
 
3
- import React, { isValidElement, cloneElement } from 'react';
3
+ import React from 'react';
4
4
  import { View, Text } from 'react-native';
5
5
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
6
  import { useTokens } from '../../design-tokens/JFSThemeProvider';
7
7
  import IconCapsule from '../IconCapsule/IconCapsule';
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 default function PaymentFeedback({
11
11
  title = '₹50,000',
12
12
  subtitle = 'Payment successful',
13
- body = 'Your payment has been\nsuccessfully processed.',
13
+ body,
14
14
  details = '18 March 2025, 4:15 pm\nTransaction ID: TXN121466784',
15
15
  showDetails = true,
16
16
  iconName = 'ic_confirm',
@@ -97,17 +97,21 @@ export default function PaymentFeedback({
97
97
  fontWeight: String(detailsFontWeight),
98
98
  textAlign: 'center'
99
99
  };
100
- const mediaContent = /*#__PURE__*/isValidElement(renderMedia) ? /*#__PURE__*/cloneElement(renderMedia, {
101
- modes
102
- }) : renderMedia;
100
+
101
+ // Cascade modes into a custom media slot (per the modes-cascade convention);
102
+ // any modes the consumer set on the slot child still take precedence.
103
+ const mediaContent = renderMedia != null ? cloneChildrenWithModes(renderMedia, modes) : null;
103
104
  const defaultMedia = /*#__PURE__*/_jsx(IconCapsule, {
104
- iconName: iconName,
105
+ iconName: iconName
106
+ // `positive` is the default; consumers override the capsule color by
107
+ // passing `AppearanceSystem` (or any other mode) via the `modes` prop.
108
+ ,
105
109
  modes: {
110
+ AppearanceSystem: 'positive',
106
111
  ...modes,
107
112
  'Icon Capsule Size': 'L',
108
113
  Emphasis: 'High',
109
- 'Semantic Intent': 'System',
110
- AppearanceSystem: 'positive'
114
+ 'Semantic Intent': 'System'
111
115
  }
112
116
  });
113
117
  const detailLines = details?.split('\n') ?? [];