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.
- package/lib/commonjs/components/Accordion/Accordion.js +2 -34
- package/lib/commonjs/components/AppBar/AppBar.js +4 -36
- package/lib/commonjs/components/Drawer/Drawer.js +11 -4
- package/lib/commonjs/components/FilterBar/FilterBar.js +2 -34
- package/lib/commonjs/components/LazyList/LazyList.js +2 -34
- package/lib/commonjs/components/ListGroup/ListGroup.js +15 -9
- package/lib/commonjs/components/MoneyValue/MoneyValue.js +1 -1
- package/lib/commonjs/components/NavArrow/NavArrow.js +45 -44
- package/lib/commonjs/components/Numpad/Numpad.js +6 -5
- package/lib/commonjs/components/Section/Section.js +7 -8
- package/lib/commonjs/components/TextInput/TextInput.js +4 -38
- package/lib/commonjs/components/TransactionBubble/TransactionBubble.js +145 -0
- package/lib/commonjs/components/index.js +7 -0
- package/lib/commonjs/design-tokens/JFSThemeProvider.js +38 -3
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/commonjs/utils/react-utils.js +18 -13
- package/lib/module/components/Accordion/Accordion.js +1 -33
- package/lib/module/components/AppBar/AppBar.js +1 -34
- package/lib/module/components/Drawer/Drawer.js +11 -4
- package/lib/module/components/FilterBar/FilterBar.js +1 -35
- package/lib/module/components/LazyList/LazyList.js +1 -35
- package/lib/module/components/ListGroup/ListGroup.js +15 -9
- package/lib/module/components/MoneyValue/MoneyValue.js +1 -1
- package/lib/module/components/NavArrow/NavArrow.js +44 -44
- package/lib/module/components/Numpad/Numpad.js +5 -5
- package/lib/module/components/Section/Section.js +8 -9
- package/lib/module/components/TextInput/TextInput.js +2 -36
- package/lib/module/components/TransactionBubble/TransactionBubble.js +140 -0
- package/lib/module/components/index.js +1 -0
- package/lib/module/design-tokens/JFSThemeProvider.js +35 -3
- package/lib/module/icons/registry.js +1 -1
- package/lib/module/utils/react-utils.js +18 -13
- package/lib/typescript/src/components/ListGroup/ListGroup.d.ts +12 -7
- package/lib/typescript/src/components/NavArrow/NavArrow.d.ts +6 -11
- package/lib/typescript/src/components/TransactionBubble/TransactionBubble.d.ts +36 -0
- package/lib/typescript/src/components/index.d.ts +1 -0
- package/lib/typescript/src/design-tokens/JFSThemeProvider.d.ts +15 -0
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/Accordion/Accordion.tsx +1 -44
- package/src/components/AppBar/AppBar.tsx +1 -44
- package/src/components/Drawer/Drawer.tsx +12 -4
- package/src/components/FilterBar/FilterBar.tsx +1 -44
- package/src/components/LazyList/LazyList.tsx +1 -41
- package/src/components/ListGroup/ListGroup.tsx +21 -11
- package/src/components/MoneyValue/MoneyValue.tsx +1 -1
- package/src/components/NavArrow/NavArrow.tsx +46 -43
- package/src/components/Numpad/Numpad.tsx +5 -5
- package/src/components/Section/Section.tsx +8 -8
- package/src/components/TextInput/TextInput.tsx +1 -44
- package/src/components/TransactionBubble/TransactionBubble.tsx +152 -0
- package/src/components/index.ts +1 -0
- package/src/design-tokens/JFSThemeProvider.tsx +37 -2
- package/src/icons/registry.ts +1 -1
- 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
|
-
|
|
10
|
+
const result = [];
|
|
11
|
+
React.Children.forEach(children, child => {
|
|
11
12
|
if (! /*#__PURE__*/React.isValidElement(child)) {
|
|
12
|
-
|
|
13
|
+
if (child !== null && child !== undefined) {
|
|
14
|
+
result.push(child);
|
|
15
|
+
}
|
|
16
|
+
return;
|
|
13
17
|
}
|
|
14
18
|
|
|
15
|
-
//
|
|
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
|
-
|
|
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
|
-
})
|
|
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
|
|
17
|
+
* - **listGroupSlot** or **children** for content (typically ListItem components)
|
|
17
18
|
* - **design-token driven styling** via `getVariableByName` and `modes`
|
|
18
19
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* -
|
|
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] -
|
|
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
|
|
15
|
+
* NavArrow component that displays a chevron arrow for navigation.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
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 -
|
|
20
|
-
* - navArrow/icon/height -
|
|
21
|
-
* - navArrow/icon/strokeWeight - stroke
|
|
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-
|
|
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
|
@@ -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
|
-
|
|
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
|
-
//
|
|
236
|
-
|
|
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
|
|
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
|
|
26
|
+
* - **listGroupSlot** or **children** for content (typically ListItem components)
|
|
26
27
|
* - **design-token driven styling** via `getVariableByName` and `modes`
|
|
27
28
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* -
|
|
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] -
|
|
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
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
const
|
|
86
|
-
?
|
|
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)
|
|
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(() => {
|