@momo-kits/foundation 0.169.6-beta.1 → 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/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.styles.ts +1 -18
- package/Application/BottomTab/BottomTabBar.tsx +58 -57
- package/Application/BottomTab/BottomTabInset.tsx +8 -4
- package/Application/BottomTab/CurvedContainer.tsx +16 -20
- package/Application/BottomTab/CustomBottomTabItem.tsx +14 -15
- package/Application/BottomTab/DuoBarCompression.tsx +13 -19
- package/Application/BottomTab/DuoRailTabBar.tsx +20 -20
- package/Application/BottomTab/DuoTabBar.tsx +11 -24
- 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 +25 -48
- 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 +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 +8 -35
- 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 +1 -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 +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/__tests__/modalLayout.test.ts +0 -128
package/Layout/screenContent.tsx
CHANGED
|
@@ -9,7 +9,8 @@ import { AnimatedHeader } from '../Application/types';
|
|
|
9
9
|
type ContentInset = { insetLeft: number; insetRight: number };
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* Wraps each screen child (Fragments flattened) in a view with the screen's top spacing
|
|
13
|
+
* and side insets; a child with `useMargin: false` gets no top spacing.
|
|
13
14
|
*/
|
|
14
15
|
export const renderContent = (content: any, layout: ContentInset): any => {
|
|
15
16
|
const results = validateChildren(content, [undefined]);
|
|
@@ -64,7 +65,8 @@ export const renderContent = (content: any, layout: ContentInset): any => {
|
|
|
64
65
|
};
|
|
65
66
|
|
|
66
67
|
/**
|
|
67
|
-
*
|
|
68
|
+
* Animated banner from `animatedHeader.component`, clipped to the banner height plus
|
|
69
|
+
* `layoutOffset`. Nothing without a component.
|
|
68
70
|
*/
|
|
69
71
|
export const renderAnimatedHeader = (
|
|
70
72
|
animatedHeader: AnimatedHeader | undefined,
|
package/Layout/screenFooter.tsx
CHANGED
|
@@ -16,6 +16,10 @@ type ScreenFooterProps = {
|
|
|
16
16
|
surfaceColor: string;
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Screen footer on the surface color with a top shadow, padded above the home indicator,
|
|
21
|
+
* or above the floating tab bar on a tab screen.
|
|
22
|
+
*/
|
|
19
23
|
export const ScreenFooter = ({
|
|
20
24
|
footer,
|
|
21
25
|
marginFooter,
|
|
@@ -26,28 +30,34 @@ export const ScreenFooter = ({
|
|
|
26
30
|
duoTabInset,
|
|
27
31
|
bottomInset,
|
|
28
32
|
surfaceColor,
|
|
29
|
-
}: ScreenFooterProps) =>
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
marginRight: insetRight,
|
|
37
|
-
},
|
|
38
|
-
]}
|
|
39
|
-
>
|
|
33
|
+
}: ScreenFooterProps) => {
|
|
34
|
+
let bottomSpace = bottomInset;
|
|
35
|
+
if (isTab && duoTabInset) {
|
|
36
|
+
bottomSpace = duoTabInset;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
40
|
<View
|
|
41
|
+
pointerEvents={'box-none'}
|
|
41
42
|
style={[
|
|
42
|
-
styles.
|
|
43
|
+
hasContentInset && styles.footerClip,
|
|
43
44
|
{
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
backgroundColor: surfaceColor,
|
|
45
|
+
marginLeft: insetLeft,
|
|
46
|
+
marginRight: insetRight,
|
|
47
47
|
},
|
|
48
48
|
]}
|
|
49
49
|
>
|
|
50
|
-
<
|
|
50
|
+
<View
|
|
51
|
+
style={[
|
|
52
|
+
styles.shadow,
|
|
53
|
+
{
|
|
54
|
+
paddingBottom: bottomSpace + Spacing.S,
|
|
55
|
+
backgroundColor: surfaceColor,
|
|
56
|
+
},
|
|
57
|
+
]}
|
|
58
|
+
>
|
|
59
|
+
<Section useMargin={marginFooter}>{footer}</Section>
|
|
60
|
+
</View>
|
|
51
61
|
</View>
|
|
52
|
-
|
|
53
|
-
|
|
62
|
+
);
|
|
63
|
+
};
|
package/Layout/screenHeader.tsx
CHANGED
|
@@ -31,6 +31,10 @@ type ScreenHeaderParams = {
|
|
|
31
31
|
layoutOffset: number;
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Applies the Screen's navigation header (plain, extended, animated banner or search) and
|
|
36
|
+
* re-applies it when those props change. Returns the search header setter and the tint.
|
|
37
|
+
*/
|
|
34
38
|
export const useScreenHeader = ({
|
|
35
39
|
navigation,
|
|
36
40
|
layout,
|
|
@@ -48,10 +52,7 @@ export const useScreenHeader = ({
|
|
|
48
52
|
const currentTint = useRef<string | undefined>(undefined);
|
|
49
53
|
const appliedHeaderMode = useRef<string | undefined>(undefined);
|
|
50
54
|
|
|
51
|
-
|
|
52
|
-
* export options for screen
|
|
53
|
-
* @param type
|
|
54
|
-
*/
|
|
55
|
+
// keepTitle leaves a title the screen set itself when the same type is re-applied
|
|
55
56
|
const setHeaderType = useCallback(
|
|
56
57
|
(type: HeaderType, keepTitle = false) => {
|
|
57
58
|
let options: StackNavigationOptions;
|
|
@@ -69,10 +70,12 @@ export const useScreenHeader = ({
|
|
|
69
70
|
...quickReturnOptions,
|
|
70
71
|
headerShown: true,
|
|
71
72
|
headerTransparent: true,
|
|
72
|
-
headerBackground: () =>
|
|
73
|
-
hasSideBar
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
headerBackground: () => {
|
|
74
|
+
if (!hasSideBar) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
return <DuoGlassBackground animatedValue={sharedValue} />;
|
|
78
|
+
},
|
|
76
79
|
headerTitle: (props: any) => <HeaderTitle {...props} />,
|
|
77
80
|
};
|
|
78
81
|
if (inputSearchProps) {
|
|
@@ -160,10 +163,7 @@ export const useScreenHeader = ({
|
|
|
160
163
|
],
|
|
161
164
|
);
|
|
162
165
|
|
|
163
|
-
|
|
164
|
-
* export animatedHeader options for screen
|
|
165
|
-
* @param nextAnimatedHeader
|
|
166
|
-
*/
|
|
166
|
+
// the first tint the animated header sets is kept for later scroll updates
|
|
167
167
|
const setAnimatedHeader = useCallback(
|
|
168
168
|
(nextAnimatedHeader: AnimatedHeader) => {
|
|
169
169
|
let options: StackNavigationOptions;
|
|
@@ -175,13 +175,16 @@ export const useScreenHeader = ({
|
|
|
175
175
|
...quickReturnOptions,
|
|
176
176
|
headerTintColor: currentTint.current ?? Colors.black_17,
|
|
177
177
|
headerTransparent: true,
|
|
178
|
-
headerBackground: (props: any) =>
|
|
179
|
-
hasSideBar
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
178
|
+
headerBackground: (props: any) => {
|
|
179
|
+
if (hasSideBar) {
|
|
180
|
+
return (
|
|
181
|
+
<DuoGlassBackground
|
|
182
|
+
animatedValue={sharedValue}
|
|
183
|
+
bannerBottom={ANIMATED_BANNER_HEIGHT + layoutOffset}
|
|
184
|
+
/>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
return (
|
|
185
188
|
<HeaderBackground
|
|
186
189
|
{...props}
|
|
187
190
|
animatedValue={sharedValue}
|
|
@@ -190,7 +193,8 @@ export const useScreenHeader = ({
|
|
|
190
193
|
headerBackground={headerBackground}
|
|
191
194
|
gradientColor={gradientColor}
|
|
192
195
|
/>
|
|
193
|
-
)
|
|
196
|
+
);
|
|
197
|
+
},
|
|
194
198
|
headerTitle: (props: any) => {
|
|
195
199
|
return <HeaderTitle {...props} />;
|
|
196
200
|
},
|
|
@@ -217,9 +221,6 @@ export const useScreenHeader = ({
|
|
|
217
221
|
],
|
|
218
222
|
);
|
|
219
223
|
|
|
220
|
-
/**
|
|
221
|
-
* export animatedHeader options for screen
|
|
222
|
-
*/
|
|
223
224
|
const setAnimatedSearch = useCallback(() => {
|
|
224
225
|
const options: StackNavigationOptions = {
|
|
225
226
|
headerShown: true,
|
|
@@ -240,9 +241,7 @@ export const useScreenHeader = ({
|
|
|
240
241
|
navigation?.instance?.setOptions({ header: undefined, ...options });
|
|
241
242
|
}, [navigation?.instance, sharedValue]);
|
|
242
243
|
|
|
243
|
-
|
|
244
|
-
* export search header
|
|
245
|
-
*/
|
|
244
|
+
// the search field spans the content, clear of the side bar and status bar clusters
|
|
246
245
|
const setSearchHeader = (params: SearchHeaderProps) => {
|
|
247
246
|
let searchWidth = layout.width - 24;
|
|
248
247
|
let searchLeft = Spacing.M;
|
|
@@ -250,9 +249,9 @@ export const useScreenHeader = ({
|
|
|
250
249
|
searchWidth = layout.contentWidth - Spacing.M * 2;
|
|
251
250
|
searchLeft = Spacing.M - getHeaderMargin(layout.width);
|
|
252
251
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
252
|
+
const clearance = layout.statusBarClearance;
|
|
253
|
+
if (clearance) {
|
|
254
|
+
searchWidth -= clearance.left + clearance.right;
|
|
256
255
|
}
|
|
257
256
|
const options: StackNavigationOptions = {
|
|
258
257
|
headerRight: undefined,
|
|
@@ -267,8 +266,12 @@ export const useScreenHeader = ({
|
|
|
267
266
|
margin: 0,
|
|
268
267
|
left: searchLeft,
|
|
269
268
|
},
|
|
270
|
-
headerLeft: (props: any) =>
|
|
271
|
-
params?.hiddenBack
|
|
269
|
+
headerLeft: (props: any) => {
|
|
270
|
+
if (params?.hiddenBack) {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
return <HeaderLeft {...props} floating />;
|
|
274
|
+
},
|
|
272
275
|
headerTitle: () => (
|
|
273
276
|
<SearchHeader {...params} animatedValue={sharedValue} />
|
|
274
277
|
),
|
|
@@ -8,6 +8,10 @@ import {
|
|
|
8
8
|
} from 'react-native-reanimated';
|
|
9
9
|
import { DuoQuickReturnHeader } from '../Application/Components/DuoQuickReturnHeader';
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* iPhone Duo quick return header: it slides out while the content scrolls down and back
|
|
13
|
+
* on scroll up. Returns the header options and a snap to call when scrolling ends.
|
|
14
|
+
*/
|
|
11
15
|
export const useScreenQuickReturn = (
|
|
12
16
|
sharedValue: SharedValue<number>,
|
|
13
17
|
useQuickReturn: boolean,
|
|
@@ -33,29 +37,22 @@ export const useScreenQuickReturn = (
|
|
|
33
37
|
[useQuickReturn, heightHeader],
|
|
34
38
|
);
|
|
35
39
|
|
|
36
|
-
const quickReturnOptions: StackNavigationOptions = useMemo(
|
|
37
|
-
()
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
);
|
|
40
|
+
const quickReturnOptions: StackNavigationOptions = useMemo(() => {
|
|
41
|
+
if (!useQuickReturn) {
|
|
42
|
+
return { header: undefined };
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
header: (props: any) => (
|
|
46
|
+
<DuoQuickReturnHeader {...props} offset={headerHide} />
|
|
47
|
+
),
|
|
48
|
+
};
|
|
49
|
+
}, [useQuickReturn, headerHide]);
|
|
47
50
|
|
|
48
|
-
|
|
49
|
-
* a half-shown quick return header settles fully in or out
|
|
50
|
-
*/
|
|
51
|
+
// a half-shown quick return header settles fully in or out
|
|
51
52
|
const snapQuickReturnHeader = (offsetY: number) => {
|
|
52
53
|
const hide = headerHide.value;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
offsetY < heightHeader ||
|
|
56
|
-
hide <= 0 ||
|
|
57
|
-
hide >= heightHeader
|
|
58
|
-
) {
|
|
54
|
+
const isSettled = hide <= 0 || hide >= heightHeader;
|
|
55
|
+
if (!useQuickReturn || offsetY < heightHeader || isSettled) {
|
|
59
56
|
return;
|
|
60
57
|
}
|
|
61
58
|
headerHide.value = withTiming(hide > heightHeader / 2 ? heightHeader : 0, {
|
package/Layout/screenScroll.ts
CHANGED
|
@@ -25,6 +25,10 @@ type ScrollHandlerParams = {
|
|
|
25
25
|
ref: any;
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Scroll handlers for Screen: feed the header animated values, retint an animated header
|
|
30
|
+
* past 50pt, snap the quick return header and reset a search screen scrolled under 100pt.
|
|
31
|
+
*/
|
|
28
32
|
export const createScrollHandlers = ({
|
|
29
33
|
scrollable,
|
|
30
34
|
animatedValue,
|
|
@@ -39,9 +43,6 @@ export const createScrollHandlers = ({
|
|
|
39
43
|
}: ScrollHandlerParams) => {
|
|
40
44
|
let handleScroll;
|
|
41
45
|
|
|
42
|
-
/**
|
|
43
|
-
* animated when use scroll && animated value
|
|
44
|
-
*/
|
|
45
46
|
if (scrollable) {
|
|
46
47
|
handleScroll = Animated.event(
|
|
47
48
|
[
|
|
@@ -80,10 +81,6 @@ export const createScrollHandlers = ({
|
|
|
80
81
|
);
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
/**
|
|
84
|
-
* handle scroll end
|
|
85
|
-
* @param e
|
|
86
|
-
*/
|
|
87
84
|
const handleMomentumScrollEnd = (
|
|
88
85
|
e: NativeSyntheticEvent<NativeScrollEvent>,
|
|
89
86
|
) => {
|
package/Layout/screenTypes.ts
CHANGED
|
@@ -38,7 +38,7 @@ export interface ScreenProps extends ViewProps {
|
|
|
38
38
|
headerType?: HeaderType;
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
* Optional.
|
|
41
|
+
* Optional. Animated banner header: its component, tint and title.
|
|
42
42
|
*/
|
|
43
43
|
animatedHeader?: AnimatedHeader;
|
|
44
44
|
|
|
@@ -71,7 +71,8 @@ export interface ScreenProps extends ViewProps {
|
|
|
71
71
|
backgroundColor?: string;
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
|
-
* Optional.
|
|
74
|
+
* Optional. Shift of the content against the animated header banner bottom; negative
|
|
75
|
+
* values overlap the banner.
|
|
75
76
|
*/
|
|
76
77
|
layoutOffset?: -8 | -24 | -56 | number;
|
|
77
78
|
|
package/Layout/types.ts
CHANGED
|
@@ -4,14 +4,12 @@ export type HeaderType = 'default' | 'extended' | 'none';
|
|
|
4
4
|
|
|
5
5
|
export type ScreenRef = {
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
* @param params
|
|
7
|
+
* Applies navigation options to the screen's header.
|
|
9
8
|
*/
|
|
10
9
|
setOptions: (params: NavigationOptions) => void;
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
|
-
*
|
|
14
|
-
* @param params
|
|
12
|
+
* Replaces the header title with a search field.
|
|
15
13
|
*/
|
|
16
14
|
setSearchHeader: (params: SearchHeaderProps) => void;
|
|
17
15
|
};
|
package/Layout/utils.ts
CHANGED
|
@@ -15,9 +15,8 @@ import {
|
|
|
15
15
|
import { ReservedRegion, ReservedRegionsContext } from './ReservedRegions';
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* @param childrenTypes
|
|
18
|
+
* Warns when a child is not one of `childrenTypes` (Fragments pass) and returns the
|
|
19
|
+
* children, flattened to the valid elements when given an array.
|
|
21
20
|
*/
|
|
22
21
|
const validateChildren = (
|
|
23
22
|
children: React.ReactNode | React.ReactNode[],
|
|
@@ -54,20 +53,20 @@ const validateChildren = (
|
|
|
54
53
|
const NO_INSETS: EdgeInsets = { top: 0, right: 0, bottom: 0, left: 0 };
|
|
55
54
|
|
|
56
55
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
56
|
+
* How far system regions (e.g. the camera, at either end depending on the rotation) reach
|
|
57
|
+
* into a side strip from its top and bottom ends, plus the center y of the region reaching
|
|
58
|
+
* furthest from the top.
|
|
60
59
|
*/
|
|
61
60
|
const getStripOcclusion = (
|
|
62
61
|
regions: ReservedRegion[],
|
|
63
62
|
stripLeft: number,
|
|
64
63
|
stripRight: number,
|
|
65
64
|
windowHeight: number,
|
|
66
|
-
) =>
|
|
67
|
-
regions.reduce(
|
|
65
|
+
) => {
|
|
66
|
+
return regions.reduce(
|
|
68
67
|
(occlusion, region) => {
|
|
69
|
-
const
|
|
70
|
-
|
|
68
|
+
const regionRight = region.x + region.width;
|
|
69
|
+
const overlapsStrip = region.x < stripRight && regionRight > stripLeft;
|
|
71
70
|
if (!overlapsStrip) {
|
|
72
71
|
return occlusion;
|
|
73
72
|
}
|
|
@@ -84,10 +83,11 @@ const getStripOcclusion = (
|
|
|
84
83
|
},
|
|
85
84
|
{ top: 0, topCenter: 0, bottom: 0 },
|
|
86
85
|
);
|
|
86
|
+
};
|
|
87
87
|
|
|
88
88
|
/**
|
|
89
|
-
*
|
|
90
|
-
* each side. `undefined` without reserved regions there
|
|
89
|
+
* How far the clock and battery clusters of a status bar on the top edge reach in from
|
|
90
|
+
* each side. `undefined` without reserved regions there.
|
|
91
91
|
*/
|
|
92
92
|
const getStatusBarClusters = (
|
|
93
93
|
regions: ReservedRegion[],
|
|
@@ -95,12 +95,11 @@ const getStatusBarClusters = (
|
|
|
95
95
|
topInset: number,
|
|
96
96
|
) => {
|
|
97
97
|
const midX = windowWidth / 2;
|
|
98
|
-
const clusters = regions.filter(
|
|
99
|
-
region
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
);
|
|
98
|
+
const clusters = regions.filter(region => {
|
|
99
|
+
const isInTopInset = region.y + region.height <= topInset;
|
|
100
|
+
const isOnOneSide = region.x + region.width <= midX || region.x >= midX;
|
|
101
|
+
return region.y < topInset && isInTopInset && isOnOneSide;
|
|
102
|
+
});
|
|
104
103
|
if (!clusters.length) {
|
|
105
104
|
return undefined;
|
|
106
105
|
}
|
|
@@ -118,8 +117,10 @@ const getStatusBarClusters = (
|
|
|
118
117
|
);
|
|
119
118
|
};
|
|
120
119
|
|
|
121
|
-
|
|
122
|
-
|
|
120
|
+
/** iOS phone idiom: iPad keeps the plain header and is never treated as iPhone Duo. */
|
|
121
|
+
const isIPhone = () => {
|
|
122
|
+
return Platform.OS === 'ios' && Platform.constants.interfaceIdiom === 'phone';
|
|
123
|
+
};
|
|
123
124
|
|
|
124
125
|
/**
|
|
125
126
|
* Header height and the room it keeps above the bar row for the status bar. On iPhone the
|
|
@@ -141,7 +142,7 @@ const getHeaderLayout = (topInset: number) => {
|
|
|
141
142
|
|
|
142
143
|
/**
|
|
143
144
|
* Header whose bar row is centered on a window y, at least iPhoneMinHeight tall and
|
|
144
|
-
* reaching 8pt below a side bar control centered there
|
|
145
|
+
* reaching 8pt below a side bar control centered there.
|
|
145
146
|
*/
|
|
146
147
|
const getCenteredHeaderLayout = (centerY: number) => {
|
|
147
148
|
const headerHeight = Math.max(
|
|
@@ -221,8 +222,7 @@ const useScreenLayout = (): ScreenLayout => {
|
|
|
221
222
|
if (sideInset >= DuoMetrics.controlSize + Spacing.M) {
|
|
222
223
|
const edge = insets.right >= insets.left ? 'right' : 'left';
|
|
223
224
|
const stripLeft = edge === 'right' ? width - sideInset : 0;
|
|
224
|
-
//
|
|
225
|
-
// calls and Live Activities. Region frames already include the system's margin
|
|
225
|
+
// clear the camera and status bar cluster; region frames include the system margin
|
|
226
226
|
const occlusion = getStripOcclusion(
|
|
227
227
|
reservedRegions,
|
|
228
228
|
stripLeft,
|
|
@@ -242,8 +242,7 @@ const useScreenLayout = (): ScreenLayout => {
|
|
|
242
242
|
} else if (height >= DuoMetrics.innerMinHeight) {
|
|
243
243
|
top = DuoMetrics.firstItemTopLandscape;
|
|
244
244
|
} else {
|
|
245
|
-
// outer
|
|
246
|
-
// on the camera when it sits at the top end, otherwise on the back button
|
|
245
|
+
// outer landscape: title row centers on a camera at the top end, else on back
|
|
247
246
|
top = DuoMetrics.edgeInset;
|
|
248
247
|
const cameraAtTop = occlusion.top > top;
|
|
249
248
|
const backCenterY = top + DuoMetrics.controlSize / 2;
|
|
@@ -285,7 +284,7 @@ const useScreenLayout = (): ScreenLayout => {
|
|
|
285
284
|
|
|
286
285
|
/**
|
|
287
286
|
* Header row options for react-navigation: its height, the room for the status bar, and
|
|
288
|
-
* on iPhone Duo inner portrait the buttons kept clear of the clock and battery clusters
|
|
287
|
+
* on iPhone Duo inner portrait the buttons kept clear of the clock and battery clusters.
|
|
289
288
|
*/
|
|
290
289
|
const getHeaderRowOptions = ({
|
|
291
290
|
headerHeight,
|
|
@@ -305,9 +304,7 @@ const getHeaderRowOptions = ({
|
|
|
305
304
|
};
|
|
306
305
|
};
|
|
307
306
|
|
|
308
|
-
/**
|
|
309
|
-
* Side margin @react-navigation/elements Header adds to its content row
|
|
310
|
-
*/
|
|
307
|
+
/** Side margin @react-navigation/elements Header adds to its content row. */
|
|
311
308
|
const getHeaderMargin = (width: number) => {
|
|
312
309
|
if (Platform.OS === 'ios' && width >= HeaderMetrics.largeWidth) {
|
|
313
310
|
return HeaderMetrics.largeMargin;
|
|
@@ -321,11 +318,9 @@ const InnerEdgeStyles: Record<SideBarEdge, ViewStyle> = {
|
|
|
321
318
|
};
|
|
322
319
|
|
|
323
320
|
/**
|
|
324
|
-
* Absolute style
|
|
325
|
-
*
|
|
321
|
+
* Absolute style floating a header item into the side bar, 12pt from the strip's inner
|
|
322
|
+
* edge, relative to the `container` rendering it; `slot` 0 is back, 1 the toolkit below.
|
|
326
323
|
* `undefined` while there is no side bar.
|
|
327
|
-
* @param container header container rendering the item
|
|
328
|
-
* @param slot position in the side bar, 0 for back, 1 for the toolkit below it
|
|
329
324
|
*/
|
|
330
325
|
const useSideBarItemStyle = (
|
|
331
326
|
container: 'left' | 'right',
|
|
@@ -338,10 +333,8 @@ const useSideBarItemStyle = (
|
|
|
338
333
|
|
|
339
334
|
const margin = getHeaderMargin(width);
|
|
340
335
|
const columnLeft = sideBar.edge === 'left' ? 0 : width - sideBar.width;
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
headerStatusBarHeight +
|
|
344
|
-
slot * (DuoMetrics.controlSize + Spacing.M);
|
|
336
|
+
const slotOffset = slot * (DuoMetrics.controlSize + Spacing.M);
|
|
337
|
+
const top = sideBar.top - headerStatusBarHeight + slotOffset;
|
|
345
338
|
const innerEdgeStyle = InnerEdgeStyles[sideBar.edge];
|
|
346
339
|
|
|
347
340
|
if (container === 'left') {
|
|
@@ -364,6 +357,7 @@ const useSideBarItemStyle = (
|
|
|
364
357
|
};
|
|
365
358
|
};
|
|
366
359
|
|
|
360
|
+
/** Column count of the content grid: 24 on content wider than 600pt, otherwise 12. */
|
|
367
361
|
const useGridSystem = () => {
|
|
368
362
|
const { contentWidth } = useScreenLayout();
|
|
369
363
|
const numberOfColumns = contentWidth > 600 ? 24 : 12;
|
package/Loader/DotLoader.tsx
CHANGED
|
@@ -6,6 +6,10 @@ import { ApplicationContext } from '../Context';
|
|
|
6
6
|
import { hexToRGBA } from './utils';
|
|
7
7
|
import { LoaderProps } from './types';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Three-dot Lottie loader recolored to `color` (theme primary by default).
|
|
11
|
+
* Recoloring mutates the shared animation JSON, so `Loader` remounts on change.
|
|
12
|
+
*/
|
|
9
13
|
const DotLoader: FC<LoaderProps> = ({ color, style }) => {
|
|
10
14
|
const { theme } = useContext(ApplicationContext);
|
|
11
15
|
|
package/Loader/Loader.tsx
CHANGED
|
@@ -3,6 +3,10 @@ import {LoaderProps} from './types';
|
|
|
3
3
|
import DotLoader from './DotLoader';
|
|
4
4
|
import Spinner from './Spinner';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Dot (default) or spinner loader. Keyed by `color` so a color change remounts
|
|
8
|
+
* the Lottie view with the recolored source.
|
|
9
|
+
*/
|
|
6
10
|
const Loader: FC<LoaderProps> = ({type = 'dot', ...props}) => {
|
|
7
11
|
if (type === 'spinner') {
|
|
8
12
|
return <Spinner {...props} key={props.color} />;
|
package/Loader/ProgressBar.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import styles from './styles';
|
|
|
9
9
|
import { ProgressBarProps } from './types';
|
|
10
10
|
import { ApplicationContext } from '../Context';
|
|
11
11
|
|
|
12
|
+
/** Full-width bar that animates its fill to `percent`, clamped to 0–100. */
|
|
12
13
|
const ProgressBar: FC<ProgressBarProps> = ({ percent = 0, style }) => {
|
|
13
14
|
const { theme } = useContext(ApplicationContext);
|
|
14
15
|
const animation = useSharedValue(0);
|
package/Loader/Spinner.tsx
CHANGED
|
@@ -6,6 +6,10 @@ import { hexToRGBA } from './utils';
|
|
|
6
6
|
import { ApplicationContext } from '../Context';
|
|
7
7
|
import { LoaderProps } from './types';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Lottie spinner recolored to `color` (theme primary by default).
|
|
11
|
+
* Recoloring mutates the shared animation JSON, so `Loader` remounts on change.
|
|
12
|
+
*/
|
|
9
13
|
const Spinner: FC<LoaderProps> = ({ color, style }) => {
|
|
10
14
|
const { theme } = useContext(ApplicationContext);
|
|
11
15
|
|
package/Loader/utils.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts `#rgb`/`#rrggbb` (the `#` is optional) to Lottie's `[r, g, b]` in
|
|
3
|
+
* 0–1, rounded to 3 decimals. Throws on an invalid hex.
|
|
4
|
+
*/
|
|
1
5
|
export const hexToRGBA = (hex: string) => {
|
|
2
6
|
const normalizedHex = hex.startsWith('#') ? hex.slice(1) : hex;
|
|
3
7
|
|
|
@@ -5,13 +9,13 @@ export const hexToRGBA = (hex: string) => {
|
|
|
5
9
|
throw new Error('Invalid HEX color.');
|
|
6
10
|
}
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
let expandedHex = normalizedHex;
|
|
13
|
+
if (normalizedHex.length === 3) {
|
|
14
|
+
expandedHex = normalizedHex
|
|
15
|
+
.split('')
|
|
16
|
+
.map(val => val + val)
|
|
17
|
+
.join('');
|
|
18
|
+
}
|
|
15
19
|
|
|
16
20
|
const hexMatch = expandedHex.match(/.{2}/g);
|
|
17
21
|
|
package/Pagination/Dot.tsx
CHANGED
|
@@ -4,6 +4,10 @@ import styles from './styles';
|
|
|
4
4
|
import { DotProps } from './types';
|
|
5
5
|
import { ApplicationContext } from '../Context';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* One pagination dot: a wide bar in the caller's color when active, otherwise
|
|
9
|
+
* a small dot in the theme pressed color.
|
|
10
|
+
*/
|
|
7
11
|
const Dot: FC<DotProps> = ({ active, style }) => {
|
|
8
12
|
const { theme } = useContext(ApplicationContext);
|
|
9
13
|
const pressedColor = theme.colors.background.pressed;
|
|
@@ -6,6 +6,7 @@ import styles from './styles';
|
|
|
6
6
|
import { Spacing } from '../Consts';
|
|
7
7
|
import { ApplicationContext, MiniAppContext } from '../Context';
|
|
8
8
|
|
|
9
|
+
/** Row of primary-color dots with the `activeIndex` dot widened. */
|
|
9
10
|
const PaginationDot: FC<ChildPaginationProps> = ({
|
|
10
11
|
dataLength = 2,
|
|
11
12
|
activeIndex = 0,
|
|
@@ -6,6 +6,7 @@ import { Text } from '../Text';
|
|
|
6
6
|
import { Colors } from '../Consts';
|
|
7
7
|
import { MiniAppContext } from '../Context';
|
|
8
8
|
|
|
9
|
+
/** `n/total` counter on a translucent dark pill. */
|
|
9
10
|
const PaginationNumber: FC<ChildPaginationProps> = ({
|
|
10
11
|
activeIndex = 0,
|
|
11
12
|
dataLength = 2,
|
|
@@ -14,15 +15,16 @@ const PaginationNumber: FC<ChildPaginationProps> = ({
|
|
|
14
15
|
const context = useContext<any>(MiniAppContext);
|
|
15
16
|
|
|
16
17
|
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
18
|
+
const pageLabel = `${activeIndex + 1}/${dataLength}`;
|
|
17
19
|
|
|
18
20
|
return (
|
|
19
21
|
<View
|
|
20
22
|
style={[style, styles.row, showBaseLineDebug && styles.debugBaseLine]}
|
|
21
23
|
>
|
|
22
24
|
<View style={styles.paginationNumberContainer}>
|
|
23
|
-
<Text color={Colors.black_01} typography={'label_default_medium'}>
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
<Text color={Colors.black_01} typography={'label_default_medium'}>
|
|
26
|
+
{pageLabel}
|
|
27
|
+
</Text>
|
|
26
28
|
</View>
|
|
27
29
|
</View>
|
|
28
30
|
);
|
|
@@ -13,6 +13,11 @@ import { ApplicationContext, MiniAppContext } from '../Context';
|
|
|
13
13
|
|
|
14
14
|
const INDICATOR_WIDTH = 24;
|
|
15
15
|
const INDICATOR_CONTAINER_WIDTH = 72;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Horizontal scroll view with a small indicator that tracks the scroll offset.
|
|
19
|
+
* The indicator shows only when the content is wider than the view.
|
|
20
|
+
*/
|
|
16
21
|
const PaginationScroll: FC<ScrollIndicatorProps> = ({
|
|
17
22
|
style,
|
|
18
23
|
children,
|
|
@@ -6,6 +6,7 @@ import Dot from './Dot';
|
|
|
6
6
|
import { Colors, Spacing } from '../Consts';
|
|
7
7
|
import { MiniAppContext } from '../Context';
|
|
8
8
|
|
|
9
|
+
/** Pagination dots on a translucent dark pill, for use over images. */
|
|
9
10
|
const PaginationWhiteDot: FC<ChildPaginationProps> = ({
|
|
10
11
|
dataLength = 2,
|
|
11
12
|
activeIndex = 0,
|
package/Pagination/index.tsx
CHANGED
|
@@ -5,6 +5,10 @@ import PaginationDot from './PaginationDot';
|
|
|
5
5
|
import PaginationNumber from './PaginationNumber';
|
|
6
6
|
import PaginationScroll from './PaginationScroll';
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Page indicator: `type` picks plain dots, dots on a dark pill or an `n/total`
|
|
10
|
+
* counter. `PaginationScroll` is the separate scroll-indicator variant.
|
|
11
|
+
*/
|
|
8
12
|
const Pagination: FC<PaginationProps> = props => {
|
|
9
13
|
const {type} = props;
|
|
10
14
|
|