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.
Files changed (106) hide show
  1. package/lib/commonjs/components/AmountInput/AmountInput.js +82 -0
  2. package/lib/commonjs/components/AmountInput/index.js +13 -0
  3. package/lib/commonjs/components/Button/Button.js +45 -28
  4. package/lib/commonjs/components/CardProviderInfo/CardProviderInfo.js +76 -0
  5. package/lib/commonjs/components/Checkbox/Checkbox.js +208 -0
  6. package/lib/commonjs/components/EmptyState/EmptyState.js +2 -1
  7. package/lib/commonjs/components/MoneyValue/MoneyValue.js +81 -49
  8. package/lib/commonjs/components/NoteInput/NoteInput.js +120 -0
  9. package/lib/commonjs/components/NoteInput/index.js +13 -0
  10. package/lib/commonjs/components/Numpad/Numpad.js +108 -0
  11. package/lib/commonjs/components/OTP/OTP.js +242 -0
  12. package/lib/commonjs/components/PortfolioHero/PortfolioHero.js +78 -0
  13. package/lib/commonjs/components/ProductLabel/ProductLabel.js +50 -0
  14. package/lib/commonjs/components/ProgressBadge/ProgressBadge.js +130 -0
  15. package/lib/commonjs/components/ProgressBadge/index.js +25 -0
  16. package/lib/commonjs/components/StatItem/StatItem.js +61 -0
  17. package/lib/commonjs/components/StatusHero/StatusHero.js +148 -0
  18. package/lib/commonjs/components/SwappableAmount/SwappableAmount.js +71 -0
  19. package/lib/commonjs/components/Tabs/TabItem.js +79 -0
  20. package/lib/commonjs/components/Tabs/Tabs.js +88 -0
  21. package/lib/commonjs/components/Text/Text.js +38 -0
  22. package/lib/commonjs/components/Toggle/Toggle.js +102 -0
  23. package/lib/commonjs/components/index.js +105 -0
  24. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -0
  25. package/lib/commonjs/design-tokens/figma-variables-resolver.js +1 -1
  26. package/lib/commonjs/icons/registry.js +1 -1
  27. package/lib/module/components/AmountInput/AmountInput.js +77 -0
  28. package/lib/module/components/AmountInput/index.js +3 -0
  29. package/lib/module/components/Button/Button.js +44 -28
  30. package/lib/module/components/CardProviderInfo/CardProviderInfo.js +71 -0
  31. package/lib/module/components/Checkbox/Checkbox.js +205 -0
  32. package/lib/module/components/EmptyState/EmptyState.js +2 -1
  33. package/lib/module/components/MoneyValue/MoneyValue.js +81 -49
  34. package/lib/module/components/NoteInput/NoteInput.js +115 -0
  35. package/lib/module/components/NoteInput/index.js +3 -0
  36. package/lib/module/components/Numpad/Numpad.js +103 -0
  37. package/lib/module/components/OTP/OTP.js +236 -0
  38. package/lib/module/components/PortfolioHero/PortfolioHero.js +73 -0
  39. package/lib/module/components/ProductLabel/ProductLabel.js +45 -0
  40. package/lib/module/components/ProgressBadge/ProgressBadge.js +125 -0
  41. package/lib/module/components/ProgressBadge/index.js +4 -0
  42. package/lib/module/components/StatItem/StatItem.js +56 -0
  43. package/lib/module/components/StatusHero/StatusHero.js +142 -0
  44. package/lib/module/components/SwappableAmount/SwappableAmount.js +66 -0
  45. package/lib/module/components/Tabs/TabItem.js +74 -0
  46. package/lib/module/components/Tabs/Tabs.js +78 -0
  47. package/lib/module/components/Text/Text.js +33 -0
  48. package/lib/module/components/Toggle/Toggle.js +97 -0
  49. package/lib/module/components/index.js +16 -1
  50. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -0
  51. package/lib/module/design-tokens/figma-variables-resolver.js +1 -1
  52. package/lib/module/icons/registry.js +1 -1
  53. package/lib/typescript/src/components/AmountInput/AmountInput.d.ts +23 -0
  54. package/lib/typescript/src/components/AmountInput/index.d.ts +3 -0
  55. package/lib/typescript/src/components/Button/Button.d.ts +6 -1
  56. package/lib/typescript/src/components/CardProviderInfo/CardProviderInfo.d.ts +24 -0
  57. package/lib/typescript/src/components/Checkbox/Checkbox.d.ts +30 -0
  58. package/lib/typescript/src/components/EmptyState/EmptyState.d.ts +6 -1
  59. package/lib/typescript/src/components/MoneyValue/MoneyValue.d.ts +18 -26
  60. package/lib/typescript/src/components/NoteInput/NoteInput.d.ts +23 -0
  61. package/lib/typescript/src/components/NoteInput/index.d.ts +3 -0
  62. package/lib/typescript/src/components/Numpad/Numpad.d.ts +35 -0
  63. package/lib/typescript/src/components/OTP/OTP.d.ts +36 -0
  64. package/lib/typescript/src/components/PortfolioHero/PortfolioHero.d.ts +21 -0
  65. package/lib/typescript/src/components/ProductLabel/ProductLabel.d.ts +14 -0
  66. package/lib/typescript/src/components/ProgressBadge/ProgressBadge.d.ts +36 -0
  67. package/lib/typescript/src/components/ProgressBadge/index.d.ts +3 -0
  68. package/lib/typescript/src/components/StatItem/StatItem.d.ts +21 -0
  69. package/lib/typescript/src/components/StatusHero/StatusHero.d.ts +47 -0
  70. package/lib/typescript/src/components/SwappableAmount/SwappableAmount.d.ts +22 -0
  71. package/lib/typescript/src/components/Tabs/TabItem.d.ts +29 -0
  72. package/lib/typescript/src/components/Tabs/Tabs.d.ts +44 -0
  73. package/lib/typescript/src/components/Text/Text.d.ts +14 -0
  74. package/lib/typescript/src/components/Toggle/Toggle.d.ts +29 -0
  75. package/lib/typescript/src/components/index.d.ts +15 -0
  76. package/lib/typescript/src/icons/registry.d.ts +1 -1
  77. package/package.json +1 -1
  78. package/src/components/AmountInput/AmountInput.tsx +81 -0
  79. package/src/components/AmountInput/index.ts +2 -0
  80. package/src/components/Button/Button.tsx +40 -20
  81. package/src/components/CardProviderInfo/CardProviderInfo.tsx +81 -0
  82. package/src/components/Checkbox/Checkbox.tsx +238 -0
  83. package/src/components/EmptyState/EmptyState.tsx +7 -1
  84. package/src/components/MoneyValue/MoneyValue.tsx +134 -79
  85. package/src/components/NoteInput/NoteInput.tsx +146 -0
  86. package/src/components/NoteInput/index.ts +2 -0
  87. package/src/components/Numpad/Numpad.tsx +162 -0
  88. package/src/components/OTP/OTP.tsx +275 -0
  89. package/src/components/PortfolioHero/PortfolioHero.tsx +91 -0
  90. package/src/components/ProductLabel/ProductLabel.tsx +58 -0
  91. package/src/components/ProgressBadge/ProgressBadge.tsx +172 -0
  92. package/src/components/ProgressBadge/index.ts +2 -0
  93. package/src/components/StatItem/StatItem.tsx +71 -0
  94. package/src/components/StatusHero/StatusHero.tsx +156 -0
  95. package/src/components/SwappableAmount/SwappableAmount.tsx +92 -0
  96. package/src/components/Tabs/TabItem.tsx +96 -0
  97. package/src/components/Tabs/Tabs.tsx +105 -0
  98. package/src/components/Text/Text.tsx +48 -0
  99. package/src/components/Toggle/Toggle.tsx +122 -0
  100. package/src/components/index.ts +15 -0
  101. package/src/design-tokens/Coin Variables-variables-full.json +1 -0
  102. package/src/design-tokens/figma-variables-resolver.ts +1 -1
  103. package/src/icons/registry.ts +1 -1
  104. package/lib/commonjs/design-tokens/JFS Variables-variables-full.json +0 -1
  105. package/lib/module/design-tokens/JFS Variables-variables-full.json +0 -1
  106. 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-02-19T12:53:50.558Z
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jfs-components",
3
- "version": "0.0.43",
3
+ "version": "0.0.45",
4
4
  "description": "React Native Jio Finance Components Library",
5
5
  "author": "sunshuaiqi@gmail.com",
6
6
  "license": "MIT",
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ export { default } from './AmountInput'
2
+ export type { AmountInputProps } from './AmountInput'
@@ -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: textColor,
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
- // Interaction states (placeholders for visuals; tweak later)
163
- const [isFocused, setIsFocused] = useState(false)
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
- ; (rest as any)?.onPressIn?.(e)
220
+ setIsPressed(true)
221
+ ;(rest as any)?.onPressIn?.(e)
198
222
  }}
199
223
  onPressOut={(e: any) => {
200
- ; (rest as any)?.onPressOut?.(e)
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
- isFocused && !disabled ? focusStyle : null,
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
- {trailing ? (
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 */}