@momo-kits/foundation 0.167.1-duo.14 → 0.167.1-duo.16
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.
|
@@ -34,8 +34,6 @@ const DEFAULT_TOOLBAR_HEIGHT =
|
|
|
34
34
|
DuoMetrics.controlSize + Spacing.M + 2 * DuoMetrics.itemPitch;
|
|
35
35
|
// Back, then the single button the toolbar folds into
|
|
36
36
|
const COMPRESSED_TOOLBAR_HEIGHT = 2 * DuoMetrics.controlSize + Spacing.M;
|
|
37
|
-
// Back alone, while a rail opened from the compressed state takes the toolbar slot
|
|
38
|
-
const BACK_ONLY_HEIGHT = DuoMetrics.controlSize;
|
|
39
37
|
|
|
40
38
|
const NO_COMPRESSION: DuoBarCompression = {
|
|
41
39
|
toolbarCompressed: false,
|
|
@@ -107,7 +105,9 @@ const DuoBarCompressionProvider: React.FC<{
|
|
|
107
105
|
expandTabBar: () => setOpened('tabBar'),
|
|
108
106
|
setToolbarHeight,
|
|
109
107
|
};
|
|
110
|
-
|
|
108
|
+
// an opened rail always shows, even when only the gap between the groups is short:
|
|
109
|
+
// it closes again once a tab is picked
|
|
110
|
+
if (opened === 'tabBar') {
|
|
111
111
|
return {
|
|
112
112
|
...open,
|
|
113
113
|
toolbarCompressed: true,
|
|
@@ -13,7 +13,7 @@ import Animated, {
|
|
|
13
13
|
withSpring,
|
|
14
14
|
} from 'react-native-reanimated';
|
|
15
15
|
import { Colors, Radius, Spacing } from '../../Consts';
|
|
16
|
-
import { ApplicationContext } from '../../Context';
|
|
16
|
+
import { ApplicationContext, MiniAppContext } from '../../Context';
|
|
17
17
|
import { Icon } from '../../Icon';
|
|
18
18
|
import { DuoMetrics, useScreenLayout } from '../../Layout/utils';
|
|
19
19
|
import { Text } from '../../Text';
|
|
@@ -33,6 +33,7 @@ const ICON_SIZE = 28;
|
|
|
33
33
|
const QR_ICON_SIZE = 24;
|
|
34
34
|
const RAIL_PADDING = 6;
|
|
35
35
|
const RAIL_GAP = Spacing.XXS;
|
|
36
|
+
const RAIL_PITCH = ITEM_SIZE + RAIL_GAP;
|
|
36
37
|
// selection bubble overflows the 48 rail cell by 5 above and below (Figma MT2)
|
|
37
38
|
const RAIL_BUBBLE_WIDTH = 44;
|
|
38
39
|
const RAIL_BUBBLE_HEIGHT = 58;
|
|
@@ -104,7 +105,7 @@ const SELECTION_TINT_ALPHA = '14';
|
|
|
104
105
|
* translucent glass capsule under the selected floating tab with the Figma pink tint on
|
|
105
106
|
* top; the tint is a separate layer since a glass tint color renders opaque
|
|
106
107
|
*/
|
|
107
|
-
const SelectionMaterial: React.FC = () => {
|
|
108
|
+
const SelectionMaterial: React.FC<{ radius: number }> = ({ radius }) => {
|
|
108
109
|
const { theme } = useContext(ApplicationContext);
|
|
109
110
|
return (
|
|
110
111
|
<>
|
|
@@ -112,7 +113,7 @@ const SelectionMaterial: React.FC = () => {
|
|
|
112
113
|
<GlassView
|
|
113
114
|
pointerEvents={'none'}
|
|
114
115
|
effect={'glass'}
|
|
115
|
-
cornerRadius={
|
|
116
|
+
cornerRadius={radius}
|
|
116
117
|
style={StyleSheet.absoluteFill}
|
|
117
118
|
/>
|
|
118
119
|
)}
|
|
@@ -120,14 +121,44 @@ const SelectionMaterial: React.FC = () => {
|
|
|
120
121
|
pointerEvents={'none'}
|
|
121
122
|
style={[
|
|
122
123
|
StyleSheet.absoluteFill,
|
|
123
|
-
|
|
124
|
-
|
|
124
|
+
{
|
|
125
|
+
borderRadius: radius,
|
|
126
|
+
backgroundColor: theme.colors.primary + SELECTION_TINT_ALPHA,
|
|
127
|
+
},
|
|
125
128
|
]}
|
|
126
129
|
/>
|
|
127
130
|
</>
|
|
128
131
|
);
|
|
129
132
|
};
|
|
130
133
|
|
|
134
|
+
/**
|
|
135
|
+
* tab label at its design size: like the system tab bar it doesn't grow with the wide
|
|
136
|
+
* Duo displays, which Text otherwise scales up to 5pt
|
|
137
|
+
*/
|
|
138
|
+
const TabLabel: React.FC<{
|
|
139
|
+
label: string;
|
|
140
|
+
color: string;
|
|
141
|
+
typography: 'label_xs_medium';
|
|
142
|
+
}> = ({ label, color, typography }) => {
|
|
143
|
+
const context = useContext<any>(MiniAppContext);
|
|
144
|
+
return (
|
|
145
|
+
<MiniAppContext.Provider
|
|
146
|
+
value={{
|
|
147
|
+
...context,
|
|
148
|
+
fontScale: { useOSScaleRate: false, userScaleRate: 1 },
|
|
149
|
+
}}
|
|
150
|
+
>
|
|
151
|
+
<Text
|
|
152
|
+
numberOfLines={1}
|
|
153
|
+
typography={typography}
|
|
154
|
+
color={color}
|
|
155
|
+
>
|
|
156
|
+
{label}
|
|
157
|
+
</Text>
|
|
158
|
+
</MiniAppContext.Provider>
|
|
159
|
+
);
|
|
160
|
+
};
|
|
161
|
+
|
|
131
162
|
const getTabItems = ({
|
|
132
163
|
state,
|
|
133
164
|
descriptors,
|
|
@@ -189,41 +220,52 @@ const QrIcon: React.FC<{ icon: FloatingButtonProps['icon']; size: number }> = ({
|
|
|
189
220
|
type TabItem = ReturnType<typeof getTabItems>[number];
|
|
190
221
|
|
|
191
222
|
/**
|
|
192
|
-
* glass column of tab items with the sliding selection bubble
|
|
223
|
+
* glass column of icon-only tab items with the sliding selection bubble
|
|
193
224
|
*/
|
|
194
225
|
const Rail: React.FC<{
|
|
195
226
|
items: TabItem[];
|
|
196
227
|
index: number;
|
|
197
228
|
onItemPress?: () => void;
|
|
198
229
|
}> = ({ items, index, onItemPress }) => {
|
|
199
|
-
const
|
|
200
|
-
const
|
|
230
|
+
const bubbleOffset = useSharedValue(index * RAIL_PITCH);
|
|
231
|
+
const lens = useSharedValue(0);
|
|
201
232
|
|
|
202
233
|
useEffect(() => {
|
|
203
|
-
bubbleOffset.value = withSpring(index *
|
|
234
|
+
bubbleOffset.value = withSpring(index * RAIL_PITCH, SPRING);
|
|
204
235
|
}, [index, bubbleOffset]);
|
|
205
236
|
|
|
206
237
|
const bubbleStyle = useAnimatedStyle(() => ({
|
|
207
|
-
transform: [
|
|
238
|
+
transform: [
|
|
239
|
+
{ translateY: bubbleOffset.value },
|
|
240
|
+
{ scaleX: 1 + lens.value * (LENS_SCALE_X - 1) },
|
|
241
|
+
{ scaleY: 1 + lens.value * (LENS_SCALE_Y - 1) },
|
|
242
|
+
],
|
|
208
243
|
}));
|
|
209
244
|
|
|
210
245
|
return (
|
|
211
246
|
<View style={styles.rail} accessibilityLabel={'bottom_tab_bar'}>
|
|
212
|
-
<BarMaterial radius={
|
|
247
|
+
<BarMaterial radius={ITEM_SIZE / 2} interactive />
|
|
213
248
|
<Animated.View
|
|
214
249
|
pointerEvents={'none'}
|
|
215
|
-
style={[
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
/>
|
|
221
|
-
{items.map(item => (
|
|
250
|
+
style={[styles.railBubble, bubbleStyle]}
|
|
251
|
+
>
|
|
252
|
+
<SelectionMaterial radius={RAIL_BUBBLE_WIDTH / 2} />
|
|
253
|
+
</Animated.View>
|
|
254
|
+
{items.map((item, itemIndex) => (
|
|
222
255
|
<TouchableOpacity
|
|
223
256
|
key={item.route.key}
|
|
224
257
|
accessibilityRole={'tab'}
|
|
225
258
|
accessibilityLabel={item.label}
|
|
226
259
|
accessibilityState={{ selected: item.focused }}
|
|
260
|
+
activeOpacity={1}
|
|
261
|
+
onPressIn={() => {
|
|
262
|
+
bubbleOffset.value = withSpring(itemIndex * RAIL_PITCH, SPRING);
|
|
263
|
+
lens.value = withSpring(1, SPRING);
|
|
264
|
+
}}
|
|
265
|
+
onPressOut={() => {
|
|
266
|
+
bubbleOffset.value = withSpring(index * RAIL_PITCH, SPRING);
|
|
267
|
+
lens.value = withSpring(0, SPRING);
|
|
268
|
+
}}
|
|
227
269
|
onPress={() => {
|
|
228
270
|
item.onPress();
|
|
229
271
|
onItemPress?.();
|
|
@@ -368,7 +410,7 @@ const DuoFloatingTabBar: React.FC<Props> = props => {
|
|
|
368
410
|
pointerEvents={'none'}
|
|
369
411
|
style={[styles.floatingBubble, { width: cellWidth }, bubbleStyle]}
|
|
370
412
|
>
|
|
371
|
-
<SelectionMaterial />
|
|
413
|
+
<SelectionMaterial radius={FLOATING_BUBBLE_RADIUS} />
|
|
372
414
|
</Animated.View>
|
|
373
415
|
)}
|
|
374
416
|
{items.map((item, index) => (
|
|
@@ -393,14 +435,11 @@ const DuoFloatingTabBar: React.FC<Props> = props => {
|
|
|
393
435
|
style={styles.floatingItem}
|
|
394
436
|
>
|
|
395
437
|
{item.icon}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
typography={'label_xxs'}
|
|
438
|
+
<TabLabel
|
|
439
|
+
label={item.label}
|
|
440
|
+
typography={'label_xs_medium'}
|
|
400
441
|
color={item.focused ? activeTintColor : inactiveTintColor}
|
|
401
|
-
|
|
402
|
-
{item.label}
|
|
403
|
-
</Text>
|
|
442
|
+
/>
|
|
404
443
|
</TouchableOpacity>
|
|
405
444
|
))}
|
|
406
445
|
</View>
|
|
@@ -461,7 +500,6 @@ const styles = StyleSheet.create({
|
|
|
461
500
|
left: (ITEM_SIZE - RAIL_BUBBLE_WIDTH) / 2,
|
|
462
501
|
width: RAIL_BUBBLE_WIDTH,
|
|
463
502
|
height: RAIL_BUBBLE_HEIGHT,
|
|
464
|
-
borderRadius: RAIL_BUBBLE_WIDTH / 2,
|
|
465
503
|
},
|
|
466
504
|
railItem: {
|
|
467
505
|
width: ITEM_SIZE,
|
|
@@ -497,9 +535,6 @@ const styles = StyleSheet.create({
|
|
|
497
535
|
bottom: 0,
|
|
498
536
|
left: 0,
|
|
499
537
|
},
|
|
500
|
-
floatingBubbleFill: {
|
|
501
|
-
borderRadius: FLOATING_BUBBLE_RADIUS,
|
|
502
|
-
},
|
|
503
538
|
floatingItem: {
|
|
504
539
|
flex: 1,
|
|
505
540
|
alignItems: 'center',
|
|
@@ -316,7 +316,7 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
316
316
|
<DuoTabBar
|
|
317
317
|
{...props}
|
|
318
318
|
floatingButton={floatingButton}
|
|
319
|
-
inactiveTintColor={theme.colors.text.
|
|
319
|
+
inactiveTintColor={theme.colors.text.hint}
|
|
320
320
|
activeTintColor={theme.colors.primary}
|
|
321
321
|
/>
|
|
322
322
|
) : (
|