Skip to content

Commit 20f92ac

Browse files
committed
Final code commit for now
1 parent 3b7ebca commit 20f92ac

File tree

5 files changed

+225
-6
lines changed

5 files changed

+225
-6
lines changed

README.md

Lines changed: 139 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,146 @@
1-
# quip-react-menu
1+
# quip-react-toolbar
2+
3+
`quip-react-toolbar` is a rendereless React component for quickly and easily using the [document menu and toolbar](https://salesforce.quip.com/dev/liveapps/documentation#menus-configuring-menu-commands) in [Quip Live Apps](https://salesforce.quip.com/dev/liveapps/documentation). This component is designed to be declarative and reactive, taking advantage of React's built-in state management and update technology.
4+
5+
This component allows developers to easily create and update the toolbar without needing to use [`quip.apps.updateToolbar`](https://salesforce.quip.com/dev/liveapps/documentation#menus-updating-the-toolbar). Instead, React will automatically update the toolbar if the component state -- and thus, the toolbar state -- changes.
26

37
## Installation
48
```
5-
npm install https://github.com/herrevilkitten/quip-react-menu.git
9+
npm install https://github.com/herrevilkitten/quip-react-toolbar.git
610
```
711

812
## Usage
913
```
10-
import { MenuBar, Menu, MainMenu, MenuSeparator } from 'quip-react-menu';
11-
```
14+
import { MenuBar, Menu, MainMenu, MenuSeparator } from 'quip-react-toolbar';
15+
16+
export default class App extends React.Component {
17+
constructor(props) {
18+
super(props);
19+
this.state = {
20+
label: 'Hello',
21+
highlighted: false,
22+
disabled: false,
23+
};
24+
25+
this.handleChange = this.handleChange.bind(this);
26+
}
27+
28+
handleChange(event) {
29+
const target = event.target;
30+
const value = target.type === 'checkbox' ? target.checked : target.value;
31+
const name = target.name;
32+
33+
this.setState({
34+
[name]: value
35+
});
36+
}
37+
38+
menuHandler() {
39+
console.log('I am a menu that has been handled!');
40+
}
41+
42+
render() {
43+
return (
44+
<div>
45+
<div>
46+
<input
47+
name="highlighted"
48+
type="checkbox"
49+
checked={this.state.highlighted}
50+
onChange={this.handleChange} />
51+
Highlighted
52+
</div>
53+
<div>
54+
<input
55+
name="disabled"
56+
type="checkbox"
57+
checked={this.state.disabled}
58+
onChange={this.handleChange} />
59+
Disabled
60+
</div>
61+
<div>
62+
<input name="label" type="text" value={this.state.label} onChange={this.handleChange} />
63+
</div>
64+
<MenuBar>
65+
<MainMenu>
66+
<Menu highlighted={this.state.highlighted} label={this.state.label}>
67+
<Menu id="submenu" label="Submenu" handler={this.menuHandler}></Menu>
68+
</Menu>
69+
<MenuSeparator></MenuSeparator>
70+
</MainMenu>
71+
<MainMenu>
72+
<Menu label="Submenu" handler={this.menuHandler} sublabel="hello"></Menu>
73+
</MainMenu>
74+
<Menu highlighted={!this.state.highlighted} label={this.state.label + " !Highlighted"} handler={this.menuHandler}></Menu>
75+
<Menu disabled={this.state.disabled} label={this.state.label + " Disabled"} handler={this.menuHandler}></Menu>
76+
</MenuBar>
77+
</div>
78+
);
79+
}
80+
}
81+
```
82+
83+
### Configuring the Toolbar and Menu
84+
85+
The root of the menu system is the `MenuBar` component. All of the menu entries must be placed within it. The `MenuBar` can accept all of the other components in order to build the menu system. The menu is configured in the order that the components are defined in the JSX.
86+
87+
Any components that are inside of a `MainMenu` component will be placed into the live app's document menu. Separate `MainMenu` entries will be merged into a single menu tree. Anything that is a direct descendent of
88+
89+
#### Headers and Separators
90+
91+
In addition to defining headers with a `Menu`, HTML elements can be used. This is done to keep the component tree a little cleaner. Any "simple" HTML element (such as `<b>` or `<em>`) and plain text will be turned into a header or separator. For example:
92+
93+
```
94+
<MenuBar>
95+
<b>This is a header</b>
96+
<b>This is another header</b>
97+
</MenuBar>
98+
```
99+
100+
will create two headers inside of the live app's toolbar. If the header's label is `---` or `----`, it will create a separator instead.
101+
102+
```
103+
<MenuBar>
104+
<b>This is a header</b>
105+
----
106+
<b>This is another header</b>
107+
</MenuBar>
108+
```
109+
110+
### `MenuBar`
111+
112+
This is the root component of the menu system. It handles updating the toolbar and main menu based on the state of its child components.
113+
114+
### `MainMenu`
115+
116+
This component can only be used as a director child of `MenuBar`. Children of `MainMenu` will be placed into the live app's document menu as `subCommands` of `quip.apps.DocumentMenuCommands.MENU_MAIN`.
117+
118+
### `Menu`
119+
This component represents a [`quip.apps.MenuCommand`](https://salesforce.quip.com/dev/liveapps/documentation#menus-configuring-menu-commands) and shares most of the properties with it. There are some important differences, however.
120+
121+
#### Properties
122+
`id: string [optional]` -- a unique identifier for the menu. If one is not provided, it will be automatically generated by the component using a simple algorithm.
123+
124+
`label: string [optional]` -- No change from the Quip documentation.
125+
126+
`sublabel: string [optional]` -- No change from the Quip documentation.
127+
128+
`handler: function(string, Object, ?) [optional]` -- No change from the Quip documentation.
129+
130+
`isHeader: boolean [optional, default=False]` -- indicates whether or not this menu should be displayed as a header. If a `handler` property is not set and the component has no child menus, this property will be automatically set.
131+
132+
`actionId: string [optional]` -- No change from the Quip documentation.
133+
134+
`actionParams: Object [optional]` -- No change from the Quip documentation.
135+
136+
`actionStarted: function() [optional]` -- No change from the Quip documentation.
137+
138+
`highlighted: boolean [optional]` -- indicates whether this menu entry should be "highlighted" by adding the menu to [`highlightedCommandIds`](https://salesforce.quip.com/dev/liveapps/documentation#menus-updating-the-toolbar).
139+
140+
`disabled: boolean [optional]` -- indicates whether this menu entry is disabled by adding the menu to [`disabledCommandIds`](https://salesforce.quip.com/dev/liveapps/documentation#menus-updating-the-toolbar).
141+
142+
The `subCommands` configuration is automatically built from the component tree and will not be used if it is defined.
143+
144+
### `MenuSeparator`
145+
146+
This adds a `quip.apps.DocumentMenuCommands.SEPARATOR` to the menu.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "quip-react-menu",
2+
"name": "quip-react-toolbar",
33
"version": "1.0.0",
44
"description": "",
55
"main": "src/index.js",

src/MainMenu.jsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import Menu from './Menu.jsx';
22
import MenuSeparator from './MenuSeparator.jsx';
33

4+
import { createHeader } from './Menu.jsx';
5+
46
export default class MainMenu extends React.Component {
57
constructor(props) {
68
super(props);
@@ -49,6 +51,23 @@ export default class MainMenu extends React.Component {
4951
}
5052
currentMenu.subCommands.push(childMenuConfig.currentMenu.id);
5153
break;
54+
default:
55+
switch (typeof (child.type)) {
56+
case 'undefined':
57+
case 'string':
58+
const header = createHeader(child);
59+
if (header) {
60+
if (!currentMenu.subCommands) {
61+
currentMenu.subCommands = [];
62+
}
63+
currentMenu.subCommands.push(header.id);
64+
if (header.id !== quip.apps.DocumentMenuCommands.SEPARATOR) {
65+
menuConfig.menuCommands.push(header);
66+
}
67+
}
68+
break;
69+
}
70+
break;
5271
}
5372
}
5473

src/Menu.jsx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,38 @@ export const KEYS = [
1010
'sublabel',
1111
'handler',
1212
'isHeader',
13+
'actionId',
1314
'actionParams',
1415
'actionStarted',
1516
];
1617

18+
export function createHeader(label) {
19+
if (!label) {
20+
return;
21+
}
22+
if (typeof (label.type) === 'string') {
23+
if (label.props && typeof (label.props.children) === 'string') {
24+
label = label.props.children;
25+
} else {
26+
label = '';
27+
}
28+
}
29+
label = label || '';
30+
if (label.match(/^\-\-\-+$/)) {
31+
return {
32+
id: quip.apps.DocumentMenuCommands.SEPARATOR,
33+
};
34+
}
35+
const header = {
36+
id: `generated-menu-${assignedMenuId}`,
37+
label: label || '',
38+
isHeader: true,
39+
};
40+
assignedMenuId++;
41+
42+
return header;
43+
}
44+
1745
export default class Menu extends React.Component {
1846
constructor(props) {
1947
super(props);
@@ -81,6 +109,23 @@ export default class Menu extends React.Component {
81109
}
82110
currentMenu.subCommands.push(childMenuConfig.currentMenu.id);
83111
break;
112+
default:
113+
switch (typeof (child.type)) {
114+
case 'undefined':
115+
case 'string':
116+
const header = createHeader(child);
117+
if (header) {
118+
if (!currentMenu.subCommands) {
119+
currentMenu.subCommands = [];
120+
}
121+
currentMenu.subCommands.push(header.id);
122+
if (header.id !== quip.apps.DocumentMenuCommands.SEPARATOR) {
123+
menuConfig.menuCommands.push(header);
124+
}
125+
}
126+
break;
127+
}
128+
break;
84129
}
85130
}
86131
}

src/MenuBar.jsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import Menu from './Menu.jsx';
22
import MainMenu from './MainMenu.jsx';
33
import MenuSeparator from './MenuSeparator.jsx';
44

5+
import { createHeader } from './Menu.jsx';
6+
57
export default class MenuBar extends React.Component {
68
constructor(props) {
79
super(props);
@@ -52,14 +54,32 @@ export default class MenuBar extends React.Component {
5254
menuConfig.highlightedCommandIds.push(...childMenuConfig.highlightedCommandIds);
5355
break;
5456
case Menu:
55-
case MenuSeparator:
5657
childMenuConfig = child.type.prototype.toQuipMenu.call(child);
5758

5859
menuConfig.toolbarCommandIds.push(childMenuConfig.currentMenu.id);
5960
menuConfig.menuCommands.push(...childMenuConfig.menuCommands);
6061
menuConfig.disabledCommandIds.push(...childMenuConfig.disabledCommandIds);
6162
menuConfig.highlightedCommandIds.push(...childMenuConfig.highlightedCommandIds);
6263
break;
64+
case MenuSeparator:
65+
childMenuConfig = child.type.prototype.toQuipMenu.call(child);
66+
67+
menuConfig.toolbarCommandIds.push(childMenuConfig.currentMenu.id);
68+
break;
69+
default:
70+
switch (typeof (child.type)) {
71+
case 'undefined':
72+
case 'string':
73+
const header = createHeader(child);
74+
if (header) {
75+
menuConfig.toolbarCommandIds.push(header.id);
76+
if (header.id !== quip.apps.DocumentMenuCommands.SEPARATOR) {
77+
menuConfig.menuCommands.push(header);
78+
}
79+
}
80+
break;
81+
}
82+
break;
6383
}
6484
}
6585

0 commit comments

Comments
 (0)