@quyvt.dev/react-native-native-menu 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  Fabric-only native system menus for React Native. iOS uses UIKit `UIMenu`; Android intentionally leaves fallback UI to the consuming app.
4
4
 
5
+ ## Demo
6
+
7
+ <p align="center">
8
+ <img src="https://raw.githubusercontent.com/vtquy98/react-native-native-menu/main/docs/images/native-menu-demo.gif" width="320" alt="Native iOS menu opening and selecting actions" />
9
+ </p>
10
+
11
+ <p align="center">
12
+ <img src="https://raw.githubusercontent.com/vtquy98/react-native-native-menu/main/docs/images/native-menu-idle.png" width="30%" alt="Custom React Native trigger" />
13
+ <img src="https://raw.githubusercontent.com/vtquy98/react-native-native-menu/main/docs/images/native-menu-open.png" width="30%" alt="Native iOS UIMenu with destructive and disabled actions" />
14
+ <img src="https://raw.githubusercontent.com/vtquy98/react-native-native-menu/main/docs/images/native-menu-selected.png" width="30%" alt="Immediate selected item callback" />
15
+ </p>
16
+
5
17
  ## Requirements
6
18
 
7
19
  - React Native with the New Architecture enabled
@@ -47,6 +59,7 @@ function MenuTrigger() {
47
59
  accessibilityLabel="More options"
48
60
  items={items}
49
61
  style={StyleSheet.absoluteFill}
62
+ theme="system"
50
63
  onSelect={handleSelect}
51
64
  />
52
65
  ) : (
@@ -79,6 +92,7 @@ A transparent native touch surface. Give it non-zero dimensions and position it
79
92
  | `items` | `ReadonlyArray<NativeMenuItem>` | Ordered native menu actions. |
80
93
  | `onSelect` | `(id: string) => void` | Called immediately with the selected item ID. |
81
94
  | `style` | `StyleProp<ViewStyle>?` | Layout for the transparent touch surface. |
95
+ | `theme` | `'light' \| 'dark' \| 'system'?` | Controls the native menu appearance. Defaults to `'dark'`. |
82
96
 
83
97
  ### `NativeMenuItem`
84
98
 
@@ -93,7 +107,7 @@ A transparent native touch surface. Give it non-zero dimensions and position it
93
107
 
94
108
  - **iOS:** Uses a Fabric component backed by `UIButton`, `UIAction`, and `UIMenu`. Item order is preserved and prop updates rebuild the native menu.
95
109
  - **System appearance:** UIKit supplies the native animation and material. Liquid Glass appears automatically on operating systems and SDKs that provide it; this package does not emulate that effect.
96
- - **Version 0.1.0 theme:** The native view uses dark interface style by default.
110
+ - **Theme:** Use `light` or `dark` to force an interface style, or `system` to inherit the current system appearance. The default remains `dark` for backward compatibility with version 0.1.0.
97
111
  - **Android:** The exported component renders `null`. The consuming app must render its own trigger and fallback menu.
98
112
 
99
113
  ## Troubleshooting
@@ -10,6 +10,7 @@ using namespace facebook::react;
10
10
  @property (nonatomic, copy) NSArray *items;
11
11
  @property (nonatomic, copy, nullable) NSString *menuAccessibilityLabel;
12
12
  @property (nonatomic, copy, nullable) void (^onSelect)(NSString *itemId);
13
+ @property (nonatomic, copy) NSString *theme;
13
14
  @end
14
15
 
15
16
  @interface VTNativeMenuButton : RCTViewComponentView <RCTVTNativeMenuButtonViewProtocol>
@@ -71,6 +72,7 @@ using namespace facebook::react;
71
72
  _menuButtonView.menuAccessibilityLabel = newMenuProps.menuAccessibilityLabel.empty()
72
73
  ? nil
73
74
  : [NSString stringWithUTF8String:newMenuProps.menuAccessibilityLabel.c_str()];
75
+ _menuButtonView.theme = [NSString stringWithUTF8String:newMenuProps.theme.c_str()];
74
76
 
75
77
  [super updateProps:props oldProps:oldProps];
76
78
  }
