jfs-components 0.0.55 → 0.0.56

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 (48) hide show
  1. package/lib/commonjs/components/Accordion/Accordion.js +2 -34
  2. package/lib/commonjs/components/AppBar/AppBar.js +4 -36
  3. package/lib/commonjs/components/FilterBar/FilterBar.js +2 -34
  4. package/lib/commonjs/components/LazyList/LazyList.js +2 -34
  5. package/lib/commonjs/components/MoneyValue/MoneyValue.js +1 -1
  6. package/lib/commonjs/components/NavArrow/NavArrow.js +45 -44
  7. package/lib/commonjs/components/Numpad/Numpad.js +6 -5
  8. package/lib/commonjs/components/Section/Section.js +7 -8
  9. package/lib/commonjs/components/TextInput/TextInput.js +4 -38
  10. package/lib/commonjs/components/TransactionBubble/TransactionBubble.js +145 -0
  11. package/lib/commonjs/components/index.js +7 -0
  12. package/lib/commonjs/design-tokens/JFSThemeProvider.js +38 -3
  13. package/lib/commonjs/icons/registry.js +1 -1
  14. package/lib/commonjs/utils/react-utils.js +18 -13
  15. package/lib/module/components/Accordion/Accordion.js +1 -33
  16. package/lib/module/components/AppBar/AppBar.js +1 -34
  17. package/lib/module/components/FilterBar/FilterBar.js +1 -35
  18. package/lib/module/components/LazyList/LazyList.js +1 -35
  19. package/lib/module/components/MoneyValue/MoneyValue.js +1 -1
  20. package/lib/module/components/NavArrow/NavArrow.js +44 -44
  21. package/lib/module/components/Numpad/Numpad.js +5 -5
  22. package/lib/module/components/Section/Section.js +8 -9
  23. package/lib/module/components/TextInput/TextInput.js +2 -36
  24. package/lib/module/components/TransactionBubble/TransactionBubble.js +140 -0
  25. package/lib/module/components/index.js +1 -0
  26. package/lib/module/design-tokens/JFSThemeProvider.js +35 -3
  27. package/lib/module/icons/registry.js +1 -1
  28. package/lib/module/utils/react-utils.js +18 -13
  29. package/lib/typescript/src/components/NavArrow/NavArrow.d.ts +6 -11
  30. package/lib/typescript/src/components/TransactionBubble/TransactionBubble.d.ts +36 -0
  31. package/lib/typescript/src/components/index.d.ts +1 -0
  32. package/lib/typescript/src/design-tokens/JFSThemeProvider.d.ts +15 -0
  33. package/lib/typescript/src/icons/registry.d.ts +1 -1
  34. package/package.json +1 -1
  35. package/src/components/Accordion/Accordion.tsx +1 -44
  36. package/src/components/AppBar/AppBar.tsx +1 -44
  37. package/src/components/FilterBar/FilterBar.tsx +1 -44
  38. package/src/components/LazyList/LazyList.tsx +1 -41
  39. package/src/components/MoneyValue/MoneyValue.tsx +1 -1
  40. package/src/components/NavArrow/NavArrow.tsx +46 -43
  41. package/src/components/Numpad/Numpad.tsx +5 -5
  42. package/src/components/Section/Section.tsx +8 -8
  43. package/src/components/TextInput/TextInput.tsx +1 -44
  44. package/src/components/TransactionBubble/TransactionBubble.tsx +152 -0
  45. package/src/components/index.ts +1 -0
  46. package/src/design-tokens/JFSThemeProvider.tsx +37 -2
  47. package/src/icons/registry.ts +1 -1
  48. package/src/utils/react-utils.ts +29 -21
@@ -12,17 +12,25 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
12
12
  * This ensures that all child components in slots receive the modes prop from the parent.
13
13
  */
