diff --git a/apps/cowswap-frontend/src/common/pure/VirtualList/index.tsx b/apps/cowswap-frontend/src/common/pure/VirtualList/index.tsx index a5f10673af..2134dc4129 100644 --- a/apps/cowswap-frontend/src/common/pure/VirtualList/index.tsx +++ b/apps/cowswap-frontend/src/common/pure/VirtualList/index.tsx @@ -1,4 +1,4 @@ -import { ReactNode, useCallback, useRef } from 'react' +import { ReactNode, useCallback, useLayoutEffect, useRef } from 'react' import { useVirtualizer, VirtualItem } from '@tanstack/react-virtual' import ms from 'ms.macro' @@ -7,15 +7,72 @@ import { ListInner, ListScroller, ListWrapper, LoadingRows } from './styled' const scrollDelay = ms`400ms` -// TODO: Add proper return type annotation -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type -const threeDivs = () => ( - <> -
-
-
- -) +const LoadingPlaceholder: () => ReactNode = () => { + return ( + <> +
+
+
+ + ) +} + +interface VirtualListRowProps { + item: VirtualItem + loading?: boolean + items: T[] + getItemView(items: T[], item: VirtualItem): ReactNode + measureElement(element: Element | null): void +} + +function VirtualListRow({ item, loading, items, getItemView, measureElement }: VirtualListRowProps): ReactNode { + if (loading) { + return ( + + + + ) + } + + return ( +
+ {getItemView(items, item)} +
+ ) +} + +interface VirtualListRowsProps { + virtualItems: VirtualItem[] + loading?: boolean + items: T[] + getItemView(items: T[], item: VirtualItem): ReactNode + measureElement(element: Element | null): void +} + +function renderVirtualListRows({ + virtualItems, + loading, + items, + getItemView, + measureElement, +}: VirtualListRowsProps): ReactNode[] { + const elements: ReactNode[] = [] + + for (const item of virtualItems) { + elements.push( + , + ) + } + + return elements +} interface VirtualListProps { id?: string @@ -26,10 +83,9 @@ interface VirtualListProps { loading?: boolean estimateSize?: () => number children?: ReactNode + scrollResetKey?: string | number | boolean } -// TODO: Add proper return type annotation -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type export function VirtualList({ id, items, @@ -37,7 +93,8 @@ export function VirtualList({ getItemView, children, estimateSize = () => 56, -}: VirtualListProps) { + scrollResetKey, +}: VirtualListProps): ReactNode { const parentRef = useRef(null) const wrapperRef = useRef(null) const scrollTimeoutRef = useRef(undefined) @@ -53,6 +110,7 @@ export function VirtualList({ }, scrollDelay) }, []) + // eslint-disable-next-line react-hooks/incompatible-library const virtualizer = useVirtualizer({ getScrollElement: () => parentRef.current, count: items.length, @@ -60,24 +118,40 @@ export function VirtualList({ overscan: 5, }) + useLayoutEffect(() => { + if (scrollResetKey === undefined) { + return + } + + const scrollContainer = parentRef.current + + if (scrollContainer) { + scrollContainer.scrollTop = 0 + scrollContainer.scrollLeft = 0 + + if (typeof scrollContainer.scrollTo === 'function') { + scrollContainer.scrollTo({ top: 0, left: 0, behavior: 'auto' }) + } + } + + virtualizer.scrollToOffset(0, { align: 'start' }) + }, [scrollResetKey, virtualizer]) + const virtualItems = virtualizer.getVirtualItems() + const virtualRows = renderVirtualListRows({ + virtualItems, + loading, + items, + getItemView, + measureElement: virtualizer.measureElement, + }) return ( {children} - {virtualItems.map((item) => { - if (loading) { - return {threeDivs()} - } - - return ( -
- {getItemView(items, item)} -
- ) - })} + {virtualRows}
diff --git a/apps/cowswap-frontend/src/locales/en-US.po b/apps/cowswap-frontend/src/locales/en-US.po index 85dd692ccd..ea987f8f0b 100644 --- a/apps/cowswap-frontend/src/locales/en-US.po +++ b/apps/cowswap-frontend/src/locales/en-US.po @@ -455,7 +455,6 @@ msgid "View details" msgstr "View details" #: apps/cowswap-frontend/src/modules/application/containers/App/menuConsts.tsx -#: apps/cowswap-frontend/src/modules/tokensList/pure/ChainsSelector/index.tsx msgid "More" msgstr "More" @@ -827,8 +826,8 @@ msgid "Copied" msgstr "Copied" #: apps/cowswap-frontend/src/modules/tokensList/containers/TokenSearchResults/index.tsx -msgid "Can't find your token on the list?" -msgstr "Can't find your token on the list?" +#~ msgid "Can't find your token on the list?" +#~ msgstr "Can't find your token on the list?" #: apps/cowswap-frontend/src/modules/trade/pure/ReceiveAmountTitle/index.tsx msgid "icon" @@ -847,8 +846,8 @@ msgid "Please connect your wallet to one of our supported networks." msgstr "Please connect your wallet to one of our supported networks." #: apps/cowswap-frontend/src/modules/tokensList/containers/TokenSearchResults/index.tsx -msgid "<0>Read our guide on how to add custom tokens." -msgstr "<0>Read our guide on how to add custom tokens." +#~ msgid "<0>Read our guide on how to add custom tokens." +#~ msgstr "<0>Read our guide on how to add custom tokens." #: apps/cowswap-frontend/src/modules/hooksStore/containers/TenderlySimulate/index.tsx msgid "Retry" @@ -1220,8 +1219,8 @@ msgid "Partner fee can not be more than {PARTNER_FEE_MAX_BPS} BPS!" msgstr "Partner fee can not be more than {PARTNER_FEE_MAX_BPS} BPS!" #: apps/cowswap-frontend/src/modules/tokensList/pure/TokensContent/index.tsx -msgid "Manage Token Lists" -msgstr "Manage Token Lists" +#~ msgid "Manage Token Lists" +#~ msgstr "Manage Token Lists" #: apps/cowswap-frontend/src/legacy/components/Tokens/TokensTable.tsx msgid "No results found" @@ -3156,7 +3155,6 @@ msgstr "CoW Swap's robust solver competition protects your slippage from being e msgid "Aave Debt Swap Flashloan" msgstr "Aave Debt Swap Flashloan" -#: apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx #: apps/cowswap-frontend/src/modules/yield/pure/TargetPoolPreviewInfo.tsx msgid "Details" msgstr "Details" @@ -3812,6 +3810,10 @@ msgstr "User rejected approval transaction" msgid "Swap on" msgstr "Swap on" +#: apps/cowswap-frontend/src/modules/tokensList/pure/TokenSearchContent/index.tsx +msgid "Can't find your token on the list? <0>Read our guide on how to add custom tokens." +msgstr "Can't find your token on the list? <0>Read our guide on how to add custom tokens." + #: apps/cowswap-frontend/src/modules/account/containers/Transaction/ActivityDetails.tsx #: apps/cowswap-frontend/src/modules/orderProgressBar/pure/TransactionSubmittedContent/index.tsx msgid "Transaction" @@ -4322,7 +4324,6 @@ msgstr "Decrease Value" #: apps/cowswap-frontend/src/legacy/components/Tokens/TokensTable.tsx #: apps/cowswap-frontend/src/modules/ethFlow/pure/WrappingPreview/WrapCard.tsx #: apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx -#: apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx msgid "Balance" msgstr "Balance" @@ -4385,8 +4386,8 @@ msgid "funds" msgstr "funds" #: apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx -msgid "Pool details" -msgstr "Pool details" +#~ msgid "Pool details" +#~ msgstr "Pool details" #: apps/cowswap-frontend/src/modules/tradeSlippage/containers/HighSuggestedSlippageWarning/index.tsx msgid "Slippage adjusted to {slippageBpsPercentage}% to ensure quick execution" @@ -5894,8 +5895,8 @@ msgid "You sold <0/>" msgstr "You sold <0/>" #: apps/cowswap-frontend/src/modules/tokensList/pure/ChainsSelector/index.tsx -msgid "Less" -msgstr "Less" +#~ msgid "Less" +#~ msgstr "Less" #: libs/hook-dapp-lib/src/hookDappsRegistry.ts msgid "Claim your LlamaPay vesting contract funds" diff --git a/apps/cowswap-frontend/src/theme/consts.tsx b/apps/cowswap-frontend/src/theme/consts.tsx index 5230490c10..8a008740d7 100644 --- a/apps/cowswap-frontend/src/theme/consts.tsx +++ b/apps/cowswap-frontend/src/theme/consts.tsx @@ -25,6 +25,7 @@ export const WIDGET_MAX_WIDTH = { limit: '1350px', content: '680px', tokenSelect: '590px', + tokenSelectSidebar: '700px', } export const TextWrapper = styled(Text)<{ color: keyof Colors; override?: boolean }>` diff --git a/libs/tokens/src/pure/TokenLogo/index.tsx b/libs/tokens/src/pure/TokenLogo/index.tsx index 7f49b5e345..6d2c67f048 100644 --- a/libs/tokens/src/pure/TokenLogo/index.tsx +++ b/libs/tokens/src/pure/TokenLogo/index.tsx @@ -1,5 +1,5 @@ import { atom, useAtom } from 'jotai' -import { useCallback, useMemo } from 'react' +import { ReactNode, useCallback, useMemo } from 'react' import { BaseChainInfo, @@ -41,11 +41,19 @@ export interface TokenLogoProps { hideNetworkBadge?: boolean } -// TODO: Break down this large function into smaller functions -// TODO: Add proper return type annotation -// TODO: Reduce function complexity by extracting logic -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type, complexity, max-lines-per-function -export function TokenLogo({ +export function TokenLogo(props: TokenLogoProps): ReactNode { + const { token } = props + + if (token instanceof LpToken) { + return + } + + return +} + +type StandardTokenLogoProps = TokenLogoProps & { token?: TokenWithLogo | Currency | null } + +function StandardTokenLogo({ logoURI, token, className, @@ -53,31 +61,10 @@ export function TokenLogo({ sizeMobile, noWrap, hideNetworkBadge, -}: TokenLogoProps) { - const tokensByAddress = useTokensByAddressMap() - +}: StandardTokenLogoProps): ReactNode { const [invalidUrls, setInvalidUrls] = useAtom(invalidUrlsAtom) - const isLpToken = token instanceof LpToken - - const urls = useMemo(() => { - if (token instanceof LpToken) return - - // TODO: get rid of Currency usage and remove type casting - if (token) { - if (token instanceof NativeCurrency) { - return [cowprotocolTokenLogoUrl(NATIVE_CURRENCY_ADDRESS.toLowerCase(), token.chainId as SupportedChainId)] - } - - return getTokenLogoUrls(token as TokenWithLogo) - } - - return logoURI ? uriToHttp(logoURI) : [] - }, [logoURI, token]) - - const validUrls = useMemo(() => urls && urls.filter((url) => !invalidUrls[url]), [urls, invalidUrls]) - - const currentUrl = validUrls?.[0] + const { currentUrl, initial } = useTokenLogoUrl({ token, logoURI, invalidUrls }) const logoUrl = useNetworkLogo(token?.chainId) const showNetworkBadge = logoUrl && !hideNetworkBadge @@ -88,40 +75,7 @@ export function TokenLogo({ setInvalidUrls((state) => ({ ...state, [currentUrl]: true })) }, [currentUrl, setInvalidUrls]) - const initial = token?.symbol?.[0] || token?.name?.[0] - - if (isLpToken) { - return ( - - -
- -
-
- -
-
-
- ) - } - - const actualTokenContent = currentUrl ? ( - - {`${token?.symbol - - ) : initial ? ( - - - - ) : ( - - - - ) + const actualTokenContent = renderTokenLogoContent({ currentUrl, onError, token, initial }) if (noWrap) { return actualTokenContent @@ -137,7 +91,12 @@ export function TokenLogo({ const cutThicknessForCalc = getBorderWidth(chainLogoSizeForCalc) return ( - + <> {showNetworkBadge ? ( ) } + +type LpTokenLogoProps = Omit & { token: LpToken } + +function LpTokenLogo({ token, className, size = 36, sizeMobile }: LpTokenLogoProps): ReactNode { + const tokensByAddress = useTokensByAddressMap() + + return ( + + +
+ +
+
+ +
+
+
+ ) +} + +interface TokenLogoUrlOptions { + token?: TokenWithLogo | Currency | null + logoURI?: string + invalidUrls: Record +} + +function useTokenLogoUrl({ token, logoURI, invalidUrls }: TokenLogoUrlOptions): { + currentUrl?: string + initial?: string +} { + const urls = useMemo(() => { + if (token instanceof LpToken) { + return [] + } + + if (token instanceof NativeCurrency) { + return [cowprotocolTokenLogoUrl(NATIVE_CURRENCY_ADDRESS.toLowerCase(), token.chainId as SupportedChainId)] + } + + if (token) { + return getTokenLogoUrls(token as TokenWithLogo) + } + + return logoURI ? uriToHttp(logoURI) : [] + }, [logoURI, token]) + + const validUrls = useMemo(() => urls && urls.filter((url) => !invalidUrls[url]), [urls, invalidUrls]) + const currentUrl = validUrls?.[0] + const initial = token?.symbol?.[0] || token?.name?.[0] + + return { currentUrl, initial } +} + +interface TokenLogoContentOptions { + currentUrl?: string + onError: () => void + token?: TokenWithLogo | Currency | null + initial?: string +} + +function renderTokenLogoContent({ currentUrl, onError, token, initial }: TokenLogoContentOptions): ReactNode { + if (currentUrl) { + return ( + + {`${token?.symbol + + ) + } + + if (initial) { + return ( + + + + ) + } + + return ( + + + + ) +} diff --git a/libs/ui/src/enum.ts b/libs/ui/src/enum.ts index 7a13500436..6e7c2039ce 100644 --- a/libs/ui/src/enum.ts +++ b/libs/ui/src/enum.ts @@ -101,6 +101,41 @@ export enum UI { COLOR_GREEN = '--cow-color-green', COLOR_RED = '--cow-color-red', + // Chain-specific accent colors + COLOR_CHAIN_ETHEREUM_BG = '--cow-color-chain-ethereum-bg', + COLOR_CHAIN_ETHEREUM_BORDER = '--cow-color-chain-ethereum-border', + COLOR_CHAIN_ETHEREUM_ACCENT = '--cow-color-chain-ethereum-accent', + COLOR_CHAIN_BNB_BG = '--cow-color-chain-bnb-bg', + COLOR_CHAIN_BNB_BORDER = '--cow-color-chain-bnb-border', + COLOR_CHAIN_BNB_ACCENT = '--cow-color-chain-bnb-accent', + COLOR_CHAIN_BASE_BG = '--cow-color-chain-base-bg', + COLOR_CHAIN_BASE_BORDER = '--cow-color-chain-base-border', + COLOR_CHAIN_BASE_ACCENT = '--cow-color-chain-base-accent', + COLOR_CHAIN_ARBITRUM_BG = '--cow-color-chain-arbitrum-bg', + COLOR_CHAIN_ARBITRUM_BORDER = '--cow-color-chain-arbitrum-border', + COLOR_CHAIN_ARBITRUM_ACCENT = '--cow-color-chain-arbitrum-accent', + COLOR_CHAIN_POLYGON_BG = '--cow-color-chain-polygon-bg', + COLOR_CHAIN_POLYGON_BORDER = '--cow-color-chain-polygon-border', + COLOR_CHAIN_POLYGON_ACCENT = '--cow-color-chain-polygon-accent', + COLOR_CHAIN_AVALANCHE_BG = '--cow-color-chain-avalanche-bg', + COLOR_CHAIN_AVALANCHE_BORDER = '--cow-color-chain-avalanche-border', + COLOR_CHAIN_AVALANCHE_ACCENT = '--cow-color-chain-avalanche-accent', + COLOR_CHAIN_GNOSIS_BG = '--cow-color-chain-gnosis-bg', + COLOR_CHAIN_GNOSIS_BORDER = '--cow-color-chain-gnosis-border', + COLOR_CHAIN_GNOSIS_ACCENT = '--cow-color-chain-gnosis-accent', + COLOR_CHAIN_LENS_BG = '--cow-color-chain-lens-bg', + COLOR_CHAIN_LENS_BORDER = '--cow-color-chain-lens-border', + COLOR_CHAIN_LENS_ACCENT = '--cow-color-chain-lens-accent', + COLOR_CHAIN_SEPOLIA_BG = '--cow-color-chain-sepolia-bg', + COLOR_CHAIN_SEPOLIA_BORDER = '--cow-color-chain-sepolia-border', + COLOR_CHAIN_SEPOLIA_ACCENT = '--cow-color-chain-sepolia-accent', + COLOR_CHAIN_LINEA_BG = '--cow-color-chain-linea-bg', + COLOR_CHAIN_LINEA_BORDER = '--cow-color-chain-linea-border', + COLOR_CHAIN_LINEA_ACCENT = '--cow-color-chain-linea-accent', + COLOR_CHAIN_PLASMA_BG = '--cow-color-chain-plasma-bg', + COLOR_CHAIN_PLASMA_BORDER = '--cow-color-chain-plasma-border', + COLOR_CHAIN_PLASMA_ACCENT = '--cow-color-chain-plasma-accent', + // Neutral colors - Base grayscale palette from black (0) to white (100) COLOR_WHITE = '--cow-color-neutral-100', COLOR_NEUTRAL_100 = '--cow-color-neutral-100', diff --git a/libs/ui/src/pure/Input/index.tsx b/libs/ui/src/pure/Input/index.tsx index 4503352d71..c1a5dfb8b3 100644 --- a/libs/ui/src/pure/Input/index.tsx +++ b/libs/ui/src/pure/Input/index.tsx @@ -1,4 +1,4 @@ -import { InputHTMLAttributes } from 'react' +import { InputHTMLAttributes, ReactNode } from 'react' import { Search } from 'react-feather' import styled from 'styled-components/macro' @@ -31,15 +31,18 @@ const SearchInputEl = styled.input` border-radius: 12px; border: none; - ::placeholder { + &::placeholder { color: inherit; opacity: 0.7; + transition: color 0.1s ease-in-out; + } + + &:focus::placeholder { + color: transparent; } ` -// TODO: Add proper return type annotation -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type -export function SearchInput(props: InputHTMLAttributes) { +export function SearchInput(props: InputHTMLAttributes): ReactNode { return ( diff --git a/libs/ui/src/pure/Popover/index.tsx b/libs/ui/src/pure/Popover/index.tsx index ca0078e5e1..9e45b4c42b 100644 --- a/libs/ui/src/pure/Popover/index.tsx +++ b/libs/ui/src/pure/Popover/index.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useMemo, useState } from 'react' +import React, { useCallback, useEffect, useMemo, useState } from 'react' import { useMediaQuery, useInterval, useElementViewportTracking } from '@cowprotocol/common-hooks' @@ -24,7 +24,9 @@ const MOBILE_FULL_WIDTH_STYLES = { boxSizing: 'border-box' as const, } -function createMobileModifiers(arrowElement: HTMLDivElement | null): Array>>> { +function createMobileModifiers( + arrowElement: HTMLDivElement | null, +): Array>>> { return [ { name: 'offset', @@ -44,7 +46,9 @@ function createMobileModifiers(arrowElement: HTMLDivElement | null): Array>>> { +function createDesktopModifiers( + arrowElement: HTMLDivElement | null, +): Array>>> { return [ { name: 'offset', options: { offset: [8, 8] } }, { name: 'arrow', options: { element: arrowElement } }, @@ -52,7 +56,6 @@ function createDesktopModifiers(arrowElement: HTMLDivElement | null): Array(() => show || forceMount) + + useEffect(() => { + if ((show || forceMount) && !hasMountedPortal) { + setHasMountedPortal(true) + } + }, [show, forceMount, hasMountedPortal]) + + return forceMount || show || hasMountedPortal } export default function Popover(props: PopoverProps): React.JSX.Element { @@ -84,75 +104,133 @@ export default function Popover(props: PopoverProps): React.JSX.Element { showMobileBackdrop = false, mobileBorderRadius, zIndex = 999999, + forceMount = false, } = props const [referenceElement, setReferenceElement] = useState(null) const [popperElement, setPopperElement] = useState(null) const [arrowElement, setArrowElement] = useState(null) - const isMobile = useMediaQuery(Media.upToSmall(false)) const shouldUseFullWidth = isMobile && mobileMode === PopoverMobileMode.FullWidth - - // Use hook for viewport tracking and utility for backdrop height calculation const { rect } = useElementViewportTracking(referenceElement, shouldUseFullWidth && showMobileBackdrop) - const backdropHeight = useMemo(() => { if (!shouldUseFullWidth || !showMobileBackdrop) return '100vh' return calculateAvailableSpaceAbove(rect, 8) }, [rect, shouldUseFullWidth, showMobileBackdrop]) - const options = useMemo( (): Options => ({ placement: shouldUseFullWidth ? 'top' : placement, strategy: 'fixed', - modifiers: shouldUseFullWidth - ? createMobileModifiers(arrowElement) - : createDesktopModifiers(arrowElement), + modifiers: shouldUseFullWidth ? createMobileModifiers(arrowElement) : createDesktopModifiers(arrowElement), }), [arrowElement, placement, shouldUseFullWidth], ) - const { styles, update, attributes } = usePopper(referenceElement, popperElement, options) - const updateCallback = useCallback(() => { update?.() }, [update]) const intervalDelay = useMemo(() => (show ? 100 : null), [show]) useInterval(updateCallback, intervalDelay) - + const shouldRenderPortal = useLazyPortalMount(show, forceMount) + const popperStyle = { + ...styles.popper, + zIndex, + ...(shouldUseFullWidth && MOBILE_FULL_WIDTH_STYLES), + ...(shouldUseFullWidth && mobileBorderRadius && { borderRadius: mobileBorderRadius }), + } + const arrowPlacement = (attributes.popper?.['data-popper-placement'] as string | undefined)?.split('-')[0] ?? '' return ( <> {children} - - {isMobile && showMobileBackdrop && } - + + ) +} + +interface PopoverPortalProps { + shouldRender: boolean + show: boolean + isMobile: boolean + showMobileBackdrop: boolean + backdropHeight: string + className?: string + setPopperElement(value: HTMLDivElement | null): void + popperStyle: React.CSSProperties + popperAttributes: ReturnType['attributes']['popper'] + bgColor?: string + color?: string + borderColor?: string + content: React.ReactNode + setArrowElement(value: HTMLDivElement | null): void + arrowStyle: React.CSSProperties + arrowAttributes: ReturnType['attributes']['arrow'] + arrowPlacement: string +} + +function PopoverPortal({ + shouldRender, + show, + isMobile, + showMobileBackdrop, + backdropHeight, + className, + setPopperElement, + popperStyle, + popperAttributes, + bgColor, + color, + borderColor, + content, + setArrowElement, + arrowStyle, + arrowAttributes, + arrowPlacement, +}: PopoverPortalProps): React.ReactNode { + if (!shouldRender) { + return null + } + + return ( + + {isMobile && showMobileBackdrop && } + + {content} + - {content} - - - - + {...arrowAttributes} + /> + + ) } diff --git a/libs/ui/src/theme/ThemeColorVars.tsx b/libs/ui/src/theme/ThemeColorVars.tsx index b5b4872b06..f39c641a93 100644 --- a/libs/ui/src/theme/ThemeColorVars.tsx +++ b/libs/ui/src/theme/ThemeColorVars.tsx @@ -5,6 +5,153 @@ import { css } from 'styled-components/macro' import { UI } from '../enum' +interface ChainAccentConfig { + bgVar: UI + borderVar: UI + accentVar?: UI + lightBg: string + darkBg: string + lightBorder: string + darkBorder: string + lightColor: string + darkColor: string +} + +interface ChainAccentInput { + bgVar: UI + borderVar: UI + accentVar?: UI + color: string + lightColor?: string + darkColor?: string + lightBgAlpha?: number + darkBgAlpha?: number + lightBorderAlpha?: number + darkBorderAlpha?: number +} + +const CHAIN_LIGHT_BG_ALPHA = 0.22 +const CHAIN_DARK_BG_ALPHA = 0.32 +const CHAIN_LIGHT_BORDER_ALPHA = 0.45 +const CHAIN_DARK_BORDER_ALPHA = 0.65 + +const chainAlpha = (color: string, alpha: number): string => transparentize(color, 1 - alpha) + +function createChainAccent({ + bgVar, + borderVar, + accentVar, + color, + lightColor = color, + darkColor = color, + lightBgAlpha = CHAIN_LIGHT_BG_ALPHA, + darkBgAlpha = CHAIN_DARK_BG_ALPHA, + lightBorderAlpha = CHAIN_LIGHT_BORDER_ALPHA, + darkBorderAlpha = CHAIN_DARK_BORDER_ALPHA, +}: ChainAccentInput): ChainAccentConfig { + return { + bgVar, + borderVar, + accentVar, + lightBg: chainAlpha(lightColor, lightBgAlpha), + darkBg: chainAlpha(darkColor, darkBgAlpha), + lightBorder: chainAlpha(lightColor, lightBorderAlpha), + darkBorder: chainAlpha(darkColor, darkBorderAlpha), + lightColor, + darkColor, + } +} + +const CHAIN_ACCENT_CONFIG: ChainAccentConfig[] = [ + createChainAccent({ + bgVar: UI.COLOR_CHAIN_ETHEREUM_BG, + borderVar: UI.COLOR_CHAIN_ETHEREUM_BORDER, + accentVar: UI.COLOR_CHAIN_ETHEREUM_ACCENT, + color: '#627EEA', + }), + createChainAccent({ + bgVar: UI.COLOR_CHAIN_BNB_BG, + borderVar: UI.COLOR_CHAIN_BNB_BORDER, + accentVar: UI.COLOR_CHAIN_BNB_ACCENT, + color: '#F0B90B', + }), + createChainAccent({ + bgVar: UI.COLOR_CHAIN_BASE_BG, + borderVar: UI.COLOR_CHAIN_BASE_BORDER, + accentVar: UI.COLOR_CHAIN_BASE_ACCENT, + color: '#0052FF', + }), + createChainAccent({ + bgVar: UI.COLOR_CHAIN_ARBITRUM_BG, + borderVar: UI.COLOR_CHAIN_ARBITRUM_BORDER, + accentVar: UI.COLOR_CHAIN_ARBITRUM_ACCENT, + color: '#1B4ADD', + }), + createChainAccent({ + bgVar: UI.COLOR_CHAIN_POLYGON_BG, + borderVar: UI.COLOR_CHAIN_POLYGON_BORDER, + accentVar: UI.COLOR_CHAIN_POLYGON_ACCENT, + color: '#8247E5', + }), + createChainAccent({ + bgVar: UI.COLOR_CHAIN_AVALANCHE_BG, + borderVar: UI.COLOR_CHAIN_AVALANCHE_BORDER, + accentVar: UI.COLOR_CHAIN_AVALANCHE_ACCENT, + color: '#FF3944', + }), + createChainAccent({ + bgVar: UI.COLOR_CHAIN_GNOSIS_BG, + borderVar: UI.COLOR_CHAIN_GNOSIS_BORDER, + accentVar: UI.COLOR_CHAIN_GNOSIS_ACCENT, + color: '#07795B', + }), + createChainAccent({ + bgVar: UI.COLOR_CHAIN_LENS_BG, + borderVar: UI.COLOR_CHAIN_LENS_BORDER, + accentVar: UI.COLOR_CHAIN_LENS_ACCENT, + color: '#5A5A5A', + darkColor: '#D7D7D7', + }), + createChainAccent({ + bgVar: UI.COLOR_CHAIN_SEPOLIA_BG, + borderVar: UI.COLOR_CHAIN_SEPOLIA_BORDER, + accentVar: UI.COLOR_CHAIN_SEPOLIA_ACCENT, + color: '#C12FF2', + }), + createChainAccent({ + bgVar: UI.COLOR_CHAIN_LINEA_BG, + borderVar: UI.COLOR_CHAIN_LINEA_BORDER, + accentVar: UI.COLOR_CHAIN_LINEA_ACCENT, + color: '#61DFFF', + }), + createChainAccent({ + bgVar: UI.COLOR_CHAIN_PLASMA_BG, + borderVar: UI.COLOR_CHAIN_PLASMA_BORDER, + accentVar: UI.COLOR_CHAIN_PLASMA_ACCENT, + color: '#569F8C', + }), +] + +const CHAIN_ACCENT_VAR_DECLARATIONS = CHAIN_ACCENT_CONFIG.map(({ + bgVar, + borderVar, + accentVar, + lightBg, + darkBg, + lightBorder, + darkBorder, + lightColor, + darkColor, +}) => css` + ${bgVar}: ${({ theme }) => (theme.darkMode ? darkBg : lightBg)}; + ${borderVar}: ${({ theme }) => (theme.darkMode ? darkBorder : lightBorder)}; + ${accentVar + ? css` + ${accentVar}: ${({ theme }) => (theme.darkMode ? darkColor : lightColor)}; + ` + : ''} +`) + export const ThemeColorVars = css` :root { // V3 @@ -83,6 +230,8 @@ export const ThemeColorVars = css` ${UI.COLOR_ALERT_TEXT_DARKER}: ${({ theme }) => getContrastText(theme.alert, theme.darkMode ? darken(theme.alert, 0.55) : darken(theme.alert, 0.35))}; + ${CHAIN_ACCENT_VAR_DECLARATIONS} + ${UI.COLOR_WARNING}: ${({ theme }) => theme.warning}; ${UI.COLOR_WARNING_BG}: ${({ theme }) => transparentize(theme.warning, 0.85)}; ${UI.COLOR_WARNING_TEXT}: ${({ theme }) =>