jfs-components 0.0.43 → 0.0.45
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/AmountInput/AmountInput.js +82 -0
- package/lib/commonjs/components/AmountInput/index.js +13 -0
- package/lib/commonjs/components/Button/Button.js +45 -28
- package/lib/commonjs/components/CardProviderInfo/CardProviderInfo.js +76 -0
- package/lib/commonjs/components/Checkbox/Checkbox.js +208 -0
- package/lib/commonjs/components/EmptyState/EmptyState.js +2 -1
- package/lib/commonjs/components/MoneyValue/MoneyValue.js +81 -49
- package/lib/commonjs/components/NoteInput/NoteInput.js +120 -0
- package/lib/commonjs/components/NoteInput/index.js +13 -0
- package/lib/commonjs/components/Numpad/Numpad.js +108 -0
- package/lib/commonjs/components/OTP/OTP.js +242 -0
- package/lib/commonjs/components/PortfolioHero/PortfolioHero.js +78 -0
- package/lib/commonjs/components/ProductLabel/ProductLabel.js +50 -0
- package/lib/commonjs/components/ProgressBadge/ProgressBadge.js +130 -0
- package/lib/commonjs/components/ProgressBadge/index.js +25 -0
- package/lib/commonjs/components/StatItem/StatItem.js +61 -0
- package/lib/commonjs/components/StatusHero/StatusHero.js +148 -0
- package/lib/commonjs/components/SwappableAmount/SwappableAmount.js +71 -0
- package/lib/commonjs/components/Tabs/TabItem.js +79 -0
- package/lib/commonjs/components/Tabs/Tabs.js +88 -0
- package/lib/commonjs/components/Text/Text.js +38 -0
- package/lib/commonjs/components/Toggle/Toggle.js +102 -0
- package/lib/commonjs/components/index.js +105 -0
- package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -0
- package/lib/commonjs/design-tokens/figma-variables-resolver.js +1 -1
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/module/components/AmountInput/AmountInput.js +77 -0
- package/lib/module/components/AmountInput/index.js +3 -0
- package/lib/module/components/Button/Button.js +44 -28
- package/lib/module/components/CardProviderInfo/CardProviderInfo.js +71 -0
- package/lib/module/components/Checkbox/Checkbox.js +205 -0
- package/lib/module/components/EmptyState/EmptyState.js +2 -1
- package/lib/module/components/MoneyValue/MoneyValue.js +81 -49
- package/lib/module/components/NoteInput/NoteInput.js +115 -0
- package/lib/module/components/NoteInput/index.js +3 -0
- package/lib/module/components/Numpad/Numpad.js +103 -0
- package/lib/module/components/OTP/OTP.js +236 -0
- package/lib/module/components/PortfolioHero/PortfolioHero.js +73 -0
- package/lib/module/components/ProductLabel/ProductLabel.js +45 -0
- package/lib/module/components/ProgressBadge/ProgressBadge.js +125 -0
- package/lib/module/components/ProgressBadge/index.js +4 -0
- package/lib/module/components/StatItem/StatItem.js +56 -0
- package/lib/module/components/StatusHero/StatusHero.js +142 -0
- package/lib/module/components/SwappableAmount/SwappableAmount.js +66 -0
- package/lib/module/components/Tabs/TabItem.js +74 -0
- package/lib/module/components/Tabs/Tabs.js +78 -0
- package/lib/module/components/Text/Text.js +33 -0
- package/lib/module/components/Toggle/Toggle.js +97 -0
- package/lib/module/components/index.js +16 -1
- package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -0
- package/lib/module/design-tokens/figma-variables-resolver.js +1 -1
- package/lib/module/icons/registry.js +1 -1
- package/lib/typescript/src/components/AmountInput/AmountInput.d.ts +23 -0
- package/lib/typescript/src/components/AmountInput/index.d.ts +3 -0
- package/lib/typescript/src/components/Button/Button.d.ts +6 -1
- package/lib/typescript/src/components/CardProviderInfo/CardProviderInfo.d.ts +24 -0
- package/lib/typescript/src/components/Checkbox/Checkbox.d.ts +30 -0
- package/lib/typescript/src/components/EmptyState/EmptyState.d.ts +6 -1
- package/lib/typescript/src/components/MoneyValue/MoneyValue.d.ts +18 -26
- package/lib/typescript/src/components/NoteInput/NoteInput.d.ts +23 -0
- package/lib/typescript/src/components/NoteInput/index.d.ts +3 -0
- package/lib/typescript/src/components/Numpad/Numpad.d.ts +35 -0
- package/lib/typescript/src/components/OTP/OTP.d.ts +36 -0
- package/lib/typescript/src/components/PortfolioHero/PortfolioHero.d.ts +21 -0
- package/lib/typescript/src/components/ProductLabel/ProductLabel.d.ts +14 -0
- package/lib/typescript/src/components/ProgressBadge/ProgressBadge.d.ts +36 -0
- package/lib/typescript/src/components/ProgressBadge/index.d.ts +3 -0
- package/lib/typescript/src/components/StatItem/StatItem.d.ts +21 -0
- package/lib/typescript/src/components/StatusHero/StatusHero.d.ts +47 -0
- package/lib/typescript/src/components/SwappableAmount/SwappableAmount.d.ts +22 -0
- package/lib/typescript/src/components/Tabs/TabItem.d.ts +29 -0
- package/lib/typescript/src/components/Tabs/Tabs.d.ts +44 -0
- package/lib/typescript/src/components/Text/Text.d.ts +14 -0
- package/lib/typescript/src/components/Toggle/Toggle.d.ts +29 -0
- package/lib/typescript/src/components/index.d.ts +15 -0
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/AmountInput/AmountInput.tsx +81 -0
- package/src/components/AmountInput/index.ts +2 -0
- package/src/components/Button/Button.tsx +40 -20
- package/src/components/CardProviderInfo/CardProviderInfo.tsx +81 -0
- package/src/components/Checkbox/Checkbox.tsx +238 -0
- package/src/components/EmptyState/EmptyState.tsx +7 -1
- package/src/components/MoneyValue/MoneyValue.tsx +134 -79
- package/src/components/NoteInput/NoteInput.tsx +146 -0
- package/src/components/NoteInput/index.ts +2 -0
- package/src/components/Numpad/Numpad.tsx +162 -0
- package/src/components/OTP/OTP.tsx +275 -0
- package/src/components/PortfolioHero/PortfolioHero.tsx +91 -0
- package/src/components/ProductLabel/ProductLabel.tsx +58 -0
- package/src/components/ProgressBadge/ProgressBadge.tsx +172 -0
- package/src/components/ProgressBadge/index.ts +2 -0
- package/src/components/StatItem/StatItem.tsx +71 -0
- package/src/components/StatusHero/StatusHero.tsx +156 -0
- package/src/components/SwappableAmount/SwappableAmount.tsx +92 -0
- package/src/components/Tabs/TabItem.tsx +96 -0
- package/src/components/Tabs/Tabs.tsx +105 -0
- package/src/components/Text/Text.tsx +48 -0
- package/src/components/Toggle/Toggle.tsx +122 -0
- package/src/components/index.ts +15 -0
- package/src/design-tokens/Coin Variables-variables-full.json +1 -0
- package/src/design-tokens/figma-variables-resolver.ts +1 -1
- package/src/icons/registry.ts +1 -1
- package/lib/commonjs/design-tokens/JFS Variables-variables-full.json +0 -1
- package/lib/module/design-tokens/JFS Variables-variables-full.json +0 -1
- package/src/design-tokens/JFS Variables-variables-full.json +0 -1
|
@@ -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-
|
|
7
|
+
* Generated: 2026-03-16T08:36:20.904Z
|
|
8
8
|
*/
|
|
9
9
|
export declare const iconRegistry: Record<string, {
|
|
10
10
|
path: string;
|
package/package.json
CHANGED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { View, type ViewStyle } from 'react-native'
|
|
3
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
4
|
+
import { useTokens } from '../../design-tokens/JFSThemeProvider'
|
|
5
|
+
import MoneyValue from '../MoneyValue/MoneyValue'
|
|
6
|
+
import NoteInput from '../NoteInput/NoteInput'
|
|
7
|
+
|
|
8
|
+
export type AmountInputProps = {
|
|
9
|
+
/**
|
|
10
|
+
* Slot for the MoneyValue component.
|
|
11
|
+
*/
|
|
12
|
+
moneyValueSlot?: React.ReactNode
|
|
13
|
+
/**
|
|
14
|
+
* Slot for the NoteInput component.
|
|
15
|
+
*/
|
|
16
|
+
noteInputSlot?: React.ReactNode
|
|
17
|
+
/** Modes for design token resolution */
|
|
18
|
+
modes?: Record<string, any>
|
|
19
|
+
/** Optional container style */
|
|
20
|
+
style?: ViewStyle
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* AmountInput component that combines MoneyValue and NoteInput from Figma design.
|
|
25
|
+
*
|
|
26
|
+
* @component
|
|
27
|
+
*/
|
|
28
|
+
export default function AmountInput({
|
|
29
|
+
moneyValueSlot,
|
|
30
|
+
noteInputSlot,
|
|
31
|
+
modes: propModes = {},
|
|
32
|
+
style,
|
|
33
|
+
}: AmountInputProps) {
|
|
34
|
+
const { modes: globalModes } = useTokens()
|
|
35
|
+
const modes = { ...globalModes, ...propModes }
|
|
36
|
+
|
|
37
|
+
// Resolve tokens
|
|
38
|
+
const gap = Number(getVariableByName('amountInput/gap', modes)) || 16
|
|
39
|
+
const paddingHorizontal = Number(getVariableByName('amountInput/padding/horizontal', modes)) || 0
|
|
40
|
+
const paddingVertical = Number(getVariableByName('amountInput/padding/vertical', modes)) || 0
|
|
41
|
+
|
|
42
|
+
const containerStyle: ViewStyle = {
|
|
43
|
+
flexDirection: 'column',
|
|
44
|
+
alignItems: 'center',
|
|
45
|
+
gap,
|
|
46
|
+
paddingHorizontal,
|
|
47
|
+
paddingVertical,
|
|
48
|
+
...style,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Handle MoneyValue Slot
|
|
52
|
+
const renderMoneyValueSlot = () => {
|
|
53
|
+
if (React.isValidElement(moneyValueSlot)) {
|
|
54
|
+
return React.cloneElement(moneyValueSlot as React.ReactElement<any>, { modes })
|
|
55
|
+
}
|
|
56
|
+
// Default fallback if no slot prop is provided
|
|
57
|
+
if (!moneyValueSlot) {
|
|
58
|
+
return <MoneyValue modes={modes} />
|
|
59
|
+
}
|
|
60
|
+
return moneyValueSlot
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Handle NoteInput Slot
|
|
64
|
+
const renderNoteInputSlot = () => {
|
|
65
|
+
if (React.isValidElement(noteInputSlot)) {
|
|
66
|
+
return React.cloneElement(noteInputSlot as React.ReactElement<any>, { modes })
|
|
67
|
+
}
|
|
68
|
+
// Default fallback if no slot prop is provided
|
|
69
|
+
if (!noteInputSlot) {
|
|
70
|
+
return <NoteInput modes={modes} />
|
|
71
|
+
}
|
|
72
|
+
return noteInputSlot
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<View style={containerStyle}>
|
|
77
|
+
{renderMoneyValueSlot()}
|
|
78
|
+
{renderNoteInputSlot()}
|
|
79
|
+
</View>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
@@ -2,6 +2,7 @@ import React, { useMemo, useState } from 'react'
|
|
|
2
2
|
import { Pressable, Text, View, type AccessibilityState, type StyleProp, type ViewStyle, type TextStyle } from 'react-native'
|
|
3
3
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
4
4
|
import { usePressableWebSupport, type SafePressableProps, type WebAccessibilityProps } from '../../utils/web-platform-utils'
|
|
5
|
+
import Icon from '../../icons/Icon'
|
|
5
6
|
|
|
6
7
|
export type ButtonProps = SafePressableProps & {
|
|
7
8
|
label?: string;
|
|
@@ -25,6 +26,11 @@ export type ButtonProps = SafePressableProps & {
|
|
|
25
26
|
renderContent?: (styles: StyleProp<TextStyle>) => React.ReactNode;
|
|
26
27
|
leading?: React.ReactNode;
|
|
27
28
|
trailing?: React.ReactNode;
|
|
29
|
+
/**
|
|
30
|
+
* Name of the icon to display on the right side of the button.
|
|
31
|
+
* Takes precedence over `trailing` if both are provided.
|
|
32
|
+
*/
|
|
33
|
+
icon?: string;
|
|
28
34
|
modes?: Record<string, any>;
|
|
29
35
|
onPress?: () => void;
|
|
30
36
|
disabled?: boolean;
|
|
@@ -64,6 +70,7 @@ function Button({
|
|
|
64
70
|
renderContent,
|
|
65
71
|
leading,
|
|
66
72
|
trailing,
|
|
73
|
+
icon,
|
|
67
74
|
modes = {},
|
|
68
75
|
onPress,
|
|
69
76
|
disabled = false,
|
|
@@ -90,9 +97,29 @@ function Button({
|
|
|
90
97
|
const lineHeight = getVariableByName('button/lineHeight', modes) || 19
|
|
91
98
|
const fontSize = getVariableByName('button/fontSize', modes) || 16
|
|
92
99
|
const textColor = getVariableByName('button/foreground', modes) || '#0f0d0a'
|
|
100
|
+
const iconColor = getVariableByName('button/icon/color', modes) ?? textColor
|
|
101
|
+
const iconSize = getVariableByName('button/icon/size', modes) ?? 18
|
|
102
|
+
|
|
103
|
+
const [isHovered, setIsHovered] = useState(false)
|
|
104
|
+
const [isPressed, setIsPressed] = useState(false)
|
|
105
|
+
|
|
106
|
+
const hoverModes = { ...modes, "Button / State": "Hover" }
|
|
107
|
+
const hoverBg = getVariableByName('button/background', hoverModes) || backgroundColor
|
|
108
|
+
const hoverBorderColor = getVariableByName('button/border/color', hoverModes) || borderColor
|
|
109
|
+
const hoverTextColor = getVariableByName('button/foreground', hoverModes) || textColor
|
|
110
|
+
const hoverIconColor = getVariableByName('button/icon/color', hoverModes) ?? hoverTextColor
|
|
111
|
+
|
|
112
|
+
const pressedModes = { ...modes, "Button / State": "Pressed" }
|
|
113
|
+
const pressedBg = getVariableByName('button/background', pressedModes) || backgroundColor
|
|
114
|
+
const pressedBorderColor = getVariableByName('button/border/color', pressedModes) || borderColor
|
|
115
|
+
const pressedTextColor = getVariableByName('button/foreground', pressedModes) || textColor
|
|
116
|
+
const pressedIconColor = getVariableByName('button/icon/color', pressedModes) ?? pressedTextColor
|
|
117
|
+
|
|
118
|
+
const activeTextColor = isPressed && !disabled ? pressedTextColor : isHovered && !disabled ? hoverTextColor : textColor
|
|
119
|
+
const activeIconColor = isPressed && !disabled ? pressedIconColor : isHovered && !disabled ? hoverIconColor : iconColor
|
|
93
120
|
|
|
94
121
|
const baseLabelTextStyle: TextStyle = {
|
|
95
|
-
color:
|
|
122
|
+
color: activeTextColor,
|
|
96
123
|
fontFamily,
|
|
97
124
|
fontWeight,
|
|
98
125
|
fontSize,
|
|
@@ -159,12 +186,8 @@ function Button({
|
|
|
159
186
|
webAccessibilityProps,
|
|
160
187
|
})
|
|
161
188
|
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
const [isHovered, setIsHovered] = useState(false)
|
|
165
|
-
const pressedStyle = { transform: [{ scale: 0.995 }], backgroundColor: '#b88940' }
|
|
166
|
-
const focusStyle = { borderWidth: 1, borderColor: '#222' }
|
|
167
|
-
const hoverStyle = { opacity: 0.95 }
|
|
189
|
+
const hoverStyle = { backgroundColor: hoverBg, borderColor: hoverBorderColor }
|
|
190
|
+
const pressedStyle = { backgroundColor: pressedBg, borderColor: pressedBorderColor }
|
|
168
191
|
|
|
169
192
|
if (__DEV__) {
|
|
170
193
|
if (children && labelStyle) {
|
|
@@ -194,18 +217,12 @@ function Button({
|
|
|
194
217
|
disabled={disabled}
|
|
195
218
|
{...(onPress !== undefined ? { onPress } : {})}
|
|
196
219
|
onPressIn={(e: any) => {
|
|
197
|
-
|
|
220
|
+
setIsPressed(true)
|
|
221
|
+
;(rest as any)?.onPressIn?.(e)
|
|
198
222
|
}}
|
|
199
223
|
onPressOut={(e: any) => {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
onFocus={(e: any) => {
|
|
203
|
-
setIsFocused(true)
|
|
204
|
-
; (rest as any)?.onFocus?.(e)
|
|
205
|
-
}}
|
|
206
|
-
onBlur={(e: any) => {
|
|
207
|
-
setIsFocused(false)
|
|
208
|
-
; (rest as any)?.onBlur?.(e)
|
|
224
|
+
setIsPressed(false)
|
|
225
|
+
;(rest as any)?.onPressOut?.(e)
|
|
209
226
|
}}
|
|
210
227
|
onHoverIn={(e: any) => {
|
|
211
228
|
setIsHovered(true)
|
|
@@ -217,9 +234,8 @@ function Button({
|
|
|
217
234
|
}}
|
|
218
235
|
style={({ pressed }) => [
|
|
219
236
|
containerBaseStyle,
|
|
220
|
-
pressed && !disabled ? pressedStyle : null,
|
|
221
237
|
isHovered && !disabled ? hoverStyle : null,
|
|
222
|
-
|
|
238
|
+
(pressed || isPressed) && !disabled ? pressedStyle : null,
|
|
223
239
|
style,
|
|
224
240
|
] as StyleProp<ViewStyle>}
|
|
225
241
|
{...webProps}
|
|
@@ -230,7 +246,11 @@ function Button({
|
|
|
230
246
|
</View>
|
|
231
247
|
) : null}
|
|
232
248
|
{content}
|
|
233
|
-
{
|
|
249
|
+
{icon ? (
|
|
250
|
+
<View style={trailingAccessoryStyle}>
|
|
251
|
+
<Icon name={icon} size={iconSize as number} color={activeIconColor as string} accessibilityElementsHidden={true} importantForAccessibility="no" />
|
|
252
|
+
</View>
|
|
253
|
+
) : trailing ? (
|
|
234
254
|
<View style={trailingAccessoryStyle}>
|
|
235
255
|
{trailing}
|
|
236
256
|
</View>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { View, type ViewStyle, type StyleProp, type ImageSourcePropType } from 'react-native'
|
|
3
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
4
|
+
import ProductLabel from '../ProductLabel/ProductLabel'
|
|
5
|
+
import { cloneChildrenWithModes } from '../../utils/react-utils'
|
|
6
|
+
|
|
7
|
+
export type CardProviderInfoProps = {
|
|
8
|
+
/** Product name shown in the ProductLabel header. */
|
|
9
|
+
label?: string
|
|
10
|
+
/** Image source for the product avatar. */
|
|
11
|
+
imageSource?: ImageSourcePropType | string
|
|
12
|
+
/** Slot content — typically StatItem components arranged in a 2-column grid. */
|
|
13
|
+
children?: React.ReactNode
|
|
14
|
+
/** Design token modes for theming. */
|
|
15
|
+
modes?: Record<string, any>
|
|
16
|
+
/** Override container styles. */
|
|
17
|
+
style?: StyleProp<ViewStyle>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* CardProviderInfo displays a product header (ProductLabel) followed by a
|
|
22
|
+
* 2-column grid of children (typically StatItem instances).
|
|
23
|
+
*
|
|
24
|
+
* @component
|
|
25
|
+
* @param {CardProviderInfoProps} props
|
|
26
|
+
*/
|
|
27
|
+
function CardProviderInfo({
|
|
28
|
+
label = 'Gold',
|
|
29
|
+
imageSource,
|
|
30
|
+
children,
|
|
31
|
+
modes = {},
|
|
32
|
+
style,
|
|
33
|
+
}: CardProviderInfoProps) {
|
|
34
|
+
const background = getVariableByName('card/providerInfo/background', modes) ?? '#fef4e5'
|
|
35
|
+
const border = getVariableByName('card/providerInfo/border', modes) ?? '#fef4e5'
|
|
36
|
+
const borderWidthVal = getVariableByName('card/providerInfo/borderWidth', modes) ?? 1
|
|
37
|
+
const padding = getVariableByName('card/providerInfo/padding', modes) ?? 20
|
|
38
|
+
const gap = getVariableByName('card/providerInfo/gap', modes) ?? 20
|
|
39
|
+
const radius = getVariableByName('card/providerInfo/radius', modes) ?? 16
|
|
40
|
+
const gridGap = getVariableByName('card/providerInfo/grid/gap', modes) ?? 8
|
|
41
|
+
|
|
42
|
+
const containerStyle: ViewStyle = {
|
|
43
|
+
backgroundColor: background as string,
|
|
44
|
+
borderColor: border as string,
|
|
45
|
+
borderWidth: borderWidthVal as number,
|
|
46
|
+
borderStyle: 'solid',
|
|
47
|
+
padding: padding as number,
|
|
48
|
+
gap: gap as number,
|
|
49
|
+
borderRadius: radius as number,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const gridGapNum = gridGap as number
|
|
53
|
+
const clonedChildren = children ? cloneChildrenWithModes(children, modes) : []
|
|
54
|
+
const childArray = React.Children.toArray(clonedChildren)
|
|
55
|
+
|
|
56
|
+
const rows: React.ReactNode[][] = []
|
|
57
|
+
for (let i = 0; i < childArray.length; i += 2) {
|
|
58
|
+
rows.push(childArray.slice(i, i + 2))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<View style={[containerStyle, style]}>
|
|
63
|
+
<ProductLabel label={label} imageSource={imageSource} modes={modes} />
|
|
64
|
+
{childArray.length > 0 && (
|
|
65
|
+
<View style={{ rowGap: gridGapNum }}>
|
|
66
|
+
{rows.map((row, i) => (
|
|
67
|
+
<View key={i} style={{ flexDirection: 'row', columnGap: gridGapNum }}>
|
|
68
|
+
{row.map((child, j) => (
|
|
69
|
+
<View key={j} style={{ flex: 1 }}>
|
|
70
|
+
{child}
|
|
71
|
+
</View>
|
|
72
|
+
))}
|
|
73
|
+
</View>
|
|
74
|
+
))}
|
|
75
|
+
</View>
|
|
76
|
+
)}
|
|
77
|
+
</View>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export default CardProviderInfo
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Pressable,
|
|
4
|
+
Platform,
|
|
5
|
+
type StyleProp,
|
|
6
|
+
type ViewStyle,
|
|
7
|
+
} from 'react-native'
|
|
8
|
+
import Svg, { Path } from 'react-native-svg'
|
|
9
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Tracks whether the last user interaction was a keyboard event (Tab).
|
|
13
|
+
* Capture-phase document listeners fire before any element-level handlers,
|
|
14
|
+
* so the flag is always up-to-date when onFocus runs.
|
|
15
|
+
*/
|
|
16
|
+
function useFocusVisible() {
|
|
17
|
+
const [isFocusVisible, setIsFocusVisible] = useState(false)
|
|
18
|
+
const hadKeyboardEventRef = useRef(false)
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (Platform.OS !== 'web' || typeof document === 'undefined') return
|
|
22
|
+
|
|
23
|
+
const onKeyDown = (e: KeyboardEvent) => {
|
|
24
|
+
if (e.key === 'Tab') hadKeyboardEventRef.current = true
|
|
25
|
+
}
|
|
26
|
+
const onPointerDown = () => {
|
|
27
|
+
hadKeyboardEventRef.current = false
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
document.addEventListener('keydown', onKeyDown, true)
|
|
31
|
+
document.addEventListener('mousedown', onPointerDown, true)
|
|
32
|
+
document.addEventListener('touchstart', onPointerDown, true)
|
|
33
|
+
|
|
34
|
+
return () => {
|
|
35
|
+
document.removeEventListener('keydown', onKeyDown, true)
|
|
36
|
+
document.removeEventListener('mousedown', onPointerDown, true)
|
|
37
|
+
document.removeEventListener('touchstart', onPointerDown, true)
|
|
38
|
+
}
|
|
39
|
+
}, [])
|
|
40
|
+
|
|
41
|
+
const onFocus = useCallback(() => {
|
|
42
|
+
if (hadKeyboardEventRef.current) setIsFocusVisible(true)
|
|
43
|
+
}, [])
|
|
44
|
+
|
|
45
|
+
const onBlur = useCallback(() => {
|
|
46
|
+
setIsFocusVisible(false)
|
|
47
|
+
}, [])
|
|
48
|
+
|
|
49
|
+
return { isFocusVisible, focusHandlers: { onFocus, onBlur } }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface CheckboxProps {
|
|
53
|
+
/** Whether the checkbox is checked (controlled) */
|
|
54
|
+
checked?: boolean
|
|
55
|
+
/** Initial checked state (uncontrolled) */
|
|
56
|
+
defaultChecked?: boolean
|
|
57
|
+
/** Callback when the checked state changes */
|
|
58
|
+
onValueChange?: (checked: boolean) => void
|
|
59
|
+
/** Whether the checkbox is disabled */
|
|
60
|
+
disabled?: boolean
|
|
61
|
+
/** Design token modes for theming */
|
|
62
|
+
modes?: Record<string, any>
|
|
63
|
+
/** Override container styles */
|
|
64
|
+
style?: StyleProp<ViewStyle>
|
|
65
|
+
/** Accessibility label for screen readers */
|
|
66
|
+
accessibilityLabel?: string
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Checkbox component that maps directly to the Figma design using design tokens.
|
|
71
|
+
*
|
|
72
|
+
* Supports 8 visual states: Idle, Hover, Focus, Selected, Selected Hover,
|
|
73
|
+
* Focus Selected, Disabled, and Disabled Active. All styling is driven by
|
|
74
|
+
* design tokens with optional mode overrides.
|
|
75
|
+
*
|
|
76
|
+
* @component
|
|
77
|
+
* @param {CheckboxProps} props
|
|
78
|
+
*/
|
|
79
|
+
function Checkbox({
|
|
80
|
+
checked: controlledChecked,
|
|
81
|
+
defaultChecked = false,
|
|
82
|
+
onValueChange,
|
|
83
|
+
disabled = false,
|
|
84
|
+
modes = {},
|
|
85
|
+
style,
|
|
86
|
+
accessibilityLabel,
|
|
87
|
+
}: CheckboxProps) {
|
|
88
|
+
const isControlled = controlledChecked !== undefined
|
|
89
|
+
const [internalChecked, setInternalChecked] = useState(defaultChecked)
|
|
90
|
+
const isChecked = isControlled ? controlledChecked : internalChecked
|
|
91
|
+
|
|
92
|
+
const [isHovered, setIsHovered] = useState(false)
|
|
93
|
+
const { isFocusVisible, focusHandlers } = useFocusVisible()
|
|
94
|
+
|
|
95
|
+
const size = getVariableByName('checkbox/size', modes) ?? 18
|
|
96
|
+
const radius = getVariableByName('checkbox/radius', modes) ?? 4
|
|
97
|
+
const strokeWidth = getVariableByName('strokeWidth', modes) ?? 1
|
|
98
|
+
|
|
99
|
+
const idleStroke = getVariableByName('checkbox/idle/stroke/color', modes) ?? '#666666'
|
|
100
|
+
const hoverFill = getVariableByName('checkbox/hover/fill/color', modes) ?? 'rgba(0,0,0,0.1)'
|
|
101
|
+
const hoverStroke = getVariableByName('checkbox/hover/stroke/color', modes) ?? '#666666'
|
|
102
|
+
const hoverRing = getVariableByName('checkbox/hoverRing', modes) ?? 4
|
|
103
|
+
const hoverShadow = getVariableByName('hover/boxShadow', modes) ?? 'rgba(0,0,0,0.1)'
|
|
104
|
+
const selectedFill = getVariableByName('checkbox/selected/fill/color', modes) ?? '#cea15a'
|
|
105
|
+
const selectedStroke = getVariableByName('checkbox/selected/stroke/color', modes) ?? '#0d0d0d'
|
|
106
|
+
const selectedMarkColor = getVariableByName('checkbox/selected/mark/color', modes) ?? '#0f0d0a'
|
|
107
|
+
const focusFill = getVariableByName('checkbox/focus/fill/color', modes) ?? '#ffffff'
|
|
108
|
+
const focusStroke = getVariableByName('checkbox/focus/stroke/color', modes) ?? '#666666'
|
|
109
|
+
const focusShadowColor = getVariableByName('checkbox/focus/shadow/color', modes) ?? '#12fc2e'
|
|
110
|
+
const focusSpread = getVariableByName('checkbox/focus/spread/effect', modes) ?? 2
|
|
111
|
+
const disabledStroke = getVariableByName('checkbox/disabled/stroke/color', modes) ?? '#999999'
|
|
112
|
+
const disabledActiveBg = getVariableByName('checkbox/disabledActive/background/color', modes) ?? '#f6d19a'
|
|
113
|
+
const disabledActiveBorder = getVariableByName('checkbox/disabledActive/border/color', modes) ?? '#f6d19a'
|
|
114
|
+
const disabledActiveMark = getVariableByName('checkbox/disabledActive/mark/color', modes) ?? '#474d54'
|
|
115
|
+
const selectedHoverShadow = getVariableByName('selected/hover/boxShadow', modes) ?? 'rgba(0,0,0,0.1)'
|
|
116
|
+
const hoverShadowSize = getVariableByName('hover/boxShadow/size', modes) ?? 4
|
|
117
|
+
|
|
118
|
+
const handlePress = useCallback(() => {
|
|
119
|
+
if (disabled) return
|
|
120
|
+
const next = !isChecked
|
|
121
|
+
if (!isControlled) {
|
|
122
|
+
setInternalChecked(next)
|
|
123
|
+
}
|
|
124
|
+
onValueChange?.(next)
|
|
125
|
+
}, [disabled, isChecked, isControlled, onValueChange])
|
|
126
|
+
|
|
127
|
+
const resolveStyle = (): ViewStyle => {
|
|
128
|
+
const base: ViewStyle = {
|
|
129
|
+
width: size as number,
|
|
130
|
+
height: size as number,
|
|
131
|
+
borderRadius: radius as number,
|
|
132
|
+
borderWidth: strokeWidth as number,
|
|
133
|
+
alignItems: 'center',
|
|
134
|
+
justifyContent: 'center',
|
|
135
|
+
overflow: 'hidden',
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (disabled && isChecked) {
|
|
139
|
+
return {
|
|
140
|
+
...base,
|
|
141
|
+
backgroundColor: disabledActiveBg as string,
|
|
142
|
+
borderColor: disabledActiveBorder as string,
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (disabled) {
|
|
147
|
+
return {
|
|
148
|
+
...base,
|
|
149
|
+
borderColor: disabledStroke as string,
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (isChecked && isFocusVisible) {
|
|
154
|
+
return {
|
|
155
|
+
...base,
|
|
156
|
+
backgroundColor: selectedFill as string,
|
|
157
|
+
borderColor: selectedStroke as string,
|
|
158
|
+
...boxShadow(`0px 0px 0px ${focusSpread}px ${focusShadowColor}`),
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (isChecked && isHovered) {
|
|
163
|
+
return {
|
|
164
|
+
...base,
|
|
165
|
+
backgroundColor: selectedFill as string,
|
|
166
|
+
borderColor: selectedStroke as string,
|
|
167
|
+
...boxShadow(`0px 0px 0px ${hoverShadowSize}px ${selectedHoverShadow}`),
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (isChecked) {
|
|
172
|
+
return {
|
|
173
|
+
...base,
|
|
174
|
+
backgroundColor: selectedFill as string,
|
|
175
|
+
borderColor: selectedStroke as string,
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (isFocusVisible) {
|
|
180
|
+
return {
|
|
181
|
+
...base,
|
|
182
|
+
backgroundColor: focusFill as string,
|
|
183
|
+
borderColor: focusStroke as string,
|
|
184
|
+
...boxShadow(`0px 0px 0px ${focusSpread}px ${focusShadowColor}`),
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (isHovered) {
|
|
189
|
+
return {
|
|
190
|
+
...base,
|
|
191
|
+
backgroundColor: hoverFill as string,
|
|
192
|
+
borderColor: hoverStroke as string,
|
|
193
|
+
...boxShadow(`0px 0px 0px ${hoverRing}px ${hoverShadow}`),
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
...base,
|
|
199
|
+
borderColor: idleStroke as string,
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const markColor = disabled && isChecked
|
|
204
|
+
? (disabledActiveMark as string)
|
|
205
|
+
: (selectedMarkColor as string)
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<Pressable
|
|
209
|
+
style={[resolveStyle(), style]}
|
|
210
|
+
onPress={handlePress}
|
|
211
|
+
disabled={disabled}
|
|
212
|
+
onHoverIn={() => setIsHovered(true)}
|
|
213
|
+
onHoverOut={() => setIsHovered(false)}
|
|
214
|
+
{...focusHandlers}
|
|
215
|
+
accessibilityRole="checkbox"
|
|
216
|
+
accessibilityState={{ checked: isChecked, disabled }}
|
|
217
|
+
accessibilityLabel={accessibilityLabel}
|
|
218
|
+
>
|
|
219
|
+
{isChecked && (
|
|
220
|
+
<Svg width={12} height={9} viewBox="0 0 12 9" fill="none">
|
|
221
|
+
<Path
|
|
222
|
+
d="M4.00091 8.66939C3.91321 8.6699 3.82628 8.65309 3.74509 8.61991C3.6639 8.58673 3.59006 8.53785 3.52779 8.47606L0.195972 5.14273C0.0704931 5.01719 -1.86978e-09 4.84693 0 4.66939C1.86978e-09 4.49186 0.0704931 4.3216 0.195972 4.19606C0.321451 4.07053 0.491636 4 0.66909 4C0.846544 4 1.01673 4.07053 1.14221 4.19606L4.00091 7.06273L10.8578 0.196061C10.9833 0.0705253 11.1535 0 11.3309 0C11.5084 0 11.6785 0.0705253 11.804 0.196061C11.9295 0.321597 12 0.49186 12 0.669394C12 0.846929 11.9295 1.01719 11.804 1.14273L4.47403 8.47606C4.41176 8.53785 4.33792 8.58673 4.25673 8.61991C4.17554 8.65309 4.08861 8.6699 4.00091 8.66939Z"
|
|
223
|
+
fill={markColor}
|
|
224
|
+
/>
|
|
225
|
+
</Svg>
|
|
226
|
+
)}
|
|
227
|
+
</Pressable>
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function boxShadow(value: string): ViewStyle {
|
|
232
|
+
if (Platform.OS === 'web') {
|
|
233
|
+
return { boxShadow: value } as ViewStyle
|
|
234
|
+
}
|
|
235
|
+
return {}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export default Checkbox
|
|
@@ -16,6 +16,11 @@ export type EmptyStateProps = {
|
|
|
16
16
|
* @default "Start by paying bills, recharge or your friends"
|
|
17
17
|
*/
|
|
18
18
|
description?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Whether to show the description text
|
|
21
|
+
* @default true
|
|
22
|
+
*/
|
|
23
|
+
showDescription?: boolean;
|
|
19
24
|
/**
|
|
20
25
|
* Custom slot for the icon area. Defaults to IconCapsule.
|
|
21
26
|
* If providing a custom component, ensure it accepts `modes` if needed.
|
|
@@ -42,6 +47,7 @@ export type EmptyStateProps = {
|
|
|
42
47
|
function EmptyState({
|
|
43
48
|
title = "No payments to show",
|
|
44
49
|
description = "Start by paying bills, recharge or your friends",
|
|
50
|
+
showDescription = true,
|
|
45
51
|
iconSlot,
|
|
46
52
|
buttonSlot,
|
|
47
53
|
modes: propModes = {},
|
|
@@ -140,7 +146,7 @@ function EmptyState({
|
|
|
140
146
|
|
|
141
147
|
{/* Texts */}
|
|
142
148
|
<Text style={titleStyle}>{title}</Text>
|
|
143
|
-
{description ? <Text style={bodyStyle}>{description}</Text> : null}
|
|
149
|
+
{showDescription && description ? <Text style={bodyStyle}>{description}</Text> : null}
|
|
144
150
|
</View>
|
|
145
151
|
|
|
146
152
|
{/* Button Slot */}
|