14
14
  function cloneChildrenWithModes(children, modes, forcedModes) {
15
- return _react.default.Children.map(children, child => {
15
+ const result = [];
16
+ _react.default.Children.forEach(children, child => {
16
17
  if (! /*#__PURE__*/_react.default.isValidElement(child)) {
17
- return child;
18
+ if (child !== null && child !== undefined) {
19
+ result.push(child);
20
+ }
21
+ return;
18
22
  }
19
23
 
20
- // Get existing children
24
+ // Unwrap Fragments: Fragments can't accept arbitrary props like `modes`,
25
+ // so recurse into their children and process each one individually.
26
+ if (child.type === _react.default.Fragment) {
27
+ const fragment = child;
28
+ result.push(...cloneChildrenWithModes(fragment.props.children, modes, forcedModes));
29
+ return;
30
+ }
21
31
  const childChildren = child.props?.children;
22
32
  const hasChildren = childChildren !== undefined && childChildren !== null;
23
33
 
24
- // Clone the child with modes prop if it doesn't already have one
25
- // or merge with existing modes if it does
26
34
  // Merge order: parent modes first, then child's explicit modes override them,
27
35
  // then forcedModes (if provided) are applied last and can never be overridden
28
36
  const existingModes = child.props?.modes;
@@ -34,16 +42,13 @@ function cloneChildrenWithModes(children, modes, forcedModes) {
34
42
  ...modes,
35
43
  ...existingModes
36
44
  } : modes;
37
-
38
- // Recursively process children if they exist
39
- const processedChildren = hasChildren ? cloneChildrenWithModes(_react.default.Children.toArray(childChildren), modes, forcedModes) : undefined;
40
-
41
- // Clone element with modes and processed children
42
- return /*#__PURE__*/_react.default.cloneElement(child, {
45
+ const processedChildren = hasChildren ? cloneChildrenWithModes(childChildren, modes, forcedModes) : undefined;
46
+ result.push(/*#__PURE__*/_react.default.cloneElement(child, {
43
47
  ...child.props,
44
48
  modes: mergedModes
45
- }, processedChildren);
46
- })?.filter(child => child !== null && child !== undefined) ?? [];
49
+ }, processedChildren));
50
+ });
51
+ return result;
47
52
  }
48
53
 
