Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict-local
*/

import codegenNativeComponent from '../../Utilities/codegenNativeComponent';

type NativeProps = $ReadOnly<{|
/**
* Whether the indicator should hide when not animating (true by default).
*
* See https://reactnative.dev/docs/activityindicator#hideswhenstopped
*/
hidesWhenStopped?: WithDefault<boolean, false>,

/**
* Whether to show the indicator (true, the default) or hide it (false).
*
* See https://reactnative.dev/docs/activityindicator#animating
*/
animating?: WithDefault<boolean, false>,

/**
* The foreground color of the spinner (default is gray).
*
* See https://reactnative.dev/docs/activityindicator#color
*/
color?: ?ColorValue,

/**
* Size of the indicator (default is 'small').
* Passing a number to the size prop is only supported on Android.
*
* See https://reactnative.dev/docs/activityindicator#size
*/
size?: WithDefault<'small' | 'large', 'small'>,
|}>;

export default (codegenNativeComponent<NativeProps>('DrawerView', {
interfaceOnly: true,
paperComponentName: 'RCTDrawerView',
}): HostComponent<NativeProps>);
14 changes: 14 additions & 0 deletions packages/react-native/React/Views/RCTDrawerView.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

#import <UIKit/UIKit.h>
#import "RCTInvalidating.h"

@class RCTBridge;

@interface RCTDrawerView : UIView <RCTInvalidating, UISplitViewControllerDelegate>

@property (nonatomic, assign) BOOL visible;
@property (nonatomic, assign) NSInteger width;

- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;

@end
151 changes: 151 additions & 0 deletions packages/react-native/React/Views/RCTDrawerView.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#import "RCTDrawerView.h"
#import "RCTDrawerViewController.h"

#import <Foundation/Foundation.h>

#import "RCTBridge.h"
#import "RCTAssert.h"
#import "RCTBridge.h"
#import "RCTModalHostViewController.h"
#import "RCTTouchHandler.h"
#import "RCTUIManager.h"
#import "RCTUtils.h"
#import "UIView+React.h"

// React UIView has a protocol with callbacks eg insertReactSubview
// to add the view in the sidebar instead


@implementation RCTDrawerView {
__weak RCTBridge *_bridge;
BOOL _visible;
NSInteger _width;
BOOL _isPresented;
RCTDrawerViewController *_drawerViewController;
RCTTouchHandler *_touchHandler;
UIView *_reactSubview;
UIInterfaceOrientation _lastKnownOrientation;
RCTDirectEventBlock _onRequestClose;
}

RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame : (CGRect)frame)
RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : coder)

- (instancetype)initWithBridge:(RCTBridge *)bridge
{
if ((self = [super initWithFrame:CGRectZero])) {
_bridge = bridge;
_drawerViewController = [[RCTDrawerViewController alloc] init];
UIView *containerView = [UIView new];
containerView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
_drawerViewController.view = containerView;
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:bridge];
_visible = NO;
_isPresented = NO;

__weak typeof(self) weakSelf = self;
_drawerViewController.boundsDidChangeBlock = ^(CGRect newBounds) {
[weakSelf notifyForBoundsChange:newBounds];
};
}

return self;
}

- (void)notifyForBoundsChange:(CGRect)newBounds
{
if (_reactSubview) {
[_bridge.uiManager setSize:CGSizeMake(_width, newBounds.size.height) forView:_reactSubview];
}

UIInterfaceOrientation currentOrientation = [RCTSharedApplication() statusBarOrientation];
if (currentOrientation == _lastKnownOrientation) {
return;
}
_lastKnownOrientation = currentOrientation;

BOOL isLandscape = currentOrientation == UIInterfaceOrientationLandscapeLeft ||
currentOrientation == UIInterfaceOrientationLandscapeRight;

// Quickly remove gesture when orientation changes
UISplitViewController *presentedViewController = RCTPresentedViewController();

if(isLandscape) {
[presentedViewController setPresentsWithGesture:NO];
}
}

- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex
{
RCTAssert(_reactSubview == nil, @"Drawer view can only have one subview");
[super insertReactSubview:subview atIndex:atIndex];
[_touchHandler attachToView:subview];

[_drawerViewController.view insertSubview:subview atIndex:0];
_reactSubview = subview;
}

- (void)removeReactSubview:(UIView *)subview
{
RCTAssert(subview == _reactSubview, @"Cannot remove view other than drawer view");
// Superclass (category) removes the `subview` from actual `superview`.
[super removeReactSubview:subview];
[_touchHandler detachFromView:subview];
_reactSubview = nil;
}

