jfs-components 0.0.54 → 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 (55) 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/Drawer/Drawer.js +11 -4
  4. package/lib/commonjs/components/FilterBar/FilterBar.js +2 -34
  5. package/lib/commonjs/components/LazyList/LazyList.js +2 -34
  6. package/lib/commonjs/components/ListGroup/ListGroup.js +15 -9
  7. package/lib/commonjs/components/MoneyValue/MoneyValue.js +1 -1
  8. package/lib/commonjs/components/NavArrow/NavArrow.js +45 -44
  9. package/lib/commonjs/components/Numpad/Numpad.js +6 -5
  10. package/lib/commonjs/components/Section/Section.js +7 -8
  11. package/lib/commonjs/components/TextInput/TextInput.js +4 -38
  12. package/lib/commonjs/components/TransactionBubble/TransactionBubble.js +145 -0
  13. package/lib/commonjs/components/index.js +7 -0
  14. package/lib/commonjs/design-tokens/JFSThemeProvider.js +38 -3
  15. package/lib/commonjs/icons/registry.js +1 -1
  16. package/lib/commonjs/utils/react-utils.js +18 -13
  17. package/lib/module/components/Accordion/Accordion.js +1 -33
  18. package/lib/module/components/AppBar/AppBar.js +1 -34
  19. package/lib/module/components/Drawer/Drawer.js +11 -4
  20. package/lib/module/components/FilterBar/FilterBar.js +1 -35
  21. package/lib/module/components/LazyList/LazyList.js +1 -35
  22. package/lib/module/components/ListGroup/ListGroup.js +15 -9
  23. package/lib/module/components/MoneyValue/MoneyValue.js +1 -1
  24. package/lib/module/components/NavArrow/NavArrow.js +44 -44
  25. package/lib/module/components/Numpad/Numpad.js +5 -5
  26. package/lib/module/components/Section/Section.js +8 -9
  27. package/lib/module/components/TextInput/TextInput.js +2 -36
  28. package/lib/module/components/TransactionBubble/TransactionBubble.js +140 -0
  29. package/lib/module/components/index.js +1 -0
  30. package/lib/module/design-tokens/JFSThemeProvider.js +35 -3
  31. package/lib/module/icons/registry.js +1 -1
  32. package/lib/module/utils/react-utils.js +18 -13
  33. package/lib/typescript/src/components/ListGroup/ListGroup.d.ts +12 -7
  34. package/lib/typescript/src/components/NavArrow/NavArrow.d.ts +6 -11
  35. package/lib/typescript/src/components/TransactionBubble/TransactionBubble.d.ts +36 -0
  36. package/lib/typescript/src/components/index.d.ts +1 -0
  37. package/lib/typescript/src/design-tokens/JFSThemeProvider.d.ts +15 -0
  38. package/lib/typescript/src/icons/registry.d.ts +1 -1
  39. package/package.json +1 -1
  40. package/src/components/Accordion/Accordion.tsx +1 -44
  41. package/src/components/AppBar/AppBar.tsx +1 -44
  42. package/src/components/Drawer/Drawer.tsx +12 -4
  43. package/src/components/FilterBar/FilterBar.tsx +1 -44
  44. package/src/components/LazyList/LazyList.tsx +1 -41
  45. package/src/components/ListGroup/ListGroup.tsx +21 -11
  46. package/src/components/MoneyValue/MoneyValue.tsx +1 -1
  47. package/src/components/NavArrow/NavArrow.tsx +46 -43
  48. package/src/components/Numpad/Numpad.tsx +5 -5
  49. package/src/components/Section/Section.tsx +8 -8
  50. package/src/components/TextInput/TextInput.tsx +1 -44
  51. package/src/components/TransactionBubble/TransactionBubble.tsx +152 -0
  52. package/src/components/index.ts +1 -0
  53. package/src/design-tokens/JFSThemeProvider.tsx +37 -2
  54. package/src/icons/registry.ts +1 -1
  55. 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,
