jfs-components 0.1.30 → 0.1.33
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 +29 -0
- package/D2C.md +118 -0
- package/lib/commonjs/components/CategoryCard/CategoryCard.js +264 -0
- package/lib/commonjs/components/CompareTable/CompareTable.js +140 -84
- package/lib/commonjs/components/ContentSheet/ContentSheet.js +28 -5
- package/lib/commonjs/components/CounterBadge/CounterBadge.js +78 -0
- package/lib/commonjs/components/FavoriteToggle/FavoriteToggle.js +180 -0
- package/lib/commonjs/components/HelloJioInput/HelloJioInput.js +287 -0
- package/lib/commonjs/components/ValueBackMetric/ValueBackMetric.js +219 -0
- package/lib/commonjs/components/index.js +35 -0
- package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +40095 -1
- package/lib/commonjs/design-tokens/figma-modes.generated.js +3 -0
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/module/components/CategoryCard/CategoryCard.js +258 -0
- package/lib/module/components/CompareTable/CompareTable.js +140 -84
- package/lib/module/components/ContentSheet/ContentSheet.js +29 -6
- package/lib/module/components/CounterBadge/CounterBadge.js +73 -0
- package/lib/module/components/FavoriteToggle/FavoriteToggle.js +174 -0
- package/lib/module/components/HelloJioInput/HelloJioInput.js +281 -0
- package/lib/module/components/ValueBackMetric/ValueBackMetric.js +214 -0
- package/lib/module/components/index.js +6 -1
- package/lib/module/design-tokens/Coin Variables-variables-full.json +40095 -1
- package/lib/module/design-tokens/figma-modes.generated.js +3 -0
- package/lib/module/icons/registry.js +1 -1
- package/lib/typescript/src/components/CategoryCard/CategoryCard.d.ts +72 -0
- package/lib/typescript/src/components/CompareTable/CompareTable.d.ts +16 -1
- package/lib/typescript/src/components/ContentSheet/ContentSheet.d.ts +7 -1
- package/lib/typescript/src/components/CounterBadge/CounterBadge.d.ts +19 -0
- package/lib/typescript/src/components/FavoriteToggle/FavoriteToggle.d.ts +66 -0
- package/lib/typescript/src/components/HelloJioInput/HelloJioInput.d.ts +111 -0
- package/lib/typescript/src/components/ValueBackMetric/ValueBackMetric.d.ts +86 -0
- package/lib/typescript/src/components/index.d.ts +5 -0
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +7 -1
- package/scripts/extract-figma-modes.js +359 -0
- package/src/components/CategoryCard/CategoryCard.tsx +404 -0
- package/src/components/CompareTable/CompareTable.tsx +201 -101
- package/src/components/ContentSheet/ContentSheet.tsx +40 -11
- package/src/components/CounterBadge/CounterBadge.tsx +95 -0
- package/src/components/FavoriteToggle/FavoriteToggle.tsx +243 -0
- package/src/components/HelloJioInput/HelloJioInput.tsx +513 -0
- package/src/components/ValueBackMetric/ValueBackMetric.tsx +298 -0
- package/src/components/index.ts +5 -0
- package/src/design-tokens/Coin Variables-variables-full.json +40095 -1
- package/src/design-tokens/figma-modes.generated.ts +3 -0
- package/src/icons/registry.ts +1 -1
|
@@ -113,7 +113,9 @@ function CompareTable({
|
|
|
113
113
|
modes = _reactUtils.EMPTY_MODES,
|
|
114
114
|
style,
|
|
115
115
|
disableTruncation,
|
|
116
|
-
columnWidth
|
|
116
|
+
columnWidth,
|
|
117
|
+
stickyHeaders = true,
|
|
118
|
+
stickyHeaderLabel = true
|
|
117
119
|
}) {
|
|
118
120
|
// --- selection card tokens ------------------------------------------------
|
|
119
121
|
const cardBg = (0, _figmaVariablesResolver.getVariableByName)('selectionCard/background/color', modes) ?? '#ffffff';
|
|
@@ -185,12 +187,12 @@ function CompareTable({
|
|
|
185
187
|
// Stable per-section ref-callback instances so React doesn't detach/reattach
|
|
186
188
|
// on every render (which would churn the map and re-fire scrollTo).
|
|
187
189
|
const bodyRefCallbacks = (0, _react.useRef)(new Map());
|
|
188
|
-
/**
|
|
189
|
-
*
|
|
190
|
+
/** Tracks which ScrollView the user is currently dragging. Only the
|
|
191
|
+
* actively-dragged ScrollView drives horizontal sync — programmatic
|
|
192
|
+
* scrollTo echoes from synced peers are ignored, eliminating jitter. */
|
|
193
|
+
const draggingSource = (0, _react.useRef)(null);
|
|
190
194
|
const lastSyncX = (0, _react.useRef)(0);
|
|
191
|
-
const
|
|
192
|
-
if (Math.abs(x - lastSyncX.current) < 0.5) return;
|
|
193
|
-
lastSyncX.current = x;
|
|
195
|
+
const syncOthers = (0, _react.useCallback)((source, x) => {
|
|
194
196
|
if (source !== 'cards' && cardsScrollRef.current) {
|
|
195
197
|
cardsScrollRef.current.scrollTo({
|
|
196
198
|
x,
|
|
@@ -206,12 +208,37 @@ function CompareTable({
|
|
|
206
208
|
}
|
|
207
209
|
});
|
|
208
210
|
}, []);
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
}, [
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
211
|
+
const onScrollBeginDrag = (0, _react.useCallback)(source => () => {
|
|
212
|
+
draggingSource.current = source;
|
|
213
|
+
}, []);
|
|
214
|
+
const onScrollEndDrag = (0, _react.useCallback)(source => e => {
|
|
215
|
+
const x = Math.round(e.nativeEvent.contentOffset.x);
|
|
216
|
+
if (x === lastSyncX.current) return;
|
|
217
|
+
lastSyncX.current = x;
|
|
218
|
+
syncOthers(source, x);
|
|
219
|
+
}, [syncOthers]);
|
|
220
|
+
const onMomentumScrollEnd = (0, _react.useCallback)(source => e => {
|
|
221
|
+
if (draggingSource.current === source) {
|
|
222
|
+
draggingSource.current = null;
|
|
223
|
+
}
|
|
224
|
+
const x = Math.round(e.nativeEvent.contentOffset.x);
|
|
225
|
+
if (x === lastSyncX.current) return;
|
|
226
|
+
lastSyncX.current = x;
|
|
227
|
+
syncOthers(source, x);
|
|
228
|
+
}, [syncOthers]);
|
|
229
|
+
|
|
230
|
+
/** Real-time scroll sync: only the actively-dragged ScrollView drives
|
|
231
|
+
* sync on native — echoed onScroll events from programmatic scrollTo
|
|
232
|
+
* on other ScrollViews are ignored because draggingSource won't match.
|
|
233
|
+
* On web, onScrollBeginDrag never fires for mouse/trackpad, so skip
|
|
234
|
+
* the guard and rely on the lastSyncX rounded-value check instead. */
|
|
235
|
+
const onScroll = (0, _react.useCallback)(source => e => {
|
|
236
|
+
if (_reactNative.Platform.OS !== 'web' && draggingSource.current !== source) return;
|
|
237
|
+
const x = Math.round(e.nativeEvent.contentOffset.x);
|
|
238
|
+
if (x === lastSyncX.current) return;
|
|
239
|
+
lastSyncX.current = x;
|
|
240
|
+
syncOthers(source, x);
|
|
241
|
+
}, [syncOthers]);
|
|
215
242
|
const getBodyRefCallback = (0, _react.useCallback)(idx => {
|
|
216
243
|
let cb = bodyRefCallbacks.current.get(idx);
|
|
217
244
|
if (!cb) {
|
|
@@ -442,55 +469,45 @@ function CompareTable({
|
|
|
442
469
|
})
|
|
443
470
|
})]
|
|
444
471
|
});
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
472
|
+
const renderHeaderContent = section => section.header != null ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
473
|
+
style: {
|
|
474
|
+
backgroundColor: headerBg,
|
|
475
|
+
paddingHorizontal: headerPaddingH,
|
|
476
|
+
paddingVertical: headerPaddingV,
|
|
477
|
+
borderBottomWidth: cellBorderSize,
|
|
478
|
+
borderBottomColor: cellBorderColor
|
|
479
|
+
},
|
|
480
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
481
|
+
style: headerTextStyle,
|
|
482
|
+
children: section.header
|
|
483
|
+
})
|
|
484
|
+
}) : null;
|
|
485
|
+
const renderBodyContent = (section, index) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
|
|
486
|
+
ref: getBodyRefCallback(index),
|
|
487
|
+
horizontal: true,
|
|
488
|
+
showsHorizontalScrollIndicator: false,
|
|
489
|
+
onScroll: onScroll(index),
|
|
490
|
+
onScrollBeginDrag: onScrollBeginDrag(index),
|
|
491
|
+
onScrollEndDrag: onScrollEndDrag(index),
|
|
492
|
+
onMomentumScrollEnd: onMomentumScrollEnd(index),
|
|
493
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
457
494
|
style: {
|
|
458
|
-
width:
|
|
459
|
-
backgroundColor: headerBg,
|
|
460
|
-
paddingHorizontal: headerPaddingH,
|
|
461
|
-
paddingVertical: headerPaddingV,
|
|
462
|
-
borderBottomWidth: cellBorderSize,
|
|
463
|
-
borderBottomColor: cellBorderColor
|
|
495
|
+
width: contentWidth
|
|
464
496
|
},
|
|
465
|
-
children:
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
style: {
|
|
477
|
-
width: contentWidth
|
|
478
|
-
},
|
|
479
|
-
children: section.rows.map((row, rowIndex) => {
|
|
480
|
-
const isLastRow = rowIndex === section.rows.length - 1;
|
|
481
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
482
|
-
style: {
|
|
483
|
-
flexDirection: 'row',
|
|
484
|
-
width: '100%'
|
|
485
|
-
},
|
|
486
|
-
children: [Array.from({
|
|
487
|
-
length: columnCount
|
|
488
|
-
}).map((_, colIndex) => renderTableCell(renderCellContent(row.values?.[colIndex], `${rowIndex}-${colIndex}`), colIndex, isLastRow, colIndex)), showAddCard && renderTableCell(null, gridColumnCount - 1, isLastRow, '__add_spacer__')]
|
|
489
|
-
}, row.key ?? rowIndex);
|
|
490
|
-
})
|
|
497
|
+
children: section.rows.map((row, rowIndex) => {
|
|
498
|
+
const isLastRow = rowIndex === section.rows.length - 1;
|
|
499
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
500
|
+
style: {
|
|
501
|
+
flexDirection: 'row',
|
|
502
|
+
width: '100%'
|
|
503
|
+
},
|
|
504
|
+
children: [Array.from({
|
|
505
|
+
length: columnCount
|
|
506
|
+
}).map((_, colIndex) => renderTableCell(renderCellContent(row.values?.[colIndex], `${rowIndex}-${colIndex}`), colIndex, isLastRow, colIndex)), showAddCard && renderTableCell(null, gridColumnCount - 1, isLastRow, '__add_spacer__')]
|
|
507
|
+
}, row.key ?? rowIndex);
|
|
491
508
|
})
|
|
492
|
-
})
|
|
493
|
-
}
|
|
509
|
+
})
|
|
510
|
+
});
|
|
494
511
|
const outerStyle = {
|
|
495
512
|
width: '100%',
|
|
496
513
|
backgroundColor: cardBg,
|
|
@@ -524,43 +541,82 @@ function CompareTable({
|
|
|
524
541
|
}
|
|
525
542
|
|
|
526
543
|
// --- fixed-width scroll mode: chained horizontal sync + sticky cards -----
|
|
527
|
-
//
|
|
528
|
-
//
|
|
529
|
-
//
|
|
530
|
-
|
|
531
|
-
|
|
544
|
+
// Build children and stickyHeaderIndices for the vertical ScrollView.
|
|
545
|
+
// When stickyHeaders is on, section headers are injected as direct children
|
|
546
|
+
// so React Native's stickyHeaderIndices can pin them with stacking.
|
|
547
|
+
const scrollChildren = [];
|
|
548
|
+
const stickyIndices = [];
|
|
549
|
+
if (showCardsRow) {
|
|
550
|
+
stickyIndices.push(scrollChildren.length);
|
|
551
|
+
scrollChildren.push(/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
|
|
552
|
+
ref: cardsScrollRef,
|
|
553
|
+
horizontal: true,
|
|
554
|
+
showsHorizontalScrollIndicator: false,
|
|
555
|
+
scrollEventThrottle: 16,
|
|
556
|
+
onScroll: onScroll('cards'),
|
|
557
|
+
onScrollBeginDrag: onScrollBeginDrag('cards'),
|
|
558
|
+
onScrollEndDrag: onScrollEndDrag('cards'),
|
|
559
|
+
onMomentumScrollEnd: onMomentumScrollEnd('cards'),
|
|
560
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
561
|
+
style: {
|
|
562
|
+
flexDirection: 'row',
|
|
563
|
+
width: contentWidth
|
|
564
|
+
},
|
|
565
|
+
children: [columns.map(renderCard), showAddCard && renderAddCard()]
|
|
566
|
+
})
|
|
567
|
+
}, "cards-row"));
|
|
568
|
+
}
|
|
569
|
+
sections.forEach((section, index) => {
|
|
570
|
+
const sectionKey = section.key ?? section.title ?? index;
|
|
571
|
+
const headerContent = renderHeaderContent(section);
|
|
572
|
+
if (stickyHeaders && headerContent != null) {
|
|
573
|
+
stickyIndices.push(scrollChildren.length);
|
|
574
|
+
scrollChildren.push(/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
575
|
+
style: {
|
|
576
|
+
width: '100%'
|
|
577
|
+
},
|
|
578
|
+
children: stickyHeaderLabel ? headerContent : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
|
|
579
|
+
horizontal: true,
|
|
580
|
+
showsHorizontalScrollIndicator: false,
|
|
581
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
582
|
+
style: {
|
|
583
|
+
width: contentWidth
|
|
584
|
+
},
|
|
585
|
+
children: headerContent
|
|
586
|
+
})
|
|
587
|
+
})
|
|
588
|
+
}, `sticky-header-${sectionKey}`));
|
|
589
|
+
}
|
|
590
|
+
scrollChildren.push(/*#__PURE__*/(0, _jsxRuntime.jsxs)(_Accordion.default, {
|
|
591
|
+
title: section.title,
|
|
592
|
+
defaultExpanded: section.defaultExpanded ?? index === 0,
|
|
593
|
+
modes: modes,
|
|
594
|
+
children: [!stickyHeaders && headerContent != null && (stickyHeaderLabel ? headerContent : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
|
|
595
|
+
horizontal: true,
|
|
596
|
+
showsHorizontalScrollIndicator: false,
|
|
597
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
598
|
+
style: {
|
|
599
|
+
width: contentWidth
|
|
600
|
+
},
|
|
601
|
+
children: headerContent
|
|
602
|
+
})
|
|
603
|
+
})), renderBodyContent(section, index)]
|
|
604
|
+
}, sectionKey));
|
|
605
|
+
});
|
|
532
606
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
533
607
|
style: [outerStyle, style, {
|
|
534
608
|
flexDirection: 'column',
|
|
535
609
|
flex: 1
|
|
536
610
|
}],
|
|
537
|
-
children: /*#__PURE__*/(0, _jsxRuntime.
|
|
611
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
|
|
538
612
|
style: {
|
|
539
613
|
width: '100%',
|
|
540
614
|
flex: 1
|
|
541
615
|
},
|
|
542
|
-
stickyHeaderIndices:
|
|
616
|
+
stickyHeaderIndices: stickyIndices,
|
|
543
617
|
showsVerticalScrollIndicator: false,
|
|
544
618
|
directionalLockEnabled: true,
|
|
545
|
-
children:
|
|
546
|
-
ref: cardsScrollRef,
|
|
547
|
-
horizontal: true,
|
|
548
|
-
showsHorizontalScrollIndicator: false,
|
|
549
|
-
scrollEventThrottle: 16,
|
|
550
|
-
onScroll: onCardsScroll,
|
|
551
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
552
|
-
style: {
|
|
553
|
-
flexDirection: 'row',
|
|
554
|
-
width: contentWidth
|
|
555
|
-
},
|
|
556
|
-
children: [columns.map(renderCard), showAddCard && renderAddCard()]
|
|
557
|
-
})
|
|
558
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
559
|
-
style: {
|
|
560
|
-
width: '100%'
|
|
561
|
-
},
|
|
562
|
-
children: sections.map(renderSection)
|
|
563
|
-
})]
|
|
619
|
+
children: scrollChildren
|
|
564
620
|
})
|
|
565
621
|
});
|
|
566
622
|
}
|
|
@@ -109,6 +109,7 @@ function ContentSheet({
|
|
|
109
109
|
safeAreaBottom = true,
|
|
110
110
|
pinToBottom = true,
|
|
111
111
|
visible = true,
|
|
112
|
+
maxHeightPercent = 0.7,
|
|
112
113
|
style,
|
|
113
114
|
...rest
|
|
114
115
|
}) {
|
|
@@ -120,9 +121,6 @@ function ContentSheet({
|
|
|
120
121
|
const resolved = (0, _react.useMemo)(() => resolveContentSheetStyle(modes), [modes]);
|
|
121
122
|
const processedChildren = (0, _react.useMemo)(() => (0, _reactUtils.cloneChildrenWithModes)((0, _reactUtils.flattenChildren)(children), modes), [children, modes]);
|
|
122
123
|
const paddingBottom = resolved.paddingBottom + (safeAreaBottom ? bottomInset : 0);
|
|
123
|
-
const containerStyle = (0, _react.useMemo)(() => [pinToBottom ? pinnedStyle : null, resolved.container, {
|
|
124
|
-
paddingBottom
|
|
125
|
-
}, style], [pinToBottom, resolved.container, paddingBottom, style]);
|
|
126
124
|
|
|
127
125
|
// Entrance / exit "pop up from bottom" animation — mirrors the Drawer. The
|
|
128
126
|
// sheet is bottom-anchored, so a positive `translateY` equal to the window
|
|
@@ -132,6 +130,11 @@ function ContentSheet({
|
|
|
132
130
|
const {
|
|
133
131
|
height: windowHeight
|
|
134
132
|
} = (0, _reactNative.useWindowDimensions)();
|
|
133
|
+
const maxHeight = windowHeight * maxHeightPercent;
|
|
134
|
+
const containerStyle = (0, _react.useMemo)(() => [pinToBottom ? pinnedStyle : null, resolved.container, {
|
|
135
|
+
maxHeight,
|
|
136
|
+
paddingBottom
|
|
137
|
+
}, style], [pinToBottom, resolved.container, maxHeight, paddingBottom, style]);
|
|
135
138
|
const entrance = (0, _reactNativeReanimated.useSharedValue)(windowHeight);
|
|
136
139
|
(0, _react.useEffect)(() => {
|
|
137
140
|
entrance.value = (0, _reactNativeReanimated.withSpring)(visible ? 0 : windowHeight, SPRING_CONFIG);
|
|
@@ -155,6 +158,7 @@ function ContentSheet({
|
|
|
155
158
|
style: containerStyle,
|
|
156
159
|
spacing: keyboardSpacing,
|
|
157
160
|
entrance: entrance,
|
|
161
|
+
maxHeight: maxHeight,
|
|
158
162
|
...rest,
|
|
159
163
|
children: [header, processedChildren]
|
|
160
164
|
});
|
|
@@ -162,6 +166,7 @@ function ContentSheet({
|
|
|
162
166
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(AnimatedSheet, {
|
|
163
167
|
style: containerStyle,
|
|
164
168
|
entrance: entrance,
|
|
169
|
+
maxHeight: maxHeight,
|
|
165
170
|
...rest,
|
|
166
171
|
children: [header, processedChildren]
|
|
167
172
|
});
|
|
@@ -178,6 +183,7 @@ function KeyboardAwareSheet({
|
|
|
178
183
|
style,
|
|
179
184
|
spacing,
|
|
180
185
|
entrance,
|
|
186
|
+
maxHeight,
|
|
181
187
|
children,
|
|
182
188
|
...rest
|
|
183
189
|
}) {
|
|
@@ -194,7 +200,15 @@ function KeyboardAwareSheet({
|
|
|
194
200
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeReanimated.default.View, {
|
|
195
201
|
style: [style, animatedStyle],
|
|
196
202
|
...rest,
|
|
197
|
-
children:
|
|
203
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
|
|
204
|
+
style: {
|
|
205
|
+
maxHeight
|
|
206
|
+
},
|
|
207
|
+
bounces: false,
|
|
208
|
+
showsVerticalScrollIndicator: false,
|
|
209
|
+
keyboardShouldPersistTaps: "handled",
|
|
210
|
+
children: children
|
|
211
|
+
})
|
|
198
212
|
});
|
|
199
213
|
}
|
|
200
214
|
|
|
@@ -206,6 +220,7 @@ function KeyboardAwareSheet({
|
|
|
206
220
|
function AnimatedSheet({
|
|
207
221
|
style,
|
|
208
222
|
entrance,
|
|
223
|
+
maxHeight,
|
|
209
224
|
children,
|
|
210
225
|
...rest
|
|
211
226
|
}) {
|
|
@@ -219,7 +234,15 @@ function AnimatedSheet({
|
|
|
219
234
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeReanimated.default.View, {
|
|
220
235
|
style: [style, animatedStyle],
|
|
221
236
|
...rest,
|
|
222
|
-
children:
|
|
237
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
|
|
238
|
+
style: {
|
|
239
|
+
maxHeight
|
|
240
|
+
},
|
|
241
|
+
bounces: false,
|
|
242
|
+
showsVerticalScrollIndicator: false,
|
|
243
|
+
keyboardShouldPersistTaps: "handled",
|
|
244
|
+
children: children
|
|
245
|
+
})
|
|
223
246
|
});
|
|
224
247
|
}
|
|
225
248
|
var _default = exports.default = ContentSheet;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _react = _interopRequireDefault(require("react"));
|
|
8
|
+
var _reactNative = require("react-native");
|
|
9
|
+
var _figmaVariablesResolver = require("../../design-tokens/figma-variables-resolver");
|
|
10
|
+
var _reactUtils = require("../../utils/react-utils");
|
|
11
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
12
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
13
|
+
/**
|
|
14
|
+
* Counter Badge — a small token-driven pill that displays a value.
|
|
15
|
+
*
|
|
16
|
+
* All visual attributes resolve from the Figma `counterBadge/*` tokens via
|
|
17
|
+
* `getVariableByName`.
|
|
18
|
+
*/
|
|
19
|
+
function CounterBadge({
|
|
20
|
+
value = '99',
|
|
21
|
+
modes = _reactUtils.EMPTY_MODES,
|
|
22
|
+
style,
|
|
23
|
+
...rest
|
|
24
|
+
}) {
|
|
25
|
+
// Resolve token values (fall back to the Figma defaults for the single
|
|
26
|
+
// `Default` mode so the badge still renders if a token is missing).
|
|
27
|
+
const backgroundColor = (0, _figmaVariablesResolver.getVariableByName)('counterBadge/background', modes) ?? '#ebebec';
|
|
28
|
+
const foreground = (0, _figmaVariablesResolver.getVariableByName)('counterBadge/foreground', modes) ?? '#0c0d10';
|
|
29
|
+
const fontFamily = (0, _figmaVariablesResolver.getVariableByName)('counterBadge/fontFamily', modes) ?? 'JioType Var';
|
|
30
|
+
const fontWeightRaw = (0, _figmaVariablesResolver.getVariableByName)('counterBadge/fontWeight', modes) ?? 500;
|
|
31
|
+
const fontWeight = typeof fontWeightRaw === 'number' ? String(fontWeightRaw) : fontWeightRaw;
|
|
32
|
+
const fontSize = Number((0, _figmaVariablesResolver.getVariableByName)('counterBadge/fontSize', modes) ?? 8);
|
|
33
|
+
const lineHeight = Number((0, _figmaVariablesResolver.getVariableByName)('counterBadge/lineHeight', modes) ?? 8);
|
|
34
|
+
const paddingHorizontal = Number((0, _figmaVariablesResolver.getVariableByName)('counterBadge/padding/horizontal', modes) ?? 4);
|
|
35
|
+
// The Figma design pins vertical padding to 4px (matching horizontal), but the
|
|
36
|
+
// committed variables snapshot resolves `counterBadge/padding/vertical` to 5px
|
|
37
|
+
// (a stale export). Figma is the source of truth here, so hardcode 4px rather
|
|
38
|
+
// than trust the token, keeping the badge one pixel shorter to match the design.
|
|
39
|
+
const paddingVertical = 4;
|
|
40
|
+
const borderRadius = Number((0, _figmaVariablesResolver.getVariableByName)('counterBadge/radius', modes) ?? 999);
|
|
41
|
+
const containerStyle = {
|
|
42
|
+
backgroundColor,
|
|
43
|
+
paddingHorizontal,
|
|
44
|
+
paddingVertical,
|
|
45
|
+
borderRadius,
|
|
46
|
+
alignSelf: 'flex-start',
|
|
47
|
+
alignItems: 'center',
|
|
48
|
+
justifyContent: 'center',
|
|
49
|
+
...(style || {})
|
|
50
|
+
};
|
|
51
|
+
const textStyle = {
|
|
52
|
+
color: foreground,
|
|
53
|
+
fontFamily,
|
|
54
|
+
fontWeight: fontWeight,
|
|
55
|
+
fontSize,
|
|
56
|
+
lineHeight,
|
|
57
|
+
textAlign: 'center'
|
|
58
|
+
};
|
|
59
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
60
|
+
style: containerStyle,
|
|
61
|
+
...rest,
|
|
62
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
63
|
+
style: WRAP_STYLE,
|
|
64
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
65
|
+
style: textStyle,
|
|
66
|
+
children: value
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Figma `wrap` node (data-node-id 7701:10879): min-width 10, centered.
|
|
73
|
+
const WRAP_STYLE = {
|
|
74
|
+
minWidth: 10,
|
|
75
|
+
alignItems: 'center',
|
|
76
|
+
justifyContent: 'center'
|
|
77
|
+
};
|
|
78
|
+
var _default = exports.default = CounterBadge;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
8
|
+
var _reactNative = require("react-native");
|
|
9
|
+
var _figmaVariablesResolver = require("../../design-tokens/figma-variables-resolver");
|
|
10
|
+
var _Icon = _interopRequireDefault(require("../../icons/Icon"));
|
|
11
|
+
var _GlassFill = _interopRequireDefault(require("../../utils/GlassFill/GlassFill"));
|
|
12
|
+
var _webPlatformUtils = require("../../utils/web-platform-utils");
|
|
13
|
+
var _reactUtils = require("../../utils/react-utils");
|
|
14
|
+
var _Skeleton = _interopRequireDefault(require("../../skeleton/Skeleton"));
|
|
15
|
+
var _SkeletonGroup = require("../../skeleton/SkeletonGroup");
|
|
16
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
17
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
18
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
19
|
+
const IS_WEB = _reactNative.Platform.OS === 'web';
|
|
20
|
+
const IS_IOS = _reactNative.Platform.OS === 'ios';
|
|
21
|
+
const PRESS_DELAY = IS_IOS ? 130 : 0;
|
|
22
|
+
const pressedOverlayStyle = {
|
|
23
|
+
opacity: 0.7
|
|
24
|
+
};
|
|
25
|
+
// The `favoriteToggle/*` tokens are not yet in the committed variables snapshot,
|
|
26
|
+
// so `getVariableByName` returns undefined and these Figma-node values are used
|
|
27
|
+
// as fallbacks. Once the token export is re-synced the resolver takes over.
|
|
28
|
+
function resolveTokens(modes, isActive) {
|
|
29
|
+
const width = Number((0, _figmaVariablesResolver.getVariableByName)('favoriteToggle/width', modes) ?? 29);
|
|
30
|
+
const height = Number((0, _figmaVariablesResolver.getVariableByName)('favoriteToggle/height', modes) ?? 29);
|
|
31
|
+
const radiusRaw = Number((0, _figmaVariablesResolver.getVariableByName)('favoriteToggle/icon/radius', modes) ?? 9999);
|
|
32
|
+
const iconSize = Number((0, _figmaVariablesResolver.getVariableByName)('favoriteToggle/icon/width', modes) ?? 20);
|
|
33
|
+
// 9999 is the design-token sentinel for "perfect circle".
|
|
34
|
+
const size = Math.max(width, height);
|
|
35
|
+
const borderRadius = radiusRaw >= 9999 ? size / 2 : radiusRaw;
|
|
36
|
+
|
|
37
|
+
// Background + icon color differ per variant. In Figma this is driven by the
|
|
38
|
+
// `isActive` collection (modes `'False'` / `'True'`), so we pass that mode
|
|
39
|
+
// through by its string name. Until the `color/favoriteToggle/*` tokens are
|
|
40
|
+
// exported the resolver returns undefined and the fallbacks below — which
|
|
41
|
+
// encode the two Figma variants directly — take over.
|
|
42
|
+
const isActiveMode = {
|
|
43
|
+
...modes,
|
|
44
|
+
isActive: isActive ? 'True' : 'False'
|
|
45
|
+
};
|
|
46
|
+
const backgroundColor = (0, _figmaVariablesResolver.getVariableByName)('color/favoriteToggle/background/color', isActiveMode) ?? (isActive ? '#ffffff' : '#ffffff33');
|
|
47
|
+
const iconColor = (0, _figmaVariablesResolver.getVariableByName)('color/favoriteToggle/icon/color', isActiveMode) ?? (isActive ? '#cea15a' : '#ffffff');
|
|
48
|
+
|
|
49
|
+
// Figma `blur/minimal` (px radius) -> GlassFill's 0-100 intensity scale, the
|
|
50
|
+
// same mapping Badge's glass path uses.
|
|
51
|
+
const blurRaw = Number((0, _figmaVariablesResolver.getVariableByName)('blur/minimal', modes) ?? 29);
|
|
52
|
+
const blurIntensity = Math.max(0, Math.min(100, Math.round(blurRaw)));
|
|
53
|
+
return {
|
|
54
|
+
size,
|
|
55
|
+
borderRadius,
|
|
56
|
+
backgroundColor,
|
|
57
|
+
iconColor,
|
|
58
|
+
iconSize,
|
|
59
|
+
blurIntensity
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Circular glass favorite (heart) toggle, mirroring the Figma `Favorite Toggle`
|
|
65
|
+
* component set's boolean `isActive` variant:
|
|
66
|
+
* - inactive: translucent frosted circle (`#ffffff33` over a `blur/minimal`
|
|
67
|
+
* backdrop) with a white heart.
|
|
68
|
+
* - active: solid white circle with a gold (`#cea15a`) heart.
|
|
69
|
+
* Both variants share the identical filled-heart glyph; only the background and
|
|
70
|
+
* icon colors change between states.
|
|
71
|
+
*
|
|
72
|
+
* Controlled or uncontrolled: pass `isActive` (+ `onChange`) to control it, or
|
|
73
|
+
* omit `isActive` and use `defaultActive` to let the component own the state.
|
|
74
|
+
*
|
|
75
|
+
* Visual attributes resolve from the `favoriteToggle/*` / `color/favoriteToggle/*`
|
|
76
|
+
* tokens via `getVariableByName`. Those tokens are not yet in the committed
|
|
77
|
+
* variables snapshot, so the component currently runs on the Figma-node values
|
|
78
|
+
* as fallbacks; once the tokens are exported (under the `isActive` collection,
|
|
79
|
+
* modes `False`/`True`) the resolver takes over.
|
|
80
|
+
*/
|
|
81
|
+
function FavoriteToggle({
|
|
82
|
+
isActive: controlledActive,
|
|
83
|
+
defaultActive = false,
|
|
84
|
+
icon = 'ic_favorite',
|
|
85
|
+
onChange,
|
|
86
|
+
onPress,
|
|
87
|
+
modes = _reactUtils.EMPTY_MODES,
|
|
88
|
+
disabled = false,
|
|
89
|
+
accessibilityLabel,
|
|
90
|
+
accessibilityHint,
|
|
91
|
+
accessibilityState,
|
|
92
|
+
webAccessibilityProps,
|
|
93
|
+
style,
|
|
94
|
+
loading,
|
|
95
|
+
...rest
|
|
96
|
+
}) {
|
|
97
|
+
// Controlled when `isActive` is supplied; otherwise own the state internally
|
|
98
|
+
// (mirrors the Toggle / SegmentedControl controlled-or-uncontrolled pattern).
|
|
99
|
+
const isControlled = controlledActive !== undefined;
|
|
100
|
+
const [internalActive, setInternalActive] = (0, _react.useState)(defaultActive);
|
|
101
|
+
const isActive = isControlled ? controlledActive : internalActive;
|
|
102
|
+
const tokens = (0, _react.useMemo)(() => resolveTokens(modes, isActive), [modes, isActive]);
|
|
103
|
+
const {
|
|
104
|
+
active: groupActive
|
|
105
|
+
} = (0, _SkeletonGroup.useSkeleton)();
|
|
106
|
+
const isLoading = loading ?? groupActive;
|
|
107
|
+
const handlePress = (0, _react.useCallback)(() => {
|
|
108
|
+
const next = !isActive;
|
|
109
|
+
if (!isControlled) {
|
|
110
|
+
setInternalActive(next);
|
|
111
|
+
}
|
|
112
|
+
onChange?.(next);
|
|
113
|
+
onPress?.();
|
|
114
|
+
}, [isActive, isControlled, onChange, onPress]);
|
|
115
|
+
const label = accessibilityLabel || 'Favorite';
|
|
116
|
+
const webProps = (0, _webPlatformUtils.usePressableWebSupport)({
|
|
117
|
+
restProps: rest,
|
|
118
|
+
onPress: disabled ? undefined : handlePress,
|
|
119
|
+
disabled,
|
|
120
|
+
accessibilityLabel: label,
|
|
121
|
+
webAccessibilityProps
|
|
122
|
+
});
|
|
123
|
+
const baseContainerStyle = {
|
|
124
|
+
width: tokens.size,
|
|
125
|
+
height: tokens.size,
|
|
126
|
+
borderRadius: tokens.borderRadius,
|
|
127
|
+
// Active is opaque white, so no glass is drawn and the background paints
|
|
128
|
+
// directly. Inactive keeps the container transparent so the GlassFill blur
|
|
129
|
+
// (clipped by overflow:hidden) shows through.
|
|
130
|
+
backgroundColor: isActive ? tokens.backgroundColor : 'transparent',
|
|
131
|
+
overflow: 'hidden',
|
|
132
|
+
alignItems: 'center',
|
|
133
|
+
justifyContent: 'center',
|
|
134
|
+
opacity: disabled ? 0.5 : 1
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// Declared before the loading short-circuit so hook order stays stable.
|
|
138
|
+
const styleCallback = (0, _react.useCallback)(({
|
|
139
|
+
pressed
|
|
140
|
+
}) => [baseContainerStyle, style, pressed && !disabled ? pressedOverlayStyle : null], [baseContainerStyle, style, disabled]);
|
|
141
|
+
if (isLoading) {
|
|
142
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_Skeleton.default, {
|
|
143
|
+
kind: "other",
|
|
144
|
+
width: tokens.size,
|
|
145
|
+
height: tokens.size,
|
|
146
|
+
style: [{
|
|
147
|
+
borderRadius: tokens.borderRadius
|
|
148
|
+
}, style],
|
|
149
|
+
modes: modes
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
|
|
153
|
+
accessibilityRole: IS_WEB ? 'switch' : 'button',
|
|
154
|
+
accessibilityLabel: undefined,
|
|
155
|
+
accessibilityHint: accessibilityHint,
|
|
156
|
+
accessibilityState: {
|
|
157
|
+
disabled,
|
|
158
|
+
checked: isActive,
|
|
159
|
+
...accessibilityState
|
|
160
|
+
},
|
|
161
|
+
onPress: disabled ? undefined : handlePress,
|
|
162
|
+
disabled: disabled,
|
|
163
|
+
unstable_pressDelay: PRESS_DELAY,
|
|
164
|
+
style: styleCallback,
|
|
165
|
+
...webProps,
|
|
166
|
+
children: [!isActive ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_GlassFill.default, {
|
|
167
|
+
tint: "light",
|
|
168
|
+
intensity: tokens.blurIntensity,
|
|
169
|
+
overlayColor: tokens.backgroundColor,
|
|
170
|
+
androidTintWash: false
|
|
171
|
+
}) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_Icon.default, {
|
|
172
|
+
name: icon,
|
|
173
|
+
size: tokens.iconSize,
|
|
174
|
+
color: tokens.iconColor,
|
|
175
|
+
accessibilityElementsHidden: true,
|
|
176
|
+
importantForAccessibility: "no"
|
|
177
|
+
})]
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
var _default = exports.default = /*#__PURE__*/_react.default.memo(FavoriteToggle);
|