create reuseable ActionMenu component

This commit is contained in:
fmtabbara
2022-07-21 11:31:03 +01:00
parent e945c94afc
commit e68a2c2ae3
6 changed files with 170 additions and 222 deletions
+44
View File
@@ -0,0 +1,44 @@
import React, { useRef } from 'react';
import { MoreVertSharp } from '@mui/icons-material';
import { IconButton, ListItemIcon, ListItemText, Menu, MenuItem } from '@mui/material';
export const ActionsMenu: React.FC<{ open: boolean; onOpen: () => void; onClose: () => void }> = ({
children,
open,
onOpen,
onClose,
}) => {
const anchorEl: any = useRef<HTMLElement>();
return (
<>
<IconButton ref={anchorEl} onClick={onOpen}>
<MoreVertSharp />
</IconButton>
<Menu anchorEl={anchorEl.current} open={open} onClose={onClose}>
{children}
</Menu>
</>
);
};
export const ActionsMenuItem = ({
title,
description,
onClick,
Icon,
disabled,
}: {
title: string;
description?: string;
onClick?: () => void;
Icon?: React.ReactNode;
disabled?: boolean;
}) => {
return (
<MenuItem sx={{ p: 2 }} onClick={onClick} disabled={disabled}>
<ListItemIcon sx={{ color: 'text.primary' }}>{Icon}</ListItemIcon>
<ListItemText sx={{ color: 'text.primary' }} primary={title} secondary={description} />
</MenuItem>
);
};
@@ -1,19 +1,8 @@
import React from 'react';
import {
Box,
Button,
IconButton,
ListItemIcon,
ListItemText,
Menu,
MenuItem,
Stack,
Tooltip,
Typography,
} from '@mui/material';
import { MoreVertSharp } from '@mui/icons-material';
import React, { useState } from 'react';
import { Box, Button, ListItemIcon, ListItemText, MenuItem, Stack, Tooltip, Typography } from '@mui/material';
import { DelegationEventKind } from '@nymproject/types';
import { Delegate, Undelegate } from '../../svg-icons';
import { ActionsMenu, ActionsMenuItem } from '../ActionsMenu';
import { DelegateListItemPending } from './types';
export type DelegationListItemActions = 'delegate' | 'undelegate' | 'redeem' | 'compound';
@@ -100,17 +89,14 @@ export const DelegationsActionsMenu: React.FC<{
disableRedeemingRewards?: boolean;
disableCompoundRewards?: boolean;
}> = ({ disableRedeemingRewards, disableCompoundRewards, onActionClick, isPending }) => {
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};
const [isOpen, setIsOpen] = useState(false);
const handleClose = () => setAnchorEl(null);
const handleOpenMenu = () => setIsOpen(true);
const handleOnClose = () => setIsOpen(false);
const handleActionSelect = (action: DelegationListItemActions) => {
handleClose();
onActionClick?.(action);
handleOnClose();
};
if (isPending) {
@@ -126,37 +112,28 @@ export const DelegationsActionsMenu: React.FC<{
}
return (
<>
<IconButton onClick={handleClick}>
<MoreVertSharp />
</IconButton>
<Menu anchorEl={anchorEl} open={open} onClose={handleClose}>
<DelegationActionsMenuItem
title="Delegate more"
Icon={<Delegate />}
onClick={() => handleActionSelect?.('delegate')}
/>
<DelegationActionsMenuItem
title="Undelegate"
Icon={<Undelegate />}
onClick={() => handleActionSelect?.('undelegate')}
disabled={false}
/>
<DelegationActionsMenuItem
title="Redeem"
description="Trasfer your rewards to your balance"
Icon={<Typography sx={{ pl: 1 }}>R</Typography>}
onClick={() => handleActionSelect?.('redeem')}
disabled={disableRedeemingRewards}
/>
<DelegationActionsMenuItem
title="Compound"
description="Add your rewards to this delegation"
Icon={<Typography sx={{ pl: 1 }}>C</Typography>}
onClick={() => handleActionSelect?.('compound')}
disabled={disableCompoundRewards}
/>
</Menu>
</>
<ActionsMenu open={isOpen} onOpen={handleOpenMenu} onClose={handleOnClose}>
<ActionsMenuItem title="Delegate more" Icon={<Delegate />} onClick={() => handleActionSelect('delegate')} />
<ActionsMenuItem
title="Undelegate"
Icon={<Undelegate />}
onClick={() => handleActionSelect('undelegate')}
disabled={false}
/>
<ActionsMenuItem
title="Redeem"
description="Trasfer your rewards to your balance"
Icon={<Typography sx={{ pl: 1 }}>R</Typography>}
onClick={() => handleActionSelect('redeem')}
disabled={disableRedeemingRewards}
/>
<ActionsMenuItem
title="Compound"
description="Add your rewards to this delegation"
Icon={<Typography sx={{ pl: 1 }}>C</Typography>}
onClick={() => handleActionSelect('compound')}
disabled={disableCompoundRewards}
/>
</ActionsMenu>
);
};
@@ -1,9 +1,9 @@
import * as React from 'react';
import { IconButton, Menu, MenuItem, Stack, Typography } from '@mui/material';
import { MoreVert } from '@mui/icons-material';
import { useEffect } from 'react';
import { MixnodeFlow } from '../mixnode/types';
import React, { useState } from 'react';
import { Typography } from '@mui/material';
import { ActionsMenu, ActionsMenuItem } from 'src/components/ActionsMenu';
import { Bond as BondIcon, Unbond as UnbondIcon } from '../../../svg-icons';
import { GatewayFlow } from '../gateway/types';
import { MixnodeFlow } from '../mixnode/types';
interface Item {
label: string;
@@ -12,72 +12,42 @@ interface Item {
description?: string;
}
interface Props {
onFlowChange: (flow: MixnodeFlow | GatewayFlow) => void;
items: Item[];
onOpen: (open: boolean) => void;
}
const NodeMenu = ({ onFlowChange }: { onFlowChange: (flow: MixnodeFlow) => void }) => {
const [isOpen, setIsOpen] = useState(false);
const NodeMenu = ({ onFlowChange, items, onOpen }: Props) => {
const [menuAnchorEl, setMenuAnchorEl] = React.useState<null | HTMLElement>(null);
const menuOpen = Boolean(menuAnchorEl);
const handleOpen = () => setIsOpen(true);
const handleClose = () => setIsOpen(false);
useEffect(() => {
onOpen(menuOpen);
}, [menuOpen]);
const handleMenuClose = () => {
setMenuAnchorEl(null);
};
const onClick = (flow: MixnodeFlow | GatewayFlow) => {
const handleActionClick = (flow: MixnodeFlow | GatewayFlow) => {
onFlowChange(flow);
handleMenuClose();
handleClose();
};
return (
<>
<IconButton
sx={{ fontSize: '1rem', padding: 0 }}
id="menu-button"
onClick={(event) => setMenuAnchorEl(event.currentTarget)}
aria-controls={menuOpen ? 'node-menu' : undefined}
aria-haspopup="true"
aria-expanded={menuOpen ? 'true' : undefined}
disableTouchRipple
disableFocusRipple
disableRipple
>
<MoreVert fontSize="inherit" sx={{ color: menuOpen ? 'primary.main' : 'text.primary' }} />
</IconButton>
<Menu
open={menuOpen}
anchorEl={menuAnchorEl}
onClose={handleMenuClose}
id="node-menu"
sx={{
'& .MuiPaper-root': {
borderRadius: '4px',
},
}}
>
{items.map(({ label, flow, icon, description }) => (
<MenuItem onClick={() => onClick(flow)} key={flow} sx={{ px: 1.6 }} disableRipple>
<Stack direction="row" spacing={1}>
<Stack display="flex" alignItems="flex-end" width={16} alignSelf="start">
{icon}
</Stack>
<Stack alignItems="flex-start" justifyContent="flex-start">
<Typography>{label}</Typography>
<Typography variant="subtitle2" color="nym.text.muted">
{description}
</Typography>
</Stack>
</Stack>
</MenuItem>
))}
</Menu>
</>
<ActionsMenu open={isOpen} onOpen={handleOpen} onClose={handleClose}>
<ActionsMenuItem
title="Bond more"
Icon={<BondIcon fontSize="inherit" />}
onClick={() => handleActionClick('bondMore')}
/>
<ActionsMenuItem
title="Unbond"
Icon={<UnbondIcon fontSize="inherit" />}
onClick={() => handleActionClick('unbond')}
/>
<ActionsMenuItem
title="Compound rewards"
Icon={<Typography sx={{ pl: 1 }}>C</Typography>}
description="Add operator rewards to bond"
onClick={() => handleActionClick('compound')}
/>
<ActionsMenuItem
title="Redeem rewards"
Icon={<Typography sx={{ pl: 1 }}>R</Typography>}
description="Add your rewards to bonding pool"
onClick={() => handleActionClick('redeem')}
/>
</ActionsMenu>
);
};
@@ -40,7 +40,7 @@ const CellHeader = ({ children, tooltip, sx, size, align, color }: TableHeader)
);
export type Header = Omit<TableHeader, 'children'> & { header?: React.ReactNode; id: string };
export type Cell = Omit<TableCell, 'children'> & { cell: React.ReactNode; id: string };
export type Cell = Omit<TableCell, 'children'> & { cell: React.ReactNode; id?: string };
export interface TableProps {
headers: Header[];
@@ -1,13 +1,11 @@
import React, { useMemo, useState } from 'react';
import { useTheme } from '@mui/material/styles';
import EditIcon from '@mui/icons-material/Edit';
import { BondedGateway } from '../../../context';
import { NodeTable, Cell, Header, NodeMenu } from '../components';
import { GatewayFlow } from './types';
import Unbond from '../unbond';
import { NymCard } from 'src/components';
import { useState } from 'react';
import { Stack, Typography } from '@mui/material';
import { NymCard } from 'src/components';
import { IdentityKey } from 'src/components/IdentityKey';
import { BondedGateway } from '../../../context';
import { Cell, Header, NodeMenu, NodeTable } from '../components';
import Unbond from '../unbond';
import { GatewayFlow } from './types';
const headers: Header[] = [
{
@@ -29,36 +27,24 @@ const headers: Header[] = [
const GatewayCard = ({ gateway }: { gateway: BondedGateway }) => {
const { ip, bond } = gateway;
const [flow, setFlow] = useState<GatewayFlow>(null);
const [nodeMenuOpen, setNodeMenuOpen] = useState(false);
const theme = useTheme();
const cells: Cell[] = useMemo(
() => [
{
cell: ip,
id: 'ip-cell',
sx: { pl: 0 },
},
{
cell: `${bond.amount} ${bond.denom}`,
id: 'bond-cell',
},
{
cell: (
<NodeMenu
onFlowChange={(newFlow) => setFlow(newFlow as GatewayFlow)}
onOpen={(open) => setNodeMenuOpen(open)}
items={[{ label: 'Unbond', flow: 'unbond', icon: <EditIcon fontSize="inherit" /> }]}
/>
),
id: 'menu-button-cell',
align: 'center',
size: 'small',
sx: { backgroundColor: nodeMenuOpen ? '#FB6E4E0D' : undefined, px: 0 },
},
],
[gateway, theme, nodeMenuOpen],
);
const cells: Cell[] = [
{
cell: ip,
id: 'ip-cell',
sx: { pl: 0 },
},
{
cell: `${bond.amount} ${bond.denom}`,
id: 'bond-cell',
},
{
cell: <NodeMenu onFlowChange={(newFlow) => setFlow(newFlow as GatewayFlow)} />,
id: 'menu-button-cell',
align: 'center',
},
];
return (
<NymCard
title={
@@ -1,4 +1,4 @@
import { useMemo, useState } from 'react';
import { useState } from 'react';
import { Button, Stack, Typography } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import { Link } from '@nymproject/react/link/Link';
@@ -6,7 +6,6 @@ import { NymCard } from 'src/components';
import { IdentityKey } from 'src/components/IdentityKey';
import { NodeStatus } from 'src/components/NodeStatus';
import { BondedMixnode } from '../../../context';
import { Bond as BondIcon, Unbond as UnbondIcon } from '../../../svg-icons';
import { Node as NodeIcon } from '../../../svg-icons/node';
import { Cell, Header, NodeMenu, NodeTable } from '../components';
import Unbond from '../unbond';
@@ -62,72 +61,44 @@ const headers: Header[] = [
const MixnodeCard = ({ mixnode }: { mixnode: BondedMixnode }) => {
const { stake, bond, stakeSaturation, profitMargin, nodeRewards, operatorRewards, delegators } = mixnode;
const [flow, setFlow] = useState<MixnodeFlow>(null);
const [nodeMenuOpen, setNodeMenuOpen] = useState(false);
const theme = useTheme();
const cells: Cell[] = useMemo(
() => [
{
cell: `${stake.amount} ${stake.denom}`,
id: 'stake-cell',
sx: { pl: 0 },
},
{
cell: `${bond.amount} ${bond.denom}`,
id: 'bond-cell',
},
{
cell: `${stakeSaturation}%`,
id: 'stake-saturation-cell',
color: stakeSaturation > 100 ? theme.palette.nym.nymWallet.selectionChance.underModerate : undefined,
},
{
cell: `${profitMargin}%`,
id: 'pm-cell',
},
{
cell: `${nodeRewards.amount} ${nodeRewards.denom}`,
id: 'node-rewards-cell',
},
{
cell: `${operatorRewards.amount} ${operatorRewards.denom}`,
id: 'operator-rewards-cell',
},
{
cell: delegators,
id: 'delegators-cell',
},
{
cell: (
<NodeMenu
onFlowChange={(newFlow) => setFlow(newFlow as MixnodeFlow)}
onOpen={(open) => setNodeMenuOpen(open)}
items={[
{ label: 'Bond more', flow: 'bondMore', icon: <BondIcon fontSize="inherit" /> },
{ label: 'Unbond', flow: 'unbond', icon: <UnbondIcon fontSize="inherit" /> },
{
label: 'Compound rewards',
flow: 'compound',
icon: <Typography fontWeight={700}>C</Typography>,
description: 'Add operator rewards to bond',
},
{
label: 'Redeem rewards',
flow: 'redeem',
icon: <Typography fontWeight={700}>R</Typography>,
description: 'Add your rewards to bonding pool',
},
]}
/>
),
id: 'menu-button-cell',
align: 'center',
size: 'small',
sx: { backgroundColor: nodeMenuOpen ? '#FB6E4E0D' : undefined, px: 0 },
},
],
[mixnode, theme, nodeMenuOpen],
);
const cells: Cell[] = [
{
cell: `${stake.amount} ${stake.denom}`,
id: 'stake-cell',
sx: { pl: 0 },
},
{
cell: `${bond.amount} ${bond.denom}`,
id: 'bond-cell',
},
{
cell: `${stakeSaturation}%`,
id: 'stake-saturation-cell',
color: stakeSaturation > 100 ? theme.palette.nym.nymWallet.selectionChance.underModerate : undefined,
},
{
cell: `${profitMargin}%`,
id: 'pm-cell',
},
{
cell: `${nodeRewards.amount} ${nodeRewards.denom}`,
id: 'node-rewards-cell',
},
{
cell: `${operatorRewards.amount} ${operatorRewards.denom}`,
id: 'operator-rewards-cell',
},
{
cell: delegators,
id: 'delegators-cell',
},
{
cell: <NodeMenu onFlowChange={(newFlow) => setFlow(newFlow)} />,
},
];
return (
<NymCard
title={