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
@@ -7,17 +7,25 @@ import React from 'react';
7
7
  * This ensures that all child components in slots receive the modes prop from the parent.
8
8
  */
9
9
  export function cloneChildrenWithModes(children, modes, forcedModes) {
10
- return React.Children.map(children, child => {
10
+ const result = [];
11
+ React.Children.forEach(children, child => {
11
12
  if (! /*#__PURE__*/React.isValidElement(child)) {
12
- return child;
13
+ if (child !== null && child !== undefined) {
14
+ result.push(child);
15
+ }
16
+ return;
13
17
  }
14
18
 
15
- // Get existing children
19
+ // Unwrap Fragments: Fragments can't accept arbitrary props like `modes`,
20
+ // so recurse into their children and process each one individually.
21
+ if (child.type === React.Fragment) {
22
+ const fragment = child;
23
+ result.push(...cloneChildrenWithModes(fragment.props.children, modes, forcedModes));
24
+ return;
25
+ }
16
26
  const childChildren = child.props?.children;
17
27
  const hasChildren = childChildren !== undefined && childChildren !== null;
18
28
 
19
- // Clone the child with modes prop if it doesn't already have one
20
- // or merge with existing modes if it does
21
29
  // Merge order: parent modes first, then child's explicit modes override them,
22
30
  // then forcedModes (if provided) are applied last and can never be overridden
23
31
  const existingModes = child.props?.modes;
@@ -29,16 +37,13 @@ export function cloneChildrenWithModes(children, modes, forcedModes) {
29
37
  ...modes,
30
38
  ...existingModes
31
39
  } : modes;
32
-
33
- // Recursively process children if they exist
34
- const processedChildren = hasChildren ? cloneChildrenWithModes(React.Children.toArray(childChildren), modes, forcedModes) : undefined;
35
-
36
- // Clone element with modes and processed children
37
- return /*#__PURE__*/React.cloneElement(child, {
40
+ const processedChildren = hasChildren ? cloneChildrenWithModes(childChildren, modes, forcedModes) : undefined;
41
+ result.push(/*#__PURE__*/React.cloneElement(child, {
38
42
  ...child.props,
39
43
  modes: mergedModes
40
- }, processedChildren);
41
- })?.filter(child => child !== null && child !== undefined) ?? [];
44
+ }, processedChildren));
45
+ });
46
+ return result;
42
47
  }
43
48
 
44
49
  /**
@@ -3,32 +3,37 @@ import { View, type StyleProp, type ViewStyle } from 'react-native';
3
3
  type ListGroupProps = {
4
4
  label?: string;
5
5
  listGroupSlot?: React.ReactNode;
6
+ children?: React.ReactNode;
6
7
  modes?: Record<string, any>;
7
8
  style?: StyleProp<ViewStyle>;
8
9
  accessibilityLabel?: string;
9
10
  accessibilityHint?: string;
10
- } & React.ComponentProps<typeof View>;
11
+ } & Omit<React.ComponentProps<typeof View>, 'children'>;
11
12
  /**
12
13
  * ListGroup component that mirrors the Figma "List Group" component.
13
14
  *
14
15
  * This component supports:
15
16
  * - **label** text at the top
16
- * - **listGroupSlot** for custom content (typically ListItem components)
17
+ * - **listGroupSlot** or **children** for content (typically ListItem components)
17
18
  * - **design-token driven styling** via `getVariableByName` and `modes`
18
19
  *
19
- * Wherever the Figma layer name contains "Slot", this component exposes a
20
- * dedicated React "slot" prop:
21
- * - Slot "List group" → `listGroupSlot`
20
+ * Content can be provided in two interchangeable ways:
21
+ * - Via the `listGroupSlot` prop (Figma Slot "List group")
22
+ * - Via `children`
23
+ *
24
+ * Both produce identical results. If both are provided, they are merged
25
+ * (listGroupSlot items render first, then children).
22
26
  *
23
27
  * @component
24
28
  * @param {Object} props
25
29
  * @param {string} [props.label=''] - Label text displayed at the top of the list group
26
- * @param {React.ReactNode} [props.listGroupSlot] - Optional custom slot for list items (Figma Slot "List group")
30
+ * @param {React.ReactNode} [props.listGroupSlot] - Slot for list items (Figma Slot "List group")
31
+ * @param {React.ReactNode} [props.children] - Children for list items (equivalent to listGroupSlot)
27
32
  * @param {Object} [props.modes={}] - Modes object passed to `getVariableByName` for all design tokens
28
33
  * @param {Object} [props.style] - Optional container style overrides
29
34
  * @param {string} [props.accessibilityLabel] - Accessibility label for the list group. If not provided, uses label
30
35
  * @param {string} [props.accessibilityHint] - Additional accessibility hint for screen readers
31
36
  */
