@momo-kits/foundation 0.169.4-beta.1 → 0.169.4-beta.2
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/BottomTab/BottomTabBar.tsx +9 -12
- package/Application/BottomTab/CustomBottomTabItem.tsx +7 -2
- package/Application/BottomTab/DuoRailTabBar.tsx +3 -3
- package/Application/BottomTab/TabBarIcon.tsx +24 -0
- package/Application/BottomTab/metrics.ts +3 -0
- package/Application/types.tabs.ts +0 -1
- package/Layout/FloatingButton.tsx +8 -18
- package/Text/utils.ts +8 -3
- package/package.json +1 -1
|
@@ -17,7 +17,7 @@ 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 {
|
|
20
|
+
import { CappedScaleText } from './TabBarIcon';
|
|
21
21
|
import { styles } from './BottomTabBar.styles';
|
|
22
22
|
|
|
23
23
|
type Props = BottomTabBarProps & {
|
|
@@ -114,7 +114,7 @@ const CenterAction: React.FC<{
|
|
|
114
114
|
</Animated.View>
|
|
115
115
|
<View style={styles.ctaLabel}>
|
|
116
116
|
{fixedFontScale ? (
|
|
117
|
-
<
|
|
117
|
+
<CappedScaleText>{labelText}</CappedScaleText>
|
|
118
118
|
) : (
|
|
119
119
|
labelText
|
|
120
120
|
)}
|
|
@@ -194,21 +194,17 @@ const HorizontalTabBar: React.FC<{
|
|
|
194
194
|
return renderItems(items);
|
|
195
195
|
};
|
|
196
196
|
|
|
197
|
-
const customContainer = withCta && floatingButton?.container;
|
|
198
|
-
|
|
199
197
|
return (
|
|
200
198
|
<View
|
|
201
199
|
style={[styles.container, { width }]}
|
|
202
200
|
accessibilityLabel={'bottom_tab_bar'}
|
|
203
201
|
>
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
/>
|
|
211
|
-
)}
|
|
202
|
+
<CurvedContainer
|
|
203
|
+
width={width}
|
|
204
|
+
height={TabBarMetrics.height}
|
|
205
|
+
radius={TabBarMetrics.height / 2}
|
|
206
|
+
notchCenterX={withCta ? width / 2 : undefined}
|
|
207
|
+
/>
|
|
212
208
|
<View style={styles.content}>
|
|
213
209
|
<ActiveIndicator
|
|
214
210
|
axis={'x'}
|
|
@@ -258,6 +254,7 @@ export default function BottomTabBar(props: Props) {
|
|
|
258
254
|
]}
|
|
259
255
|
>
|
|
260
256
|
<HorizontalTabBar
|
|
257
|
+
fixedFontScale
|
|
261
258
|
items={getTabItems(props)}
|
|
262
259
|
width={width}
|
|
263
260
|
floatingButton={floatingButton}
|
|
@@ -10,7 +10,7 @@ import Animated, {
|
|
|
10
10
|
useSharedValue,
|
|
11
11
|
withTiming,
|
|
12
12
|
} from 'react-native-reanimated';
|
|
13
|
-
import {
|
|
13
|
+
import { CappedScaleText, TabBarIcon } from './TabBarIcon';
|
|
14
14
|
import { Text } from '../../Text';
|
|
15
15
|
import { MiniAppContext } from '../../Context';
|
|
16
16
|
import { Colors, Spacing } from '../../Consts';
|
|
@@ -98,7 +98,7 @@ const TabItemContent: React.FC<TabItemContentProps> = ({
|
|
|
98
98
|
/>
|
|
99
99
|
</View>
|
|
100
100
|
{!hideLabel &&
|
|
101
|
-
(fixedFontScale ? <
|
|
101
|
+
(fixedFontScale ? <CappedScaleText>{text}</CappedScaleText> : text)}
|
|
102
102
|
</>
|
|
103
103
|
);
|
|
104
104
|
};
|
|
@@ -166,6 +166,11 @@ const styles = StyleSheet.create({
|
|
|
166
166
|
},
|
|
167
167
|
labelBox: {
|
|
168
168
|
alignSelf: 'stretch',
|
|
169
|
+
// fixed to the design line height so a capped-scale label overflows downward and the
|
|
170
|
+
// icon above it stays put
|
|
171
|
+
height: TabBarMetrics.labelHeight,
|
|
172
|
+
justifyContent: 'flex-start',
|
|
173
|
+
overflow: 'visible',
|
|
169
174
|
},
|
|
170
175
|
label: {
|
|
171
176
|
textAlign: 'center',
|
|
@@ -13,7 +13,7 @@ import { TabItemContent } from './CustomBottomTabItem';
|
|
|
13
13
|
import { CurvedContainer } from './CurvedContainer';
|
|
14
14
|
import { TabBarMetrics } from './metrics';
|
|
15
15
|
import { getTabItems, TabItem } from './tabItems';
|
|
16
|
-
import {
|
|
16
|
+
import { CappedScaleText } from './TabBarIcon';
|
|
17
17
|
import { usePressLift } from './usePressLift';
|
|
18
18
|
import { ActiveIndicator } from './ActiveIndicator';
|
|
19
19
|
import {
|
|
@@ -116,7 +116,7 @@ const RailCenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
|
|
|
116
116
|
<CtaIcon icon={floatingButton.icon} />
|
|
117
117
|
</Animated.View>
|
|
118
118
|
<View style={styles.railCtaLabel}>
|
|
119
|
-
<
|
|
119
|
+
<CappedScaleText>
|
|
120
120
|
<Text
|
|
121
121
|
numberOfLines={1}
|
|
122
122
|
typography={'label_xs_medium'}
|
|
@@ -124,7 +124,7 @@ const RailCenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
|
|
|
124
124
|
>
|
|
125
125
|
{floatingButton.label}
|
|
126
126
|
</Text>
|
|
127
|
-
</
|
|
127
|
+
</CappedScaleText>
|
|
128
128
|
{floatingButton.labelContent && (
|
|
129
129
|
<View pointerEvents={'none'} style={styles.railCtaLabelOverlay}>
|
|
130
130
|
{floatingButton.labelContent}
|
|
@@ -71,6 +71,30 @@ export const FixedScaleText: React.FC<{ children: React.ReactNode }> = ({
|
|
|
71
71
|
);
|
|
72
72
|
};
|
|
73
73
|
|
|
74
|
+
// the tab labels follow the OS/host font scale but never grow past this, so the fixed-height
|
|
75
|
+
// container and the notch stay aligned
|
|
76
|
+
const TAB_LABEL_MAX_SCALE = 1.2;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* caps the label scale at `TAB_LABEL_MAX_SCALE`: it keeps following the host/OS font scale but
|
|
80
|
+
* stops growing past 1.2× so it fits the fixed bar geometry
|
|
81
|
+
*/
|
|
82
|
+
export const CappedScaleText: React.FC<{ children: React.ReactNode }> = ({
|
|
83
|
+
children,
|
|
84
|
+
}) => {
|
|
85
|
+
const context = useContext<any>(MiniAppContext);
|
|
86
|
+
return (
|
|
87
|
+
<MiniAppContext.Provider
|
|
88
|
+
value={{
|
|
89
|
+
...context,
|
|
90
|
+
fontScale: { ...context?.fontScale, maxScaleRate: TAB_LABEL_MAX_SCALE },
|
|
91
|
+
}}
|
|
92
|
+
>
|
|
93
|
+
{children}
|
|
94
|
+
</MiniAppContext.Provider>
|
|
95
|
+
);
|
|
96
|
+
};
|
|
97
|
+
|
|
74
98
|
/**
|
|
75
99
|
* numbers use the error color, text the warning color (Figma Badge round numeric / text)
|
|
76
100
|
*/
|
|
@@ -20,6 +20,9 @@ const TabBarMetrics = {
|
|
|
20
20
|
indicatorPaddingHorizontal: Spacing.M,
|
|
21
21
|
indicatorRadius: Radius.XL,
|
|
22
22
|
indicatorHeight: 32,
|
|
23
|
+
// label_s_medium line height: the label box keeps this height so a scaled label grows
|
|
24
|
+
// downward instead of pushing the icon up
|
|
25
|
+
labelHeight: 18,
|
|
23
26
|
// home indicator / gesture bar area under the container
|
|
24
27
|
systemIndicatorHeight: 24,
|
|
25
28
|
// unfolded foldables (short side from 600, the smallest in the spec) use 65% of the short
|
|
@@ -13,7 +13,7 @@ import Animated, {
|
|
|
13
13
|
} from 'react-native-reanimated';
|
|
14
14
|
import { scheduleOnRN } from 'react-native-worklets';
|
|
15
15
|
import { ApplicationContext } from '../Context';
|
|
16
|
-
import { Colors
|
|
16
|
+
import { Colors } from '../Consts';
|
|
17
17
|
import { Icon } from '../Icon';
|
|
18
18
|
import { useScaleSize, exportFontFamily } from '../Text';
|
|
19
19
|
import { type AnimatedCompatValue } from '../Application/utils';
|
|
@@ -50,11 +50,8 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
|
|
|
50
50
|
const labelFontFamily = exportFontFamily('bold');
|
|
51
51
|
const maxWidth = useSharedValue(0);
|
|
52
52
|
const minWidth = size === 'small' ? 36 : 48;
|
|
53
|
-
const iconSize = size === 'small' ? 12 : 24;
|
|
54
53
|
const opacityAnimated = useSharedValue(0);
|
|
55
54
|
const widthAnimated = useSharedValue<number | null>(null);
|
|
56
|
-
const paddingLeftAnimated = useSharedValue(Spacing.M);
|
|
57
|
-
const collapsedPaddingLeft = useSharedValue(Spacing.M);
|
|
58
55
|
const lastOffset = useSharedValue(0);
|
|
59
56
|
const lastDirection = useSharedValue<string | null>(null);
|
|
60
57
|
const [showText, setShowText] = useState(true);
|
|
@@ -76,9 +73,6 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
|
|
|
76
73
|
lastDirection.value = direction;
|
|
77
74
|
if (direction === 'down') {
|
|
78
75
|
opacityAnimated.value = withTiming(0, { duration: 100 });
|
|
79
|
-
paddingLeftAnimated.value = withTiming(collapsedPaddingLeft.value, {
|
|
80
|
-
duration: 100,
|
|
81
|
-
});
|
|
82
76
|
widthAnimated.value = withTiming(
|
|
83
77
|
minWidth,
|
|
84
78
|
{ duration: 100 },
|
|
@@ -88,7 +82,6 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
|
|
|
88
82
|
);
|
|
89
83
|
} else {
|
|
90
84
|
opacityAnimated.value = withTiming(1, { duration: 100 });
|
|
91
|
-
paddingLeftAnimated.value = withTiming(Spacing.M, { duration: 100 });
|
|
92
85
|
widthAnimated.value = withTiming(
|
|
93
86
|
maxWidth.value,
|
|
94
87
|
{ duration: 100 },
|
|
@@ -105,7 +98,6 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
|
|
|
105
98
|
|
|
106
99
|
const containerStyle = useAnimatedStyle(() => ({
|
|
107
100
|
width: widthAnimated.value ?? undefined,
|
|
108
|
-
paddingLeft: paddingLeftAnimated.value,
|
|
109
101
|
}));
|
|
110
102
|
|
|
111
103
|
const labelStyle = useAnimatedStyle(() => ({
|
|
@@ -115,14 +107,8 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
|
|
|
115
107
|
const handleLayout = (event: LayoutChangeEvent) => {
|
|
116
108
|
const layout = event.nativeEvent.layout;
|
|
117
109
|
if (widthAnimated.value != null) return;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
maxWidth.value = expandedWidth;
|
|
121
|
-
widthAnimated.value = expandedWidth;
|
|
122
|
-
// collapsed it is a circle as wide as it is tall: center the icon from the
|
|
123
|
-
// rendered height so it stays centered when sizes are scaled but padding is not
|
|
124
|
-
collapsedPaddingLeft.value =
|
|
125
|
-
(layout.height - (layout.height * iconSize) / minWidth) / 2;
|
|
110
|
+
maxWidth.value = layout.width;
|
|
111
|
+
widthAnimated.value = layout.width;
|
|
126
112
|
};
|
|
127
113
|
|
|
128
114
|
if (renderComponent) {
|
|
@@ -154,7 +140,11 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
|
|
|
154
140
|
]}
|
|
155
141
|
>
|
|
156
142
|
<TouchableOpacity style={styles.content} onPress={onPress}>
|
|
157
|
-
<Icon
|
|
143
|
+
<Icon
|
|
144
|
+
source={icon}
|
|
145
|
+
size={size === 'small' ? 12 : 24}
|
|
146
|
+
color={iconColor}
|
|
147
|
+
/>
|
|
158
148
|
{showText && !!label && (
|
|
159
149
|
<Animated.Text
|
|
160
150
|
allowFontScaling={false}
|
package/Text/utils.ts
CHANGED
|
@@ -9,13 +9,18 @@ const MAX_DEVICE_SCALE = 5;
|
|
|
9
9
|
const useScaleSize = (size: number, scaleRate?: number) => {
|
|
10
10
|
const { width } = useWindowDimensions();
|
|
11
11
|
const context = useContext<any>(MiniAppContext);
|
|
12
|
+
// opt-in ceiling: when set (arg or context), the final size never exceeds size × capRate,
|
|
13
|
+
// in both the mini-app userScaleRate path and the OS path. Undefined keeps the old behavior.
|
|
14
|
+
const capRate = scaleRate ?? context?.fontScale?.maxScaleRate;
|
|
15
|
+
const cap = (scaled: number) =>
|
|
16
|
+
capRate != null ? Math.min(scaled, size * capRate) : scaled;
|
|
12
17
|
if (context?.fontScale?.useOSScaleRate === false) {
|
|
13
|
-
return size * (context.fontScale.userScaleRate ?? 1);
|
|
18
|
+
return cap(size * (context.fontScale.userScaleRate ?? 1));
|
|
14
19
|
}
|
|
15
20
|
const fontScale = PixelRatio.getFontScale();
|
|
16
21
|
const deviceScale = width / DEFAULT_SCREEN_SIZE;
|
|
17
22
|
|
|
18
|
-
const maxScaleRate =
|
|
23
|
+
const maxScaleRate = capRate ?? MAX_FONT_SCALE;
|
|
19
24
|
|
|
20
25
|
let fontSizeDeviceScale = size;
|
|
21
26
|
let fontSizeOSScale = size;
|
|
@@ -34,7 +39,7 @@ const useScaleSize = (size: number, scaleRate?: number) => {
|
|
|
34
39
|
);
|
|
35
40
|
}
|
|
36
41
|
|
|
37
|
-
return Math.max(fontSizeDeviceScale, fontSizeOSScale);
|
|
42
|
+
return cap(Math.max(fontSizeDeviceScale, fontSizeOSScale));
|
|
38
43
|
};
|
|
39
44
|
|
|
40
45
|
const scaleSize = (size: number) => {
|