jfs-components 0.1.25 → 0.1.30
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/CHANGELOG.md +53 -0
- package/lib/commonjs/components/AutoplayControl/AutoplayControl.js +66 -0
- package/lib/commonjs/components/Carousel/Carousel.js +112 -19
- package/lib/commonjs/components/CompareTable/CompareTable.js +191 -25
- package/lib/commonjs/components/ContentSheet/ContentSheet.js +104 -10
- package/lib/commonjs/components/FormUpload/Additem.js +168 -0
- package/lib/commonjs/components/FormUpload/FormUpload.js +198 -0
- package/lib/commonjs/components/Grid/Grid.js +206 -0
- package/lib/commonjs/components/MoneyValue/MoneyValue.js +179 -54
- package/lib/commonjs/components/NumberPagination/NumberPagination.js +144 -0
- package/lib/commonjs/components/PdpCcCard/PdpCcCard.js +7 -1
- package/lib/commonjs/components/ProfileCard/ProfileCard.js +208 -0
- package/lib/commonjs/components/Table/Table.js +27 -7
- package/lib/commonjs/components/UpiHandle/UpiHandle.js +23 -11
- package/lib/commonjs/components/index.js +42 -0
- package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/commonjs/design-tokens/figma-modes.generated.js +21 -10
- package/lib/commonjs/icons/components/IconBrokenimage.js +19 -0
- package/lib/commonjs/icons/components/IconImage.js +19 -0
- package/lib/commonjs/icons/components/IconMic.js +19 -0
- package/lib/commonjs/icons/components/IconMicoff.js +19 -0
- package/lib/commonjs/icons/components/IconPause.js +19 -0
- package/lib/commonjs/icons/components/IconPlay.js +19 -0
- package/lib/commonjs/icons/components/index.js +66 -0
- package/lib/commonjs/icons/registry.js +2 -2
- package/lib/module/components/AutoplayControl/AutoplayControl.js +60 -0
- package/lib/module/components/Carousel/Carousel.js +111 -19
- package/lib/module/components/CompareTable/CompareTable.js +191 -26
- package/lib/module/components/ContentSheet/ContentSheet.js +108 -14
- package/lib/module/components/FormUpload/Additem.js +162 -0
- package/lib/module/components/FormUpload/FormUpload.js +192 -0
- package/lib/module/components/Grid/Grid.js +201 -0
- package/lib/module/components/MoneyValue/MoneyValue.js +182 -57
- package/lib/module/components/NumberPagination/NumberPagination.js +138 -0
- package/lib/module/components/PdpCcCard/PdpCcCard.js +7 -1
- package/lib/module/components/ProfileCard/ProfileCard.js +202 -0
- package/lib/module/components/Table/Table.js +27 -7
- package/lib/module/components/UpiHandle/UpiHandle.js +23 -11
- package/lib/module/components/index.js +7 -1
- package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/module/design-tokens/figma-modes.generated.js +21 -10
- package/lib/module/icons/components/IconBrokenimage.js +12 -0
- package/lib/module/icons/components/IconImage.js +12 -0
- package/lib/module/icons/components/IconMic.js +12 -0
- package/lib/module/icons/components/IconMicoff.js +12 -0
- package/lib/module/icons/components/IconPause.js +12 -0
- package/lib/module/icons/components/IconPlay.js +12 -0
- package/lib/module/icons/components/index.js +6 -0
- package/lib/module/icons/registry.js +2 -2
- package/lib/typescript/src/components/AutoplayControl/AutoplayControl.d.ts +15 -0
- package/lib/typescript/src/components/Carousel/Carousel.d.ts +8 -1
- package/lib/typescript/src/components/CompareTable/CompareTable.d.ts +21 -1
- package/lib/typescript/src/components/ContentSheet/ContentSheet.d.ts +20 -1
- package/lib/typescript/src/components/FormUpload/Additem.d.ts +96 -0
- package/lib/typescript/src/components/FormUpload/FormUpload.d.ts +85 -0
- package/lib/typescript/src/components/Grid/Grid.d.ts +87 -0
- package/lib/typescript/src/components/MoneyValue/MoneyValue.d.ts +10 -1
- package/lib/typescript/src/components/NumberPagination/NumberPagination.d.ts +21 -0
- package/lib/typescript/src/components/PdpCcCard/PdpCcCard.d.ts +25 -1
- package/lib/typescript/src/components/ProfileCard/ProfileCard.d.ts +89 -0
- package/lib/typescript/src/components/Table/Table.d.ts +9 -1
- package/lib/typescript/src/components/UpiHandle/UpiHandle.d.ts +7 -1
- package/lib/typescript/src/components/index.d.ts +34 -28
- package/lib/typescript/src/design-tokens/figma-modes.generated.d.ts +10 -4
- package/lib/typescript/src/icons/components/IconBrokenimage.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconImage.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconMic.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconMicoff.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconPause.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconPlay.d.ts +3 -0
- package/lib/typescript/src/icons/components/index.d.ts +6 -0
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/AutoplayControl/AutoplayControl.tsx +77 -0
- package/src/components/Carousel/Carousel.tsx +133 -23
- package/src/components/CompareTable/CompareTable.tsx +224 -30
- package/src/components/ContentSheet/ContentSheet.tsx +138 -8
- package/src/components/FormUpload/Additem.tsx +262 -0
- package/src/components/FormUpload/FormUpload.tsx +313 -0
- package/src/components/Grid/Grid.tsx +368 -0
- package/src/components/MoneyValue/MoneyValue.tsx +216 -65
- package/src/components/NumberPagination/NumberPagination.tsx +170 -0
- package/src/components/PdpCcCard/PdpCcCard.tsx +36 -1
- package/src/components/ProfileCard/ProfileCard.tsx +268 -0
- package/src/components/Table/Table.tsx +29 -4
- package/src/components/UpiHandle/UpiHandle.tsx +35 -24
- package/src/components/index.ts +329 -182
- package/src/design-tokens/Coin Variables-variables-full.json +1 -1
- package/src/design-tokens/figma-modes.generated.ts +21 -10
- package/src/icons/components/IconBrokenimage.tsx +11 -0
- package/src/icons/components/IconImage.tsx +11 -0
- package/src/icons/components/IconMic.tsx +11 -0
- package/src/icons/components/IconMicoff.tsx +11 -0
- package/src/icons/components/IconPause.tsx +11 -0
- package/src/icons/components/IconPlay.tsx +11 -0
- package/src/icons/components/index.ts +6 -0
- package/src/icons/registry.ts +25 -1
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import React, { useMemo, useEffect, useRef } from 'react'
|
|
1
|
+
import React, { useMemo, useEffect, useRef, useState, useCallback } from 'react'
|
|
2
2
|
import {
|
|
3
3
|
View,
|
|
4
4
|
Text,
|
|
5
|
+
TextInput,
|
|
5
6
|
Pressable,
|
|
6
7
|
Animated,
|
|
8
|
+
Platform,
|
|
9
|
+
Keyboard,
|
|
10
|
+
type GestureResponderEvent,
|
|
11
|
+
type LayoutChangeEvent,
|
|
7
12
|
type StyleProp,
|
|
8
13
|
type ViewStyle,
|
|
9
14
|
type TextStyle,
|
|
@@ -58,6 +63,15 @@ export type MoneyValueProps = {
|
|
|
58
63
|
hidden?: boolean
|
|
59
64
|
/** When true, a blinking vertical cursor is shown at the end of the value text. */
|
|
60
65
|
focused?: boolean
|
|
66
|
+
/**
|
|
67
|
+
* When true, tapping the component enters an inline edit mode where the value
|
|
68
|
+
* becomes a numeric TextInput. The user can type digits or delete the existing
|
|
69
|
+
* amount. Only numeric characters (including one decimal point) are accepted.
|
|
70
|
+
* On blur or submit the edit is committed and the display restores.
|
|
71
|
+
*/
|
|
72
|
+
editable?: boolean
|
|
73
|
+
/** Called when the value changes during editing (after commit on blur). */
|
|
74
|
+
onValueChange?: (newValue: string | number) => void
|
|
61
75
|
/** Modes configuration mapped to Figma tokens. */
|
|
62
76
|
modes?: Modes
|
|
63
77
|
style?: StyleProp<ViewStyle>
|
|
@@ -85,6 +99,8 @@ function MoneyValue({
|
|
|
85
99
|
negative,
|
|
86
100
|
focused = false,
|
|
87
101
|
hidden = false,
|
|
102
|
+
editable = false,
|
|
103
|
+
onValueChange,
|
|
88
104
|
modes = EMPTY_MODES,
|
|
89
105
|
style,
|
|
90
106
|
valueStyle,
|
|
@@ -96,23 +112,60 @@ function MoneyValue({
|
|
|
96
112
|
...rest
|
|
97
113
|
}: MoneyValueProps) {
|
|
98
114
|
// Auto-detect negative from value and compute display value
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
trimmed.startsWith('-') || (typeof value === 'number' && value < 0)
|
|
115
|
+
const stringValue = String(value)
|
|
116
|
+
const trimmed = stringValue.trim()
|
|
117
|
+
const valueIsNegative =
|
|
118
|
+
trimmed.startsWith('-') || (typeof value === 'number' && value < 0)
|
|
104
119
|
|
|
105
|
-
|
|
106
|
-
|
|
120
|
+
// Strip leading minus sign for display (we show it separately)
|
|
121
|
+
const absoluteValue = trimmed.startsWith('-') ? trimmed.slice(1) : trimmed
|
|
107
122
|
|
|
108
|
-
|
|
109
|
-
|
|
123
|
+
// Use explicit negative prop if provided, otherwise use auto-detected
|
|
124
|
+
const showNegative = negative !== undefined ? negative : valueIsNegative
|
|
110
125
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
126
|
+
const isNegative = hidden ? false : showNegative
|
|
127
|
+
const displayValue = hidden ? '•••' : absoluteValue
|
|
128
|
+
|
|
129
|
+
// Inline editing state
|
|
130
|
+
const [isEditing, setIsEditing] = useState(false)
|
|
131
|
+
const [editBuffer, setEditBuffer] = useState('')
|
|
132
|
+
const inputRef = useRef<TextInput>(null)
|
|
133
|
+
// Always-current snapshot of the display value for callbacks to reference.
|
|
134
|
+
const displayValueRef = useRef(displayValue)
|
|
135
|
+
displayValueRef.current = displayValue
|
|
136
|
+
|
|
137
|
+
const enterEditMode = useCallback(() => {
|
|
138
|
+
setIsEditing(true)
|
|
139
|
+
setEditBuffer(displayValueRef.current)
|
|
140
|
+
// Focus the input on next frame so the keyboard opens
|
|
141
|
+
setTimeout(() => inputRef.current?.focus(), 0)
|
|
142
|
+
}, [])
|
|
143
|
+
|
|
144
|
+
const commitEdit = useCallback(() => {
|
|
145
|
+
setIsEditing(false)
|
|
146
|
+
const resolved = editBuffer || '0'
|
|
147
|
+
if (resolved !== displayValueRef.current) {
|
|
148
|
+
onValueChange?.(resolved)
|
|
149
|
+
}
|
|
150
|
+
}, [editBuffer, onValueChange])
|
|
151
|
+
|
|
152
|
+
const handleChangeText = useCallback(
|
|
153
|
+
(text: string) => {
|
|
154
|
+
// Allow only digits and at most one decimal point
|
|
155
|
+
const filtered = text.replace(/[^0-9.]/g, '')
|
|
156
|
+
const parts = filtered.split('.')
|
|
157
|
+
const clean = parts.length > 2 ? parts[0] + '.' + parts.slice(1).join('') : filtered
|
|
158
|
+
setEditBuffer(clean)
|
|
159
|
+
},
|
|
160
|
+
[],
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
const handlePress = useCallback((event: GestureResponderEvent) => {
|
|
164
|
+
if (editable) {
|
|
165
|
+
enterEditMode()
|
|
114
166
|
}
|
|
115
|
-
|
|
167
|
+
onPress?.(event)
|
|
168
|
+
}, [editable, enterEditMode, onPress])
|
|
116
169
|
|
|
117
170
|
// Resolve typography and layout tokens from Figma
|
|
118
171
|
const textColor = getVariableByName('moneyValue/text/color', modes)
|
|
@@ -191,64 +244,162 @@ function MoneyValue({
|
|
|
191
244
|
}
|
|
192
245
|
}, [focused, cursorOpacity])
|
|
193
246
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
>
|
|
218
|
-
{resolvedCurrency}
|
|
219
|
-
</Text>
|
|
220
|
-
|
|
221
|
-
{/* Group value and cursor in their own view to bypass the parent's generic gap constraint */}
|
|
222
|
-
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
223
|
-
<Text
|
|
224
|
-
style={[valueTextStyle, valueStyle]}
|
|
225
|
-
accessibilityElementsHidden={true}
|
|
226
|
-
importantForAccessibility="no"
|
|
227
|
-
>
|
|
228
|
-
{displayValue}
|
|
229
|
-
</Text>
|
|
230
|
-
{focused && (
|
|
231
|
-
<Animated.View
|
|
232
|
-
style={{
|
|
233
|
-
opacity: cursorOpacity,
|
|
234
|
-
width: 2,
|
|
235
|
-
height: (valueFontSize as number) * 1.1,
|
|
236
|
-
backgroundColor: textColor,
|
|
237
|
-
marginLeft: 2,
|
|
238
|
-
}}
|
|
239
|
-
/>
|
|
240
|
-
)}
|
|
241
|
-
</View>
|
|
242
|
-
</Pressable>
|
|
247
|
+
// Keyboard avoidance — lift the component when the keyboard would cover it.
|
|
248
|
+
// Follows the same pattern as ActionFooter (uses Keyboard.addListener).
|
|
249
|
+
// Resets when editing ends or the keyboard hides.
|
|
250
|
+
const rootRef = useRef<View>(null)
|
|
251
|
+
const keyboardLift = useRef(new Animated.Value(0)).current
|
|
252
|
+
const rootWindowYRef = useRef(0)
|
|
253
|
+
const rootMeasuredHeightRef = useRef(0)
|
|
254
|
+
|
|
255
|
+
const measureRoot = useCallback(() => {
|
|
256
|
+
rootRef.current?.measureInWindow((_x, y, _w, h) => {
|
|
257
|
+
rootWindowYRef.current = y
|
|
258
|
+
rootMeasuredHeightRef.current = h
|
|
259
|
+
})
|
|
260
|
+
}, [])
|
|
261
|
+
|
|
262
|
+
const handleRootLayout = useCallback(
|
|
263
|
+
(_e: LayoutChangeEvent) => {
|
|
264
|
+
// measureInWindow gives the component's position relative to the window,
|
|
265
|
+
// which is what we need for keyboard overlap calculation. onLayout fires
|
|
266
|
+
// after the component mounts and whenever its size changes.
|
|
267
|
+
measureRoot()
|
|
268
|
+
},
|
|
269
|
+
[measureRoot],
|
|
243
270
|
)
|
|
244
|
-
}
|
|
245
271
|
|
|
246
|
-
|
|
272
|
+
useEffect(() => {
|
|
273
|
+
if (!editable) return
|
|
274
|
+
|
|
275
|
+
const showSub = Keyboard.addListener('keyboardDidShow', (e) => {
|
|
276
|
+
measureRoot()
|
|
277
|
+
if (!isEditing) return
|
|
247
278
|
|
|
279
|
+
const kbHeight = e.endCoordinates.height
|
|
280
|
+
const { height: screenH } = require('react-native').Dimensions.get('window')
|
|
281
|
+
const componentBottom = rootWindowYRef.current + rootMeasuredHeightRef.current
|
|
282
|
+
const visibleBottom = screenH - kbHeight
|
|
283
|
+
const overlap = componentBottom - visibleBottom + 12
|
|
248
284
|
|
|
285
|
+
if (overlap > 0) {
|
|
286
|
+
Animated.timing(keyboardLift, {
|
|
287
|
+
toValue: -overlap,
|
|
288
|
+
duration: e.duration ?? 250,
|
|
289
|
+
useNativeDriver: true,
|
|
290
|
+
}).start()
|
|
291
|
+
}
|
|
292
|
+
})
|
|
249
293
|
|
|
294
|
+
const hideSub = Keyboard.addListener('keyboardDidHide', () => {
|
|
295
|
+
Animated.timing(keyboardLift, {
|
|
296
|
+
toValue: 0,
|
|
297
|
+
duration: 150,
|
|
298
|
+
useNativeDriver: true,
|
|
299
|
+
}).start()
|
|
300
|
+
})
|
|
250
301
|
|
|
302
|
+
return () => {
|
|
303
|
+
showSub.remove()
|
|
304
|
+
hideSub.remove()
|
|
305
|
+
}
|
|
306
|
+
}, [editable, isEditing, keyboardLift, measureRoot])
|
|
307
|
+
|
|
308
|
+
// Reset lift when editing ends
|
|
309
|
+
useEffect(() => {
|
|
310
|
+
if (!isEditing) {
|
|
311
|
+
keyboardLift.setValue(0)
|
|
312
|
+
}
|
|
313
|
+
}, [isEditing, keyboardLift])
|
|
251
314
|
|
|
315
|
+
const handleBlur = useCallback(() => {
|
|
316
|
+
commitEdit()
|
|
317
|
+
}, [commitEdit])
|
|
252
318
|
|
|
319
|
+
const handleSubmitEditing = useCallback(() => {
|
|
320
|
+
commitEdit()
|
|
321
|
+
}, [commitEdit])
|
|
253
322
|
|
|
323
|
+
return (
|
|
324
|
+
<Animated.View style={{ transform: [{ translateY: keyboardLift }] }}>
|
|
325
|
+
<View ref={rootRef} onLayout={handleRootLayout}>
|
|
326
|
+
<Pressable
|
|
327
|
+
style={[containerStyle, style]}
|
|
328
|
+
accessibilityRole="text"
|
|
329
|
+
accessibilityLabel={undefined}
|
|
330
|
+
accessibilityHint={accessibilityHint}
|
|
331
|
+
onPress={handlePress}
|
|
332
|
+
disabled={!onPress && !editable}
|
|
333
|
+
{...rest}
|
|
334
|
+
>
|
|
335
|
+
{isNegative && (
|
|
336
|
+
<Text
|
|
337
|
+
style={[currencyTextStyle, negativeSignStyle]}
|
|
338
|
+
accessibilityElementsHidden={true}
|
|
339
|
+
importantForAccessibility="no"
|
|
340
|
+
>
|
|
341
|
+
-
|
|
342
|
+
</Text>
|
|
343
|
+
)}
|
|
344
|
+
<Text
|
|
345
|
+
style={[currencyTextStyle, currencyStyle]}
|
|
346
|
+
accessibilityElementsHidden={true}
|
|
347
|
+
importantForAccessibility="no"
|
|
348
|
+
>
|
|
349
|
+
{resolvedCurrency}
|
|
350
|
+
</Text>
|
|
351
|
+
|
|
352
|
+
{/* Group value and cursor in their own view to bypass the parent's generic gap constraint */}
|
|
353
|
+
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
354
|
+
{isEditing ? (
|
|
355
|
+
<TextInput
|
|
356
|
+
ref={inputRef}
|
|
357
|
+
value={editBuffer}
|
|
358
|
+
onChangeText={handleChangeText}
|
|
359
|
+
onBlur={handleBlur}
|
|
360
|
+
onSubmitEditing={handleSubmitEditing}
|
|
361
|
+
keyboardType="decimal-pad"
|
|
362
|
+
returnKeyType="done"
|
|
363
|
+
selectTextOnFocus
|
|
364
|
+
style={[
|
|
365
|
+
valueTextStyle,
|
|
366
|
+
valueStyle,
|
|
367
|
+
{
|
|
368
|
+
padding: 0,
|
|
369
|
+
margin: 0,
|
|
370
|
+
// On web a thin border helps the cursor appear precisely
|
|
371
|
+
...(Platform.OS === 'web' ? { outlineStyle: 'none' as any } : {}),
|
|
372
|
+
},
|
|
373
|
+
]}
|
|
374
|
+
accessibilityLabel={accessibilityLabel || 'Edit value'}
|
|
375
|
+
/>
|
|
376
|
+
) : (
|
|
377
|
+
<>
|
|
378
|
+
<Text
|
|
379
|
+
style={[valueTextStyle, valueStyle]}
|
|
380
|
+
accessibilityElementsHidden={true}
|
|
381
|
+
importantForAccessibility="no"
|
|
382
|
+
>
|
|
383
|
+
{displayValue}
|
|
384
|
+
</Text>
|
|
385
|
+
{focused && (
|
|
386
|
+
<Animated.View
|
|
387
|
+
style={{
|
|
388
|
+
opacity: cursorOpacity,
|
|
389
|
+
width: 2,
|
|
390
|
+
height: (valueFontSize as number) * 1.1,
|
|
391
|
+
backgroundColor: textColor,
|
|
392
|
+
marginLeft: 2,
|
|
393
|
+
}}
|
|
394
|
+
/>
|
|
395
|
+
)}
|
|
396
|
+
</>
|
|
397
|
+
)}
|
|
398
|
+
</View>
|
|
399
|
+
</Pressable>
|
|
400
|
+
</View>
|
|
401
|
+
</Animated.View>
|
|
402
|
+
)
|
|
403
|
+
}
|
|
254
404
|
|
|
405
|
+
export default MoneyValue
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import React, { useMemo } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Pressable,
|
|
4
|
+
Text,
|
|
5
|
+
View,
|
|
6
|
+
type StyleProp,
|
|
7
|
+
type TextStyle,
|
|
8
|
+
type ViewStyle,
|
|
9
|
+
} from 'react-native'
|
|
10
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
11
|
+
import GlassFill from '../../utils/GlassFill/GlassFill'
|
|
12
|
+
import type { Modes } from '../../design-tokens'
|
|
13
|
+
|
|
14
|
+
export type NumberPaginationProps = {
|
|
15
|
+
/** The 1-based active page index. Ignored if `children` is supplied. Defaults to 1. */
|
|
16
|
+
activePage?: number;
|
|
17
|
+
/** The total number of pages. Ignored if `children` is supplied. Defaults to 1. */
|
|
18
|
+
totalPages?: number;
|
|
19
|
+
/** Callback fired when a page button is pressed. Ignored if `children` is supplied. */
|
|
20
|
+
onPageChange?: (page: number) => void;
|
|
21
|
+
/** Custom children to override the default page buttons. */
|
|
22
|
+
children?: React.ReactNode;
|
|
23
|
+
modes?: Modes;
|
|
24
|
+
style?: StyleProp<ViewStyle>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface NumberPaginationTokens {
|
|
28
|
+
containerStyle: ViewStyle;
|
|
29
|
+
activeItemStyle: ViewStyle;
|
|
30
|
+
inactiveItemStyle: ViewStyle;
|
|
31
|
+
activeTextStyle: TextStyle;
|
|
32
|
+
inactiveTextStyle: TextStyle;
|
|
33
|
+
blurIntensity: number;
|
|
34
|
+
backgroundColor: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function resolveNumberPaginationTokens(modes: Modes): NumberPaginationTokens {
|
|
38
|
+
const horizontalPadding = (getVariableByName('numberPagination/padding/horizontal', modes) ?? 4) as number
|
|
39
|
+
const verticalPadding = (getVariableByName('numberPagination/padding/vertical', modes) ?? 4) as number
|
|
40
|
+
const radiusRaw = (getVariableByName('numberPagination/radius', modes) ?? 9999) as number
|
|
41
|
+
const backgroundColor = (getVariableByName('numberPagination/background/color', modes) ?? 'rgba(141, 141, 141, 0.4)') as string
|
|
42
|
+
const borderColor = (getVariableByName('numberPagination/border/color', modes) ?? 'rgba(255, 255, 255, 0.15)') as string
|
|
43
|
+
|
|
44
|
+
// control number variables
|
|
45
|
+
const itemWidth = (getVariableByName('controlNumber/width', modes) ?? 32) as number
|
|
46
|
+
const itemHeight = (getVariableByName('controlNumber/height', modes) ?? 32) as number
|
|
47
|
+
const itemRadiusRaw = (getVariableByName('controlNumber/radius', modes) ?? 9999) as number
|
|
48
|
+
const activeBgColor = (getVariableByName('control/active/background/color', modes) ?? '#ffffff') as string
|
|
49
|
+
const activeTextColor = (getVariableByName('control/active/text/color', modes) ?? '#000000') as string
|
|
50
|
+
const inactiveTextColor = (getVariableByName('control/text/default', modes) ?? '#ffffff') as string
|
|
51
|
+
|
|
52
|
+
// typography variables
|
|
53
|
+
const fontFamily = (getVariableByName('numberPagination/fontFamily', modes) ?? 'JioType') as string
|
|
54
|
+
const fontSize = (getVariableByName('numberPagination/fontSize', modes) ?? 16) as number
|
|
55
|
+
const fontWeight = String(getVariableByName('numberPagination/fontWeight', modes) ?? '500') as TextStyle['fontWeight']
|
|
56
|
+
const lineHeight = (getVariableByName('numberPagination/lineHeight', modes) ?? 18) as number
|
|
57
|
+
|
|
58
|
+
const blurMinimal = (getVariableByName('blur/minimal', modes) ?? 29) as number
|
|
59
|
+
const blurIntensity = Math.max(0, Math.min(100, Math.round(Number(blurMinimal))))
|
|
60
|
+
|
|
61
|
+
const borderRadius = radiusRaw === 9999 ? 39 / 2 : radiusRaw
|
|
62
|
+
const itemRadius = itemRadiusRaw === 9999 ? itemWidth / 2 : itemRadiusRaw
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
containerStyle: {
|
|
66
|
+
height: 39,
|
|
67
|
+
borderRadius,
|
|
68
|
+
borderWidth: 1,
|
|
69
|
+
borderColor,
|
|
70
|
+
backgroundColor: 'transparent',
|
|
71
|
+
overflow: 'hidden',
|
|
72
|
+
flexDirection: 'row',
|
|
73
|
+
alignItems: 'center',
|
|
74
|
+
paddingHorizontal: horizontalPadding,
|
|
75
|
+
paddingVertical: verticalPadding,
|
|
76
|
+
},
|
|
77
|
+
activeItemStyle: {
|
|
78
|
+
width: itemWidth,
|
|
79
|
+
height: itemHeight,
|
|
80
|
+
borderRadius: itemRadius,
|
|
81
|
+
backgroundColor: activeBgColor,
|
|
82
|
+
alignItems: 'center',
|
|
83
|
+
justifyContent: 'center',
|
|
84
|
+
},
|
|
85
|
+
inactiveItemStyle: {
|
|
86
|
+
width: itemWidth,
|
|
87
|
+
height: itemHeight,
|
|
88
|
+
borderRadius: itemRadius,
|
|
89
|
+
backgroundColor: 'transparent',
|
|
90
|
+
alignItems: 'center',
|
|
91
|
+
justifyContent: 'center',
|
|
92
|
+
},
|
|
93
|
+
activeTextStyle: {
|
|
94
|
+
fontFamily,
|
|
95
|
+
fontSize,
|
|
96
|
+
fontWeight,
|
|
97
|
+
lineHeight,
|
|
98
|
+
color: activeTextColor,
|
|
99
|
+
textAlign: 'center',
|
|
100
|
+
},
|
|
101
|
+
inactiveTextStyle: {
|
|
102
|
+
fontFamily,
|
|
103
|
+
fontSize,
|
|
104
|
+
fontWeight,
|
|
105
|
+
lineHeight,
|
|
106
|
+
color: inactiveTextColor,
|
|
107
|
+
textAlign: 'center',
|
|
108
|
+
},
|
|
109
|
+
blurIntensity,
|
|
110
|
+
backgroundColor,
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* NumberPagination component displays a horizontal list of page numbers on a frosted glass surface.
|
|
116
|
+
* All styling is driven by Figma design tokens with support for custom children slot.
|
|
117
|
+
*/
|
|
118
|
+
export default function NumberPagination({
|
|
119
|
+
activePage = 1,
|
|
120
|
+
totalPages = 1,
|
|
121
|
+
onPageChange,
|
|
122
|
+
children,
|
|
123
|
+
modes,
|
|
124
|
+
style,
|
|
125
|
+
}: NumberPaginationProps) {
|
|
126
|
+
const tokens = useMemo(() => resolveNumberPaginationTokens(modes ?? {}), [modes])
|
|
127
|
+
|
|
128
|
+
const renderPages = () => {
|
|
129
|
+
if (children) {
|
|
130
|
+
return children
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const pages = []
|
|
134
|
+
for (let i = 1; i <= totalPages; i++) {
|
|
135
|
+
const active = i === activePage
|
|
136
|
+
pages.push(
|
|
137
|
+
<Pressable
|
|
138
|
+
key={i}
|
|
139
|
+
onPress={() => onPageChange?.(i)}
|
|
140
|
+
style={({ pressed }) => [
|
|
141
|
+
active ? tokens.activeItemStyle : tokens.inactiveItemStyle,
|
|
142
|
+
pressed && { opacity: 0.7 },
|
|
143
|
+
]}
|
|
144
|
+
accessibilityRole="button"
|
|
145
|
+
accessibilityState={{ selected: active }}
|
|
146
|
+
accessibilityLabel={`Page ${i}`}
|
|
147
|
+
>
|
|
148
|
+
<Text style={active ? tokens.activeTextStyle : tokens.inactiveTextStyle}>
|
|
149
|
+
{i}
|
|
150
|
+
</Text>
|
|
151
|
+
</Pressable>
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
return pages
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<View style={[tokens.containerStyle, style]}>
|
|
159
|
+
<GlassFill
|
|
160
|
+
tint="light"
|
|
161
|
+
intensity={tokens.blurIntensity}
|
|
162
|
+
overlayColor={tokens.backgroundColor}
|
|
163
|
+
androidTintWash={false}
|
|
164
|
+
/>
|
|
165
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', height: '100%' }}>
|
|
166
|
+
{renderPages()}
|
|
167
|
+
</View>
|
|
168
|
+
</View>
|
|
169
|
+
)
|
|
170
|
+
}
|
|
@@ -50,6 +50,30 @@ export interface PdpCcCardProps {
|
|
|
50
50
|
title?: string
|
|
51
51
|
/** Subtitle rendered below the title (14px medium). */
|
|
52
52
|
subtitle?: string
|
|
53
|
+
/**
|
|
54
|
+
* Number of lines to limit the title to. The `Title` component defaults to
|
|
55
|
+
* `1` when this is unset, so the headline is a single line with an ellipsis
|
|
56
|
+
* by default. Pass a larger value (e.g. `2`) to allow the title to wrap onto
|
|
57
|
+
* that many lines before truncating, or set {@link disableTruncation} to
|
|
58
|
+
* remove the clamp entirely. The subtitle is never line-clamped.
|
|
59
|
+
*/
|
|
60
|
+
numberOfLines?: number
|
|
61
|
+
/**
|
|
62
|
+
* When `true`, disables truncation for both the title and subtitle: the
|
|
63
|
+
* trailing ellipsis (`…`) is never shown and the `numberOfLines` clamp is
|
|
64
|
+
* ignored. The text is also never shrunk by a tight parent — it keeps its
|
|
65
|
+
* full intrinsic size and overflows (even breaking the parent layout) rather
|
|
66
|
+
* than ever clipping into an ellipsis. The text still wraps onto multiple
|
|
67
|
+
* lines.
|
|
68
|
+
*/
|
|
69
|
+
disableTruncation?: boolean
|
|
70
|
+
/**
|
|
71
|
+
* When `true`, forces both the title and subtitle onto a single line with no
|
|
72
|
+
* wrapping (implies {@link disableTruncation}). On web the line overflows
|
|
73
|
+
* horizontally — brutally breaking the parent layout if it has to — instead
|
|
74
|
+
* of wrapping or truncating.
|
|
75
|
+
*/
|
|
76
|
+
singleLine?: boolean
|
|
53
77
|
/**
|
|
54
78
|
* The metric columns rendered in the stats row. Vertical dividers are
|
|
55
79
|
* inserted automatically between adjacent metrics. Defaults to two sample
|
|
@@ -105,6 +129,9 @@ function PdpCcCard({
|
|
|
105
129
|
media,
|
|
106
130
|
title = 'Title',
|
|
107
131
|
subtitle = 'Subtitle',
|
|
132
|
+
numberOfLines,
|
|
133
|
+
disableTruncation,
|
|
134
|
+
singleLine,
|
|
108
135
|
metrics = DEFAULT_METRICS,
|
|
109
136
|
buttonLabel = 'button',
|
|
110
137
|
buttonIcon = 'ic_add_circle',
|
|
@@ -165,7 +192,15 @@ function PdpCcCard({
|
|
|
165
192
|
<>
|
|
166
193
|
<View style={styles.mediaSlot}>{mediaNode}</View>
|
|
167
194
|
|
|
168
|
-
<Title
|
|
195
|
+
<Title
|
|
196
|
+
title={title}
|
|
197
|
+
subtitle={subtitle}
|
|
198
|
+
textAlign="Center"
|
|
199
|
+
modes={modes}
|
|
200
|
+
numberOfLines={numberOfLines}
|
|
201
|
+
disableTruncation={disableTruncation}
|
|
202
|
+
singleLine={singleLine}
|
|
203
|
+
/>
|
|
169
204
|
|
|
170
205
|
{metrics.length > 0 ? (
|
|
171
206
|
<View style={styles.metricsRow}>
|