Skip to content
Merged
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
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,40 @@ It will contain id of the given action.
|-------------------------|----------|
| ({nativeEvent}) => void | No |

### Events

#### `onCloseMenu`

Callback function that will be called when the menu is dismissed. This event fires at the start of the dismissal, before any animations complete.

| Type | Required |
|------------|----------|
| () => void | No |

#### `onOpenMenu`

Callback function that will be called when the menu is opened. This event fires right before the menu is displayed.

| Type | Required |
|------------|----------|
| () => void | No |

Example usage:
```jsx
<MenuView
onOpenMenu={() => {
console.log('Menu was opened');
}}
onCloseMenu={() => {
console.log('Menu was closed');
}}
// ... other props
>
<View>
<Text>Open Menu</Text>
</View>
</MenuView>
```
## Testing with Jest

In some cases, you might want to mock the package to test your components. You can do this by using the `jest.mock` function.
Expand Down
13 changes: 13 additions & 0 deletions android/src/main/java/com/reactnativemenu/MenuOnCloseEvent.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.reactnativemenu

import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.events.Event

class MenuOnCloseEvent(surfaceId: Int, viewId: Int, private val targetId: Int) : Event<MenuOnCloseEvent>(surfaceId, viewId) {
override fun getEventName() = "onCloseMenu"

override fun getEventData(): WritableMap? {
return Arguments.createMap()
}
}
13 changes: 13 additions & 0 deletions android/src/main/java/com/reactnativemenu/MenuOnOpenEvent.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.reactnativemenu

import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.events.Event

class MenuOnOpenEvent(surfaceId: Int, viewId: Int, private val targetId: Int) : Event<MenuOnOpenEvent>(surfaceId, viewId) {
override fun getEventName() = "onOpenMenu"

override fun getEventData(): WritableMap? {
return Arguments.createMap()
}
}
6 changes: 6 additions & 0 deletions android/src/main/java/com/reactnativemenu/MenuView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -261,8 +261,14 @@ class MenuView(private val mContext: ReactContext) : ReactViewGroup(mContext) {
}
mPopupMenu.setOnDismissListener {
mIsMenuDisplayed = false
val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(mContext, id)
val surfaceId: Int = UIManagerHelper.getSurfaceId(this)
dispatcher?.dispatchEvent(MenuOnCloseEvent(surfaceId, id, id))
}
mIsMenuDisplayed = true
val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(mContext, id)
val surfaceId: Int = UIManagerHelper.getSurfaceId(this)
dispatcher?.dispatchEvent(MenuOnOpenEvent(surfaceId, id, id))
mPopupMenu.show()
}
}
Expand Down
101 changes: 78 additions & 23 deletions android/src/main/java/com/reactnativemenu/MenuViewManagerBase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import com.facebook.react.views.view.ReactDrawableHelper
import com.facebook.react.views.view.ReactViewGroup
import com.facebook.yoga.YogaConstants

