create reuseable ActionMenu component
This commit is contained in:
@@ -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={
|
||||
|
||||
Reference in New Issue
Block a user