@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.
Files changed (141) hide show
  1. package/Application/BottomSheet.hooks.ts +8 -8
  2. package/Application/BottomSheet.parts.tsx +34 -44
  3. package/Application/BottomSheet.tsx +37 -40
  4. package/Application/BottomTab/ActiveIndicator.tsx +14 -15
  5. package/Application/BottomTab/BottomTabBar.tsx +62 -49
  6. package/Application/BottomTab/BottomTabInset.tsx +4 -8
  7. package/Application/BottomTab/CurvedContainer.tsx +12 -14
  8. package/Application/BottomTab/CustomBottomTabItem.tsx +13 -13
  9. package/Application/BottomTab/DuoBarCompression.tsx +19 -13
  10. package/Application/BottomTab/DuoRailTabBar.tsx +24 -20
  11. package/Application/BottomTab/DuoTabBar.tsx +10 -8
  12. package/Application/BottomTab/TabBarIcon.tsx +29 -15
  13. package/Application/BottomTab/TabScreen.tsx +5 -1
  14. package/Application/BottomTab/index.tsx +30 -33
  15. package/Application/BottomTab/metrics.ts +23 -23
  16. package/Application/BottomTab/tabItems.ts +1 -1
  17. package/Application/BottomTab/usePressLift.ts +2 -2
  18. package/Application/Components/BackgroundImageView.tsx +0 -4
  19. package/Application/Components/DuoGlassBackground.tsx +4 -5
  20. package/Application/Components/HeaderAnimated.tsx +0 -4
  21. package/Application/Components/HeaderBackground.tsx +0 -4
  22. package/Application/Components/HeaderExtendHeader.tsx +10 -11
  23. package/Application/Components/HeaderLeft.tsx +1 -2
  24. package/Application/Components/HeaderRight.tsx +1 -2
  25. package/Application/Components/HeaderTitle.shared.ts +9 -11
  26. package/Application/Components/HeaderTitle.tsx +3 -4
  27. package/Application/Components/HeaderToolkitAction.tsx +20 -17
  28. package/Application/Components/NavigationButton.tsx +1 -2
  29. package/Application/Components/SearchHeader.tsx +10 -17
  30. package/Application/Components/TitleJourney.tsx +2 -2
  31. package/Application/Components/TitleLocation.tsx +2 -2
  32. package/Application/Components/TitleUser.tsx +2 -2
  33. package/Application/Components/useToolkitActions.tsx +32 -17
  34. package/Application/KitAdoption/AdoptionBadge.tsx +119 -0
  35. package/Application/KitAdoption/AdoptionProbe.tsx +166 -0
  36. package/Application/KitAdoption/AdoptionStore.ts +80 -0
  37. package/Application/KitAdoption/Authorship/utils.ts +137 -0
  38. package/Application/KitAdoption/Slots/utils.ts +68 -0
  39. package/Application/KitAdoption/constants.ts +120 -0
  40. package/Application/KitAdoption/index.ts +12 -0
  41. package/Application/KitAdoption/styles.ts +55 -0
  42. package/Application/KitAdoption/types.ts +61 -0
  43. package/Application/KitAdoption/utils.ts +376 -0
  44. package/Application/Localize.ts +0 -7
  45. package/Application/ModalScreen.tsx +10 -10
  46. package/Application/Navigation.ts +16 -14
  47. package/Application/NavigationContainer.tsx +18 -19
  48. package/Application/Navigator.ts +48 -20
  49. package/Application/ScaleSizeProvider.tsx +6 -13
  50. package/Application/StackScreen.tracking.ts +33 -11
  51. package/Application/StackScreen.tsx +54 -10
  52. package/Application/TooltipPortal.tsx +5 -12
  53. package/Application/WidgetContainer.tsx +4 -10
  54. package/Application/__tests__/KitAdoption.integration.test.tsx +253 -0
  55. package/Application/__tests__/KitAdoption.test.ts +283 -0
  56. package/Application/__tests__/KitAdoptionAuthorship.test.ts +132 -0
  57. package/Application/__tests__/KitAdoptionStore.test.ts +110 -0
  58. package/Application/__tests__/KitAdoptionStructural.test.ts +193 -0
  59. package/Application/__tests__/modalLayout.test.ts +128 -0
  60. package/Application/modalLayout.ts +14 -16
  61. package/Application/navigationOptions.tsx +17 -21
  62. package/Badge/Badge.tsx +0 -5
  63. package/Badge/BadgeDot.tsx +0 -1
  64. package/Badge/BadgeDotAnimation.tsx +7 -15
  65. package/Badge/BadgeRibbon.tsx +38 -46
  66. package/Badge/Shape.tsx +0 -2
  67. package/Badge/types.ts +2 -2
  68. package/Button/index.tsx +16 -5
  69. package/Button/utils.ts +15 -9
  70. package/CheckBox/index.tsx +0 -4
  71. package/Consts/theme.ts +10 -10
  72. package/Context/index.ts +1 -5
  73. package/Divider/DashDivider.tsx +0 -1
  74. package/Divider/index.tsx +0 -4
  75. package/FoundationList/index.tsx +0 -4
  76. package/Icon/index.tsx +6 -6
  77. package/Icon/types.ts +1 -1
  78. package/IconButton/index.tsx +12 -4
  79. package/Image/index.tsx +7 -9
  80. package/Input/Input.tsx +9 -8
  81. package/Input/InputDropDown.tsx +12 -10
  82. package/Input/InputMoney.tsx +8 -12
  83. package/Input/InputMoneyView.tsx +7 -14
  84. package/Input/InputOTP.tsx +33 -35
  85. package/Input/InputPhoneNumber.tsx +9 -8
  86. package/Input/InputSearch.tsx +6 -4
  87. package/Input/InputTextArea.tsx +0 -4
  88. package/Input/SearchTextTyping.tsx +0 -4
  89. package/Input/SystemTextInput.tsx +2 -7
  90. package/Input/TextTyping.tsx +0 -4
  91. package/Input/common.tsx +12 -26
  92. package/Input/index.tsx +20 -17
  93. package/Input/moneyUtils.ts +4 -10
  94. package/Input/styles.ts +5 -0
  95. package/Input/utils.ts +12 -14
  96. package/Layout/Card.tsx +4 -7
  97. package/Layout/FloatingButton.tsx +0 -4
  98. package/Layout/Item.tsx +2 -6
  99. package/Layout/ItemList.tsx +7 -9
  100. package/Layout/ItemSectionList.tsx +1 -2
  101. package/Layout/ReservedRegions.tsx +25 -25
  102. package/Layout/Screen.tsx +58 -56
  103. package/Layout/Section.tsx +5 -8
  104. package/Layout/TrackingScope.tsx +0 -1
  105. package/Layout/screenContent.tsx +2 -4
  106. package/Layout/screenFooter.tsx +18 -28
  107. package/Layout/screenHeader.tsx +31 -34
  108. package/Layout/screenQuickReturn.tsx +20 -17
  109. package/Layout/screenScroll.ts +7 -4
  110. package/Layout/screenTypes.ts +2 -3
  111. package/Layout/types.ts +4 -2
  112. package/Layout/utils.ts +37 -31
  113. package/Loader/DotLoader.tsx +0 -4
  114. package/Loader/Loader.tsx +0 -4
  115. package/Loader/ProgressBar.tsx +0 -1
  116. package/Loader/Spinner.tsx +0 -4
  117. package/Loader/utils.ts +7 -11
  118. package/Pagination/Dot.tsx +0 -4
  119. package/Pagination/PaginationDot.tsx +0 -1
  120. package/Pagination/PaginationNumber.tsx +3 -5
  121. package/Pagination/PaginationScroll.tsx +0 -5
  122. package/Pagination/PaginationWhiteDot.tsx +0 -1
  123. package/Pagination/index.tsx +0 -4
  124. package/Pagination/types.ts +2 -2
  125. package/Popup/PopupNotify.tsx +48 -44
  126. package/Popup/PopupPromotion.tsx +7 -4
  127. package/Popup/types.ts +4 -4
  128. package/Popup/utils.ts +0 -4
  129. package/Radio/index.tsx +0 -4
  130. package/Skeleton/index.tsx +0 -8
  131. package/Switch/index.tsx +0 -1
  132. package/Tag/index.tsx +1 -5
  133. package/Tag/types.ts +3 -3
  134. package/Text/index.tsx +3 -14
  135. package/Text/styles.ts +1 -1
  136. package/Text/types.ts +6 -1
  137. package/Text/utils.ts +4 -20
  138. package/Title/index.tsx +32 -41
  139. package/ViewBoundary/index.tsx +2 -4
  140. package/index.ts +6 -0
  141. 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
