jfs-components 0.1.25 → 0.1.28

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 (81) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/lib/commonjs/components/AutoplayControl/AutoplayControl.js +66 -0
  3. package/lib/commonjs/components/Carousel/Carousel.js +112 -19
  4. package/lib/commonjs/components/ContentSheet/ContentSheet.js +39 -5
  5. package/lib/commonjs/components/FormUpload/Additem.js +168 -0
  6. package/lib/commonjs/components/FormUpload/FormUpload.js +198 -0
  7. package/lib/commonjs/components/Grid/Grid.js +206 -0
  8. package/lib/commonjs/components/NumberPagination/NumberPagination.js +144 -0
  9. package/lib/commonjs/components/ProfileCard/ProfileCard.js +208 -0
  10. package/lib/commonjs/components/UpiHandle/UpiHandle.js +23 -11
  11. package/lib/commonjs/components/index.js +42 -0
  12. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
  13. package/lib/commonjs/design-tokens/figma-modes.generated.js +21 -10
  14. package/lib/commonjs/icons/components/IconBrokenimage.js +19 -0
  15. package/lib/commonjs/icons/components/IconImage.js +19 -0
  16. package/lib/commonjs/icons/components/IconMic.js +19 -0
  17. package/lib/commonjs/icons/components/IconMicoff.js +19 -0
  18. package/lib/commonjs/icons/components/IconPause.js +19 -0
  19. package/lib/commonjs/icons/components/IconPlay.js +19 -0
  20. package/lib/commonjs/icons/components/index.js +66 -0
  21. package/lib/commonjs/icons/registry.js +2 -2
  22. package/lib/module/components/AutoplayControl/AutoplayControl.js +60 -0
  23. package/lib/module/components/Carousel/Carousel.js +111 -19
  24. package/lib/module/components/ContentSheet/ContentSheet.js +41 -7
  25. package/lib/module/components/FormUpload/Additem.js +162 -0
  26. package/lib/module/components/FormUpload/FormUpload.js +192 -0
  27. package/lib/module/components/Grid/Grid.js +201 -0
  28. package/lib/module/components/NumberPagination/NumberPagination.js +138 -0
  29. package/lib/module/components/ProfileCard/ProfileCard.js +202 -0
  30. package/lib/module/components/UpiHandle/UpiHandle.js +23 -11
  31. package/lib/module/components/index.js +7 -1
  32. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
  33. package/lib/module/design-tokens/figma-modes.generated.js +21 -10
  34. package/lib/module/icons/components/IconBrokenimage.js +12 -0
  35. package/lib/module/icons/components/IconImage.js +12 -0
  36. package/lib/module/icons/components/IconMic.js +12 -0
  37. package/lib/module/icons/components/IconMicoff.js +12 -0
  38. package/lib/module/icons/components/IconPause.js +12 -0
  39. package/lib/module/icons/components/IconPlay.js +12 -0
  40. package/lib/module/icons/components/index.js +6 -0
  41. package/lib/module/icons/registry.js +2 -2
  42. package/lib/typescript/src/components/AutoplayControl/AutoplayControl.d.ts +15 -0
  43. package/lib/typescript/src/components/Carousel/Carousel.d.ts +8 -1
  44. package/lib/typescript/src/components/ContentSheet/ContentSheet.d.ts +7 -1
  45. package/lib/typescript/src/components/FormUpload/Additem.d.ts +96 -0
  46. package/lib/typescript/src/components/FormUpload/FormUpload.d.ts +85 -0
  47. package/lib/typescript/src/components/Grid/Grid.d.ts +87 -0
  48. package/lib/typescript/src/components/NumberPagination/NumberPagination.d.ts +21 -0
  49. package/lib/typescript/src/components/ProfileCard/ProfileCard.d.ts +89 -0
  50. package/lib/typescript/src/components/UpiHandle/UpiHandle.d.ts +7 -1
  51. package/lib/typescript/src/components/index.d.ts +34 -28
  52. package/lib/typescript/src/design-tokens/figma-modes.generated.d.ts +10 -4
  53. package/lib/typescript/src/icons/components/IconBrokenimage.d.ts +3 -0
  54. package/lib/typescript/src/icons/components/IconImage.d.ts +3 -0
  55. package/lib/typescript/src/icons/components/IconMic.d.ts +3 -0
  56. package/lib/typescript/src/icons/components/IconMicoff.d.ts +3 -0
  57. package/lib/typescript/src/icons/components/IconPause.d.ts +3 -0
  58. package/lib/typescript/src/icons/components/IconPlay.d.ts +3 -0
  59. package/lib/typescript/src/icons/components/index.d.ts +6 -0
  60. package/lib/typescript/src/icons/registry.d.ts +1 -1
  61. package/package.json +1 -1
  62. package/src/components/AutoplayControl/AutoplayControl.tsx +77 -0
  63. package/src/components/Carousel/Carousel.tsx +133 -23
  64. package/src/components/ContentSheet/ContentSheet.tsx +50 -0
  65. package/src/components/FormUpload/Additem.tsx +262 -0
  66. package/src/components/FormUpload/FormUpload.tsx +313 -0
  67. package/src/components/Grid/Grid.tsx +368 -0
  68. package/src/components/NumberPagination/NumberPagination.tsx +170 -0
  69. package/src/components/ProfileCard/ProfileCard.tsx +268 -0
  70. package/src/components/UpiHandle/UpiHandle.tsx +35 -24
  71. package/src/components/index.ts +329 -182
  72. package/src/design-tokens/Coin Variables-variables-full.json +1 -1
  73. package/src/design-tokens/figma-modes.generated.ts +21 -10
  74. package/src/icons/components/IconBrokenimage.tsx +11 -0
  75. package/src/icons/components/IconImage.tsx +11 -0
  76. package/src/icons/components/IconMic.tsx +11 -0
  77. package/src/icons/components/IconMicoff.tsx +11 -0
  78. package/src/icons/components/IconPause.tsx +11 -0
  79. package/src/icons/components/IconPlay.tsx +11 -0
  80. package/src/icons/components/index.ts +6 -0
  81. package/src/icons/registry.ts +25 -1
