feat(dashboard-v2): implement PopoverMenu
This commit is contained in:
parent
cb5a162fe4
commit
cd425240a0
|
@ -0,0 +1,90 @@
|
||||||
|
import { Children, cloneElement, useRef, useState } from "react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import { useClickAway } from "react-use";
|
||||||
|
import styled, { css, keyframes } from "styled-components";
|
||||||
|
|
||||||
|
const dropDown = keyframes`
|
||||||
|
0% {
|
||||||
|
transform: scaleY(0);
|
||||||
|
}
|
||||||
|
80% {
|
||||||
|
transform: scaleY(1.1);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scaleY(1);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Container = styled.div.attrs({ className: "relative inline-flex" })``;
|
||||||
|
|
||||||
|
const Flyout = styled.ul.attrs({
|
||||||
|
className: `absolute right-0 z-10 py-2
|
||||||
|
border rounded bg-white
|
||||||
|
overflow-hidden pointer-events-none
|
||||||
|
shadow-md shadow-palette-200/50
|
||||||
|
pointer-events-auto h-auto overflow-visible border-primary`,
|
||||||
|
})`
|
||||||
|
top: calc(100% + 2px);
|
||||||
|
animation: ${css`
|
||||||
|
${dropDown} 0.1s ease-in-out
|
||||||
|
`};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Option = styled.li.attrs({
|
||||||
|
className: `font-sans text-xs uppercase
|
||||||
|
relative pl-3 pr-5 py-1
|
||||||
|
text-palette-600 cursor-pointer
|
||||||
|
hover:text-primary hover:font-normal
|
||||||
|
active:text-primary active:font-normal
|
||||||
|
|
||||||
|
before:content-[initial] before:absolute before:left-0 before:h-3 before:w-0.5 before:bg-primary before:top-1.5
|
||||||
|
hover:before:content-['']`,
|
||||||
|
})``;
|
||||||
|
|
||||||
|
export const PopoverMenu = ({ options, children, openClassName, ...props }) => {
|
||||||
|
const containerRef = useRef();
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
useClickAway(containerRef, () => setOpen(false));
|
||||||
|
|
||||||
|
const handleChoice = (callback) => () => {
|
||||||
|
setOpen(false);
|
||||||
|
callback();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container ref={containerRef} {...props}>
|
||||||
|
{Children.only(
|
||||||
|
cloneElement(children, {
|
||||||
|
onClick: () => setOpen((open) => !open),
|
||||||
|
className: `${children.props.className ?? ""} ${open ? openClassName : ""}`,
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
{open && (
|
||||||
|
<Flyout>
|
||||||
|
{options.map(({ label, callback }) => (
|
||||||
|
<Option key={label} onClick={handleChoice(callback)}>
|
||||||
|
{label}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</Flyout>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
PopoverMenu.propTypes = {
|
||||||
|
/**
|
||||||
|
* Accepts a single child node that will become a menu toggle.
|
||||||
|
*/
|
||||||
|
children: PropTypes.element.isRequired,
|
||||||
|
/**
|
||||||
|
* Positions in the menu
|
||||||
|
*/
|
||||||
|
options: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({
|
||||||
|
label: PropTypes.string.isRequired,
|
||||||
|
callback: PropTypes.func.isRequired,
|
||||||
|
})
|
||||||
|
).isRequired,
|
||||||
|
};
|
Reference in New Issue