Skip to content

Commit

Permalink
refactor: replace Stepper for MUI's List (#1467)
Browse files Browse the repository at this point in the history
* refactor: replace Stepper for MUI List. Prefer custom svg over MUI icons

* fix leftover naming

* replace MUI icons by custom svgs

* use sx palette

* fix isConfirmed logic
  • Loading branch information
DiogoSoaress authored Jan 18, 2023
1 parent db190fc commit 7e6aa6c
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 104 deletions.
3 changes: 3 additions & 0 deletions public/images/common/cancel.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions public/images/common/circle-check.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/images/common/circle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions public/images/common/created.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/images/common/dot.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
223 changes: 130 additions & 93 deletions src/components/transactions/TxSigners/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { useState, type ReactElement } from 'react'
import type { Palette } from '@mui/material'
import { Box, Link, Step, StepConnector, StepContent, StepLabel, Stepper, type StepProps } from '@mui/material'
import FiberManualRecordIcon from '@mui/icons-material/FiberManualRecord'
import AddCircleIcon from '@mui/icons-material/AddCircle'
import RadioButtonUncheckedOutlinedIcon from '@mui/icons-material/RadioButtonUncheckedOutlined'
import CheckCircleIcon from '@mui/icons-material/CheckCircle'
import CancelIcon from '@mui/icons-material/Cancel'
import {
Box,
Link,
List,
ListItem,
ListItemIcon,
ListItemText,
type Palette,
SvgIcon,
Typography,
type ListItemIconProps,
} from '@mui/material'
import type {
AddressEx,
DetailedExecutionInfo,
TransactionDetails,
TransactionSummary,
Expand All @@ -20,17 +24,36 @@ import EthHashInfo from '@/components/common/EthHashInfo'

import css from './styles.module.css'
import useSafeInfo from '@/hooks/useSafeInfo'
import CreatedIcon from '@/public/images/common/created.svg'
import DotIcon from '@/public/images/common/dot.svg'
import CircleIcon from '@/public/images/common/circle.svg'
import CheckIcon from '@/public/images/common/circle-check.svg'
import CancelIcon from '@/public/images/common/cancel.svg'

// Icons

const TxCreationIcon = () => <AddCircleIcon className={css.icon} />
const TxRejectionIcon = () => <CancelIcon className={css.icon} />
const CheckIcon = () => <CheckCircleIcon className={css.icon} />

const CircleIcon = () => (
<RadioButtonUncheckedOutlinedIcon className={css.icon} sx={{ stroke: 'currentColor', strokeWidth: '1px' }} />
const Created = () => (
<SvgIcon
component={CreatedIcon}
inheritViewBox
className={css.icon}
sx={{
'& path:last-of-type': { fill: ({ palette }) => palette.background.paper },
}}
/>
)
const MissingConfirmation = () => <SvgIcon component={CircleIcon} inheritViewBox className={css.icon} />
const Check = () => (
<SvgIcon
component={CheckIcon}
inheritViewBox
className={css.icon}
sx={{
'& path:last-of-type': { fill: ({ palette }) => palette.background.paper },
}}
/>
)
const DotIcon = () => <FiberManualRecordIcon className={css.icon} />
const Cancel = () => <SvgIcon component={CancelIcon} inheritViewBox className={css.icon} />
const Dot = () => <SvgIcon component={DotIcon} inheritViewBox className={css.dot} />

enum StepState {
CONFIRMED = 'CONFIRMED',
Expand All @@ -49,19 +72,15 @@ const getStepColor = (state: StepState, palette: Palette): string => {
return colors[state]
}

type StyledStepProps = {
$bold?: boolean
const StyledListItemIcon = ({
$state,
...rest
}: {
$state: StepState
}
const StyledStep = ({ $bold, $state, sx, ...rest }: StyledStepProps & StepProps) => (
<Step
} & ListItemIconProps) => (
<ListItemIcon
sx={({ palette }) => ({
'.MuiStepLabel-label': {
fontWeight: `${$bold ? 'bold' : 'normal'} !important`,
color: palette.text.primary,
fontSize: '16px !important',
},
'.MuiStepLabel-iconContainer': {
'.MuiSvgIcon-root': {
color: getStepColor($state, palette),
alignItems: 'center',
},
Expand All @@ -82,21 +101,12 @@ const shouldHideConfirmations = (detailedExecutionInfo?: DetailedExecutionInfo):
return isConfirmed || detailedExecutionInfo.confirmations.length > 3
}

const getConfirmationStep = ({ value, name }: AddressEx, key: string | undefined = undefined): ReactElement => (
<StyledStep key={key} $state={StepState.CONFIRMED}>
<StepLabel icon={<DotIcon />}>
<EthHashInfo address={value} name={name} hasExplorer showCopyButton />
</StepLabel>
</StyledStep>
)

export const TxSigners = ({
txDetails,
txSummary,
}: {
type TxSignersProps = {
txDetails: TransactionDetails
txSummary: TransactionSummary
}): ReactElement | null => {
}

export const TxSigners = ({ txDetails, txSummary }: TxSignersProps): ReactElement | null => {
const { detailedExecutionInfo, txInfo, txId } = txDetails
const [hideConfirmations, setHideConfirmations] = useState<boolean>(shouldHideConfirmations(detailedExecutionInfo))
const isPending = useIsPending(txId)
Expand All @@ -116,63 +126,90 @@ export const TxSigners = ({
const confirmationsCount = confirmations.length
const canExecute = wallet?.address ? isExecutable(txSummary, wallet.address, safe) : false
const confirmationsNeeded = confirmationsRequired - confirmations.length
const isConfirmed = confirmationsNeeded <= 0 || isPending || canExecute
const isConfirmed = confirmationsNeeded <= 0 || canExecute

return (
<Stepper
orientation="vertical"
nonLinear
connector={<StepConnector sx={{ padding: '3px 0' }} />}
className={css.stepper}
>
{isCancellationTxInfo(txInfo) ? (
<StyledStep $bold $state={StepState.ERROR}>
<StepLabel icon={<TxRejectionIcon />}>On-chain rejection created</StepLabel>
</StyledStep>
<>
<List className={css.signers}>
<ListItem>
{isCancellationTxInfo(txInfo) ? (
<>
<StyledListItemIcon $state={StepState.ERROR}>
<Cancel />
</StyledListItemIcon>
<ListItemText primaryTypographyProps={{ fontWeight: 700 }}>On-chain rejection created</ListItemText>
</>
) : (
<>
<StyledListItemIcon $state={StepState.CONFIRMED}>
<Created />
</StyledListItemIcon>
<ListItemText primaryTypographyProps={{ fontWeight: 700 }}>Created</ListItemText>
</>
)}
</ListItem>

<ListItem>
<StyledListItemIcon $state={isConfirmed ? StepState.CONFIRMED : StepState.ACTIVE}>
{isConfirmed ? <Check /> : <MissingConfirmation />}
</StyledListItemIcon>
<ListItemText primaryTypographyProps={{ fontWeight: 700 }}>
Confirmations{' '}
<Box className={css.confirmationsTotal}>({`${confirmationsCount} of ${confirmationsRequired}`})</Box>
</ListItemText>
</ListItem>
{!hideConfirmations &&
confirmations.map(({ signer }) => (
<ListItem key={signer.value} sx={{ py: 0 }}>
<StyledListItemIcon $state={StepState.CONFIRMED}>
<Dot />
</StyledListItemIcon>
<ListItemText>
<EthHashInfo address={signer.value} name={signer.name} hasExplorer showCopyButton />
</ListItemText>
</ListItem>
))}
{confirmations.length > 0 && (
<ListItem>
<StyledListItemIcon $state={StepState.CONFIRMED}>
<Dot />
</StyledListItemIcon>
<ListItemText>
<Link component="button" onClick={toggleHide} fontSize="medium">
{hideConfirmations ? 'Show all' : 'Hide all'}
</Link>
</ListItemText>
</ListItem>
)}
<ListItem>
<StyledListItemIcon $state={executor ? StepState.CONFIRMED : StepState.DISABLED}>
{executor ? <Check /> : <MissingConfirmation />}
</StyledListItemIcon>
<ListItemText primaryTypographyProps={{ fontWeight: 700 }}>
{executor ? 'Executed' : isPending ? 'Executing' : 'Can be executed'}
</ListItemText>
</ListItem>
</List>
{executor ? (
<Box className={css.listFooter}>
<EthHashInfo
address={executor.value}
name={executor.name}
customAvatar={executor.logoUri}
hasExplorer
showCopyButton
/>
</Box>
) : (
<StyledStep $bold $state={StepState.CONFIRMED}>
<StepLabel icon={<TxCreationIcon />}>Created</StepLabel>
</StyledStep>
)}
<StyledStep $bold $state={isConfirmed ? StepState.CONFIRMED : StepState.ACTIVE}>
<StepLabel icon={isConfirmed ? <CheckIcon /> : <CircleIcon />}>
Confirmations{' '}
<Box className={css.confirmationsTotal}>({`${confirmationsCount} of ${confirmationsRequired}`})</Box>
</StepLabel>
</StyledStep>
{!hideConfirmations && confirmations.map(({ signer }) => getConfirmationStep(signer, signer.value))}
{confirmations.length > 0 && (
<StyledStep $state={StepState.CONFIRMED}>
<StepLabel icon={<DotIcon />}>
<Link component="button" onClick={toggleHide} fontSize="medium">
{hideConfirmations ? 'Show all' : 'Hide all'}
</Link>
</StepLabel>
</StyledStep>
)}
<StyledStep expanded $bold $state={executor ? StepState.CONFIRMED : StepState.DISABLED}>
<StepLabel icon={executor ? <CheckIcon /> : <CircleIcon />} sx={{ marginBottom: 1, fontWeight: 'bold' }}>
{executor ? 'Executed' : isPending ? 'Executing' : 'Can be executed'}
</StepLabel>
{executor ? (
<StepContent>
<EthHashInfo
address={executor.value}
name={executor.name}
customAvatar={executor.logoUri}
hasExplorer
showCopyButton
/>
</StepContent>
) : (
!isConfirmed && (
<StepContent sx={({ palette }) => ({ color: palette.border.main })}>
!isConfirmed && (
<Box className={css.listFooter}>
<Typography sx={({ palette }) => ({ color: palette.border.main })}>
Can be executed once the threshold is reached
</StepContent>
)
)}
</StyledStep>
</Stepper>
</Typography>
</Box>
)
)}
</>
)
}

Expand Down
46 changes: 35 additions & 11 deletions src/components/transactions/TxSigners/styles.module.css
Original file line number Diff line number Diff line change
@@ -1,26 +1,50 @@
.icon {
height: 20px;
width: 20px;
margin-right: 2px;
height: 16px;
width: 16px;
}

.stepper {
.dot {
height: 10px;
width: 10px;
}

.signers {
padding: 0;
}

.stepper :global .MuiStepConnector-line {
margin-left: -3px;
border-color: var(--color-border-light);
border-left-width: 2px;
min-height: 14px;
.signers::before {
content: '';
position: absolute;
border-left: 2px solid var(--color-border-light);
left: 15px;
top: 20px;
height: calc(100% - 40px);
}

.signers :global .MuiListItem-root:first-of-type {
padding-top: 0;
}

.stepper :global .MuiStepLabel-root {
padding: 0 !important;
.signers :global .MuiListItem-root {
padding-left: 0;
padding-right: 0;
}

.signers :global .MuiListItemIcon-root {
color: var(--color-primary-main);
justify-content: center;
min-width: 32px;
padding: var(--space-1) 0;
background-color: var(--color-background-paper);
}

.confirmationsTotal {
color: var(--color-border-main);
display: inline;
font-weight: normal;
}

.listFooter {
margin-left: var(--space-4);
margin-top: calc(var(--space-1) * -1);
}

0 comments on commit 7e6aa6c

Please sign in to comment.