@momo-kits/foundation 0.168.1-beta.37 → 0.168.1-beta.38
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 +35 -26
- package/Application/BottomTab/ActiveIndicator.tsx +15 -14
- package/Application/BottomTab/BottomTabBar.tsx +49 -62
- package/Application/BottomTab/BottomTabInset.tsx +8 -4
- package/Application/BottomTab/CurvedContainer.tsx +14 -12
- package/Application/BottomTab/CustomBottomTabItem.tsx +13 -13
- package/Application/BottomTab/DuoBarCompression.tsx +13 -19
- package/Application/BottomTab/DuoRailTabBar.tsx +20 -24
- package/Application/BottomTab/DuoTabBar.tsx +8 -10
- 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 +23 -23
- 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 +40 -12
- package/Application/Components/HeaderBackground.tsx +19 -2
- package/Application/Components/HeaderExtendHeader.tsx +56 -34
- package/Application/Components/HeaderLeft.tsx +4 -2
- package/Application/Components/HeaderRight.styles.ts +15 -9
- package/Application/Components/HeaderRight.tsx +2 -1
- package/Application/Components/HeaderTitle.shared.ts +19 -12
- package/Application/Components/HeaderTitle.tsx +4 -3
- package/Application/Components/HeaderToolkitAction.tsx +28 -22
- package/Application/Components/NavigationButton.tsx +10 -6
- package/Application/Components/SearchHeader.tsx +41 -15
- 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 +9 -3
- package/Application/Navigation.ts +14 -16
- package/Application/NavigationContainer.tsx +19 -16
- package/Application/Navigator.ts +20 -48
- package/Application/ScaleSizeProvider.tsx +13 -6
- package/Application/StackScreen.tracking.ts +11 -26
- package/Application/StackScreen.tsx +17 -35
- package/Application/TooltipPortal.tsx +12 -5
- package/Application/WidgetContainer.tsx +10 -4
- package/Application/headerLayout.ts +158 -0
- package/Application/index.ts +11 -1
- package/Application/modalLayout.ts +13 -14
- package/Application/navigationOptions.tsx +27 -32
- package/Application/types.header.ts +7 -2
- package/Application/types.ts +7 -0
- 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 +22 -8
- 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 +24 -21
- 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 +59 -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 +88 -82
- package/Layout/screenQuickReturn.tsx +17 -20
- package/Layout/screenScroll.ts +4 -7
- package/Layout/screenTypes.ts +13 -4
- 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 +37 -37
- 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.
|
|
@@ -30,6 +30,10 @@ import {
|
|
|
30
30
|
renderSheetHeader,
|
|
31
31
|
} from './BottomSheet.parts';
|
|
32
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
|
+
*/
|
|
33
37
|
const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
34
38
|
const { theme, navigator } = useContext(ApplicationContext);
|
|
35
39
|
const context = useContext<any>(MiniAppContext);
|
|
@@ -98,8 +102,9 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
98
102
|
const panResponder = useRef(
|
|
99
103
|
PanResponder.create({
|
|
100
104
|
onStartShouldSetPanResponder: () => draggable,
|
|
101
|
-
onMoveShouldSetPanResponder: (_, gestureState) =>
|
|
102
|
-
draggable && gestureState.dy > 0
|
|
105
|
+
onMoveShouldSetPanResponder: (_, gestureState) => {
|
|
106
|
+
return draggable && gestureState.dy > 0;
|
|
107
|
+
},
|
|
103
108
|
onPanResponderMove: (_, gestureState) => {
|
|
104
109
|
if (gestureState.dy > 0) {
|
|
105
110
|
translateY.value = gestureState.dy;
|
|
@@ -131,10 +136,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
131
136
|
backgroundColor = theme.colors.background.surface;
|
|
132
137
|
}
|
|
133
138
|
|
|
134
|
-
|
|
135
|
-
* emit dismiss event, once: folding or rotating iPhone Duo changes the
|
|
136
|
-
* window height while the sheet stays open
|
|
137
|
-
*/
|
|
139
|
+
// Mount only, so folding or rotating iPhone Duo doesn't emit a dismiss.
|
|
138
140
|
useEffect(() => {
|
|
139
141
|
openAnimation();
|
|
140
142
|
return () => {
|
|
@@ -142,14 +144,14 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
142
144
|
};
|
|
143
145
|
}, [openAnimation]);
|
|
144
146
|
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
147
|
+
const handlers = useSheetDismiss({
|
|
148
|
+
barrierDismissible,
|
|
149
|
+
closeAnimation,
|
|
150
|
+
navigator,
|
|
151
|
+
leftOptions,
|
|
152
|
+
action,
|
|
153
|
+
});
|
|
154
|
+
const { onDismiss, onRequestClose, onPressBack, onPressClose } = handlers;
|
|
153
155
|
|
|
154
156
|
const showBack = !!leftOptions?.onPressIconLeft;
|
|
155
157
|
const iconBack = leftOptions?.iconLeft ?? 'ic_back';
|
|
@@ -180,6 +182,24 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
180
182
|
/>
|
|
181
183
|
);
|
|
182
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
|
+
|
|
183
203
|
return (
|
|
184
204
|
<Container
|
|
185
205
|
transparent
|
|
@@ -225,18 +245,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
225
245
|
}),
|
|
226
246
|
]}
|
|
227
247
|
>
|
|
228
|
-
{
|
|
229
|
-
// content shrinks so a tall sheet keeps its header and footer
|
|
230
|
-
<View style={styles.shrink}>
|
|
231
|
-
{renderSheetHeader(headerParams)}
|
|
232
|
-
<View style={styles.shrink}>{screen}</View>
|
|
233
|
-
</View>
|
|
234
|
-
) : (
|
|
235
|
-
<>
|
|
236
|
-
{renderSheetHeader(headerParams)}
|
|
237
|
-
{screen}
|
|
238
|
-
</>
|
|
239
|
-
)}
|
|
248
|
+
{content}
|
|
240
249
|
{renderSheetFooter({ footerComponent, theme })}
|
|
241
250
|
{useBottomInset && (
|
|
242
251
|
<View
|
|
@@ -19,8 +19,8 @@ type Props = {
|
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
23
|
-
* selected item
|
|
22
|
+
* The single active indicator of a bar (Background/tonal with inner shadows) that slides
|
|
23
|
+
* to the selected item. It lands in place on the first measure and slides from then on.
|
|
24
24
|
*/
|
|
25
25
|
const ActiveIndicator: React.FC<Props> = ({
|
|
26
26
|
axis,
|
|
@@ -49,18 +49,19 @@ const ActiveIndicator: React.FC<Props> = ({
|
|
|
49
49
|
position.value = withTiming(offset, TabBarMotion.indicator);
|
|
50
50
|
}, [offset, extent, position, size, visible]);
|
|
51
51
|
|
|
52
|
-
const animatedStyle = useAnimatedStyle(() =>
|
|
53
|
-
axis === 'x'
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
52
|
+
const animatedStyle = useAnimatedStyle(() => {
|
|
53
|
+
if (axis === 'x') {
|
|
54
|
+
return {
|
|
55
|
+
opacity: visible.value,
|
|
56
|
+
width: size.value,
|
|
57
|
+
transform: [{ translateX: position.value }],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
opacity: visible.value,
|
|
62
|
+
transform: [{ translateY: position.value }],
|
|
63
|
+
};
|
|
64
|
+
});
|
|
64
65
|
|
|
65
66
|
return (
|
|
66
67
|
<Animated.View
|
|
@@ -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
|
);
|
|
@@ -227,9 +214,9 @@ const HorizontalTabBar: React.FC<{
|
|
|
227
214
|
};
|
|
228
215
|
|
|
229
216
|
/**
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
217
|
+
* Phone tab bar: the horizontal bar floats over the tab content above the home indicator,
|
|
218
|
+
* so content shows around it and through the notch. The transparent strip above it keeps
|
|
219
|
+
* the rising center action touchable.
|
|
233
220
|
*/
|
|
234
221
|
export default function BottomTabBar(props: Props) {
|
|
235
222
|
const { state, floatingButton, activeTintColor, inactiveTintColor } = props;
|
|
@@ -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
|
|
|
@@ -15,16 +15,18 @@ type Props = {
|
|
|
15
15
|
|
|
16
16
|
// room for the floating shadow around the shape: the key shadow reaches 12 + 3 × 16 below it
|
|
17
17
|
const SHADOW_PADDING = 64;
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Figma Shadow/floating: 12% blur 24, plus 4% blur 32 spread -16 offset 12. SVG blur takes
|
|
20
|
+
* half the CSS blur radius; the spread shrinks the shape the key shadow is cast from.
|
|
21
|
+
*/
|
|
20
22
|
const AMBIENT_BLUR = 12;
|
|
21
23
|
const KEY_BLUR = 16;
|
|
22
24
|
const KEY_SPREAD = 16;
|
|
23
25
|
const KEY_OFFSET_Y = 12;
|
|
24
26
|
|
|
25
27
|
/**
|
|
26
|
-
* rounded container
|
|
27
|
-
* top edge around `notchCenterX
|
|
28
|
+
* SVG path of the rounded container placed at (`x`, `y`) on the canvas, with the notch
|
|
29
|
+
* dipped into the top edge around `notchCenterX`.
|
|
28
30
|
*/
|
|
29
31
|
const getContainerPath = (
|
|
30
32
|
width: number,
|
|
@@ -43,11 +45,10 @@ const getContainerPath = (
|
|
|
43
45
|
if (notchCenterX !== undefined) {
|
|
44
46
|
const cx = x + notchCenterX;
|
|
45
47
|
const point = ([dx, dy]: [number, number]) => `${cx + dx} ${top + dy}`;
|
|
46
|
-
|
|
47
|
-
`
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
).join('');
|
|
48
|
+
const curves = NOTCH_CURVES.map(([c1, c2, end]) => {
|
|
49
|
+
return ` C ${point(c1)} ${point(c2)} ${point(end)}`;
|
|
50
|
+
});
|
|
51
|
+
notch = ` L ${cx - NOTCH_HALF_WIDTH} ${top}${curves.join('')}`;
|
|
51
52
|
}
|
|
52
53
|
return (
|
|
53
54
|
`M ${left + r} ${top}` +
|
|
@@ -64,8 +65,8 @@ const getContainerPath = (
|
|
|
64
65
|
};
|
|
65
66
|
|
|
66
67
|
/**
|
|
67
|
-
*
|
|
68
|
-
* the shadow follows the notch
|
|
68
|
+
* Navigation bar container: surface fill with the floating shadow, drawn as an SVG shape
|
|
69
|
+
* so the shadow follows the notch.
|
|
69
70
|
*/
|
|
70
71
|
const CurvedContainer: React.FC<Props> = ({
|
|
71
72
|
width,
|
|
@@ -87,6 +88,7 @@ const CurvedContainer: React.FC<Props> = ({
|
|
|
87
88
|
SHADOW_PADDING,
|
|
88
89
|
notchCenterX,
|
|
89
90
|
);
|
|
91
|
+
const hasKeyShadow = width > 2 * KEY_SPREAD && height > 2 * KEY_SPREAD;
|
|
90
92
|
|
|
91
93
|
return (
|
|
92
94
|
<Svg
|
|
@@ -123,7 +125,7 @@ const CurvedContainer: React.FC<Props> = ({
|
|
|
123
125
|
fillOpacity={0.12}
|
|
124
126
|
filter={'url(#ambientShadow)'}
|
|
125
127
|
/>
|
|
126
|
-
{
|
|
128
|
+
{hasKeyShadow && (
|
|
127
129
|
<Path
|
|
128
130
|
d={getContainerPath(
|
|
129
131
|
width - 2 * KEY_SPREAD,
|
|
@@ -10,7 +10,7 @@ import Animated, {
|
|
|
10
10
|
useSharedValue,
|
|
11
11
|
withTiming,
|
|
12
12
|
} from 'react-native-reanimated';
|
|
13
|
-
import {
|
|
13
|
+
import { TabBarIcon } from './TabBarIcon';
|
|
14
14
|
import { Text } from '../../Text';
|
|
15
15
|
import { MiniAppContext } from '../../Context';
|
|
16
16
|
import { Colors, Spacing } from '../../Consts';
|
|
@@ -31,14 +31,12 @@ type TabItemContentProps = {
|
|
|
31
31
|
badge?: string | number;
|
|
32
32
|
// rail item (.Nav/Duo/Item) instead of the horizontal one (.Nav/itemTab)
|
|
33
33
|
vertical?: boolean;
|
|
34
|
-
// keep the label at its design size, the Duo displays otherwise scale it up
|
|
35
|
-
fixedFontScale?: boolean;
|
|
36
34
|
hideLabel?: boolean;
|
|
37
35
|
};
|
|
38
36
|
|
|
39
37
|
/**
|
|
40
|
-
* icon in its indicator slot, label
|
|
41
|
-
*
|
|
38
|
+
* Tab item body: the icon in its indicator slot, the label in a fixed slot under it so a
|
|
39
|
+
* scaled label grows down. The bar slides one shared active indicator behind the icon slots.
|
|
42
40
|
*/
|
|
43
41
|
const TabItemContent: React.FC<TabItemContentProps> = ({
|
|
44
42
|
label,
|
|
@@ -48,7 +46,6 @@ const TabItemContent: React.FC<TabItemContentProps> = ({
|
|
|
48
46
|
icon,
|
|
49
47
|
badge,
|
|
50
48
|
vertical,
|
|
51
|
-
fixedFontScale,
|
|
52
49
|
hideLabel,
|
|
53
50
|
}) => {
|
|
54
51
|
const progress = useSharedValue(focused ? 1 : 0);
|
|
@@ -72,9 +69,9 @@ const TabItemContent: React.FC<TabItemContentProps> = ({
|
|
|
72
69
|
</Text>
|
|
73
70
|
);
|
|
74
71
|
|
|
75
|
-
//
|
|
76
|
-
const
|
|
77
|
-
<View style={
|
|
72
|
+
// Active label fades in over the inactive one; a scaled label overflows down.
|
|
73
|
+
const labelNode = (
|
|
74
|
+
<View style={vertical ? styles.railLabelSlot : styles.labelSlot}>
|
|
78
75
|
{renderText(inactiveTintColor)}
|
|
79
76
|
<Animated.View
|
|
80
77
|
pointerEvents={'none'}
|
|
@@ -97,8 +94,7 @@ const TabItemContent: React.FC<TabItemContentProps> = ({
|
|
|
97
94
|
renderIcon={icon}
|
|
98
95
|
/>
|
|
99
96
|
</View>
|
|
100
|
-
{!hideLabel &&
|
|
101
|
-
(fixedFontScale ? <FixedScaleText>{text}</FixedScaleText> : text)}
|
|
97
|
+
{!hideLabel && labelNode}
|
|
102
98
|
</>
|
|
103
99
|
);
|
|
104
100
|
};
|
|
@@ -112,7 +108,7 @@ type CustomBottomTabItemProps = TabItemContentProps & {
|
|
|
112
108
|
};
|
|
113
109
|
|
|
114
110
|
/**
|
|
115
|
-
*
|
|
111
|
+
* Horizontal navigation item (.Nav/itemTab), filling an even share of the container.
|
|
116
112
|
*/
|
|
117
113
|
const CustomBottomTabItem: React.FC<CustomBottomTabItemProps> = ({
|
|
118
114
|
onPress,
|
|
@@ -164,9 +160,13 @@ const styles = StyleSheet.create({
|
|
|
164
160
|
paddingVertical: TabBarMetrics.railIndicatorPaddingVertical,
|
|
165
161
|
paddingHorizontal: TabBarMetrics.indicatorPaddingHorizontal,
|
|
166
162
|
},
|
|
167
|
-
|
|
163
|
+
labelSlot: {
|
|
164
|
+
height: TabBarMetrics.labelHeight,
|
|
168
165
|
alignSelf: 'stretch',
|
|
169
166
|
},
|
|
167
|
+
railLabelSlot: {
|
|
168
|
+
height: TabBarMetrics.railLabelHeight,
|
|
169
|
+
},
|
|
170
170
|
label: {
|
|
171
171
|
textAlign: 'center',
|
|
172
172
|
},
|