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.
- package/CHANGELOG.md +40 -0
- package/lib/commonjs/components/AutoplayControl/AutoplayControl.js +66 -0
- package/lib/commonjs/components/Carousel/Carousel.js +112 -19
- package/lib/commonjs/components/ContentSheet/ContentSheet.js +39 -5
- 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/NumberPagination/NumberPagination.js +144 -0
- package/lib/commonjs/components/ProfileCard/ProfileCard.js +208 -0
- 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/ContentSheet/ContentSheet.js +41 -7
- 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/NumberPagination/NumberPagination.js +138 -0
- package/lib/module/components/ProfileCard/ProfileCard.js +202 -0
- 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/ContentSheet/ContentSheet.d.ts +7 -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/NumberPagination/NumberPagination.d.ts +21 -0
- package/lib/typescript/src/components/ProfileCard/ProfileCard.d.ts +89 -0
- 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/ContentSheet/ContentSheet.tsx +50 -0
- 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/NumberPagination/NumberPagination.tsx +170 -0
- package/src/components/ProfileCard/ProfileCard.tsx +268 -0
- 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
|
@@ -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
|
+
}
|