Skip to content

Commit 444fe9b

Browse files
authored
Support drawer menus opening as overlay instead of pushing content in iOS (#8038)
1 parent 1fbffe1 commit 444fe9b

16 files changed

+1102
-235
lines changed

e2e/SetRoot.test.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,14 @@ describe('SetRoot', () => {
5151
await elementById(TestIDs.OK_BUTTON).tap();
5252
});
5353
});
54+
55+
it.e2e(':ios: set root with left and right side menus - menu visibility', async () => {
56+
await elementById(TestIDs.SET_ROOT_WITH_MENUS).tap();
57+
await elementById(TestIDs.TOGGLE_SIDE_MENU_OPEN_MODE_BTN).tap();
58+
await elementById(TestIDs.OPEN_LEFT_SIDE_MENU_BTN).tap();
59+
await elementById(TestIDs.CLOSE_LEFT_SIDE_MENU_BTN).tap();
60+
await expect(elementById(TestIDs.CLOSE_LEFT_SIDE_MENU_BTN)).toBeNotVisible();
61+
await elementById(TestIDs.OPEN_RIGHT_SIDE_MENU_BTN).tap();
62+
await elementById(TestIDs.CLOSE_RIGHT_SIDE_MENU_BTN).tap();
63+
await expect(elementById(TestIDs.CLOSE_RIGHT_SIDE_MENU_BTN)).toBeNotVisible();
64+
});

e2e/SideMenu.test.js

Lines changed: 101 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,107 @@
11
import Utils from './Utils';
22
import TestIDs from '../playground/src/testIDs';
33

4-
const { elementByLabel, elementById } = Utils;
4+
const {elementByLabel, elementById, expectImagesToBeEqual} = Utils;
55