@@ -0,0 +1,368 @@
1
+ import React, { useMemo } from 'react'
2
+ import { View, type StyleProp, type ViewStyle } from 'react-native'
3
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
4
+ import { useTokens } from '../../design-tokens/JFSThemeProvider'
5
+ import { EMPTY_MODES, cloneChildrenWithModes, flattenChildren } from '../../utils/react-utils'
6
+ import type { Modes } from '../../design-tokens'
7
+
8
+ export type GridJustifyContent = 'start' | 'stretch' | 'space-between'
9
+ export type GridAlignItems = 'start' | 'stretch' | 'center'
10
+
11
+ export type GridProps = {
12
+ /**
13
+ * Number of columns per row. Defaults to `1`. Children flow left-to-right,
14
+ * top-to-bottom into this many columns.
15
+ */
16
+ columns?: number
17
+ /**
18
+ * Fixed row count. When set, the grid always renders `columns × rows` cells.
19
+ * Missing cells render as empty placeholders so the geometry stays stable.
20
+ * When omitted, the row count is derived from the number of children.
21
+ */
22
+ rows?: number
23
+ /**
24
+ * Fractional column track weights — the React Native equivalent of CSS
25
+ * `grid-template-columns: 1fr 2fr 1fr`. Each value becomes a `flex` grow
26
+ * weight on the matching column. Shorter arrays repeat the last weight;
27
+ * omitted values default to equal `1fr` columns.
28
+ */
29
+ columnFrs?: readonly number[]
30
+ /**
31
+ * Fractional row track weights. When provided, each row container receives
32
+ * a matching `flexGrow` weight. Useful when the grid should fill a fixed
33
+ * height and rows should share space proportionally.
34
+ */
35
+ rowFrs?: readonly number[]
36
+ /** Override the resolved column gap token. */
37
+ columnGap?: number
38
+ /** Override the resolved row gap token. */
39
+ rowGap?: number
40
+ /**
41
+ * Shorthand gap override applied when `columnGap` / `rowGap` are not set
42
+ * individually. Falls back to the `Grid/gap` token.
43
+ */
44
+ gap?: number
45
+ /** Uniform padding override for all sides. */
46
+ padding?: number
47
+ paddingHorizontal?: number
48
+ paddingVertical?: number
49
+ paddingTop?: number
50
+ paddingBottom?: number
51
+ paddingLeft?: number
52
+ paddingRight?: number
53
+ /**
54
+ * How cells are distributed along each row.
55
+ * - `'stretch'` (default) — cells expand using `columnFrs`.
56
+ * - `'start'` — cells hug the leading edge; trailing space stays empty.
57
+ * - `'space-between'` — leftover space is distributed between cells
58
+ * (first cell flush-left, last cell flush-right).
59
+ */
60
+ justifyContent?: GridJustifyContent
61
+ /** Cross-axis alignment applied to every cell wrapper. Defaults to `'stretch'`. */
62
+ alignItems?: GridAlignItems
63
+ /**
64
+ * Slot content. Each child becomes one grid cell. `modes` cascade into all
65
+ * slot children automatically.
66
+ */
67
+ children?: React.ReactNode
68
+ /** Design token modes (e.g. `{ 'Slot gap': 'M' }`). */
69
+ modes?: Modes
70
+ /** Style override for the outer container. */
71
+ style?: StyleProp<ViewStyle>
72
+ /** Style override for the inner rows wrapper. */
73
+ contentStyle?: StyleProp<ViewStyle>
74
+ /** Style applied to every cell wrapper. */
75
+ cellStyle?: StyleProp<ViewStyle>
76
+ /** Accessibility label for the grid container. */
77
+ accessibilityLabel?: string
78
+ testID?: string
79
+ }
80
+
81
+ function toNumber(value: unknown, fallback: number): number {
82
+ if (typeof value === 'number' && Number.isFinite(value)) return value
83
+ if (typeof value === 'string') {
84
+ const parsed = parseFloat(value)
85
+ if (Number.isFinite(parsed)) return parsed
86
+ }
87
+ return fallback
88
+ }
89
+
90
+ interface GridTokens {
91
+ backgroundColor: string
92
+ columnGap: number
93
+ rowGap: number
94
+ paddingTop: number
95
+ paddingBottom: number
96
+ paddingLeft: number
97
+ paddingRight: number
98
+ }
99
+
100
+ function resolveGridTokens(modes: Modes): GridTokens {
101
+ const gapFallback = toNumber(getVariableByName('Grid/gap', modes), 8)
102
+ const columnGap = toNumber(getVariableByName('Grid/column/gap', modes), gapFallback)
103
+ const rowGap = toNumber(getVariableByName('Grid/row/gap', modes), gapFallback)
104
+
105
+ const paddingHorizontal = toNumber(getVariableByName('Grid/padding/horizontal', modes), 8)
106
+ const paddingVertical = toNumber(getVariableByName('Grid/padding/vertical', modes), 8)
107
+ const paddingTop = toNumber(getVariableByName('Grid/padding/top', modes), paddingVertical)
108
+ const paddingBottom = toNumber(getVariableByName('Grid/padding/bottom', modes), paddingVertical)
109
+ const paddingLeft = toNumber(getVariableByName('Grid/padding/left', modes), paddingHorizontal)
110
+ const paddingRight = toNumber(getVariableByName('Grid/padding/right', modes), paddingHorizontal)
111
+
112
+ const backgroundColor =
113
+ (getVariableByName('Grid/background/color', modes) as string | null) ?? 'transparent'
114
+
115
+ return {
116
+ backgroundColor,
117
+ columnGap,
118
+ rowGap,
119
+ paddingTop,
120
+ paddingBottom,
121
+ paddingLeft,
122
+ paddingRight,
123
+ }
124
+ }
125
+
126
+ function normalizeFrs(frs: readonly number[] | undefined, count: number): number[] {
127
+ if (!frs || frs.length === 0) {
128
+ return Array.from({ length: count }, () => 1)
129
+ }
130
+ return Array.from({ length: count }, (_, index) => {
131
+ const value = frs[index] ?? frs[frs.length - 1] ?? 1
132
+ return value > 0 ? value : 1
133
+ })
134
+ }
135
+
136
+ function resolveAlignSelf(alignItems: GridAlignItems): ViewStyle['alignSelf'] {
137
+ if (alignItems === 'center') return 'center'
138
+ if (alignItems === 'start') return 'flex-start'
139
+ return 'stretch'
140
+ }
141
+
142
+ function resolveRowJustifyContent(justifyContent: GridJustifyContent): ViewStyle['justifyContent'] {
143
+ if (justifyContent === 'space-between') return 'space-between'
144
+ if (justifyContent === 'start') return 'flex-start'
145
+ return 'flex-start'
146
+ }
147
+
148
+ function buildCellStyle(
149
+ columnFr: number,
150
+ justifyContent: GridJustifyContent,
151
+ alignItems: GridAlignItems,
152
+ ): ViewStyle {
153
+ const alignSelf = resolveAlignSelf(alignItems)
154
+
155
+ if (justifyContent === 'space-between' || justifyContent === 'start') {
156
+ return {
157
+ flexGrow: 0,
158
+ flexShrink: 1,
159
+ flexBasis: 'auto',
160
+ minWidth: 0,
161
+ alignSelf,
162
+ }
163
+ }
164
+
165
+ return {
166
+ flex: columnFr,
167
+ minWidth: 0,
168
+ alignSelf,
169
+ }
170
+ }
171
+
172
+ /**
173
+ * `Grid` — a token-driven layout grid for React Native.
174
+ *
175
+ * Mirrors the Figma "Grid" component: a padded container with configurable
176
+ * columns, rows, fractional column tracks (`columnFrs`), and gap tokens.
177
+ * Each slot child maps to one cell — no separate `GridItem` wrapper is needed.
178
+ *
179
+ * @component
180
+ */
181
+ function Grid({
182
+ columns = 1,
183
+ rows,
184
+ columnFrs,
185
+ rowFrs,
186
+ columnGap: columnGapProp,
187
+ rowGap: rowGapProp,
188
+ gap: gapProp,
189
+ padding: paddingProp,
190
+ paddingHorizontal: paddingHorizontalProp,
191
+ paddingVertical: paddingVerticalProp,
192
+ paddingTop: paddingTopProp,
193
+ paddingBottom: paddingBottomProp,
194
+ paddingLeft: paddingLeftProp,
195
+ paddingRight: paddingRightProp,
196
+ justifyContent = 'stretch',
197
+ alignItems = 'stretch',
198
+ children,
199
+ modes: propModes = EMPTY_MODES,
200
+ style,
201
+ contentStyle,
202
+ cellStyle,
203
+ accessibilityLabel,
204
+ testID,
205
+ }: GridProps) {
206
+ const { modes: globalModes } = useTokens()
207
+ const modes = useMemo(
208
+ () =>
209
+ globalModes === EMPTY_MODES && propModes === EMPTY_MODES
210
+ ? EMPTY_MODES
211
+ : { ...globalModes, ...propModes },
212
+ [globalModes, propModes],
213
+ )
214
+
215
+ const tokens = useMemo(() => resolveGridTokens(modes), [modes])
216
+
217
+ const resolvedColumnGap = columnGapProp ?? gapProp ?? tokens.columnGap
218
+ const resolvedRowGap = rowGapProp ?? gapProp ?? tokens.rowGap
219
+
220
+ const resolvedPadding = useMemo(
221
+ () => ({
222
+ paddingTop: paddingTopProp ?? paddingVerticalProp ?? paddingProp ?? tokens.paddingTop,
223
+ paddingBottom: paddingBottomProp ?? paddingVerticalProp ?? paddingProp ?? tokens.paddingBottom,
224
+ paddingLeft: paddingLeftProp ?? paddingHorizontalProp ?? paddingProp ?? tokens.paddingLeft,
225
+ paddingRight: paddingRightProp ?? paddingHorizontalProp ?? paddingProp ?? tokens.paddingRight,
226
+ }),
227
+ [
228
+ paddingTopProp,
229
+ paddingBottomProp,
230
+ paddingLeftProp,
231
+ paddingRightProp,
232
+ paddingHorizontalProp,
233
+ paddingVerticalProp,
234
+ paddingProp,
235
+ tokens,
236
+ ],
237
+ )
238
+
239
+ const safeColumns = Math.max(1, Math.floor(columns))
240
+
241
+ const processedChildren = useMemo(
242
+ () => cloneChildrenWithModes(flattenChildren(children), modes),
243
+ [children, modes],
244
+ )
245
+
246
+ const cellNodes = useMemo(() => {
247
+ const nodes = [...processedChildren]
248
+ if (rows != null) {
249
+ const targetCount = safeColumns * Math.max(1, Math.floor(rows))
250
+ while (nodes.length < targetCount) {
251
+ nodes.push(null)
252
+ }
253
+ if (nodes.length > targetCount) {
254
+ nodes.length = targetCount
255
+ }
256
+ }
257
+ return nodes
258
+ }, [processedChildren, rows, safeColumns])
259
+
260
+ const gridRows = useMemo(() => {
261
+ const result: React.ReactNode[][] = []
262
+ for (let index = 0; index < cellNodes.length; index += safeColumns) {
263
+ result.push(cellNodes.slice(index, index + safeColumns))
264
+ }
265
+ return result
266
+ }, [cellNodes, safeColumns])
267
+
268
+ const normalizedColumnFrs = useMemo(
269
+ () => normalizeFrs(columnFrs, safeColumns),
270
+ [columnFrs, safeColumns],
271
+ )
272
+
273
+ const normalizedRowFrs = useMemo(
274
+ () => (rowFrs ? normalizeFrs(rowFrs, gridRows.length) : null),
275
+ [rowFrs, gridRows.length],
276
+ )
277
+
278
+ const containerStyle = useMemo<ViewStyle>(
279
+ () => ({
280
+ backgroundColor: tokens.backgroundColor,
281
+ ...resolvedPadding,
282
+ width: '100%',
283
+ }),
284
+ [tokens.backgroundColor, resolvedPadding],
285
+ )
286
+
287
+ const rowsContainerStyle = useMemo<ViewStyle>(
288
+ () => ({
289
+ gap: resolvedRowGap,
290
+ width: '100%',
291
+ }),
292
+ [resolvedRowGap],
293
+ )
294
+
295
+ const rowStyle = useMemo<ViewStyle>(
296
+ () => ({
297
+ flexDirection: 'row',
298
+ columnGap: resolvedColumnGap,
299
+ justifyContent: resolveRowJustifyContent(justifyContent),
300
+ alignItems: alignItems === 'stretch' ? 'stretch' : alignItems === 'center' ? 'center' : 'flex-start',
301
+ width: '100%',
302
+ }),
303
+ [resolvedColumnGap, justifyContent, alignItems],
304
+ )
305
+
306
+ const defaultAccessibilityLabel =
307
+ accessibilityLabel ??
308
+ `Grid, ${safeColumns} column${safeColumns === 1 ? '' : 's'}, ${gridRows.length} row${
309
+ gridRows.length === 1 ? '' : 's'
310
+ }`
311
+
312
+ return (
313
+ <View
314
+ style={[containerStyle, style]}
315
+ accessibilityRole="none"
316
+ accessibilityLabel={defaultAccessibilityLabel}
317
+ testID={testID}
318
+ >
319
+ <View style={[rowsContainerStyle, contentStyle]}>
320
+ {gridRows.map((row, rowIndex) => {
321
+ const rowFr = normalizedRowFrs?.[rowIndex]
322
+ const spacersNeeded =
323
+ justifyContent === 'space-between' && row.length < safeColumns
324
+ ? safeColumns - row.length
325
+ : 0
326
+
327
+ return (
328
+ <View
329
+ key={`grid-row-${rowIndex}`}
330
+ style={rowFr != null ? [rowStyle, { flexGrow: rowFr }] : rowStyle}
331
+ >
332
+ {row.map((cell, columnIndex) => {
333
+ const absoluteIndex = rowIndex * safeColumns + columnIndex
334
+ const cellFlexStyle = buildCellStyle(
335
+ normalizedColumnFrs[columnIndex] ?? 1,
336
+ justifyContent,
337
+ alignItems,
338
+ )
339
+
340
+ return (
341
+ <View
342
+ key={`grid-cell-${absoluteIndex}`}
343
+ style={[cellFlexStyle, cellStyle]}
344
+ >
345
+ {cell}
346
+ </View>
347
+ )
348
+ })}
349
+ {spacersNeeded > 0 &&
350
+ Array.from({ length: spacersNeeded }, (_, spacerIndex) => (
351
+ <View
352
+ key={`grid-spacer-${rowIndex}-${spacerIndex}`}
353
+ style={buildCellStyle(
354
+ normalizedColumnFrs[row.length + spacerIndex] ?? 1,
355
+ justifyContent,
356
+ alignItems,
357
+ )}
358
+ />
359
+ ))}
360
+ </View>
361
+ )
362
+ })}
363
+ </View>
364
+ </View>
365
+ )
366
+ }
367
+
368
+ export default React.memo(Grid)
@@ -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
+ }