@@ -14,14 +14,17 @@ final class VTNativeMenuButtonView: UIView {
14
14
 
15
15
  @objc var onSelect: ((NSString) -> Void)?
16
16
 
17
+ @objc var theme: NSString = "dark" {
18
+ didSet { updateInterfaceStyle() }
19
+ }
20
+
17
21
  override init(frame: CGRect) {
18
22
  super.init(frame: frame)
19
23
 
20
- overrideUserInterfaceStyle = .dark
21
24
  button.backgroundColor = .clear
22
- button.overrideUserInterfaceStyle = .dark
23
25
  button.showsMenuAsPrimaryAction = true
24
26
  addSubview(button)
27
+ updateInterfaceStyle()
25
28
  updateMenu()
26
29
  }
27
30
 
@@ -34,6 +37,22 @@ final class VTNativeMenuButtonView: UIView {
34
37
  button.frame = bounds
35
38
  }
36
39
 
40
+ private func updateInterfaceStyle() {
41
+ let interfaceStyle: UIUserInterfaceStyle
42
+
43
+ switch theme as String {
44
+ case "light":
45
+ interfaceStyle = .light
46
+ case "system":
47
+ interfaceStyle = .unspecified
48
+ default:
49
+ interfaceStyle = .dark
50
+ }
51
+
52
+ overrideUserInterfaceStyle = interfaceStyle
53
+ button.overrideUserInterfaceStyle = interfaceStyle
54
+ }
55
+
37
56
  private func updateMenu() {
38
57
  let actions = items.compactMap { value -> UIAction? in
39
58
  guard
@@ -7,11 +7,13 @@ const NativeMenuButton = ({
7
7
  accessibilityLabel,
8
8
  items,
9
9
  onSelect,
10
- style
10
+ style,
11
+ theme = 'dark'
11
12
  }) => /*#__PURE__*/_jsx(NativeMenuButtonComponent, {
12
13
  items: items,
13
14
  menuAccessibilityLabel: accessibilityLabel,
14
15
  style: style,
16
+ theme: theme,
15
17
  onSelect: event => onSelect(getNativeMenuItemId(event))
16
18
  });
17
19
  export default NativeMenuButton;
@@ -1 +1 @@
1
- {"version":3,"names":["NativeMenuButtonComponent","jsx","_jsx","getNativeMenuItemId","event","nativeEvent","id","NativeMenuButton","accessibilityLabel","items","onSelect","style","menuAccessibilityLabel"],"sourceRoot":"../../src","sources":["NativeMenuButton.ios.tsx"],"mappings":";;AAEA,OAAOA,yBAAyB,MAAM,mCAAmC;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAY1E,OAAO,MAAMC,mBAAmB,GAAIC,KAA4B,IAC9DA,KAAK,CAACC,WAAW,CAACC,EAAE;AAEtB,MAAMC,gBAAgB,GAAGA,CAAC;EACxBC,kBAAkB;EAClBC,KAAK;EACLC,QAAQ;EACRC;AACqB,CAAC,kBACtBT,IAAA,CAACF,yBAAyB;EACxBS,KAAK,EAAEA,KAAM;EACbG,sBAAsB,EAAEJ,kBAAmB;EAC3CG,KAAK,EAAEA,KAAM;EACbD,QAAQ,EAAGN,KAA4B,IACrCM,QAAQ,CAACP,mBAAmB,CAACC,KAAK,CAAC;AACpC,CACF,CACF;AAED,eAAeG,gBAAgB","ignoreList":[]}
1
+ {"version":3,"names":["NativeMenuButtonComponent","jsx","_jsx","getNativeMenuItemId","event","nativeEvent","id","NativeMenuButton","accessibilityLabel","items","onSelect","style","theme","menuAccessibilityLabel"],"sourceRoot":"../../src","sources":["NativeMenuButton.ios.tsx"],"mappings":";;AAEA,OAAOA,yBAAyB,MAAM,mCAAmC;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAgB1E,OAAO,MAAMC,mBAAmB,GAAIC,KAA4B,IAC9DA,KAAK,CAACC,WAAW,CAACC,EAAE;AAEtB,MAAMC,gBAAgB,GAAGA,CAAC;EACxBC,kBAAkB;EAClBC,KAAK;EACLC,QAAQ;EACRC,KAAK;EACLC,KAAK,GAAG;AACa,CAAC,kBACtBV,IAAA,CAACF,yBAAyB;EACxBS,KAAK,EAAEA,KAAM;EACbI,sBAAsB,EAAEL,kBAAmB;EAC3CG,KAAK,EAAEA,KAAM;EACbC,KAAK,EAAEA,KAAM;EACbF,QAAQ,EAAGN,KAA4B,IACrCM,QAAQ,CAACP,mBAAmB,CAACC,KAAK,CAAC;AACpC,CACF,CACF;AAED,eAAeG,gBAAgB","ignoreList":[]}
@@ -8,10 +8,13 @@ export type NativeMenuItem = Readonly<{
8
8
  label: string;
9
9
  }>;
10
10
 
11
+ export type NativeMenuTheme = 'light' | 'dark' | 'system';
12
+
11
13
  export interface NativeProps extends ViewProps {
12
14
  items: ReadonlyArray<NativeMenuItem>;
13
15
  menuAccessibilityLabel?: string;
14
16
  onSelect?: CodegenTypes.BubblingEventHandler<Readonly<{ id: string }>>;
17
+ theme: string;
15
18
  }
16
19
 
17
20
  export default codegenNativeComponent<NativeProps>(
@@ -1,5 +1,5 @@
1
1
  import type { NativeSyntheticEvent, StyleProp, ViewStyle } from 'react-native';
2
- import type { NativeMenuItem } from './NativeMenuButtonNativeComponent';
2
+ import type { NativeMenuItem, NativeMenuTheme } from './NativeMenuButtonNativeComponent';
3
3
  type NativeMenuSelectEvent = NativeSyntheticEvent<{
4
4
  id: string;
5
5
  }>;
@@ -8,8 +8,9 @@ export type NativeMenuButtonProps = {
8
8
  items: ReadonlyArray<NativeMenuItem>;
9
9
  onSelect: (id: string) => void;
10
10
  style?: StyleProp<ViewStyle>;
11
+ theme?: NativeMenuTheme;
11
12
  };
12
13
  export declare const getNativeMenuItemId: (event: NativeMenuSelectEvent) => string;
13
- declare const NativeMenuButton: ({ accessibilityLabel, items, onSelect, style, }: NativeMenuButtonProps) => import("react").JSX.Element;
14
+ declare const NativeMenuButton: ({ accessibilityLabel, items, onSelect, style, theme, }: NativeMenuButtonProps) => import("react").JSX.Element;
14
15
  export default NativeMenuButton;
15
16
  //# sourceMappingURL=NativeMenuButton.ios.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"NativeMenuButton.ios.d.ts","sourceRoot":"","sources":["../../../src/NativeMenuButton.ios.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAG/E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAExE,KAAK,qBAAqB,GAAG,oBAAoB,CAAC;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAElE,MAAM,MAAM,qBAAqB,GAAG;IAClC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,KAAK,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IACrC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,OAAO,qBAAqB,WAC1C,CAAC;AAEvB,QAAA,MAAM,gBAAgB,GAAI,iDAKvB,qBAAqB,gCASvB,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"NativeMenuButton.ios.d.ts","sourceRoot":"","sources":["../../../src/NativeMenuButton.ios.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAG/E,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EAChB,MAAM,mCAAmC,CAAC;AAE3C,KAAK,qBAAqB,GAAG,oBAAoB,CAAC;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAElE,MAAM,MAAM,qBAAqB,GAAG;IAClC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,KAAK,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IACrC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,KAAK,CAAC,EAAE,eAAe,CAAC;CACzB,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,OAAO,qBAAqB,WAC1C,CAAC;AAEvB,QAAA,MAAM,gBAAgB,GAAI,wDAMvB,qBAAqB,gCAUvB,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
@@ -5,12 +5,14 @@ export type NativeMenuItem = Readonly<{
5
5
  id: string;
6
6
  label: string;
7
7
  }>;
8
+ export type NativeMenuTheme = 'light' | 'dark' | 'system';
8
9
  export interface NativeProps extends ViewProps {
9
10
  items: ReadonlyArray<NativeMenuItem>;
10
11
  menuAccessibilityLabel?: string;
11
12
  onSelect?: CodegenTypes.BubblingEventHandler<Readonly<{
12
13
  id: string;
13
14
  }>>;
15
+ theme: string;
14
16
  }
15
17
  declare const _default: HostComponent<NativeProps>;
16
18
  export default _default;
@@ -1 +1 @@
1
- {"version":3,"file":"NativeMenuButtonNativeComponent.d.ts","sourceRoot":"","sources":["../../../src/NativeMenuButtonNativeComponent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3E,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;IACpC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf,CAAC,CAAC;AAEH,MAAM,WAAW,WAAY,SAAQ,SAAS;IAC5C,KAAK,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IACrC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,EAAE,YAAY,CAAC,oBAAoB,CAAC,QAAQ,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;CACxE;wBAII,aAAa,CAAC,WAAW,CAAC;AAF/B,wBAEgC"}
1
+ {"version":3,"file":"NativeMenuButtonNativeComponent.d.ts","sourceRoot":"","sources":["../../../src/NativeMenuButtonNativeComponent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3E,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;IACpC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf,CAAC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;AAE1D,MAAM,WAAW,WAAY,SAAQ,SAAS;IAC5C,KAAK,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IACrC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,EAAE,YAAY,CAAC,oBAAoB,CAAC,QAAQ,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IACvE,KAAK,EAAE,MAAM,CAAC;CACf;wBAII,aAAa,CAAC,WAAW,CAAC;AAF/B,wBAEgC"}
@@ -1,4 +1,4 @@
1
1
  export { default as NativeMenuButton } from './NativeMenuButton';
2
2
  export type { NativeMenuButtonProps } from './NativeMenuButton';
3
- export type { NativeMenuItem } from './NativeMenuButtonNativeComponent';
3
+ export type { NativeMenuItem, NativeMenuTheme, } from './NativeMenuButtonNativeComponent';
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACjE,YAAY,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAChE,YAAY,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACjE,YAAY,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAChE,YAAY,EACV,cAAc,EACd,eAAe,GAChB,MAAM,mCAAmC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quyvt.dev/react-native-native-menu",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Native system menus for React Native using Fabric and UIKit UIMenu",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -1,7 +1,10 @@
1
1
  import type { NativeSyntheticEvent, StyleProp, ViewStyle } from 'react-native';
2
2
 
3
3
  import NativeMenuButtonComponent from './NativeMenuButtonNativeComponent';
4
- import type { NativeMenuItem } from './NativeMenuButtonNativeComponent';
4
+ import type {
5
+ NativeMenuItem,
6
+ NativeMenuTheme,
7
+ } from './NativeMenuButtonNativeComponent';
5
8
 
6
9
  type NativeMenuSelectEvent = NativeSyntheticEvent<{ id: string }>;
7
10
 
@@ -10,6 +13,7 @@ export type NativeMenuButtonProps = {
10
13
  items: ReadonlyArray<NativeMenuItem>;
11
14
  onSelect: (id: string) => void;
12
15
  style?: StyleProp<ViewStyle>;
16
+ theme?: NativeMenuTheme;
13
17
  };
14
18
 
15
19
  export const getNativeMenuItemId = (event: NativeMenuSelectEvent) =>
@@ -20,11 +24,13 @@ const NativeMenuButton = ({
20
24
  items,
21
25
  onSelect,
22
26
  style,
27
+ theme = 'dark',
23
28
  }: NativeMenuButtonProps) => (
24
29
  <NativeMenuButtonComponent
25
30
  items={items}
26
31
  menuAccessibilityLabel={accessibilityLabel}
27
32
  style={style}
33
+ theme={theme}
28
34
  onSelect={(event: NativeMenuSelectEvent) =>
29
35
  onSelect(getNativeMenuItemId(event))
30
36
  }
@@ -8,10 +8,13 @@ export type NativeMenuItem = Readonly<{
8
8
  label: string;
9
9
  }>;
10
10
 
11
+ export type NativeMenuTheme = 'light' | 'dark' | 'system';
12
+
11
13
  export interface NativeProps extends ViewProps {
12
14
  items: ReadonlyArray<NativeMenuItem>;
13
15
  menuAccessibilityLabel?: string;
14
16
  onSelect?: CodegenTypes.BubblingEventHandler<Readonly<{ id: string }>>;
17
+ theme: string;
15
18
  }
16
19
 
17
20
  export default codegenNativeComponent<NativeProps>(
package/src/index.tsx CHANGED
@@ -1,3 +1,6 @@
1
1
  export { default as NativeMenuButton } from './NativeMenuButton';
2
2
  export type { NativeMenuButtonProps } from './NativeMenuButton';
3
- export type { NativeMenuItem } from './NativeMenuButtonNativeComponent';
3
+ export type {
4
+ NativeMenuItem,
5
+ NativeMenuTheme,
6
+ } from './NativeMenuButtonNativeComponent';