32
- declare function ListGroup({ label, listGroupSlot, modes, style, accessibilityLabel, accessibilityHint, ...rest }: ListGroupProps): import("react/jsx-runtime").JSX.Element;
37
+ declare function ListGroup({ label, listGroupSlot, children, modes, style, accessibilityLabel, accessibilityHint, ...rest }: ListGroupProps): import("react/jsx-runtime").JSX.Element;
33
38
  export default ListGroup;
34
39
  //# sourceMappingURL=ListGroup.d.ts.map
@@ -12,23 +12,18 @@ type NavArrowProps = {
12
12
  accessibilityLabel?: string;
13
13
  } & Omit<React.ComponentProps<typeof View>, 'style' | 'accessibilityLabel'>;
14
14
  /**
15
- * NavArrow component that displays a small chevron arrow for navigation.
15
+ * NavArrow component that displays a chevron arrow for navigation.
16
16
  *
17
- * This component uses design tokens for all visual properties:
17
+ * Renders a stroked SVG chevron whose dimensions and thickness are
18
+ * fully driven by design tokens:
18
19
  * - navArrow/icon/color - chevron stroke color
19
- * - navArrow/icon/width - icon width
20
- * - navArrow/icon/height - icon height
21
- * - navArrow/icon/strokeWeight - stroke width
20
+ * - navArrow/icon/width - chevron arm width (horizontal spread)
21
+ * - navArrow/icon/height - chevron arm height (vertical spread)
22
+ * - navArrow/icon/strokeWeight - stroke thickness
22
23
  * - navArrow/width - container width
23
24
  * - navArrow/height - container height
24
25
  * - navArrow/radius - border radius
25
26
  * - navArrow/background - background color
26
- *
27
- * @component
28
- * @param {Object} props
29
- * @param {'Back'|'Forward'|'Down'} [props.direction='Back'] - Arrow direction
30
- * @param {Object} [props.modes={}] - Modes for design token resolution
31
- * @param {Object} [props.style] - Additional container styles
32
27
  */
33
28
  export default function NavArrow({ direction, modes, style, accessibilityLabel, ...rest }: NavArrowProps): import("react/jsx-runtime").JSX.Element;
34
29
  export {};
@@ -0,0 +1,36 @@
1
+ import React from 'react';
2
+ import { type ViewStyle, type PressableProps } from 'react-native';
3
+ import { type WebAccessibilityProps } from '../../utils/web-platform-utils';
4
+ export type TransactionBubbleProps = {
5
+ description?: string;
6
+ value?: string | number;
7
+ currency?: string;
8
+ status?: string;
9
+ date?: string;
10
+ /** Slot for the status area. When provided, replaces the default TransactionStatus + NavArrow. */
11
+ statusSlot?: React.ReactNode;
12
+ children?: React.ReactNode;
13
+ modes?: Record<string, any>;
14
+ onPress?: () => void;
15
+ style?: ViewStyle;
16
+ accessibilityLabel?: string;
17
+ accessibilityHint?: string;
18
+ webAccessibilityProps?: WebAccessibilityProps;
19
+ } & Omit<PressableProps, 'style' | 'children' | 'onPress'>;
20
+ /**
21
+ * TransactionBubble — Figma node 1517:1155.
22
+ *
23
+ * Layout (single horizontal row inside a rounded bordered pill):
24
+ *
25
+ * ┌──────────────────────────────────────────────┐
26
+ * │ Description Status · Date │
27
+ * │ ₹56 › │
28
+ * └──────────────────────────────────────────────┘
29
+ *
30
+ * Left column: description text + MoneyValue, stacked vertically with `transactionBubble/gap`.
31
+ * Right column: TransactionStatus + NavArrow, stacked vertically with `transactionBubble/statusWrap/gap`,
32
+ * right-aligned.
33
+ */
34
+ declare function TransactionBubble({ description, value, currency, status, date, statusSlot, children, modes, onPress, style, accessibilityLabel, accessibilityHint, webAccessibilityProps, ...rest }: TransactionBubbleProps): import("react/jsx-runtime").JSX.Element;
35
+ export default TransactionBubble;
36
+ //# sourceMappingURL=TransactionBubble.d.ts.map
@@ -47,6 +47,7 @@ export { default as ThreadHero, type ThreadHeroProps } from './ThreadHero/Thread
47
47
  export { Tooltip } from './Tooltip/Tooltip';
