Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Graph network dropdown #667

Merged
merged 1 commit into from
Jul 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .changelog/667.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Graph network dropdown
85 changes: 66 additions & 19 deletions src/app/components/Select/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import SelectUnstyled, { SelectProps, selectClasses, SelectRootSlotProps } from '@mui/base/Select'
import Option, { optionClasses } from '@mui/base/Option'
import Popper from '@mui/base/Popper'
import Popper, { PopperPlacementType } from '@mui/base/Popper'
import { styled } from '@mui/material/styles'
import Box from '@mui/material/Box'
import {
Expand All @@ -21,8 +21,11 @@ import ExpandLessIcon from '@mui/icons-material/ExpandLess'
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
import { COLORS } from '../../../styles/theme/colors'
import { useTranslation } from 'react-i18next'
import { WithOptionalOwnerState } from '@mui/base/utils'
import { SelectPopperSlotProps, SelectSlots } from '@mui/base/Select/Select.types'
import * as React from 'react'

const StyledButton = styled(Button)(({ theme }) => ({
export const StyledSelectButton = styled(Button)(({ theme }) => ({
height: '36px',
minWidth: '135px',
padding: `0 ${theme.spacing(4)}`,
Expand All @@ -35,7 +38,7 @@ const StyledButton = styled(Button)(({ theme }) => ({
},
}))

const StyledListbox = styled('ul')(({ theme }) => ({
export const StyledSelectListbox = styled('ul')(({ theme }) => ({
boxSizing: 'border-box',
padding: theme.spacing(0),
margin: theme.spacing(3, 0),
Expand All @@ -47,7 +50,7 @@ const StyledListbox = styled('ul')(({ theme }) => ({
filter: 'drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25))',
}))

const StyledOption = styled(Option)(({ theme }) => ({
export const StyledSelectOption = styled(Option)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
boxSizing: 'border-box',
Expand Down Expand Up @@ -85,47 +88,85 @@ const TertiaryButton = forwardRef(
const { t } = useTranslation()

return (
<StyledButton {...restProps} ref={ref} color="tertiary">
<StyledSelectButton {...restProps} ref={ref} color="tertiary">
<Typography variant="select">{children ? children : t('select.placeholder')}</Typography>
{ownerState.open ? <ExpandLessIcon /> : <ExpandMoreIcon />}
</StyledButton>
</StyledSelectButton>
)
},
)

const CustomSelect = forwardRef(function CustomSelect<TValue extends string | number>(
props: SelectProps<TValue, false>,
{
root,
listbox,
popper,
placement,
...restProps
}: SelectProps<TValue, false> & SelectSlots<TValue, false> & Partial<SelectPopperSlotProps<TValue, false>>,
ref: ForwardedRef<HTMLButtonElement>,
) {
const slots: SelectProps<TValue, false>['slots'] = {
root: TertiaryButton,
listbox: StyledListbox,
popper: StyledPopper,
...props.slots,
root,
listbox,
popper,
...restProps.slots,
}

return <SelectUnstyled {...props} ref={ref} slots={slots} />
return (
<SelectUnstyled
{...restProps}
ref={ref}
slots={slots}
slotProps={{
popper: {
placement,
},
}}
/>
)
}) as <TValue extends string | number>(
props: SelectProps<TValue, false> & RefAttributes<HTMLButtonElement>,
props: SelectProps<TValue, false> &
SelectSlots<TValue, false> &
Partial<SelectPopperSlotProps<TValue, false>> &
RefAttributes<HTMLButtonElement>,
) => JSX.Element

export interface SelectOptionBase {
label: string | number
value: string | number
}

const SelectOption = <T extends SelectOptionBase>({ value, label }: T): ReactElement => (
<StyledSelectOption key={value} value={value}>
<Typography variant="select">{label}</Typography>
</StyledSelectOption>
)

interface SelectCmpProps<T extends SelectOptionBase> {
label?: string
options: T[]
defaultValue?: T['value']
handleChange?: (selectedOption: T['value'] | null) => void
placement?: PopperPlacementType
className?: string
root?: React.ElementType
Option?: typeof SelectOption<T> | undefined
listbox?: React.ElementType
popper?: React.ComponentType<WithOptionalOwnerState<SelectPopperSlotProps<T['value'], false>>>
}

const SelectCmp = <T extends SelectOptionBase>({
label,
options,
defaultValue,
handleChange,
placement = 'bottom-start',
className,
root = TertiaryButton,
listbox = StyledSelectListbox,
popper = StyledPopper,
Option = SelectOption,
}: SelectCmpProps<T>): ReactElement => {
const selectId = useId()

Expand All @@ -137,17 +178,23 @@ const SelectCmp = <T extends SelectOptionBase>({
)

return (
<Box>
<Box className={className}>
{label && (
<label htmlFor={selectId}>
<Typography variant="body2">{label}</Typography>
</label>
)}
<CustomSelect<T['value']> id={selectId} defaultValue={defaultValue} onChange={onChange}>
{options.map(({ label, value }) => (
<StyledOption key={value} value={value}>
<Typography variant="select">{label}</Typography>
</StyledOption>
<CustomSelect<T['value']>
id={selectId}
defaultValue={defaultValue}
onChange={onChange}
root={root}
listbox={listbox}
popper={popper}
placement={placement}
>
{options.map((props: T) => (
<Option key={props.value.toString()} {...props} />
))}
</CustomSelect>
</Box>
Expand Down
180 changes: 108 additions & 72 deletions src/app/pages/HomePage/Graph/NetworkSelector/index.tsx
Original file line number Diff line number Diff line change
@@ -1,100 +1,136 @@
import { FC, useState } from 'react'
import { FC, ForwardedRef, forwardRef, ReactElement } from 'react'
import { useTranslation } from 'react-i18next'
import { useTheme } from '@mui/material/styles'
import { useScreenSize } from '../../../../hooks/useScreensize'
import AddIcon from '@mui/icons-material/Add'
import Box from '@mui/material/Box'
import Button from '@mui/material/Button'
import IconButton from '@mui/material/IconButton'
import RemoveIcon from '@mui/icons-material/Remove'
import Typography from '@mui/material/Typography'
import { styled } from '@mui/material/styles'
import { COLORS } from '../../../../../styles/theme/colors'
import { getNetworkNames, Network } from '../../../../../types/network'
import Collapse from '@mui/material/Collapse'
import { RouteUtils } from '../../../../utils/route-utils'
import {
Select,
SelectOptionBase,
StyledSelectButton,
StyledSelectListbox,
StyledSelectOption,
} from '../../../../components/Select'
import { selectClasses, SelectRootSlotProps } from '@mui/base/Select'
import ExpandLessIcon from '@mui/icons-material/ExpandLess'
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
import { getNetworkIcons } from '../../../../utils/content'
import { optionClasses } from '@mui/base/Option'

const StyledNetworkSelector = styled(Box)(() => ({
interface NetworkOption extends SelectOptionBase {
label: Network
value: Network
}

const StyledNetworkSelector = styled(Select<NetworkOption>)(({ theme }) => ({
position: 'absolute',
bottom: 0,
bottom: theme.spacing(5),
display: 'flex',
width: '100%',
justifyContent: 'center',
}))

const StyledBox = styled(Box)(({ theme }) => ({
const StyledButton = styled(StyledSelectButton)(({ theme }) => ({
height: '44px',
minWidth: '200px',
backgroundColor: theme.palette.layout.primaryBackground,
border: `solid 3px ${theme.palette.layout.lightBorder}`,
borderRadius: '45px',
padding: theme.spacing(3, 4),
display: 'inline-flex',
alignItems: 'center',
border: `2px solid ${theme.palette.layout.lightBorder}`,
color: theme.palette.layout.main,
'&:hover': {
backgroundColor: theme.palette.layout.primaryBackground,
},
lubej marked this conversation as resolved.
Show resolved Hide resolved
[`&.${selectClasses.focusVisible}`]: {
backgroundColor: theme.palette.layout.primaryBackground,
outline: 'none',
border: `2px solid ${theme.palette.layout.main}`,
},
}))

export const SelectNetworkButton = styled(Button, {
shouldForwardProp: prop => prop !== 'isSelectedNetwork',
})<{ isSelectedNetwork: boolean }>(({ isSelectedNetwork, theme }) => ({
height: '30px',
padding: theme.spacing(2, 3),
textTransform: 'capitalize',
fontSize: '16px',
borderRadius: '9px',
backgroundColor: isSelectedNetwork
? theme.palette.layout.primaryBackground
: theme.palette.layout.secondary,
borderColor: isSelectedNetwork ? theme.palette.layout.hoverBorder : theme.palette.layout.secondary,
borderWidth: theme.spacing(1),
color: theme.palette.layout.main,
'&:hover, &:focus-visible': {
backgroundColor: theme.palette.layout.secondary,
borderWidth: theme.spacing(1),
borderColor: COLORS.white,
const StyledListbox = styled(StyledSelectListbox)(() => ({
minWidth: '200px',
background: COLORS.white,
color: COLORS.grayDark,
}))

const StyledOption = styled(StyledSelectOption)(() => ({
height: '44px',
color: COLORS.grayDark,
svg: {
color: COLORS.grayExtraDark,
},
[`&:hover:not(.${optionClasses.disabled}),
&.${optionClasses.highlighted}`]: {
backgroundColor: COLORS.grayLight,
},
}))
SelectNetworkButton.defaultProps = {
size: 'small',
variant: 'outlined',
sx: { ml: 4 },

const SelectOption = ({ value }: NetworkOption): ReactElement => {
const { t } = useTranslation()

const labels = getNetworkNames(t)
const icons = getNetworkIcons()

return (
<StyledOption key={value} value={value}>
<Box
sx={theme => ({ display: 'flex', gap: theme.spacing(3), pl: theme.spacing(3), alignItems: 'center' })}
>
{icons[value]}
<Typography variant="inherit">{labels[value]}</Typography>
</Box>
</StyledOption>
)
}

type NetworkSelectorProps = {
const NetworkSelectorButton = forwardRef(
(props: SelectRootSlotProps<Network, false>, ref: ForwardedRef<HTMLButtonElement>) => {
const { ownerState, ...restProps } = props
const { open, value } = ownerState
const { t } = useTranslation()
const label = getNetworkNames(t)
const icons = getNetworkIcons()

return (
<StyledButton {...restProps} ref={ref} color="inherit">
<Box
sx={theme => ({
display: 'flex',
gap: theme.spacing(3),
pl: theme.spacing(3),
alignItems: 'center',
})}
>
{icons[value!]}
<Typography variant="inherit">{label[value!]}</Typography>
</Box>
{open ? <ExpandLessIcon /> : <ExpandMoreIcon />}
</StyledButton>
)
},
)

interface NetworkSelectProps {
network: Network
setNetwork: (network: Network) => void
setNetwork: (network: Network | null) => void
}

export const NetworkSelector: FC<NetworkSelectorProps> = ({ network, setNetwork }) => {
const { t } = useTranslation()
const theme = useTheme()
const { isMobile } = useScreenSize()
const [open, setOpen] = useState(false)
const options: Network[] = RouteUtils.getEnabledNetworks()
const labels = getNetworkNames(t)
export const NetworkSelector: FC<NetworkSelectProps> = ({ network, setNetwork }) => {
const options = RouteUtils.getEnabledNetworks().map(network => ({
label: network,
value: network,
}))

return (
<StyledNetworkSelector>
<StyledBox>
{!isMobile && (
<Typography component="span" sx={{ fontSize: '12px', color: theme.palette.layout.main }}>
{t('home.selectNetwork')}
</Typography>
)}
<Box sx={{ height: 30, display: 'flex' }}>
{options.map(option => (
<Collapse orientation="horizontal" in={open || network === option} key={option}>
<SelectNetworkButton onClick={() => setNetwork(option)} isSelectedNetwork={option === network}>
{labels[option]}
</SelectNetworkButton>
</Collapse>
))}
</Box>
<IconButton aria-label={t('home.selectNetworkAria')} onClick={() => setOpen(!open)}>
{open ? (
<RemoveIcon fontSize="medium" sx={{ color: theme.palette.layout.main, fontSize: '18px' }} />
) : (
<AddIcon fontSize="medium" sx={{ color: theme.palette.layout.main, fontSize: '18px' }} />
)}
</IconButton>
</StyledBox>
</StyledNetworkSelector>
<StyledNetworkSelector
defaultValue={network}
handleChange={setNetwork}
options={options}
placement="top-start"
root={NetworkSelectorButton}
Option={SelectOption}
listbox={StyledListbox}
/>
)
}
2 changes: 1 addition & 1 deletion src/app/pages/HomePage/Graph/ParaTimeSelector/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ const ParaTimeSelectorCmp: FC<ParaTimeSelectorProps> = ({
)}
</ParaTimeSelectorGlobe>
{step === ParaTimeSelectorStep.Explore && (
<NetworkSelector network={network} setNetwork={setNetwork} />
<NetworkSelector network={network} setNetwork={network => setNetwork(network ?? Network.mainnet)} />
)}
</ParaTimeSelectorGlow>
{activeMobileGraphTooltip.current && (
Expand Down