Skip to content

Commit 0971d87

Browse files
committed
Add long press support, improve styleguide CSS
1 parent bc94c3f commit 0971d87

18 files changed

+435
-278
lines changed

docs/Readme.md

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,7 @@ React Context Menu Wrapper
1616
</a>
1717
</div>
1818

19-
This library provides a simple wrapper for your context menus. It handles user interaction on desktop and mobile
20-
devices, and guarantees correct context menu positioning.
21-
22-
In an effort to stay unopinionated, this library does not provide any styling or additional components. It is up to you
23-
define your own styles and logic for the menu. This is useful when your CSS framework already provides dropdown menu
24-
styling (e.g. [Bulma](https://bulma.io/), [Bootstrap](https://getbootstrap.com/)) or when you need to implement some
25-
non-trivial logic for your context menu (e.g. [Ogma context menus](https://github.com/TimboKZ/Ogma) with tagging
26-
support).
19+
#### [View on Github](https://github.com/TimboKZ/react-context-menu-wrapper)
2720

2821
> **Important:** React Context Menu Wrapper v3+ uses [React hooks](https://reactjs.org/docs/hooks-intro.html) and
2922
functional components. If your project does not support them, consider using v2 or below.
@@ -32,5 +25,3 @@ functional components. If your project does not support them, consider using v2
3225

3326
> **Note:** You can view source code of all examples on this page - just click the "View Code" button below the
3427
relevant example.
35-
36-
#### [View on Github](https://github.com/TimboKZ/react-context-menu-wrapper)

docs/components/BasicGlobalMenu.js

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,39 @@
11
import React, { useCallback, useEffect, useState } from 'react';
22
import { ContextMenuWrapper } from 'react-context-menu-wrapper';
33

4-
const MyContextMenu = () => {
4+
const MyContextMenu = React.memo(() => {
55
const [seconds, setSeconds] = useState(0);
66
useEffect(() => {
7-
const timer = setInterval(() => setSeconds(s => s + 1), 1000);
7+
const timer = setInterval(() => setSeconds(s => s + 0.5), 500);
88
return () => clearInterval(timer);
9-
});
9+
}, []);
1010

1111
return (
1212
<div className="context-menu">
1313
<p>
14-
I am a custom <strong>global</strong> context menu!
15-
</p>
16-
<p>
17-
Open for {seconds} {seconds === 1 ? 'second' : 'seconds'}.
14+
This is a <strong>global</strong> menu.
15+
<div style={{ height: 10 }} />
16+
Open for {seconds.toFixed(1)} {seconds === 1 ? 'second' : 'seconds'}.
1817
</p>
18+
<button>Dummy button #1</button>
19+
<button>Dummy button #2</button>
1920
</div>
2021
);
21-
};
22+
});
2223

23-
const GlobalMenuExample = () => {
24+
const GlobalMenuExamplePage = React.memo(() => {
2425
const [globalMenuEnabled, setGlobalMenuEnabled] = useState(true);
2526
const onClick = useCallback(() => setGlobalMenuEnabled(!globalMenuEnabled), [globalMenuEnabled]);
2627

27-
const status = globalMenuEnabled ? 'ENABLED' : 'DISABLED';
28+
const currentMenuType = globalMenuEnabled ? 'custom' : "Browser's default";
29+
const currentMenuTypeStyle = { color: globalMenuEnabled ? 'red' : 'green' };
2830
const action = globalMenuEnabled ? 'Disable' : 'Enable';
2931

3032
return (
3133
<React.Fragment>
32-
<p className="text">Custom global context menu status: {status}</p>
34+
<p className="text">
35+
Currently using <span style={currentMenuTypeStyle}>{currentMenuType}</span> global context menu.
36+
</p>
3337
<button className="button" onClick={onClick}>
3438
{action} custom global context menu
3539
</button>
@@ -41,6 +45,6 @@ const GlobalMenuExample = () => {
4145
)}
4246
</React.Fragment>
4347
);
44-
};
48+
});
4549

46-
export default GlobalMenuExample;
50+
export default GlobalMenuExamplePage;

docs/components/BasicLocalMenu.js

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,35 @@
11
import React, { useRef } from 'react';
22
import { ContextMenuWrapper, useContextMenuHandlers } from 'react-context-menu-wrapper';
33

4-
const LocalMenuExample = () => {
5-
const menuId = 'my-component-local-menu';
6-
const bannerRef = useRef();
4+
const menuId = 'my-component-local-menu';
75

8-
// This hook attaches relevant DOM event listeners to `bannerRef`.
6+
const MyContextMenuTrigger = React.memo(() => {
7+
const bannerRef = useRef();
98
useContextMenuHandlers(bannerRef, { id: menuId });
109

10+
return (
11+
<div ref={bannerRef} className="banner">
12+
Right click or long-press this box
13+
</div>
14+
);
15+
});
16+
17+
const LocalMenuExample = React.memo(() => {
1118
return (
1219
<React.Fragment>
13-
<div ref={bannerRef} className="banner">
14-
Right click or long-press this box
15-
</div>
20+
<MyContextMenuTrigger />
1621

1722
<ContextMenuWrapper id={menuId}>
1823
<div className="context-menu" style={{ backgroundColor: '#d6fffc' }}>
1924
<p>
20-
I am <strong>component-local</strong> context menu!
25+
This is a <strong>local</strong> menu!
2126
</p>
27+
<button>Dummy button #1</button>
28+
<button>Dummy button #2</button>
2229
</div>
2330
</ContextMenuWrapper>
2431
</React.Fragment>
2532
);
26-
};
33+
});
2734

2835
export default LocalMenuExample;

docs/components/SharedMenu.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useCallback, useRef, useState } from 'react';
22
import { ContextMenuWrapper, useContextMenuEvent, useContextMenuHandlers } from 'react-context-menu-wrapper';
33

