@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 +15 -1
- package/ios/NativeMenuButtonComponentView.mm +2 -0
- package/ios/NativeMenuButtonView.swift +21 -2
- package/lib/module/NativeMenuButton.ios.js +3 -1
- package/lib/module/NativeMenuButton.ios.js.map +1 -1
- package/lib/module/NativeMenuButtonNativeComponent.ts +3 -0
- package/lib/typescript/src/NativeMenuButton.ios.d.ts +3 -2
- package/lib/typescript/src/NativeMenuButton.ios.d.ts.map +1 -1
- package/lib/typescript/src/NativeMenuButtonNativeComponent.d.ts +2 -0
- package/lib/typescript/src/NativeMenuButtonNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/NativeMenuButton.ios.tsx +7 -1
- package/src/NativeMenuButtonNativeComponent.ts +3 -0
- package/src/index.tsx +4 -1
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
|
-
- **
|
|
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;
|
|
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,
|
|
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;
|
|
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,
|
|
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,7 +1,10 @@
|
|
|
1
1
|
import type { NativeSyntheticEvent, StyleProp, ViewStyle } from 'react-native';
|
|
2
2
|
|
|
3
3
|
import NativeMenuButtonComponent from './NativeMenuButtonNativeComponent';
|
|
4
|
-
import type {
|
|
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 {
|
|
3
|
+
export type {
|
|
4
|
+
NativeMenuItem,
|
|
5
|
+
NativeMenuTheme,
|
|
6
|
+
} from './NativeMenuButtonNativeComponent';
|