Skip to content

Commit d4a0bae

Browse files
committed
feat: add ActionMenu component
Signed-off-by: Mike Murray <[email protected]>
1 parent 07b8ef0 commit d4a0bae

File tree

2 files changed

+155
-0
lines changed

2 files changed

+155
-0
lines changed
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import React, { Fragment } from "react";
2+
import PropTypes from "prop-types";
3+
import {
4+
Box,
5+
ClickAwayListener,
6+
ListItemText,
7+
Menu,
8+
MenuItem,
9+
MenuList,
10+
makeStyles
11+
} from "@material-ui/core";
12+
import ChevronDownIcon from "mdi-material-ui/ChevronDown";
13+
import Button from "../Button";
14+
15+
const useStyles = makeStyles((theme) => ({
16+
button: {
17+
paddingRight: theme.spacing(1.5)
18+
}
19+
}));
20+
21+
/**
22+
* @name ActionMenu
23+
* @param {Object} props Component props
24+
* @returns {React.Component} A React component
25+
*/
26+
const ActionMenu = React.forwardRef(function ActionMenu(props, ref) {
27+
const {
28+
children,
29+
onSelect,
30+
options,
31+
...otherProps
32+
} = props;
33+
const classes = useStyles();
34+
const [open, setOpen] = React.useState(false);
35+
const anchorRef = ref || React.useRef(null);
36+
37+
/**
38+
* Handle menu item click
39+
* @param {SyntheticEvent} event Event object
40+
* @param {Number} index Menu item index
41+
* @returns {undefined}
42+
*/
43+
function handleMenuItemClick(event, index) {
44+
const selectedOption = options[index];
45+
onSelect && onSelect(selectedOption, index);
46+
setOpen(false);
47+
}
48+
49+
/**
50+
* Toggle menu open
51+
* @returns {undefined}
52+
*/
53+
function handleToggle() {
54+
setOpen((prevOpen) => !prevOpen);
55+
}
56+
57+
/**
58+
* Handle menu close
59+
* @param {SyntheticEvent} event Event object
60+
* @returns {undefined}
61+
*/
62+
function handleClose(event) {
63+
if (anchorRef.current && anchorRef.current.contains(event.target)) {
64+
return;
65+
}
66+
67+
setOpen(false);
68+
}
69+
70+
return (
71+
<Fragment>
72+
<Button
73+
className={classes.button}
74+
onClick={handleToggle}
75+
ref={anchorRef}
76+
{...otherProps}
77+
>
78+
{children}
79+
<Box display="flex" paddingLeft={1}>
80+
<ChevronDownIcon />
81+
</Box>
82+
</Button>
83+
<Menu open={open} anchorEl={anchorRef.current}>
84+
<ClickAwayListener onClickAway={handleClose}>
85+
<MenuList disablePadding>
86+
<MenuItem key="default-label" disabled>
87+
<Box maxWidth={320} whiteSpace="normal">
88+
<ListItemText
89+
primary={children}
90+
/>
91+
</Box>
92+
</MenuItem>
93+
{options.map(({ label, details, isDisabled }, index) => (
94+
<MenuItem
95+
key={index}
96+
disabled={isDisabled}
97+
onClick={(event) => handleMenuItemClick(event, index)}
98+
>
99+
<Box maxWidth={320} whiteSpace="normal">
100+
<ListItemText
101+
primary={label}
102+
secondary={details}
103+
/>
104+
</Box>
105+
</MenuItem>
106+
))}
107+
</MenuList>
108+
</ClickAwayListener>
109+
</Menu>
110+
</Fragment>
111+
);
112+
});
113+
114+
ActionMenu.defaultProps = {
115+
color: "primary",
116+
variant: "outlined"
117+
};
118+
119+
ActionMenu.propTypes = {
120+
/**
121+
* The content of the Button
122+
*/
123+
children: PropTypes.node,
124+
/**
125+
* Override or extend the styles applied to the component.
126+
*/
127+
classes: PropTypes.object,
128+
/**
129+
* Options: `default` | `inherit` | `primary` | `secondary` | `error`
130+
*/
131+
color: PropTypes.string,
132+
/**
133+
* If `true`, the button will be disabled.
134+
*/
135+
disabled: PropTypes.bool, // eslint-disable-line
136+
/**
137+
* If `true`, the CircularProgress will be displayed and the button will be disabled.
138+
*/
139+
isWaiting: PropTypes.bool,
140+
/**
141+
* onSelect callback when an option is selected from the menu
142+
*/
143+
onSelect: PropTypes.func,
144+
/**
145+
* Menu options
146+
*/
147+
options: PropTypes.arrayOf(PropTypes.shape({
148+
details: PropTypes.string,
149+
isDisabled: PropTypes.bool,
150+
label: PropTypes.string.isRequired
151+
}))
152+
};
153+
154+
export default ActionMenu;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from "./ActionMenu";

0 commit comments

Comments
 (0)