- (void)didUpdateReactSubviews
{

// Do nothing, as subview (singular) is managed by `insertReactSubview:atIndex:`
}

- (void)setVisible:(BOOL)visible
{
if (_visible != visible) {
_visible = visible;
[self ensurePresentedOnlyIfNeeded];
}
}

- (void)setWidth:(NSInteger)width {
_width = width;
}

- (void)ensurePresentedOnlyIfNeeded
{
BOOL shouldBePresented = !_isPresented && _visible;
if (shouldBePresented) {
UISplitViewController *presentedViewController = RCTPresentedViewController();
[presentedViewController setViewController:_drawerViewController forColumn:UISplitViewControllerColumnPrimary];
[presentedViewController setPresentsWithGesture:YES];
_isPresented = YES;
}

BOOL shouldBeHidden = _isPresented && !_visible;
if (shouldBeHidden) {
[self removeDrawer];
}
}

- (void)invalidate
{
// When going back, react navigation renderers the previous view
// before invalidating the current one
//dispatch_async(dispatch_get_main_queue(), ^{
// [self removeDrawer];
//});
}

- (void) removeDrawer
{
if (_isPresented) {
UISplitViewController *presentedViewController = RCTPresentedViewController();
[presentedViewController setViewController:nil forColumn:UISplitViewControllerColumnPrimary];
[presentedViewController setPresentsWithGesture:NO];
_isPresented = NO;
}
}


@end
7 changes: 7 additions & 0 deletions packages/react-native/React/Views/RCTDrawerViewController.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#import <UIKit/UIKit.h>

@interface RCTDrawerViewController : UIViewController

@property (nonatomic, copy) void (^boundsDidChangeBlock)(CGRect newBounds);

@end
27 changes: 27 additions & 0 deletions packages/react-native/React/Views/RCTDrawerViewController.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#import <Foundation/Foundation.h>

#import "RCTDrawerViewController.h"

@implementation RCTDrawerViewController {
CGRect _lastViewFrame;
}

- (instancetype)init
{
if (!(self = [super init])) {
return nil;
}
return self;
}

- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];

if (self.boundsDidChangeBlock && !CGRectEqualToRect(_lastViewFrame, self.view.frame)) {
self.boundsDidChangeBlock(self.view.bounds);
_lastViewFrame = self.view.frame;
}
}

@end
76 changes: 76 additions & 0 deletions packages/react-native/React/Views/RCTDrawerViewManager.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#import "RCTViewManager.h"
#import "RCTShadowView.h"
#import "RCTDrawerView.h"

// Shadow view used for layout
@interface RCTDrawerHostShadowView : RCTShadowView

- (instancetype)initWithWidth:(NSInteger)width;

@end

@implementation RCTDrawerHostShadowView

NSInteger _width;

- (instancetype)initWithWidth:(NSInteger)width
{
if ((self = [super init])) {
_width = width;
}

return self;
}

- (void)insertReactSubview:(id<RCTComponent>)subview atIndex:(NSInteger)atIndex
{
[super insertReactSubview:subview atIndex:atIndex];
if ([subview isKindOfClass:[RCTShadowView class]]) {
((RCTShadowView *)subview).size = CGSizeMake(_width, RCTScreenSize().height);
}
}

@end

@interface RCTDrawerViewManager : RCTViewManager

@property (nonatomic) NSInteger width;

@end

@implementation RCTDrawerViewManager

- (instancetype)init {
if ((self = [super init])) {
_width = 320;
}
return self;
}

RCT_EXPORT_MODULE()

+ (BOOL)requiresMainQueueSetup
{
return NO;
}

- (UIView *)view
{
RCTDrawerView *view = [[RCTDrawerView alloc] initWithBridge:self.bridge];
return view;
}

- (RCTShadowView *)shadowView
{
return [[RCTDrawerHostShadowView alloc] initWithWidth:_width];
}

RCT_EXPORT_VIEW_PROPERTY(visible, BOOL)
RCT_CUSTOM_VIEW_PROPERTY(width, NSInteger, RCTDrawerView)
{
_width = [RCTConvert NSInteger:json];
view.width = _width;
}

@end

3 changes: 3 additions & 0 deletions packages/react-native/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ module.exports = {
get Button(): Button {
return require('./Libraries/Components/Button');
},
get DrawerLayoutIos(): DrawerLayoutIos {
return require('./Libraries/Components/DrawerIos/DrawerLayoutIos').default;
},
// $FlowFixMe[value-as-type]
get DrawerLayoutAndroid(): DrawerLayoutAndroid {
return require('./Libraries/Components/DrawerAndroid/DrawerLayoutAndroid');
Expand Down
Loading