48
48
  export { default as TransactionDetails } from './TransactionDetails/TransactionDetails';
49
49
  export { default as TransactionStatus } from './TransactionStatus/TransactionStatus';
50
+ export { default as TransactionBubble, type TransactionBubbleProps } from './TransactionBubble/TransactionBubble';
50
51
  export { default as UpiHandle } from './UpiHandle/UpiHandle';
51
52
  export { default as VStack, type VStackProps } from './VStack/VStack';
52
53
  export { default as ChipGroup, type ChipGroupProps } from './ChipGroup/ChipGroup';
@@ -40,5 +40,20 @@ export declare const JFSThemeProvider: React.FC<JFSThemeProviderProps>;
40
40
  * }
41
41
  */
42
42
  export declare const useTokens: () => TokenContextType;
43
+ /**
44
+ * Returns the JFS font map. The TTF is encapsulated within the package at
45
+ * src/assets/fonts/JioType Var.ttf (included via package.json "files").
46
+ * Call this inside load functions to avoid top-level require errors if font missing.
47
+ */
48
+ export declare const getJFSFonts: () => {
49
+ readonly 'JioType Var': any;
50
+ };
51
+ /**
52
+ * Hook for loading JFS fonts using expo-font. This improves Android font support by explicitly registering
53
+ * the custom 'JioType Var' font (encapsulated in the package) via Font.loadAsync before components render.
54
+ * Without it, Android defaults to Roboto. Call at app root (e.g. before JFSThemeProvider). Returns loaded state.
55
+ * See getJFSFonts() for direct use with Font.loadAsync. Handles missing font gracefully for web/Storybook.
56
+ */
57
+ export declare function useJFSFonts(): boolean;
43
58
  export {};
44
59
  //# sourceMappingURL=JFSThemeProvider.d.ts.map
@@ -4,7 +4,7 @@
4
4
  * Auto-generated from SVG files in src/icons/
5
5
  * DO NOT EDIT MANUALLY - Run "npm run icons:generate" to regenerate
6
6
  *
7
- * Generated: 2026-04-13T20:33:05.821Z
7
+ * Generated: 2026-04-14T12:07:25.514Z
8
8
  */
