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.
- package/lib/commonjs/components/Accordion/Accordion.js +2 -34
- package/lib/commonjs/components/AppBar/AppBar.js +4 -36
- package/lib/commonjs/components/FilterBar/FilterBar.js +2 -34
- package/lib/commonjs/components/LazyList/LazyList.js +2 -34
- 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/FilterBar/FilterBar.js +1 -35
- package/lib/module/components/LazyList/LazyList.js +1 -35
- 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/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/FilterBar/FilterBar.tsx +1 -44
- package/src/components/LazyList/LazyList.tsx +1 -41
- 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
|
@@ -2,50 +2,7 @@ import React, { useState } from 'react'
|
|
|
2
2
|
import { Pressable, View, TextInput as RNTextInput, type StyleProp, type ViewStyle, type TextInputProps as RNTextInputProps, type TextStyle } from 'react-native'
|
|
3
3
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
4
4
|
import Icon from '../../icons/Icon'
|
|
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
|
/**
|
|
51
8
|
* TextInput component that mirrors the Figma "textInput" component.
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { View, Text, Pressable, type ViewStyle, type TextStyle, type PressableProps } from 'react-native'
|
|
3
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
4
|
+
import MoneyValue from '../MoneyValue/MoneyValue'
|
|
5
|
+
import TransactionStatus from '../TransactionStatus/TransactionStatus'
|
|
6
|
+
import NavArrow from '../NavArrow/NavArrow'
|
|
7
|
+
import { cloneChildrenWithModes } from '../../utils/react-utils'
|
|
8
|
+
import { usePressableWebSupport, type WebAccessibilityProps } from '../../utils/web-platform-utils'
|
|
9
|
+
|
|
10
|
+
export type TransactionBubbleProps = {
|
|
11
|
+
description?: string
|
|
12
|
+
value?: string | number
|
|
13
|
+
currency?: string
|
|
14
|
+
status?: string
|
|
15
|
+
date?: string
|
|
16
|
+
/** Slot for the status area. When provided, replaces the default TransactionStatus + NavArrow. */
|
|
17
|
+
statusSlot?: React.ReactNode
|
|
18
|
+
children?: React.ReactNode
|
|
19
|
+
modes?: Record<string, any>
|
|
20
|
+
onPress?: () => void
|
|
21
|
+
style?: ViewStyle
|
|
22
|
+
accessibilityLabel?: string
|
|
23
|
+
accessibilityHint?: string
|
|
24
|
+
webAccessibilityProps?: WebAccessibilityProps
|
|
25
|
+
} & Omit<PressableProps, 'style' | 'children' | 'onPress'>
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* TransactionBubble — Figma node 1517:1155.
|
|
29
|
+
*
|
|
30
|
+
* Layout (single horizontal row inside a rounded bordered pill):
|
|
31
|
+
*
|
|
32
|
+
* ┌──────────────────────────────────────────────┐
|
|
33
|
+
* │ Description Status · Date │
|
|
34
|
+
* │ ₹56 › │
|
|
35
|
+
* └──────────────────────────────────────────────┘
|
|
36
|
+
*
|
|
37
|
+
* Left column: description text + MoneyValue, stacked vertically with `transactionBubble/gap`.
|
|
38
|
+
* Right column: TransactionStatus + NavArrow, stacked vertically with `transactionBubble/statusWrap/gap`,
|
|
39
|
+
* right-aligned.
|
|
40
|
+
*/
|
|
41
|
+
function TransactionBubble({
|
|
42
|
+
description = 'Payment to Uber India',
|
|
43
|
+
value = '56',
|
|
44
|
+
currency = '₹',
|
|
45
|
+
status = 'Expired',
|
|
46
|
+
date = '20 Mar 2025',
|
|
47
|
+
statusSlot,
|
|
48
|
+
children,
|
|
49
|
+
modes = {},
|
|
50
|
+
onPress,
|
|
51
|
+
style,
|
|
52
|
+
accessibilityLabel,
|
|
53
|
+
accessibilityHint,
|
|
54
|
+
webAccessibilityProps,
|
|
55
|
+
...rest
|
|
56
|
+
}: TransactionBubbleProps) {
|
|
57
|
+
const resolvedModes = {
|
|
58
|
+
...modes,
|
|
59
|
+
'Context3': 'Transaction Bubble',
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const padding = Number(getVariableByName('transactionBubble/padding', resolvedModes)) || 16
|
|
63
|
+
const radius = Number(getVariableByName('transactionBubble/radius', resolvedModes)) || 23
|
|
64
|
+
const borderSize = Number(getVariableByName('transactionBubble/border/size', resolvedModes)) || 1
|
|
65
|
+
const backgroundColor = getVariableByName('transactionBubble/background', resolvedModes) || '#ffffff'
|
|
66
|
+
const borderColor = getVariableByName('transactionBubble/border/color', resolvedModes) || '#e5e5e5'
|
|
67
|
+
const bubbleGap = Number(getVariableByName('transactionBubble/gap', resolvedModes)) || 8
|
|
68
|
+
const wrapGap = Number(getVariableByName('transactionBubble/wrap/gap', resolvedModes)) || 8
|
|
69
|
+
|
|
70
|
+
const descriptionColor = getVariableByName('transactionBubble/description/color', resolvedModes) || '#24262b'
|
|
71
|
+
const descriptionFontSize = Number(getVariableByName('transactionBubble/description/fontSize', resolvedModes)) || 14
|
|
72
|
+
const descriptionLineHeight = Number(getVariableByName('transactionBubble/description/lineHeight', resolvedModes)) || 17
|
|
73
|
+
const descriptionFontFamily = getVariableByName('transactionBubble/description/fontFamily', resolvedModes) || 'JioType Var'
|
|
74
|
+
|
|
75
|
+
const statusWrapGap = Number(getVariableByName('transactionBubble/statusWrap/gap', resolvedModes)) || 4
|
|
76
|
+
|
|
77
|
+
const containerStyle: ViewStyle = {
|
|
78
|
+
padding,
|
|
79
|
+
borderRadius: radius,
|
|
80
|
+
borderWidth: borderSize,
|
|
81
|
+
borderColor,
|
|
82
|
+
backgroundColor,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const descriptionStyle: TextStyle = {
|
|
86
|
+
color: descriptionColor,
|
|
87
|
+
fontSize: descriptionFontSize,
|
|
88
|
+
lineHeight: descriptionLineHeight,
|
|
89
|
+
fontFamily: descriptionFontFamily,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const processedChildren = children
|
|
93
|
+
? cloneChildrenWithModes(React.Children.toArray(children), resolvedModes)
|
|
94
|
+
: null
|
|
95
|
+
|
|
96
|
+
const processedStatusSlot = statusSlot
|
|
97
|
+
? cloneChildrenWithModes(React.Children.toArray(statusSlot), resolvedModes)
|
|
98
|
+
: null
|
|
99
|
+
|
|
100
|
+
const defaultAccessibilityLabel = accessibilityLabel || `${description} • ${status}`
|
|
101
|
+
|
|
102
|
+
const webProps = usePressableWebSupport({
|
|
103
|
+
restProps: rest,
|
|
104
|
+
onPress,
|
|
105
|
+
disabled: false,
|
|
106
|
+
accessibilityLabel: defaultAccessibilityLabel,
|
|
107
|
+
webAccessibilityProps,
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
const rightColumn = processedStatusSlot || (
|
|
111
|
+
<View style={{ alignItems: 'flex-end', justifyContent: 'flex-end', gap: statusWrapGap }}>
|
|
112
|
+
<TransactionStatus status={status} date={date} modes={resolvedModes} />
|
|
113
|
+
<NavArrow direction="Forward" modes={resolvedModes} />
|
|
114
|
+
</View>
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
const mainContent = (
|
|
118
|
+
<View style={{ flexDirection: 'row', gap: wrapGap }}>
|
|
119
|
+
<View style={{ flex: 1, gap: bubbleGap }}>
|
|
120
|
+
<Text style={descriptionStyle} numberOfLines={1}>{description}</Text>
|
|
121
|
+
<MoneyValue value={value} currency={currency} modes={resolvedModes} />
|
|
122
|
+
</View>
|
|
123
|
+
{rightColumn}
|
|
124
|
+
</View>
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
if (onPress) {
|
|
128
|
+
return (
|
|
129
|
+
<Pressable
|
|
130
|
+
onPress={onPress}
|
|
131
|
+
style={({ pressed }) => [containerStyle, style, pressed && { opacity: 0.85 }]}
|
|
132
|
+
accessibilityRole="button"
|
|
133
|
+
accessibilityLabel={defaultAccessibilityLabel}
|
|
134
|
+
accessibilityHint={accessibilityHint}
|
|
135
|
+
{...webProps}
|
|
136
|
+
{...rest}
|
|
137
|
+
>
|
|
138
|
+
{mainContent}
|
|
139
|
+
{processedChildren}
|
|
140
|
+
</Pressable>
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<View style={[containerStyle, style]} accessibilityLabel={defaultAccessibilityLabel} accessibilityHint={accessibilityHint}>
|
|
146
|
+
{mainContent}
|
|
147
|
+
{processedChildren}
|
|
148
|
+
</View>
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export default TransactionBubble
|
package/src/components/index.ts
CHANGED
|
@@ -48,6 +48,7 @@ export { Tooltip } from './Tooltip/Tooltip';
|
|
|
48
48
|
|
|
49
49
|
export { default as TransactionDetails } from './TransactionDetails/TransactionDetails';
|
|
50
50
|
export { default as TransactionStatus } from './TransactionStatus/TransactionStatus';
|
|
51
|
+
export { default as TransactionBubble, type TransactionBubbleProps } from './TransactionBubble/TransactionBubble';
|
|
51
52
|
export { default as UpiHandle } from './UpiHandle/UpiHandle';
|
|
52
53
|
export { default as VStack, type VStackProps } from './VStack/VStack';
|
|
53
54
|
export { default as ChipGroup, type ChipGroupProps } from './ChipGroup/ChipGroup';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
|
|
2
|
-
import React, { createContext, useContext, ReactNode, useMemo } from 'react';
|
|
2
|
+
import React, { createContext, useContext, ReactNode, useMemo, useState, useEffect } from 'react';
|
|
3
3
|
import { getVariableByName } from './figma-variables-resolver';
|
|
4
|
+
import * as Font from 'expo-font';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Shape of the TokenContext
|
|
@@ -42,7 +43,6 @@ export const JFSThemeProvider: React.FC<JFSThemeProviderProps> = ({
|
|
|
42
43
|
children
|
|
43
44
|
}) => {
|
|
44
45
|
const value = useMemo(() => {
|
|
45
|
-
// We bind the current modes to getVariableByName so consumers don't have to pass it
|
|
46
46
|
const getVariable = (name: string) => getVariableByName(name, modes);
|
|
47
47
|
|
|
48
48
|
return {
|
|
@@ -77,3 +77,38 @@ export const useTokens = (): TokenContextType => {
|
|
|
77
77
|
}
|
|
78
78
|
return context;
|
|
79
79
|
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Returns the JFS font map. The TTF is encapsulated within the package at
|
|
83
|
+
* src/assets/fonts/JioType Var.ttf (included via package.json "files").
|
|
84
|
+
* Call this inside load functions to avoid top-level require errors if font missing.
|
|
85
|
+
*/
|
|
86
|
+
export const getJFSFonts = () => ({
|
|
87
|
+
'JioType Var': require('../assets/fonts/JioType Var.ttf'),
|
|
88
|
+
} as const);
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Hook for loading JFS fonts using expo-font. This improves Android font support by explicitly registering
|
|
92
|
+
* the custom 'JioType Var' font (encapsulated in the package) via Font.loadAsync before components render.
|
|
93
|
+
* Without it, Android defaults to Roboto. Call at app root (e.g. before JFSThemeProvider). Returns loaded state.
|
|
94
|
+
* See getJFSFonts() for direct use with Font.loadAsync. Handles missing font gracefully for web/Storybook.
|
|
95
|
+
*/
|
|
96
|
+
export function useJFSFonts(): boolean {
|
|
97
|
+
const [fontsLoaded, setFontsLoaded] = useState(false);
|
|
98
|
+
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
async function loadJFSFonts() {
|
|
101
|
+
try {
|
|
102
|
+
await Font.loadAsync(getJFSFonts());
|
|
103
|
+
setFontsLoaded(true);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.warn('Failed to load JFS fonts (this is common in web/Storybook or if font file missing):', error);
|
|
106
|
+
setFontsLoaded(true);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
loadJFSFonts();
|
|
111
|
+
}, []);
|
|
112
|
+
|
|
113
|
+
return fontsLoaded;
|
|
114
|
+
}
|
package/src/icons/registry.ts
CHANGED
package/src/utils/react-utils.ts
CHANGED
|
@@ -9,17 +9,27 @@ export function cloneChildrenWithModes(
|
|
|
9
9
|
modes: Record<string, any>,
|
|
10
10
|
forcedModes?: Record<string, any>
|
|
11
11
|
): React.ReactNode[] {
|
|
12
|
-
|
|
12
|
+
const result: React.ReactNode[] = [];
|
|
13
|
+
|
|
14
|
+
React.Children.forEach(children, (child) => {
|
|
13
15
|
if (!React.isValidElement(child)) {
|
|
14
|
-
|
|
16
|
+
if (child !== null && child !== undefined) {
|
|
17
|
+
result.push(child);
|
|
18
|
+
}
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Unwrap Fragments: Fragments can't accept arbitrary props like `modes`,
|
|
23
|
+
// so recurse into their children and process each one individually.
|
|
24
|
+
if (child.type === React.Fragment) {
|
|
25
|
+
const fragment = child as React.ReactElement<{ children?: React.ReactNode }>;
|
|
26
|
+
result.push(...cloneChildrenWithModes(fragment.props.children, modes, forcedModes));
|
|
27
|
+
return;
|
|
15
28
|
}
|
|
16
29
|
|
|
17
|
-
// Get existing children
|
|
18
30
|
const childChildren = (child.props as any)?.children
|
|
19
31
|
const hasChildren = childChildren !== undefined && childChildren !== null
|
|
20
32
|
|
|
21
|
-
// Clone the child with modes prop if it doesn't already have one
|
|
22
|
-
// or merge with existing modes if it does
|
|
23
33
|
// Merge order: parent modes first, then child's explicit modes override them,
|
|
24
34
|
// then forcedModes (if provided) are applied last and can never be overridden
|
|
25
35
|
const existingModes = (child.props as any)?.modes
|
|
@@ -29,25 +39,23 @@ export function cloneChildrenWithModes(
|
|
|
29
39
|
? { ...modes, ...existingModes }
|
|
30
40
|
: modes
|
|
31
41
|
|
|
32
|
-
// Recursively process children if they exist
|
|
33
42
|
const processedChildren: React.ReactNode | undefined = hasChildren
|
|
34
|
-
? cloneChildrenWithModes(
|
|
35
|
-
React.Children.toArray(childChildren),
|
|
36
|
-
modes,
|
|
37
|
-
forcedModes
|
|
38
|
-
)
|
|
43
|
+
? cloneChildrenWithModes(childChildren, modes, forcedModes)
|
|
39
44
|
: undefined
|
|
40
45
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
result.push(
|
|
47
|
+
React.cloneElement(
|
|
48
|
+
child,
|
|
49
|
+
{
|
|
50
|
+
...(child.props as any),
|
|
51
|
+
modes: mergedModes,
|
|
52
|
+
},
|
|
53
|
+
processedChildren
|
|
54
|
+
)
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return result;
|
|
51
59
|
}
|
|
52
60
|
|
|
53
61
|
/**
|