4-
const MyContextMenu = ({ selection, toggleSelection }) => {
4+
const MyContextMenu = React.memo(({ selection, toggleSelection }) => {
55
const menuEvent = useContextMenuEvent();
66
if (!menuEvent) return null;
77

@@ -14,9 +14,9 @@ const MyContextMenu = ({ selection, toggleSelection }) => {
1414
<button onClick={onClick}>Toggle state</button>
1515
</div>
1616
);
17-
};
17+
});
1818

19-
const Box = ({ name, selected, menuId }) => {
19+
const Box = React.memo(({ name, selected, menuId }) => {
2020
const boxRef = useRef();
2121
useContextMenuHandlers(boxRef, { id: menuId, data: name });
2222

@@ -29,9 +29,9 @@ const Box = ({ name, selected, menuId }) => {
2929
<span>{text}</span>
3030
</div>
3131
);
32-
};
32+
});
3333

34-
const SharedMenu = () => {
34+
const SharedMenu = React.memo(() => {
3535
const [selection, setSelection] = useState({});
3636
const toggleSelection = useCallback(
3737
name => {
@@ -54,6 +54,6 @@ const SharedMenu = () => {
5454
</ContextMenuWrapper>
5555
</div>
5656
);
57-
};
57+
});
5858

5959
export default SharedMenu;

docs/markdown/0-Introduction.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
This library provides a simple wrapper for your context menus. It handles user interaction on desktop and mobile
2+
devices, and guarantees correct context menu positioning.
3+
4+
In an effort to stay unopinionated, this library does not provide any styling or additional components. It is up to you
5+
define your own styles and logic for the menu. This is useful when your CSS framework already provides dropdown menu
6+
styling (e.g. [Bulma](https://bulma.io/), [Bootstrap](https://getbootstrap.com/)) or when you need to implement some
7+
non-trivial logic for your context menu (e.g. [Ogma context menus](https://github.com/TimboKZ/Ogma) with tagging
8+
support).
9+
10+
**Features:**
11+
* Supports global and component-local context menus.
12+
* Supports long-press for mobile devices.
13+
* Lets you trigger context menus programmatically.
14+
* Lets you define context menu precedence.
15+
* Correctly displays context menus on nested components.
16+
* Correctly displays context menus near the borders of the window.
17+
* **Uses hooks!**
18+
* **Does not provide out-of-the-box styling.**

docs/markdown/1-Installation.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,23 @@
1-
Add React context menu wrapper to your project:
2-
1+
Add [`react-context-menu-wrapper`](https://www.npmjs.com/package/react-context-menu-wrapper) to your NPM project:
32
```bash
43
npm install --save react-context-menu-wrapper
54
```
65

7-
Now you can import it as any other React library:
6+
Then import it as usual:
87
```js
98
import {ContextMenuWrapper, useContextMenuHandlers} from 'react-context-menu-wrapper';
109
```
10+
11+
> **Before we start:** On mobile, the long press gesture can accidentally start the text selection process. To prevent
12+
> this, you can copy the CSS snippet shown below into your stylesheet and add `.no-select` class to your context menu
13+
> and relevant trigger components. For best user experience, make sure you only add this class on mobile devices and
14+
> that affected components are relatively small (e.g. don't add `.no-select` to the root component of your page).
15+
```css
16+
.no-select {
17+
-webkit-touch-callout: none;
18+
-webkit-user-select: none;
19+
-moz-user-select: none;
20+
-ms-user-select: none;
21+
user-select: none;
22+
}
23+
```

docs/markdown/2-Basic-menus.md

Lines changed: 0 additions & 62 deletions
This file was deleted.

docs/markdown/2-Global-menu.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
A quick demo first: **Right click anywhere on this page to bring up a custom global context menu.** On mobile devices,
2+
long-press gesture should bring up the menu too.
3+
4+
Defining a global context menu is very straightforward - you need to create a `<ContextMenuWrapper>` component and set
5+
its `global` property to `true`. For example:
6+
```jsx
7+
import React from 'react';
8+
import { ContextMenuWrapper } from 'react-context-menu-wrapper';
9+
10+
export const GlobalMenuExample = () => {
11+
return (
12+
<ContextMenuWrapper global={true}>
13+
<div className="context-menu">
14+
<p>I am a global context menu!</p>
15+
</div>
16+
</ContextMenuWrapper>
17+
);
18+
};
19+
```
20+
21+
Your custom global menu will replace your browser's default context menu. If you want to enable the default context menu
22+
again, just unmount the `<ContextMenuWrapper>` component.
23+
24+
**Remember** that `react-context-menu-wrapper` does not provide any styling out of the box, so you will need to define
25+
your own styles. If you're interested, the styles used for this demo are shown at the bottom of the page.
26+
27+
```js { "componentPath": "../components/BasicGlobalMenu.js" }
28+
```

docs/markdown/3-Local-menu.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
Local menus
2+
3+
Unlike global context menus, local menus do **not** replace your Browser's default context menu. Instead, local menus
4+
are attached to specific components. For example, you +
5+
6+
```jsx
7+
import React, { useRef } from 'react';
8+
import { ContextMenuWrapper, useContextMenuHandlers } from 'react-context-menu-wrapper';
9+
10+
export const LocalMenuExample = () => {
11+
const menuId = 'my-component-local-menu';
12+
const bannerRef = useRef();
13+
14+
// This hook attaches relevant DOM event listeners to `bannerRef`.
15+
useContextMenuHandlers(bannerRef, { id: menuId });
16+
17+
return (
18+
<React.Fragment>
19+
{/* We attach `bannerRef` to the component that will trigger the context menu: */}
20+
<div ref={bannerRef} className="banner">Right click or long-press this box</div>
21+
22+
<ContextMenuWrapper id={menuId}>
23+
<div className="context-menu">
24+
<p>I am component-local context menu!</p>
25+
</div>
26+
</ContextMenuWrapper>
27+
</React.Fragment>
28+
);
29+
};
30+
```
31+
32+
```js { "componentPath": "../components/BasicLocalMenu.js" }
33+
```
File renamed without changes.

0 commit comments

Comments
 (0)