diff --git a/assets/avatar.com.svg.react b/assets/avatar.com.svg.react new file mode 100644 index 00000000..85e2c590 --- /dev/null +++ b/assets/avatar.com.svg.react @@ -0,0 +1,4 @@ + + + + diff --git a/modules/delegation/providers/DelegateFromPublicListContext.tsx b/modules/delegation/providers/DelegateFromPublicListContext.tsx new file mode 100644 index 00000000..36ba49f2 --- /dev/null +++ b/modules/delegation/providers/DelegateFromPublicListContext.tsx @@ -0,0 +1,50 @@ +import { createContext, FC, useCallback, useContext, useState } from 'react' +import invariant from 'tiny-invariant' + +// +// Data context +// + +type Value = { + selectedPublicDelegate: string | undefined + onPublicDelegateSelect: (address: string) => () => void + onPublicDelegateReset: () => void +} + +const DelegateFromPublicListContext = createContext(null) + +export const useDelegateFromPublicList = () => { + const value = useContext(DelegateFromPublicListContext) + invariant( + value, + 'useDelegateFromPublicList was used outside the DelegateFromPublicListContext provider', + ) + return value +} + +export const DelegateFromPublicListProvider: FC = ({ children }) => { + const [selectedPublicDelegate, setSelectedPublicDelegate] = useState() + + const handleDelegatePick = useCallback( + (address: string) => () => { + setSelectedPublicDelegate(address) + }, + [], + ) + + const handleDelegateReset = useCallback(() => { + setSelectedPublicDelegate(undefined) + }, []) + + return ( + + {children} + + ) +} diff --git a/modules/delegation/providers/DelegationFormContext.tsx b/modules/delegation/providers/DelegationFormContext.tsx index 7810d96b..fbbffbae 100644 --- a/modules/delegation/providers/DelegationFormContext.tsx +++ b/modules/delegation/providers/DelegationFormContext.tsx @@ -21,6 +21,7 @@ import { useDelegationFormSubmit } from '../hooks/useDelegationFormSubmit' import { useDelegationRevoke } from '../hooks/useDelegationRevoke' import { ToastSuccess } from '@lidofinance/lido-ui' import { isValidAddress } from 'modules/shared/utils/addressValidation' +import { useDelegateFromPublicList } from './DelegateFromPublicListContext' // // Data context @@ -120,15 +121,15 @@ const useDelegationFormActions = ( // export type DelegationFormProviderProps = { mode: DelegationFormMode - presetDelegateAddress?: string } export const DelegationFormProvider: FC = ({ children, mode, - presetDelegateAddress, }) => { const networkData = useDelegationFormNetworkData() + const { selectedPublicDelegate, onPublicDelegateReset } = + useDelegateFromPublicList() const formObject = useForm({ defaultValues: { delegateAddress: '' }, @@ -138,14 +139,17 @@ export const DelegationFormProvider: FC = ({ useEffect(() => { const currentValue = formObject.getValues('delegateAddress') if ( - presetDelegateAddress && - isValidAddress(presetDelegateAddress) && - currentValue?.toLowerCase() !== presetDelegateAddress.toLowerCase() + selectedPublicDelegate && + isValidAddress(selectedPublicDelegate) && + currentValue?.toLowerCase() !== selectedPublicDelegate.toLowerCase() ) { - formObject.setValue('delegateAddress', presetDelegateAddress) + formObject.setValue('delegateAddress', selectedPublicDelegate, { + shouldValidate: true, + }) + onPublicDelegateReset() } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [presetDelegateAddress]) + }, [selectedPublicDelegate]) const { isSubmitting, diff --git a/modules/delegation/ui/DelegationForm/DelegationFormStyle.ts b/modules/delegation/ui/DelegationForm/DelegationFormStyle.ts index 54e67188..65b656d0 100644 --- a/modules/delegation/ui/DelegationForm/DelegationFormStyle.ts +++ b/modules/delegation/ui/DelegationForm/DelegationFormStyle.ts @@ -1,4 +1,4 @@ -import styled from 'styled-components' +import styled, { css } from 'styled-components' import { Button, Text } from '@lidofinance/lido-ui' export const DelegationFormControllerStyled = styled.form<{ @@ -9,11 +9,11 @@ export const DelegationFormControllerStyled = styled.form<{ ${({ $customMode }) => $customMode && - ` - padding: 24px 16px; - background-color: var(--lido-color-accentControlBg); - border-radius: 20px; - `} + css` + padding: 24px 16px; + background-color: var(--lido-color-accentControlBg); + border-radius: ${({ theme }) => theme.borderRadiusesMap.xl}px; + `} ` export const DelegationSubtitleStyled = styled.div` diff --git a/modules/delegation/ui/DelegationForm/DelegationFormSubtitle.tsx b/modules/delegation/ui/DelegationForm/DelegationFormSubtitle.tsx index 0af0bac9..58b2a616 100644 --- a/modules/delegation/ui/DelegationForm/DelegationFormSubtitle.tsx +++ b/modules/delegation/ui/DelegationForm/DelegationFormSubtitle.tsx @@ -1,4 +1,4 @@ -import { Text } from '@lidofinance/lido-ui' +import { Text, useBreakpoint } from '@lidofinance/lido-ui' import { DelegationSubtitleStyled } from './DelegationFormStyle' import { useDelegationFormData } from 'modules/delegation/providers/DelegationFormContext' @@ -7,12 +7,13 @@ import SnapshotSvg from 'assets/snapshot.com.svg.react' export function DelegationFormSubtitle() { const { mode } = useDelegationFormData() + const isMobile = useBreakpoint('md') if (mode === 'aragon') { return ( - + On Aragon @@ -23,7 +24,7 @@ export function DelegationFormSubtitle() { return ( - + On Snapshot diff --git a/modules/delegation/ui/DelegationSettings/DelegationSettings.tsx b/modules/delegation/ui/DelegationSettings/DelegationSettings.tsx index ed9c097a..5b0e7946 100644 --- a/modules/delegation/ui/DelegationSettings/DelegationSettings.tsx +++ b/modules/delegation/ui/DelegationSettings/DelegationSettings.tsx @@ -1,58 +1,47 @@ -import { useCallback, useState } from 'react' -import { Button, Text } from '@lidofinance/lido-ui' +import { useState } from 'react' +import { Button, Text, useBreakpoint } from '@lidofinance/lido-ui' import { FormTitle, FormWrap, Wrap } from './DelegationSettingsStyle' import { DelegationForm } from '../DelegationForm' import { PublicDelegateList } from '../PublicDelegateList' +import { DelegateFromPublicListProvider } from '../../providers/DelegateFromPublicListContext' export function DelegationSettings() { const [isSimpleModeOn, setIsSimpleModeOn] = useState(true) - const [delegateFromPublicList, setDelegateFromPublicList] = useState() - - const handleDelegatePick = useCallback( - (address: string) => () => { - setDelegateFromPublicList(address) - }, - [], - ) + const isMobile = useBreakpoint('md') return ( - - - - Delegation - - {!isSimpleModeOn && ( - - )} - - {isSimpleModeOn ? ( - setIsSimpleModeOn(false)} - /> - ) : ( - <> + + + + + Delegation + + {!isSimpleModeOn && ( + + )} + + {isSimpleModeOn ? ( setIsSimpleModeOn(false)} /> - - - )} - - + ) : ( + <> + + + + )} + + + ) } diff --git a/modules/delegation/ui/DelegationSettings/DelegationSettingsStyle.ts b/modules/delegation/ui/DelegationSettings/DelegationSettingsStyle.ts index 9e945747..8ec2c3ba 100644 --- a/modules/delegation/ui/DelegationSettings/DelegationSettingsStyle.ts +++ b/modules/delegation/ui/DelegationSettings/DelegationSettingsStyle.ts @@ -1,4 +1,4 @@ -import { BREAKPOINT_MOBILE } from 'modules/globalStyles' +import { BREAKPOINT_MD, BREAKPOINT_MOBILE } from 'modules/globalStyles' import styled from 'styled-components' export const Wrap = styled.div` @@ -6,6 +6,7 @@ export const Wrap = styled.div` gap: 20px; justify-content: center; align-items: flex-start; + flex-wrap: wrap; & > div { flex: 1; @@ -22,7 +23,7 @@ export const Wrap = styled.div` ` export const FormWrap = styled.div<{ $customizable: boolean }>` - border-radius: 20px; + border-radius: ${({ theme }) => theme.borderRadiusesMap.xl}px; background-color: var(--lido-color-foreground); display: flex; flex-direction: column; @@ -35,6 +36,10 @@ export const FormWrap = styled.div<{ $customizable: boolean }>` ` padding: 32px 24px; `} + + @media (max-width: ${BREAKPOINT_MD}) { + padding: 20px; + } ` export const FormTitle = styled.div` diff --git a/modules/delegation/ui/DelegatorsList/DelegatorsListStyle.ts b/modules/delegation/ui/DelegatorsList/DelegatorsListStyle.ts index bd419488..44ee7d4e 100644 --- a/modules/delegation/ui/DelegatorsList/DelegatorsListStyle.ts +++ b/modules/delegation/ui/DelegatorsList/DelegatorsListStyle.ts @@ -2,7 +2,7 @@ import { Button } from '@lidofinance/lido-ui' import styled from 'styled-components' export const Wrap = styled.div<{ $empty?: boolean }>` - border-radius: 20px; + border-radius: ${({ theme }) => theme.borderRadiusesMap.xl}px; background-color: var(--lido-color-foreground); padding: 32px; display: flex; diff --git a/modules/delegation/ui/PublicDelegateList/PublicDelegateAvatar.tsx b/modules/delegation/ui/PublicDelegateList/PublicDelegateAvatar.tsx new file mode 100644 index 00000000..c864bdd1 --- /dev/null +++ b/modules/delegation/ui/PublicDelegateList/PublicDelegateAvatar.tsx @@ -0,0 +1,30 @@ +import Image from 'next/image' +import { AvatarWrap } from './PublicDelegateListStyle' + +import AvatarSvg from 'assets/avatar.com.svg.react' + +type Props = { + avatarSrc: string | null | undefined +} + +export function PublicDelegateAvatar({ avatarSrc }: Props) { + if (!avatarSrc) { + return ( + + + + ) + } + + return ( + + src} + unoptimized + /> + + ) +} diff --git a/modules/delegation/ui/PublicDelegateList/PublicDelegateList.tsx b/modules/delegation/ui/PublicDelegateList/PublicDelegateList.tsx index 07f87a9d..edbb6b25 100644 --- a/modules/delegation/ui/PublicDelegateList/PublicDelegateList.tsx +++ b/modules/delegation/ui/PublicDelegateList/PublicDelegateList.tsx @@ -1,33 +1,20 @@ -import { Button, Text, trimAddress } from '@lidofinance/lido-ui' -import Image from 'next/image' +import { Text, useBreakpoint } from '@lidofinance/lido-ui' import { - DelegateInfo, Header, HeaderTitleWithIcon, InnerWrap, - ListItem, Wrap, - SocialButtons, - AvatarWrap, } from './PublicDelegateListStyle' -import { ExternalLink } from 'modules/shared/ui/Common/ExternalLink' import { useWeb3 } from 'modules/blockChain/hooks/useWeb3' import { useProcessedPublicDelegatesList } from './useProcessedPublicDelegatesList' import { PageLoader } from 'modules/shared/ui/Common/PageLoader' -import { isValidAddress } from 'modules/shared/utils/addressValidation' -import { formatBalance } from 'modules/blockChain/utils/formatBalance' -import { AddressPop } from 'modules/shared/ui/Common/AddressPop' +import { PublicDelegateListItem } from './PublicDelegateListItem' import AragonSvg from 'assets/aragon.com.svg.react' -import XSocialSvg from 'assets/x.social.com.svg.react' -import LidoSocialSvg from 'assets/lido.social.com.svg.react' -type Props = { - onDelegatePick: (address: string) => () => void -} - -export function PublicDelegateList({ onDelegatePick }: Props) { +export function PublicDelegateList() { const { isWalletConnected } = useWeb3() + const isMobile = useBreakpoint('md') const { data, initialLoading } = useProcessedPublicDelegatesList() @@ -41,75 +28,32 @@ export function PublicDelegateList({ onDelegatePick }: Props) { return ( - + Public Delegate List -
- - Delegate - - - VP - - From -

