Skip to content
Open
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
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,13 +177,21 @@ Actions to be displayed in the menu.
|--------------|----------|
| MenuAction[] | Yes |

### `themeVariant` (iOS only)
### `themeVariant`

String to override theme of the menu. If you want to control theme universally across your app, [see this package](https://github.com/vonovak/react-native-theme-control).

| Type | Required |
|-----------------------|----------|
| enum('light', 'dark') | No |
| Type | Required |
| ------------------------------- | -------- |
| enum('light', 'dark', 'system') | No |

### `uiKit` (Android only)

String to override UI kit of the menu. Allows you to choose between different Android UI implementations.

| Type | Required |
| -------------------------------------- | -------- |
| enum('auto', 'material3', 'appcompat') | No |

#### `MenuAction`

Expand Down
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -158,4 +158,4 @@ if (isNewArchitectureEnabled()) {
libraryName = "MenuView"
codegenJavaPackageName = "com.reactnativemenu"
}
}
}
39 changes: 37 additions & 2 deletions android/src/main/java/com/reactnativemenu/MenuView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,18 @@ import android.widget.PopupMenu
import com.facebook.react.bridge.*
import com.facebook.react.uimanager.UIManagerHelper
import com.facebook.react.views.view.ReactViewGroup
import com.reactnativemenu.ThemeHelper.appcompat
import com.reactnativemenu.ThemeHelper.isNight
import com.reactnativemenu.ThemeHelper.material3
import java.lang.reflect.Field


class MenuView(private val mContext: ReactContext) : ReactViewGroup(mContext) {
private lateinit var mActions: ReadableArray
private var uiKit = "auto"
private var themeVariant = "system"
private var mIsAnchoredToRight = false
private val mPopupMenu: PopupMenu = PopupMenu(context, this)
private var mPopupMenu: PopupMenu = PopupMenu(context, this)
private var mIsMenuDisplayed = false
private var mIsOnLongPress = false
private var mGestureDetector: GestureDetector
Expand All @@ -43,7 +48,7 @@ class MenuView(private val mContext: ReactContext) : ReactViewGroup(mContext) {
})
}

fun show(){
fun show() {
prepareMenu()
}

Expand Down Expand Up @@ -94,6 +99,18 @@ class MenuView(private val mContext: ReactContext) : ReactViewGroup(mContext) {
mIsOnLongPress = isLongPress
}

fun setUiKit(uiKit: String?) {
this.uiKit = uiKit ?: "auto"

setStyle()
}

fun setThemeVariant(themeVariant: String?) {
this.themeVariant = themeVariant ?: "system"

setStyle()
}

private val getActionsCount: Int
get() = mActions.size()

Expand Down Expand Up @@ -318,4 +335,22 @@ class MenuView(private val mContext: ReactContext) : ReactViewGroup(mContext) {
0, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
return textWithColor
}

private fun setStyle() {
val dark = when (this.themeVariant) {
"dark" -> true
"light" -> false
else -> isNight(context)
}

val theme = when (this.uiKit) {
"material3" -> material3(dark).takeIf { it != 0 } ?: appcompat(dark)
"appcompat" -> appcompat(dark)
else -> material3(dark).takeIf { it != 0 } ?: appcompat(dark)
}

val themedCtx = ContextThemeWrapper(context, theme)

mPopupMenu = PopupMenu(themedCtx, this)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,16 @@ abstract class MenuViewManagerBase : ReactClippingViewManager<MenuView>() {
view.setBackfaceVisibility(backfaceVisibility)
}

@ReactProp(name = "themeVariant")
fun setThemeVariant(view: MenuView, themeVariant: String?) {
view.setThemeVariant(themeVariant)
}

@ReactProp(name = "uiKit")
fun setUiKit(view: MenuView, uiKit: String?) {
view.setUiKit(uiKit)
}

override fun setOpacity(@NonNull view: MenuView, opacity: Float) {
view.setOpacityIfPossible(opacity)
}
Expand Down
33 changes: 33 additions & 0 deletions android/src/main/java/com/reactnativemenu/ThemeHelper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.reactnativemenu

import android.content.Context
import android.content.res.Configuration

object ThemeHelper {
fun isNight(context: Context): Boolean {
val mask = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
return mask == Configuration.UI_MODE_NIGHT_YES
}

private fun styleIdOrZero(className: String, fieldName: String): Int {
return try {
val cls = Class.forName(className) // exp: "com.google.android.material.R$style"
val f = cls.getField(fieldName) // exp: "ThemeOverlay_Material3_Dark"
f.getInt(null)
} catch (_: Throwable) {
0
}
}

fun material3(dark: Boolean): Int {
// Reflection ile: Material3 varsa al, yoksa 0 dΓΆner
val name = if (dark) "ThemeOverlay_Material3_Dark" else "ThemeOverlay_Material3_Light"
return styleIdOrZero("com.google.android.material.R\$style", name)
}

fun appcompat(dark: Boolean): Int {
// AppCompat reflection (RN projelerinde zaten var)
val name = if (dark) "ThemeOverlay_AppCompat_Dark" else "ThemeOverlay_AppCompat_Light"
return styleIdOrZero("androidx.appcompat.R\$style", name)
}
}
8 changes: 6 additions & 2 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import * as React from "react";
import { Button, Platform, StyleSheet, Text, View } from "react-native";
import { MenuView, type MenuComponentRef } from "@react-native-menu/menu";
import {
MenuView,
type MenuComponentRef,
type MenuThemeVariant,
} from "@react-native-menu/menu";
import { useRef } from "react";

export const App = () => {
const [themeVariant] = React.useState<string | undefined>("light");
const [themeVariant] = React.useState<MenuThemeVariant | undefined>("light");
const menuRef = useRef<MenuComponentRef>(null);

return (
Expand Down
1 change: 1 addition & 0 deletions src/NativeModuleSpecs/UIMenuNativeComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export interface NativeProps extends ViewProps {
actionsHash: string; // just a workaround to make sure we don't have to manually compare MenuActions manually in C++ (since it's a struct and that's a pain)
title?: string;
themeVariant?: string;
uiKit?: string;
shouldOpenOnLongPress?: boolean;
hitSlop: {
top: Int32;
Expand Down
4 changes: 4 additions & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import type {
ProcessedMenuAction,
NativeActionEvent,
MenuComponentRef,
MenuThemeVariant,
MenuUiKit,
} from "./types";
import { objectHash } from "./utils";

Expand Down Expand Up @@ -49,4 +51,6 @@ export type {
MenuComponentRef,
MenuAction,
NativeActionEvent,
MenuThemeVariant,
MenuUiKit,
};
16 changes: 12 additions & 4 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ export type NativeActionEvent = {
};
};

export type MenuThemeVariant = "light" | "dark" | "system";

export type MenuUiKit = "auto" | "material3" | "appcompat";

type MenuAttributes = {
/**
* An attribute indicating the destructive style.
Expand Down Expand Up @@ -147,11 +151,15 @@ type MenuComponentPropsBase = {
shouldOpenOnLongPress?: boolean;
/**
* Overrides theme variant of menu to light mode, dark mode or system theme
* (Only support iOS for now)
*
* @platform iOS
* @default system
*/
themeVariant?: MenuThemeVariant;
/**
* Overrides UI kit of menu to auto, material3 or appcompat
* @platform Android
* @default auto
*/
themeVariant?: string;
uiKit?: MenuUiKit;
/**
* Custom OpenSpace hitSlop prop. Works like touchable hitslop.
* @platform iOS
Expand Down