@momo-kits/foundation 0.169.7-beta.4 → 0.169.7-beta.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 +40 -37
- 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 +10 -10
- package/Application/Navigation.ts +14 -16
- package/Application/NavigationContainer.tsx +19 -18
- package/Application/Navigator.ts +20 -48
- package/Application/ScaleSizeProvider.tsx +13 -6
- package/Application/StackScreen.tracking.ts +11 -33
- package/Application/StackScreen.tsx +19 -54
- 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 +1 -1
- package/Application/KitAdoption/AdoptionBadge.tsx +0 -119
- package/Application/KitAdoption/AdoptionProbe.tsx +0 -166
- package/Application/KitAdoption/AdoptionStore.ts +0 -80
- package/Application/KitAdoption/Authorship/utils.ts +0 -137
- package/Application/KitAdoption/Slots/utils.ts +0 -68
- package/Application/KitAdoption/constants.ts +0 -120
- package/Application/KitAdoption/index.ts +0 -12
- package/Application/KitAdoption/styles.ts +0 -55
- package/Application/KitAdoption/types.ts +0 -61
- package/Application/KitAdoption/utils.ts +0 -376
- package/Application/__tests__/KitAdoption.integration.test.tsx +0 -253
- package/Application/__tests__/KitAdoption.test.ts +0 -283
- package/Application/__tests__/KitAdoptionAuthorship.test.ts +0 -132
- package/Application/__tests__/KitAdoptionStore.test.ts +0 -110
- package/Application/__tests__/KitAdoptionStructural.test.ts +0 -193
- package/Application/__tests__/modalLayout.test.ts +0 -128
package/Input/index.tsx
CHANGED
|
@@ -36,7 +36,7 @@ export interface InputProps extends TextInputProps {
|
|
|
36
36
|
errorMessage?: string;
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
|
-
* Optional.
|
|
39
|
+
* Optional. Trailing icon name or URL; any other string is shown as a text action.
|
|
40
40
|
*/
|
|
41
41
|
trailing?: string;
|
|
42
42
|
|
|
@@ -69,19 +69,18 @@ export interface InputProps extends TextInputProps {
|
|
|
69
69
|
required?: boolean;
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
|
-
* Optional. If `true`,
|
|
73
|
-
* include spacing when Input not have error message.
|
|
72
|
+
* Optional. If `true`, keeps an empty error row when there is no error or hint.
|
|
74
73
|
* Defaults to `false` if not provided.
|
|
75
74
|
*/
|
|
76
75
|
errorSpacing?: boolean;
|
|
77
76
|
|
|
78
77
|
/**
|
|
79
|
-
*
|
|
78
|
+
* Optional. If `true`, shows a loading spinner in place of the trailing icon.
|
|
80
79
|
*/
|
|
81
80
|
loading?: boolean;
|
|
82
81
|
|
|
83
82
|
/**
|
|
84
|
-
*
|
|
83
|
+
* Optional. Represents the leading icon in the Input component.
|
|
85
84
|
*/
|
|
86
85
|
leadingIcon?: string;
|
|
87
86
|
|
|
@@ -111,12 +110,13 @@ export interface InputProps extends TextInputProps {
|
|
|
111
110
|
hintText?: string;
|
|
112
111
|
|
|
113
112
|
/**
|
|
114
|
-
* Optional.
|
|
113
|
+
* Optional. Callback function to be called when the floating icon is pressed.
|
|
115
114
|
*/
|
|
116
115
|
onPressFloatingIcon?: () => void;
|
|
117
116
|
|
|
118
117
|
/**
|
|
119
|
-
* Optional.
|
|
118
|
+
* Optional. If `true`, shows a clear icon while focused with a value.
|
|
119
|
+
* Defaults to `true` if not provided.
|
|
120
120
|
*/
|
|
121
121
|
showClearIcon?: boolean;
|
|
122
122
|
}
|
|
@@ -137,12 +137,12 @@ export interface InputSearchProps extends TextInputProps {
|
|
|
137
137
|
|
|
138
138
|
/**
|
|
139
139
|
* Optional. If `true`, the text on the search button will be displayed.
|
|
140
|
-
* Defaults to `
|
|
140
|
+
* Defaults to `true` if not provided.
|
|
141
141
|
*/
|
|
142
142
|
showButtonText?: boolean;
|
|
143
143
|
|
|
144
144
|
/**
|
|
145
|
-
* Optional.
|
|
145
|
+
* Optional. Callback function to be called when the search button is pressed.
|
|
146
146
|
*/
|
|
147
147
|
onPressButtonText?: () => void;
|
|
148
148
|
|
|
@@ -180,7 +180,7 @@ export interface InputSearchProps extends TextInputProps {
|
|
|
180
180
|
onPressTrailing?: () => void;
|
|
181
181
|
|
|
182
182
|
/**
|
|
183
|
-
* Optional.
|
|
183
|
+
* Optional. Background color of the search field. Defaults to the surface color.
|
|
184
184
|
*/
|
|
185
185
|
backgroundColor?: string;
|
|
186
186
|
|
|
@@ -195,8 +195,13 @@ export interface InputSearchProps extends TextInputProps {
|
|
|
195
195
|
typingData?: string[];
|
|
196
196
|
|
|
197
197
|
/**
|
|
198
|
-
* Optional.
|
|
199
|
-
*
|
|
198
|
+
* Optional. `header` is the 32pt field with a 20pt search icon that sits in the
|
|
199
|
+
* navigation header. Defaults to `default`, the 36pt field for content.
|
|
200
|
+
*/
|
|
201
|
+
size?: 'default' | 'header';
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Optional. Called when the field is pressed while showing `typingData`.
|
|
200
205
|
*/
|
|
201
206
|
onPress?: (e: GestureResponderEvent) => void;
|
|
202
207
|
}
|
|
@@ -259,7 +264,7 @@ export interface InputDropDownProps extends TouchableOpacityProps {
|
|
|
259
264
|
placeholder?: string;
|
|
260
265
|
|
|
261
266
|
/**
|
|
262
|
-
* Optional.
|
|
267
|
+
* Optional. If `true`, the value wraps to multiple lines and the chevron aligns to the top.
|
|
263
268
|
*/
|
|
264
269
|
multiline?: boolean;
|
|
265
270
|
|
|
@@ -303,19 +308,18 @@ export interface InputDropDownProps extends TouchableOpacityProps {
|
|
|
303
308
|
required?: boolean;
|
|
304
309
|
|
|
305
310
|
/**
|
|
306
|
-
* Optional. If `true`,
|
|
307
|
-
* include spacing when Input not have error message.
|
|
311
|
+
* Optional. If `true`, keeps an empty error row when there is no error or hint.
|
|
308
312
|
* Defaults to `false` if not provided.
|
|
309
313
|
*/
|
|
310
314
|
errorSpacing?: boolean;
|
|
311
315
|
|
|
312
316
|
/**
|
|
313
|
-
*
|
|
317
|
+
* Optional. If `true`, shows a loading spinner in place of the trailing icon.
|
|
314
318
|
*/
|
|
315
319
|
loading?: boolean;
|
|
316
320
|
|
|
317
321
|
/**
|
|
318
|
-
*
|
|
322
|
+
* Optional. Represents the leading icon in the Input component.
|
|
319
323
|
*/
|
|
320
324
|
leadingIcon?: string;
|
|
321
325
|
|
|
@@ -340,15 +344,14 @@ export interface InputDropDownProps extends TouchableOpacityProps {
|
|
|
340
344
|
hintText?: string;
|
|
341
345
|
|
|
342
346
|
/**
|
|
343
|
-
* Optional.
|
|
347
|
+
* Optional. Callback function to be called when the floating icon is pressed.
|
|
344
348
|
*/
|
|
345
349
|
onPressFloatingIcon?: () => void;
|
|
346
350
|
}
|
|
347
351
|
|
|
348
352
|
export interface InputPhoneNumberProps extends InputProps {
|
|
349
353
|
/**
|
|
350
|
-
* Optional.
|
|
351
|
-
* It should be in the format of a string, e.g., "+1" for the United States.
|
|
354
|
+
* Optional. Currently unused: the displayed prefix is always `+84`.
|
|
352
355
|
*/
|
|
353
356
|
countryCode?: string;
|
|
354
357
|
}
|
|
@@ -370,4 +373,4 @@ export {
|
|
|
370
373
|
InputPhoneNumber,
|
|
371
374
|
};
|
|
372
375
|
|
|
373
|
-
export {checkValidPhoneNumber};
|
|
376
|
+
export { checkValidPhoneNumber };
|
package/Input/moneyUtils.ts
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
export const moneyFormatter = new Intl.NumberFormat('vi-VN');
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Locates the single edited span between `old` and `text`: common prefix
|
|
5
|
+
* length (`start`), common suffix length (`suffix`) and removed + inserted
|
|
6
|
+
* char count (`size`).
|
|
7
|
+
*/
|
|
3
8
|
export const findEdit = (old: string, text: string) => {
|
|
4
9
|
let start = 0;
|
|
5
10
|
while (start < text.length && old[start] === text[start]) {
|
|
6
11
|
start += 1;
|
|
7
12
|
}
|
|
13
|
+
const maxSuffix = Math.min(text.length, old.length) - start;
|
|
8
14
|
let suffix = 0;
|
|
9
|
-
while (
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
15
|
+
while (suffix < maxSuffix) {
|
|
16
|
+
if (old[old.length - 1 - suffix] !== text[text.length - 1 - suffix]) {
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
13
19
|
suffix += 1;
|
|
14
20
|
}
|
|
15
21
|
const size = old.length + text.length - 2 * (start + suffix);
|
package/Input/styles.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { Platform, StyleSheet } from 'react-native';
|
|
|
2
2
|
import { Colors, Radius, Spacing } from '../Consts';
|
|
3
3
|
|
|
4
4
|
export default StyleSheet.create({
|
|
5
|
-
//input style
|
|
6
5
|
input: {
|
|
7
6
|
flex: 1,
|
|
8
7
|
paddingLeft: Spacing.M,
|
|
@@ -65,7 +64,6 @@ export default StyleSheet.create({
|
|
|
65
64
|
marginTop: Spacing.S,
|
|
66
65
|
},
|
|
67
66
|
|
|
68
|
-
//text area style
|
|
69
67
|
textAreaView: {
|
|
70
68
|
flex: 1,
|
|
71
69
|
},
|
|
@@ -94,7 +92,6 @@ export default StyleSheet.create({
|
|
|
94
92
|
marginBottom: Spacing.XS,
|
|
95
93
|
},
|
|
96
94
|
|
|
97
|
-
//searchInput
|
|
98
95
|
searchInputWrapper: {
|
|
99
96
|
flexDirection: 'row',
|
|
100
97
|
borderRadius: Radius.XL,
|
|
@@ -136,7 +133,6 @@ export default StyleSheet.create({
|
|
|
136
133
|
ios: 36,
|
|
137
134
|
}),
|
|
138
135
|
},
|
|
139
|
-
//OTP
|
|
140
136
|
otpWrapper: {
|
|
141
137
|
width: '100%',
|
|
142
138
|
marginTop: Spacing.M,
|
|
@@ -183,7 +179,6 @@ export default StyleSheet.create({
|
|
|
183
179
|
opacity: 0.02,
|
|
184
180
|
letterSpacing: Spacing.XXL,
|
|
185
181
|
},
|
|
186
|
-
//DropDown
|
|
187
182
|
inputDropDownWrapper: {
|
|
188
183
|
flexDirection: 'row',
|
|
189
184
|
marginTop: Spacing.S,
|
package/Input/utils.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { Localize } from '../Application';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Formats a number Vietnamese-style (`.` thousands, `,` decimals) with
|
|
5
|
+
* `currency` appended; 0, NaN or a falsy value gives `0<currency>`.
|
|
6
|
+
*/
|
|
3
7
|
const formatNumberToMoney = (number: number, currency = '') => {
|
|
4
8
|
if (!number || isNaN(number) || Number(number) === 0) {
|
|
5
9
|
return `0${currency}`;
|
|
@@ -41,6 +45,10 @@ const formatNumberToMoney = (number: number, currency = '') => {
|
|
|
41
45
|
return isNegative ? `-${result}${currency}` : `${result}${currency}`;
|
|
42
46
|
};
|
|
43
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Parses a formatted money string back to a number by stripping
|
|
50
|
+
* `currencyUnit`, separators, `đ` and spaces; unparsable text gives 0.
|
|
51
|
+
*/
|
|
44
52
|
const formatMoneyToNumber = (money: string, currencyUnit: string) => {
|
|
45
53
|
if (money && money.length > 0) {
|
|
46
54
|
const moneyString = money
|
|
@@ -59,26 +67,20 @@ const formatMoneyToNumber = (money: string, currencyUnit: string) => {
|
|
|
59
67
|
return Number(money);
|
|
60
68
|
};
|
|
61
69
|
|
|
62
|
-
// --- PHONE VALIDATION ---
|
|
63
70
|
function formatPhoneNumber(phone: string): string {
|
|
64
71
|
return phone.replace(/\D/g, '');
|
|
65
72
|
}
|
|
66
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Keeps the digits of `phone` (prepending the `0` a 9-char local number lacks)
|
|
76
|
+
* and returns a localized error when empty or not 10-11 digits.
|
|
77
|
+
*/
|
|
67
78
|
function checkValidPhoneNumber(phone: string): {
|
|
68
79
|
phoneFormatted: string;
|
|
69
80
|
error?: string;
|
|
70
81
|
} {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// if not starting with 0 and has 9 digits => prepend 0
|
|
74
|
-
if (
|
|
75
|
-
phoneNumber.length > 0 &&
|
|
76
|
-
phoneNumber[0] !== '0' &&
|
|
77
|
-
/^\d$/.test(phoneNumber[0]) &&
|
|
78
|
-
phoneNumber.length === 9
|
|
79
|
-
) {
|
|
80
|
-
phoneNumber = '0' + phoneNumber;
|
|
81
|
-
}
|
|
82
|
+
const isMissingLeadingZero = phone.length === 9 && /^[1-9]/.test(phone);
|
|
83
|
+
const phoneNumber = isMissingLeadingZero ? `0${phone}` : phone;
|
|
82
84
|
|
|
83
85
|
const phoneFormatted = formatPhoneNumber(phoneNumber);
|
|
84
86
|
const localize = new Localize({ vi: {}, en: {} });
|
package/Layout/Card.tsx
CHANGED
|
@@ -20,6 +20,10 @@ export interface CardProps extends ViewProps {
|
|
|
20
20
|
useShadow?: boolean;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Surface card across the content width minus 12pt margins, with its own grid for child
|
|
25
|
+
* Items; optional background image and light shadow.
|
|
26
|
+
*/
|
|
23
27
|
const Card: React.FC<CardProps> = ({
|
|
24
28
|
children,
|
|
25
29
|
style,
|
|
@@ -34,10 +38,9 @@ const Card: React.FC<CardProps> = ({
|
|
|
34
38
|
const gutterSize = 8;
|
|
35
39
|
const margin = 12;
|
|
36
40
|
const widthSection = contentWidth - margin * 2;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
1 / numberOfColumns;
|
|
41
|
+
const innerWidth = widthSection - margin * 2;
|
|
42
|
+
const columnsWidth = innerWidth - gutterSize * (numberOfColumns - 1);
|
|
43
|
+
const sizePerSpan = columnsWidth / numberOfColumns - 1 / numberOfColumns;
|
|
41
44
|
|
|
42
45
|
const gridContext: GridContextProps = {
|
|
43
46
|
numberOfColumns,
|
|
@@ -31,6 +31,10 @@ export interface FloatingButtonProps {
|
|
|
31
31
|
renderComponent?: () => React.ReactNode;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Floating action button above the screen bottom. With a label and a scroll value it
|
|
36
|
+
* shrinks to its icon while scrolling down and expands again on scroll up.
|
|
37
|
+
*/
|
|
34
38
|
export const FloatingButton: React.FC<FloatingButtonProps> = ({
|
|
35
39
|
renderComponent,
|
|
36
40
|
label = '',
|
package/Layout/Item.tsx
CHANGED
|
@@ -7,16 +7,20 @@ import { SpanNumber } from './types';
|
|
|
7
7
|
|
|
8
8
|
export interface ItemProps extends ViewProps {
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* Width in grid columns (1-12). Defaults to the full row.
|
|
11
11
|
*/
|
|
12
12
|
widthSpan?: SpanNumber;
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* Optional.
|
|
15
|
+
* Optional. Height in grid spans (1-12). Unset keeps the content height.
|
|
16
16
|
*/
|
|
17
17
|
heightSpan?: SpanNumber;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Grid cell sized from the enclosing GridContext; outlined when the mini app turns on
|
|
22
|
+
* the baseline debug feature.
|
|
23
|
+
*/
|
|
20
24
|
const Item: React.FC<ItemProps> = ({
|
|
21
25
|
widthSpan,
|
|
22
26
|
heightSpan,
|
package/Layout/ItemList.tsx
CHANGED
|
@@ -7,12 +7,16 @@ import { SpanNumber } from './types';
|
|
|
7
7
|
|
|
8
8
|
export interface ItemListProps extends FlatListProps<any> {
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* Width of each item in grid columns (1-12). Defaults to the full row.
|
|
11
11
|
*/
|
|
12
12
|
widthSpan?: SpanNumber;
|
|
13
13
|
data: any[];
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* FlatList on the grid: `widthSpan` sets how many items share a row, and every item but
|
|
18
|
+
* the first of a row gets the gutter on its left.
|
|
19
|
+
*/
|
|
16
20
|
const ItemList = forwardRef(
|
|
17
21
|
(
|
|
18
22
|
{
|
|
@@ -30,6 +34,9 @@ const ItemList = forwardRef(
|
|
|
30
34
|
const indexBegin = props?.data?.map(
|
|
31
35
|
(_i: any, index: number) => index * numColumns,
|
|
32
36
|
);
|
|
37
|
+
const defaultKeyExtractor = (item: any, index: number) => {
|
|
38
|
+
return (item?.id ?? item?.key ?? index).toString();
|
|
39
|
+
};
|
|
33
40
|
const _renderItem = (item: any) => {
|
|
34
41
|
if (indexBegin?.includes(item?.index)) {
|
|
35
42
|
return <Item widthSpan={widthItem}>{renderItem?.(item)}</Item>;
|
|
@@ -46,12 +53,7 @@ const ItemList = forwardRef(
|
|
|
46
53
|
ref={ref as React.RefObject<FlatList>}
|
|
47
54
|
{...props}
|
|
48
55
|
key={`ItemList-${numColumns}`}
|
|
49
|
-
keyExtractor={
|
|
50
|
-
// prefer caller's keyExtractor; fallback to common id/key/index
|
|
51
|
-
(props as any)?.keyExtractor ??
|
|
52
|
-
((item: any, index: number) =>
|
|
53
|
-
(item?.id ?? item?.key ?? index).toString())
|
|
54
|
-
}
|
|
56
|
+
keyExtractor={(props as any)?.keyExtractor ?? defaultKeyExtractor}
|
|
55
57
|
numColumns={numColumns}
|
|
56
58
|
renderItem={_renderItem}
|
|
57
59
|
horizontal={false}
|
|
@@ -7,11 +7,12 @@ import { SpanNumber } from './types';
|
|
|
7
7
|
|
|
8
8
|
export interface ItemSectionListProps extends SectionListProps<any> {
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* Width of each item in grid columns (1-12). Defaults to the full row.
|
|
11
11
|
*/
|
|
12
12
|
widthSpan?: SpanNumber;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/** SectionList whose items sit on the grid, `widthSpan` columns wide, a gutter apart. */
|
|
15
16
|
const ItemSectionList: FC<ItemSectionListProps> = ({
|
|
16
17
|
renderItem,
|
|
17
18
|
widthSpan,
|
|
@@ -19,43 +19,43 @@ try {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
23
|
-
* the root view so they line up with window coordinates
|
|
22
|
+
* Screen regions the system currently occludes, relative to the provider, which fills
|
|
23
|
+
* the root view so they line up with window coordinates.
|
|
24
24
|
*/
|
|
25
25
|
const ReservedRegionsContext = createContext<ReservedRegion[]>([]);
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
29
|
-
* half folded. `undefined` while the display is flat or the system reports none
|
|
28
|
+
* Vertical seam splitting the display into left and right halves, e.g. iPhone Duo
|
|
29
|
+
* half folded. `undefined` while the display is flat or the system reports none.
|
|
30
30
|
*/
|
|
31
31
|
const HingeContext = createContext<ReservedRegion | undefined>(undefined);
|
|
32
32
|
|
|
33
|
+
/** Frame of the vertical hinge (see HingeContext), `undefined` while the display is flat. */
|
|
33
34
|
const useHinge = () => useContext(HingeContext);
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
|
-
*
|
|
37
|
+
* Provides the occlusion regions and the vertical division (hinge) reported by the native
|
|
38
|
+
* provider.
|
|
37
39
|
*/
|
|
38
40
|
const OcclusionRegions: React.FC<{
|
|
39
41
|
useRegions: NativeReservedRegions['useReservedRegions'];
|
|
40
42
|
children: ReactNode;
|
|
41
43
|
}> = ({ useRegions, children }) => {
|
|
42
44
|
const regions = useRegions();
|
|
43
|
-
const occlusion = useMemo(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
[regions],
|
|
58
|
-
);
|
|
45
|
+
const occlusion = useMemo(() => {
|
|
46
|
+
return regions
|
|
47
|
+
.filter(region => region.kind === 'occlusion')
|
|
48
|
+
.map(region => region.frame);
|
|
49
|
+
}, [regions]);
|
|
50
|
+
const hinge = useMemo(() => {
|
|
51
|
+
const division = regions.find(region => {
|
|
52
|
+
if (region.kind !== 'division') {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
return region.frame.height > region.frame.width;
|
|
56
|
+
});
|
|
57
|
+
return division?.frame;
|
|
58
|
+
}, [regions]);
|
|
59
59
|
|
|
60
60
|
return (
|
|
61
61
|
<ReservedRegionsContext.Provider value={occlusion}>
|
|
@@ -65,8 +65,8 @@ const OcclusionRegions: React.FC<{
|
|
|
65
65
|
};
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
|
-
*
|
|
69
|
-
* cluster growing for a call or a Live Activity
|
|
68
|
+
* Tracks the system occlusion regions (iOS 27.1+), e.g. the iPhone Duo status bar
|
|
69
|
+
* cluster growing for a call or a Live Activity.
|
|
70
70
|
*/
|
|
71
71
|
const ReservedRegionsProvider: React.FC<{ children: ReactNode }> = ({
|
|
72
72
|
children,
|
|
@@ -75,8 +75,8 @@ const ReservedRegionsProvider: React.FC<{ children: ReactNode }> = ({
|
|
|
75
75
|
return <>{children}</>;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
const
|
|
79
|
-
|
|
78
|
+
const NativeProvider = nativeLib.ReservedRegionsProvider;
|
|
79
|
+
const { useReservedRegions } = nativeLib;
|
|
80
80
|
return (
|
|
81
81
|
<NativeProvider style={styles.container}>
|
|
82
82
|
<OcclusionRegions useRegions={useReservedRegions}>
|
package/Layout/Screen.tsx
CHANGED
|
@@ -37,6 +37,10 @@ import { useScreenQuickReturn } from './screenQuickReturn';
|
|
|
37
37
|
|
|
38
38
|
export type { ScreenProps };
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Screen container: sets the navigation header, lays children on the grid, and adds the
|
|
42
|
+
* scroll view, floating button and footer, with iPhone Duo side bar and tab bar insets.
|
|
43
|
+
*/
|
|
40
44
|
const Screen = forwardRef(
|
|
41
45
|
(
|
|
42
46
|
{
|
|
@@ -63,6 +67,7 @@ const Screen = forwardRef(
|
|
|
63
67
|
headerBackground,
|
|
64
68
|
gradientColor,
|
|
65
69
|
trackingParams,
|
|
70
|
+
contentLayout,
|
|
66
71
|
}: ScreenProps,
|
|
67
72
|
ref: any,
|
|
68
73
|
) => {
|
|
@@ -108,14 +113,12 @@ const Screen = forwardRef(
|
|
|
108
113
|
const isIOS = Platform.OS === 'ios';
|
|
109
114
|
const bottomInset = isIOS ? Math.min(insets.bottom, 21) : insets.bottom;
|
|
110
115
|
const hasSideBar = !!layout.sideBar;
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
!animatedHeader &&
|
|
115
|
-
!inputSearchProps;
|
|
116
|
+
const isPlainHeader = headerType === 'default' || headerType === 'extended';
|
|
117
|
+
const hasCustomHeader = !!animatedHeader || !!inputSearchProps;
|
|
118
|
+
const useDuoGlass = hasSideBar && isPlainHeader && !hasCustomHeader;
|
|
116
119
|
|
|
117
|
-
const
|
|
118
|
-
|
|
120
|
+
const hasOverlayHeader = useDuoGlass || !!animatedHeader;
|
|
121
|
+
const useQuickReturn = hasSideBar && scrollable && hasOverlayHeader;
|
|
119
122
|
const { quickReturnOptions, snapQuickReturnHeader } = useScreenQuickReturn(
|
|
120
123
|
sharedValue,
|
|
121
124
|
useQuickReturn,
|
|
@@ -123,18 +126,10 @@ const Screen = forwardRef(
|
|
|
123
126
|
);
|
|
124
127
|
|
|
125
128
|
let keyboardOffset = heightHeader - bottomInset;
|
|
126
|
-
if (
|
|
127
|
-
headerType === 'extended' ||
|
|
128
|
-
animatedHeader ||
|
|
129
|
-
inputSearchProps ||
|
|
130
|
-
useDuoGlass
|
|
131
|
-
) {
|
|
129
|
+
if (headerType === 'extended' || hasCustomHeader || useDuoGlass) {
|
|
132
130
|
keyboardOffset = -bottomInset;
|
|
133
131
|
}
|
|
134
132
|
|
|
135
|
-
/**
|
|
136
|
-
* inject params for screen tracking
|
|
137
|
-
*/
|
|
138
133
|
screen?.onSetParams?.(trackingParams);
|
|
139
134
|
|
|
140
135
|
const { setSearchHeader, currentTint } = useScreenHeader({
|
|
@@ -150,11 +145,10 @@ const Screen = forwardRef(
|
|
|
150
145
|
gradientColor,
|
|
151
146
|
headerBackground,
|
|
152
147
|
layoutOffset,
|
|
148
|
+
contentLayout,
|
|
153
149
|
});
|
|
154
150
|
|
|
155
|
-
|
|
156
|
-
* expose ref for screen
|
|
157
|
-
*/
|
|
151
|
+
// mutates the content node so callers keep its methods plus the header setters
|
|
158
152
|
useImperativeHandle(ref, () => {
|
|
159
153
|
return Object.assign(screenRef.current || {}, {
|
|
160
154
|
setOptions: (params: NavigationOptions) => {
|
|
@@ -165,19 +159,18 @@ const Screen = forwardRef(
|
|
|
165
159
|
});
|
|
166
160
|
});
|
|
167
161
|
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
});
|
|
162
|
+
const scrollHandlers = createScrollHandlers({
|
|
163
|
+
scrollable,
|
|
164
|
+
animatedValue: animatedValue.current,
|
|
165
|
+
sharedValue,
|
|
166
|
+
scrollViewProps,
|
|
167
|
+
animatedHeader,
|
|
168
|
+
inputSearchProps,
|
|
169
|
+
currentTint,
|
|
170
|
+
navigation,
|
|
171
|
+
snapQuickReturnHeader,
|
|
172
|
+
ref,
|
|
173
|
+
});
|
|
181
174
|
const Component: any = scrollable ? Animated.ScrollView : View;
|
|
182
175
|
|
|
183
176
|
const getHeight = () => {
|
|
@@ -207,6 +200,36 @@ const Screen = forwardRef(
|
|
|
207
200
|
floatingButtonBottom = 12 + duoTabInset;
|
|
208
201
|
}
|
|
209
202
|
|
|
203
|
+
const renderHeaderLayer = () => {
|
|
204
|
+
if (useDuoGlass) {
|
|
205
|
+
return (
|
|
206
|
+
<DuoScrollGradient
|
|
207
|
+
animatedValue={sharedValue}
|
|
208
|
+
gradientColor={gradientColor}
|
|
209
|
+
headerBackground={headerBackground}
|
|
210
|
+
heightHeader={heightHeader}
|
|
211
|
+
extended={headerType === 'extended'}
|
|
212
|
+
followScroll={useQuickReturn}
|
|
213
|
+
/>
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
return (
|
|
217
|
+
<HeaderExtendHeader
|
|
218
|
+
headerType={headerType}
|
|
219
|
+
heightHeader={heightHeader}
|
|
220
|
+
headerRightWidth={headerRightWidth}
|
|
221
|
+
animatedValue={sharedValue}
|
|
222
|
+
inputSearchProps={inputSearchProps}
|
|
223
|
+
navigation={navigation}
|
|
224
|
+
inputSearchRef={inputSearchRef}
|
|
225
|
+
useShadowHeader={useShadowHeader}
|
|
226
|
+
gradientColor={gradientColor}
|
|
227
|
+
headerBackground={headerBackground}
|
|
228
|
+
contentLayout={contentLayout}
|
|
229
|
+
/>
|
|
230
|
+
);
|
|
231
|
+
};
|
|
232
|
+
|
|
210
233
|
return (
|
|
211
234
|
<View
|
|
212
235
|
style={[
|
|
@@ -217,29 +240,7 @@ const Screen = forwardRef(
|
|
|
217
240
|
},
|
|
218
241
|
]}
|
|
219
242
|
>
|
|
220
|
-
{
|
|
221
|
-
<DuoScrollGradient
|
|
222
|
-
animatedValue={sharedValue}
|
|
223
|
-
gradientColor={gradientColor}
|
|
224
|
-
headerBackground={headerBackground}
|
|
225
|
-
heightHeader={heightHeader}
|
|
226
|
-
extended={headerType === 'extended'}
|
|
227
|
-
followScroll={useQuickReturn}
|
|
228
|
-
/>
|
|
229
|
-
) : (
|
|
230
|
-
<HeaderExtendHeader
|
|
231
|
-
headerType={headerType}
|
|
232
|
-
heightHeader={heightHeader}
|
|
233
|
-
headerRightWidth={headerRightWidth}
|
|
234
|
-
animatedValue={sharedValue}
|
|
235
|
-
inputSearchProps={inputSearchProps}
|
|
236
|
-
navigation={navigation}
|
|
237
|
-
inputSearchRef={inputSearchRef}
|
|
238
|
-
useShadowHeader={useShadowHeader}
|
|
239
|
-
gradientColor={gradientColor}
|
|
240
|
-
headerBackground={headerBackground}
|
|
241
|
-
/>
|
|
242
|
-
)}
|
|
243
|
+
{renderHeaderLayer()}
|
|
243
244
|
|
|
244
245
|
<KeyboardAvoidingView
|
|
245
246
|
style={[
|
|
@@ -259,9 +260,9 @@ const Screen = forwardRef(
|
|
|
259
260
|
{...scrollViewProps}
|
|
260
261
|
ref={screenRef}
|
|
261
262
|
showsVerticalScrollIndicator={false}
|
|
262
|
-
onScroll={handleScroll}
|
|
263
|
-
onScrollEndDrag={handleScrollEnd}
|
|
264
|
-
onMomentumScrollEnd={handleMomentumScrollEnd}
|
|
263
|
+
onScroll={scrollHandlers.handleScroll}
|
|
264
|
+
onScrollEndDrag={scrollHandlers.handleScrollEnd}
|
|
265
|
+
onMomentumScrollEnd={scrollHandlers.handleMomentumScrollEnd}
|
|
265
266
|
scrollEventThrottle={16}
|
|
266
267
|
style={[
|
|
267
268
|
Styles.flex,
|