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
@@ -0,0 +1,275 @@
1
+ import React, { useState, useRef, useCallback, useEffect } from 'react'
2
+ import {
3
+ View,
4
+ Text,
5
+ TextInput as RNTextInput,
6
+ Pressable,
7
+ Animated,
8
+ type ViewStyle,
9
+ type TextStyle,
10
+ type StyleProp,
11
+ } from 'react-native'
12
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
13
+ import { useTokens } from '../../design-tokens/JFSThemeProvider'
14
+ import SupportText, { type SupportTextProps } from '../SupportText/SupportText'
15
+ import { cloneChildrenWithModes } from '../../utils/react-utils'
16
+
17
+ export type OTPProps = {
18
+ /** Number of OTP digits. Defaults to 6. */
19
+ length?: number
20
+ /** Controlled value of the OTP input. */
21
+ value?: string
22
+ /** Default value for uncontrolled usage. */
23
+ defaultValue?: string
24
+ /** Called when the OTP value changes. Receives the current string of entered digits. */
25
+ onChange?: (value: string) => void
26
+ /** Alias for onChange — called when the OTP value changes. */
27
+ onValueChange?: (value: string) => void
28
+ /** Called when all digits have been entered. Receives the complete OTP string. */
29
+ onComplete?: (value: string) => void
30
+ /** Whether the OTP input is disabled. */
31
+ isDisabled?: boolean
32
+ /** Whether the OTP input is in an invalid/error state. */
33
+ isInvalid?: boolean
34
+ /** Regex pattern to filter allowed characters. Defaults to digits only. */
35
+ allowedPattern?: RegExp
36
+ /** Auto-focus the input on mount. */
37
+ autoFocus?: boolean
38
+ /** Design token modes for Figma token resolution. */
39
+ modes?: Record<string, any>
40
+ /** Container style override. */
41
+ style?: StyleProp<ViewStyle>
42
+ /** Optional SupportText rendered below the slots. Pass a string for the label or a ReactNode for full control. */
43
+ supportText?: React.ReactNode
44
+ /** SupportText status when using the string shorthand. */
45
+ supportTextStatus?: SupportTextProps['status']
46
+ }
47
+
48
+ const DIGITS_ONLY = /^\d*$/
49
+
50
+ function OTP({
51
+ length = 6,
52
+ value: controlledValue,
53
+ defaultValue = '',
54
+ onChange,
55
+ onValueChange,
56
+ onComplete,
57
+ isDisabled = false,
58
+ isInvalid = false,
59
+ allowedPattern = DIGITS_ONLY,
60
+ autoFocus = false,
61
+ modes: propModes = {},
62
+ style,
63
+ supportText,
64
+ supportTextStatus,
65
+ }: OTPProps) {
66
+ const { modes: globalModes } = useTokens()
67
+ const modes = { ...globalModes, ...propModes }
68
+
69
+ const isControlled = controlledValue !== undefined
70
+ const [internalValue, setInternalValue] = useState(defaultValue)
71
+ const currentValue = isControlled ? controlledValue : internalValue
72
+
73
+ const inputRef = useRef<RNTextInput>(null)
74
+ const [isFocused, setIsFocused] = useState(false)
75
+
76
+ const caretAnim = useRef(new Animated.Value(1)).current
77
+
78
+ useEffect(() => {
79
+ if (!isFocused) return
80
+ const blink = Animated.loop(
81
+ Animated.sequence([
82
+ Animated.timing(caretAnim, { toValue: 0, duration: 400, useNativeDriver: true }),
83
+ Animated.timing(caretAnim, { toValue: 1, duration: 400, useNativeDriver: true }),
84
+ ]),
85
+ )
86
+ blink.start()
87
+ return () => blink.stop()
88
+ }, [isFocused, caretAnim])
89
+
90
+ const prevCompleteRef = useRef(false)
91
+
92
+ const setValue = useCallback(
93
+ (next: string) => {
94
+ const clamped = next.slice(0, length)
95
+ if (!isControlled) setInternalValue(clamped)
96
+ onChange?.(clamped)
97
+ onValueChange?.(clamped)
98
+
99
+ if (clamped.length === length && !prevCompleteRef.current) {
100
+ prevCompleteRef.current = true
101
+ onComplete?.(clamped)
102
+ }
103
+ if (clamped.length < length) {
104
+ prevCompleteRef.current = false
105
+ }
106
+ },
107
+ [length, isControlled, onChange, onValueChange, onComplete],
108
+ )
109
+
110
+ const handleChangeText = useCallback(
111
+ (text: string) => {
112
+ if (isDisabled) return
113
+ if (!allowedPattern.test(text)) return
114
+ setValue(text)
115
+ },
116
+ [isDisabled, allowedPattern, setValue],
117
+ )
118
+
119
+ const handlePress = useCallback(() => {
120
+ if (isDisabled) return
121
+ inputRef.current?.focus()
122
+ }, [isDisabled])
123
+
124
+ // --- Token resolution (state-independent tokens resolved once) ---
125
+ const otpGap = Number(getVariableByName('otp/gap', modes)) || 36
126
+ const otpPaddingH = Number(getVariableByName('otp/padding/horizontal', modes)) || 8
127
+ const otpPaddingV = Number(getVariableByName('otp/padding/vertical', modes)) || 8
128
+
129
+ const slotWidth = Number(getVariableByName('pinSlot/width', modes)) || 48
130
+ const slotGap = Number(getVariableByName('pinSlot/gap', modes)) || 8
131
+ // digit/color has no state variants in Figma — resolved once from the Output collection
132
+ const digitColor = (getVariableByName('pinSlot/digit/color', modes) as string) || '#000000'
133
+ const digitFontSize = Number(getVariableByName('pinSlot/digit/fontSize', modes)) || 24
134
+ const digitFontFamily = (getVariableByName('pinSlot/digit/fontFamily', modes) as string) || 'JioType Var'
135
+ const digitLineHeight = Number(getVariableByName('pinSlot/digit/lineHeight', modes)) || 29
136
+ const digitFontWeight = (getVariableByName('pinSlot/digit/fontWeight', modes) as string) || '500'
137
+ const underlineHeight = Number(getVariableByName('pinSlot/underline/height', modes)) || 2
138
+ const underlineRadius = Number(getVariableByName('pinSlot/underline/radius', modes)) || 1
139
+
140
+ // --- State-driven slot modes ---
141
+ // Collection name in Figma is "Input/PINSlot States" (double space before States).
142
+ // Only PinSlot/underline/color (capital P/S) lives in this collection with Idle/Active/Error modes.
143
+ // isInvalid takes priority over active focus; the component maps semantic state → token mode
144
+ // internally so consumers never need to know the collection key name.
145
+ const getSlotModes = (isActiveSlot: boolean): Record<string, any> => {
146
+ if (isInvalid) return { ...modes, 'Input/PINSlot States': 'Error' }
147
+ if (isActiveSlot && isFocused) return { ...modes, 'Input/PINSlot States': 'Active' }
148
+ return { ...modes, 'Input/PINSlot States': 'Idle' }
149
+ }
150
+
151
+ // --- Styles ---
152
+ const containerStyle: ViewStyle = {
153
+ flexDirection: 'column',
154
+ alignItems: 'flex-end',
155
+ gap: otpGap,
156
+ paddingHorizontal: otpPaddingH,
157
+ paddingVertical: otpPaddingV,
158
+ }
159
+
160
+ const slotWrapStyle: ViewStyle = {
161
+ flexDirection: 'row',
162
+ gap: 8,
163
+ alignItems: 'flex-start',
164
+ alignSelf: 'stretch',
165
+ }
166
+
167
+ const renderSlot = (index: number) => {
168
+ const char = currentValue[index]
169
+ const isActiveSlot = index === currentValue.length && currentValue.length < length
170
+ const isFilled = char !== undefined
171
+
172
+ // Underline color is the only state-sensitive token (lives in "Input/PINSlot States" collection).
173
+ // Note: token name is "PinSlot/underline/color" (capital P/S) — different from the static
174
+ // "pinSlot/underline/color" in the Output collection.
175
+ const slotModes = getSlotModes(isActiveSlot)
176
+ const underlineColor = (getVariableByName('PinSlot/underline/color', slotModes) as string) || '#303338'
177
+
178
+ const slotStyle: ViewStyle = {
179
+ width: slotWidth,
180
+ flexDirection: 'column',
181
+ alignItems: 'center',
182
+ justifyContent: 'flex-end',
183
+ gap: slotGap,
184
+ }
185
+
186
+ const digitStyle: TextStyle = {
187
+ fontFamily: digitFontFamily,
188
+ fontWeight: digitFontWeight as TextStyle['fontWeight'],
189
+ fontSize: digitFontSize,
190
+ lineHeight: digitLineHeight,
191
+ color: digitColor,
192
+ textAlign: 'center',
193
+ minWidth: '100%' as any,
194
+ }
195
+
196
+ const underlineStyle: ViewStyle = {
197
+ width: slotWidth,
198
+ height: underlineHeight,
199
+ borderRadius: underlineRadius,
200
+ backgroundColor: underlineColor,
201
+ }
202
+
203
+ return (
204
+ <View key={index} style={slotStyle}>
205
+ <View style={{ minHeight: digitLineHeight, justifyContent: 'center', alignItems: 'center', width: '100%' }}>
206
+ {isFilled ? (
207
+ <Text style={digitStyle}>{char}</Text>
208
+ ) : isActiveSlot && isFocused ? (
209
+ <Animated.View
210
+ style={{
211
+ width: 2,
212
+ height: digitFontSize,
213
+ backgroundColor: digitColor,
214
+ opacity: caretAnim,
215
+ }}
216
+ />
217
+ ) : (
218
+ <Text style={[digitStyle, { color: 'transparent' }]}>{'\u00A0'}</Text>
219
+ )}
220
+ </View>
221
+ <View style={underlineStyle} />
222
+ </View>
223
+ )
224
+ }
225
+
226
+ const renderSupportText = () => {
227
+ if (!supportText) return null
228
+ if (typeof supportText === 'string') {
229
+ return (
230
+ <SupportText
231
+ label={supportText}
232
+ status={supportTextStatus ?? (isInvalid ? 'Error' : 'Neutral')}
233
+ modes={modes}
234
+ />
235
+ )
236
+ }
237
+ return <>{cloneChildrenWithModes(React.Children.toArray(supportText), modes)}</>
238
+ }
239
+
240
+ return (
241
+ <Pressable
242
+ style={[containerStyle, isDisabled && { opacity: 0.4 }, style]}
243
+ onPress={handlePress}
244
+ disabled={isDisabled}
245
+ accessibilityRole="none"
246
+ >
247
+ <RNTextInput
248
+ ref={inputRef}
249
+ value={currentValue}
250
+ onChangeText={handleChangeText}
251
+ maxLength={length}
252
+ keyboardType="number-pad"
253
+ autoFocus={autoFocus}
254
+ editable={!isDisabled}
255
+ onFocus={() => setIsFocused(true)}
256
+ onBlur={() => setIsFocused(false)}
257
+ caretHidden
258
+ style={{
259
+ position: 'absolute',
260
+ width: 1,
261
+ height: 1,
262
+ opacity: 0,
263
+ }}
264
+ accessibilityLabel={`OTP input, ${length} digits`}
265
+ accessibilityHint="Enter your verification code"
266
+ />
267
+ <View style={slotWrapStyle}>
268
+ {Array.from({ length }, (_, i) => renderSlot(i))}
269
+ </View>
270
+ {renderSupportText()}
271
+ </Pressable>
272
+ )
273
+ }
274
+
275
+ export default OTP
@@ -0,0 +1,91 @@
1
+ import React from 'react'
2
+ import {
3
+ View,
4
+ Text,
5
+ type ViewStyle,
6
+ type TextStyle,
7
+ type ImageSourcePropType,
8
+ } from 'react-native'
9
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
10
+ import { cloneChildrenWithModes } from '../../utils/react-utils'
11
+ import Avatar from '../Avatar/Avatar'
12
+ import MoneyValue from '../MoneyValue/MoneyValue'
13
+
14
+ export type PortfolioHeroProps = {
15
+ /** Product label text displayed next to the avatar. */
16
+ label?: string
17
+ /** Image source for the product avatar. */
18
+ imageSource?: ImageSourcePropType | string
19
+ /** Monetary value to display. */
20
+ value?: string
21
+ /** Currency symbol or ISO code. */
22
+ currency?: string
23
+ /** Modes configuration mapped to Figma tokens. */
24
+ modes?: Record<string, any>
25
+ /** Slot content rendered below the money value (e.g. Badge, subtitle). */
26
+ children?: React.ReactNode
27
+ /** Container style override. */
28
+ style?: ViewStyle
29
+ }
30
+
31
+ function PortfolioHero({
32
+ label = 'Gold',
33
+ imageSource,
34
+ value = '12,000.11',
35
+ currency = '₹',
36
+ modes = { Context3: 'Balance' },
37
+ children,
38
+ style,
39
+ }: PortfolioHeroProps) {
40
+ const gap = getVariableByName('portfolioHero/gap', modes) ?? 16
41
+ const padding = getVariableByName('portfolioHero/padding', modes) ?? 8
42
+ const slotWrapGap = getVariableByName('portfolioHero/slotWrap/gap', modes) ?? 8
43
+
44
+ const containerStyle: ViewStyle = {
45
+ flexDirection: 'column',
46
+ alignItems: 'center',
47
+ justifyContent: 'center',
48
+ gap: gap as number,
49
+ padding: padding as number,
50
+ }
51
+
52
+ const productLabelStyle: ViewStyle = {
53
+ flexDirection: 'row',
54
+ alignItems: 'center',
55
+ gap: 8,
56
+ }
57
+
58
+ const labelTextStyle: TextStyle = {
59
+ fontFamily: 'System',
60
+ fontWeight: '700',
61
+ fontSize: 16,
62
+ lineHeight: 16 * 1.3,
63
+ color: '#0d0d0f',
64
+ textAlign: 'center',
65
+ }
66
+
67
+ const slotWrapStyle: ViewStyle = {
68
+ flexDirection: 'column',
69
+ alignItems: 'center',
70
+ gap: slotWrapGap as number,
71
+ }
72
+
73
+ return (
74
+ <View style={[containerStyle, style]}>
75
+ <View style={productLabelStyle}>
76
+ <Avatar style="Image" {...(imageSource != null && { imageSource })} modes={modes} />
77
+ <Text style={labelTextStyle}>{label}</Text>
78
+ </View>
79
+
80
+ <MoneyValue value={value} currency={currency} modes={modes} />
81
+
82
+ {children && (
83
+ <View style={slotWrapStyle}>
84
+ {cloneChildrenWithModes(children, modes)}
85
+ </View>
86
+ )}
87
+ </View>
88
+ )
89
+ }
90
+
91
+ export default PortfolioHero
@@ -0,0 +1,58 @@
1
+ import React from 'react'
2
+ import { View, Text, type ViewStyle, type TextStyle, type ImageSourcePropType } from 'react-native'
3
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
4
+ import Avatar from '../Avatar/Avatar'
5
+
6
+ export type ProductLabelProps = {
7
+ /** The product name label text. */
8
+ label?: string
9
+ /** Image source for the product avatar. */
10
+ imageSource?: ImageSourcePropType | string
11
+ /** Modes configuration for design token resolution. */
12
+ modes?: Record<string, any>
13
+ /** Container style override. */
14
+ style?: ViewStyle
15
+ }
16
+
17
+ function ProductLabel({
18
+ label = 'Gold',
19
+ imageSource,
20
+ modes = {},
21
+ style,
22
+ }: ProductLabelProps) {
23
+ const gap = getVariableByName('productLabel/gap', modes) ?? 8
24
+
25
+ const textColor = getVariableByName('productLabel/text/color', modes) ?? '#000000'
26
+ const textFontSize = getVariableByName('productLabel/text/fontSize', modes) ?? 16
27
+ const textFontFamily = getVariableByName('productLabel/text/fontFamily', modes) ?? 'JioType Var'
28
+ const textFontWeight = getVariableByName('productLabel/text/fontWeight', modes) ?? 700
29
+ const textLineHeight = getVariableByName('productLabel/text/lineHeight', modes) ?? 21
30
+
31
+ const containerStyle: ViewStyle = {
32
+ flexDirection: 'row',
33
+ alignItems: 'center',
34
+ gap: gap as number,
35
+ }
36
+
37
+ const labelTextStyle: TextStyle = {
38
+ color: textColor as string,
39
+ fontSize: textFontSize as number,
40
+ fontFamily: textFontFamily as string,
41
+ fontWeight: String(textFontWeight) as TextStyle['fontWeight'],
42
+ lineHeight: textLineHeight as number,
43
+ textAlign: 'center',
44
+ }
45
+
46
+ return (
47
+ <View style={[containerStyle, style]}>
48
+ <Avatar
49
+ style="Image"
50
+ imageSource={imageSource}
51
+ modes={modes}
52
+ />
53
+ <Text style={labelTextStyle}>{label}</Text>
54
+ </View>
55
+ )
56
+ }
57
+
58
+ export default ProductLabel
@@ -0,0 +1,172 @@
1
+ import React from 'react'
2
+ import {
3
+ View,
4
+ Text,
5
+ type StyleProp,
6
+ type ViewStyle,
7
+ type TextStyle,
8
+ } from 'react-native'
9
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
10
+ import Icon from '../../icons/Icon'
11
+ import { cloneChildrenWithModes } from '../../utils/react-utils'
12
+
13
+ export type ProgressBadgeProps = {
14
+ /** The text displayed in the badge (e.g. "Live price...") */
15
+ taskName?: string
16
+ /** The icon name to display to the left of the text */
17
+ iconName?: string
18
+ /** The progress value between 0 and 100 */
19
+ value?: number
20
+ /** Modes object passed to `getVariableByName` for design token resolution */
21
+ modes?: Record<string, any>
22
+ /** Optional container style overrides */
23
+ style?: StyleProp<ViewStyle>
24
+ /** Optional text style overrides */
25
+ textStyle?: StyleProp<TextStyle>
26
+ /** Accessibility label for screen readers */
27
+ accessibilityLabel?: string
28
+ } & React.ComponentProps<typeof View>
29
+
30
+ /**
31
+ * ProgressBadge component that displays an icon, text, and an internal progress bar.
32
+ *
33
+ * All visual attributes resolve from Figma tokens via `getVariableByName` using the provided `modes`.
34
+ *
35
+ * @component
36
+ * @param {Object} props
37
+ * @param {string} [props.taskName="Live price: [price] (00:43)"] - The text displayed.
38
+ * @param {string} [props.iconName="ic_live"] - Icon name from the registry.
39
+ * @param {number} [props.value=0] - The progress value between 0 and 100.
40
+ * @param {Object} [props.modes={}] - Modes object passed to `getVariableByName` for design tokens.
41
+ * @param {Object} [props.style] - Optional container style overrides.
42
+ * @param {Object} [props.textStyle] - Optional text style overrides.
43
+ * @param {string} [props.accessibilityLabel] - Accessibility label.
44
+ */
45
+ function ProgressBadge({
46
+ taskName = 'Live price: [price] (00:43)',
47
+ iconName = 'ic_live',
48
+ value = 0,
49
+ modes = {},
50
+ style,
51
+ textStyle: textStyleOverride,
52
+ accessibilityLabel,
53
+ ...rest
54
+ }: ProgressBadgeProps) {
55
+ // Resolve layout tokens
56
+ const backgroundColor =
57
+ getVariableByName('progressBadge/background', modes) || '#ffffff'
58
+ const progressColor =
59
+ getVariableByName('progressBadge/progress/color', modes) || '#ebebed'
60
+ const gap = getVariableByName('progressBadge/gap', modes) ?? 12
61
+ const paddingHorizontal =
62
+ getVariableByName('progressBadge/padding/horizontal', modes) ?? 16
63
+ const paddingVertical =
64
+ getVariableByName('progressBadge/padding/vertical', modes) ?? 6
65
+ const borderRadius = getVariableByName('progressBadge/radius', modes) ?? 999
66
+ const descriptionGap =
67
+ getVariableByName('progressBadge/description/gap', modes) ?? 8
68
+
69
+ // Resolve typography tokens
70
+ const fontFamily =
71
+ getVariableByName('progressBadge/fontFamily', modes) || 'JioType_Var:Medium'
72
+ const fontWeightRaw = getVariableByName('progressBadge/fontWeight', modes) ?? 500
73
+ const fontWeight =
74
+ typeof fontWeightRaw === 'number'
75
+ ? fontWeightRaw.toString()
76
+ : fontWeightRaw
77
+ const fontSize = getVariableByName('progressBadge/fontSize', modes) ?? 14
78
+ const lineHeight = getVariableByName('progressBadge/lineHeight', modes) ?? 17
79
+ const textColor =
80
+ getVariableByName('progressBadge/foreground', modes) || '#000000'
81
+
82
+ // Resolve icon tokens
83
+ const iconColor =
84
+ getVariableByName('progressBadge/icon/color', modes) || '#f50030'
85
+ const iconSize = getVariableByName('progressBadge/icon/size', modes) ?? 18
86
+
87
+ const containerStyle: ViewStyle = {
88
+ backgroundColor,
89
+ flexDirection: 'row',
90
+ alignItems: 'center',
91
+ gap,
92
+ paddingHorizontal,
93
+ paddingVertical,
94
+ borderRadius,
95
+ overflow: 'hidden',
96
+ position: 'relative',
97
+ flexWrap: 'wrap',
98
+ }
99
+
100
+ const descriptionContainerStyle: ViewStyle = {
101
+ flexDirection: 'row',
102
+ alignItems: 'center',
103
+ justifyContent: 'center',
104
+ gap: descriptionGap,
105
+ flex: 1,
106
+ paddingVertical: 0,
107
+ minHeight: 1,
108
+ minWidth: 1,
109
+ position: 'relative',
110
+ }
111
+
112
+ const textStyle: TextStyle = {
113
+ fontFamily,
114
+ fontWeight,
115
+ fontSize,
116
+ lineHeight,
117
+ color: textColor,
118
+ textAlign: 'center',
119
+ overflow: 'hidden',
120
+ flexShrink: 0,
121
+ }
122
+
123
+ // Ensure value is between 0 and 100
124
+ const clampedValue = Math.max(0, Math.min(100, value))
125
+
126
+ const progressStyle: ViewStyle = {
127
+ position: 'absolute',
128
+ left: 0,
129
+ top: 0,
130
+ bottom: 0,
131
+ width: `${clampedValue}%`,
132
+ backgroundColor: progressColor,
133
+ }
134
+
135
+ const defaultAccessibilityLabel = accessibilityLabel || taskName
136
+
137
+ return (
138
+ <View
139
+ style={[containerStyle, style]}
140
+ accessibilityRole="text"
141
+ accessibilityLabel={defaultAccessibilityLabel}
142
+ {...rest}
143
+ >
144
+ {/* Background Progress Bar */}
145
+ <View style={progressStyle} pointerEvents="none" />
146
+
147
+ {/* Description Header */}
148
+ <View style={descriptionContainerStyle}>
149
+ {iconName ? (
150
+ <Icon
151
+ name={iconName}
152
+ size={iconSize}
153
+ color={iconColor}
154
+ accessibilityElementsHidden={true}
155
+ importantForAccessibility="no"
156
+ />
157
+ ) : null}
158
+ <Text
159
+ style={[textStyle, textStyleOverride]}
160
+ numberOfLines={1}
161
+ ellipsizeMode="tail"
162
+ accessibilityElementsHidden={true}
163
+ importantForAccessibility="no"
164
+ >
165
+ {taskName}
166
+ </Text>
167
+ </View>
168
+ </View>
169
+ )
170
+ }
171
+
172
+ export default ProgressBadge
@@ -0,0 +1,2 @@
1
+ export { default } from './ProgressBadge'
2
+ export * from './ProgressBadge'
@@ -0,0 +1,71 @@
1
+ import React from 'react'
2
+ import { View, Text, type StyleProp, type ViewStyle } from 'react-native'
3
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
4
+
5
+ export type StatItemProps = {
6
+ /** The small descriptive label above the value. */
7
+ label?: string
8
+ /** The large prominent value to display. */
9
+ value?: string
10
+ /** Design token modes for theming. */
11
+ modes?: Record<string, any>
12
+ /** Override container styles. */
13
+ style?: StyleProp<ViewStyle>
14
+ }
15
+
16
+ /**
17
+ * StatItem displays a label/value pair with the value in a large, bold style.
18
+ * Useful for metrics, product stats, or KPI callouts.
19
+ *
20
+ * @component
21
+ * @param {StatItemProps} props
22
+ */
23
+ function StatItem({
24
+ label = 'Purity verified by NABL',
25
+ value = '99.99%',
26
+ modes = {},
27
+ style,
28
+ }: StatItemProps) {
29
+ const gap = getVariableByName('statItem/gap', modes) ?? 2
30
+
31
+ const labelForeground = getVariableByName('statItem/label/foreground', modes) ?? '#1a1c1f'
32
+ const labelFontFamily = getVariableByName('statItem/label/fontFamily', modes) ?? 'JioType Var'
33
+ const labelFontSize = getVariableByName('statItem/label/fontSize', modes) ?? 12
34
+ const labelFontWeight = getVariableByName('statItem/label/fontWeight', modes) ?? '500'
35
+ const labelLineHeight = getVariableByName('statItem/label/lineHeight', modes) ?? 16
36
+
37
+ const valueForeground = getVariableByName('statItem/value/foreground', modes) ?? '#0d0d0f'
38
+ const valueFontFamily = getVariableByName('statItem/value/fontFamily', modes) ?? 'JioType Var'
39
+ const valueFontSize = getVariableByName('statItem/value/fontSize', modes) ?? 26
40
+ const valueFontWeight = getVariableByName('statItem/value/fontWeight', modes) ?? '900'
41
+ const valueLineHeight = getVariableByName('statItem/value/lineHeight', modes) ?? 26
42
+
43
+ return (
44
+ <View style={[{ gap: gap as number }, style]}>
45
+ <Text
46
+ style={{
47
+ color: labelForeground as string,
48
+ fontFamily: labelFontFamily as string,
49
+ fontSize: labelFontSize as number,
50
+ fontWeight: String(labelFontWeight) as any,
51
+ lineHeight: labelLineHeight as number,
52
+ }}
53
+ >
54
+ {label}
55
+ </Text>
56
+ <Text
57
+ style={{
58
+ color: valueForeground as string,
59
+ fontFamily: valueFontFamily as string,
60
+ fontSize: valueFontSize as number,
61
+ fontWeight: String(valueFontWeight) as any,
62
+ lineHeight: valueLineHeight as number,
63
+ }}
64
+ >
65
+ {value}
66
+ </Text>
67
+ </View>
68
+ )
69
+ }
70
+
71
+ export default StatItem