66
describe('SideMenu', () => {
7-
beforeEach(async () => {
8-
await device.launchApp({ newInstance: true });
9-
await elementById(TestIDs.SIDE_MENU_BTN).tap();
10-
});
11-
12-
it('close SideMenu and push to stack with static id', async () => {
13-
await elementById(TestIDs.OPEN_LEFT_SIDE_MENU_BTN).tap();
14-
await elementById(TestIDs.LEFT_SIDE_MENU_PUSH_BTN).tap();
15-
await elementById(TestIDs.CLOSE_LEFT_SIDE_MENU_BTN).tap();
16-
await expect(elementById(TestIDs.PUSHED_SCREEN_HEADER)).toBeVisible();
17-
await elementById(TestIDs.POP_BTN).tap();
18-
await expect(elementById(TestIDs.CENTER_SCREEN_HEADER)).toBeVisible();
19-
});
20-
21-
it('Push to stack with static id and close SideMenu with screen options', async () => {
22-
await elementById(TestIDs.OPEN_LEFT_SIDE_MENU_BTN).tap();
23-
await elementById(TestIDs.LEFT_SIDE_MENU_PUSH_AND_CLOSE_BTN).tap();
24-
await expect(elementById(TestIDs.PUSHED_SCREEN_HEADER)).toBeVisible();
25-
await elementById(TestIDs.POP_BTN).tap();
26-
await expect(elementById(TestIDs.CENTER_SCREEN_HEADER)).toBeVisible();
27-
});
28-
29-
it('side menu visibility - left', async () => {
30-
await elementById(TestIDs.OPEN_LEFT_SIDE_MENU_BTN).tap();
31-
await elementById(TestIDs.CLOSE_LEFT_SIDE_MENU_BTN).tap();
32-
await expect(elementById(TestIDs.CLOSE_LEFT_SIDE_MENU_BTN)).toBeNotVisible();
33-
});
34-
35-
it('side menu visibility - right', async () => {
36-
await elementById(TestIDs.OPEN_RIGHT_SIDE_MENU_BTN).tap();
37-
await elementById(TestIDs.CLOSE_RIGHT_SIDE_MENU_BTN).tap();
38-
await expect(elementById(TestIDs.CLOSE_RIGHT_SIDE_MENU_BTN)).toBeNotVisible();
39-
});
40-
41-
it.e2e('should rotate', async () => {
42-
await elementById(TestIDs.OPEN_LEFT_SIDE_MENU_BTN).tap();
43-
await device.setOrientation('landscape');
44-
await expect(elementById(TestIDs.LEFT_SIDE_MENU_PUSH_BTN)).toBeVisible();
45-
});
46-
47-
it.e2e(':ios: rotation should update drawer height', async () => {
48-
await elementById(TestIDs.OPEN_LEFT_SIDE_MENU_BTN).tap();
49-
await expect(elementByLabel('left drawer height: 869')).toBeVisible();
50-
await device.setOrientation('landscape');
51-
await expect(elementByLabel('left drawer height: 428')).toBeVisible();
52-
await device.setOrientation('portrait');
53-
await expect(elementByLabel('left drawer height: 869')).toBeVisible();
54-
});
55-
56-
it.e2e('should set left drawer width', async () => {
57-
await elementById(TestIDs.OPEN_LEFT_SIDE_MENU_BTN).tap();
58-
await expect(elementById(TestIDs.SIDE_MENU_LEFT_DRAWER_HEIGHT_TEXT)).toBeVisible();
59-
await expect(elementByLabel('left drawer width: 250')).toBeVisible();
60-
});
61-
62-
it.e2e('should change left drawer width', async () => {
63-
await elementById(TestIDs.CHANGE_LEFT_SIDE_MENU_WIDTH_BTN).tap();
64-
await elementById(TestIDs.OPEN_LEFT_SIDE_MENU_BTN).tap();
65-
await expect(elementByLabel('left drawer width: 100')).toBeVisible();
66-
});
67-
68-
it.e2e('should set right drawer width', async () => {
69-
await elementById(TestIDs.OPEN_RIGHT_SIDE_MENU_BTN).tap();
70-
await expect(elementByLabel('right drawer width: 250')).toBeVisible();
71-
});
72-
73-
it.e2e('should change right drawer width', async () => {
74-
await elementById(TestIDs.CHANGE_RIGHT_SIDE_MENU_WIDTH_BTN).tap();
75-
await elementById(TestIDs.OPEN_RIGHT_SIDE_MENU_BTN).tap();
76-
await expect(elementByLabel('right drawer width: 50')).toBeVisible();
77-
});
7+
beforeEach(async () => {
8+
await device.launchApp({newInstance : true});
9+
await elementById(TestIDs.SIDE_MENU_BTN).tap();
10+
});
11+
12+
describe.each(['aboveContent', 'pushContent'])('Open mode %s', (openMode) => {
13+
beforeEach(async () => {
14+
if (openMode === 'pushContent') {
15+
await elementById(TestIDs.TOGGLE_SIDE_MENU_OPEN_MODE_BTN).tap();
16+
}
17+
});
18+
19+
it('close SideMenu and push to stack with static id', async () => {
20+
await elementById(TestIDs.OPEN_LEFT_SIDE_MENU_BTN).tap();
21+
await elementById(TestIDs.LEFT_SIDE_MENU_PUSH_BTN).tap();
22+
await elementById(TestIDs.CLOSE_LEFT_SIDE_MENU_BTN).tap();
23+
await expect(elementById(TestIDs.PUSHED_SCREEN_HEADER)).toBeVisible();
24+
await elementById(TestIDs.POP_BTN).tap();
25+
await expect(elementById(TestIDs.CENTER_SCREEN_HEADER)).toBeVisible();
26+
});
27+
28+
it('Push to stack with static id and close SideMenu with screen options', async () => {
29+
await elementById(TestIDs.OPEN_LEFT_SIDE_MENU_BTN).tap();
30+
await elementById(TestIDs.LEFT_SIDE_MENU_PUSH_AND_CLOSE_BTN).tap();
31+
await expect(elementById(TestIDs.PUSHED_SCREEN_HEADER)).toBeVisible();
32+
await elementById(TestIDs.POP_BTN).tap();
33+
await expect(elementById(TestIDs.CENTER_SCREEN_HEADER)).toBeVisible();
34+
});
35+
36+
it('side menu visibility - left', async () => {
37+
await elementById(TestIDs.OPEN_LEFT_SIDE_MENU_BTN).tap();
38+
await elementById(TestIDs.CLOSE_LEFT_SIDE_MENU_BTN).tap();
39+
await expect(elementById(TestIDs.CLOSE_LEFT_SIDE_MENU_BTN)).toBeNotVisible();
40+
});
41+
42+
it('side menu visibility - right', async () => {
43+
await elementById(TestIDs.OPEN_RIGHT_SIDE_MENU_BTN).tap();
44+
await elementById(TestIDs.CLOSE_RIGHT_SIDE_MENU_BTN).tap();
45+
await expect(elementById(TestIDs.CLOSE_RIGHT_SIDE_MENU_BTN)).toBeNotVisible();
46+
});
47+
48+
it.e2e('should rotate', async () => {
49+
await elementById(TestIDs.OPEN_LEFT_SIDE_MENU_BTN).tap();
50+
await device.setOrientation('landscape');
51+
await expect(elementById(TestIDs.LEFT_SIDE_MENU_PUSH_BTN)).toBeVisible();
52+
});
53+
54+
it.e2e(':ios: rotation should update drawer height', async () => {
55+
await elementById(TestIDs.OPEN_LEFT_SIDE_MENU_BTN).tap();
56+
await expect(elementByLabel('left drawer height: 869')).toBeVisible();
57+
await device.setOrientation('landscape');
58+
await expect(elementByLabel('left drawer height: 428')).toBeVisible();
59+
await device.setOrientation('portrait');
60+
await expect(elementByLabel('left drawer height: 869')).toBeVisible();
61+
});
62+
63+
it.e2e('should set left drawer width', async () => {
64+
await elementById(TestIDs.OPEN_LEFT_SIDE_MENU_BTN).tap();
65+
await expect(elementById(TestIDs.SIDE_MENU_LEFT_DRAWER_HEIGHT_TEXT)).toBeVisible();
66+
await expect(elementByLabel('left drawer width: 250')).toBeVisible();
67+
});
68+
69+
it.e2e('should change left drawer width', async () => {
70+
await elementById(TestIDs.CHANGE_LEFT_SIDE_MENU_WIDTH_BTN).tap();
71+
await elementById(TestIDs.OPEN_LEFT_SIDE_MENU_BTN).tap();
72+
await expect(elementByLabel('left drawer width: 100')).toBeVisible();
73+
});
74+
75+
it.e2e('should set right drawer width', async () => {
76+
await elementById(TestIDs.OPEN_RIGHT_SIDE_MENU_BTN).tap();
77+
await expect(elementByLabel('right drawer width: 250')).toBeVisible();
78+
});
79+
80+
it.e2e('should change right drawer width', async () => {
81+
await elementById(TestIDs.CHANGE_RIGHT_SIDE_MENU_WIDTH_BTN).tap();
82+
await elementById(TestIDs.OPEN_RIGHT_SIDE_MENU_BTN).tap();
83+
await expect(elementByLabel('right drawer width: 50')).toBeVisible();
84+
});
85+
86+
it.e2e(':ios: should render side menu correctly', async () => {
87+
await elementById(TestIDs.OPEN_LEFT_SIDE_MENU_BTN).tap();
88+
const snapshottedImagePath = `./e2e/assets/side_menu.${openMode}.png`;
89+
const actual =
90+
await elementById('SideMenuContainer').takeScreenshot(`side_menu_${openMode}`);
91+
expectImagesToBeEqual(actual, snapshottedImagePath);
92+
});
93+
});
94+
95+
it.e2e(':ios: should open above-content by default', async () => {
96+
await elementById(TestIDs.TOGGLE_SIDE_MENU_OPEN_MODE_BTN).tap(); // aboveContent --> pushContent
97+
await elementById(TestIDs.TOGGLE_SIDE_MENU_OPEN_MODE_BTN).tap(); // pushContent --> undefined
98+
await expect(elementByLabel('Open mode: undefined')).toBeVisible();
99+
100+
await elementById(TestIDs.OPEN_LEFT_SIDE_MENU_BTN).tap();
101+
102+
const snapshottedImagePath = `./e2e/assets/side_menu.undefined.png`;
103+
const actual =
104+
await elementById('SideMenuContainer').takeScreenshot(`side_menu_undefined`);
105+
expectImagesToBeEqual(actual, snapshottedImagePath);
106+
});
78107
});

