From f144bd7680662da042343a2d6541840370dfe0cf Mon Sep 17 00:00:00 2001 From: kligarski Date: Tue, 15 Jul 2025 09:34:32 +0200 Subject: [PATCH 01/17] orientation for tabs and screenstack --- apps/src/tests/TestBottomTabs/index.tsx | 2 + apps/src/tests/TestBottomTabs/tabs/Tab4.tsx | 3 +- ios/RNSEnums.h | 11 ++++ ios/RNSOrientationProviding.h | 11 ++++ ios/RNSScreen.h | 3 +- ios/RNSScreen.mm | 13 +++-- ios/RNSScreenStack.h | 4 +- ios/RNSScreenStack.mm | 10 ++++ ios/UIViewController+RNScreens.mm | 26 ++++++++- .../RNSBottomTabsScreenComponentView.h | 1 + .../RNSBottomTabsScreenComponentView.mm | 6 +++ ios/bottom-tabs/RNSTabBarController.h | 3 +- ios/bottom-tabs/RNSTabBarController.mm | 13 +++++ .../RNSTabBarControllerDelegate.mm | 4 +- ios/bottom-tabs/RNSTabsScreenViewController.h | 3 +- .../RNSTabsScreenViewController.mm | 14 +++++ ios/conversion/RNSConversions-BottomTabs.mm | 25 +++++++++ ios/conversion/RNSConversions.h | 9 ++++ ios/conversion/RNSConversions.mm | 54 +++++++++++++++++++ .../BottomTabsScreenNativeComponent.ts | 11 ++++ 20 files changed, 214 insertions(+), 12 deletions(-) create mode 100644 ios/RNSOrientationProviding.h create mode 100644 ios/conversion/RNSConversions.mm diff --git a/apps/src/tests/TestBottomTabs/index.tsx b/apps/src/tests/TestBottomTabs/index.tsx index 75ecad6ddf..974c84bc3e 100644 --- a/apps/src/tests/TestBottomTabs/index.tsx +++ b/apps/src/tests/TestBottomTabs/index.tsx @@ -53,6 +53,7 @@ const TAB_CONFIGS: TabConfiguration[] = [ tabBarItemIconColor: Colors.RedDark120, iconResourceName: 'sym_call_missed', // Android specific title: 'Tab2', + orientation: 'landscape', }, contentViewRenderFn: Tab2, }, @@ -85,6 +86,7 @@ const TAB_CONFIGS: TabConfiguration[] = [ iconResourceName: 'sym_action_chat', // Android specific title: 'Tab4', badgeValue: '', + orientation: 'portrait', }, contentViewRenderFn: Tab4, }, diff --git a/apps/src/tests/TestBottomTabs/tabs/Tab4.tsx b/apps/src/tests/TestBottomTabs/tabs/Tab4.tsx index acb3bed5ab..30681f73fa 100644 --- a/apps/src/tests/TestBottomTabs/tabs/Tab4.tsx +++ b/apps/src/tests/TestBottomTabs/tabs/Tab4.tsx @@ -113,13 +113,14 @@ export function Tab4() { @@ -33,7 +34,7 @@ namespace react = facebook::react; @class RNSScreenView; -@interface RNSScreen : UIViewController +@interface RNSScreen : UIViewController - (instancetype)initWithView:(UIView *)view; - (UIViewController *)findChildVCForConfigAndTrait:(RNSWindowTrait)trait includingModals:(BOOL)includingModals; diff --git a/ios/RNSScreen.mm b/ios/RNSScreen.mm index 5dfeab016b..83287ee761 100644 --- a/ios/RNSScreen.mm +++ b/ios/RNSScreen.mm @@ -33,6 +33,7 @@ #import "RNSScreenStackHeaderConfig.h" #import "RNSTabBarController.h" #import "RNSScrollViewHelper.h" +#import "RNSConversions.h" #import "RNSDefines.h" #import "UIView+RNSUtility.h" @@ -1173,11 +1174,11 @@ - (BOOL)shouldOverrideScrollViewContentInsetAdjustmentBehavior // RNSScreenView does not have a property to control this behavior. // It looks for parent that conforms to RNSScrollViewBehaviorOverriding to determine // if it should override ScrollView's behavior. - + // As this method is called when RNSScreen willMoveToParentViewController // and view does not have superView yet, we need to use reactSuperViews. UIView *parent = [self reactSuperview]; - + while (parent != nil) { if ([parent respondsToSelector:@selector(shouldOverrideScrollViewContentInsetAdjustmentBehavior)]) { id overrideProvider = static_cast>(parent); @@ -1185,7 +1186,7 @@ - (BOOL)shouldOverrideScrollViewContentInsetAdjustmentBehavior } parent = [parent reactSuperview]; } - + return NO; } @@ -1981,6 +1982,12 @@ - (void)presentViewController:(UIViewController *)viewControllerToPresent [super presentViewController:viewControllerToPresent animated:flag completion:completion]; } +#pragma mark - RNSOrientationProviding +- (RNSOrientation)evaluateOrientation +{ + return rnscreens::conversion::RNSOrientationFromUIInterfaceOrientationMask([self supportedInterfaceOrientations]); +} + #ifdef RCT_NEW_ARCH_ENABLED #pragma mark - Fabric specific diff --git a/ios/RNSScreenStack.h b/ios/RNSScreenStack.h index f3a236384e..ce310f36b3 100644 --- a/ios/RNSScreenStack.h +++ b/ios/RNSScreenStack.h @@ -7,11 +7,11 @@ #import "RNSBottomTabsSpecialEffectsSupporting.h" #import "RNSScreenContainer.h" +#import "RNSOrientationProviding.h" NS_ASSUME_NONNULL_BEGIN -@interface RNSNavigationController - : UINavigationController +@interface RNSNavigationController : UINavigationController @end diff --git a/ios/RNSScreenStack.mm b/ios/RNSScreenStack.mm index cc59b260b8..74df007936 100644 --- a/ios/RNSScreenStack.mm +++ b/ios/RNSScreenStack.mm @@ -100,6 +100,16 @@ - (UIInterfaceOrientationMask)supportedInterfaceOrientations return [self topViewController].supportedInterfaceOrientations; } +- (RNSOrientation)evaluateOrientation +{ + if ([self.topViewController respondsToSelector:@selector(evaluateOrientation)]) { + id top = static_cast>(self.topViewController); + return [top evaluateOrientation]; + } + + return RNSOrientationInherit; +} + - (UIViewController *)childViewControllerForHomeIndicatorAutoHidden { return [self topViewController]; diff --git a/ios/UIViewController+RNScreens.mm b/ios/UIViewController+RNScreens.mm index 3c52f3aad3..2b401072e3 100644 --- a/ios/UIViewController+RNScreens.mm +++ b/ios/UIViewController+RNScreens.mm @@ -1,5 +1,8 @@ #import "RNSScreenContainer.h" #import "UIViewController+RNScreens.h" +#import "RNSOrientationProviding.h" +#import "RNSEnums.h" +#import "RNSConversions.h" #import @@ -27,8 +30,18 @@ - (UIStatusBarAnimation)reactNativeScreensPreferredStatusBarUpdateAnimation - (UIInterfaceOrientationMask)reactNativeScreensSupportedInterfaceOrientations { - UIViewController *childVC = [self findChildRNSScreensViewController]; - return childVC ? childVC.supportedInterfaceOrientations : [self reactNativeScreensSupportedInterfaceOrientations]; + id childOrientationProvidingVC = [self findChildRNSOrientationProvidingViewController]; + + if (childOrientationProvidingVC != nil) { + RNSOrientation orientation = [childOrientationProvidingVC evaluateOrientation]; + if (orientation == RNSOrientationInherit) { + return [[UIApplication sharedApplication] supportedInterfaceOrientationsForWindow:self.view.window]; + } + + return rnscreens::conversion::UIInterfaceOrientationMaskFromRNSOrientation(orientation); + } + + return [self reactNativeScreensSupportedInterfaceOrientations]; } - (UIViewController *)reactNativeScreensChildViewControllerForHomeIndicatorAutoHidden @@ -37,6 +50,15 @@ - (UIViewController *)reactNativeScreensChildViewControllerForHomeIndicatorAutoH return childVC ?: [self reactNativeScreensChildViewControllerForHomeIndicatorAutoHidden]; } +- (id)findChildRNSOrientationProvidingViewController +{ + UIViewController *lastViewController = [[self childViewControllers] lastObject]; + if ([lastViewController respondsToSelector:@selector(evaluateOrientation)]) { + return static_cast>(lastViewController); + } + return nil; +} + - (UIViewController *)findChildRNSScreensViewController { UIViewController *lastViewController = [[self childViewControllers] lastObject]; diff --git a/ios/bottom-tabs/RNSBottomTabsScreenComponentView.h b/ios/bottom-tabs/RNSBottomTabsScreenComponentView.h index fcf425953f..e0972a7c79 100644 --- a/ios/bottom-tabs/RNSBottomTabsScreenComponentView.h +++ b/ios/bottom-tabs/RNSBottomTabsScreenComponentView.h @@ -72,6 +72,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, nullable) UIColor *tabBarItemBadgeBackgroundColor; @property (nonatomic, nullable) NSString *title; +@property (nonatomic, readonly) RNSOrientation orientation; @property (nonatomic) BOOL shouldUseRepeatedTabSelectionPopToRootSpecialEffect; @property (nonatomic) BOOL shouldUseRepeatedTabSelectionScrollToTopSpecialEffect; diff --git a/ios/bottom-tabs/RNSBottomTabsScreenComponentView.mm b/ios/bottom-tabs/RNSBottomTabsScreenComponentView.mm index 9fd4afc3b7..028bb4a6a8 100644 --- a/ios/bottom-tabs/RNSBottomTabsScreenComponentView.mm +++ b/ios/bottom-tabs/RNSBottomTabsScreenComponentView.mm @@ -72,6 +72,7 @@ - (void)resetProps _badgeValue = nil; _title = nil; _tabBarBlurEffect = RNSBlurEffectStyleSystemDefault; + _orientation = RNSOrientationInherit; _tabBarBackgroundColor = nil; _tabBarItemTitleFontFamily = nil; @@ -158,6 +159,11 @@ - (void)updateProps:(const facebook::react::Props::Shared &)props _controller.title = _title; } + if (newComponentProps.orientation != oldComponentProps.orientation) { + _orientation = rnscreens::conversion::RNSOrientationFromRNSBottomTabsScreenOrientation(newComponentProps.orientation); + // TODO: force orientation + } + if (newComponentProps.tabKey != oldComponentProps.tabKey) { RCTAssert(!newComponentProps.tabKey.empty(), @"[RNScreens] tabKey must not be empty!"); _tabKey = RCTNSStringFromString(newComponentProps.tabKey); diff --git a/ios/bottom-tabs/RNSTabBarController.h b/ios/bottom-tabs/RNSTabBarController.h index 8c79a71de2..ecda7f2fca 100644 --- a/ios/bottom-tabs/RNSTabBarController.h +++ b/ios/bottom-tabs/RNSTabBarController.h @@ -1,6 +1,7 @@ #import #import "RNSTabBarAppearanceCoordinator.h" #import "RNSTabsScreenViewController.h" +#import "RNSOrientationProviding.h" NS_ASSUME_NONNULL_BEGIN @@ -21,7 +22,7 @@ NS_ASSUME_NONNULL_BEGIN * i.e. if you made changes through one of signals method, unless you flush them immediately (not needed atm), they will * be executed only after react finishes the transaction (from within transaction execution block). */ -@interface RNSTabBarController : UITabBarController +@interface RNSTabBarController : UITabBarController - (instancetype)initWithTabsHostComponentView:(nullable RNSBottomTabsHostComponentView *)tabsHostComponentView; diff --git a/ios/bottom-tabs/RNSTabBarController.mm b/ios/bottom-tabs/RNSTabBarController.mm index 8c2b0c785d..2db42154d6 100644 --- a/ios/bottom-tabs/RNSTabBarController.mm +++ b/ios/bottom-tabs/RNSTabBarController.mm @@ -1,6 +1,7 @@ #import "RNSTabBarController.h" #import #import +#import "RNSScreenWindowTraits.h" @implementation RNSTabBarController { NSArray *_Nullable _tabScreenControllers; @@ -141,6 +142,7 @@ - (void)updateSelectedViewController [selectedViewController.tabScreenComponentView overrideScrollViewBehaviorInFirstDescendantChainIfNeeded]; [self setSelectedViewController:selectedViewController]; + [RNSScreenWindowTraits enforceDesiredDeviceOrientation]; } - (void)updateTabBarAppearanceIfNeeded @@ -203,4 +205,15 @@ - (void)scheduleControllerUpdateIfNeeded #endif // !RCT_NEW_ARCH_ENABLED +#pragma mark - RNSOrientationProviding +- (RNSOrientation)evaluateOrientation +{ + if ([self.selectedViewController respondsToSelector:@selector(evaluateOrientation)]) { + id selected = static_cast>(self.selectedViewController); + return [selected evaluateOrientation]; + } + + return RNSOrientationInherit; +} + @end diff --git a/ios/bottom-tabs/RNSTabBarControllerDelegate.mm b/ios/bottom-tabs/RNSTabBarControllerDelegate.mm index 6559f1f2a6..d37fdcb694 100644 --- a/ios/bottom-tabs/RNSTabBarControllerDelegate.mm +++ b/ios/bottom-tabs/RNSTabBarControllerDelegate.mm @@ -32,11 +32,13 @@ - (BOOL)tabBarController:(UITabBarController *)tabBarController if (!repeatedSelectionHandledNatively) { [tabBarCtrl.tabsHostComponentView emitOnNativeFocusChangeRequestSelectedTabScreen:tabScreenCtrl.tabScreenComponentView]; - + // TODO: handle overrideScrollViewBehaviorInFirstDescendantChainIfNeeded for natively-driven tabs return ![self shouldPreventNativeTabChangeWithinTabBarController:tabBarCtrl]; } + // TODO: handle enforcing orientation with natively-driven tabs + // As we're selecting the same controller, returning both true and false works here. return true; } diff --git a/ios/bottom-tabs/RNSTabsScreenViewController.h b/ios/bottom-tabs/RNSTabsScreenViewController.h index 0b2438fc00..a5440f7d5b 100644 --- a/ios/bottom-tabs/RNSTabsScreenViewController.h +++ b/ios/bottom-tabs/RNSTabsScreenViewController.h @@ -1,10 +1,11 @@ #import #import "RNSBottomTabsScreenComponentView.h" #import "RNSBottomTabsSpecialEffectsSupporting.h" +#import "RNSOrientationProviding.h" NS_ASSUME_NONNULL_BEGIN -@interface RNSTabsScreenViewController : UIViewController +@interface RNSTabsScreenViewController : UIViewController @property (nonatomic, strong, readonly, nullable) RNSBottomTabsScreenComponentView *tabScreenComponentView; @property (nonatomic, weak, readonly, nullable) id tabsSpecialEffectsDelegate; diff --git a/ios/bottom-tabs/RNSTabsScreenViewController.mm b/ios/bottom-tabs/RNSTabsScreenViewController.mm index 1943c722d0..0e9efce02a 100644 --- a/ios/bottom-tabs/RNSTabsScreenViewController.mm +++ b/ios/bottom-tabs/RNSTabsScreenViewController.mm @@ -102,4 +102,18 @@ - (bool)tabScreenSelectedRepeatedly return false; } +- (RNSOrientation)evaluateOrientation +{ + if ([self.childViewControllers.lastObject respondsToSelector:@selector(evaluateOrientation)]) { + id child = static_cast>(self.childViewControllers.lastObject); + RNSOrientation childOrientation = [child evaluateOrientation]; + + if (childOrientation != RNSOrientationInherit) { + return childOrientation; + } + } + + return self.tabScreenComponentView.orientation; +} + @end diff --git a/ios/conversion/RNSConversions-BottomTabs.mm b/ios/conversion/RNSConversions-BottomTabs.mm index de6e616e80..ddb205b9f8 100644 --- a/ios/conversion/RNSConversions-BottomTabs.mm +++ b/ios/conversion/RNSConversions-BottomTabs.mm @@ -287,4 +287,29 @@ RNSBottomTabsIconType RNSBottomTabsIconTypeFromIcon(react::RNSBottomTabsScreenIc return iconImageSource; } +RNSOrientation RNSOrientationFromRNSBottomTabsScreenOrientation( + react::RNSBottomTabsScreenOrientation orientation) +{ + using enum facebook::react::RNSBottomTabsScreenOrientation; + + switch (orientation) { + case Inherit: + return RNSOrientationInherit; + case All: + return RNSOrientationAll; + case AllButUpsideDown: + return RNSOrientationAllButUpsideDown; + case Portrait: + return RNSOrientationPortrait; + case PortraitUpsideDown: + return RNSOrientationPortraitUpsideDown; + case Landscape: + return RNSOrientationLandscape; + case LandscapeLeft: + return RNSOrientationLandscapeLeft; + case LandscapeRight: + return RNSOrientationLandscapeRight; + } +} + }; // namespace rnscreens::conversion diff --git a/ios/conversion/RNSConversions.h b/ios/conversion/RNSConversions.h index 1e03834733..674c2db140 100644 --- a/ios/conversion/RNSConversions.h +++ b/ios/conversion/RNSConversions.h @@ -55,6 +55,15 @@ RCTImageSource *RCTImageSourceFromImageSourceAndIconType( const facebook::react::ImageSource *imageSource, RNSBottomTabsIconType iconType); +RNSOrientation RNSOrientationFromRNSBottomTabsScreenOrientation( + react::RNSBottomTabsScreenOrientation orientation); + +UIInterfaceOrientationMask UIInterfaceOrientationMaskFromRNSOrientation( + RNSOrientation orientation); + +RNSOrientation RNSOrientationFromUIInterfaceOrientationMask( + UIInterfaceOrientationMask orientationMask); + #pragma mark SplitViewHost props UISplitViewControllerSplitBehavior SplitViewPreferredSplitBehaviorFromHostProp( diff --git a/ios/conversion/RNSConversions.mm b/ios/conversion/RNSConversions.mm new file mode 100644 index 0000000000..0befb1282b --- /dev/null +++ b/ios/conversion/RNSConversions.mm @@ -0,0 +1,54 @@ +#import "RNSConversions.h" +#import + +namespace rnscreens::conversion { + +UIInterfaceOrientationMask UIInterfaceOrientationMaskFromRNSOrientation(RNSOrientation orientation) +{ + switch (orientation) { + case RNSOrientationAll: + return UIInterfaceOrientationMaskAll; + case RNSOrientationAllButUpsideDown: + return UIInterfaceOrientationMaskAllButUpsideDown; + case RNSOrientationPortrait: + return UIInterfaceOrientationMaskPortrait; + case RNSOrientationPortraitUpsideDown: + return UIInterfaceOrientationMaskPortraitUpsideDown; + case RNSOrientationLandscape: + return UIInterfaceOrientationMaskLandscape; + case RNSOrientationLandscapeLeft: + return UIInterfaceOrientationMaskLandscapeLeft; + case RNSOrientationLandscapeRight: + return UIInterfaceOrientationMaskLandscapeRight; + case RNSOrientationInherit: + RCTLogError(@"[RNScreens] RNSOrientationInherit does not map directly to any UIInterfaceOrientationMask"); + return 0; + default: + RCTLogError(@"[RNScreens] unsupported orientaion"); + return 0; + } +} + +RNSOrientation RNSOrientationFromUIInterfaceOrientationMask(UIInterfaceOrientationMask orientationMask) { + switch (orientationMask) { + case UIInterfaceOrientationMaskAll: + return RNSOrientationAll; + case UIInterfaceOrientationMaskAllButUpsideDown: + return RNSOrientationAllButUpsideDown; + case UIInterfaceOrientationMaskLandscape: + return RNSOrientationLandscape; + case UIInterfaceOrientationMaskLandscapeLeft: + return RNSOrientationLandscapeLeft; + case UIInterfaceOrientationLandscapeRight: + return RNSOrientationLandscapeRight; + case UIInterfaceOrientationMaskPortraitUpsideDown: + return RNSOrientationPortraitUpsideDown; + case UIInterfaceOrientationMaskPortrait: + return RNSOrientationPortrait; + default: + RCTLogError(@"[RNScreens] unsupported orientation mask"); + return RNSOrientationInherit; + } +} + +}; // namespace rnscreens::conversion diff --git a/src/fabric/bottom-tabs/BottomTabsScreenNativeComponent.ts b/src/fabric/bottom-tabs/BottomTabsScreenNativeComponent.ts index 144a381c7e..0b780e8298 100644 --- a/src/fabric/bottom-tabs/BottomTabsScreenNativeComponent.ts +++ b/src/fabric/bottom-tabs/BottomTabsScreenNativeComponent.ts @@ -47,6 +47,16 @@ type BlurEffect = | 'systemThickMaterialDark' | 'systemChromeMaterialDark'; +export type Orientation = + | 'inherit' + | 'all' + | 'allButUpsideDown' + | 'portrait' + | 'portraitUpsideDown' + | 'landscape' + | 'landscapeLeft' + | 'landscapeRight'; + export interface NativeProps extends ViewProps { // Events onLifecycleStateChange?: DirectEventHandler; @@ -80,6 +90,7 @@ export interface NativeProps extends ViewProps { // General title?: string | undefined | null; + orientation?: WithDefault; // Android-specific image handling iconResourceName?: string; From 20a021a10e2e492940f3bcc973d4d5eaeed59519 Mon Sep 17 00:00:00 2001 From: kligarski Date: Tue, 15 Jul 2025 11:06:12 +0200 Subject: [PATCH 02/17] handle updating prop --- .../RNSBottomTabsScreenComponentView.mm | 7 +++++- ios/bottom-tabs/RNSTabBarController.h | 22 +++++++++++++++++++ ios/bottom-tabs/RNSTabBarController.mm | 22 +++++++++++++++++-- ios/bottom-tabs/RNSTabsScreenViewController.h | 9 ++++++-- .../RNSTabsScreenViewController.mm | 5 +++++ 5 files changed, 60 insertions(+), 5 deletions(-) diff --git a/ios/bottom-tabs/RNSBottomTabsScreenComponentView.mm b/ios/bottom-tabs/RNSBottomTabsScreenComponentView.mm index 028bb4a6a8..5ed42f3141 100644 --- a/ios/bottom-tabs/RNSBottomTabsScreenComponentView.mm +++ b/ios/bottom-tabs/RNSBottomTabsScreenComponentView.mm @@ -153,6 +153,7 @@ - (void)updateProps:(const facebook::react::Props::Shared &)props const auto &newComponentProps = *std::static_pointer_cast(props); bool tabItemNeedsAppearanceUpdate{false}; + bool tabScreenOrientationNeedsUpdate{false}; if (newComponentProps.title != oldComponentProps.title) { _title = RCTNSStringFromStringNilIfEmpty(newComponentProps.title); @@ -161,7 +162,7 @@ - (void)updateProps:(const facebook::react::Props::Shared &)props if (newComponentProps.orientation != oldComponentProps.orientation) { _orientation = rnscreens::conversion::RNSOrientationFromRNSBottomTabsScreenOrientation(newComponentProps.orientation); - // TODO: force orientation + tabScreenOrientationNeedsUpdate = YES; } if (newComponentProps.tabKey != oldComponentProps.tabKey) { @@ -296,6 +297,10 @@ - (void)updateProps:(const facebook::react::Props::Shared &)props if (tabItemNeedsAppearanceUpdate) { [_controller tabItemAppearanceHasChanged]; } + + if (tabScreenOrientationNeedsUpdate) { + [_controller tabScreenOrientationHasChanged]; + } [super updateProps:props oldProps:oldProps]; } diff --git a/ios/bottom-tabs/RNSTabBarController.h b/ios/bottom-tabs/RNSTabBarController.h index ecda7f2fca..53a0fa2f40 100644 --- a/ios/bottom-tabs/RNSTabBarController.h +++ b/ios/bottom-tabs/RNSTabBarController.h @@ -90,6 +90,23 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)updateTabBarAppearance; +/** + * Updates the interface orientation based on selected tab screen and its children. + * + * This method does nothing if the update has not been previoulsy requested. + * If needed, the requested update is performed immediately. If you do not need this, consider just raising an + * appropriate invalidation signal & let the controller decide when to flush the updates. + */ +- (void)updateOrientationIfNeeded; + +/** + * Updates the interface orientation based on selected tab screen and its children. + * + * The requested update is performed immediately. If you do not need this, consider just raising an appropriate + * invalidation signal & let the controller decide when to flush the updates. + */ +- (void)updateOrientation; + @end #pragma mark - Signals @@ -128,6 +145,11 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, readwrite) bool needsUpdateOfTabBarAppearance; +/** + * Tell the controller that some configuration regarding interface orientation has changed & it requires update. + */ +@property (nonatomic, readwrite) bool needsUpdateOfOrientation; + @end NS_ASSUME_NONNULL_END diff --git a/ios/bottom-tabs/RNSTabBarController.mm b/ios/bottom-tabs/RNSTabBarController.mm index 2db42154d6..0704b7b905 100644 --- a/ios/bottom-tabs/RNSTabBarController.mm +++ b/ios/bottom-tabs/RNSTabBarController.mm @@ -71,6 +71,11 @@ - (void)setNeedsUpdateOfTabBarAppearance:(bool)needsUpdateOfTabBarAppearance #endif // !RCT_NEW_ARCH_ENABLED } +- (void)setNeedsUpdateOfOrientation:(bool)needsUpdateOfOrientation +{ + _needsUpdateOfOrientation = needsUpdateOfOrientation; +} + #pragma mark-- RNSReactTransactionObserving - (void)reactMountingTransactionWillMount @@ -84,6 +89,7 @@ - (void)reactMountingTransactionDidMount [self updateReactChildrenControllersIfNeeded]; [self updateSelectedViewControllerIfNeeded]; [self updateTabBarAppearanceIfNeeded]; + [self updateOrientationIfNeeded]; } #pragma mark-- Signals related @@ -142,7 +148,7 @@ - (void)updateSelectedViewController [selectedViewController.tabScreenComponentView overrideScrollViewBehaviorInFirstDescendantChainIfNeeded]; [self setSelectedViewController:selectedViewController]; - [RNSScreenWindowTraits enforceDesiredDeviceOrientation]; + [self updateOrientation]; } - (void)updateTabBarAppearanceIfNeeded @@ -205,6 +211,18 @@ - (void)scheduleControllerUpdateIfNeeded #endif // !RCT_NEW_ARCH_ENABLED +- (void)updateOrientationIfNeeded +{ + if (_needsUpdateOfOrientation) { + [self updateOrientation]; + } +} + +- (void)updateOrientation +{ + [RNSScreenWindowTraits enforceDesiredDeviceOrientation]; +} + #pragma mark - RNSOrientationProviding - (RNSOrientation)evaluateOrientation { @@ -212,7 +230,7 @@ - (RNSOrientation)evaluateOrientation id selected = static_cast>(self.selectedViewController); return [selected evaluateOrientation]; } - + return RNSOrientationInherit; } diff --git a/ios/bottom-tabs/RNSTabsScreenViewController.h b/ios/bottom-tabs/RNSTabsScreenViewController.h index a5440f7d5b..575e1df516 100644 --- a/ios/bottom-tabs/RNSTabsScreenViewController.h +++ b/ios/bottom-tabs/RNSTabsScreenViewController.h @@ -21,8 +21,13 @@ NS_ASSUME_NONNULL_BEGIN - (void)tabItemAppearanceHasChanged; /** - * Tell the controller that the tab item related to this controller has been selected again after being presented. - * Returns boolean indicating whether the action has been handled. + * Tell the controller that the tab screen it owns has got its react-props-orientation changed. + */ +- (void)tabScreenOrientationHasChanged; + +/** +* Tell the controller that the tab item related to this controller has been selected again after being presented. +* Returns boolean indicating whether the action has been handled. */ - (bool)tabScreenSelectedRepeatedly; diff --git a/ios/bottom-tabs/RNSTabsScreenViewController.mm b/ios/bottom-tabs/RNSTabsScreenViewController.mm index 0e9efce02a..474b23a4aa 100644 --- a/ios/bottom-tabs/RNSTabsScreenViewController.mm +++ b/ios/bottom-tabs/RNSTabsScreenViewController.mm @@ -32,6 +32,11 @@ - (void)tabItemAppearanceHasChanged [[self findTabBarController] setNeedsUpdateOfTabBarAppearance:true]; } +- (void)tabScreenOrientationHasChanged +{ + [[self findTabBarController] setNeedsUpdateOfOrientation:true]; +} + - (void)viewWillAppear:(BOOL)animated { [self.tabScreenComponentView.reactEventEmitter emitOnWillAppear]; From 720071b83d4d540bddbd21f275b3bbb5d753fddc Mon Sep 17 00:00:00 2001 From: kligarski Date: Tue, 15 Jul 2025 11:17:38 +0200 Subject: [PATCH 03/17] build on Android Fabric --- .../swmansion/rnscreens/gamma/tabs/TabScreenViewManager.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/TabScreenViewManager.kt b/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/TabScreenViewManager.kt index 9d35d8d31a..19315c7d4c 100644 --- a/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/TabScreenViewManager.kt +++ b/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/TabScreenViewManager.kt @@ -187,6 +187,11 @@ class TabScreenViewManager : view.iconResourceName = value } + override fun setOrientation( + view: TabScreen, + value: String?, + ) = Unit + companion object { const val REACT_CLASS = "RNSBottomTabsScreen" } From f767418d58c72f484eeb091273707267be199a1c Mon Sep 17 00:00:00 2001 From: kligarski Date: Tue, 15 Jul 2025 11:22:31 +0200 Subject: [PATCH 04/17] build on Android Paper --- .../react/viewmanagers/RNSBottomTabsScreenManagerDelegate.java | 3 +++ .../viewmanagers/RNSBottomTabsScreenManagerInterface.java | 1 + 2 files changed, 4 insertions(+) diff --git a/android/src/paper/java/com/facebook/react/viewmanagers/RNSBottomTabsScreenManagerDelegate.java b/android/src/paper/java/com/facebook/react/viewmanagers/RNSBottomTabsScreenManagerDelegate.java index 35a2414ae7..72f7e4d9e6 100644 --- a/android/src/paper/java/com/facebook/react/viewmanagers/RNSBottomTabsScreenManagerDelegate.java +++ b/android/src/paper/java/com/facebook/react/viewmanagers/RNSBottomTabsScreenManagerDelegate.java @@ -63,6 +63,9 @@ public void setProperty(T view, String propName, @Nullable Object value) { case "title": mViewManager.setTitle(view, value == null ? null : (String) value); break; + case "orientation": + mViewManager.setOrientation(view, (String) value); + break; case "iconResourceName": mViewManager.setIconResourceName(view, value == null ? null : (String) value); break; diff --git a/android/src/paper/java/com/facebook/react/viewmanagers/RNSBottomTabsScreenManagerInterface.java b/android/src/paper/java/com/facebook/react/viewmanagers/RNSBottomTabsScreenManagerInterface.java index 432261a5a8..09997f3023 100644 --- a/android/src/paper/java/com/facebook/react/viewmanagers/RNSBottomTabsScreenManagerInterface.java +++ b/android/src/paper/java/com/facebook/react/viewmanagers/RNSBottomTabsScreenManagerInterface.java @@ -28,6 +28,7 @@ public interface RNSBottomTabsScreenManagerInterface { void setTabBarItemIconColor(T view, @Nullable Integer value); void setTabBarItemBadgeBackgroundColor(T view, @Nullable Integer value); void setTitle(T view, @Nullable String value); + void setOrientation(T view, @Nullable String value); void setIconResourceName(T view, @Nullable String value); void setTabBarItemBadgeTextColor(T view, @Nullable Integer value); void setIconType(T view, @Nullable String value); From c0a1608762bcfb08e769533240cbe445a1471d02 Mon Sep 17 00:00:00 2001 From: kligarski Date: Wed, 16 Jul 2025 13:25:47 +0200 Subject: [PATCH 05/17] apply suggestions --- ios/RNSScreen.mm | 1 + ios/bottom-tabs/RNSTabBarController.h | 14 +++++++------- ios/bottom-tabs/RNSTabBarController.mm | 7 ++++--- ios/bottom-tabs/RNSTabsScreenViewController.mm | 2 +- ios/conversion/RNSConversions.mm | 4 ++-- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/ios/RNSScreen.mm b/ios/RNSScreen.mm index 83287ee761..25d39a9f69 100644 --- a/ios/RNSScreen.mm +++ b/ios/RNSScreen.mm @@ -1983,6 +1983,7 @@ - (void)presentViewController:(UIViewController *)viewControllerToPresent } #pragma mark - RNSOrientationProviding + - (RNSOrientation)evaluateOrientation { return rnscreens::conversion::RNSOrientationFromUIInterfaceOrientationMask([self supportedInterfaceOrientations]); diff --git a/ios/bottom-tabs/RNSTabBarController.h b/ios/bottom-tabs/RNSTabBarController.h index 53a0fa2f40..cbefad5505 100644 --- a/ios/bottom-tabs/RNSTabBarController.h +++ b/ios/bottom-tabs/RNSTabBarController.h @@ -59,7 +59,7 @@ NS_ASSUME_NONNULL_BEGIN /** * Find out which tab bar controller is currently focused & select it. * - * This method does nothing if the update has not been previoulsy requested. + * This method does nothing if the update has not been previously requested. * If needed, the requested update is performed immediately. If you do not need this, consider just raising an * appropriate invalidation signal & let the controller decide when to flush the updates. */ @@ -76,7 +76,7 @@ NS_ASSUME_NONNULL_BEGIN /** * Updates the tab bar appearance basing on configuration sources (host view, tab screens). * - * This method does nothing if the update has not been previoulsy requested. + * This method does nothing if the update has not been previously requested. * If needed, the requested update is performed immediately. If you do not need this, consider just raising an * appropriate invalidation signal & let the controller decide when to flush the updates. */ @@ -93,7 +93,7 @@ NS_ASSUME_NONNULL_BEGIN /** * Updates the interface orientation based on selected tab screen and its children. * - * This method does nothing if the update has not been previoulsy requested. + * This method does nothing if the update has not been previously requested. * If needed, the requested update is performed immediately. If you do not need this, consider just raising an * appropriate invalidation signal & let the controller decide when to flush the updates. */ @@ -119,7 +119,7 @@ NS_ASSUME_NONNULL_BEGIN /** * Tell the controller that react provided tabs have changed (count / instances) & the child view controllers need to be - * udpated. + * updated. * * This also automatically raises `needsReactChildrenUpdate` flag, no need to call it manually. */ @@ -127,7 +127,7 @@ NS_ASSUME_NONNULL_BEGIN /** * Tell the controller that react provided tabs have changed (count / instances) & the child view controllers need to be - * udpated. + * updated. * * Do not raise this signal only when focused state of the tab has changed - use `needsSelectedTabUpdate` instead. */ @@ -135,7 +135,7 @@ NS_ASSUME_NONNULL_BEGIN /** * Tell the controller that react provided tabs have changed (count / instances) & the child view controllers need to be - * udpated. + * updated. */ @property (nonatomic, readwrite) bool needsUpdateOfSelectedTab; @@ -148,7 +148,7 @@ NS_ASSUME_NONNULL_BEGIN /** * Tell the controller that some configuration regarding interface orientation has changed & it requires update. */ -@property (nonatomic, readwrite) bool needsUpdateOfOrientation; +@property (nonatomic, readwrite) bool needsOrientationUpdate; @end diff --git a/ios/bottom-tabs/RNSTabBarController.mm b/ios/bottom-tabs/RNSTabBarController.mm index 0704b7b905..096e050e8b 100644 --- a/ios/bottom-tabs/RNSTabBarController.mm +++ b/ios/bottom-tabs/RNSTabBarController.mm @@ -71,9 +71,9 @@ - (void)setNeedsUpdateOfTabBarAppearance:(bool)needsUpdateOfTabBarAppearance #endif // !RCT_NEW_ARCH_ENABLED } -- (void)setNeedsUpdateOfOrientation:(bool)needsUpdateOfOrientation +- (void)setNeedsOrientationUpdate:(bool)needsOrientationUpdate { - _needsUpdateOfOrientation = needsUpdateOfOrientation; + _needsOrientationUpdate = needsOrientationUpdate; } #pragma mark-- RNSReactTransactionObserving @@ -213,7 +213,7 @@ - (void)scheduleControllerUpdateIfNeeded - (void)updateOrientationIfNeeded { - if (_needsUpdateOfOrientation) { + if (_needsOrientationUpdate) { [self updateOrientation]; } } @@ -224,6 +224,7 @@ - (void)updateOrientation } #pragma mark - RNSOrientationProviding + - (RNSOrientation)evaluateOrientation { if ([self.selectedViewController respondsToSelector:@selector(evaluateOrientation)]) { diff --git a/ios/bottom-tabs/RNSTabsScreenViewController.mm b/ios/bottom-tabs/RNSTabsScreenViewController.mm index 474b23a4aa..e11883346c 100644 --- a/ios/bottom-tabs/RNSTabsScreenViewController.mm +++ b/ios/bottom-tabs/RNSTabsScreenViewController.mm @@ -34,7 +34,7 @@ - (void)tabItemAppearanceHasChanged - (void)tabScreenOrientationHasChanged { - [[self findTabBarController] setNeedsUpdateOfOrientation:true]; + [[self findTabBarController] setNeedsOrientationUpdate:true]; } - (void)viewWillAppear:(BOOL)animated diff --git a/ios/conversion/RNSConversions.mm b/ios/conversion/RNSConversions.mm index 0befb1282b..36987df3b1 100644 --- a/ios/conversion/RNSConversions.mm +++ b/ios/conversion/RNSConversions.mm @@ -24,7 +24,7 @@ UIInterfaceOrientationMask UIInterfaceOrientationMaskFromRNSOrientation(RNSOrien RCTLogError(@"[RNScreens] RNSOrientationInherit does not map directly to any UIInterfaceOrientationMask"); return 0; default: - RCTLogError(@"[RNScreens] unsupported orientaion"); + RCTLogError(@"[RNScreens] Unsupported orientation"); return 0; } } @@ -46,7 +46,7 @@ RNSOrientation RNSOrientationFromUIInterfaceOrientationMask(UIInterfaceOrientati case UIInterfaceOrientationMaskPortrait: return RNSOrientationPortrait; default: - RCTLogError(@"[RNScreens] unsupported orientation mask"); + RCTLogError(@"[RNScreens] Unsupported orientation mask"); return RNSOrientationInherit; } } From 50c6ad777023071b0584e56ddd06bc7371533155 Mon Sep 17 00:00:00 2001 From: kligarski Date: Wed, 16 Jul 2025 13:54:29 +0200 Subject: [PATCH 06/17] paper support --- ios/bottom-tabs/RCTConvert+RNSBottomTabs.h | 2 ++ ios/bottom-tabs/RCTConvert+RNSBottomTabs.mm | 15 +++++++++++++++ .../RNSBottomTabsScreenComponentView.mm | 15 ++++++++++++++- .../RNSBottomTabsScreenComponentViewManager.mm | 1 + ios/bottom-tabs/RNSTabBarController.mm | 4 ++++ 5 files changed, 36 insertions(+), 1 deletion(-) diff --git a/ios/bottom-tabs/RCTConvert+RNSBottomTabs.h b/ios/bottom-tabs/RCTConvert+RNSBottomTabs.h index 6b12361946..101104b8d5 100644 --- a/ios/bottom-tabs/RCTConvert+RNSBottomTabs.h +++ b/ios/bottom-tabs/RCTConvert+RNSBottomTabs.h @@ -11,6 +11,8 @@ NS_ASSUME_NONNULL_BEGIN + (RNSBottomTabsIconType)RNSBottomTabsIconType:(nonnull id)json; ++ (RNSOrientation)RNSOrientation:(nonnull id)json; + @end NS_ASSUME_NONNULL_END diff --git a/ios/bottom-tabs/RCTConvert+RNSBottomTabs.mm b/ios/bottom-tabs/RCTConvert+RNSBottomTabs.mm index 7a7e65b114..7d68c88c54 100644 --- a/ios/bottom-tabs/RCTConvert+RNSBottomTabs.mm +++ b/ios/bottom-tabs/RCTConvert+RNSBottomTabs.mm @@ -30,6 +30,21 @@ + (UIOffset)UIOffset:(id)json; RNSTabBarMinimizeBehaviorAutomatic, integerValue) +RCT_ENUM_CONVERTER( + RNSOrientation, + (@{ + @"inherit" : @(RNSOrientationInherit), + @"all" : @(RNSOrientationAll), + @"allButUpsideDown" : @(RNSOrientationAllButUpsideDown), + @"portrait" : @(RNSOrientationPortrait), + @"portraitUpsideDown" : @(RNSOrientationPortraitUpsideDown), + @"landscape" : @(RNSOrientationLandscape), + @"landscapeLeft" : @(RNSOrientationLandscapeLeft), + @"landscapeRight" : @(RNSOrientationLandscapeRight), + }), + RNSOrientationInherit, + integerValue) + @end #endif // !RCT_NEW_ARCH_ENABLED diff --git a/ios/bottom-tabs/RNSBottomTabsScreenComponentView.mm b/ios/bottom-tabs/RNSBottomTabsScreenComponentView.mm index 5ed42f3141..acd1b0caaf 100644 --- a/ios/bottom-tabs/RNSBottomTabsScreenComponentView.mm +++ b/ios/bottom-tabs/RNSBottomTabsScreenComponentView.mm @@ -30,6 +30,7 @@ @implementation RNSBottomTabsScreenComponentView { BOOL _isOverrideScrollViewContentInsetAdjustmentBehaviorSet; #if !RCT_NEW_ARCH_ENABLED BOOL _tabItemNeedsAppearanceUpdate; + BOOL _tabScreenOrientationNeedsUpdate; #endif // !RCT_NEW_ARCH_ENABLED } @@ -56,6 +57,7 @@ - (void)initState #if !RCT_NEW_ARCH_ENABLED _tabItemNeedsAppearanceUpdate = NO; + _tabScreenOrientationNeedsUpdate = NO; #endif // This is a temporary workaround to avoid UIScrollEdgeEffect glitch @@ -297,7 +299,7 @@ - (void)updateProps:(const facebook::react::Props::Shared &)props if (tabItemNeedsAppearanceUpdate) { [_controller tabItemAppearanceHasChanged]; } - + if (tabScreenOrientationNeedsUpdate) { [_controller tabScreenOrientationHasChanged]; } @@ -364,6 +366,11 @@ - (void)didSetProps:(NSArray *)changedProps [_controller tabItemAppearanceHasChanged]; _tabItemNeedsAppearanceUpdate = NO; } + + if (_tabScreenOrientationNeedsUpdate) { + [_controller tabScreenOrientationHasChanged]; + _tabScreenOrientationNeedsUpdate = NO; + } } #pragma mark - LEGACY prop setters @@ -491,6 +498,12 @@ - (void)setOverrideScrollViewContentInsetAdjustmentBehavior:(BOOL)overrideScroll // when the prop is undefined in JS and default value is used instead of calling this setter. } +- (void)setOrientation:(RNSOrientation)orientation +{ + _orientation = orientation; + _tabItemNeedsAppearanceUpdate = YES; +} + - (void)setOnWillAppear:(RCTDirectEventBlock)onWillAppear { [self.reactEventEmitter setOnWillAppear:onWillAppear]; diff --git a/ios/bottom-tabs/RNSBottomTabsScreenComponentViewManager.mm b/ios/bottom-tabs/RNSBottomTabsScreenComponentViewManager.mm index 56c6dca843..80215ee9b2 100644 --- a/ios/bottom-tabs/RNSBottomTabsScreenComponentViewManager.mm +++ b/ios/bottom-tabs/RNSBottomTabsScreenComponentViewManager.mm @@ -21,6 +21,7 @@ - (UIView *)view RCT_REMAP_VIEW_PROPERTY(isFocused, isSelectedScreen, BOOL); RCT_EXPORT_VIEW_PROPERTY(title, NSString); +RCT_EXPORT_VIEW_PROPERTY(orientation, RNSOrientation); RCT_EXPORT_VIEW_PROPERTY(badgeValue, NSString); RCT_EXPORT_VIEW_PROPERTY(tabBarBackgroundColor, UIColor); RCT_EXPORT_VIEW_PROPERTY(tabBarBlurEffect, RNSBlurEffectStyle); diff --git a/ios/bottom-tabs/RNSTabBarController.mm b/ios/bottom-tabs/RNSTabBarController.mm index 096e050e8b..f31d34a9e0 100644 --- a/ios/bottom-tabs/RNSTabBarController.mm +++ b/ios/bottom-tabs/RNSTabBarController.mm @@ -74,6 +74,10 @@ - (void)setNeedsUpdateOfTabBarAppearance:(bool)needsUpdateOfTabBarAppearance - (void)setNeedsOrientationUpdate:(bool)needsOrientationUpdate { _needsOrientationUpdate = needsOrientationUpdate; +#if !RCT_NEW_ARCH_ENABLED + [self scheduleControllerUpdateIfNeeded]; +#endif // !RCT_NEW_ARCH_ENABLED + } #pragma mark-- RNSReactTransactionObserving From 13c12a22c835333d4f621bad1a73a9906143d39c Mon Sep 17 00:00:00 2001 From: kligarski Date: Thu, 17 Jul 2025 14:36:29 +0200 Subject: [PATCH 07/17] formatting --- ios/bottom-tabs/RCTConvert+RNSBottomTabs.mm | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ios/bottom-tabs/RCTConvert+RNSBottomTabs.mm b/ios/bottom-tabs/RCTConvert+RNSBottomTabs.mm index 7d68c88c54..dfc00de3f0 100644 --- a/ios/bottom-tabs/RCTConvert+RNSBottomTabs.mm +++ b/ios/bottom-tabs/RCTConvert+RNSBottomTabs.mm @@ -33,14 +33,14 @@ + (UIOffset)UIOffset:(id)json; RCT_ENUM_CONVERTER( RNSOrientation, (@{ - @"inherit" : @(RNSOrientationInherit), - @"all" : @(RNSOrientationAll), - @"allButUpsideDown" : @(RNSOrientationAllButUpsideDown), - @"portrait" : @(RNSOrientationPortrait), - @"portraitUpsideDown" : @(RNSOrientationPortraitUpsideDown), - @"landscape" : @(RNSOrientationLandscape), - @"landscapeLeft" : @(RNSOrientationLandscapeLeft), - @"landscapeRight" : @(RNSOrientationLandscapeRight), + @"inherit": @(RNSOrientationInherit), + @"all": @(RNSOrientationAll), + @"allButUpsideDown": @(RNSOrientationAllButUpsideDown), + @"portrait": @(RNSOrientationPortrait), + @"portraitUpsideDown": @(RNSOrientationPortraitUpsideDown), + @"landscape": @(RNSOrientationLandscape), + @"landscapeLeft": @(RNSOrientationLandscapeLeft), + @"landscapeRight": @(RNSOrientationLandscapeRight), }), RNSOrientationInherit, integerValue) From 300884dcf06494a8ca83e2d32e071ad5f0fc2775 Mon Sep 17 00:00:00 2001 From: kligarski Date: Thu, 17 Jul 2025 15:03:00 +0200 Subject: [PATCH 08/17] support for tabs nested in screen-stack --- apps/src/tests/TestBottomTabs/index.tsx | 56 +++++++++++++++++++++++++ ios/RNSScreen.mm | 9 ++++ 2 files changed, 65 insertions(+) diff --git a/apps/src/tests/TestBottomTabs/index.tsx b/apps/src/tests/TestBottomTabs/index.tsx index 974c84bc3e..7acd15508a 100644 --- a/apps/src/tests/TestBottomTabs/index.tsx +++ b/apps/src/tests/TestBottomTabs/index.tsx @@ -11,6 +11,12 @@ import { } from '../../shared/gamma/containers/bottom-tabs/BottomTabsContainer'; import { Tab1, Tab2, Tab3, Tab4 } from './tabs'; import Colors from '../../shared/styling/Colors'; +// import { NavigationContainer, ParamListBase } from '@react-navigation/native'; +// import { +// NativeStackNavigationProp, +// createNativeStackNavigator, +// } from '@react-navigation/native-stack'; +// import { Button, View } from 'react-native'; enableFreeze(true); @@ -71,6 +77,7 @@ const TAB_CONFIGS: TabConfiguration[] = [ }, iconResourceName: 'sym_action_email', // Android specific title: 'Tab3', + orientation: 'portrait', }, contentViewRenderFn: Tab3, }, @@ -109,3 +116,52 @@ function App() { } export default App; + +// type RouteParamList = { +// Auth: undefined; +// Tabs: undefined; +// }; + +// type NavigationProp = { +// navigation: NativeStackNavigationProp; +// }; + +// type StackNavigationProp = NavigationProp; + +// const Stack = createNativeStackNavigator(); + +// function AuthScreen({ navigation }: StackNavigationProp) { +// return ( +// +//