abstract class MenuViewManagerBase: ReactClippingViewManager<MenuView>() {
abstract class MenuViewManagerBase : ReactClippingViewManager<MenuView>() {
override fun getName() = "MenuView"

@ReactProp(name = "actions")
Expand All @@ -37,8 +37,12 @@ abstract class MenuViewManagerBase: ReactClippingViewManager<MenuView>() {

override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> {
return MapBuilder.of(
"onPressAction",
MapBuilder.of("registrationName", "onPressAction")
"onPressAction",
MapBuilder.of("registrationName", "onPressAction"),
"onCloseMenu",
MapBuilder.of("registrationName", "onCloseMenu"),
"onOpenMenu",
MapBuilder.of("registrationName", "onOpenMenu")
)
}

Expand Down Expand Up @@ -82,7 +86,19 @@ abstract class MenuViewManagerBase: ReactClippingViewManager<MenuView>() {
view.nextFocusUpId = viewId
}

@ReactPropGroup(names = [ViewProps.BORDER_RADIUS, ViewProps.BORDER_TOP_LEFT_RADIUS, ViewProps.BORDER_TOP_RIGHT_RADIUS, ViewProps.BORDER_BOTTOM_RIGHT_RADIUS, ViewProps.BORDER_BOTTOM_LEFT_RADIUS, ViewProps.BORDER_TOP_START_RADIUS, ViewProps.BORDER_TOP_END_RADIUS, ViewProps.BORDER_BOTTOM_START_RADIUS, ViewProps.BORDER_BOTTOM_END_RADIUS])
@ReactPropGroup(
names =
[
ViewProps.BORDER_RADIUS,
ViewProps.BORDER_TOP_LEFT_RADIUS,
ViewProps.BORDER_TOP_RIGHT_RADIUS,
ViewProps.BORDER_BOTTOM_RIGHT_RADIUS,
ViewProps.BORDER_BOTTOM_LEFT_RADIUS,
ViewProps.BORDER_TOP_START_RADIUS,
ViewProps.BORDER_TOP_END_RADIUS,
ViewProps.BORDER_BOTTOM_START_RADIUS,
ViewProps.BORDER_BOTTOM_END_RADIUS]
)
fun setBorderRadius(view: ReactViewGroup, index: Int, borderRadius: Float) {
var borderRadius = borderRadius
if (!YogaConstants.isUndefined(borderRadius) && borderRadius < 0) {
Expand All @@ -109,33 +125,60 @@ abstract class MenuViewManagerBase: ReactClippingViewManager<MenuView>() {
// We should keep using setters as `Val cannot be reassigned`
view.setHitSlopRect(null)
} else {
view.setHitSlopRect(Rect(
if (hitSlop.hasKey("left")) PixelUtil.toPixelFromDIP(hitSlop.getDouble("left")).toInt() else 0,
if (hitSlop.hasKey("top")) PixelUtil.toPixelFromDIP(hitSlop.getDouble("top")).toInt() else 0,
if (hitSlop.hasKey("right")) PixelUtil.toPixelFromDIP(hitSlop.getDouble("right")).toInt() else 0,
if (hitSlop.hasKey("bottom")) PixelUtil.toPixelFromDIP(hitSlop.getDouble("bottom")).toInt() else 0))
view.setHitSlopRect(
Rect(
if (hitSlop.hasKey("left"))
PixelUtil.toPixelFromDIP(hitSlop.getDouble("left")).toInt()
else 0,
if (hitSlop.hasKey("top"))
PixelUtil.toPixelFromDIP(hitSlop.getDouble("top")).toInt()
else 0,
if (hitSlop.hasKey("right"))
PixelUtil.toPixelFromDIP(hitSlop.getDouble("right")).toInt()
else 0,
if (hitSlop.hasKey("bottom"))
PixelUtil.toPixelFromDIP(hitSlop.getDouble("bottom")).toInt()
else 0
)
)
}
}

@ReactProp(name = "nativeBackgroundAndroid")
fun setNativeBackground(view: ReactViewGroup, @Nullable bg: ReadableMap?) {
view.setTranslucentBackgroundDrawable(
if (bg == null) null else ReactDrawableHelper.createDrawableFromJSDescription(view.context, bg))
if (bg == null) null
else ReactDrawableHelper.createDrawableFromJSDescription(view.context, bg)
)
}

@TargetApi(Build.VERSION_CODES.M)
@ReactProp(name = "nativeForegroundAndroid")
fun setNativeForeground(view: ReactViewGroup, @Nullable fg: ReadableMap?) {
view.foreground = if (fg == null) null else ReactDrawableHelper.createDrawableFromJSDescription(view.context, fg)
view.foreground =
if (fg == null) null
else ReactDrawableHelper.createDrawableFromJSDescription(view.context, fg)
}

@ReactProp(name = ViewProps.NEEDS_OFFSCREEN_ALPHA_COMPOSITING)
fun setNeedsOffscreenAlphaCompositing(
view: ReactViewGroup, needsOffscreenAlphaCompositing: Boolean) {
view: ReactViewGroup,
needsOffscreenAlphaCompositing: Boolean
) {
view.setNeedsOffscreenAlphaCompositing(needsOffscreenAlphaCompositing)
}

@ReactPropGroup(names = [ViewProps.BORDER_WIDTH, ViewProps.BORDER_LEFT_WIDTH, ViewProps.BORDER_RIGHT_WIDTH, ViewProps.BORDER_TOP_WIDTH, ViewProps.BORDER_BOTTOM_WIDTH, ViewProps.BORDER_START_WIDTH, ViewProps.BORDER_END_WIDTH])
@ReactPropGroup(
names =
[
ViewProps.BORDER_WIDTH,
ViewProps.BORDER_LEFT_WIDTH,
ViewProps.BORDER_RIGHT_WIDTH,
ViewProps.BORDER_TOP_WIDTH,
ViewProps.BORDER_BOTTOM_WIDTH,
ViewProps.BORDER_START_WIDTH,
ViewProps.BORDER_END_WIDTH]
)
fun setBorderWidth(view: ReactViewGroup, index: Int, width: Float) {
var width = width
if (!YogaConstants.isUndefined(width) && width < 0) {
Expand All @@ -147,7 +190,18 @@ abstract class MenuViewManagerBase: ReactClippingViewManager<MenuView>() {
view.setBorderWidth(SPACING_TYPES[index], width)
}

@ReactPropGroup(names = [ViewProps.BORDER_COLOR, ViewProps.BORDER_LEFT_COLOR, ViewProps.BORDER_RIGHT_COLOR, ViewProps.BORDER_TOP_COLOR, ViewProps.BORDER_BOTTOM_COLOR, ViewProps.BORDER_START_COLOR, ViewProps.BORDER_END_COLOR], customType = "Color")
@ReactPropGroup(
names =
[
ViewProps.BORDER_COLOR,
ViewProps.BORDER_LEFT_COLOR,
ViewProps.BORDER_RIGHT_COLOR,
ViewProps.BORDER_TOP_COLOR,
ViewProps.BORDER_BOTTOM_COLOR,
ViewProps.BORDER_START_COLOR,
ViewProps.BORDER_END_COLOR],
customType = "Color"
)
abstract fun setBorderColor(view: ReactViewGroup, index: Int, color: Int?)

@ReactProp(name = ViewProps.OVERFLOW)
Expand Down Expand Up @@ -181,14 +235,15 @@ abstract class MenuViewManagerBase: ReactClippingViewManager<MenuView>() {

companion object {
val COMMAND_SHOW = 1
val SPACING_TYPES = arrayOf(
Spacing.ALL,
Spacing.LEFT,
Spacing.RIGHT,
Spacing.TOP,
Spacing.BOTTOM,
Spacing.START,
Spacing.END
)
val SPACING_TYPES =
arrayOf(
Spacing.ALL,
Spacing.LEFT,
Spacing.RIGHT,
Spacing.TOP,
Spacing.BOTTOM,
Spacing.START,
Spacing.END
)
}
}
8 changes: 8 additions & 0 deletions ios/MenuViewManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ - (UIView *)view
* onPressAction: callback to be called once user selects an action
*/
RCT_EXPORT_VIEW_PROPERTY(onPressAction, RCTDirectEventBlock);
/**
* onCloseMenu: callback to be called when the menu is closed
*/
RCT_EXPORT_VIEW_PROPERTY(onCloseMenu, RCTDirectEventBlock);
/**
* onOpenMenu: callback to be called when the menu is opened
*/
RCT_EXPORT_VIEW_PROPERTY(onOpenMenu, RCTDirectEventBlock);
/**
* shouldOpenOnLongPress: determines whether menu should be opened after long press or normal press
*/
Expand Down
16 changes: 14 additions & 2 deletions ios/NewArch/FabricActionSheetView.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@

@objc(FabricActionSheetView)
public class FabricActionSheetView: ActionSheetView, FabricViewImplementationProtocol {
public var onPressAction: ((String) -> Void)?

public var onCloseMenu: (() -> Void)?
public var onOpenMenu: (() -> Void)?

@objc override func sendButtonAction(_ action: String) {
if let onPress = onPressAction {
onPress(action)
}
}

@objc override func sendMenuClose() {
if let onCloseMenu = onCloseMenu {
onCloseMenu()
}
}
@objc override func sendMenuOpen() {
if let onOpenMenu = onOpenMenu {
onOpenMenu()
}
}
}
16 changes: 15 additions & 1 deletion ios/NewArch/FabricMenuViewImplementation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,25 @@ import UIKit
@objc(FabricMenuViewImplementation)
public class FabricMenuViewImplementation: MenuViewImplementation, FabricViewImplementationProtocol {
public var onPressAction: ((String) -> Void)?

public var onCloseMenu: (() -> Void)?
public var onOpenMenu: (() -> Void)?

@objc override func sendButtonAction(_ action: UIAction) {
if let onPress = onPressAction {
onPress(action.identifier.rawValue)
}
}

@objc override func sendMenuClose() {
if let onCloseMenu = onCloseMenu {
onCloseMenu()
}
}

@objc override func sendMenuOpen() {
if let onOpenMenu = onOpenMenu {
onOpenMenu()
}
}

}
2 changes: 2 additions & 0 deletions ios/NewArch/FabricViewImplementationProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ import Foundation
var shouldOpenOnLongPress: Bool { get set }
@objc optional var hitSlop: UIEdgeInsets { get set }
var onPressAction: ((String) -> Void)? { get set }
var onCloseMenu: (() -> Void)? { get set }
var onOpenMenu: (() -> Void)? { get set }
}
19 changes: 19 additions & 0 deletions ios/NewArch/MenuView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ - (instancetype)initWithFrame:(CGRect)frame
_view.onPressAction = ^(NSString *eventString) {
[self onPressAction:eventString];
};
_view.onCloseMenu = ^{
[self onCloseMenu];
};
self.contentView = _view;
}

Expand Down Expand Up @@ -68,6 +71,22 @@ - (void)onPressAction:(NSString * _Nonnull)eventString {
}
}

- (void)onCloseMenu {
// If screen is already unmounted then there will be no event emitter
const auto eventEmitter = [self getEventEmitter];
if (eventEmitter != nullptr) {
eventEmitter->onCloseMenu({});
}
}

- (void)onOpenMenu {
// If screen is already unmounted then there will be no event emitter
const auto eventEmitter = [self getEventEmitter];
if (eventEmitter != nullptr) {
eventEmitter->onOpenMenu({});
}
}

/**
Responsible for iterating through the C++ vector<struct> and convert each struct element to NSDictionary, then return it all in an NSArray
*/
Expand Down
19 changes: 17 additions & 2 deletions ios/OldArch/LegacyActionSheetView.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@

@objc(LegacyActionSheetView)
public class LegacyActionSheetView: ActionSheetView {
@objc var onPressAction: RCTDirectEventBlock?

@objc var onCloseMenu: RCTDirectEventBlock?
@objc var onOpenMenu: RCTDirectEventBlock?



@objc override func sendButtonAction(_ action: String) {
if let onPress = onPressAction {
onPress(["event":action])
}
}

@objc override func sendMenuClose() {
if let onCloseMenu = onCloseMenu {
onCloseMenu([:])
}
}

@objc override func sendMenuOpen() {
if let onOpenMenu = onOpenMenu {
onOpenMenu([:])
}
}
}
Loading
Loading