-

-

- {data.map(delegate => ( - - - - {delegate.avatar ? ( - src} - unoptimized - /> - ) : ( -
- )} - -
- - {delegate.name} - - - - {isValidAddress(delegate.address) - ? trimAddress(delegate.address, 6) - : delegate.address} - - -
- - - {delegate.delegatedVotingPower === 'N/A' - ? delegate.delegatedVotingPower - : formatBalance(delegate.delegatedVotingPower)} + {!isMobile && ( +
+ + Delegate - {delegate.delegatorsCount} - - - - - - - - - {isWalletConnected && ( - - )} - + + VP + + + From + +

+

+

+ )} + {data.map(delegate => ( + ))} diff --git a/modules/delegation/ui/PublicDelegateList/PublicDelegateListItem.tsx b/modules/delegation/ui/PublicDelegateList/PublicDelegateListItem.tsx new file mode 100644 index 00000000..09ac0cc5 --- /dev/null +++ b/modules/delegation/ui/PublicDelegateList/PublicDelegateListItem.tsx @@ -0,0 +1,128 @@ +import { + DelegateInfo, + DelegateNameAndAddress, + DelegateNumbersMobile, + HeaderTitleWithIcon, + ListItem, + SocialButtons, +} from './PublicDelegateListStyle' +import { ProcessedDelegate } from './useProcessedPublicDelegatesList' +import { Button, Text, trimAddress } from '@lidofinance/lido-ui' +import { AddressPop } from 'modules/shared/ui/Common/AddressPop' +import { isValidAddress } from 'modules/shared/utils/addressValidation' +import { formatBalance } from 'modules/blockChain/utils/formatBalance' +import { ExternalLink } from 'modules/shared/ui/Common/ExternalLink' +import { PublicDelegateAvatar } from './PublicDelegateAvatar' +import { useDelegateFromPublicList } from 'modules/delegation/providers/DelegateFromPublicListContext' + +import XSocialSvg from 'assets/x.social.com.svg.react' +import LidoSocialSvg from 'assets/lido.social.com.svg.react' +import AragonSvg from 'assets/aragon.com.svg.react' + +type Props = { + delegate: ProcessedDelegate + isWalletConnected: boolean + isMobile: boolean +} + +export function PublicDelegateListItem({ + delegate, + isWalletConnected, + isMobile, +}: Props) { + const { onPublicDelegateSelect } = useDelegateFromPublicList() + + const addressToShow = isValidAddress(delegate.address) + ? trimAddress(delegate.address, 6) + : delegate.address + + const balanceToShow = + delegate.delegatedVotingPower === 'N/A' + ? delegate.delegatedVotingPower + : formatBalance(delegate.delegatedVotingPower) + + if (isMobile) { + return ( + + + + + + {delegate.name} + + + + {addressToShow} + + + + + + + + + + + + + + + VP + {balanceToShow} + + + From {delegate.delegatorsCount} + + + {isWalletConnected && ( + + )} + + ) + } + + return ( + + + + + + {delegate.name} + + + + {addressToShow} + + + + + {balanceToShow} + {delegate.delegatorsCount} + + + + + + + + + {isWalletConnected && ( + + )} + + ) +} diff --git a/modules/delegation/ui/PublicDelegateList/PublicDelegateListStyle.ts b/modules/delegation/ui/PublicDelegateList/PublicDelegateListStyle.ts index 0bd4766d..f6d0458c 100644 --- a/modules/delegation/ui/PublicDelegateList/PublicDelegateListStyle.ts +++ b/modules/delegation/ui/PublicDelegateList/PublicDelegateListStyle.ts @@ -1,37 +1,43 @@ import { Text } from '@lidofinance/lido-ui' -import { BREAKPOINT_MOBILE } from 'modules/globalStyles' +import { BREAKPOINT_MOBILE, BREAKPOINT_MD } from 'modules/globalStyles' import styled from 'styled-components' export const Wrap = styled.div` padding: 32px 24px; - border-radius: 20px; + border-radius: ${({ theme }) => theme.borderRadiusesMap.xl}px; background-color: var(--lido-color-foreground); display: flex; flex-direction: column; gap: 24px; max-width: 542px; - max-height: 492px; - overflow: hidden; + min-width: 460px; + + @media (max-width: ${BREAKPOINT_MOBILE}) { + min-width: unset; + max-height: unset; + } + + @media (max-width: ${BREAKPOINT_MD}) { + padding: 20px; + } ` export const InnerWrap = styled.div<{ $connected: boolean }>` - border-radius: 20px; + border-radius: inherit; border: 1px solid var(--lido-color-border); display: flex; flex-direction: column; - overflow-y: auto; - max-height: 310px; - border-radius: inherit; + overflow-x: hidden; & > div { display: grid; gap: 10px; - grid-template-columns: 3fr 1fr 1fr 40px ${({ $connected }) => - $connected ? '2fr' : ''}; + grid-template-columns: minmax(130px, 3fr) repeat(2, minmax(42px, 1fr)) 40px ${({ + $connected, + }) => ($connected ? 'minmax(0, 85px)' : '')}; - @media (max-width: ${BREAKPOINT_MOBILE}) { - grid-template-columns: 2fr ${({ $connected }) => - $connected ? '1fr' : ''}; + @media (max-width: ${BREAKPOINT_MD}) { + display: flex; } } ` @@ -43,14 +49,6 @@ export const Header = styled.div` background-color: var(--lido-color-foreground); z-index: 2; border-bottom: 1px solid var(--lido-color-border); - - @media (max-width: ${BREAKPOINT_MOBILE}) { - & > *:nth-child(2), - & > *:nth-child(3), - & > *:nth-child(4) { - display: none; - } - } ` export const HeaderTitleWithIcon = styled(Text).attrs({ @@ -70,18 +68,22 @@ export const ListItem = styled.div` background-color: var(--lido-color-background); } - @media (max-width: ${BREAKPOINT_MOBILE}) { - & > *:nth-child(2), - & > *:nth-child(3), - & > *:nth-child(4) { - display: none; - } + @media (max-width: ${BREAKPOINT_MD}) { + padding: 20px 12px; + display: flex; + flex-direction: column; + gap: 12px; + align-items: stretch; } ` export const DelegateInfo = styled.div` display: flex; align-items: center; gap: 8px; + + @media (max-width: ${BREAKPOINT_MD}) { + gap: 12px; + } ` export const AvatarWrap = styled.div` @@ -89,26 +91,23 @@ export const AvatarWrap = styled.div` border-radius: 50%; width: 28px; height: 28px; - background-color: var(--lido-color-primaryVisited); overflow: hidden; + flex-shrink: 0; & > img, - div { + & > svg { width: 100%; height: 100%; - object-fit: cover; - display: block; } & > img[src=''] { display: none; } -` -export const Avatar = styled.img` - border-radius: 50%; - width: 28px; - height: 28px; + @media (max-width: ${BREAKPOINT_MD}) { + width: 32px; + height: 32px; + } ` export const SocialButtons = styled.div` @@ -116,6 +115,48 @@ export const SocialButtons = styled.div` gap: 6px; & > span { - max-height: 16px; + height: 16px; + + & > svg path { + transition: fill ease ${({ theme }) => theme.duration.norm}; + } + + &:hover > svg path { + fill: var(--lido-color-primaryHover); + } + } + + @media (max-width: ${BREAKPOINT_MD}) { + & > span { + height: 24px; + } + + svg { + width: 24px; + height: 24px; + } + } +` + +export const DelegateNameAndAddress = styled.div` + max-width: calc(100% - 36px); + + & > p, + & > span { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; } + + @media (max-width: ${BREAKPOINT_MD}) { + max-width: 100%; + flex: 1; + } +` + +export const DelegateNumbersMobile = styled.div` + display: inline-flex; + align-items: center; + gap: 32px; ` diff --git a/modules/delegation/ui/PublicDelegateList/useProcessedPublicDelegatesList.ts b/modules/delegation/ui/PublicDelegateList/useProcessedPublicDelegatesList.ts index b6ab280b..bdd3b7c9 100644 --- a/modules/delegation/ui/PublicDelegateList/useProcessedPublicDelegatesList.ts +++ b/modules/delegation/ui/PublicDelegateList/useProcessedPublicDelegatesList.ts @@ -10,7 +10,7 @@ import { isValidEns, } from 'modules/shared/utils/addressValidation' -type ProcessedDelegate = typeof PUBLIC_DELEGATES[number] & { +export type ProcessedDelegate = typeof PUBLIC_DELEGATES[number] & { delegatorsCount: string delegatedVotingPower: BigNumber | string resolvedDelegateAddress: string | null diff --git a/modules/globalStyles/index.ts b/modules/globalStyles/index.ts index 4f6ac5eb..9ccb5913 100644 --- a/modules/globalStyles/index.ts +++ b/modules/globalStyles/index.ts @@ -1,6 +1,8 @@ +import { themeDefault } from '@lidofinance/lido-ui' import { createGlobalStyle } from 'styled-components' export const BREAKPOINT_MOBILE = '960px' +export const BREAKPOINT_MD = themeDefault.breakpointsMap.md.width export const GlobalStyle = createGlobalStyle` *, diff --git a/modules/shared/ui/Layout/Header/HeaderStyle.ts b/modules/shared/ui/Layout/Header/HeaderStyle.ts index 2fa9d5df..824f00af 100644 --- a/modules/shared/ui/Layout/Header/HeaderStyle.ts +++ b/modules/shared/ui/Layout/Header/HeaderStyle.ts @@ -123,10 +123,6 @@ export const InputWrap = styled.div` @media (max-width: 1060px) { width: 200px; } - - @media (max-width: 810px) { - width: 200px; - } ` export const Network = styled.div`