@momo-kits/foundation 0.169.7-beta.4 → 0.169.7-beta.5
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/Application/BottomSheet.hooks.ts +8 -8
- package/Application/BottomSheet.parts.tsx +44 -34
- package/Application/BottomSheet.tsx +40 -37
- package/Application/BottomTab/ActiveIndicator.tsx +20 -15
- package/Application/BottomTab/BottomTabBar.styles.ts +18 -1
- package/Application/BottomTab/BottomTabBar.tsx +57 -72
- package/Application/BottomTab/BottomTabInset.tsx +8 -4
- package/Application/BottomTab/CurvedContainer.tsx +22 -14
- package/Application/BottomTab/CustomBottomTabItem.tsx +15 -15
- package/Application/BottomTab/DuoBarCompression.tsx +13 -19
- package/Application/BottomTab/DuoRailTabBar.tsx +20 -24
- package/Application/BottomTab/DuoTabBar.tsx +23 -13
- package/Application/BottomTab/TabBarIcon.tsx +15 -29
- package/Application/BottomTab/TabScreen.tsx +1 -5
- package/Application/BottomTab/index.tsx +33 -30
- package/Application/BottomTab/metrics.ts +46 -26
- package/Application/BottomTab/tabItems.ts +1 -1
- package/Application/BottomTab/usePressLift.ts +2 -2
- package/Application/Components/BackgroundImageView.tsx +4 -0
- package/Application/Components/DuoGlassBackground.tsx +5 -4
- package/Application/Components/HeaderAnimated.tsx +4 -0
- package/Application/Components/HeaderBackground.tsx +4 -0
- package/Application/Components/HeaderExtendHeader.tsx +11 -10
- package/Application/Components/HeaderLeft.tsx +2 -1
- package/Application/Components/HeaderRight.tsx +2 -1
- package/Application/Components/HeaderTitle.shared.ts +11 -9
- package/Application/Components/HeaderTitle.tsx +4 -3
- package/Application/Components/HeaderToolkitAction.tsx +17 -20
- package/Application/Components/NavigationButton.tsx +2 -1
- package/Application/Components/SearchHeader.tsx +17 -10
- package/Application/Components/TitleJourney.tsx +2 -2
- package/Application/Components/TitleLocation.tsx +2 -2
- package/Application/Components/TitleUser.tsx +2 -2
- package/Application/Components/useToolkitActions.tsx +17 -32
- package/Application/Localize.ts +7 -0
- package/Application/ModalScreen.tsx +10 -10
- package/Application/Navigation.ts +14 -16
- package/Application/NavigationContainer.tsx +19 -18
- package/Application/Navigator.ts +20 -48
- package/Application/ScaleSizeProvider.tsx +13 -6
- package/Application/StackScreen.tracking.ts +11 -33
- package/Application/StackScreen.tsx +10 -54
- package/Application/TooltipPortal.tsx +12 -5
- package/Application/WidgetContainer.tsx +10 -4
- package/Application/modalLayout.ts +13 -14
- package/Application/navigationOptions.tsx +21 -17
- package/Application/types.tabs.ts +0 -1
- package/Badge/Badge.tsx +5 -0
- package/Badge/BadgeDot.tsx +1 -0
- package/Badge/BadgeDotAnimation.tsx +15 -7
- package/Badge/BadgeRibbon.tsx +46 -38
- package/Badge/Shape.tsx +2 -0
- package/Badge/types.ts +2 -2
- package/Button/index.tsx +5 -16
- package/Button/utils.ts +9 -15
- package/CheckBox/index.tsx +4 -0
- package/Consts/theme.ts +10 -10
- package/Context/index.ts +5 -1
- package/Divider/DashDivider.tsx +1 -0
- package/Divider/index.tsx +4 -0
- package/FoundationList/index.tsx +4 -0
- package/Icon/index.tsx +6 -6
- package/Icon/types.ts +1 -1
- package/IconButton/index.tsx +4 -12
- package/Image/index.tsx +9 -7
- package/Input/Input.tsx +8 -9
- package/Input/InputDropDown.tsx +10 -12
- package/Input/InputMoney.tsx +12 -8
- package/Input/InputMoneyView.tsx +14 -7
- package/Input/InputOTP.tsx +35 -33
- package/Input/InputPhoneNumber.tsx +8 -9
- package/Input/InputSearch.tsx +4 -6
- package/Input/InputTextArea.tsx +4 -0
- package/Input/SearchTextTyping.tsx +4 -0
- package/Input/SystemTextInput.tsx +7 -2
- package/Input/TextTyping.tsx +4 -0
- package/Input/common.tsx +26 -12
- package/Input/index.tsx +17 -20
- package/Input/moneyUtils.ts +10 -4
- package/Input/styles.ts +0 -5
- package/Input/utils.ts +14 -12
- package/Layout/Card.tsx +7 -4
- package/Layout/FloatingButton.tsx +4 -0
- package/Layout/Item.tsx +6 -2
- package/Layout/ItemList.tsx +9 -7
- package/Layout/ItemSectionList.tsx +2 -1
- package/Layout/ReservedRegions.tsx +25 -25
- package/Layout/Screen.tsx +56 -58
- package/Layout/Section.tsx +8 -5
- package/Layout/TrackingScope.tsx +1 -0
- package/Layout/screenContent.tsx +4 -2
- package/Layout/screenFooter.tsx +28 -18
- package/Layout/screenHeader.tsx +34 -31
- package/Layout/screenQuickReturn.tsx +17 -20
- package/Layout/screenScroll.ts +4 -7
- package/Layout/screenTypes.ts +3 -2
- package/Layout/types.ts +2 -4
- package/Layout/utils.ts +31 -37
- package/Loader/DotLoader.tsx +4 -0
- package/Loader/Loader.tsx +4 -0
- package/Loader/ProgressBar.tsx +1 -0
- package/Loader/Spinner.tsx +4 -0
- package/Loader/utils.ts +11 -7
- package/Pagination/Dot.tsx +4 -0
- package/Pagination/PaginationDot.tsx +1 -0
- package/Pagination/PaginationNumber.tsx +5 -3
- package/Pagination/PaginationScroll.tsx +5 -0
- package/Pagination/PaginationWhiteDot.tsx +1 -0
- package/Pagination/index.tsx +4 -0
- package/Pagination/types.ts +2 -2
- package/Popup/PopupNotify.tsx +44 -48
- package/Popup/PopupPromotion.tsx +4 -7
- package/Popup/types.ts +4 -4
- package/Popup/utils.ts +4 -0
- package/Radio/index.tsx +4 -0
- package/Skeleton/index.tsx +8 -0
- package/Switch/index.tsx +1 -0
- package/Tag/index.tsx +5 -1
- package/Tag/types.ts +3 -3
- package/Text/index.tsx +14 -3
- package/Text/styles.ts +1 -1
- package/Text/types.ts +1 -6
- package/Text/utils.ts +20 -4
- package/Title/index.tsx +41 -32
- package/ViewBoundary/index.tsx +4 -2
- package/index.ts +0 -6
- package/package.json +1 -1
- package/Application/KitAdoption/AdoptionBadge.tsx +0 -119
- package/Application/KitAdoption/AdoptionProbe.tsx +0 -166
- package/Application/KitAdoption/AdoptionStore.ts +0 -80
- package/Application/KitAdoption/Authorship/utils.ts +0 -137
- package/Application/KitAdoption/Slots/utils.ts +0 -68
- package/Application/KitAdoption/constants.ts +0 -120
- package/Application/KitAdoption/index.ts +0 -12
- package/Application/KitAdoption/styles.ts +0 -55
- package/Application/KitAdoption/types.ts +0 -61
- package/Application/KitAdoption/utils.ts +0 -376
- package/Application/__tests__/KitAdoption.integration.test.tsx +0 -253
- package/Application/__tests__/KitAdoption.test.ts +0 -283
- package/Application/__tests__/KitAdoptionAuthorship.test.ts +0 -132
- package/Application/__tests__/KitAdoptionStore.test.ts +0 -110
- package/Application/__tests__/KitAdoptionStructural.test.ts +0 -193
- package/Application/__tests__/modalLayout.test.ts +0 -128
|
@@ -51,8 +51,10 @@ export const useBottomSheetLayout = () => {
|
|
|
51
51
|
};
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Keyboard lift of the sheet on iPhone Duo, where KeyboardAvoidingView pushes
|
|
56
|
+
* the whole sheet past the top; the sheet lifts by it and shrinks its maxHeight.
|
|
57
|
+
*/
|
|
56
58
|
export const useDuoKeyboardLift = (
|
|
57
59
|
isDuo: boolean,
|
|
58
60
|
useKeyboardAvoidingView: boolean,
|
|
@@ -136,6 +138,10 @@ type SheetDismissParams = {
|
|
|
136
138
|
action: MutableRefObject<undefined | string>;
|
|
137
139
|
};
|
|
138
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Close handlers of a bottom sheet. A non-forced dismiss (scrim tap, hardware
|
|
143
|
+
* back) is ignored while `barrierDismissible` is set.
|
|
144
|
+
*/
|
|
139
145
|
export const useSheetDismiss = ({
|
|
140
146
|
barrierDismissible,
|
|
141
147
|
closeAnimation,
|
|
@@ -143,9 +149,6 @@ export const useSheetDismiss = ({
|
|
|
143
149
|
leftOptions,
|
|
144
150
|
action,
|
|
145
151
|
}: SheetDismissParams) => {
|
|
146
|
-
/**
|
|
147
|
-
* handler dismiss
|
|
148
|
-
*/
|
|
149
152
|
const onDismiss = useCallback(
|
|
150
153
|
(force = false, callback?: () => void) => {
|
|
151
154
|
if (barrierDismissible && !force) {
|
|
@@ -161,9 +164,6 @@ export const useSheetDismiss = ({
|
|
|
161
164
|
[barrierDismissible, closeAnimation, navigator],
|
|
162
165
|
);
|
|
163
166
|
|
|
164
|
-
/**
|
|
165
|
-
* on request close
|
|
166
|
-
*/
|
|
167
167
|
const onRequestClose = useCallback(
|
|
168
168
|
(callback?: () => void) => {
|
|
169
169
|
onDismiss(true, callback);
|
|
@@ -34,42 +34,52 @@ export const renderSheetHeader = ({
|
|
|
34
34
|
iconBack,
|
|
35
35
|
options,
|
|
36
36
|
leftOptions,
|
|
37
|
-
}: SheetHeaderParams) =>
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
<View
|
|
43
|
-
style={[
|
|
44
|
-
styles.header,
|
|
45
|
-
useDivider && styles.headerDivider,
|
|
46
|
-
useDivider && {
|
|
47
|
-
borderBottomColor: theme.colors.border.default,
|
|
48
|
-
},
|
|
49
|
-
]}
|
|
50
|
-
>
|
|
51
|
-
{showBack ? (
|
|
52
|
-
<ModalIconButton icon={iconBack} onPress={onPressBack} />
|
|
53
|
-
) : null}
|
|
37
|
+
}: SheetHeaderParams) => {
|
|
38
|
+
let backButton: React.ReactNode = null;
|
|
39
|
+
if (showBack) {
|
|
40
|
+
backButton = <ModalIconButton icon={iconBack} onPress={onPressBack} />;
|
|
41
|
+
}
|
|
54
42
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
43
|
+
let headerContent: React.ReactNode;
|
|
44
|
+
if (options.header) {
|
|
45
|
+
headerContent = <View style={Styles.flex}>{options.header}</View>;
|
|
46
|
+
} else {
|
|
47
|
+
headerContent = (
|
|
48
|
+
<>
|
|
49
|
+
{!leftOptions ? <View style={styles.iconButtonSpace} /> : null}
|
|
50
|
+
<Text
|
|
51
|
+
style={[Styles.flex, styles.title]}
|
|
52
|
+
numberOfLines={1}
|
|
53
|
+
typography="header_default_bold"
|
|
54
|
+
>
|
|
55
|
+
{options.title ?? ''}
|
|
56
|
+
</Text>
|
|
57
|
+
</>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<View {...panHandlers}>
|
|
63
|
+
<View style={{ height: INDICATOR_HEIGHT }}>
|
|
64
|
+
<View style={styles.indicator} />
|
|
65
|
+
</View>
|
|
66
|
+
<View
|
|
67
|
+
style={[
|
|
68
|
+
styles.header,
|
|
69
|
+
useDivider && styles.headerDivider,
|
|
70
|
+
useDivider && {
|
|
71
|
+
borderBottomColor: theme.colors.border.default,
|
|
72
|
+
},
|
|
73
|
+
]}
|
|
74
|
+
>
|
|
75
|
+
{backButton}
|
|
76
|
+
|
|
77
|
+
{headerContent}
|
|
78
|
+
<ModalIconButton icon="navigation_close" onPress={onPressClose} />
|
|
79
|
+
</View>
|
|
70
80
|
</View>
|
|
71
|
-
|
|
72
|
-
|
|
81
|
+
);
|
|
82
|
+
};
|
|
73
83
|
|
|
74
84
|
/**
|
|
75
85
|
* Footer pinned under the sheet content, `null` when the sheet has none.
|
|
@@ -18,7 +18,6 @@ import Animated, {
|
|
|
18
18
|
} from 'react-native-reanimated';
|
|
19
19
|
import { scheduleOnRN } from 'react-native-worklets';
|
|
20
20
|
import { DUO_ORIENTATIONS, styles } from './BottomSheet.styles';
|
|
21
|
-
import { AdoptionProbe } from './KitAdoption';
|
|
22
21
|
import {
|
|
23
22
|
useBottomSheetLayout,
|
|
24
23
|
useDuoKeyboardLift,
|
|
@@ -31,6 +30,10 @@ import {
|
|
|
31
30
|
renderSheetHeader,
|
|
32
31
|
} from './BottomSheet.parts';
|
|
33
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Bottom sheet route of the modal stack: slides the given screen up under a
|
|
35
|
+
* drag handle and header, and reports how it was closed through `onDismiss`.
|
|
36
|
+
*/
|
|
34
37
|
const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
35
38
|
const { theme, navigator } = useContext(ApplicationContext);
|
|
36
39
|
const context = useContext<any>(MiniAppContext);
|
|
@@ -99,8 +102,9 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
99
102
|
const panResponder = useRef(
|
|
100
103
|
PanResponder.create({
|
|
101
104
|
onStartShouldSetPanResponder: () => draggable,
|
|
102
|
-
onMoveShouldSetPanResponder: (_, gestureState) =>
|
|
103
|
-
draggable && gestureState.dy > 0
|
|
105
|
+
onMoveShouldSetPanResponder: (_, gestureState) => {
|
|
106
|
+
return draggable && gestureState.dy > 0;
|
|
107
|
+
},
|
|
104
108
|
onPanResponderMove: (_, gestureState) => {
|
|
105
109
|
if (gestureState.dy > 0) {
|
|
106
110
|
translateY.value = gestureState.dy;
|
|
@@ -132,10 +136,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
132
136
|
backgroundColor = theme.colors.background.surface;
|
|
133
137
|
}
|
|
134
138
|
|
|
135
|
-
|
|
136
|
-
* emit dismiss event, once: folding or rotating iPhone Duo changes the
|
|
137
|
-
* window height while the sheet stays open
|
|
138
|
-
*/
|
|
139
|
+
// Mount only, so folding or rotating iPhone Duo doesn't emit a dismiss.
|
|
139
140
|
useEffect(() => {
|
|
140
141
|
openAnimation();
|
|
141
142
|
return () => {
|
|
@@ -143,14 +144,14 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
143
144
|
};
|
|
144
145
|
}, [openAnimation]);
|
|
145
146
|
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
147
|
+
const handlers = useSheetDismiss({
|
|
148
|
+
barrierDismissible,
|
|
149
|
+
closeAnimation,
|
|
150
|
+
navigator,
|
|
151
|
+
leftOptions,
|
|
152
|
+
action,
|
|
153
|
+
});
|
|
154
|
+
const { onDismiss, onRequestClose, onPressBack, onPressClose } = handlers;
|
|
154
155
|
|
|
155
156
|
const showBack = !!leftOptions?.onPressIconLeft;
|
|
156
157
|
const iconBack = leftOptions?.iconLeft ?? 'ic_back';
|
|
@@ -174,18 +175,31 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
174
175
|
);
|
|
175
176
|
|
|
176
177
|
const screen = (
|
|
177
|
-
<
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
{...props}
|
|
183
|
-
{...props.route.params}
|
|
184
|
-
onRequestClose={onRequestClose}
|
|
185
|
-
/>
|
|
186
|
-
</AdoptionProbe>
|
|
178
|
+
<Screen
|
|
179
|
+
{...props}
|
|
180
|
+
{...props.route.params}
|
|
181
|
+
onRequestClose={onRequestClose}
|
|
182
|
+
/>
|
|
187
183
|
);
|
|
188
184
|
|
|
185
|
+
let content: React.ReactNode;
|
|
186
|
+
if (isFoldable) {
|
|
187
|
+
// Content shrinks so a tall sheet keeps its header and footer.
|
|
188
|
+
content = (
|
|
189
|
+
<View style={styles.shrink}>
|
|
190
|
+
{renderSheetHeader(headerParams)}
|
|
191
|
+
<View style={styles.shrink}>{screen}</View>
|
|
192
|
+
</View>
|
|
193
|
+
);
|
|
194
|
+
} else {
|
|
195
|
+
content = (
|
|
196
|
+
<>
|
|
197
|
+
{renderSheetHeader(headerParams)}
|
|
198
|
+
{screen}
|
|
199
|
+
</>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
189
203
|
return (
|
|
190
204
|
<Container
|
|
191
205
|
transparent
|
|
@@ -231,18 +245,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
231
245
|
}),
|
|
232
246
|
]}
|
|
233
247
|
>
|
|
234
|
-
{
|
|
235
|
-
// content shrinks so a tall sheet keeps its header and footer
|
|
236
|
-
<View style={styles.shrink}>
|
|
237
|
-
{renderSheetHeader(headerParams)}
|
|
238
|
-
<View style={styles.shrink}>{screen}</View>
|
|
239
|
-
</View>
|
|
240
|
-
) : (
|
|
241
|
-
<>
|
|
242
|
-
{renderSheetHeader(headerParams)}
|
|
243
|
-
{screen}
|
|
244
|
-
</>
|
|
245
|
-
)}
|
|
248
|
+
{content}
|
|
246
249
|
{renderSheetFooter({ footerComponent, theme })}
|
|
247
250
|
{useBottomInset && (
|
|
248
251
|
<View
|
|
@@ -5,6 +5,7 @@ import Animated, {
|
|
|
5
5
|
useSharedValue,
|
|
6
6
|
withTiming,
|
|
7
7
|
} from 'react-native-reanimated';
|
|
8
|
+
import { defaultTheme } from '../../Consts';
|
|
8
9
|
import { ApplicationContext } from '../../Context';
|
|
9
10
|
import { getIndicatorShadow, TabBarMotion } from './metrics';
|
|
10
11
|
|
|
@@ -19,8 +20,8 @@ type Props = {
|
|
|
19
20
|
};
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
|
-
*
|
|
23
|
-
* selected item
|
|
23
|
+
* The single active indicator of a bar (Background/tonal with inner shadows) that slides
|
|
24
|
+
* to the selected item. It lands in place on the first measure and slides from then on.
|
|
24
25
|
*/
|
|
25
26
|
const ActiveIndicator: React.FC<Props> = ({
|
|
26
27
|
axis,
|
|
@@ -49,18 +50,19 @@ const ActiveIndicator: React.FC<Props> = ({
|
|
|
49
50
|
position.value = withTiming(offset, TabBarMotion.indicator);
|
|
50
51
|
}, [offset, extent, position, size, visible]);
|
|
51
52
|
|
|
52
|
-
const animatedStyle = useAnimatedStyle(() =>
|
|
53
|
-
axis === 'x'
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
53
|
+
const animatedStyle = useAnimatedStyle(() => {
|
|
54
|
+
if (axis === 'x') {
|
|
55
|
+
return {
|
|
56
|
+
opacity: visible.value,
|
|
57
|
+
width: size.value,
|
|
58
|
+
transform: [{ translateX: position.value }],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
opacity: visible.value,
|
|
63
|
+
transform: [{ translateY: position.value }],
|
|
64
|
+
};
|
|
65
|
+
});
|
|
64
66
|
|
|
65
67
|
return (
|
|
66
68
|
<Animated.View
|
|
@@ -69,7 +71,10 @@ const ActiveIndicator: React.FC<Props> = ({
|
|
|
69
71
|
styles.indicator,
|
|
70
72
|
{
|
|
71
73
|
borderRadius: radius,
|
|
72
|
-
|
|
74
|
+
// a mini app's own theme may lack `tonal`; without the fallback the indicator is transparent
|
|
75
|
+
backgroundColor:
|
|
76
|
+
theme.colors.background.tonal ??
|
|
77
|
+
defaultTheme.colors.background.tonal,
|
|
73
78
|
boxShadow: getIndicatorShadow(theme.colors.primary),
|
|
74
79
|
},
|
|
75
80
|
style,
|
|
@@ -2,6 +2,20 @@ import { StyleSheet } from 'react-native';
|
|
|
2
2
|
import { Colors, Spacing } from '../../Consts';
|
|
3
3
|
import { CTA_SHADOW, TabBarMetrics } from './metrics';
|
|
4
4
|
|
|
5
|
+
const ITEM_HEIGHT =
|
|
6
|
+
TabBarMetrics.indicatorHeight +
|
|
7
|
+
TabBarMetrics.itemIconLabelGap +
|
|
8
|
+
TabBarMetrics.labelHeight;
|
|
9
|
+
const CONTENT_HEIGHT = TabBarMetrics.height - 2 * TabBarMetrics.paddingVertical;
|
|
10
|
+
// the items are centered in the content, so their label starts this far below the content top
|
|
11
|
+
const ITEM_LABEL_TOP =
|
|
12
|
+
(CONTENT_HEIGHT - ITEM_HEIGHT) / 2 +
|
|
13
|
+
TabBarMetrics.indicatorHeight +
|
|
14
|
+
TabBarMetrics.itemIconLabelGap;
|
|
15
|
+
// the center action column is placed so its label starts on the same line as the tab labels
|
|
16
|
+
const CTA_TOP =
|
|
17
|
+
ITEM_LABEL_TOP - TabBarMetrics.ctaLabelGap - TabBarMetrics.ctaSize;
|
|
18
|
+
|
|
5
19
|
export const styles = StyleSheet.create({
|
|
6
20
|
bar: {
|
|
7
21
|
position: 'absolute',
|
|
@@ -37,7 +51,7 @@ export const styles = StyleSheet.create({
|
|
|
37
51
|
position: 'absolute',
|
|
38
52
|
left: 0,
|
|
39
53
|
right: 0,
|
|
40
|
-
top:
|
|
54
|
+
top: CTA_TOP,
|
|
41
55
|
alignItems: 'center',
|
|
42
56
|
gap: TabBarMetrics.ctaLabelGap,
|
|
43
57
|
},
|
|
@@ -46,6 +60,9 @@ export const styles = StyleSheet.create({
|
|
|
46
60
|
width: TabBarMetrics.ctaSize,
|
|
47
61
|
height: TabBarMetrics.ctaSize,
|
|
48
62
|
borderRadius: TabBarMetrics.ctaSize / 2,
|
|
63
|
+
// Figma Icon Button border: white 60% hairline
|
|
64
|
+
borderWidth: 1,
|
|
65
|
+
borderColor: `rgba(255, 255, 255, ${TabBarMetrics.ctaBorderOpacity})`,
|
|
49
66
|
alignItems: 'center',
|
|
50
67
|
justifyContent: 'center',
|
|
51
68
|
},
|
|
@@ -17,7 +17,6 @@ import { CurvedContainer } from './CurvedContainer';
|
|
|
17
17
|
import { ActiveIndicator } from './ActiveIndicator';
|
|
18
18
|
import { getHorizontalBarWidth, TabBarMetrics, TabBarMotion } from './metrics';
|
|
19
19
|
import { getTabItems, TabItem } from './tabItems';
|
|
20
|
-
import { FixedScaleText } from './TabBarIcon';
|
|
21
20
|
import { styles } from './BottomTabBar.styles';
|
|
22
21
|
|
|
23
22
|
type Props = BottomTabBarProps & {
|
|
@@ -27,24 +26,28 @@ type Props = BottomTabBarProps & {
|
|
|
27
26
|
};
|
|
28
27
|
|
|
29
28
|
/**
|
|
30
|
-
* the center action needs a tab on each side
|
|
29
|
+
* Whether the bar shows the center action. It needs a tab on each side, so the
|
|
30
|
+
* Special type takes 2 or 4 tabs.
|
|
31
31
|
*/
|
|
32
|
-
const hasCenterAction = (floatingButton: unknown, tabCount: number) =>
|
|
33
|
-
!!floatingButton && tabCount % 2 === 0;
|
|
32
|
+
const hasCenterAction = (floatingButton: unknown, tabCount: number) => {
|
|
33
|
+
return !!floatingButton && tabCount % 2 === 0;
|
|
34
|
+
};
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
|
-
*
|
|
37
|
+
* Gap under the bar container for the home indicator / gesture bar area, at
|
|
38
|
+
* least `Spacing.S`; iOS caps it at the indicator height.
|
|
37
39
|
*/
|
|
38
|
-
const getSystemIndicatorHeight = (bottomInset: number) =>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
const getSystemIndicatorHeight = (bottomInset: number) => {
|
|
41
|
+
let height = bottomInset;
|
|
42
|
+
if (Platform.OS === 'ios') {
|
|
43
|
+
height = Math.min(bottomInset, TabBarMetrics.systemIndicatorHeight);
|
|
44
|
+
}
|
|
45
|
+
return Math.max(height, Spacing.S);
|
|
46
|
+
};
|
|
45
47
|
|
|
46
48
|
/**
|
|
47
|
-
* center action
|
|
49
|
+
* Press feedback of the center action: the button shrinks while held, the label
|
|
50
|
+
* stays still.
|
|
48
51
|
*/
|
|
49
52
|
const useCenterActionPress = () => {
|
|
50
53
|
const scale = useSharedValue(1);
|
|
@@ -64,26 +67,27 @@ const useCenterActionPress = () => {
|
|
|
64
67
|
};
|
|
65
68
|
|
|
66
69
|
/**
|
|
67
|
-
*
|
|
70
|
+
* Center action (.Nav/CTA): a button rising out of the notch, its label below
|
|
71
|
+
* (growing downward when scaled).
|
|
68
72
|
*/
|
|
69
|
-
const CenterAction: React.FC<{
|
|
70
|
-
floatingButton
|
|
71
|
-
|
|
72
|
-
}> = ({ floatingButton, fixedFontScale }) => {
|
|
73
|
+
const CenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
|
|
74
|
+
floatingButton,
|
|
75
|
+
}) => {
|
|
73
76
|
const { theme } = useContext(ApplicationContext);
|
|
74
77
|
const context = useContext<any>(MiniAppContext);
|
|
75
78
|
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
76
79
|
const { icon, label, onPress, labelContent } = floatingButton;
|
|
77
80
|
const { buttonStyle, onPressIn, onPressOut } = useCenterActionPress();
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
let iconNode = icon;
|
|
82
|
+
if (typeof icon === 'string') {
|
|
83
|
+
iconNode = (
|
|
84
|
+
<Icon
|
|
85
|
+
source={icon}
|
|
86
|
+
size={TabBarMetrics.ctaIconSize}
|
|
87
|
+
color={Colors.black_01}
|
|
88
|
+
/>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
87
91
|
|
|
88
92
|
return (
|
|
89
93
|
<View style={[styles.ctaSlot, showBaseLineDebug && styles.debugBaseLine]}>
|
|
@@ -102,22 +106,16 @@ const CenterAction: React.FC<{
|
|
|
102
106
|
buttonStyle,
|
|
103
107
|
]}
|
|
104
108
|
>
|
|
105
|
-
{
|
|
106
|
-
<Icon
|
|
107
|
-
source={icon}
|
|
108
|
-
size={TabBarMetrics.ctaIconSize}
|
|
109
|
-
color={Colors.black_01}
|
|
110
|
-
/>
|
|
111
|
-
) : (
|
|
112
|
-
icon
|
|
113
|
-
)}
|
|
109
|
+
{iconNode}
|
|
114
110
|
</Animated.View>
|
|
115
111
|
<View style={styles.ctaLabel}>
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
112
|
+
<Text
|
|
113
|
+
numberOfLines={1}
|
|
114
|
+
typography={'label_s_medium'}
|
|
115
|
+
color={theme.colors.text.secondary}
|
|
116
|
+
>
|
|
117
|
+
{label}
|
|
118
|
+
</Text>
|
|
121
119
|
{labelContent && (
|
|
122
120
|
<View pointerEvents={'none'} style={styles.ctaLabelOverlay}>
|
|
123
121
|
{labelContent}
|
|
@@ -130,8 +128,8 @@ const CenterAction: React.FC<{
|
|
|
130
128
|
};
|
|
131
129
|
|
|
132
130
|
/**
|
|
133
|
-
*
|
|
134
|
-
* the center action in a notch in the middle
|
|
131
|
+
* Horizontal navigation bar: a floating container with evenly split items; the
|
|
132
|
+
* Special type puts the center action in a notch in the middle.
|
|
135
133
|
*/
|
|
136
134
|
const HorizontalTabBar: React.FC<{
|
|
137
135
|
items: TabItem[];
|
|
@@ -139,23 +137,15 @@ const HorizontalTabBar: React.FC<{
|
|
|
139
137
|
activeTintColor: string;
|
|
140
138
|
inactiveTintColor: string;
|
|
141
139
|
floatingButton?: FloatingButtonProps;
|
|
142
|
-
|
|
143
|
-
}> = ({
|
|
144
|
-
items,
|
|
145
|
-
width,
|
|
146
|
-
activeTintColor,
|
|
147
|
-
inactiveTintColor,
|
|
148
|
-
floatingButton,
|
|
149
|
-
fixedFontScale,
|
|
150
|
-
}) => {
|
|
140
|
+
}> = ({ items, width, activeTintColor, inactiveTintColor, floatingButton }) => {
|
|
151
141
|
const withCta = hasCenterAction(floatingButton, items.length);
|
|
152
142
|
const [layouts, setLayouts] = useState<
|
|
153
143
|
Record<string, { x: number; y: number; width: number }>
|
|
154
144
|
>({});
|
|
155
145
|
const selected = layouts[items.find(item => item.focused)?.route.key ?? ''];
|
|
156
146
|
|
|
157
|
-
const renderItems = (data: TabItem[]) =>
|
|
158
|
-
data.map(item => (
|
|
147
|
+
const renderItems = (data: TabItem[]) => {
|
|
148
|
+
return data.map(item => (
|
|
159
149
|
<CustomBottomTabItem
|
|
160
150
|
key={item.route.key}
|
|
161
151
|
onLayout={e => {
|
|
@@ -173,9 +163,9 @@ const HorizontalTabBar: React.FC<{
|
|
|
173
163
|
onLongPress={item.onLongPress}
|
|
174
164
|
activeTintColor={activeTintColor}
|
|
175
165
|
inactiveTintColor={inactiveTintColor}
|
|
176
|
-
fixedFontScale={fixedFontScale}
|
|
177
166
|
/>
|
|
178
167
|
));
|
|
168
|
+
};
|
|
179
169
|
|
|
180
170
|
const renderContent = () => {
|
|
181
171
|
if (withCta && floatingButton) {
|
|
@@ -183,10 +173,7 @@ const HorizontalTabBar: React.FC<{
|
|
|
183
173
|
return (
|
|
184
174
|
<>
|
|
185
175
|
{renderItems(items.slice(0, middle))}
|
|
186
|
-
<CenterAction
|
|
187
|
-
floatingButton={floatingButton}
|
|
188
|
-
fixedFontScale={fixedFontScale}
|
|
189
|
-
/>
|
|
176
|
+
<CenterAction floatingButton={floatingButton} />
|
|
190
177
|
{renderItems(items.slice(middle))}
|
|
191
178
|
</>
|
|
192
179
|
);
|
|
@@ -194,21 +181,17 @@ const HorizontalTabBar: React.FC<{
|
|
|
194
181
|
return renderItems(items);
|
|
195
182
|
};
|
|
196
183
|
|
|
197
|
-
const customContainer = withCta && floatingButton?.container;
|
|
198
|
-
|
|
199
184
|
return (
|
|
200
185
|
<View
|
|
201
186
|
style={[styles.container, { width }]}
|
|
202
187
|
accessibilityLabel={'bottom_tab_bar'}
|
|
203
188
|
>
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
/>
|
|
211
|
-
)}
|
|
189
|
+
<CurvedContainer
|
|
190
|
+
width={width}
|
|
191
|
+
height={TabBarMetrics.height}
|
|
192
|
+
radius={TabBarMetrics.height / 2}
|
|
193
|
+
notchCenterX={withCta ? width / 2 : undefined}
|
|
194
|
+
/>
|
|
212
195
|
<View style={styles.content}>
|
|
213
196
|
<ActiveIndicator
|
|
214
197
|
axis={'x'}
|
|
@@ -227,9 +210,9 @@ const HorizontalTabBar: React.FC<{
|
|
|
227
210
|
};
|
|
228
211
|
|
|
229
212
|
/**
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
213
|
+
* Phone tab bar: the horizontal bar floats over the tab content above the home indicator,
|
|
214
|
+
* so content shows around it and through the notch. The transparent strip above it keeps
|
|
215
|
+
* the rising center action touchable.
|
|
233
216
|
*/
|
|
234
217
|
export default function BottomTabBar(props: Props) {
|
|
235
218
|
const { state, floatingButton, activeTintColor, inactiveTintColor } = props;
|
|
@@ -241,6 +224,8 @@ export default function BottomTabBar(props: Props) {
|
|
|
241
224
|
window.height,
|
|
242
225
|
insets.left,
|
|
243
226
|
insets.right,
|
|
227
|
+
state.routes.length,
|
|
228
|
+
withCta,
|
|
244
229
|
);
|
|
245
230
|
|
|
246
231
|
return (
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { createContext } from 'react';
|
|
2
2
|
|
|
3
3
|
type BottomTabInset = {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Space the floating tab bar takes over the bottom of a tab's content (bar + home
|
|
6
|
+
* indicator area, plus the rising center action when shown); 0 on the iPhone Duo rail.
|
|
7
|
+
*/
|
|
6
8
|
inset: number;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Called by a Screen in the tab that keeps its content clear of the bar itself (inside
|
|
11
|
+
* its scroll view, or above the bar when static), so the tab stops padding its content.
|
|
12
|
+
*/
|
|
9
13
|
claim?: () => void;
|
|
10
14
|
};
|
|
11
15
|
|