9
9
  export declare const iconRegistry: Record<string, {
10
10
  path: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jfs-components",
3
- "version": "0.0.54",
3
+ "version": "0.0.56",
4
4
  "description": "React Native Jio Finance Components Library",
5
5
  "author": "sunshuaiqi@gmail.com",
6
6
  "license": "MIT",
@@ -14,56 +14,13 @@ import {
14
14
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
15
15
  import Icon from '../../icons/Icon'
16
16
  import { usePressableWebSupport, type WebAccessibilityProps } from '../../utils/web-platform-utils'
17
+ import { cloneChildrenWithModes } from '../../utils/react-utils'
17
18
 
18
19
  // Enable LayoutAnimation on Android
19
20
  if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
20
21
  UIManager.setLayoutAnimationEnabledExperimental(true)
21
22
  }
22
23
 
23
- /**
24
- * Helper function to recursively clone children and pass modes prop to components that accept it.
25
- * This ensures that all child components in slots receive the modes prop from the parent.
26
- */
27
- function cloneChildrenWithModes(
28
- children: React.ReactNode,
29
- modes: Record<string, any>
30
- ): React.ReactNode[] {
31
- const result = React.Children.map(children, (child) => {
32
- if (!React.isValidElement(child)) {
33
- return child
34
- }
35
-
36
- // Get existing children
37
- const childChildren = (child.props as any)?.children
38
- const hasChildren = childChildren !== undefined && childChildren !== null
39
-
40
- // Merge modes: parent modes first, then child's explicit modes override them
41
- const existingModes = (child.props as any)?.modes
42
- const mergedModes = existingModes
43
- ? { ...modes, ...existingModes }
44
- : modes
45
-
46
- // Recursively process children if they exist
47
- const processedChildren: React.ReactNode | undefined = hasChildren
48
- ? cloneChildrenWithModes(
49
- React.Children.toArray(childChildren),
50
- modes
51
- )
52
- : undefined
53
-
54
- // Clone element with modes and processed children
55
- return React.cloneElement(
56
- child,
57
- {
58
- ...(child.props as any),
59
- modes: mergedModes,
60
- },
61
- processedChildren
62
- )
63
- })
64
- return result || []
65
- }
66
-
67
24
  export type AccordionProps = {
68
25
  /** The accordion header title */
69
26
  title?: string;
@@ -3,51 +3,8 @@ import { View, type StyleProp, type ViewStyle, Pressable } from 'react-native'
3
3
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
4
4
  import { useTokens } from '../../design-tokens/JFSThemeProvider'
5
5
 
6
- import Avatar from '../Avatar/Avatar'
7
6
  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
- */
14
- function cloneChildrenWithModes(
15
- children: React.ReactNode,
16
- modes: Record<string, any>
17
- ): React.ReactNode[] {
18
- return React.Children.map(children, (child) => {
19
- if (!React.isValidElement(child)) {
20
- return child
21
- }
22
-
23
- // Get existing children
24
- const childChildren = (child.props as any)?.children
25
- const hasChildren = childChildren !== undefined && childChildren !== null
26
-
27
- // Clone the child with modes prop if it doesn't already have one
28
- // or merge with existing modes if it does
29
- const existingModes = (child.props as any)?.modes
30
- const mergedModes = existingModes ? { ...modes, ...existingModes } : modes
31
-
32
- // Recursively process children if they exist
33
- const processedChildren: React.ReactNode | undefined = hasChildren
34
- ? cloneChildrenWithModes(
35
- React.Children.toArray(childChildren),
36
- modes
37
- )
38
- : undefined
39
-
40
- // Clone element with modes and processed children
41
- return React.cloneElement(
42
- child,
43
- {
44
- ...(child.props as any),
45
- modes: mergedModes,
46
- },
47
- processedChildren
48
- )
49
- })?.filter((child) => child !== null && child !== undefined) as React.ReactNode[] ?? []
50
- }
7
+ import { cloneChildrenWithModes } from '../../utils/react-utils'
51
8
 
52
9
  type AppBarType = 'MainPage' | 'SubPage'
53
10
 
@@ -232,17 +232,25 @@ function Drawer({
232
232
  ? (translateY.value - minTranslateY) / totalRange
233
233
  : 0
234
234
 
235
- // Fast fling always follow the fling direction
236
- if (event.velocityY < -500) {
235
+ // How far the drawer sheet itself moved from its position at gesture
236
+ // start. When the scroll view consumed the gesture (content was not
237
+ // at the top), the drawer stays put and displacement ≈ 0 — even
238
+ // though the finger moved fast. We must NOT use velocity to snap in
239
+ // that case; otherwise a fast content-scroll collapses the drawer.
240
+ const drawerDisplacement = Math.abs(translateY.value - context.value.y)
241
+ const drawerMovedEnough = drawerDisplacement > 10
242
+
243
+ if (drawerMovedEnough && event.velocityY < -500) {
237
244
  scrollTo(minTranslateY)
238
245
  isFullyExpanded.value = true
239
246
  runOnJS(updateMode)('expanded')
240
- } else if (event.velocityY > 500) {
247
+ } else if (drawerMovedEnough && event.velocityY > 500) {
241
248
  scrollTo(maxTranslateY)
242
249
  isFullyExpanded.value = false
243
250
  runOnJS(updateMode)('collapsed')
244
251
  } else {
245
- // Slow / medium gesture use asymmetric snap thresholds.
252
+ // Slow / medium gesture, or the drawer barely moved → position
253
+ // based snap with asymmetric thresholds.
246
254
  // Down stroke: must cross 75% to collapse (hard to dismiss).
247
255
  // Up stroke: must reach top 35% to expand (35% from top).
248
256
  const isDraggingDown = event.velocityY >= 0
@@ -2,50 +2,7 @@ import React, { useState } from 'react'
2
2
  import { View, type StyleProp, type ViewStyle } from 'react-native'
3
3
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
4
4
  import TextInput from '../TextInput/TextInput'
5
-
6
- /**
7
- * Helper function to recursively clone children and pass modes prop to components that accept it.
8
- * This ensures that all child components in slots receive the modes prop from the parent.
9
- */
10
- function cloneChildrenWithModes(
11
- children: React.ReactNode,
12
- modes: Record<string, any>
13
- ): React.ReactNode[] {
14
- const result = React.Children.map(children, (child) => {
15
- if (!React.isValidElement(child)) {
16
- return child
17
- }
18
-
19
- // Get existing children
20
- const childChildren = (child.props as any)?.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 as any)?.modes
27
- const mergedModes = existingModes ? { ...modes, ...existingModes } : modes
28
-
29
- // Recursively process children if they exist
30
- const processedChildren: React.ReactNode | undefined = hasChildren
31
- ? cloneChildrenWithModes(
32
- React.Children.toArray(childChildren),
33
- modes
34
- )
35
- : undefined
36
-
37
- // Clone element with modes and processed children
38
- return React.cloneElement(
39
- child,
40
- {
41
- ...(child.props as any),
42
- modes: mergedModes,
43
- },
44
- processedChildren
45
- )
46
- })
47
- return result || []
48
- }
5
+ import { cloneChildrenWithModes } from '../../utils/react-utils'
49
6
 
50
7
  type RenderInputArgs = {
51
8
  placeholder: string;
@@ -1,47 +1,7 @@
1
1
  import React from 'react'
2
2
  import { View, type StyleProp, type ViewStyle } from 'react-native'
3
3
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
4
-
5
- /**
6
- * Helper function to recursively clone children and pass modes prop to components that accept it.
7
- * This ensures that all child components in slots receive the modes prop from the parent.
8
- */
9
- function cloneChildrenWithModes(children: React.ReactNode, modes: Record<string, any>): React.ReactNode {
10
- return React.Children.map(children, (child) => {
11
- if (!React.isValidElement(child)) {
12
- return child
13
- }
14
-
15
- // Get existing children
16
- const childProps = child.props as { children?: React.ReactNode; modes?: Record<string, any>; [key: string]: any }
17
- const childChildren = childProps?.children
18
- const hasChildren = childChildren !== undefined && childChildren !== null
19
-
20
- // Clone the child with modes prop if it doesn't already have one
21
- // or merge with existing modes if it does
22
- // Merge order: parent modes first, then child's explicit modes override them
23
- const existingModes = childProps?.modes
24
- const mergedModes = existingModes ? { ...modes, ...existingModes } : modes
25
-
26
- // Recursively process children if they exist
27
- const processedChildren = hasChildren
28
- ? cloneChildrenWithModes(
29
- React.Children.toArray(childChildren),
30
- modes
31
- )
32
- : undefined
33
-
34
- // Clone element with modes and processed children
35
- return React.cloneElement(
36
- child,
37
- {
38
- ...childProps,
39
- modes: mergedModes,
40
- } as any,
41
- processedChildren
42
- )
43
- })
44
- }
4
+ import { cloneChildrenWithModes } from '../../utils/react-utils'
45
5
 
46
6
  type LazyListProps = {
47
7
  listGroupsSlot?: React.ReactNode;
@@ -11,28 +11,33 @@ import { cloneChildrenWithModes, flattenChildren } from '../../utils/react-utils
11
11
  type ListGroupProps = {
12
12
  label?: string;
13
13
  listGroupSlot?: React.ReactNode;
14
+ children?: React.ReactNode;
14
15
  modes?: Record<string, any>;
15
16
  style?: StyleProp<ViewStyle>;
16
17
  accessibilityLabel?: string;
17
18
  accessibilityHint?: string;
18
- } & React.ComponentProps<typeof View>;
19
+ } & Omit<React.ComponentProps<typeof View>, 'children'>;
19
20
 
20
21
  /**
21
22
  * ListGroup component that mirrors the Figma "List Group" component.
22
23
  *
23
24
  * This component supports:
24
25
  * - **label** text at the top
25
- * - **listGroupSlot** for custom content (typically ListItem components)
26
+ * - **listGroupSlot** or **children** for content (typically ListItem components)
26
27
  * - **design-token driven styling** via `getVariableByName` and `modes`
27
28
  *
28
- * Wherever the Figma layer name contains "Slot", this component exposes a
29
- * dedicated React "slot" prop:
30
- * - Slot "List group" → `listGroupSlot`
29
+ * Content can be provided in two interchangeable ways:
30
+ * - Via the `listGroupSlot` prop (Figma Slot "List group")
31
+ * - Via `children`
32
+ *
33
+ * Both produce identical results. If both are provided, they are merged
34
+ * (listGroupSlot items render first, then children).
31
35
  *
32
36
  * @component
33
37
  * @param {Object} props
34
38
  * @param {string} [props.label=''] - Label text displayed at the top of the list group
35
- * @param {React.ReactNode} [props.listGroupSlot] - Optional custom slot for list items (Figma Slot "List group")
39
+ * @param {React.ReactNode} [props.listGroupSlot] - Slot for list items (Figma Slot "List group")
40
+ * @param {React.ReactNode} [props.children] - Children for list items (equivalent to listGroupSlot)
36
41
  * @param {Object} [props.modes={}] - Modes object passed to `getVariableByName` for all design tokens
37
42
  * @param {Object} [props.style] - Optional container style overrides
38
43
  * @param {string} [props.accessibilityLabel] - Accessibility label for the list group. If not provided, uses label
@@ -41,6 +46,7 @@ type ListGroupProps = {
41
46
  function ListGroup({
42
47
  label = '',
43
48
  listGroupSlot,
49
+ children,
44
50
  modes = {},
45
51
  style,
46
52
  accessibilityLabel,
@@ -79,11 +85,15 @@ function ListGroup({
79
85
  fontWeight: labelFontWeight,
80
86
  }
81
87
 
82
- // Clone listGroupSlot children and pass modes to all children that accept it
83
- // We flatten children first so that if a Fragment is passed, the items inside
84
- // become direct children of the container, allowing `gap` to apply correctly between them.
85
- const processedSlot = listGroupSlot
86
- ? cloneChildrenWithModes(flattenChildren(listGroupSlot), modes)
88
+ // Merge listGroupSlot and children into a single list, then flatten and
89
+ // propagate modes. Both props are interchangeable; when both are provided
90
+ // the slot items render first, followed by children.
91
+ const rawItems = [
92
+ ...(listGroupSlot ? flattenChildren(listGroupSlot) : []),
93
+ ...(children ? flattenChildren(children) : []),
94
+ ]
95
+ const processedSlot = rawItems.length > 0
96
+ ? cloneChildrenWithModes(rawItems, modes)
87
97
  : null
88
98
 
89
99
  // Use provided accessibilityLabel or fall back to label
@@ -127,7 +127,7 @@ function MoneyValue({
127
127
  ? fontWeightValue.toString()
128
128
  : fontWeightValue
129
129
  const fontFamily = getVariableByName('moneyValue/fontFamily', modes) || 'System'
130
- const gap = getVariableByName('moneyValue/gap', modes) || 4
130
+ const gap = getVariableByName('moneyValue/gap', modes) ?? 4
131
131
 
132
132
  // Resolve currency to a symbol, supporting both symbols and ISO codes
133
133
  const resolvedCurrency = useMemo(() => {