49
54
  /**
@@ -5,45 +5,13 @@ import { View, Text, Pressable, LayoutAnimation, Platform, UIManager } from 'rea
5
5
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
6
  import Icon from '../../icons/Icon';
7
7
  import { usePressableWebSupport } from '../../utils/web-platform-utils';
8
+ import { cloneChildrenWithModes } from '../../utils/react-utils';
8
9
 
9
10
  // Enable LayoutAnimation on Android
10
11
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
12
  if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
12
13
  UIManager.setLayoutAnimationEnabledExperimental(true);
13
14
  }
14
-
15
- /**
16
- * Helper function to recursively clone children and pass modes prop to components that accept it.
17
- * This ensures that all child components in slots receive the modes prop from the parent.
18
- */
19
- function cloneChildrenWithModes(children, modes) {
20
- const result = React.Children.map(children, child => {
21
- if (! /*#__PURE__*/React.isValidElement(child)) {
22
- return child;
23
- }
24
-
25
- // Get existing children
26
- const childChildren = child.props?.children;
27
- const hasChildren = childChildren !== undefined && childChildren !== null;
28
-
29
- // Merge modes: parent modes first, then child's explicit modes override them
30
- const existingModes = child.props?.modes;
31
- const mergedModes = existingModes ? {
32
- ...modes,
33
- ...existingModes
34
- } : modes;
35
-
36
- // Recursively process children if they exist
37
- const processedChildren = hasChildren ? cloneChildrenWithModes(React.Children.toArray(childChildren), modes) : undefined;
38
-
39
- // Clone element with modes and processed children
40
- return /*#__PURE__*/React.cloneElement(child, {
41
- ...child.props,
42
- modes: mergedModes
43
- }, processedChildren);
44
- });
45
- return result || [];
46
- }
47
15
  /**
48
16
  * Accordion component that mirrors the Figma "Accordion" component.
49
17
  *
@@ -5,41 +5,8 @@ import { View, Pressable } from 'react-native';
5
5
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
6
  import { useTokens } from '../../design-tokens/JFSThemeProvider';
7
7
  import NavArrow from '../NavArrow/NavArrow';
8
- // We might need to import Image or Svg if we had the assets locally, but for now we'll use placeholders or standard icons for defaults.
9
- // The user prompt mentioned "Use getVariableByName... strict camelCase".
10
-
11
- /**
12
- * Helper function to recursively clone children and pass modes prop to components that accept it.
13
- */
8
+ import { cloneChildrenWithModes } from '../../utils/react-utils';
14
9
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
15
- function cloneChildrenWithModes(children, modes) {
16
- return React.Children.map(children, child => {
17
- if (! /*#__PURE__*/React.isValidElement(child)) {
18
- return child;
19
- }
20
-
21
- // Get existing children
22
- const childChildren = child.props?.children;
23
- const hasChildren = childChildren !== undefined && childChildren !== null;
24
-
25
- // Clone the child with modes prop if it doesn't already have one
26
- // or merge with existing modes if it does
27
- const existingModes = child.props?.modes;
28
- const mergedModes = existingModes ? {
29
- ...modes,
30
- ...existingModes
31
- } : modes;
32
-
33
- // Recursively process children if they exist
34
- const processedChildren = hasChildren ? cloneChildrenWithModes(React.Children.toArray(childChildren), modes) : undefined;
35
-
36
- // Clone element with modes and processed children
37
- return /*#__PURE__*/React.cloneElement(child, {
38
- ...child.props,
39
- modes: mergedModes
40
- }, processedChildren);
41
- })?.filter(child => child !== null && child !== undefined) ?? [];
42
- }
43
10
  export default function AppBar({
44
11
  type = 'MainPage',
45
12
  leadingSlot,
@@ -4,42 +4,8 @@ import React, { useState } from 'react';
4
4
  import { View } from 'react-native';
5
5
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
6
  import TextInput from '../TextInput/TextInput';
7
-
8
- /**
9
- * Helper function to recursively clone children and pass modes prop to components that accept it.
10
- * This ensures that all child components in slots receive the modes prop from the parent.
11
- */
7
+ import { cloneChildrenWithModes } from '../../utils/react-utils';
12
8
  import { jsx as _jsx } from "react/jsx-runtime";
13
- function cloneChildrenWithModes(children, modes) {
14
- const result = React.Children.map(children, child => {
15
- if (! /*#__PURE__*/React.isValidElement(child)) {
16
- return child;
17
- }
18
-
19
- // Get existing children
20
- const childChildren = child.props?.children;
21
- const hasChildren = childChildren !== undefined && childChildren !== null;
22
-
23
- // Clone the child with modes prop if it doesn't already have one
24
- // or merge with existing modes if it does
25
- // Merge order: parent modes first, then child's explicit modes override them
26
- const existingModes = child.props?.modes;
27
- const mergedModes = existingModes ? {
28
- ...modes,
29
- ...existingModes
30
- } : modes;
31
-
32
- // Recursively process children if they exist
33
- const processedChildren = hasChildren ? cloneChildrenWithModes(React.Children.toArray(childChildren), modes) : undefined;
34
-
35
- // Clone element with modes and processed children
36
- return /*#__PURE__*/React.cloneElement(child, {
37
- ...child.props,
38
- modes: mergedModes
39
- }, processedChildren);
40
- });
41
- return result || [];
42
- }
43
9
  /**
44
10
  * FilterBar component that mirrors the Figma "filterBar" component.
45
11
  *
@@ -3,42 +3,8 @@
3
3
  import React from 'react';
4
4
  import { View } from 'react-native';
5
5
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
-
7
- /**
8
- * Helper function to recursively clone children and pass modes prop to components that accept it.
9
- * This ensures that all child components in slots receive the modes prop from the parent.
10
- */
6
+ import { cloneChildrenWithModes } from '../../utils/react-utils';
11
7
  import { jsx as _jsx } from "react/jsx-runtime";
12
- function cloneChildrenWithModes(children, modes) {
13
- return React.Children.map(children, child => {
14
- if (! /*#__PURE__*/React.isValidElement(child)) {
15
- return child;
16
- }
17
-
18
- // Get existing children
19
- const childProps = child.props;
20
- const childChildren = childProps?.children;
21
- const hasChildren = childChildren !== undefined && childChildren !== null;
22
-
23
- // Clone the child with modes prop if it doesn't already have one
24
- // or merge with existing modes if it does
25
- // Merge order: parent modes first, then child's explicit modes override them
26
- const existingModes = childProps?.modes;
27
- const mergedModes = existingModes ? {
28
- ...modes,
29
- ...existingModes
30
- } : modes;
31
-
32
- // Recursively process children if they exist
33
- const processedChildren = hasChildren ? cloneChildrenWithModes(React.Children.toArray(childChildren), modes) : undefined;
34
-
35
- // Clone element with modes and processed children
36
- return /*#__PURE__*/React.cloneElement(child, {
37
- ...childProps,
38
- modes: mergedModes
39
- }, processedChildren);
40
- });
41
- }
42
8
  /**
43
9
  * LazyList component that mirrors the Figma "LazyList" component.
44
10
  *
@@ -95,7 +95,7 @@ function MoneyValue({
95
95
  const fontWeightValue = getVariableByName('moneyValue/fontWeight', modes) || 500;
96
96
  const fontWeight = typeof fontWeightValue === 'number' ? fontWeightValue.toString() : fontWeightValue;
97
97
  const fontFamily = getVariableByName('moneyValue/fontFamily', modes) || 'System';
98
- const gap = getVariableByName('moneyValue/gap', modes) || 4;
98
+ const gap = getVariableByName('moneyValue/gap', modes) ?? 4;
99
99
 
100
100
  // Resolve currency to a symbol, supporting both symbols and ISO codes
101
101
  const resolvedCurrency = useMemo(() => {
@@ -2,27 +2,22 @@
2
2
 
3
3
  import React from 'react';
4
4
  import { View } from 'react-native';
5
+ import Svg, { Polyline } from 'react-native-svg';
5
6
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
- import Icon from '../../icons/Icon';
7
7
  import { jsx as _jsx } from "react/jsx-runtime";
8
8
  /**
9
- * NavArrow component that displays a small chevron arrow for navigation.
9
+ * NavArrow component that displays a chevron arrow for navigation.
10
10
  *
11
- * This component uses design tokens for all visual properties:
11
+ * Renders a stroked SVG chevron whose dimensions and thickness are
12
+ * fully driven by design tokens:
12
13
  * - navArrow/icon/color - chevron stroke color
13
- * - navArrow/icon/width - icon width
14
- * - navArrow/icon/height - icon height
15
- * - navArrow/icon/strokeWeight - stroke width
14
+ * - navArrow/icon/width - chevron arm width (horizontal spread)
15
+ * - navArrow/icon/height - chevron arm height (vertical spread)
16
+ * - navArrow/icon/strokeWeight - stroke thickness
16
17
  * - navArrow/width - container width
17
18
  * - navArrow/height - container height
18
19
  * - navArrow/radius - border radius
19
20
  * - navArrow/background - background color
20
- *
21
- * @component
22
- * @param {Object} props
23
- * @param {'Back'|'Forward'|'Down'} [props.direction='Back'] - Arrow direction
24
- * @param {Object} [props.modes={}] - Modes for design token resolution
25
- * @param {Object} [props.style] - Additional container styles
26
21
  */
27
22
  export default function NavArrow({
28
23
  direction = 'Back',
@@ -31,22 +26,20 @@ export default function NavArrow({
31
26
  accessibilityLabel,
32
27
  ...rest
33
28
  }) {
34
- // Resolve design tokens
35
29
  const iconColor = getVariableByName('navArrow/icon/color', modes) || '#24262b';
36
-
37
- // Dimensions from tokens
38
30
  const widthToken = Number(getVariableByName('navArrow/width', modes)) || 6;
39
31
  const heightToken = Number(getVariableByName('navArrow/height', modes)) || 10;
40
32
  const borderRadius = Number(getVariableByName('navArrow/radius', modes)) || 0;
41
33
  const backgroundColor = getVariableByName('navArrow/background', modes) || 'transparent';
42
-
43
- // Swap dimensions if direction is Down
34
+ const iconWidth = Number(getVariableByName('navArrow/icon/width', modes)) || 4;
35
+ const iconHeight = Number(getVariableByName('navArrow/icon/height', modes)) || 8;
36
+ const strokeWeight = Number(getVariableByName('navArrow/icon/strokeWeight', modes)) || 2;
44
37
  const isDown = direction === 'Down';
45
- const width = isDown ? heightToken : widthToken;
46
- const height = isDown ? widthToken : heightToken;
38
+ const containerWidth = isDown ? heightToken : widthToken;
39
+ const containerHeight = isDown ? widthToken : heightToken;
47
40
  const containerStyle = {
48
- width,
49
- height,
41
+ width: containerWidth,
42
+ height: containerHeight,
50
43
  borderRadius,
51
44
  backgroundColor,
52
45
  alignItems: 'center',
@@ -54,34 +47,41 @@ export default function NavArrow({
54
47
  ...(style || {})
55
48
  };
56
49
  const defaultAccessibilityLabel = accessibilityLabel || (direction === 'Back' ? 'Go back' : direction === 'Forward' ? 'Go forward' : 'Go down');
57
-
58
- // Map direction to icon name
59
- let iconName = 'ic_chevron_left'; // Default for Back
60
- if (direction === 'Forward') {
61
- iconName = 'ic_chevron_right';
62
- } else if (direction === 'Down') {
63
- iconName = 'ic_chevron_down';
50
+ const chevronW = isDown ? iconHeight : iconWidth;
51
+ const chevronH = isDown ? iconWidth : iconHeight;
52
+ const pad = strokeWeight / 2;
53
+ const svgWidth = chevronW + pad * 2;
54
+ const svgHeight = chevronH + pad * 2;
55
+ let points;
56
+ switch (direction) {
57
+ case 'Forward':
58
+ points = `${pad},${pad} ${chevronW + pad},${chevronH / 2 + pad} ${pad},${chevronH + pad}`;
59
+ break;
60
+ case 'Down':
61
+ points = `${pad},${pad} ${chevronW / 2 + pad},${chevronH + pad} ${chevronW + pad},${pad}`;
62
+ break;
63
+ case 'Back':
64
+ default:
65
+ points = `${chevronW + pad},${pad} ${pad},${chevronH / 2 + pad} ${chevronW + pad},${chevronH + pad}`;
66
+ break;
64
67
  }
65
68
  return /*#__PURE__*/_jsx(View, {
66
69
  style: containerStyle,
67
70
  accessibilityRole: "image",
68
- accessibilityLabel: undefined,
71
+ accessibilityLabel: defaultAccessibilityLabel,
69
72
  ...rest,
70
- children: /*#__PURE__*/_jsx(Icon, {
71
- name: iconName,
72
- size: 24 // Internal icon size is fixed/clipped by container in design but Icon requires a size
73
- ,
74
- color: iconColor,
75
- style: {
76
- // Center the larger icon within the small container if needed,
77
- // though flex center on container handles this.
78
- // If the container is 6x10 and icon is 24, we might want to ensure it doesn't affect layout
79
- // but Flexbox 'center' centers the content.
80
- // However, if the icon component has its own frame, it might overflow.
81
- // React Native View has overflow: 'hidden' by default if borderRadius is set? No.
82
- // We might want overflow: 'hidden' if strictly following design clip.
83
- // Figma design had "overflow-clip" class.
84
- }
73
+ children: /*#__PURE__*/_jsx(Svg, {
74
+ width: svgWidth,
75
+ height: svgHeight,
76
+ viewBox: `0 0 ${svgWidth} ${svgHeight}`,
77
+ children: /*#__PURE__*/_jsx(Polyline, {
78
+ points: points,
79
+ stroke: iconColor,
80
+ strokeWidth: strokeWeight,
81
+ strokeLinecap: "round",
82
+ strokeLinejoin: "round",
83
+ fill: "none"
84
+ })
85
85
  })
86
86
  });
87
87
  }
@@ -3,7 +3,7 @@
3
3
  import React, { useMemo, useCallback } from 'react';
4
4
  import { View, Text, Pressable } from 'react-native';
5
5
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
- import { IconDeletebackspace } from '../../icons/components/IconDeletebackspace';
6
+ import Icon from '../../icons/Icon';
7
7
  import { jsx as _jsx } from "react/jsx-runtime";
8
8
  function shuffleArray(arr) {
9
9
  const shuffled = [...arr];
@@ -87,10 +87,10 @@ function Numpad({
87
87
  onPress: () => handlePress(key),
88
88
  accessibilityRole: "button",
89
89
  accessibilityLabel: isBackspace ? 'Backspace' : key,
90
- children: isBackspace ? /*#__PURE__*/_jsx(IconDeletebackspace, {
91
- width: fontSize,
92
- height: fontSize,
93
- fill: foreground
90
+ children: isBackspace ? /*#__PURE__*/_jsx(Icon, {
91
+ name: "ic_delete_backspace",
92
+ size: fontSize,
93
+ color: foreground
94
94
  }) : /*#__PURE__*/_jsx(Text, {
95
95
  style: [textStyle, keyTextStyle],
96
96
  children: key
@@ -5,7 +5,7 @@ import { View, Text, Pressable, Platform } from 'react-native';
5
5
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
6
  import NavArrow from '../NavArrow/NavArrow';
7
7
  import { usePressableWebSupport } from '../../utils/web-platform-utils';
8
- import { cloneChildrenWithModes } from '../../utils/react-utils';
8
+ import { cloneChildrenWithModes, flattenChildren } from '../../utils/react-utils';
9
9
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
10
10
  /**
11
11
  * Section component that mirrors the Figma "Section" component.
@@ -60,7 +60,8 @@ function Section({
60
60
  } : {};
61
61
  // Resolve section container tokens
62
62
  const backgroundColor = getVariableByName('section/background/color', modes) || '#ffffff';
63
- const gap = getVariableByName('slot/gap', modes) || 12;
63
+ const sectionGap = getVariableByName('section/gap', modes) || 12;
64
+ const slotGap = getVariableByName('slot/gap', modes) || 12;
64
65
  const paddingHorizontal = getVariableByName('section/padding/horizontal', modes) || 12;
65
66
  const paddingVertical = getVariableByName('section/padding/vertical', modes) || 16;
66
67
  const radius = getVariableByName('section/radius', modes) || 12;
@@ -90,7 +91,7 @@ function Section({
90
91
  paddingHorizontal,
91
92
  paddingVertical,
92
93
  borderRadius: radius,
93
- gap
94
+ gap: sectionGap
94
95
  };
95
96
  const headerStyle = {
96
97
  paddingHorizontal: headerPaddingHorizontal,
@@ -198,9 +199,9 @@ function Section({
198
199
  }), slot && /*#__PURE__*/_jsx(View, {
199
200
  style: {
200
201
  flexDirection: slotDirection,
201
- gap
202
+ gap: slotGap
202
203
  },
203
- children: cloneChildrenWithModes(React.Children.toArray(slot), modes)
204
+ children: cloneChildrenWithModes(flattenChildren(slot), modes)
204
205
  })]
205
206
  });
206
207
  }
@@ -248,10 +249,8 @@ function SectionBento({
248
249
  borderRadius: radius,
249
250
  gap
250
251
  };
251
-
252
- // Process slots to pass modes to children
253
- const processedNavSlot = navSlot ? cloneChildrenWithModes(React.Children.toArray(navSlot), modes) : null;
254
- const processedUpiSlot = upiSlot ? cloneChildrenWithModes(React.Children.toArray(upiSlot), modes) : null;
252
+ const processedNavSlot = navSlot ? cloneChildrenWithModes(flattenChildren(navSlot), modes) : null;
253
+ const processedUpiSlot = upiSlot ? cloneChildrenWithModes(flattenChildren(upiSlot), modes) : null;
255
254
  return /*#__PURE__*/_jsxs(View, {
256
255
  style: [containerStyle, style],
257
256
  ...(Platform.OS === 'web' ? {
@@ -4,42 +4,7 @@ import React, { useState } from 'react';
4
4
  import { Pressable, View, TextInput as RNTextInput } from 'react-native';
5
5
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
6
  import Icon from '../../icons/Icon';
7
-
8
- /**
9
- * Helper function to recursively clone children and pass modes prop to components that accept it.
10
- * This ensures that all child components in slots receive the modes prop from the parent.
11
- */
12
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
- function cloneChildrenWithModes(children, modes) {
14
- const result = React.Children.map(children, child => {
15
- if (! /*#__PURE__*/React.isValidElement(child)) {
16
- return child;
17
- }
18
-
19
- // Get existing children
20
- const childChildren = child.props?.children;
21
- const hasChildren = childChildren !== undefined && childChildren !== null;
22
-
23
- // Clone the child with modes prop if it doesn't already have one
24
- // or merge with existing modes if it does
25
- // Merge order: parent modes first, then child's explicit modes override them
26
- const existingModes = child.props?.modes;
27
- const mergedModes = existingModes ? {
28
- ...modes,
29
- ...existingModes
30
- } : modes;
31
-
32
- // Recursively process children if they exist
33
- const processedChildren = hasChildren ? cloneChildrenWithModes(React.Children.toArray(childChildren), modes) : undefined;
34
-
35
- // Clone element with modes and processed children
36
- return /*#__PURE__*/React.cloneElement(child, {
37
- ...child.props,
38
- modes: mergedModes
39
- }, processedChildren);
40
- });
41
- return result || [];
42
- }
7
+ import { cloneChildrenWithModes } from '../../utils/react-utils';
43
8
 
44
9
  /**
45
10
  * TextInput component that mirrors the Figma "textInput" component.
@@ -73,6 +38,7 @@ function cloneChildrenWithModes(children, modes) {
73
38
  * Helper function to convert a color to a more transparent version for placeholder text.
74
39
  * Takes a color string (hex, rgb, rgba) and returns it with reduced opacity.
75
40
  */
41
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
76
42
  function makePlaceholderColor(color, opacity = 0.5) {
77
43
  if (!color || typeof color !== 'string') {
78
44
  return color || '';
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+
3
+ import React from 'react';
4
+ import { View, Text, Pressable } from 'react-native';
5
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
+ import MoneyValue from '../MoneyValue/MoneyValue';
7
+ import TransactionStatus from '../TransactionStatus/TransactionStatus';
8
+ import NavArrow from '../NavArrow/NavArrow';
9
+ import { cloneChildrenWithModes } from '../../utils/react-utils';
10
+ import { usePressableWebSupport } from '../../utils/web-platform-utils';
11
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
12
+ /**
13
+ * TransactionBubble — Figma node 1517:1155.
14
+ *
15
+ * Layout (single horizontal row inside a rounded bordered pill):
16
+ *
17
+ * ┌──────────────────────────────────────────────┐
18
+ * │ Description Status · Date │
19
+ * │ ₹56 › │
20
+ * └──────────────────────────────────────────────┘
21
+ *
22
+ * Left column: description text + MoneyValue, stacked vertically with `transactionBubble/gap`.
23
+ * Right column: TransactionStatus + NavArrow, stacked vertically with `transactionBubble/statusWrap/gap`,
24
+ * right-aligned.
25
+ */
26
+ function TransactionBubble({
27
+ description = 'Payment to Uber India',
28
+ value = '56',
29
+ currency = '₹',
30
+ status = 'Expired',
31
+ date = '20 Mar 2025',
32
+ statusSlot,
33
+ children,
34
+ modes = {},
35
+ onPress,
36
+ style,
37
+ accessibilityLabel,
38
+ accessibilityHint,
39
+ webAccessibilityProps,
40
+ ...rest
41
+ }) {
42
+ const resolvedModes = {
43
+ ...modes,
44
+ 'Context3': 'Transaction Bubble'
45
+ };
46
+ const padding = Number(getVariableByName('transactionBubble/padding', resolvedModes)) || 16;
47
+ const radius = Number(getVariableByName('transactionBubble/radius', resolvedModes)) || 23;
48
+ const borderSize = Number(getVariableByName('transactionBubble/border/size', resolvedModes)) || 1;
49
+ const backgroundColor = getVariableByName('transactionBubble/background', resolvedModes) || '#ffffff';
50
+ const borderColor = getVariableByName('transactionBubble/border/color', resolvedModes) || '#e5e5e5';
51
+ const bubbleGap = Number(getVariableByName('transactionBubble/gap', resolvedModes)) || 8;
52
+ const wrapGap = Number(getVariableByName('transactionBubble/wrap/gap', resolvedModes)) || 8;
53
+ const descriptionColor = getVariableByName('transactionBubble/description/color', resolvedModes) || '#24262b';
54
+ const descriptionFontSize = Number(getVariableByName('transactionBubble/description/fontSize', resolvedModes)) || 14;
55
+ const descriptionLineHeight = Number(getVariableByName('transactionBubble/description/lineHeight', resolvedModes)) || 17;
56
+ const descriptionFontFamily = getVariableByName('transactionBubble/description/fontFamily', resolvedModes) || 'JioType Var';
57
+ const statusWrapGap = Number(getVariableByName('transactionBubble/statusWrap/gap', resolvedModes)) || 4;
58
+ const containerStyle = {
59
+ padding,
60
+ borderRadius: radius,
61
+ borderWidth: borderSize,
62
+ borderColor,
63
+ backgroundColor
64
+ };
65
+ const descriptionStyle = {
66
+ color: descriptionColor,
67
+ fontSize: descriptionFontSize,
68
+ lineHeight: descriptionLineHeight,
69
+ fontFamily: descriptionFontFamily
70
+ };
71
+ const processedChildren = children ? cloneChildrenWithModes(React.Children.toArray(children), resolvedModes) : null;
72
+ const processedStatusSlot = statusSlot ? cloneChildrenWithModes(React.Children.toArray(statusSlot), resolvedModes) : null;
73
+ const defaultAccessibilityLabel = accessibilityLabel || `${description} • ${status}`;
74
+ const webProps = usePressableWebSupport({
75
+ restProps: rest,
76
+ onPress,
77
+ disabled: false,
78
+ accessibilityLabel: defaultAccessibilityLabel,
79
+ webAccessibilityProps
80
+ });
81
+ const rightColumn = processedStatusSlot || /*#__PURE__*/_jsxs(View, {
82
+ style: {
83
+ alignItems: 'flex-end',
84
+ justifyContent: 'flex-end',
85
+ gap: statusWrapGap
86
+ },
87
+ children: [/*#__PURE__*/_jsx(TransactionStatus, {
88
+ status: status,
89
+ date: date,
90
+ modes: resolvedModes
91
+ }), /*#__PURE__*/_jsx(NavArrow, {
92
+ direction: "Forward",
93
+ modes: resolvedModes
94
+ })]
95
+ });
96
+ const mainContent = /*#__PURE__*/_jsxs(View, {
97
+ style: {
98
+ flexDirection: 'row',
99
+ gap: wrapGap
100
+ },
101
+ children: [/*#__PURE__*/_jsxs(View, {
102
+ style: {
103
+ flex: 1,
104
+ gap: bubbleGap
105
+ },
106
+ children: [/*#__PURE__*/_jsx(Text, {
107
+ style: descriptionStyle,
108
+ numberOfLines: 1,
109
+ children: description
110
+ }), /*#__PURE__*/_jsx(MoneyValue, {
111
+ value: value,
112
+ currency: currency,
113
+ modes: resolvedModes
114
+ })]
115
+ }), rightColumn]
116
+ });
117
+ if (onPress) {
118
+ return /*#__PURE__*/_jsxs(Pressable, {
119
+ onPress: onPress,
120
+ style: ({
121
+ pressed
122
+ }) => [containerStyle, style, pressed && {
123
+ opacity: 0.85
124
+ }],
125
+ accessibilityRole: "button",
126
+ accessibilityLabel: defaultAccessibilityLabel,
127
+ accessibilityHint: accessibilityHint,
128
+ ...webProps,
129
+ ...rest,
130
+ children: [mainContent, processedChildren]
131
+ });
132
+ }
133
+ return /*#__PURE__*/_jsxs(View, {
134
+ style: [containerStyle, style],
135
+ accessibilityLabel: defaultAccessibilityLabel,
136
+ accessibilityHint: accessibilityHint,
137
+ children: [mainContent, processedChildren]
138
+ });
139
+ }
140
+ export default TransactionBubble;
@@ -48,6 +48,7 @@ export { default as ThreadHero } from './ThreadHero/ThreadHero';
48
48
  export { Tooltip } from './Tooltip/Tooltip';
49
49
  export { default as TransactionDetails } from './TransactionDetails/TransactionDetails';
50
50
  export { default as TransactionStatus } from './TransactionStatus/TransactionStatus';
51
+ export { default as TransactionBubble } from './TransactionBubble/TransactionBubble';
51
52
  export { default as UpiHandle } from './UpiHandle/UpiHandle';
52
53
  export { default as VStack } from './VStack/VStack';
53
54
  export { default as ChipGroup } from './ChipGroup/ChipGroup';