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,192 @@
1
+ "use strict";
2
+
3
+ import React, { useMemo, useCallback } from 'react';
4
+ import { View, Text } from 'react-native';
5
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
+ import { useTokens } from '../../design-tokens/JFSThemeProvider';
7
+ import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils';
8
+ import { useFormContext } from '../Form/Form';
9
+ import SupportText from '../SupportText/SupportText';
10
+ import Additem from './Additem';
11
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
12
+ function toNumber(value, fallback) {
13
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
14
+ if (typeof value === 'string') {
15
+ const parsed = parseFloat(value);
16
+ if (Number.isFinite(parsed)) return parsed;
17
+ }
18
+ return fallback;
19
+ }
20
+ function toFontWeight(value, fallback) {
21
+ if (typeof value === 'number') return value.toString();
22
+ if (typeof value === 'string' && value.length > 0) return value;
23
+ return fallback;
24
+ }
25
+ function resolveFormUploadTokens(modes) {
26
+ const gap = toNumber(getVariableByName('formField/input/gap', modes), 8);
27
+ const rowGap = toNumber(getVariableByName('formField/gap', modes), 8);
28
+ const labelColor = getVariableByName('formField/label/color', modes);
29
+ const labelFontFamily = getVariableByName('formField/label/fontFamily', modes);
30
+ const labelFontSize = toNumber(getVariableByName('formField/label/fontSize', modes), 14);
31
+ const labelLineHeight = toNumber(getVariableByName('formField/label/lineHeight', modes), 17);
32
+ const labelFontWeight = toFontWeight(getVariableByName('formField/label/fontWeight', modes), '500');
33
+ return {
34
+ gap,
35
+ rowGap,
36
+ labelColor,
37
+ labelFontFamily,
38
+ labelFontSize,
39
+ labelLineHeight,
40
+ labelFontWeight
41
+ };
42
+ }
43
+ function firstError(error) {
44
+ if (!error) return undefined;
45
+ if (Array.isArray(error)) return error[0];
46
+ return error;
47
+ }
48
+
49
+ /** Figma defaults for the remove `IconCapsule` on filled preview cells. Overridable via `modes`. */
50
+ const FORM_UPLOAD_PREVIEW_ICON_CAPSULE_MODES = {
51
+ AppearanceBrand: 'Neutral',
52
+ Emphasis: 'Medium',
53
+ 'Icon Capsule Size': 'XS'
54
+ };
55
+
56
+ /**
57
+ * `FormUpload` — a form-aware upload row built from reusable `Additem` cells.
58
+ *
59
+ * The component mirrors the Figma "FormUpload" design: a label, a horizontal
60
+ * row of upload/preview cells, and support text. It integrates with the
61
+ * existing `<Form>` context so server-side validation errors automatically
62
+ * show below the row, and it clears those errors when attachments change.
63
+ *
64
+ * Upload behaviour is provided through the `picker` prop so the library stays
65
+ * free of native image-picker dependencies. Pass any picker that returns the
66
+ * `AdditemPickerResult` shape and the empty cell becomes a real upload trigger
67
+ * on both iOS and Android.
68
+ */
69
+ function FormUpload({
70
+ label,
71
+ supportText,
72
+ name,
73
+ attachments = [],
74
+ maxCount,
75
+ onAttachmentsChange,
76
+ picker,
77
+ errorMessage,
78
+ isInvalid = false,
79
+ isDisabled = false,
80
+ children,
81
+ modes: propModes = EMPTY_MODES,
82
+ style,
83
+ rowStyle,
84
+ accessibilityLabel,
85
+ testID
86
+ }) {
87
+ const formCtx = useFormContext();
88
+ const formError = name && formCtx ? firstError(formCtx.validationErrors[name]) : undefined;
89
+ const resolvedIsInvalid = isInvalid || Boolean(formError);
90
+ const resolvedErrorMessage = errorMessage ?? formError;
91
+ const {
92
+ modes: globalModes
93
+ } = useTokens();
94
+ const baseModes = useMemo(() => globalModes === EMPTY_MODES && propModes === EMPTY_MODES ? EMPTY_MODES : {
95
+ ...globalModes,
96
+ ...propModes
97
+ }, [globalModes, propModes]);
98
+ const modes = useMemo(() => ({
99
+ ...baseModes,
100
+ 'FormField States': resolvedIsInvalid ? 'Error' : 'Idle',
101
+ Status: resolvedIsInvalid ? 'Error' : 'Auto'
102
+ }), [baseModes, resolvedIsInvalid]);
103
+
104
+ // Empty cells rely on `Additem`'s built-in Neutral / Low / S defaults for the
105
+ // add icon. Preview cells use the tighter remove-icon treatment from Figma.
106
+ const previewAdditemModes = useMemo(() => ({
107
+ ...FORM_UPLOAD_PREVIEW_ICON_CAPSULE_MODES,
108
+ ...modes
109
+ }), [modes]);
110
+ const tokens = useMemo(() => resolveFormUploadTokens(modes), [modes]);
111
+ const handleAssetsPicked = useCallback(assets => {
112
+ if (isDisabled) return;
113
+ const next = [...attachments, ...assets];
114
+ if (maxCount != null && next.length > maxCount) {
115
+ next.length = maxCount;
116
+ }
117
+ onAttachmentsChange?.(next);
118
+ if (name && formCtx) {
119
+ formCtx.onFieldChange(name);
120
+ }
121
+ }, [attachments, maxCount, onAttachmentsChange, isDisabled, name, formCtx]);
122
+ const handleRemove = useCallback(index => {
123
+ if (isDisabled) return;
124
+ const next = attachments.filter((_, i) => i !== index);
125
+ onAttachmentsChange?.(next);
126
+ if (name && formCtx) {
127
+ formCtx.onFieldChange(name);
128
+ }
129
+ }, [attachments, onAttachmentsChange, isDisabled, name, formCtx]);
130
+ const containerStyle = useMemo(() => ({
131
+ gap: tokens.gap,
132
+ opacity: isDisabled ? 0.5 : 1
133
+ }), [tokens.gap, isDisabled]);
134
+ const rowStyleResolved = useMemo(() => ({
135
+ flexDirection: 'row',
136
+ flexWrap: 'wrap',
137
+ gap: tokens.rowGap,
138
+ alignItems: 'flex-start'
139
+ }), [tokens.rowGap]);
140
+ const labelStyle = useMemo(() => ({
141
+ color: tokens.labelColor,
142
+ fontFamily: tokens.labelFontFamily,
143
+ fontSize: tokens.labelFontSize,
144
+ lineHeight: tokens.labelLineHeight,
145
+ fontWeight: tokens.labelFontWeight
146
+ }), [tokens]);
147
+ const resolvedA11yLabel = accessibilityLabel ?? label ?? 'Attachment upload';
148
+ const canAddMore = maxCount == null || attachments.length < maxCount;
149
+ const renderContent = () => {
150
+ if (children) {
151
+ return cloneChildrenWithModes(children, modes);
152
+ }
153
+ return /*#__PURE__*/_jsxs(_Fragment, {
154
+ children: [attachments.map((attachment, index) => /*#__PURE__*/_jsx(Additem, {
155
+ state: "preview",
156
+ imageSource: attachment.uri,
157
+ onRemove: () => handleRemove(index),
158
+ modes: previewAdditemModes,
159
+ isDisabled: isDisabled,
160
+ accessibilityLabel: attachment.name ?? `Attachment ${index + 1}`,
161
+ testID: `${testID}-item-${index}`
162
+ }, `${attachment.uri}-${index}`)), canAddMore && /*#__PURE__*/_jsx(Additem, {
163
+ state: "empty",
164
+ picker: picker,
165
+ onAssetsPicked: handleAssetsPicked,
166
+ modes: modes,
167
+ isDisabled: isDisabled,
168
+ testID: `${testID}-add`
169
+ })]
170
+ });
171
+ };
172
+ const supportLabel = resolvedIsInvalid && resolvedErrorMessage ? resolvedErrorMessage : supportText;
173
+ const supportStatus = resolvedIsInvalid ? 'Error' : 'Neutral';
174
+ return /*#__PURE__*/_jsxs(View, {
175
+ style: [containerStyle, style],
176
+ accessibilityRole: "none",
177
+ accessibilityLabel: resolvedA11yLabel,
178
+ testID: testID,
179
+ children: [label != null && label !== '' && /*#__PURE__*/_jsx(Text, {
180
+ style: labelStyle,
181
+ children: label
182
+ }), /*#__PURE__*/_jsx(View, {
183
+ style: [rowStyleResolved, rowStyle],
184
+ children: renderContent()
185
+ }), supportLabel != null && supportLabel !== '' && /*#__PURE__*/_jsx(SupportText, {
186
+ label: supportLabel,
187
+ status: supportStatus,
188
+ modes: modes
189
+ })]
190
+ });
191
+ }
192
+ export default /*#__PURE__*/React.memo(FormUpload);
@@ -0,0 +1,201 @@
1
+ "use strict";
2
+
3
+ import React, { useMemo } from 'react';
4
+ import { View } from 'react-native';
5
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
+ import { useTokens } from '../../design-tokens/JFSThemeProvider';
7
+ import { EMPTY_MODES, cloneChildrenWithModes, flattenChildren } from '../../utils/react-utils';
8
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
+ function toNumber(value, fallback) {
10
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
11
+ if (typeof value === 'string') {
12
+ const parsed = parseFloat(value);
13
+ if (Number.isFinite(parsed)) return parsed;
14
+ }
15
+ return fallback;
16
+ }
17
+ function resolveGridTokens(modes) {
18
+ const gapFallback = toNumber(getVariableByName('Grid/gap', modes), 8);
19
+ const columnGap = toNumber(getVariableByName('Grid/column/gap', modes), gapFallback);
20
+ const rowGap = toNumber(getVariableByName('Grid/row/gap', modes), gapFallback);
21
+ const paddingHorizontal = toNumber(getVariableByName('Grid/padding/horizontal', modes), 8);
22
+ const paddingVertical = toNumber(getVariableByName('Grid/padding/vertical', modes), 8);
23
+ const paddingTop = toNumber(getVariableByName('Grid/padding/top', modes), paddingVertical);
24
+ const paddingBottom = toNumber(getVariableByName('Grid/padding/bottom', modes), paddingVertical);
25
+ const paddingLeft = toNumber(getVariableByName('Grid/padding/left', modes), paddingHorizontal);
26
+ const paddingRight = toNumber(getVariableByName('Grid/padding/right', modes), paddingHorizontal);
27
+ const backgroundColor = getVariableByName('Grid/background/color', modes) ?? 'transparent';
28
+ return {
29
+ backgroundColor,
30
+ columnGap,
31
+ rowGap,
32
+ paddingTop,
33
+ paddingBottom,
34
+ paddingLeft,
35
+ paddingRight
36
+ };
37
+ }
38
+ function normalizeFrs(frs, count) {
39
+ if (!frs || frs.length === 0) {
40
+ return Array.from({
41
+ length: count
42
+ }, () => 1);
43
+ }
44
+ return Array.from({
45
+ length: count
46
+ }, (_, index) => {
47
+ const value = frs[index] ?? frs[frs.length - 1] ?? 1;
48
+ return value > 0 ? value : 1;
49
+ });
50
+ }
51
+ function resolveAlignSelf(alignItems) {
52
+ if (alignItems === 'center') return 'center';
53
+ if (alignItems === 'start') return 'flex-start';
54
+ return 'stretch';
55
+ }
56
+ function resolveRowJustifyContent(justifyContent) {
57
+ if (justifyContent === 'space-between') return 'space-between';
58
+ if (justifyContent === 'start') return 'flex-start';
59
+ return 'flex-start';
60
+ }
61
+ function buildCellStyle(columnFr, justifyContent, alignItems) {
62
+ const alignSelf = resolveAlignSelf(alignItems);
63
+ if (justifyContent === 'space-between' || justifyContent === 'start') {
64
+ return {
65
+ flexGrow: 0,
66
+ flexShrink: 1,
67
+ flexBasis: 'auto',
68
+ minWidth: 0,
69
+ alignSelf
70
+ };
71
+ }
72
+ return {
73
+ flex: columnFr,
74
+ minWidth: 0,
75
+ alignSelf
76
+ };
77
+ }
78
+
79
+ /**
80
+ * `Grid` — a token-driven layout grid for React Native.
81
+ *
82
+ * Mirrors the Figma "Grid" component: a padded container with configurable
83
+ * columns, rows, fractional column tracks (`columnFrs`), and gap tokens.
84
+ * Each slot child maps to one cell — no separate `GridItem` wrapper is needed.
85
+ *
86
+ * @component
87
+ */
88
+ function Grid({
89
+ columns = 1,
90
+ rows,
91
+ columnFrs,
92
+ rowFrs,
93
+ columnGap: columnGapProp,
94
+ rowGap: rowGapProp,
95
+ gap: gapProp,
96
+ padding: paddingProp,
97
+ paddingHorizontal: paddingHorizontalProp,
98
+ paddingVertical: paddingVerticalProp,
99
+ paddingTop: paddingTopProp,
100
+ paddingBottom: paddingBottomProp,
101
+ paddingLeft: paddingLeftProp,
102
+ paddingRight: paddingRightProp,
103
+ justifyContent = 'stretch',
104
+ alignItems = 'stretch',
105
+ children,
106
+ modes: propModes = EMPTY_MODES,
107
+ style,
108
+ contentStyle,
109
+ cellStyle,
110
+ accessibilityLabel,
111
+ testID
112
+ }) {
113
+ const {
114
+ modes: globalModes
115
+ } = useTokens();
116
+ const modes = useMemo(() => globalModes === EMPTY_MODES && propModes === EMPTY_MODES ? EMPTY_MODES : {
117
+ ...globalModes,
118
+ ...propModes
119
+ }, [globalModes, propModes]);
120
+ const tokens = useMemo(() => resolveGridTokens(modes), [modes]);
121
+ const resolvedColumnGap = columnGapProp ?? gapProp ?? tokens.columnGap;
122
+ const resolvedRowGap = rowGapProp ?? gapProp ?? tokens.rowGap;
123
+ const resolvedPadding = useMemo(() => ({
124
+ paddingTop: paddingTopProp ?? paddingVerticalProp ?? paddingProp ?? tokens.paddingTop,
125
+ paddingBottom: paddingBottomProp ?? paddingVerticalProp ?? paddingProp ?? tokens.paddingBottom,
126
+ paddingLeft: paddingLeftProp ?? paddingHorizontalProp ?? paddingProp ?? tokens.paddingLeft,
127
+ paddingRight: paddingRightProp ?? paddingHorizontalProp ?? paddingProp ?? tokens.paddingRight
128
+ }), [paddingTopProp, paddingBottomProp, paddingLeftProp, paddingRightProp, paddingHorizontalProp, paddingVerticalProp, paddingProp, tokens]);
129
+ const safeColumns = Math.max(1, Math.floor(columns));
130
+ const processedChildren = useMemo(() => cloneChildrenWithModes(flattenChildren(children), modes), [children, modes]);
131
+ const cellNodes = useMemo(() => {
132
+ const nodes = [...processedChildren];
133
+ if (rows != null) {
134
+ const targetCount = safeColumns * Math.max(1, Math.floor(rows));
135
+ while (nodes.length < targetCount) {
136
+ nodes.push(null);
137
+ }
138
+ if (nodes.length > targetCount) {
139
+ nodes.length = targetCount;
140
+ }
141
+ }
142
+ return nodes;
143
+ }, [processedChildren, rows, safeColumns]);
144
+ const gridRows = useMemo(() => {
145
+ const result = [];
146
+ for (let index = 0; index < cellNodes.length; index += safeColumns) {
147
+ result.push(cellNodes.slice(index, index + safeColumns));
148
+ }
149
+ return result;
150
+ }, [cellNodes, safeColumns]);
151
+ const normalizedColumnFrs = useMemo(() => normalizeFrs(columnFrs, safeColumns), [columnFrs, safeColumns]);
152
+ const normalizedRowFrs = useMemo(() => rowFrs ? normalizeFrs(rowFrs, gridRows.length) : null, [rowFrs, gridRows.length]);
153
+ const containerStyle = useMemo(() => ({
154
+ backgroundColor: tokens.backgroundColor,
155
+ ...resolvedPadding,
156
+ width: '100%'
157
+ }), [tokens.backgroundColor, resolvedPadding]);
158
+ const rowsContainerStyle = useMemo(() => ({
159
+ gap: resolvedRowGap,
160
+ width: '100%'
161
+ }), [resolvedRowGap]);
162
+ const rowStyle = useMemo(() => ({
163
+ flexDirection: 'row',
164
+ columnGap: resolvedColumnGap,
165
+ justifyContent: resolveRowJustifyContent(justifyContent),
166
+ alignItems: alignItems === 'stretch' ? 'stretch' : alignItems === 'center' ? 'center' : 'flex-start',
167
+ width: '100%'
168
+ }), [resolvedColumnGap, justifyContent, alignItems]);
169
+ const defaultAccessibilityLabel = accessibilityLabel ?? `Grid, ${safeColumns} column${safeColumns === 1 ? '' : 's'}, ${gridRows.length} row${gridRows.length === 1 ? '' : 's'}`;
170
+ return /*#__PURE__*/_jsx(View, {
171
+ style: [containerStyle, style],
172
+ accessibilityRole: "none",
173
+ accessibilityLabel: defaultAccessibilityLabel,
174
+ testID: testID,
175
+ children: /*#__PURE__*/_jsx(View, {
176
+ style: [rowsContainerStyle, contentStyle],
177
+ children: gridRows.map((row, rowIndex) => {
178
+ const rowFr = normalizedRowFrs?.[rowIndex];
179
+ const spacersNeeded = justifyContent === 'space-between' && row.length < safeColumns ? safeColumns - row.length : 0;
180
+ return /*#__PURE__*/_jsxs(View, {
181
+ style: rowFr != null ? [rowStyle, {
182
+ flexGrow: rowFr
183
+ }] : rowStyle,
184
+ children: [row.map((cell, columnIndex) => {
185
+ const absoluteIndex = rowIndex * safeColumns + columnIndex;
186
+ const cellFlexStyle = buildCellStyle(normalizedColumnFrs[columnIndex] ?? 1, justifyContent, alignItems);
187
+ return /*#__PURE__*/_jsx(View, {
188
+ style: [cellFlexStyle, cellStyle],
189
+ children: cell
190
+ }, `grid-cell-${absoluteIndex}`);
191
+ }), spacersNeeded > 0 && Array.from({
192
+ length: spacersNeeded
193
+ }, (_, spacerIndex) => /*#__PURE__*/_jsx(View, {
194
+ style: buildCellStyle(normalizedColumnFrs[row.length + spacerIndex] ?? 1, justifyContent, alignItems)
195
+ }, `grid-spacer-${rowIndex}-${spacerIndex}`))]
196
+ }, `grid-row-${rowIndex}`);
197
+ })
198
+ })
199
+ });
200
+ }
201
+ export default /*#__PURE__*/React.memo(Grid);
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+
3
+ import React, { useMemo } from 'react';
4
+ import { Pressable, Text, View } from 'react-native';
5
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
+ import GlassFill from '../../utils/GlassFill/GlassFill';
7
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
8
+ function resolveNumberPaginationTokens(modes) {
9
+ const horizontalPadding = getVariableByName('numberPagination/padding/horizontal', modes) ?? 4;
10
+ const verticalPadding = getVariableByName('numberPagination/padding/vertical', modes) ?? 4;
11
+ const radiusRaw = getVariableByName('numberPagination/radius', modes) ?? 9999;
12
+ const backgroundColor = getVariableByName('numberPagination/background/color', modes) ?? 'rgba(141, 141, 141, 0.4)';
13
+ const borderColor = getVariableByName('numberPagination/border/color', modes) ?? 'rgba(255, 255, 255, 0.15)';
14
+
15
+ // control number variables
16
+ const itemWidth = getVariableByName('controlNumber/width', modes) ?? 32;
17
+ const itemHeight = getVariableByName('controlNumber/height', modes) ?? 32;
18
+ const itemRadiusRaw = getVariableByName('controlNumber/radius', modes) ?? 9999;
19
+ const activeBgColor = getVariableByName('control/active/background/color', modes) ?? '#ffffff';
20
+ const activeTextColor = getVariableByName('control/active/text/color', modes) ?? '#000000';
21
+ const inactiveTextColor = getVariableByName('control/text/default', modes) ?? '#ffffff';
22
+
23
+ // typography variables
24
+ const fontFamily = getVariableByName('numberPagination/fontFamily', modes) ?? 'JioType';
25
+ const fontSize = getVariableByName('numberPagination/fontSize', modes) ?? 16;
26
+ const fontWeight = String(getVariableByName('numberPagination/fontWeight', modes) ?? '500');
27
+ const lineHeight = getVariableByName('numberPagination/lineHeight', modes) ?? 18;
28
+ const blurMinimal = getVariableByName('blur/minimal', modes) ?? 29;
29
+ const blurIntensity = Math.max(0, Math.min(100, Math.round(Number(blurMinimal))));
30
+ const borderRadius = radiusRaw === 9999 ? 39 / 2 : radiusRaw;
31
+ const itemRadius = itemRadiusRaw === 9999 ? itemWidth / 2 : itemRadiusRaw;
32
+ return {
33
+ containerStyle: {
34
+ height: 39,
35
+ borderRadius,
36
+ borderWidth: 1,
37
+ borderColor,
38
+ backgroundColor: 'transparent',
39
+ overflow: 'hidden',
40
+ flexDirection: 'row',
41
+ alignItems: 'center',
42
+ paddingHorizontal: horizontalPadding,
43
+ paddingVertical: verticalPadding
44
+ },
45
+ activeItemStyle: {
46
+ width: itemWidth,
47
+ height: itemHeight,
48
+ borderRadius: itemRadius,
49
+ backgroundColor: activeBgColor,
50
+ alignItems: 'center',
51
+ justifyContent: 'center'
52
+ },
53
+ inactiveItemStyle: {
54
+ width: itemWidth,
55
+ height: itemHeight,
56
+ borderRadius: itemRadius,
57
+ backgroundColor: 'transparent',
58
+ alignItems: 'center',
59
+ justifyContent: 'center'
60
+ },
61
+ activeTextStyle: {
62
+ fontFamily,
63
+ fontSize,
64
+ fontWeight,
65
+ lineHeight,
66
+ color: activeTextColor,
67
+ textAlign: 'center'
68
+ },
69
+ inactiveTextStyle: {
70
+ fontFamily,
71
+ fontSize,
72
+ fontWeight,
73
+ lineHeight,
74
+ color: inactiveTextColor,
75
+ textAlign: 'center'
76
+ },
77
+ blurIntensity,
78
+ backgroundColor
79
+ };
80
+ }
81
+
82
+ /**
83
+ * NumberPagination component displays a horizontal list of page numbers on a frosted glass surface.
84
+ * All styling is driven by Figma design tokens with support for custom children slot.
85
+ */
86
+ export default function NumberPagination({
87
+ activePage = 1,
88
+ totalPages = 1,
89
+ onPageChange,
90
+ children,
91
+ modes,
92
+ style
93
+ }) {
94
+ const tokens = useMemo(() => resolveNumberPaginationTokens(modes ?? {}), [modes]);
95
+ const renderPages = () => {
96
+ if (children) {
97
+ return children;
98
+ }
99
+ const pages = [];
100
+ for (let i = 1; i <= totalPages; i++) {
101
+ const active = i === activePage;
102
+ pages.push(/*#__PURE__*/_jsx(Pressable, {
103
+ onPress: () => onPageChange?.(i),
104
+ style: ({
105
+ pressed
106
+ }) => [active ? tokens.activeItemStyle : tokens.inactiveItemStyle, pressed && {
107
+ opacity: 0.7
108
+ }],
109
+ accessibilityRole: "button",
110
+ accessibilityState: {
111
+ selected: active
112
+ },
113
+ accessibilityLabel: `Page ${i}`,
114
+ children: /*#__PURE__*/_jsx(Text, {
115
+ style: active ? tokens.activeTextStyle : tokens.inactiveTextStyle,
116
+ children: i
117
+ })
118
+ }, i));
119
+ }
120
+ return pages;
121
+ };
122
+ return /*#__PURE__*/_jsxs(View, {
123
+ style: [tokens.containerStyle, style],
124
+ children: [/*#__PURE__*/_jsx(GlassFill, {
125
+ tint: "light",
126
+ intensity: tokens.blurIntensity,
127
+ overlayColor: tokens.backgroundColor,
128
+ androidTintWash: false
129
+ }), /*#__PURE__*/_jsx(View, {
130
+ style: {
131
+ flexDirection: 'row',
132
+ alignItems: 'center',
133
+ height: '100%'
134
+ },
135
+ children: renderPages()
136
+ })]
137
+ });
138
+ }