- * `value` limited to the `[min, max]` range.
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 hasHinge = !!hinge && hinge.x > 0;
99
- const isHingeInWindow = hingeRight < width - insets.right;
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.XL,
115
+ radius: Radius.L,
121
116
  },
122
- sheet: { margin: 0, radius: Radius.XL },
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(sheetSpace, sheetMinWidth, SHEET_MAX_WIDTH),
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; follows fold, unfold, rotation and
160
- * the iPhone Duo hinge.
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
- return (
138
- <HeaderTitle
139
- {...props}
140
- children={params.headerTitle}
141
- animatedValue={animatedValue}
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
- return (
163
- <HeaderLeft
164
- {...props}
165
- preventBack={params.preventBack}
166
- onPressLeftHeader={params.onPressLeftHeader}
167
- onBackHandler={params.onBackHandler}
168
- floating
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
- // Only ever turns swipe back off; screens opt in via gestureEnabled.
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,
@@ -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
- return {
66
- transform: [{ scale: waveScaleAnim.value }],
67
- opacity: waveOpacityAnim.value,
68
- };
69
- });
60
+ const waveAnimatedStyle = useAnimatedStyle(() => ({
61
+ transform: [{ scale: waveScaleAnim.value }],
62
+ opacity: waveOpacityAnim.value,
63
+ }));
70
64
 
71
- const dotAnimatedStyle = useAnimatedStyle(() => {
72
- return {
73
- transform: [{ scale: scaleAnim.value }],
74
- };
75
- });
65
+ const dotAnimatedStyle = useAnimatedStyle(() => ({
66
+ transform: [{ scale: scaleAnim.value }],
67
+ }));
76
68
 
77
69
  return (
78
70
  <View style={[styles.dotAnimationContainer, style]}>