@momo-kits/foundation 0.169.5 → 0.169.6-beta.1
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 +34 -44
- package/Application/BottomSheet.tsx +26 -35
- package/Application/BottomTab/ActiveIndicator.tsx +14 -15
- package/Application/BottomTab/BottomTabBar.styles.ts +18 -1
- package/Application/BottomTab/BottomTabBar.tsx +40 -63
- package/Application/BottomTab/BottomTabInset.tsx +4 -8
- package/Application/BottomTab/CurvedContainer.tsx +20 -16
- package/Application/BottomTab/CustomBottomTabItem.tsx +13 -16
- package/Application/BottomTab/DuoBarCompression.tsx +19 -13
- package/Application/BottomTab/DuoRailTabBar.tsx +13 -17
- package/Application/BottomTab/DuoTabBar.tsx +24 -12
- package/Application/BottomTab/TabBarIcon.tsx +8 -13
- package/Application/BottomTab/TabScreen.tsx +5 -1
- package/Application/BottomTab/index.tsx +30 -38
- package/Application/BottomTab/metrics.ts +46 -24
- package/Application/BottomTab/tabItems.ts +1 -1
- package/Application/BottomTab/usePressLift.ts +2 -2
- package/Application/Components/BackgroundImageView.tsx +0 -4
- package/Application/Components/DuoGlassBackground.tsx +4 -5
- package/Application/Components/HeaderAnimated.tsx +0 -4
- package/Application/Components/HeaderBackground.tsx +0 -4
- package/Application/Components/HeaderExtendHeader.tsx +10 -11
- package/Application/Components/HeaderLeft.tsx +1 -2
- package/Application/Components/HeaderRight.tsx +1 -2
- package/Application/Components/HeaderTitle.shared.ts +9 -11
- package/Application/Components/HeaderTitle.tsx +3 -4
- package/Application/Components/HeaderToolkitAction.tsx +20 -17
- package/Application/Components/NavigationButton.tsx +1 -2
- package/Application/Components/SearchHeader.tsx +10 -17
- 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 +32 -17
- package/Application/Localize.ts +0 -7
- package/Application/ModalScreen.tsx +3 -9
- package/Application/Navigation.ts +16 -14
- package/Application/NavigationContainer.tsx +16 -19
- package/Application/Navigator.ts +48 -20
- package/Application/StackScreen.tracking.ts +26 -11
- package/Application/StackScreen.tsx +35 -8
- package/Application/TooltipPortal.tsx +5 -12
- package/Application/WidgetContainer.tsx +4 -10
- package/Application/__tests__/modalLayout.test.ts +128 -0
- package/Application/modalLayout.ts +14 -13
- package/Application/navigationOptions.tsx +17 -21
- package/Application/types.tabs.ts +0 -1
- package/Badge/Badge.tsx +0 -5
- package/Badge/BadgeDot.tsx +0 -1
- package/Badge/BadgeDotAnimation.tsx +7 -15
- package/Badge/BadgeRibbon.tsx +38 -46
- package/Badge/Shape.tsx +0 -2
- package/Badge/types.ts +2 -2
- package/Button/index.tsx +16 -5
- package/Button/utils.ts +15 -9
- package/CheckBox/index.tsx +0 -4
- package/Consts/theme.ts +10 -10
- package/Context/index.ts +0 -1
- package/Divider/DashDivider.tsx +0 -1
- package/Divider/index.tsx +0 -4
- package/FoundationList/index.tsx +0 -4
- package/Icon/index.tsx +6 -6
- package/Icon/types.ts +1 -1
- package/IconButton/index.tsx +12 -4
- package/Image/index.tsx +7 -9
- package/Input/Input.tsx +9 -8
- package/Input/InputDropDown.tsx +12 -10
- package/Input/InputMoney.tsx +8 -12
- package/Input/InputMoneyView.tsx +7 -14
- package/Input/InputOTP.tsx +33 -35
- package/Input/InputPhoneNumber.tsx +9 -8
- package/Input/InputSearch.tsx +6 -4
- package/Input/InputTextArea.tsx +0 -4
- package/Input/SearchTextTyping.tsx +0 -4
- package/Input/SystemTextInput.tsx +2 -7
- package/Input/TextTyping.tsx +0 -4
- package/Input/common.tsx +12 -26
- package/Input/index.tsx +20 -17
- package/Input/moneyUtils.ts +4 -10
- package/Input/styles.ts +5 -0
- package/Input/utils.ts +12 -14
- package/Layout/Card.tsx +4 -7
- package/Layout/FloatingButton.tsx +0 -4
- package/Layout/Item.tsx +2 -6
- package/Layout/ItemList.tsx +7 -9
- package/Layout/ItemSectionList.tsx +1 -2
- package/Layout/ReservedRegions.tsx +25 -25
- package/Layout/Screen.tsx +58 -56
- package/Layout/Section.tsx +5 -8
- package/Layout/TrackingScope.tsx +0 -1
- package/Layout/screenContent.tsx +2 -4
- package/Layout/screenFooter.tsx +18 -28
- package/Layout/screenHeader.tsx +31 -34
- package/Layout/screenQuickReturn.tsx +20 -17
- package/Layout/screenScroll.ts +7 -4
- package/Layout/screenTypes.ts +2 -3
- package/Layout/types.ts +4 -2
- package/Layout/utils.ts +37 -31
- package/Loader/DotLoader.tsx +0 -4
- package/Loader/Loader.tsx +0 -4
- package/Loader/ProgressBar.tsx +0 -1
- package/Loader/Spinner.tsx +0 -4
- package/Loader/utils.ts +7 -11
- package/Pagination/Dot.tsx +0 -4
- package/Pagination/PaginationDot.tsx +0 -1
- package/Pagination/PaginationNumber.tsx +3 -5
- package/Pagination/PaginationScroll.tsx +0 -5
- package/Pagination/PaginationWhiteDot.tsx +0 -1
- package/Pagination/index.tsx +0 -4
- package/Pagination/types.ts +2 -2
- package/Popup/PopupNotify.tsx +48 -44
- package/Popup/PopupPromotion.tsx +7 -4
- package/Popup/types.ts +4 -4
- package/Popup/utils.ts +0 -4
- package/Radio/index.tsx +0 -4
- package/Skeleton/index.tsx +0 -8
- package/Switch/index.tsx +0 -1
- package/Tag/index.tsx +1 -5
- package/Tag/types.ts +3 -3
- package/Text/index.tsx +3 -14
- package/Text/styles.ts +1 -1
- package/Text/types.ts +6 -1
- package/Text/utils.ts +0 -9
- package/Title/index.tsx +32 -41
- package/ViewBoundary/index.tsx +2 -4
- package/index.ts +6 -0
- package/package.json +1 -1
|
@@ -51,10 +51,8 @@ export const useBottomSheetLayout = () => {
|
|
|
51
51
|
};
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
* the whole sheet past the top; the sheet lifts by it and shrinks its maxHeight.
|
|
57
|
-
*/
|
|
54
|
+
// KeyboardAvoidingView lifts the whole sheet past the top on iPhone Duo,
|
|
55
|
+
// so lift it here and shrink maxHeight by the same amount instead
|
|
58
56
|
export const useDuoKeyboardLift = (
|
|
59
57
|
isDuo: boolean,
|
|
60
58
|
useKeyboardAvoidingView: boolean,
|
|
@@ -138,10 +136,6 @@ type SheetDismissParams = {
|
|
|
138
136
|
action: MutableRefObject<undefined | string>;
|
|
139
137
|
};
|
|
140
138
|
|
|
141
|
-
/**
|
|
142
|
-
* Close handlers of a bottom sheet. A non-forced dismiss (scrim tap, hardware
|
|
143
|
-
* back) is ignored while `barrierDismissible` is set.
|
|
144
|
-
*/
|
|
145
139
|
export const useSheetDismiss = ({
|
|
146
140
|
barrierDismissible,
|
|
147
141
|
closeAnimation,
|
|
@@ -149,6 +143,9 @@ export const useSheetDismiss = ({
|
|
|
149
143
|
leftOptions,
|
|
150
144
|
action,
|
|
151
145
|
}: SheetDismissParams) => {
|
|
146
|
+
/**
|
|
147
|
+
* handler dismiss
|
|
148
|
+
*/
|
|
152
149
|
const onDismiss = useCallback(
|
|
153
150
|
(force = false, callback?: () => void) => {
|
|
154
151
|
if (barrierDismissible && !force) {
|
|
@@ -164,6 +161,9 @@ export const useSheetDismiss = ({
|
|
|
164
161
|
[barrierDismissible, closeAnimation, navigator],
|
|
165
162
|
);
|
|
166
163
|
|
|
164
|
+
/**
|
|
165
|
+
* on request close
|
|
166
|
+
*/
|
|
167
167
|
const onRequestClose = useCallback(
|
|
168
168
|
(callback?: () => void) => {
|
|
169
169
|
onDismiss(true, callback);
|
|
@@ -34,52 +34,42 @@ export const renderSheetHeader = ({
|
|
|
34
34
|
iconBack,
|
|
35
35
|
options,
|
|
36
36
|
leftOptions,
|
|
37
|
-
}: SheetHeaderParams) =>
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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}
|
|
37
|
+
}: SheetHeaderParams) => (
|
|
38
|
+
<View {...panHandlers}>
|
|
39
|
+
<View style={{ height: INDICATOR_HEIGHT }}>
|
|
40
|
+
<View style={styles.indicator} />
|
|
41
|
+
</View>
|
|
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}
|
|
76
54
|
|
|
77
|
-
|
|
78
|
-
<
|
|
79
|
-
|
|
55
|
+
{options.header ? (
|
|
56
|
+
<View style={Styles.flex}>{options.header}</View>
|
|
57
|
+
) : (
|
|
58
|
+
<>
|
|
59
|
+
{!leftOptions ? <View style={styles.iconButtonSpace} /> : null}
|
|
60
|
+
<Text
|
|
61
|
+
style={[Styles.flex, styles.title]}
|
|
62
|
+
numberOfLines={1}
|
|
63
|
+
typography="header_default_bold"
|
|
64
|
+
>
|
|
65
|
+
{options.title ?? ''}
|
|
66
|
+
</Text>
|
|
67
|
+
</>
|
|
68
|
+
)}
|
|
69
|
+
<ModalIconButton icon="navigation_close" onPress={onPressClose} />
|
|
80
70
|
</View>
|
|
81
|
-
|
|
82
|
-
|
|
71
|
+
</View>
|
|
72
|
+
);
|
|
83
73
|
|
|
84
74
|
/**
|
|
85
75
|
* Footer pinned under the sheet content, `null` when the sheet has none.
|
|
@@ -30,10 +30,6 @@ 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
|
-
*/
|
|
37
33
|
const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
38
34
|
const { theme, navigator } = useContext(ApplicationContext);
|
|
39
35
|
const context = useContext<any>(MiniAppContext);
|
|
@@ -102,9 +98,8 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
102
98
|
const panResponder = useRef(
|
|
103
99
|
PanResponder.create({
|
|
104
100
|
onStartShouldSetPanResponder: () => draggable,
|
|
105
|
-
onMoveShouldSetPanResponder: (_, gestureState) =>
|
|
106
|
-
|
|
107
|
-
},
|
|
101
|
+
onMoveShouldSetPanResponder: (_, gestureState) =>
|
|
102
|
+
draggable && gestureState.dy > 0,
|
|
108
103
|
onPanResponderMove: (_, gestureState) => {
|
|
109
104
|
if (gestureState.dy > 0) {
|
|
110
105
|
translateY.value = gestureState.dy;
|
|
@@ -136,7 +131,10 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
136
131
|
backgroundColor = theme.colors.background.surface;
|
|
137
132
|
}
|
|
138
133
|
|
|
139
|
-
|
|
134
|
+
/**
|
|
135
|
+
* emit dismiss event, once: folding or rotating iPhone Duo changes the
|
|
136
|
+
* window height while the sheet stays open
|
|
137
|
+
*/
|
|
140
138
|
useEffect(() => {
|
|
141
139
|
openAnimation();
|
|
142
140
|
return () => {
|
|
@@ -144,14 +142,14 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
144
142
|
};
|
|
145
143
|
}, [openAnimation]);
|
|
146
144
|
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
145
|
+
const { onDismiss, onRequestClose, onPressBack, onPressClose } =
|
|
146
|
+
useSheetDismiss({
|
|
147
|
+
barrierDismissible,
|
|
148
|
+
closeAnimation,
|
|
149
|
+
navigator,
|
|
150
|
+
leftOptions,
|
|
151
|
+
action,
|
|
152
|
+
});
|
|
155
153
|
|
|
156
154
|
const showBack = !!leftOptions?.onPressIconLeft;
|
|
157
155
|
const iconBack = leftOptions?.iconLeft ?? 'ic_back';
|
|
@@ -182,24 +180,6 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
182
180
|
/>
|
|
183
181
|
);
|
|
184
182
|
|
|
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
|
-
|
|
203
183
|
return (
|
|
204
184
|
<Container
|
|
205
185
|
transparent
|
|
@@ -245,7 +225,18 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
245
225
|
}),
|
|
246
226
|
]}
|
|
247
227
|
>
|
|
248
|
-
{
|
|
228
|
+
{isFoldable ? (
|
|
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
|
+
)}
|
|
249
240
|
{renderSheetFooter({ footerComponent, theme })}
|
|
250
241
|
{useBottomInset && (
|
|
251
242
|
<View
|
|
@@ -19,8 +19,8 @@ type Props = {
|
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
22
|
+
* one active indicator per bar (Background/tonal with its inner shadows) sliding to the
|
|
23
|
+
* 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,19 +49,18 @@ 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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
});
|
|
52
|
+
const animatedStyle = useAnimatedStyle(() =>
|
|
53
|
+
axis === 'x'
|
|
54
|
+
? {
|
|
55
|
+
opacity: visible.value,
|
|
56
|
+
width: size.value,
|
|
57
|
+
transform: [{ translateX: position.value }],
|
|
58
|
+
}
|
|
59
|
+
: {
|
|
60
|
+
opacity: visible.value,
|
|
61
|
+
transform: [{ translateY: position.value }],
|
|
62
|
+
},
|
|
63
|
+
);
|
|
65
64
|
|
|
66
65
|
return (
|
|
67
66
|
<Animated.View
|
|
@@ -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,28 +26,24 @@ type Props = BottomTabBarProps & {
|
|
|
27
26
|
};
|
|
28
27
|
|
|
29
28
|
/**
|
|
30
|
-
*
|
|
31
|
-
* Special type takes 2 or 4 tabs.
|
|
29
|
+
* the center action needs a tab on each side: Special type takes 2 or 4 tabs
|
|
32
30
|
*/
|
|
33
|
-
const hasCenterAction = (floatingButton: unknown, tabCount: number) =>
|
|
34
|
-
|
|
35
|
-
};
|
|
31
|
+
const hasCenterAction = (floatingButton: unknown, tabCount: number) =>
|
|
32
|
+
!!floatingButton && tabCount % 2 === 0;
|
|
36
33
|
|
|
37
34
|
/**
|
|
38
|
-
*
|
|
39
|
-
* least `Spacing.S`; iOS caps it at the indicator height.
|
|
35
|
+
* gap under the container: the home indicator / gesture bar area
|
|
40
36
|
*/
|
|
41
|
-
const getSystemIndicatorHeight = (bottomInset: number) =>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
37
|
+
const getSystemIndicatorHeight = (bottomInset: number) =>
|
|
38
|
+
Math.max(
|
|
39
|
+
Platform.OS === 'ios'
|
|
40
|
+
? Math.min(bottomInset, TabBarMetrics.systemIndicatorHeight)
|
|
41
|
+
: bottomInset,
|
|
42
|
+
Spacing.S,
|
|
43
|
+
);
|
|
48
44
|
|
|
49
45
|
/**
|
|
50
|
-
*
|
|
51
|
-
* stays still.
|
|
46
|
+
* center action press: the button shrinks while held; the label stays plain text
|
|
52
47
|
*/
|
|
53
48
|
const useCenterActionPress = () => {
|
|
54
49
|
const scale = useSharedValue(1);
|
|
@@ -68,12 +63,11 @@ const useCenterActionPress = () => {
|
|
|
68
63
|
};
|
|
69
64
|
|
|
70
65
|
/**
|
|
71
|
-
*
|
|
66
|
+
* center action (.Nav/CTA): button rising out of the notch, label under it
|
|
72
67
|
*/
|
|
73
68
|
const CenterAction: React.FC<{
|
|
74
69
|
floatingButton: FloatingButtonProps;
|
|
75
|
-
|
|
76
|
-
}> = ({ floatingButton, fixedFontScale }) => {
|
|
70
|
+
}> = ({ floatingButton }) => {
|
|
77
71
|
const { theme } = useContext(ApplicationContext);
|
|
78
72
|
const context = useContext<any>(MiniAppContext);
|
|
79
73
|
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
@@ -89,22 +83,6 @@ const CenterAction: React.FC<{
|
|
|
89
83
|
</Text>
|
|
90
84
|
);
|
|
91
85
|
|
|
92
|
-
let iconNode = icon;
|
|
93
|
-
if (typeof icon === 'string') {
|
|
94
|
-
iconNode = (
|
|
95
|
-
<Icon
|
|
96
|
-
source={icon}
|
|
97
|
-
size={TabBarMetrics.ctaIconSize}
|
|
98
|
-
color={Colors.black_01}
|
|
99
|
-
/>
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
let labelNode = labelText;
|
|
104
|
-
if (fixedFontScale) {
|
|
105
|
-
labelNode = <FixedScaleText>{labelText}</FixedScaleText>;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
86
|
return (
|
|
109
87
|
<View style={[styles.ctaSlot, showBaseLineDebug && styles.debugBaseLine]}>
|
|
110
88
|
<Pressable
|
|
@@ -122,10 +100,18 @@ const CenterAction: React.FC<{
|
|
|
122
100
|
buttonStyle,
|
|
123
101
|
]}
|
|
124
102
|
>
|
|
125
|
-
{
|
|
103
|
+
{typeof icon === 'string' ? (
|
|
104
|
+
<Icon
|
|
105
|
+
source={icon}
|
|
106
|
+
size={TabBarMetrics.ctaIconSize}
|
|
107
|
+
color={Colors.black_01}
|
|
108
|
+
/>
|
|
109
|
+
) : (
|
|
110
|
+
icon
|
|
111
|
+
)}
|
|
126
112
|
</Animated.View>
|
|
127
113
|
<View style={styles.ctaLabel}>
|
|
128
|
-
{
|
|
114
|
+
{labelText}
|
|
129
115
|
{labelContent && (
|
|
130
116
|
<View pointerEvents={'none'} style={styles.ctaLabelOverlay}>
|
|
131
117
|
{labelContent}
|
|
@@ -138,8 +124,8 @@ const CenterAction: React.FC<{
|
|
|
138
124
|
};
|
|
139
125
|
|
|
140
126
|
/**
|
|
141
|
-
*
|
|
142
|
-
*
|
|
127
|
+
* horizontal navigation bar: floating container, items split evenly, Special type puts
|
|
128
|
+
* the center action in a notch in the middle
|
|
143
129
|
*/
|
|
144
130
|
const HorizontalTabBar: React.FC<{
|
|
145
131
|
items: TabItem[];
|
|
@@ -147,14 +133,12 @@ const HorizontalTabBar: React.FC<{
|
|
|
147
133
|
activeTintColor: string;
|
|
148
134
|
inactiveTintColor: string;
|
|
149
135
|
floatingButton?: FloatingButtonProps;
|
|
150
|
-
fixedFontScale?: boolean;
|
|
151
136
|
}> = ({
|
|
152
137
|
items,
|
|
153
138
|
width,
|
|
154
139
|
activeTintColor,
|
|
155
140
|
inactiveTintColor,
|
|
156
141
|
floatingButton,
|
|
157
|
-
fixedFontScale,
|
|
158
142
|
}) => {
|
|
159
143
|
const withCta = hasCenterAction(floatingButton, items.length);
|
|
160
144
|
const [layouts, setLayouts] = useState<
|
|
@@ -162,8 +146,8 @@ const HorizontalTabBar: React.FC<{
|
|
|
162
146
|
>({});
|
|
163
147
|
const selected = layouts[items.find(item => item.focused)?.route.key ?? ''];
|
|
164
148
|
|
|
165
|
-
const renderItems = (data: TabItem[]) =>
|
|
166
|
-
|
|
149
|
+
const renderItems = (data: TabItem[]) =>
|
|
150
|
+
data.map(item => (
|
|
167
151
|
<CustomBottomTabItem
|
|
168
152
|
key={item.route.key}
|
|
169
153
|
onLayout={e => {
|
|
@@ -181,10 +165,8 @@ const HorizontalTabBar: React.FC<{
|
|
|
181
165
|
onLongPress={item.onLongPress}
|
|
182
166
|
activeTintColor={activeTintColor}
|
|
183
167
|
inactiveTintColor={inactiveTintColor}
|
|
184
|
-
fixedFontScale={fixedFontScale}
|
|
185
168
|
/>
|
|
186
169
|
));
|
|
187
|
-
};
|
|
188
170
|
|
|
189
171
|
const renderContent = () => {
|
|
190
172
|
if (withCta && floatingButton) {
|
|
@@ -192,10 +174,7 @@ const HorizontalTabBar: React.FC<{
|
|
|
192
174
|
return (
|
|
193
175
|
<>
|
|
194
176
|
{renderItems(items.slice(0, middle))}
|
|
195
|
-
<CenterAction
|
|
196
|
-
floatingButton={floatingButton}
|
|
197
|
-
fixedFontScale={fixedFontScale}
|
|
198
|
-
/>
|
|
177
|
+
<CenterAction floatingButton={floatingButton} />
|
|
199
178
|
{renderItems(items.slice(middle))}
|
|
200
179
|
</>
|
|
201
180
|
);
|
|
@@ -203,21 +182,17 @@ const HorizontalTabBar: React.FC<{
|
|
|
203
182
|
return renderItems(items);
|
|
204
183
|
};
|
|
205
184
|
|
|
206
|
-
const customContainer = withCta && floatingButton?.container;
|
|
207
|
-
|
|
208
185
|
return (
|
|
209
186
|
<View
|
|
210
187
|
style={[styles.container, { width }]}
|
|
211
188
|
accessibilityLabel={'bottom_tab_bar'}
|
|
212
189
|
>
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
/>
|
|
220
|
-
)}
|
|
190
|
+
<CurvedContainer
|
|
191
|
+
width={width}
|
|
192
|
+
height={TabBarMetrics.height}
|
|
193
|
+
radius={TabBarMetrics.height / 2}
|
|
194
|
+
notchCenterX={withCta ? width / 2 : undefined}
|
|
195
|
+
/>
|
|
221
196
|
<View style={styles.content}>
|
|
222
197
|
<ActiveIndicator
|
|
223
198
|
axis={'x'}
|
|
@@ -236,9 +211,9 @@ const HorizontalTabBar: React.FC<{
|
|
|
236
211
|
};
|
|
237
212
|
|
|
238
213
|
/**
|
|
239
|
-
*
|
|
240
|
-
* so content shows around it and through the notch. The transparent strip
|
|
241
|
-
* the rising center action touchable
|
|
214
|
+
* phone tab bar: the horizontal navigation bar floating over the tab content above the home
|
|
215
|
+
* indicator, so the content shows around it and through the notch. The transparent strip
|
|
216
|
+
* above it keeps the rising center action touchable
|
|
242
217
|
*/
|
|
243
218
|
export default function BottomTabBar(props: Props) {
|
|
244
219
|
const { state, floatingButton, activeTintColor, inactiveTintColor } = props;
|
|
@@ -250,6 +225,8 @@ export default function BottomTabBar(props: Props) {
|
|
|
250
225
|
window.height,
|
|
251
226
|
insets.left,
|
|
252
227
|
insets.right,
|
|
228
|
+
state.routes.length,
|
|
229
|
+
withCta,
|
|
253
230
|
);
|
|
254
231
|
|
|
255
232
|
return (
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import { createContext } from 'react';
|
|
2
2
|
|
|
3
3
|
type BottomTabInset = {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* indicator area, plus the rising center action when shown); 0 on the iPhone Duo rail.
|
|
7
|
-
*/
|
|
4
|
+
// space the floating tab bar takes over the bottom of a tab's content (bar + home
|
|
5
|
+
// indicator area, plus the rising center action when shown); 0 on the iPhone Duo rail
|
|
8
6
|
inset: number;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* its scroll view, or above the bar when static), so the tab stops padding its content.
|
|
12
|
-
*/
|
|
7
|
+
// a Screen in the tab keeps its content clear of the bar itself (inside its scroll view,
|
|
8
|
+
// or above the bar when static), so the tab stops padding its content
|
|
13
9
|
claim?: () => void;
|
|
14
10
|
};
|
|
15
11
|
|
|
@@ -3,7 +3,7 @@ import { StyleSheet } from 'react-native';
|
|
|
3
3
|
import Svg, { Defs, FeGaussianBlur, Filter, Path } from 'react-native-svg';
|
|
4
4
|
import { Colors } from '../../Consts';
|
|
5
5
|
import { ApplicationContext } from '../../Context';
|
|
6
|
-
import { NOTCH_CURVES, NOTCH_HALF_WIDTH } from './metrics';
|
|
6
|
+
import { NOTCH_CURVES, NOTCH_HALF_WIDTH, TabBarMetrics } from './metrics';
|
|
7
7
|
|
|
8
8
|
type Props = {
|
|
9
9
|
width: number;
|
|
@@ -15,18 +15,16 @@ 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
|
-
|
|
20
|
-
* half the CSS blur radius; the spread shrinks the shape the key shadow is cast from.
|
|
21
|
-
*/
|
|
18
|
+
// Figma Shadow/floating: 12% blur 24, plus 4% blur 32 spread -16 offset 12; svg blur takes
|
|
19
|
+
// half the CSS blur radius, the spread shrinks the shape the key shadow is cast from
|
|
22
20
|
const AMBIENT_BLUR = 12;
|
|
23
21
|
const KEY_BLUR = 16;
|
|
24
22
|
const KEY_SPREAD = 16;
|
|
25
23
|
const KEY_OFFSET_Y = 12;
|
|
26
24
|
|
|
27
25
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
26
|
+
* rounded container path placed at (`x`, `y`) on the canvas, with the notch dipped into the
|
|
27
|
+
* top edge around `notchCenterX`
|
|
30
28
|
*/
|
|
31
29
|
const getContainerPath = (
|
|
32
30
|
width: number,
|
|
@@ -45,10 +43,11 @@ const getContainerPath = (
|
|
|
45
43
|
if (notchCenterX !== undefined) {
|
|
46
44
|
const cx = x + notchCenterX;
|
|
47
45
|
const point = ([dx, dy]: [number, number]) => `${cx + dx} ${top + dy}`;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
notch =
|
|
47
|
+
` L ${cx - NOTCH_HALF_WIDTH} ${top}` +
|
|
48
|
+
NOTCH_CURVES.map(
|
|
49
|
+
([c1, c2, end]) => ` C ${point(c1)} ${point(c2)} ${point(end)}`,
|
|
50
|
+
).join('');
|
|
52
51
|
}
|
|
53
52
|
return (
|
|
54
53
|
`M ${left + r} ${top}` +
|
|
@@ -65,8 +64,8 @@ const getContainerPath = (
|
|
|
65
64
|
};
|
|
66
65
|
|
|
67
66
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
67
|
+
* navigation bar container: surface fill with the floating shadow, drawn as a shape so
|
|
68
|
+
* the shadow follows the notch
|
|
70
69
|
*/
|
|
71
70
|
const CurvedContainer: React.FC<Props> = ({
|
|
72
71
|
width,
|
|
@@ -88,7 +87,6 @@ const CurvedContainer: React.FC<Props> = ({
|
|
|
88
87
|
SHADOW_PADDING,
|
|
89
88
|
notchCenterX,
|
|
90
89
|
);
|
|
91
|
-
const hasKeyShadow = width > 2 * KEY_SPREAD && height > 2 * KEY_SPREAD;
|
|
92
90
|
|
|
93
91
|
return (
|
|
94
92
|
<Svg
|
|
@@ -125,7 +123,7 @@ const CurvedContainer: React.FC<Props> = ({
|
|
|
125
123
|
fillOpacity={0.12}
|
|
126
124
|
filter={'url(#ambientShadow)'}
|
|
127
125
|
/>
|
|
128
|
-
{
|
|
126
|
+
{width > 2 * KEY_SPREAD && height > 2 * KEY_SPREAD && (
|
|
129
127
|
<Path
|
|
130
128
|
d={getContainerPath(
|
|
131
129
|
width - 2 * KEY_SPREAD,
|
|
@@ -139,7 +137,13 @@ const CurvedContainer: React.FC<Props> = ({
|
|
|
139
137
|
filter={'url(#keyShadow)'}
|
|
140
138
|
/>
|
|
141
139
|
)}
|
|
142
|
-
<Path
|
|
140
|
+
<Path
|
|
141
|
+
d={path}
|
|
142
|
+
fill={theme.colors.background.surface}
|
|
143
|
+
fillOpacity={TabBarMetrics.backgroundOpacity}
|
|
144
|
+
stroke={theme.colors.border.default}
|
|
145
|
+
strokeWidth={1}
|
|
146
|
+
/>
|
|
143
147
|
</Svg>
|
|
144
148
|
);
|
|
145
149
|
};
|