@@ -181,17 +181,24 @@ function Drawer({
181
181
  const totalRange = maxTranslateY - minTranslateY;
182
182
  const currentRatio = totalRange > 0 ? (translateY.value - minTranslateY) / totalRange : 0;
183
183
 
184
- // Fast fling always follow the fling direction
185
- if (event.velocityY < -500) {
184
+ // How far the drawer sheet itself moved from its position at gesture
185
+ // start. When the scroll view consumed the gesture (content was not
186
+ // at the top), the drawer stays put and displacement ≈ 0 — even
187
+ // though the finger moved fast. We must NOT use velocity to snap in
188
+ // that case; otherwise a fast content-scroll collapses the drawer.
189
+ const drawerDisplacement = Math.abs(translateY.value - context.value.y);
190
+ const drawerMovedEnough = drawerDisplacement > 10;
191
+ if (drawerMovedEnough && event.velocityY < -500) {
186
192
  scrollTo(minTranslateY);
187
193
  isFullyExpanded.value = true;
188
194
  runOnJS(updateMode)('expanded');
189
- } else if (event.velocityY > 500) {
195
+ } else if (drawerMovedEnough && event.velocityY > 500) {
190
196
  scrollTo(maxTranslateY);
191
197
  isFullyExpanded.value = false;
192
198
  runOnJS(updateMode)('collapsed');
193
199
  } else {
194
- // Slow / medium gesture use asymmetric snap thresholds.
200
+ // Slow / medium gesture, or the drawer barely moved → position
201
+ // based snap with asymmetric thresholds.
195
202
  // Down stroke: must cross 75% to collapse (hard to dismiss).
196
203
  // Up stroke: must reach top 35% to expand (35% from top).
197
204
  const isDraggingDown = event.velocityY >= 0;
@@ -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
  *
@@ -15,17 +15,21 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
15
15
  *
16
16
  * This component supports:
17
17
  * - **label** text at the top
18
- * - **listGroupSlot** for custom content (typically ListItem components)
18
+ * - **listGroupSlot** or **children** for content (typically ListItem components)
19
19
  * - **design-token driven styling** via `getVariableByName` and `modes`
20
20
  *
21
- * Wherever the Figma layer name contains "Slot", this component exposes a
22
- * dedicated React "slot" prop:
23
- * - Slot "List group" → `listGroupSlot`
21
+ * Content can be provided in two interchangeable ways:
22
+ * - Via the `listGroupSlot` prop (Figma Slot "List group")
23
+ * - Via `children`
24
+ *
25
+ * Both produce identical results. If both are provided, they are merged
26
+ * (listGroupSlot items render first, then children).
24
27
  *
25
28
  * @component
26
29
  * @param {Object} props
27
30
  * @param {string} [props.label=''] - Label text displayed at the top of the list group
28
- * @param {React.ReactNode} [props.listGroupSlot] - Optional custom slot for list items (Figma Slot "List group")
31
+ * @param {React.ReactNode} [props.listGroupSlot] - Slot for list items (Figma Slot "List group")
32
+ * @param {React.ReactNode} [props.children] - Children for list items (equivalent to listGroupSlot)
29
33
  * @param {Object} [props.modes={}] - Modes object passed to `getVariableByName` for all design tokens
30
34
  * @param {Object} [props.style] - Optional container style overrides
31
35
  * @param {string} [props.accessibilityLabel] - Accessibility label for the list group. If not provided, uses label
@@ -34,6 +38,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
34
38
  function ListGroup({
35
39
  label = '',
36
40
  listGroupSlot,
41
+ children,
37
42
  modes = {},
38
43
  style,
39
44
  accessibilityLabel,
@@ -70,10 +75,11 @@ function ListGroup({
70
75
  fontWeight: labelFontWeight
71
76
  };
72
77
 
73
- // Clone listGroupSlot children and pass modes to all children that accept it
74
- // We flatten children first so that if a Fragment is passed, the items inside
75
- // become direct children of the container, allowing `gap` to apply correctly between them.
76
- const processedSlot = listGroupSlot ? cloneChildrenWithModes(flattenChildren(listGroupSlot), modes) : null;
78
+ // Merge listGroupSlot and children into a single list, then flatten and
79
+ // propagate modes. Both props are interchangeable; when both are provided
80
+ // the slot items render first, followed by children.
81
+ const rawItems = [...(listGroupSlot ? flattenChildren(listGroupSlot) : []), ...(children ? flattenChildren(children) : [])];
82
+ const processedSlot = rawItems.length > 0 ? cloneChildrenWithModes(rawItems, modes) : null;
77
83
 
78
84
  // Use provided accessibilityLabel or fall back to label
79
85
  const defaultAccessibilityLabel = accessibilityLabel || label || "List group";
@@ -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 || '';