e2e/assets/side_menu.aboveContent.png

144 KB
Loading

e2e/assets/side_menu.pushContent.png

153 KB
Loading

e2e/assets/side_menu.undefined.png

143 KB
Loading

lib/ios/RNNSideMenu/MMDrawerController/MMDrawerController.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,14 @@ typedef void (^MMDrawerControllerDrawerVisualStateBlock)(MMDrawerController *dra
127127

128128
@interface MMDrawerController : UIViewController
129129

130+
/**
131+
Enum defining how the drawer opens
132+
*/
133+
typedef NS_ENUM(NSInteger, MMDrawerOpenMode) {
134+
MMDrawerOpenModePushContent = 0, // Original behavior - pushes content aside
135+
MMDrawerOpenModeAboveContent = 1, // Overlay behavior - opens above content
136+
};
137+
130138
///---------------------------------------
131139
/// @name Accessing Drawer Container View Controller Properties
132140
///---------------------------------------
@@ -213,6 +221,20 @@ typedef void (^MMDrawerControllerDrawerVisualStateBlock)(MMDrawerController *dra
213221
@property(nonatomic, assign) BOOL shouldStretchLeftDrawer;
214222
@property(nonatomic, assign) BOOL shouldStretchRightDrawer;
215223

224+
/**
225+
* Specifies how the drawer should open relative to the center content.
226+
*
227+
* Possible values:
228+
* - MMDrawerOpenModePushContent: The drawer will push the center content aside when opening
229+
* (traditional behavior).
230+
* - MMDrawerOpenModeAboveContent: The drawer will open above the center content with a
231+
* semi-transparent overlay.
232+
*
233+
* By default, this value is set to MMDrawerOpenModePushContent.
234+
*/
235+
@property(nonatomic, assign) MMDrawerOpenMode leftDrawerOpenMode;
236+
@property(nonatomic, assign) MMDrawerOpenMode rightDrawerOpenMode;
237+
216238
/**
217239
The current open side of the drawer.
218240
@@ -600,4 +622,6 @@ typedef void (^MMDrawerControllerDrawerVisualStateBlock)(MMDrawerController *dra
600622
(BOOL (^)(MMDrawerController *drawerController, UIGestureRecognizer *gesture,
601623
UITouch *touch))gestureShouldRecognizeTouchBlock;
602624

625+
- (void)side:(MMDrawerSide)drawerSide openMode:(MMDrawerOpenMode)openMode;
626+
603627
@end

0 commit comments

Comments
 (0)