├── .yarnrc.yml
├── assets
└── images
│ ├── icon.png
│ ├── moon.png
│ ├── sun.png
│ ├── threeDots.png
│ ├── splash-icon.png
│ └── adaptive-icon.png
├── tsconfig.json
├── app
├── index.tsx
├── _layout.tsx
└── myNotes.tsx
├── .gitignore
├── constants
├── MenuActions.ts
└── SampleNotes.ts
├── components
└── NotesList.tsx
├── app.json
├── package.json
├── plugins
└── android
│ └── withRoundedPopupMenu.js
└── README.md
/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | nodeLinker: node-modules
2 |
--------------------------------------------------------------------------------
/assets/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arunabhverma/expo-android-native-menu/HEAD/assets/images/icon.png
--------------------------------------------------------------------------------
/assets/images/moon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arunabhverma/expo-android-native-menu/HEAD/assets/images/moon.png
--------------------------------------------------------------------------------
/assets/images/sun.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arunabhverma/expo-android-native-menu/HEAD/assets/images/sun.png
--------------------------------------------------------------------------------
/assets/images/threeDots.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arunabhverma/expo-android-native-menu/HEAD/assets/images/threeDots.png
--------------------------------------------------------------------------------
/assets/images/splash-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arunabhverma/expo-android-native-menu/HEAD/assets/images/splash-icon.png
--------------------------------------------------------------------------------
/assets/images/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arunabhverma/expo-android-native-menu/HEAD/assets/images/adaptive-icon.png
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "expo/tsconfig.base",
3 | "compilerOptions": {
4 | "strict": true,
5 | "paths": {
6 | "@/*": [
7 | "./*"
8 | ]
9 | }
10 | },
11 | "include": [
12 | "**/*.ts",
13 | "**/*.tsx",
14 | ".expo/types/**/*.ts",
15 | "expo-env.d.ts"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/app/index.tsx:
--------------------------------------------------------------------------------
1 | import { StyleSheet, Text, View } from "react-native";
2 | import React from "react";
3 | import { useTheme } from "@react-navigation/native";
4 | import { Link } from "expo-router";
5 |
6 | export default function Main() {
7 | const theme = useTheme();
8 | return (
9 |
10 |
11 | My Notes
12 |
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
2 |
3 | # dependencies
4 | node_modules/
5 |
6 | # Expo
7 | .expo/
8 | dist/
9 | web-build/
10 | expo-env.d.ts
11 |
12 | # Native
13 | *.orig.*
14 | *.jks
15 | *.p8
16 | *.p12
17 | *.key
18 | *.mobileprovision
19 |
20 | # Native folders
21 | /android/
22 | /ios/
23 |
24 |
25 | # Metro
26 | .metro-health-check*
27 |
28 | # debug
29 | npm-debug.*
30 | yarn-debug.*
31 | yarn-error.*
32 |
33 | # macOS
34 | .DS_Store
35 | *.pem
36 |
37 | # local env files
38 | .env*.local
39 |
40 | # typescript
41 | *.tsbuildinfo
42 |
43 | app-example
44 |
--------------------------------------------------------------------------------
/constants/MenuActions.ts:
--------------------------------------------------------------------------------
1 | export const MENU_ACTIONS = [
2 | {
3 | id: "edit",
4 | title: "Edit",
5 | },
6 | {
7 | id: "share",
8 | title: "Share",
9 |
10 | },
11 | { id: "addLabel",
12 | title: "Add Label",
13 |
14 | },
15 | {
16 | id: "changeColor",
17 | title: "Change Color",
18 | },
19 | {
20 | id: "export",
21 | title: "Export",
22 | subactions: [{
23 | id: "exportAsPDF",
24 | title: "Export as PDF",
25 | },
26 | {
27 | id: "exportAsImage",
28 | title: "Export as Image",
29 | },
30 | {
31 | id: "copyToClipboard",
32 | title: "Copy to Clipboard",
33 | },
34 | ],
35 | },
36 | {
37 | id: "delete",
38 | title: "Delete",
39 | attributes: {
40 | destructive: true,
41 | }
42 | },
43 | ];
44 |
--------------------------------------------------------------------------------
/app/_layout.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | DarkTheme,
3 | DefaultTheme,
4 | ThemeProvider,
5 | } from "@react-navigation/native";
6 | import { Stack } from "expo-router";
7 | import { StatusBar } from "expo-status-bar";
8 | import { useColorScheme } from "react-native";
9 | import "react-native-reanimated";
10 |
11 | declare module "@react-navigation/native" {
12 | export type ExtendedTheme = {
13 | dark: boolean;
14 | colors: {
15 | primary: string;
16 | background: string;
17 | card: string;
18 | text: string;
19 | border: string;
20 | notification: string;
21 | };
22 | };
23 | export function useTheme(): ExtendedTheme;
24 | }
25 |
26 | export default function RootLayout() {
27 | const colorScheme = useColorScheme();
28 |
29 | let dark = {
30 | ...DarkTheme,
31 | colors: {
32 | ...DarkTheme.colors,
33 | background: "#181818",
34 | card: "#181818",
35 | text: "#FFFFFF",
36 | },
37 | };
38 | let light = {
39 | ...DefaultTheme,
40 | colors: {
41 | ...DefaultTheme.colors,
42 | background: "#FDFBF7",
43 | card: "#FDFBF7",
44 | text: "#000000",
45 | },
46 | };
47 | const theme = colorScheme === "dark" ? dark : light;
48 |
49 | return (
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | );
58 | }
59 |
--------------------------------------------------------------------------------
/components/NotesList.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { View, Text, StyleSheet, ScrollView, Pressable } from "react-native";
3 | import { useTheme } from "@react-navigation/native";
4 | import { SAMPLE_NOTES } from "@/constants/SampleNotes";
5 |
6 | export const NotesList = () => {
7 | const theme = useTheme();
8 |
9 | return (
10 |
14 | {SAMPLE_NOTES.map((note) => (
15 |
16 |
17 |
18 | {note.title}
19 |
20 |
21 | {note.date}
22 |
23 |
24 |
25 | {note.content}
26 |
27 |
28 | ))}
29 |
30 | );
31 | };
32 |
33 | const styles = StyleSheet.create({
34 | container: {
35 | flex: 1,
36 | },
37 | noteCard: {
38 | paddingHorizontal: 20,
39 | paddingVertical: 16,
40 | },
41 | noteHeader: {
42 | flexDirection: "row",
43 | justifyContent: "space-between",
44 | alignItems: "center",
45 | marginBottom: 8,
46 | },
47 | noteTitle: {
48 | fontSize: 18,
49 | fontWeight: "600",
50 | },
51 | noteDate: {
52 | fontSize: 12,
53 | opacity: 0.7,
54 | },
55 | noteContent: {
56 | fontSize: 14,
57 | lineHeight: 20,
58 | },
59 | });
60 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "expo-android-native-menu",
4 | "slug": "expo-android-native-menu",
5 | "version": "1.0.0",
6 | "orientation": "portrait",
7 | "icon": "./assets/images/icon.png",
8 | "scheme": "myapp",
9 | "userInterfaceStyle": "automatic",
10 | "newArchEnabled": true,
11 | "ios": {
12 | "supportsTablet": true
13 | },
14 | "android": {
15 | "adaptiveIcon": {
16 | "foregroundImage": "./assets/images/adaptive-icon.png",
17 | "backgroundColor": "#ffffff"
18 | },
19 | "package": "com.arunabhdecaf.expoandroidnativemenu"
20 | },
21 | "web": {
22 | "bundler": "metro",
23 | "output": "static",
24 | "favicon": "./assets/images/favicon.png"
25 | },
26 | "plugins": [
27 | "expo-router",
28 | [
29 | "react-native-edge-to-edge",
30 | {
31 | "android": {
32 | "parentTheme": "Material3",
33 | "enforceNavigationBarContrast": false
34 | }
35 | }
36 | ],
37 | [
38 | "./plugins/android/withRoundedPopupMenu",
39 | {
40 | "lightBackgroundColor": "#FFFFFF",
41 | "darkBackgroundColor": "#212121",
42 | "radius": 14,
43 | "paddingVertical": 8
44 | }
45 | ],
46 | [
47 | "expo-splash-screen",
48 | {
49 | "image": "./assets/images/splash-icon.png",
50 | "imageWidth": 200,
51 | "resizeMode": "contain",
52 | "backgroundColor": "#ffffff"
53 | }
54 | ],
55 | "expo-asset"
56 | ],
57 | "experiments": {
58 | "typedRoutes": true
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/constants/SampleNotes.ts:
--------------------------------------------------------------------------------
1 | export interface Note {
2 | id: number;
3 | title: string;
4 | content: string;
5 | date: string;
6 | }
7 |
8 | export const SAMPLE_NOTES: Note[] = [
9 | {
10 | id: 1,
11 | title: "Meeting Notes",
12 | content: "Discuss project timeline and deliverables for Q2",
13 | date: "2024-03-20",
14 | },
15 | {
16 | id: 2,
17 | title: "Shopping List",
18 | content: "Milk, eggs, bread, fruits",
19 | date: "2024-03-19",
20 | },
21 | {
22 | id: 3,
23 | title: "Book Recommendations",
24 | content: "Atomic Habits, Deep Work, The Psychology of Success",
25 | date: "2024-03-18",
26 | },
27 | {
28 | id: 4,
29 | title: "Workout Plan",
30 | content: "30 min cardio, strength training, stretching",
31 | date: "2024-03-17",
32 | },
33 | {
34 | id: 5,
35 | title: "Travel Plans",
36 | content: "Book flights for summer vacation, research hotels",
37 | date: "2024-03-16",
38 | },
39 | {
40 | id: 6,
41 | title: "Movie Watchlist",
42 | content: "Inception, The Matrix, Interstellar",
43 | date: "2024-03-15",
44 | },
45 | {
46 | id: 7,
47 | title: "Project Ideas",
48 | content: "Mobile app for task management, website redesign",
49 | date: "2024-03-14",
50 | },
51 | {
52 | id: 8,
53 | title: "Health Goals",
54 | content: "Drink more water, get 8 hours of sleep, meditate daily",
55 | date: "2024-03-13",
56 | },
57 | {
58 | id: 9,
59 | title: "Learning Goals",
60 | content: "Complete React Native course, learn TypeScript",
61 | date: "2024-03-12",
62 | },
63 | {
64 | id: 10,
65 | title: "Home Improvement",
66 | content: "Paint living room, organize garage, fix leaky faucet",
67 | date: "2024-03-11",
68 | },
69 | ];
70 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "expo-android-native-menu",
3 | "main": "expo-router/entry",
4 | "version": "1.0.0",
5 | "scripts": {
6 | "start": "expo start",
7 | "reset-project": "node ./scripts/reset-project.js",
8 | "android": "expo run:android",
9 | "ios": "expo run:ios",
10 | "web": "expo start --web",
11 | "test": "jest --watchAll",
12 | "lint": "expo lint"
13 | },
14 | "jest": {
15 | "preset": "jest-expo"
16 | },
17 | "dependencies": {
18 | "@expo/vector-icons": "^14.0.2",
19 | "@react-native-menu/menu": "^1.2.3",
20 | "@react-navigation/bottom-tabs": "^7.2.0",
21 | "@react-navigation/native": "^7.0.14",
22 | "expo": "~52.0.46",
23 | "expo-blur": "~14.0.3",
24 | "expo-constants": "~17.0.8",
25 | "expo-font": "~13.0.4",
26 | "expo-haptics": "~14.0.1",
27 | "expo-linking": "~7.0.5",
28 | "expo-router": "~4.0.20",
29 | "expo-splash-screen": "~0.29.24",
30 | "expo-status-bar": "~2.0.1",
31 | "expo-symbols": "~0.2.2",
32 | "expo-system-ui": "~4.0.9",
33 | "expo-web-browser": "~14.0.2",
34 | "metro": "^0.82.1",
35 | "react": "18.3.1",
36 | "react-dom": "18.3.1",
37 | "react-native": "0.76.9",
38 | "react-native-edge-to-edge": "^1.6.0",
39 | "react-native-gesture-handler": "~2.20.2",
40 | "react-native-reanimated": "~3.16.1",
41 | "react-native-safe-area-context": "4.12.0",
42 | "react-native-screens": "~4.4.0",
43 | "react-native-web": "~0.19.13",
44 | "react-native-webview": "13.12.5"
45 | },
46 | "devDependencies": {
47 | "@babel/core": "^7.25.2",
48 | "@types/jest": "^29.5.12",
49 | "@types/react": "~18.3.12",
50 | "@types/react-test-renderer": "^18.3.0",
51 | "jest": "^29.2.1",
52 | "jest-expo": "~52.0.6",
53 | "react-test-renderer": "18.3.1",
54 | "typescript": "^5.3.3"
55 | },
56 | "private": true,
57 | "packageManager": "yarn@4.2.2+sha512.c44e283c54e02de9d1da8687025b030078c1b9648d2895a65aab8e64225bfb7becba87e1809fc0b4b6778bbd47a1e2ab6ac647de4c5e383a53a7c17db6c3ff4b"
58 | }
59 |
--------------------------------------------------------------------------------
/app/myNotes.tsx:
--------------------------------------------------------------------------------
1 | import { Appearance, Image, Pressable, View, StyleSheet } from "react-native";
2 | import React, { useLayoutEffect } from "react";
3 | import { MenuView } from "@react-native-menu/menu";
4 | import { useTheme } from "@react-navigation/native";
5 | import { useNavigation } from "expo-router";
6 | import { NotesList } from "@/components/NotesList";
7 | import { MENU_ACTIONS } from "@/constants/menuActions";
8 |
9 | const Moon = require("../assets/images/moon.png");
10 | const Sun = require("../assets/images/sun.png");
11 |
12 | const ThemeToggle = () => {
13 | const theme = useTheme();
14 | const isDark = Appearance.getColorScheme() === "dark";
15 |
16 | const handleThemeToggle = () => {
17 | Appearance.setColorScheme(isDark ? "light" : "dark");
18 | };
19 |
20 | return (
21 |
22 |
27 |
28 | );
29 | };
30 |
31 | const HeaderMenu = () => {
32 | const theme = useTheme();
33 | const MENU_ACTION_TRANSFORMED = MENU_ACTIONS.map((action) => ({
34 | ...action,
35 | titleColor: theme.colors.text,
36 | }));
37 |
38 | return (
39 | {}}
44 | >
45 |
50 |
51 | );
52 | };
53 |
54 | const HeaderActions = () => {
55 | return (
56 |
57 |
58 |
59 |
60 | );
61 | };
62 |
63 | export default function MyNotes() {
64 | const navigation = useNavigation();
65 |
66 | useLayoutEffect(() => {
67 | navigation.setOptions({
68 | headerRight: () => ,
69 | });
70 | }, [navigation]);
71 |
72 | return ;
73 | }
74 |
75 | const styles = StyleSheet.create({
76 | themeIcon: {
77 | width: 20,
78 | height: 20,
79 | },
80 |
81 | menuIcon: {
82 | width: 24,
83 | height: 24,
84 | },
85 | menuHitSlop: {
86 | top: 20,
87 | bottom: 20,
88 | left: 20,
89 | right: 20,
90 | },
91 |
92 | headerActions: {
93 | flexDirection: "row",
94 | alignItems: "center",
95 | gap: 18,
96 | },
97 | });
98 |
--------------------------------------------------------------------------------
/plugins/android/withRoundedPopupMenu.js:
--------------------------------------------------------------------------------
1 | const { withDangerousMod } = require("@expo/config-plugins");
2 | const fs = require("node:fs");
3 | const path = require("node:path");
4 |
5 | /**
6 | * Config plugin to add border radius to Android popup menus
7 | * @param {import('@expo/config-types').ExpoConfig} config - Expo config
8 | * @param {Object} options - Plugin options
9 | * @param {number} [options.radius=14] - Border radius in dp
10 | * @param {string} [options.lightBackgroundColor='#FFFFFF'] - Background color for light theme
11 | * @param {string} [options.darkBackgroundColor='#000000'] - Background color for dark theme
12 | * @param {number} [options.paddingVertical=14] - Vertical padding in dp
13 | * @param {number} [options.paddingHorizontal=0] - Horizontal padding in dp
14 | * @returns {import('@expo/config-types').ExpoConfig} - Modified Expo config
15 | */
16 | const withRoundedPopupMenu = (config, options = {}) => {
17 | const radius = options.radius || 14;
18 | const lightBackgroundColor = options.lightBackgroundColor || "#FFFFFF";
19 | const darkBackgroundColor = options.darkBackgroundColor || "#000000";
20 | const paddingVertical = options.paddingVertical || 14;
21 | const paddingHorizontal = options.paddingHorizontal || 0;
22 |
23 | let modifiedConfig = withDangerousMod(config, [
24 | "android",
25 | async (config) => {
26 | const androidDir = path.join(config.modRequest.platformProjectRoot, "app", "src", "main");
27 | const resDir = path.join(androidDir, "res");
28 | const drawableDir = path.join(resDir, "drawable");
29 | const drawableNightDir = path.join(resDir, "drawable-night");
30 |
31 | if (!fs.existsSync(drawableDir)) {
32 | fs.mkdirSync(drawableDir, { recursive: true });
33 | }
34 |
35 | if (!fs.existsSync(drawableNightDir)) {
36 | fs.mkdirSync(drawableNightDir, { recursive: true });
37 | }
38 |
39 | const roundedPopupLightXml = `
40 |
41 |
42 |
43 |
48 | `;
49 |
50 | const roundedPopupDarkXml = `
51 |
52 |
53 |
54 |
59 | `;
60 |
61 | fs.writeFileSync(path.join(drawableDir, "rounded_popup.xml"), roundedPopupLightXml);
62 | fs.writeFileSync(path.join(drawableNightDir, "rounded_popup.xml"), roundedPopupDarkXml);
63 |
64 | return config;
65 | },
66 | ]);
67 |
68 | modifiedConfig = withDangerousMod(modifiedConfig, [
69 | "android",
70 | async (config) => {
71 | const androidDir = path.join(config.modRequest.platformProjectRoot, "app", "src", "main");
72 | const resDir = path.join(androidDir, "res");
73 | const valuesDir = path.join(resDir, "values");
74 |
75 | if (!fs.existsSync(valuesDir)) {
76 | fs.mkdirSync(valuesDir, { recursive: true });
77 | }
78 |
79 | const stylesPath = path.join(valuesDir, "styles.xml");
80 |
81 | const stylesXml = `
82 |
83 |
86 |
87 |
90 | `;
91 |
92 | fs.writeFileSync(stylesPath, stylesXml);
93 |
94 | return config;
95 | },
96 | ]);
97 |
98 | return modifiedConfig;
99 | };
100 |
101 | module.exports = withRoundedPopupMenu;
102 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Expo Android Native Menu Styling
2 |
3 | This repository provides a complete implementation for customizing the Android native menu in your Expo app. This implementation is highly inspired by [this solution](https://github.com/react-native-menu/menu/issues/58#issuecomment-806530467) for styling Android popup menus.
4 |
5 | ## Demo
6 |
7 | | Android |
8 | | ----------------------------------------------------------------------------------------------- |
9 | | |
10 |
11 | ## Compatibility
12 |
13 | This plugin is compatible with:
14 |
15 | - [Zeego](https://zeego.dev/) - Beautiful, native menus for React Native + Web
16 | - [@react-native-menu/menu](https://github.com/react-native-menu/menu) - UIMenu Component for React Native
17 |
18 | ## Quick Start
19 |
20 | 1. Clone this repository:
21 |
22 | ```bash
23 | git clone https://github.com/yourusername/expo-android-native-menu.git
24 | ```
25 |
26 | 2. Copy the following directories/files to your Expo project:
27 |
28 | - `plugins/` directory - Contains the config plugin implementation
29 | - `app.json` - Contains the plugin configuration
30 |
31 | 3. Install the required dependencies in your project:
32 |
33 | ```bash
34 | npm install
35 | ```
36 |
37 | 4. Add the plugin to your `app.json` or `app.config.js`:
38 |
39 | ```json
40 | {
41 | "expo": {
42 | "plugins": [
43 | [
44 | "expo-android-native-menu",
45 | {
46 | "radius": 14,
47 | "lightBackgroundColor": "#FFFFFF",
48 | "darkBackgroundColor": "#000000",
49 | "paddingVertical": 14,
50 | "paddingHorizontal": 0
51 | }
52 | ]
53 | ]
54 | }
55 | }
56 | ```
57 |
58 | ## Project Structure
59 |
60 | ```
61 | expo-android-native-menu/
62 | ├── plugins/ # Config plugin implementation
63 | │ └── android/ # Android-specific plugins
64 | │ └── withRoundedPopupMenu.js # Main plugin file
65 | ├── app.json # Plugin configuration
66 | └── package.json # Project dependencies
67 | ```
68 |
69 | ## Configuration Options
70 |
71 | The plugin accepts the following configuration options:
72 |
73 | | Option | Type | Default | Description |
74 | | -------------------- | ------ | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
75 | | radius | number | 14 | Border radius in dp |
76 | | lightBackgroundColor | string | "#FFFFFF" | Background color for light theme |
77 | | darkBackgroundColor | string | "#000000" | Background color for dark theme |
78 | | paddingVertical | number | 14 | Vertical padding in dp |
79 | | paddingHorizontal | number | 0 | Horizontal padding in dp. Note: Keep this at 0 to maintain the native Android ripple effect. On press, the padding will be visible as the ripple effect won't touch the edge. |
80 |
81 | ## Implementation Details
82 |
83 | ### Config Plugin
84 |
85 | The config plugin (`plugins/android/withRoundedPopupMenu.js`) handles:
86 |
87 | - Creating rounded popup menu backgrounds for both light and dark themes
88 | - Applying custom padding to menu items
89 | - Setting up the necessary drawable resources
90 |
91 | ## Usage
92 |
93 | 1. After copying the files and adding the configuration, rebuild your app:
94 |
95 | ```bash
96 | npx expo prebuild
97 | ```
98 |
99 | 2. Run your app:
100 |
101 | ```bash
102 | npx expo run:android
103 | ```
104 |
105 | ## Example Configuration
106 |
107 | Here's a complete example of how to configure the plugin in your `app.config.js`:
108 |
109 | ```javascript
110 | export default {
111 | expo: {
112 | name: "your-app-name",
113 | // ... other expo config
114 | plugins: [
115 | [
116 | "expo-android-native-menu",
117 | {
118 | radius: 14,
119 | lightBackgroundColor: "#FFFFFF",
120 | darkBackgroundColor: "#000000",
121 | paddingVertical: 14,
122 | paddingHorizontal: 0,
123 | },
124 | ],
125 | ],
126 | },
127 | };
128 | ```
129 |
130 | ## Notes
131 |
132 | - This implementation only affects the Android native menu appearance
133 | - Changes require a rebuild of your app
134 | - Make sure to test your menu styling on different Android versions and screen sizes
135 | - Horizontal padding is 0 by default to keep the native Android ripple effect clean, adding padding would make the gap visible when tapping.
136 |
137 | ## Contributing
138 |
139 | Contributions are welcome! Please feel free to submit a Pull Request.
140 |
141 | ## License
142 |
143 | This project is licensed under the MIT License.
144 |
--------------------------------------------------------------------------------