@momo-kits/foundation 0.169.5 → 0.169.6
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 +14 -36
- package/Application/BottomTab/CustomBottomTabItem.tsx +11 -15
- package/Application/BottomTab/DuoRailTabBar.tsx +7 -11
- package/Application/BottomTab/DuoTabBar.tsx +0 -1
- package/Application/BottomTab/TabBarIcon.tsx +5 -24
- package/Application/BottomTab/index.tsx +7 -12
- package/Application/BottomTab/metrics.ts +3 -4
- package/Application/ScaleSizeProvider.tsx +13 -6
- package/Context/index.ts +4 -1
- package/Text/utils.ts +14 -7
- package/package.json +1 -1
|
@@ -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 & {
|
|
@@ -68,27 +67,17 @@ const useCenterActionPress = () => {
|
|
|
68
67
|
};
|
|
69
68
|
|
|
70
69
|
/**
|
|
71
|
-
* Center action (.Nav/CTA): a button rising out of the notch, its label below
|
|
70
|
+
* Center action (.Nav/CTA): a button rising out of the notch, its label below
|
|
71
|
+
* (growing downward when scaled).
|
|
72
72
|
*/
|
|
73
|
-
const CenterAction: React.FC<{
|
|
74
|
-
floatingButton
|
|
75
|
-
|
|
76
|
-
}> = ({ floatingButton, fixedFontScale }) => {
|
|
73
|
+
const CenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
|
|
74
|
+
floatingButton,
|
|
75
|
+
}) => {
|
|
77
76
|
const { theme } = useContext(ApplicationContext);
|
|
78
77
|
const context = useContext<any>(MiniAppContext);
|
|
79
78
|
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
80
79
|
const { icon, label, onPress, labelContent } = floatingButton;
|
|
81
80
|
const { buttonStyle, onPressIn, onPressOut } = useCenterActionPress();
|
|
82
|
-
const labelText = (
|
|
83
|
-
<Text
|
|
84
|
-
numberOfLines={1}
|
|
85
|
-
typography={'label_s_medium'}
|
|
86
|
-
color={theme.colors.text.secondary}
|
|
87
|
-
>
|
|
88
|
-
{label}
|
|
89
|
-
</Text>
|
|
90
|
-
);
|
|
91
|
-
|
|
92
81
|
let iconNode = icon;
|
|
93
82
|
if (typeof icon === 'string') {
|
|
94
83
|
iconNode = (
|
|
@@ -100,11 +89,6 @@ const CenterAction: React.FC<{
|
|
|
100
89
|
);
|
|
101
90
|
}
|
|
102
91
|
|
|
103
|
-
let labelNode = labelText;
|
|
104
|
-
if (fixedFontScale) {
|
|
105
|
-
labelNode = <FixedScaleText>{labelText}</FixedScaleText>;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
92
|
return (
|
|
109
93
|
<View style={[styles.ctaSlot, showBaseLineDebug && styles.debugBaseLine]}>
|
|
110
94
|
<Pressable
|
|
@@ -125,7 +109,13 @@ const CenterAction: React.FC<{
|
|
|
125
109
|
{iconNode}
|
|
126
110
|
</Animated.View>
|
|
127
111
|
<View style={styles.ctaLabel}>
|
|
128
|
-
|
|
112
|
+
<Text
|
|
113
|
+
numberOfLines={1}
|
|
114
|
+
typography={'label_s_medium'}
|
|
115
|
+
color={theme.colors.text.secondary}
|
|
116
|
+
>
|
|
117
|
+
{label}
|
|
118
|
+
</Text>
|
|
129
119
|
{labelContent && (
|
|
130
120
|
<View pointerEvents={'none'} style={styles.ctaLabelOverlay}>
|
|
131
121
|
{labelContent}
|
|
@@ -147,15 +137,7 @@ const HorizontalTabBar: React.FC<{
|
|
|
147
137
|
activeTintColor: string;
|
|
148
138
|
inactiveTintColor: string;
|
|
149
139
|
floatingButton?: FloatingButtonProps;
|
|
150
|
-
|
|
151
|
-
}> = ({
|
|
152
|
-
items,
|
|
153
|
-
width,
|
|
154
|
-
activeTintColor,
|
|
155
|
-
inactiveTintColor,
|
|
156
|
-
floatingButton,
|
|
157
|
-
fixedFontScale,
|
|
158
|
-
}) => {
|
|
140
|
+
}> = ({ items, width, activeTintColor, inactiveTintColor, floatingButton }) => {
|
|
159
141
|
const withCta = hasCenterAction(floatingButton, items.length);
|
|
160
142
|
const [layouts, setLayouts] = useState<
|
|
161
143
|
Record<string, { x: number; y: number; width: number }>
|
|
@@ -181,7 +163,6 @@ const HorizontalTabBar: React.FC<{
|
|
|
181
163
|
onLongPress={item.onLongPress}
|
|
182
164
|
activeTintColor={activeTintColor}
|
|
183
165
|
inactiveTintColor={inactiveTintColor}
|
|
184
|
-
fixedFontScale={fixedFontScale}
|
|
185
166
|
/>
|
|
186
167
|
));
|
|
187
168
|
};
|
|
@@ -192,10 +173,7 @@ const HorizontalTabBar: React.FC<{
|
|
|
192
173
|
return (
|
|
193
174
|
<>
|
|
194
175
|
{renderItems(items.slice(0, middle))}
|
|
195
|
-
<CenterAction
|
|
196
|
-
floatingButton={floatingButton}
|
|
197
|
-
fixedFontScale={fixedFontScale}
|
|
198
|
-
/>
|
|
176
|
+
<CenterAction floatingButton={floatingButton} />
|
|
199
177
|
{renderItems(items.slice(middle))}
|
|
200
178
|
</>
|
|
201
179
|
);
|
|
@@ -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
|
-
* Tab item body: the icon in its indicator slot
|
|
41
|
-
* shared active indicator behind the
|
|
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'}
|
|
@@ -85,11 +82,6 @@ const TabItemContent: React.FC<TabItemContentProps> = ({
|
|
|
85
82
|
</View>
|
|
86
83
|
);
|
|
87
84
|
|
|
88
|
-
let labelNode = text;
|
|
89
|
-
if (fixedFontScale) {
|
|
90
|
-
labelNode = <FixedScaleText>{text}</FixedScaleText>;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
85
|
return (
|
|
94
86
|
<>
|
|
95
87
|
<View style={vertical ? styles.railSlot : styles.slot}>
|
|
@@ -168,9 +160,13 @@ const styles = StyleSheet.create({
|
|
|
168
160
|
paddingVertical: TabBarMetrics.railIndicatorPaddingVertical,
|
|
169
161
|
paddingHorizontal: TabBarMetrics.indicatorPaddingHorizontal,
|
|
170
162
|
},
|
|
171
|
-
|
|
163
|
+
labelSlot: {
|
|
164
|
+
height: TabBarMetrics.labelHeight,
|
|
172
165
|
alignSelf: 'stretch',
|
|
173
166
|
},
|
|
167
|
+
railLabelSlot: {
|
|
168
|
+
height: TabBarMetrics.railLabelHeight,
|
|
169
|
+
},
|
|
174
170
|
label: {
|
|
175
171
|
textAlign: 'center',
|
|
176
172
|
},
|
|
@@ -13,7 +13,6 @@ 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 { FixedScaleText } from './TabBarIcon';
|
|
17
16
|
import { usePressLift } from './usePressLift';
|
|
18
17
|
import { ActiveIndicator } from './ActiveIndicator';
|
|
19
18
|
import {
|
|
@@ -76,7 +75,6 @@ const Rail: React.FC<{
|
|
|
76
75
|
>
|
|
77
76
|
<TabItemContent
|
|
78
77
|
vertical
|
|
79
|
-
fixedFontScale
|
|
80
78
|
label={item.label}
|
|
81
79
|
focused={item.focused}
|
|
82
80
|
icon={item.icon}
|
|
@@ -116,15 +114,13 @@ const RailCenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
|
|
|
116
114
|
<CtaIcon icon={floatingButton.icon} />
|
|
117
115
|
</Animated.View>
|
|
118
116
|
<View style={styles.railCtaLabel}>
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
</Text>
|
|
127
|
-
</FixedScaleText>
|
|
117
|
+
<Text
|
|
118
|
+
numberOfLines={1}
|
|
119
|
+
typography={'label_xs_medium'}
|
|
120
|
+
color={theme.colors.text.secondary}
|
|
121
|
+
>
|
|
122
|
+
{floatingButton.label}
|
|
123
|
+
</Text>
|
|
128
124
|
{floatingButton.labelContent && (
|
|
129
125
|
<View pointerEvents={'none'} style={styles.railCtaLabelOverlay}>
|
|
130
126
|
{floatingButton.labelContent}
|
|
@@ -7,9 +7,10 @@ import Animated, {
|
|
|
7
7
|
} from 'react-native-reanimated';
|
|
8
8
|
|
|
9
9
|
import { Colors, Spacing } from '../../Consts';
|
|
10
|
-
import { ApplicationContext
|
|
10
|
+
import { ApplicationContext } from '../../Context';
|
|
11
11
|
import { BadgeDot } from '../../Badge';
|
|
12
12
|
import { Text } from '../../Text';
|
|
13
|
+
import ScaleSizeProvider from '../ScaleSizeProvider';
|
|
13
14
|
import { TabBarMetrics, TabBarMotion } from './metrics';
|
|
14
15
|
|
|
15
16
|
type Props = {
|
|
@@ -52,29 +53,9 @@ export const getTabBadge = (
|
|
|
52
53
|
return text;
|
|
53
54
|
};
|
|
54
55
|
|
|
55
|
-
/**
|
|
56
|
-
* Keeps text at its design size: like the system tab bar it doesn't grow with the OS font
|
|
57
|
-
* scale or the wide Duo displays, which Text otherwise scales up to 5pt.
|
|
58
|
-
*/
|
|
59
|
-
export const FixedScaleText: React.FC<{ children: React.ReactNode }> = ({
|
|
60
|
-
children,
|
|
61
|
-
}) => {
|
|
62
|
-
const context = useContext<any>(MiniAppContext);
|
|
63
|
-
return (
|
|
64
|
-
<MiniAppContext.Provider
|
|
65
|
-
value={{
|
|
66
|
-
...context,
|
|
67
|
-
fontScale: { useOSScaleRate: false, userScaleRate: 1 },
|
|
68
|
-
}}
|
|
69
|
-
>
|
|
70
|
-
{children}
|
|
71
|
-
</MiniAppContext.Provider>
|
|
72
|
-
);
|
|
73
|
-
};
|
|
74
|
-
|
|
75
56
|
/**
|
|
76
57
|
* Label badge of a tab: numbers use the error color, text the warning color (Figma Badge
|
|
77
|
-
* round numeric / text).
|
|
58
|
+
* round numeric / text). The text keeps its design size to fit the 16 height.
|
|
78
59
|
*/
|
|
79
60
|
const TabBadge: React.FC<{ label: string; vertical?: boolean }> = ({
|
|
80
61
|
label,
|
|
@@ -96,7 +77,7 @@ const TabBadge: React.FC<{ label: string; vertical?: boolean }> = ({
|
|
|
96
77
|
},
|
|
97
78
|
]}
|
|
98
79
|
>
|
|
99
|
-
<
|
|
80
|
+
<ScaleSizeProvider scaleSizeMaxRate={1}>
|
|
100
81
|
<Text
|
|
101
82
|
numberOfLines={1}
|
|
102
83
|
typography={'action_xxs_bold'}
|
|
@@ -104,7 +85,7 @@ const TabBadge: React.FC<{ label: string; vertical?: boolean }> = ({
|
|
|
104
85
|
>
|
|
105
86
|
{label}
|
|
106
87
|
</Text>
|
|
107
|
-
</
|
|
88
|
+
</ScaleSizeProvider>
|
|
108
89
|
</View>
|
|
109
90
|
);
|
|
110
91
|
if (vertical) {
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
} from 'react-native';
|
|
15
15
|
import { Icon } from '../../Icon';
|
|
16
16
|
import { BottomTabProps } from '../types';
|
|
17
|
+
import ScaleSizeProvider from '../ScaleSizeProvider';
|
|
17
18
|
import BottomTabBar, { hasCenterAction } from './BottomTabBar';
|
|
18
19
|
import { TabBarMetrics } from './metrics';
|
|
19
20
|
import { getTabBadge } from './TabBarIcon';
|
|
@@ -131,24 +132,18 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
131
132
|
},
|
|
132
133
|
};
|
|
133
134
|
|
|
135
|
+
// One cap for the whole bar (labels, CTA, custom content) so it fits the fixed bar and notch.
|
|
134
136
|
const renderTabBar = (props: BottomTabBarProps) => {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
137
|
+
const TabBar = isIPhoneDuo ? DuoTabBar : BottomTabBar;
|
|
138
|
+
return (
|
|
139
|
+
<ScaleSizeProvider scaleSizeMaxRate={TabBarMetrics.labelMaxScale}>
|
|
140
|
+
<TabBar
|
|
138
141
|
{...props}
|
|
139
142
|
floatingButton={floatingButton}
|
|
140
143
|
inactiveTintColor={theme.colors.text.secondary}
|
|
141
144
|
activeTintColor={theme.colors.primary}
|
|
142
145
|
/>
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
return (
|
|
146
|
-
<BottomTabBar
|
|
147
|
-
{...props}
|
|
148
|
-
floatingButton={floatingButton}
|
|
149
|
-
inactiveTintColor={theme.colors.text.secondary}
|
|
150
|
-
activeTintColor={theme.colors.primary}
|
|
151
|
-
/>
|
|
146
|
+
</ScaleSizeProvider>
|
|
152
147
|
);
|
|
153
148
|
};
|
|
154
149
|
|
|
@@ -19,16 +19,15 @@ const TabBarMetrics = {
|
|
|
19
19
|
indicatorPaddingHorizontal: Spacing.M,
|
|
20
20
|
indicatorRadius: Radius.XL,
|
|
21
21
|
indicatorHeight: 32,
|
|
22
|
-
|
|
22
|
+
labelMaxScale: 1.2,
|
|
23
|
+
labelHeight: 18,
|
|
24
|
+
railLabelHeight: 14,
|
|
23
25
|
systemIndicatorHeight: 24,
|
|
24
|
-
// Unfolded foldables (short side from 600) use 65% of it, centered; others fill the window
|
|
25
26
|
foldableMinShortSide: 600,
|
|
26
27
|
foldableWidthRatio: 0.65,
|
|
27
28
|
ctaSlotWidth: 92,
|
|
28
29
|
ctaSize: 48,
|
|
29
|
-
// the icon sits 10 inside the 48 button (prototype .cta: 28 icon), 12 inside the 52 rail one
|
|
30
30
|
ctaIconSize: 28,
|
|
31
|
-
// the button rises 22 above the container top edge
|
|
32
31
|
ctaLift: 22,
|
|
33
32
|
ctaLabelGap: Spacing.M,
|
|
34
33
|
ctaLabelHeight: 18,
|
|
@@ -1,16 +1,23 @@
|
|
|
1
|
-
import React, { FC } from 'react';
|
|
2
|
-
import {
|
|
1
|
+
import React, { FC, useContext, useMemo } from 'react';
|
|
2
|
+
import { MiniAppContext } from '../Context';
|
|
3
3
|
import { ScaleSizeProviderProps } from './types';
|
|
4
4
|
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* Caps the font scale of its subtree at `scaleSizeMaxRate` × the design size
|
|
7
|
+
* (`useScaleSize`), passed down in MiniAppContext. The nearest provider wins;
|
|
8
|
+
* an undefined rate lifts an outer cap.
|
|
9
|
+
*/
|
|
6
10
|
const ScaleSizeProvider: FC<ScaleSizeProviderProps> = ({
|
|
7
11
|
scaleSizeMaxRate,
|
|
8
12
|
children,
|
|
9
13
|
}) => {
|
|
14
|
+
const context = useContext<object | undefined>(MiniAppContext);
|
|
15
|
+
const value = useMemo(() => {
|
|
16
|
+
return { ...context, scaleSizeMaxRate };
|
|
17
|
+
}, [context, scaleSizeMaxRate]);
|
|
18
|
+
|
|
10
19
|
return (
|
|
11
|
-
<
|
|
12
|
-
{children}
|
|
13
|
-
</ScaleSizeContext.Provider>
|
|
20
|
+
<MiniAppContext.Provider value={value}>{children}</MiniAppContext.Provider>
|
|
14
21
|
);
|
|
15
22
|
};
|
|
16
23
|
|
package/Context/index.ts
CHANGED
|
@@ -10,7 +10,10 @@ const MiniAppContext = (Platform as any).MiniAppContext ?? Context;
|
|
|
10
10
|
const ScreenContext = (Platform as any).ScreenContext ?? Context;
|
|
11
11
|
const ComponentContext = (Platform as any).ComponentContext ?? Context;
|
|
12
12
|
const SkeletonContext = createContext({ loading: false });
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* @deprecated Not read: wrap the subtree in `ScaleSizeProvider`, which caps the
|
|
15
|
+
* font scale through MiniAppContext. Kept for mini apps built on older kits.
|
|
16
|
+
*/
|
|
14
17
|
const ScaleSizeContext = createContext<{ scaleSizeMaxRate?: number }>({
|
|
15
18
|
scaleSizeMaxRate: undefined,
|
|
16
19
|
});
|
package/Text/utils.ts
CHANGED
|
@@ -6,21 +6,28 @@ const DEFAULT_SCREEN_SIZE = 375;
|
|
|
6
6
|
const MAX_FONT_SCALE = 1.5;
|
|
7
7
|
const MAX_DEVICE_SCALE = 5;
|
|
8
8
|
|
|
9
|
+
type MiniAppScale = {
|
|
10
|
+
scaleSizeMaxRate?: number;
|
|
11
|
+
fontScale?: { useOSScaleRate?: boolean; userScaleRate?: number };
|
|
12
|
+
};
|
|
13
|
+
|
|
9
14
|
/**
|
|
10
|
-
* Scales `size` by screen width (
|
|
11
|
-
*
|
|
12
|
-
*
|
|
15
|
+
* Scales `size` by screen width (+5 max) and OS font scale (max `scaleRate`,
|
|
16
|
+
* else the ScaleSizeProvider rate, else 1.5), taking the larger, or by the mini
|
|
17
|
+
* app `fontScale.userScaleRate`; a provider rate caps both at `size × rate`.
|
|
13
18
|
*/
|
|
14
19
|
const useScaleSize = (size: number, scaleRate?: number) => {
|
|
15
20
|
const { width } = useWindowDimensions();
|
|
16
|
-
const context = useContext<
|
|
21
|
+
const context = useContext<MiniAppScale | undefined>(MiniAppContext);
|
|
22
|
+
const scaleSizeMaxRate = context?.scaleSizeMaxRate;
|
|
23
|
+
const maxSize = scaleSizeMaxRate ? size * scaleSizeMaxRate : Infinity;
|
|
17
24
|
if (context?.fontScale?.useOSScaleRate === false) {
|
|
18
|
-
return size * (context.fontScale.userScaleRate ?? 1);
|
|
25
|
+
return Math.min(size * (context.fontScale.userScaleRate ?? 1), maxSize);
|
|
19
26
|
}
|
|
20
27
|
const fontScale = PixelRatio.getFontScale();
|
|
21
28
|
const deviceScale = width / DEFAULT_SCREEN_SIZE;
|
|
22
29
|
|
|
23
|
-
const maxScaleRate = scaleRate ?? MAX_FONT_SCALE;
|
|
30
|
+
const maxScaleRate = scaleRate ?? scaleSizeMaxRate ?? MAX_FONT_SCALE;
|
|
24
31
|
|
|
25
32
|
let fontSizeDeviceScale = size;
|
|
26
33
|
let fontSizeOSScale = size;
|
|
@@ -39,7 +46,7 @@ const useScaleSize = (size: number, scaleRate?: number) => {
|
|
|
39
46
|
);
|
|
40
47
|
}
|
|
41
48
|
|
|
42
|
-
return Math.max(fontSizeDeviceScale, fontSizeOSScale);
|
|
49
|
+
return Math.min(Math.max(fontSizeDeviceScale, fontSizeOSScale), maxSize);
|
|
43
50
|
};
|
|
44
51
|
|
|
45
52
|
/**
|