@momo-kits/foundation 0.169.7-beta.2 → 0.169.7-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Application/BottomSheet.hooks.ts +8 -8
- package/Application/BottomSheet.parts.tsx +34 -44
- package/Application/BottomSheet.tsx +37 -40
- package/Application/BottomTab/ActiveIndicator.tsx +14 -15
- package/Application/BottomTab/BottomTabBar.tsx +62 -49
- package/Application/BottomTab/BottomTabInset.tsx +4 -8
- package/Application/BottomTab/CurvedContainer.tsx +12 -14
- package/Application/BottomTab/CustomBottomTabItem.tsx +13 -13
- package/Application/BottomTab/DuoBarCompression.tsx +19 -13
- package/Application/BottomTab/DuoRailTabBar.tsx +24 -20
- package/Application/BottomTab/DuoTabBar.tsx +10 -8
- package/Application/BottomTab/TabBarIcon.tsx +29 -15
- package/Application/BottomTab/TabScreen.tsx +5 -1
- package/Application/BottomTab/index.tsx +30 -33
- 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 +0 -4
- package/Application/Components/DuoGlassBackground.tsx +4 -5
- package/Application/Components/HeaderAnimated.tsx +0 -4
- package/Application/Components/HeaderBackground.tsx +0 -4
- package/Application/Components/HeaderExtendHeader.tsx +10 -11
- package/Application/Components/HeaderLeft.tsx +1 -2
- package/Application/Components/HeaderRight.tsx +1 -2
- package/Application/Components/HeaderTitle.shared.ts +9 -11
- package/Application/Components/HeaderTitle.tsx +3 -4
- package/Application/Components/HeaderToolkitAction.tsx +20 -17
- package/Application/Components/NavigationButton.tsx +1 -2
- package/Application/Components/SearchHeader.tsx +10 -17
- package/Application/Components/TitleJourney.tsx +2 -2
- package/Application/Components/TitleLocation.tsx +2 -2
- package/Application/Components/TitleUser.tsx +2 -2
- package/Application/Components/useToolkitActions.tsx +32 -17
- package/Application/KitAdoption/AdoptionBadge.tsx +119 -0
- package/Application/KitAdoption/AdoptionProbe.tsx +166 -0
- package/Application/KitAdoption/AdoptionStore.ts +80 -0
- package/Application/KitAdoption/Authorship/utils.ts +137 -0
- package/Application/KitAdoption/Slots/utils.ts +68 -0
- package/Application/KitAdoption/constants.ts +120 -0
- package/Application/KitAdoption/index.ts +12 -0
- package/Application/KitAdoption/styles.ts +55 -0
- package/Application/KitAdoption/types.ts +61 -0
- package/Application/KitAdoption/utils.ts +376 -0
- package/Application/Localize.ts +0 -7
- package/Application/ModalScreen.tsx +10 -10
- package/Application/Navigation.ts +16 -14
- package/Application/NavigationContainer.tsx +18 -19
- package/Application/Navigator.ts +48 -20
- package/Application/ScaleSizeProvider.tsx +6 -13
- package/Application/StackScreen.tracking.ts +33 -11
- package/Application/StackScreen.tsx +54 -10
- package/Application/TooltipPortal.tsx +5 -12
- package/Application/WidgetContainer.tsx +4 -10
- package/Application/__tests__/KitAdoption.integration.test.tsx +253 -0
- package/Application/__tests__/KitAdoption.test.ts +283 -0
- package/Application/__tests__/KitAdoptionAuthorship.test.ts +132 -0
- package/Application/__tests__/KitAdoptionStore.test.ts +110 -0
- package/Application/__tests__/KitAdoptionStructural.test.ts +193 -0
- package/Application/__tests__/modalLayout.test.ts +128 -0
- package/Application/modalLayout.ts +14 -16
- package/Application/navigationOptions.tsx +17 -21
- package/Badge/Badge.tsx +0 -5
- package/Badge/BadgeDot.tsx +0 -1
- package/Badge/BadgeDotAnimation.tsx +7 -15
- package/Badge/BadgeRibbon.tsx +38 -46
- package/Badge/Shape.tsx +0 -2
- package/Badge/types.ts +2 -2
- package/Button/index.tsx +16 -5
- package/Button/utils.ts +15 -9
- package/CheckBox/index.tsx +0 -4
- package/Consts/theme.ts +10 -10
- package/Context/index.ts +1 -5
- package/Divider/DashDivider.tsx +0 -1
- package/Divider/index.tsx +0 -4
- package/FoundationList/index.tsx +0 -4
- package/Icon/index.tsx +6 -6
- package/Icon/types.ts +1 -1
- package/IconButton/index.tsx +12 -4
- package/Image/index.tsx +7 -9
- package/Input/Input.tsx +9 -8
- package/Input/InputDropDown.tsx +12 -10
- package/Input/InputMoney.tsx +8 -12
- package/Input/InputMoneyView.tsx +7 -14
- package/Input/InputOTP.tsx +33 -35
- package/Input/InputPhoneNumber.tsx +9 -8
- package/Input/InputSearch.tsx +6 -4
- package/Input/InputTextArea.tsx +0 -4
- package/Input/SearchTextTyping.tsx +0 -4
- package/Input/SystemTextInput.tsx +2 -7
- package/Input/TextTyping.tsx +0 -4
- package/Input/common.tsx +12 -26
- package/Input/index.tsx +20 -17
- package/Input/moneyUtils.ts +4 -10
- package/Input/styles.ts +5 -0
- package/Input/utils.ts +12 -14
- package/Layout/Card.tsx +4 -7
- package/Layout/FloatingButton.tsx +0 -4
- package/Layout/Item.tsx +2 -6
- package/Layout/ItemList.tsx +7 -9
- package/Layout/ItemSectionList.tsx +1 -2
- package/Layout/ReservedRegions.tsx +25 -25
- package/Layout/Screen.tsx +58 -56
- package/Layout/Section.tsx +5 -8
- package/Layout/TrackingScope.tsx +0 -1
- package/Layout/screenContent.tsx +2 -4
- package/Layout/screenFooter.tsx +18 -28
- package/Layout/screenHeader.tsx +31 -34
- package/Layout/screenQuickReturn.tsx +20 -17
- package/Layout/screenScroll.ts +7 -4
- package/Layout/screenTypes.ts +2 -3
- package/Layout/types.ts +4 -2
- package/Layout/utils.ts +37 -31
- package/Loader/DotLoader.tsx +0 -4
- package/Loader/Loader.tsx +0 -4
- package/Loader/ProgressBar.tsx +0 -1
- package/Loader/Spinner.tsx +0 -4
- package/Loader/utils.ts +7 -11
- package/Pagination/Dot.tsx +0 -4
- package/Pagination/PaginationDot.tsx +0 -1
- package/Pagination/PaginationNumber.tsx +3 -5
- package/Pagination/PaginationScroll.tsx +0 -5
- package/Pagination/PaginationWhiteDot.tsx +0 -1
- package/Pagination/index.tsx +0 -4
- package/Pagination/types.ts +2 -2
- package/Popup/PopupNotify.tsx +48 -44
- package/Popup/PopupPromotion.tsx +7 -4
- package/Popup/types.ts +4 -4
- package/Popup/utils.ts +0 -4
- package/Radio/index.tsx +0 -4
- package/Skeleton/index.tsx +0 -8
- package/Switch/index.tsx +0 -1
- package/Tag/index.tsx +1 -5
- package/Tag/types.ts +3 -3
- package/Text/index.tsx +3 -14
- package/Text/styles.ts +1 -1
- package/Text/types.ts +6 -1
- package/Text/utils.ts +4 -20
- package/Title/index.tsx +32 -41
- package/ViewBoundary/index.tsx +2 -4
- package/index.ts +6 -0
- package/package.json +1 -1
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { AdoptionStore } from '../KitAdoption/AdoptionStore';
|
|
2
|
+
import type { ScreenSnapshot } from '../KitAdoption/types';
|
|
3
|
+
|
|
4
|
+
/** Snapshot with the given K/R/O counts for one app and screen. */
|
|
5
|
+
const makeSnapshot = (
|
|
6
|
+
screen: string,
|
|
7
|
+
counts: { K: number; R: number; O: number },
|
|
8
|
+
appId = 'app-1',
|
|
9
|
+
): ScreenSnapshot => ({
|
|
10
|
+
counts,
|
|
11
|
+
kit: {},
|
|
12
|
+
rn: {},
|
|
13
|
+
other: {},
|
|
14
|
+
top_transparent: [],
|
|
15
|
+
fibers: 0,
|
|
16
|
+
truncated: false,
|
|
17
|
+
status: 'ok',
|
|
18
|
+
app_id: appId,
|
|
19
|
+
screen_name: screen,
|
|
20
|
+
platform: 'ios',
|
|
21
|
+
ts: 0,
|
|
22
|
+
walk_ms: 0,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('AdoptionStore save', () => {
|
|
26
|
+
it('keeps the existing snapshot when it has more nodes', () => {
|
|
27
|
+
const store = new AdoptionStore();
|
|
28
|
+
store.save(makeSnapshot('Home', { K: 3, R: 0, O: 0 }));
|
|
29
|
+
store.save(makeSnapshot('Home', { K: 1, R: 0, O: 0 }));
|
|
30
|
+
|
|
31
|
+
expect(store.getAll()).toHaveLength(1);
|
|
32
|
+
expect(store.getAll()[0].counts.K).toBe(3);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('replaces the existing snapshot when the new one has more nodes', () => {
|
|
36
|
+
const store = new AdoptionStore();
|
|
37
|
+
store.save(makeSnapshot('Home', { K: 1, R: 0, O: 0 }));
|
|
38
|
+
store.save(makeSnapshot('Home', { K: 2, R: 2, O: 1 }));
|
|
39
|
+
|
|
40
|
+
expect(store.getAll()[0].counts).toEqual({ K: 2, R: 2, O: 1 });
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('keys snapshots by app and screen', () => {
|
|
44
|
+
const store = new AdoptionStore();
|
|
45
|
+
store.save(makeSnapshot('Home', { K: 1, R: 0, O: 0 }, 'app-1'));
|
|
46
|
+
store.save(makeSnapshot('Home', { K: 1, R: 0, O: 0 }, 'app-2'));
|
|
47
|
+
|
|
48
|
+
expect(store.getAll()).toHaveLength(2);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('getLatest returns the most recently saved screen', () => {
|
|
52
|
+
const store = new AdoptionStore();
|
|
53
|
+
store.save(makeSnapshot('Home', { K: 1, R: 0, O: 0 }));
|
|
54
|
+
store.save(makeSnapshot('Detail', { K: 1, R: 0, O: 0 }));
|
|
55
|
+
|
|
56
|
+
expect(store.getLatest()?.screen_name).toBe('Detail');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('clear empties the store and getLatest', () => {
|
|
60
|
+
const store = new AdoptionStore();
|
|
61
|
+
store.save(makeSnapshot('Home', { K: 1, R: 0, O: 0 }));
|
|
62
|
+
store.clear();
|
|
63
|
+
|
|
64
|
+
expect(store.getAll()).toEqual([]);
|
|
65
|
+
expect(store.getLatest()).toBeUndefined();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('AdoptionStore subscribe', () => {
|
|
70
|
+
it('notifies listeners on save and stops after unsubscribe', () => {
|
|
71
|
+
const store = new AdoptionStore();
|
|
72
|
+
const listener = jest.fn();
|
|
73
|
+
const unsubscribe = store.subscribe(listener);
|
|
74
|
+
|
|
75
|
+
store.save(makeSnapshot('Home', { K: 1, R: 0, O: 0 }));
|
|
76
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
77
|
+
|
|
78
|
+
unsubscribe();
|
|
79
|
+
store.save(makeSnapshot('Detail', { K: 1, R: 0, O: 0 }));
|
|
80
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('AdoptionStore active measurer', () => {
|
|
85
|
+
it('remeasureActive calls the active measurer', () => {
|
|
86
|
+
const store = new AdoptionStore();
|
|
87
|
+
const measure = jest.fn();
|
|
88
|
+
store.setActive(measure);
|
|
89
|
+
|
|
90
|
+
store.remeasureActive();
|
|
91
|
+
|
|
92
|
+
expect(measure).toHaveBeenCalledTimes(1);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('clearActive only clears the measurer that is still active', () => {
|
|
96
|
+
const store = new AdoptionStore();
|
|
97
|
+
const first = jest.fn();
|
|
98
|
+
const second = jest.fn();
|
|
99
|
+
store.setActive(first);
|
|
100
|
+
store.setActive(second);
|
|
101
|
+
|
|
102
|
+
store.clearActive(first);
|
|
103
|
+
store.remeasureActive();
|
|
104
|
+
expect(second).toHaveBeenCalledTimes(1);
|
|
105
|
+
|
|
106
|
+
store.clearActive(second);
|
|
107
|
+
store.remeasureActive();
|
|
108
|
+
expect(second).toHaveBeenCalledTimes(1);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { createElement } from 'react';
|
|
2
|
+
|
|
3
|
+
import { FIBER_TAG } from '../KitAdoption/constants';
|
|
4
|
+
import type { FiberLike, WalkResult } from '../KitAdoption/types';
|
|
5
|
+
import {
|
|
6
|
+
createRegistries,
|
|
7
|
+
getAdoptionTrackingParams,
|
|
8
|
+
isAdoptionTrackingEnabled,
|
|
9
|
+
walkScreen,
|
|
10
|
+
} from '../KitAdoption/utils';
|
|
11
|
+
|
|
12
|
+
const Button = () => null;
|
|
13
|
+
const RNView = () => null;
|
|
14
|
+
const RNFlatList = () => null;
|
|
15
|
+
const Cell = () => null;
|
|
16
|
+
const Item = () => null;
|
|
17
|
+
const Wrapper = () => null;
|
|
18
|
+
|
|
19
|
+
const registries = createRegistries([{ Button }], {
|
|
20
|
+
View: RNView,
|
|
21
|
+
FlatList: RNFlatList,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const makeFiber = (overrides: Partial<FiberLike> = {}): FiberLike => ({
|
|
25
|
+
tag: FIBER_TAG.FunctionComponent,
|
|
26
|
+
type: undefined,
|
|
27
|
+
child: null,
|
|
28
|
+
sibling: null,
|
|
29
|
+
return: null,
|
|
30
|
+
alternate: null,
|
|
31
|
+
memoizedProps: {},
|
|
32
|
+
...overrides,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/** Sets children of parent (child, sibling, return links) and returns parent. */
|
|
36
|
+
const link = (parent: FiberLike, children: FiberLike[]): FiberLike => {
|
|
37
|
+
children.forEach((child, index) => {
|
|
38
|
+
child.return = parent;
|
|
39
|
+
child.sibling = children[index + 1] ?? null;
|
|
40
|
+
});
|
|
41
|
+
parent.child = children[0] ?? null;
|
|
42
|
+
return parent;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const hostView = (): FiberLike =>
|
|
46
|
+
makeFiber({ tag: FIBER_TAG.HostComponent, type: 'RCTView' });
|
|
47
|
+
|
|
48
|
+
const walkStructural = (root: FiberLike): WalkResult =>
|
|
49
|
+
walkScreen(root, registries, undefined, undefined, true);
|
|
50
|
+
|
|
51
|
+
const makeResult = (status: WalkResult['status']): WalkResult => ({
|
|
52
|
+
counts: { K: 3, R: 2, O: 1 },
|
|
53
|
+
kit: {},
|
|
54
|
+
rn: {},
|
|
55
|
+
other: {},
|
|
56
|
+
top_transparent: [],
|
|
57
|
+
fibers: 6,
|
|
58
|
+
truncated: false,
|
|
59
|
+
status,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('KitAdoption walkScreen structural mode', () => {
|
|
63
|
+
it('counts a kit component once and skips internals outside its slots', () => {
|
|
64
|
+
const button = link(makeFiber({ type: Button }), [
|
|
65
|
+
link(makeFiber({ type: RNView }), [hostView()]),
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
const result = walkStructural(button);
|
|
69
|
+
|
|
70
|
+
expect(result.counts).toEqual({ K: 1, R: 0, O: 0 });
|
|
71
|
+
expect(result.kit).toEqual({ Button: 1 });
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('counts an app element passed as children of a kit component', () => {
|
|
75
|
+
const child = createElement(RNView);
|
|
76
|
+
const internal = link(makeFiber({ type: RNView }), [
|
|
77
|
+
makeFiber({ type: RNView, memoizedProps: child.props }),
|
|
78
|
+
]);
|
|
79
|
+
const button = link(
|
|
80
|
+
makeFiber({ type: Button, memoizedProps: { children: child } }),
|
|
81
|
+
[internal],
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const result = walkStructural(button);
|
|
85
|
+
|
|
86
|
+
expect(result.counts).toEqual({ K: 1, R: 1, O: 0 });
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('counts an RN View once, skips its host internal and counts app children', () => {
|
|
90
|
+
const child = createElement(Button);
|
|
91
|
+
const view = link(
|
|
92
|
+
makeFiber({ type: RNView, memoizedProps: { children: child } }),
|
|
93
|
+
[
|
|
94
|
+
link(hostView(), [
|
|
95
|
+
makeFiber({ type: Button, memoizedProps: child.props }),
|
|
96
|
+
]),
|
|
97
|
+
],
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const result = walkStructural(view);
|
|
101
|
+
|
|
102
|
+
expect(result.counts).toEqual({ K: 1, R: 1, O: 0 });
|
|
103
|
+
expect(result.rn).toEqual({ RNView: 1 });
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('counts the rendered item of a list cell as a slot', () => {
|
|
107
|
+
const itemElement = createElement(Item);
|
|
108
|
+
const item = makeFiber({ type: Button, memoizedProps: itemElement.props });
|
|
109
|
+
const wrapperView = link(
|
|
110
|
+
makeFiber({
|
|
111
|
+
type: RNView,
|
|
112
|
+
memoizedProps: { children: [itemElement, null] },
|
|
113
|
+
}),
|
|
114
|
+
[item],
|
|
115
|
+
);
|
|
116
|
+
const provider = link(makeFiber({ tag: FIBER_TAG.ContextProvider }), [
|
|
117
|
+
wrapperView,
|
|
118
|
+
]);
|
|
119
|
+
const cell = link(
|
|
120
|
+
makeFiber({
|
|
121
|
+
tag: FIBER_TAG.ClassComponent,
|
|
122
|
+
type: Cell,
|
|
123
|
+
memoizedProps: { cellKey: 'k', index: 0, item: {} },
|
|
124
|
+
}),
|
|
125
|
+
[provider],
|
|
126
|
+
);
|
|
127
|
+
const list = link(makeFiber({ type: RNFlatList }), [cell]);
|
|
128
|
+
|
|
129
|
+
const result = walkStructural(list);
|
|
130
|
+
|
|
131
|
+
expect(result.counts).toEqual({ K: 1, R: 1, O: 0 });
|
|
132
|
+
expect(result.rn).toEqual({ RNFlatList: 1 });
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('counts an O host component once and its app children', () => {
|
|
136
|
+
const child = createElement(Button);
|
|
137
|
+
const host = link(
|
|
138
|
+
makeFiber({
|
|
139
|
+
tag: FIBER_TAG.HostComponent,
|
|
140
|
+
type: 'custom-host',
|
|
141
|
+
memoizedProps: { children: child },
|
|
142
|
+
}),
|
|
143
|
+
[makeFiber({ type: Button, memoizedProps: child.props })],
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
const result = walkStructural(host);
|
|
147
|
+
|
|
148
|
+
expect(result.counts).toEqual({ K: 1, R: 0, O: 1 });
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('keeps counting internals when structural mode is off', () => {
|
|
152
|
+
const button = link(makeFiber({ type: RNView }), [
|
|
153
|
+
link(makeFiber({ type: Wrapper }), [makeFiber({ type: RNView })]),
|
|
154
|
+
]);
|
|
155
|
+
|
|
156
|
+
expect(walkStructural(button).counts.R).toBe(1);
|
|
157
|
+
expect(
|
|
158
|
+
walkScreen(button, registries, undefined, undefined, false).counts.R,
|
|
159
|
+
).toBe(2);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe('KitAdoption tracking helpers', () => {
|
|
164
|
+
it('enables tracking only for designSystemConfig.isWhiteList === true', () => {
|
|
165
|
+
expect(
|
|
166
|
+
isAdoptionTrackingEnabled({ designSystemConfig: { isWhiteList: true } }),
|
|
167
|
+
).toBe(true);
|
|
168
|
+
expect(
|
|
169
|
+
isAdoptionTrackingEnabled({
|
|
170
|
+
designSystemConfig: { isWhiteList: 'true' },
|
|
171
|
+
}),
|
|
172
|
+
).toBe(false);
|
|
173
|
+
expect(isAdoptionTrackingEnabled({ designSystemConfig: {} })).toBe(false);
|
|
174
|
+
expect(isAdoptionTrackingEnabled({})).toBe(false);
|
|
175
|
+
expect(isAdoptionTrackingEnabled(undefined)).toBe(false);
|
|
176
|
+
expect(isAdoptionTrackingEnabled('x')).toBe(false);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('maps an ok result to analytics params', () => {
|
|
180
|
+
expect(getAdoptionTrackingParams(makeResult('ok'))).toEqual({
|
|
181
|
+
num_design_system: 3,
|
|
182
|
+
num_rn: 2,
|
|
183
|
+
num_other: 1,
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('returns undefined for any other status or no result', () => {
|
|
188
|
+
expect(getAdoptionTrackingParams(makeResult('error'))).toBeUndefined();
|
|
189
|
+
expect(getAdoptionTrackingParams(makeResult('dev_bundle'))).toBeUndefined();
|
|
190
|
+
expect(getAdoptionTrackingParams(makeResult('truncated'))).toBeUndefined();
|
|
191
|
+
expect(getAdoptionTrackingParams(undefined)).toBeUndefined();
|
|
192
|
+
});
|
|
193
|
+
});
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// document/__mocks__ shadows react-native with react-native-web for the doc site
|
|
2
|
+
jest.mock('react-native', () => jest.requireActual('react-native'));
|
|
3
|
+
|
|
4
|
+
import { getModalLayout } from '../modalLayout';
|
|
5
|
+
|
|
6
|
+
const NO_INSETS = { top: 0, right: 0, bottom: 0, left: 0 };
|
|
7
|
+
|
|
8
|
+
describe('getModalLayout', () => {
|
|
9
|
+
it('keeps the phone look', () => {
|
|
10
|
+
const layout = getModalLayout({
|
|
11
|
+
width: 390,
|
|
12
|
+
height: 844,
|
|
13
|
+
insets: { top: 47, right: 0, bottom: 34, left: 0 },
|
|
14
|
+
isDuo: false,
|
|
15
|
+
});
|
|
16
|
+
expect(layout.isFoldable).toBe(false);
|
|
17
|
+
expect(layout.popup.width).toBe(342);
|
|
18
|
+
expect(layout.popup.maxHeight).toBeUndefined();
|
|
19
|
+
expect(layout.popup.radius).toBe(16);
|
|
20
|
+
expect(layout.sheet.width).toBeUndefined();
|
|
21
|
+
expect(layout.sheet.maxHeight).toBeUndefined();
|
|
22
|
+
expect(layout.sheet.radius).toBe(12);
|
|
23
|
+
expect(layout.scrimOpacity).toBe(0.6);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('keeps the phone look in landscape', () => {
|
|
27
|
+
const layout = getModalLayout({
|
|
28
|
+
width: 844,
|
|
29
|
+
height: 390,
|
|
30
|
+
insets: { top: 0, right: 47, bottom: 21, left: 47 },
|
|
31
|
+
isDuo: false,
|
|
32
|
+
});
|
|
33
|
+
expect(layout.isFoldable).toBe(false);
|
|
34
|
+
expect(layout.popup.width).toBe(844 - 48);
|
|
35
|
+
expect(layout.popup.maxHeight).toBeUndefined();
|
|
36
|
+
expect(layout.popup.radius).toBe(16);
|
|
37
|
+
expect(layout.sheet.width).toBeUndefined();
|
|
38
|
+
expect(layout.scrimOpacity).toBe(0.6);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('caps popup and sheet on a wide foldable window', () => {
|
|
42
|
+
const layout = getModalLayout({
|
|
43
|
+
width: 884,
|
|
44
|
+
height: 1104,
|
|
45
|
+
insets: { top: 24, right: 0, bottom: 20, left: 0 },
|
|
46
|
+
isDuo: false,
|
|
47
|
+
});
|
|
48
|
+
expect(layout.isFoldable).toBe(true);
|
|
49
|
+
expect(layout.popup).toMatchObject({
|
|
50
|
+
width: 450,
|
|
51
|
+
minWidth: 280,
|
|
52
|
+
maxWidth: 450,
|
|
53
|
+
maxHeight: 600,
|
|
54
|
+
margin: 24,
|
|
55
|
+
radius: 24,
|
|
56
|
+
});
|
|
57
|
+
expect(layout.sheet).toMatchObject({
|
|
58
|
+
width: 653,
|
|
59
|
+
maxHeight: 1104 - 24 - 12,
|
|
60
|
+
minHeight: 230,
|
|
61
|
+
margin: 12,
|
|
62
|
+
radius: 24,
|
|
63
|
+
});
|
|
64
|
+
expect(layout.scrimOpacity).toBe(0.2);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('limits popup height to the safe area minus 48', () => {
|
|
68
|
+
const layout = getModalLayout({
|
|
69
|
+
width: 900,
|
|
70
|
+
height: 600,
|
|
71
|
+
insets: { top: 24, right: 0, bottom: 20, left: 0 },
|
|
72
|
+
isDuo: false,
|
|
73
|
+
});
|
|
74
|
+
expect(layout.popup.maxHeight).toBe(600 - 24 - 20 - 48);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('places modals left of the control column on the iPhone Duo outer display', () => {
|
|
78
|
+
const layout = getModalLayout({
|
|
79
|
+
width: 420,
|
|
80
|
+
height: 760,
|
|
81
|
+
insets: { top: 0, right: 64, bottom: 20, left: 0 },
|
|
82
|
+
isDuo: true,
|
|
83
|
+
});
|
|
84
|
+
expect(layout.isFoldable).toBe(true);
|
|
85
|
+
expect(layout.placement).toEqual({ x: 0, y: 0, width: 356, height: 740 });
|
|
86
|
+
expect(layout.popup.margin).toBe(12);
|
|
87
|
+
expect(layout.popup.width).toBe(356 - 24);
|
|
88
|
+
expect(layout.sheet.width).toBe(356 - 24);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('never grows past the margins when the space is under the min width', () => {
|
|
92
|
+
const layout = getModalLayout({
|
|
93
|
+
width: 300,
|
|
94
|
+
height: 600,
|
|
95
|
+
insets: NO_INSETS,
|
|
96
|
+
isDuo: true,
|
|
97
|
+
});
|
|
98
|
+
expect(layout.popup.width).toBe(300 - 24);
|
|
99
|
+
expect(layout.popup.minWidth).toBe(300 - 24);
|
|
100
|
+
expect(layout.sheet.width).toBe(300 - 24);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('moves to the right half when iPhone Duo is half folded', () => {
|
|
104
|
+
const layout = getModalLayout({
|
|
105
|
+
width: 1000,
|
|
106
|
+
height: 800,
|
|
107
|
+
insets: NO_INSETS,
|
|
108
|
+
isDuo: true,
|
|
109
|
+
hinge: { x: 495, y: 0, width: 10, height: 800 },
|
|
110
|
+
});
|
|
111
|
+
expect(layout.placement).toEqual({ x: 505, y: 0, width: 495, height: 800 });
|
|
112
|
+
expect(layout.popup.margin).toBe(12);
|
|
113
|
+
expect(layout.popup.width).toBe(450);
|
|
114
|
+
expect(layout.sheet.width).toBe(495 - 24);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('ignores a division off the iPhone Duo', () => {
|
|
118
|
+
const layout = getModalLayout({
|
|
119
|
+
width: 1000,
|
|
120
|
+
height: 800,
|
|
121
|
+
insets: NO_INSETS,
|
|
122
|
+
isDuo: false,
|
|
123
|
+
hinge: { x: 495, y: 0, width: 10, height: 800 },
|
|
124
|
+
});
|
|
125
|
+
expect(layout.placement.x).toBe(0);
|
|
126
|
+
expect(layout.popup.margin).toBe(24);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -73,15 +73,11 @@ type ModalLayoutInput = {
|
|
|
73
73
|
hinge?: ReservedRegion;
|
|
74
74
|
};
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
*/
|
|
79
|
-
const clamp = (value: number, min: number, max: number) => {
|
|
80
|
-
return Math.min(Math.max(value, min), max);
|
|
81
|
-
};
|
|
76
|
+
const clamp = (value: number, min: number, max: number) =>
|
|
77
|
+
Math.min(Math.max(value, min), max);
|
|
82
78
|
|
|
83
79
|
/**
|
|
84
|
-
* Modal geometry for a window, pure so it can be tested without a device
|
|
80
|
+
* Modal geometry for a window, pure so it can be tested without a device
|
|
85
81
|
*/
|
|
86
82
|
const getModalLayout = ({
|
|
87
83
|
width,
|
|
@@ -95,9 +91,8 @@ const getModalLayout = ({
|
|
|
95
91
|
const isFoldable = isDuo || shortSide >= FOLDABLE_MIN_WIDTH;
|
|
96
92
|
const isDuoOuter = isDuo && shortSide < FOLDABLE_MIN_WIDTH;
|
|
97
93
|
const hingeRight = hinge ? hinge.x + hinge.width : 0;
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
const isHalfFolded = isDuo && hasHinge && isHingeInWindow;
|
|
94
|
+
const isHalfFolded =
|
|
95
|
+
isDuo && !!hinge && hinge.x > 0 && hingeRight < width - insets.right;
|
|
101
96
|
|
|
102
97
|
const safeLeft = isHalfFolded ? hingeRight : insets.left;
|
|
103
98
|
const placement = {
|
|
@@ -117,9 +112,9 @@ const getModalLayout = ({
|
|
|
117
112
|
minWidth: popupWidth,
|
|
118
113
|
maxWidth: popupWidth,
|
|
119
114
|
margin: Spacing.XL,
|
|
120
|
-
radius: Radius.
|
|
115
|
+
radius: Radius.L,
|
|
121
116
|
},
|
|
122
|
-
sheet: { margin: 0, radius: Radius.
|
|
117
|
+
sheet: { margin: 0, radius: Radius.M },
|
|
123
118
|
scrimOpacity: SCRIM_OPACITY,
|
|
124
119
|
};
|
|
125
120
|
}
|
|
@@ -130,7 +125,6 @@ const getModalLayout = ({
|
|
|
130
125
|
|
|
131
126
|
const sheetMargin = Spacing.M;
|
|
132
127
|
const sheetSpace = placement.width - sheetMargin * 2;
|
|
133
|
-
const sheetMinWidth = Math.min(SHEET_MIN_WIDTH, sheetSpace);
|
|
134
128
|
const sheetMaxHeight = height - insets.top - Spacing.M;
|
|
135
129
|
|
|
136
130
|
return {
|
|
@@ -145,7 +139,11 @@ const getModalLayout = ({
|
|
|
145
139
|
radius: Radius.XL,
|
|
146
140
|
},
|
|
147
141
|
sheet: {
|
|
148
|
-
width: clamp(
|
|
142
|
+
width: clamp(
|
|
143
|
+
sheetSpace,
|
|
144
|
+
Math.min(SHEET_MIN_WIDTH, sheetSpace),
|
|
145
|
+
SHEET_MAX_WIDTH,
|
|
146
|
+
),
|
|
149
147
|
maxHeight: sheetMaxHeight,
|
|
150
148
|
minHeight: Math.min(SHEET_MIN_HEIGHT, sheetMaxHeight),
|
|
151
149
|
margin: sheetMargin,
|
|
@@ -156,8 +154,8 @@ const getModalLayout = ({
|
|
|
156
154
|
};
|
|
157
155
|
|
|
158
156
|
/**
|
|
159
|
-
* Modal geometry for the current window
|
|
160
|
-
*
|
|
157
|
+
* Modal geometry for the current window, follows fold, unfold, rotation and the
|
|
158
|
+
* iPhone Duo hinge
|
|
161
159
|
*/
|
|
162
160
|
const useModalLayout = (): ModalLayout => {
|
|
163
161
|
const { width, height } = useWindowDimensions();
|
|
@@ -133,15 +133,13 @@ const exportHeaderTitle = (
|
|
|
133
133
|
};
|
|
134
134
|
} else if (typeof params.headerTitle === 'string') {
|
|
135
135
|
return {
|
|
136
|
-
headerTitle: (props: any) =>
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
);
|
|
144
|
-
},
|
|
136
|
+
headerTitle: (props: any) => (
|
|
137
|
+
<HeaderTitle
|
|
138
|
+
{...props}
|
|
139
|
+
children={params.headerTitle}
|
|
140
|
+
animatedValue={animatedValue}
|
|
141
|
+
/>
|
|
142
|
+
),
|
|
145
143
|
};
|
|
146
144
|
}
|
|
147
145
|
return {};
|
|
@@ -158,17 +156,15 @@ const getOptions = (
|
|
|
158
156
|
let options: StackNavigationOptions = {};
|
|
159
157
|
|
|
160
158
|
if (hasCustomHeaderLeft(params)) {
|
|
161
|
-
let headerLeft: any = (props: any) =>
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
);
|
|
171
|
-
};
|
|
159
|
+
let headerLeft: any = (props: any) => (
|
|
160
|
+
<HeaderLeft
|
|
161
|
+
{...props}
|
|
162
|
+
preventBack={params.preventBack}
|
|
163
|
+
onPressLeftHeader={params.onPressLeftHeader}
|
|
164
|
+
onBackHandler={params.onBackHandler}
|
|
165
|
+
floating
|
|
166
|
+
/>
|
|
167
|
+
);
|
|
172
168
|
if (params.hiddenBack) {
|
|
173
169
|
headerLeft = null;
|
|
174
170
|
options.headerTitleContainerStyle = {
|
|
@@ -207,7 +203,7 @@ const getOptions = (
|
|
|
207
203
|
};
|
|
208
204
|
}
|
|
209
205
|
|
|
210
|
-
//
|
|
206
|
+
// only ever turns swipe back off, screens opt in via gestureEnabled
|
|
211
207
|
if (params.gestureEnabled === undefined && hasCustomBackHandling(params)) {
|
|
212
208
|
options.gestureEnabled = false;
|
|
213
209
|
}
|
package/Badge/Badge.tsx
CHANGED
|
@@ -7,11 +7,6 @@ import { BadgeProps } from './types';
|
|
|
7
7
|
import { Colors, Radius, Spacing } from '../Consts';
|
|
8
8
|
import { Text, useScaleSize } from '../Text';
|
|
9
9
|
|
|
10
|
-
/**
|
|
11
|
-
* Label or count badge; counts over 99 show "99+". Counts use the error color,
|
|
12
|
-
* labels the warning color, and `backgroundColor` applies only if it's a
|
|
13
|
-
* `Colors.*_03` token.
|
|
14
|
-
*/
|
|
15
10
|
const Badge: FC<BadgeProps> = ({
|
|
16
11
|
label = '',
|
|
17
12
|
style,
|
package/Badge/BadgeDot.tsx
CHANGED
|
@@ -4,7 +4,6 @@ import { BadgeDotProps } from './types';
|
|
|
4
4
|
import styles from './styles';
|
|
5
5
|
import { MiniAppContext } from '../Context';
|
|
6
6
|
|
|
7
|
-
/** Static red notification dot with a white ring, `large` or `small`. */
|
|
8
7
|
const BadgeDot: FC<BadgeDotProps> = ({ size = 'large', style }) => {
|
|
9
8
|
const context = useContext<any>(MiniAppContext);
|
|
10
9
|
let dotStyle;
|
|
@@ -14,10 +14,6 @@ import styles from './styles';
|
|
|
14
14
|
|
|
15
15
|
const DURATION = 500;
|
|
16
16
|
|
|
17
|
-
/**
|
|
18
|
-
* Red notification dot that pulses with a spring and emits an expanding,
|
|
19
|
-
* fading wave ring, looping until unmounted.
|
|
20
|
-
*/
|
|
21
17
|
const BadgeDotAnimation = ({ size, style }: BadgeDotProps) => {
|
|
22
18
|
const scaleAnim = useSharedValue(1);
|
|
23
19
|
const waveScaleAnim = useSharedValue(1);
|
|
@@ -61,18 +57,14 @@ const BadgeDotAnimation = ({ size, style }: BadgeDotProps) => {
|
|
|
61
57
|
};
|
|
62
58
|
}, [scaleAnim, waveOpacityAnim, waveScaleAnim]);
|
|
63
59
|
|
|
64
|
-
const waveAnimatedStyle = useAnimatedStyle(() => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
};
|
|
69
|
-
});
|
|
60
|
+
const waveAnimatedStyle = useAnimatedStyle(() => ({
|
|
61
|
+
transform: [{ scale: waveScaleAnim.value }],
|
|
62
|
+
opacity: waveOpacityAnim.value,
|
|
63
|
+
}));
|
|
70
64
|
|
|
71
|
-
const dotAnimatedStyle = useAnimatedStyle(() => {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
};
|
|
75
|
-
});
|
|
65
|
+
const dotAnimatedStyle = useAnimatedStyle(() => ({
|
|
66
|
+
transform: [{ scale: scaleAnim.value }],
|
|
67
|
+
}));
|
|
76
68
|
|
|
77
69
|
return (
|
|
78
70
|
<View style={[styles.dotAnimationContainer, style]}>
|