Commit 8600aeba authored by tom's avatar tom

fix image loading state

parent d053f9da
...@@ -6,12 +6,11 @@ import { Skeleton } from './skeleton'; ...@@ -6,12 +6,11 @@ import { Skeleton } from './skeleton';
export interface ImageProps extends ChakraImageProps { export interface ImageProps extends ChakraImageProps {
fallback?: React.ReactNode; fallback?: React.ReactNode;
containerProps?: BoxProps;
} }
export const Image = React.forwardRef<HTMLImageElement, ImageProps>( export const Image = React.forwardRef<HTMLImageElement, ImageProps>(
function Image(props, ref) { function Image(props, ref) {
const { fallback, src, containerProps, ...rest } = props; const { fallback, src, ...rest } = props;
const [ loading, setLoading ] = React.useState(true); const [ loading, setLoading ] = React.useState(true);
const [ error, setError ] = React.useState(false); const [ error, setError ] = React.useState(false);
...@@ -34,14 +33,17 @@ export const Image = React.forwardRef<HTMLImageElement, ImageProps>( ...@@ -34,14 +33,17 @@ export const Image = React.forwardRef<HTMLImageElement, ImageProps>(
} }
return ( return (
<Skeleton loading={ loading } { ...containerProps }> <>
{ loading && <Skeleton loading { ...rest as BoxProps }/> }
<ChakraImage <ChakraImage
ref={ ref } ref={ ref }
src={ src } src={ src }
onError={ handleLoadError } onError={ handleLoadError }
onLoad={ handleLoadSuccess } onLoad={ handleLoadSuccess }
{ ...rest } { ...rest }
display={ loading ? 'none' : rest.display || 'inline-block' }
/> />
</Skeleton> </>
); );
}); },
);
'use client'; 'use client';
import type { CollectionItem } from '@chakra-ui/react'; import type { CollectionItem } from '@chakra-ui/react';
import { Select as ChakraSelect, Portal, useSelect, useSelectContext } from '@chakra-ui/react'; import { Select as ChakraSelect, Portal, useSelectContext } from '@chakra-ui/react';
import * as React from 'react'; import * as React from 'react';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
......
...@@ -31,8 +31,8 @@ const NativeTokenIcon = ({ isLoading, className, type }: Props) => { ...@@ -31,8 +31,8 @@ const NativeTokenIcon = ({ isLoading, className, type }: Props) => {
return ( return (
<Image <Image
className={ className }
borderRadius="base" borderRadius="base"
containerProps={{ className }}
src={ src || undefined } src={ src || undefined }
alt={ `${ config.chain.currency.symbol } logo` } alt={ `${ config.chain.currency.symbol } logo` }
fallback={ <TokenLogoPlaceholder borderRadius="base" className={ className }/> } fallback={ <TokenLogoPlaceholder borderRadius="base" className={ className }/> }
......
import type { StyleProps, ThemingProps } from '@chakra-ui/react';
import { Box } from '@chakra-ui/react';
import React from 'react';
import { useScrollDirection } from 'lib/contexts/scrollDirection';
import useIsMobile from 'lib/hooks/useIsMobile';
import useIsSticky from 'lib/hooks/useIsSticky';
import { Skeleton } from 'toolkit/chakra/skeleton';
import { TabsList, TabsTrigger } from 'toolkit/chakra/tabs';
import TabCounter from './TabCounter';
import TabsMenu from './TabsMenu';
import type { Props as TabsProps } from './TabsWithScroll';
import useAdaptiveTabs from './useAdaptiveTabs';
import useScrollToActiveTab from './useScrollToActiveTab';
import { getTabValue, menuButton } from './utils';
const hiddenItemStyles: StyleProps = {
position: 'absolute',
top: '-9999px',
left: '-9999px',
visibility: 'hidden',
};
interface Props extends TabsProps {
activeTab: string;
onItemClick: (index: number) => void;
themeProps: ThemingProps<'Tabs'>;
isLoading?: boolean;
}
const AdaptiveTabsList = (props: Props) => {
const scrollDirection = useScrollDirection();
const isMobile = useIsMobile();
const tabsList = React.useMemo(() => {
return [ ...props.tabs, menuButton ];
}, [ props.tabs ]);
// TODO @tom2drum remove isMobile || true
const { tabsCut, tabsRefs, listRef, rightSlotRef, leftSlotRef } = useAdaptiveTabs(tabsList, isMobile || true);
const isSticky = useIsSticky(listRef, 5, props.stickyEnabled);
const activeTabIndex = tabsList.findIndex((tab) => getTabValue(tab) === props.activeTab) ?? 0;
useScrollToActiveTab({ activeTabIndex, listRef, tabsRefs, isMobile, isLoading: props.isLoading });
return (
<TabsList
marginBottom={ 6 }
mx={{ base: '-12px', lg: 'unset' }}
px={{ base: '12px', lg: 'unset' }}
flexWrap="nowrap"
alignItems="center"
whiteSpace="nowrap"
ref={ listRef }
overflowX={{ base: 'auto', lg: 'initial' }}
overscrollBehaviorX="contain"
css={{
'scroll-snap-type': 'x mandatory',
'scroll-padding-inline': '12px', // mobile page padding
// hide scrollbar
'&::-webkit-scrollbar': { /* Chromiums */
display: 'none',
},
'-ms-overflow-style': 'none', /* IE and Edge */
'scrollbar-width': 'none', /* Firefox */
}}
bgColor={{ _light: 'white', _dark: 'black' }}
transitionProperty="top,box-shadow,background-color,color"
transitionDuration="normal"
transitionTimingFunction="ease"
{
...(props.stickyEnabled ? {
position: 'sticky',
boxShadow: { base: isSticky ? 'md' : 'none', lg: 'none' },
top: { base: scrollDirection === 'down' ? `0px` : `106px`, lg: 0 },
zIndex: { base: 'sticky2', lg: 'docked' },
} : { })
}
{
...(typeof props.tabListProps === 'function' ?
props.tabListProps({ isSticky, activeTab: props.activeTab }) :
props.tabListProps)
}
>
{ props.leftSlot && <Box ref={ leftSlotRef } { ...props.leftSlotProps }> { props.leftSlot } </Box> }
{ tabsList.slice(0, props.isLoading ? 5 : Infinity).map((tab, index) => {
const value = getTabValue(tab);
if (tab.id === 'menu') {
if (props.isLoading) {
return null;
}
return (
<TabsMenu
key="menu"
tabs={ props.tabs }
activeTab={ props.tabs[activeTabIndex] }
tabsCut={ tabsCut }
isActive={ activeTabIndex >= tabsCut }
styles={ tabsCut < props.tabs.length ?
// initially our cut is 0 and we don't want to show the menu button too
// but we want to keep it in the tabs row so it won't collapse
// that's why we only change opacity but not the position itself
{ opacity: tabsCut === 0 ? 0 : 1 } :
hiddenItemStyles
}
onItemClick={ props.onItemClick }
buttonRef={ tabsRefs[index] }
size={ props.themeProps.size || 'md' }
/>
);
}
return (
<TabsTrigger
key={ value }
value={ value }
ref={ tabsRefs[index] }
{ ...(index < tabsCut ? {} : hiddenItemStyles) }
scrollSnapAlign="start"
flexShrink={ 0 }
sx={{
'&:hover span': {
color: 'inherit',
},
}}
{ ...(value === props.activeTab ? { 'data-selected': true } : {}) }
>
<Skeleton loading={ props.isLoading }>
{ typeof tab.title === 'function' ? tab.title() : tab.title }
<TabCounter count={ tab.count }/>
</Skeleton>
</TabsTrigger>
);
}) }
{
props.rightSlot && tabsCut > 0 ?
<Box ref={ rightSlotRef } ml="auto" { ...props.rightSlotProps }> { props.rightSlot } </Box> :
null
}
</TabsList>
);
};
export default React.memo(AdaptiveTabsList);
import type { ChakraProps, ThemingProps } from '@chakra-ui/react';
import { chakra } from '@chakra-ui/react';
import { pickBy } from 'es-toolkit';
import { useRouter } from 'next/router';
import React, { useEffect, useRef } from 'react';
import type { RoutedTab } from './types';
import TabsWithScroll from './TabsWithScroll';
import useTabIndexFromQuery from './useTabIndexFromQuery';
interface Props extends ThemingProps<'Tabs'> {
tabs: Array<RoutedTab>;
tabListProps?: ChakraProps | (({ isSticky, activeTabIndex }: { isSticky: boolean; activeTabIndex: number }) => ChakraProps);
rightSlot?: React.ReactNode;
rightSlotProps?: ChakraProps;
leftSlot?: React.ReactNode;
leftSlotProps?: ChakraProps;
stickyEnabled?: boolean;
className?: string;
onTabChange?: (index: number) => void;
isLoading?: boolean;
}
const RoutedTabs = ({
tabs,
tabListProps,
rightSlot,
rightSlotProps,
leftSlot,
leftSlotProps,
stickyEnabled,
className,
onTabChange,
isLoading,
...themeProps
}: Props) => {
const router = useRouter();
const tabIndex = useTabIndexFromQuery(tabs);
const tabsRef = useRef<HTMLDivElement>(null);
const handleTabChange = React.useCallback((index: number) => {
const nextTab = tabs[index];
const queryForPathname = pickBy(router.query, (value, key) => router.pathname.includes(`[${ key }]`));
const tabId = Array.isArray(nextTab.id) ? nextTab.id[0] : nextTab.id;
router.push(
{ pathname: router.pathname, query: { ...queryForPathname, tab: tabId } },
undefined,
{ shallow: true },
);
onTabChange?.(index);
}, [ tabs, router, onTabChange ]);
useEffect(() => {
if (router.query.scroll_to_tabs) {
tabsRef?.current?.scrollIntoView(true);
delete router.query.scroll_to_tabs;
router.push(
{
pathname: router.pathname,
query: router.query,
},
undefined,
{ shallow: true },
);
}
// replicate componentDidMount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<TabsWithScroll
tabs={ tabs }
tabListProps={ tabListProps }
leftSlot={ leftSlot }
leftSlotProps={ leftSlotProps }
rightSlot={ rightSlot }
rightSlotProps={ rightSlotProps }
stickyEnabled={ stickyEnabled }
onTabChange={ handleTabChange }
defaultTabIndex={ tabIndex }
isLoading={ isLoading }
{ ...themeProps }
/>
);
};
export default React.memo(chakra(RoutedTabs));
import { chakra } from '@chakra-ui/react';
import React from 'react';
const COUNTER_OVERLOAD = 50;
type Props = {
count?: number | null;
};
// TODO @tom2drum remove this
const TabCounter = ({ count }: Props) => {
if (count === undefined || count === null) {
return null;
}
return (
<chakra.span
color={ count > 0 ? 'text.secondary' : { _light: 'blackAlpha.400', _dark: 'whiteAlpha.400' } }
ml={ 1 }
>
{ count > COUNTER_OVERLOAD ? `${ COUNTER_OVERLOAD }+` : count }
</chakra.span>
);
};
export default TabCounter;
import type { StyleProps } from '@chakra-ui/styled-system';
import React from 'react';
import type { MenuButton, TabItem } from './types';
import type { ButtonProps } from 'toolkit/chakra/button';
import { Button } from 'toolkit/chakra/button';
import { PopoverBody, PopoverContent, PopoverRoot, PopoverTrigger } from 'toolkit/chakra/popover';
import { useDisclosure } from 'toolkit/hooks/useDisclosure';
import TabCounter from './TabCounter';
import { menuButton } from './utils';
interface Props {
tabs: Array<TabItem | MenuButton>;
activeTab?: TabItem;
tabsCut: number;
isActive: boolean;
styles?: StyleProps;
onItemClick: (index: number) => void;
buttonRef: React.RefObject<HTMLButtonElement>;
size: ButtonProps['size'];
}
const TabsMenu = ({ tabs, tabsCut, isActive, styles, onItemClick, buttonRef, activeTab, size }: Props) => {
// const { isOpen, onClose, onOpen } = useDisclosure();
const handleItemClick = React.useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
// onClose();
const tabIndex = event.currentTarget.getAttribute('data-index');
if (tabIndex) {
onItemClick(tabsCut + Number(tabIndex));
}
}, [ onItemClick, tabsCut ]);
return (
<PopoverRoot positioning={{ placement: 'bottom-end' }}>
<PopoverTrigger>
<Button
as="div"
role="button"
variant="ghost"
// isActive={ isOpen || isActive }
ref={ buttonRef }
size={ size }
{ ...styles }
>
{ menuButton.title }
</Button>
</PopoverTrigger>
<PopoverContent w="auto">
<PopoverBody display="flex" flexDir="column">
{ tabs.slice(tabsCut).map((tab, index) => (
<Button
key={ tab.id?.toString() }
variant="ghost"
onClick={ handleItemClick }
active={ activeTab ? activeTab.id === tab.id : false }
justifyContent="left"
data-index={ index }
css={{
'&:hover span': {
color: 'inherit',
},
}}
>
{ typeof tab.title === 'function' ? tab.title() : tab.title }
<TabCounter count={ tab.count }/>
</Button>
)) }
</PopoverBody>
</PopoverContent>
</PopoverRoot>
);
};
export default React.memo(TabsMenu);
import React from 'react';
import type { TabItem } from './types';
import { test, expect } from 'playwright/lib';
import TabsWithScroll from './TabsWithScroll';
test('with counters', async({ render }) => {
const tabs: Array<TabItem> = [
{
id: 'tab1',
title: 'First tab',
count: 11,
component: null,
},
{
id: 'tab2',
title: 'Second tab',
count: 0,
component: null,
},
{
id: 'tab3',
title: 'Third tab',
count: 51,
component: null,
},
];
const component = await render(<TabsWithScroll tabs={ tabs }/>);
await component.getByText('Third tab').hover();
await expect(component).toHaveScreenshot();
});
import { chakra } from '@chakra-ui/react';
import { debounce } from 'es-toolkit';
import React, { useEffect, useRef, useState } from 'react';
import type { TabItem } from './types';
import isBrowser from 'lib/isBrowser';
import type { TabsProps } from 'toolkit/chakra/tabs';
import { TabsContent, TabsRoot } from 'toolkit/chakra/tabs';
import AdaptiveTabsList from './AdaptiveTabsList';
import { getTabValue, menuButton } from './utils';
export interface Props extends TabsProps {
tabs: Array<TabItem>;
lazyBehavior?: LazyMode;
tabListProps?: ChakraProps | (({ isSticky, activeTabIndex }: { isSticky: boolean; activeTabIndex: number }) => ChakraProps);
rightSlot?: React.ReactNode;
rightSlotProps?: ChakraProps;
leftSlot?: React.ReactNode;
leftSlotProps?: ChakraProps;
stickyEnabled?: boolean;
onTabChange?: (value: string) => void;
defaultTab?: string;
isLoading?: boolean;
className?: string;
}
// TODO @tom2drum remove this component
const TabsWithScroll = ({
tabs,
lazyBehavior,
tabListProps,
rightSlot,
rightSlotProps,
leftSlot,
leftSlotProps,
stickyEnabled,
onTabChange,
defaultTab,
isLoading,
className,
...themeProps
}: Props) => {
const [ activeTab, setActiveTab ] = useState<string>(defaultTab || getTabValue(tabs[0]));
const [ screenWidth, setScreenWidth ] = React.useState(isBrowser() ? window.innerWidth : 0);
const tabsRef = useRef<HTMLDivElement>(null);
const tabsList = React.useMemo(() => {
return [ ...tabs, menuButton ];
}, [ tabs ]);
const handleTabChange = React.useCallback(({ value }: { value: string }) => {
if (isLoading) {
return;
}
onTabChange ? onTabChange(value) : setActiveTab(value);
}, [ isLoading, onTabChange ]);
useEffect(() => {
if (defaultTab !== undefined) {
setActiveTab(defaultTab);
}
}, [ defaultTab ]);
React.useEffect(() => {
const resizeHandler = debounce(() => {
setScreenWidth(window.innerWidth);
}, 100);
const resizeObserver = new ResizeObserver(resizeHandler);
resizeObserver.observe(document.body);
return function cleanup() {
resizeObserver.unobserve(document.body);
};
}, []);
if (tabs.length === 1) {
return <div>{ tabs[0].component }</div>;
}
return (
<TabsRoot
className={ className }
variant={ themeProps.variant }
// colorScheme={ themeProps.colorScheme || 'blue' }
lazyMount
unmountOnExit
onValueChange={ handleTabChange }
value={ activeTab }
position="relative"
size={ themeProps.size }
ref={ tabsRef }
>
<AdaptiveTabsList
// the easiest and most readable way to achieve correct tab's cut recalculation when
// - screen is resized or
// - tabs list is changed when API data is loaded
// is to do full re-render of the tabs list
// so we use screenWidth + tabIds as a key for the TabsList component
key={ isLoading + '_' + screenWidth + '_' + tabsList.map((tab) => tab.id).join(':') }
tabs={ tabs }
tabListProps={ tabListProps }
leftSlot={ leftSlot }
leftSlotProps={ leftSlotProps }
rightSlot={ rightSlot }
rightSlotProps={ rightSlotProps }
stickyEnabled={ stickyEnabled }
activeTab={ activeTab }
onItemClick={ handleTabChange }
themeProps={ themeProps }
isLoading={ isLoading }
/>
{ tabsList.map((tab) => (
<TabsContent padding={ 0 } key={ getTabValue(tab) } value={ getTabValue(tab) }>
{ tab.component }
</TabsContent>
)) }
</TabsRoot>
);
};
export default React.memo(chakra(TabsWithScroll));
import type React from 'react';
export interface TabItem {
// NOTE, in case of array of ids, when switching tabs, the first id will be used
// switching between other ids should be handled in the underlying component
id: string | Array<string>;
title: string | (() => React.ReactNode);
count?: number | null;
component: React.ReactNode;
}
export type RoutedTab = TabItem & { subTabs?: Array<string> };
export type RoutedSubTab = Omit<TabItem, 'subTabs'>;
export interface MenuButton {
id: 'menu';
title: string;
count?: never;
component: null;
}
import React from 'react';
import type { MenuButton, RoutedTab } from './types';
export default function useAdaptiveTabs(tabs: Array<RoutedTab | MenuButton>, disabled?: boolean) {
// to avoid flickering we set initial value to 0
// so there will be no displayed tabs initially
const [ tabsCut, setTabsCut ] = React.useState(disabled ? tabs.length : 0);
const [ tabsRefs, setTabsRefs ] = React.useState<Array<React.RefObject<HTMLButtonElement>>>([]);
const listRef = React.useRef<HTMLDivElement>(null);
const rightSlotRef = React.useRef<HTMLDivElement>(null);
const leftSlotRef = React.useRef<HTMLDivElement>(null);
const calculateCut = React.useCallback(() => {
const listWidth = listRef.current?.getBoundingClientRect().width;
const rightSlotWidth = rightSlotRef.current?.getBoundingClientRect().width || 0;
const leftSlotWidth = leftSlotRef.current?.getBoundingClientRect().width || 0;
const tabWidths = tabsRefs.map((tab) => tab.current?.getBoundingClientRect().width);
const menuWidth = tabWidths[tabWidths.length - 1];
if (!listWidth || !menuWidth) {
return tabs.length;
}
const { visibleNum } = tabWidths.slice(0, -1).reduce((result, item, index, array) => {
if (!item) {
return result;
}
if (result.visibleNum < index) {
// means that we haven't increased visibleNum on the previous iteration, so there is no space left
// we skip now till the end of the loop
return result;
}
if (index === array.length - 1) {
// last element
if (result.accWidth + item < listWidth - rightSlotWidth - leftSlotWidth) {
return { visibleNum: result.visibleNum + 1, accWidth: result.accWidth + item };
}
} else {
if (result.accWidth + item + menuWidth < listWidth - rightSlotWidth - leftSlotWidth) {
return { visibleNum: result.visibleNum + 1, accWidth: result.accWidth + item };
}
}
return result;
}, { visibleNum: 0, accWidth: 0 });
return visibleNum;
}, [ tabs.length, tabsRefs ]);
React.useEffect(() => {
setTabsRefs(tabs.map((_, index) => tabsRefs[index] || React.createRef()));
setTabsCut(disabled ? tabs.length : 0);
// update refs only when disabled prop changes
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ disabled ]);
React.useEffect(() => {
if (tabsRefs.length > 0 && !disabled) {
setTabsCut(calculateCut());
}
}, [ calculateCut, disabled, tabsRefs ]);
return React.useMemo(() => {
return {
tabsCut,
tabsRefs,
listRef,
rightSlotRef,
leftSlotRef,
};
}, [ tabsCut, tabsRefs ]);
}
import React from 'react';
interface Props {
activeTabIndex: number;
tabsRefs: Array<React.RefObject<HTMLButtonElement>>;
listRef: React.RefObject<HTMLDivElement>;
isMobile?: boolean;
isLoading?: boolean;
}
export default function useScrollToActiveTab({ activeTabIndex, tabsRefs, listRef, isMobile, isLoading }: Props) {
React.useEffect(() => {
if (isLoading) {
return;
}
if (activeTabIndex < tabsRefs.length && isMobile) {
window.setTimeout(() => {
const activeTabRef = tabsRefs[activeTabIndex];
if (activeTabRef.current && listRef.current) {
const containerWidth = listRef.current.getBoundingClientRect().width;
const activeTabWidth = activeTabRef.current.getBoundingClientRect().width;
const left = tabsRefs.slice(0, activeTabIndex)
.map((tab) => tab.current?.getBoundingClientRect())
.filter(Boolean)
.map((rect) => rect.width)
.reduce((result, item) => result + item, 0);
const isWithinFirstPage = containerWidth > left + activeTabWidth;
if (isWithinFirstPage) {
return;
}
listRef.current.scrollTo({
left,
behavior: 'smooth',
});
}
// have to wait until DOM is updated and all styles to tabs is applied
}, 300);
}
// run only when tab index or device type is changed
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ activeTabIndex, isMobile, isLoading ]);
}
import { useRouter } from 'next/router';
import type { RoutedTab } from './types';
import getQueryParamString from 'lib/router/getQueryParamString';
export default function useTabIndexFromQuery(tabs: Array<RoutedTab>) {
const router = useRouter();
const tabFromQuery = getQueryParamString(router.query.tab);
if (!tabFromQuery) {
return 0;
}
const tabIndex = tabs.findIndex(({ id, subTabs }) => {
if (Array.isArray(id)) {
return id.includes(tabFromQuery);
}
return id === tabFromQuery || subTabs?.some((id) => id === tabFromQuery);
});
if (tabIndex < 0) {
return 0;
}
return tabIndex;
}
import type { MenuButton, TabItem } from './types';
import { middot } from 'lib/html-entities';
export const menuButton: MenuButton = {
id: 'menu',
title: `${ middot }${ middot }${ middot }`,
component: null,
};
export const getTabValue = (tab: MenuButton | TabItem): string => tab.id.toString();
import { Box, Link, Text, chakra } from '@chakra-ui/react'; import { Box, Text, chakra } from '@chakra-ui/react';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { ndash } from 'lib/html-entities'; import { ndash } from 'lib/html-entities';
import isBrowser from 'lib/isBrowser'; import isBrowser from 'lib/isBrowser';
import { Image } from 'toolkit/chakra/image'; import { Image } from 'toolkit/chakra/image';
import { Link } from 'toolkit/chakra/link';
import { Skeleton } from 'toolkit/chakra/skeleton'; import { Skeleton } from 'toolkit/chakra/skeleton';
type AdData = { type AdData = {
...@@ -52,6 +53,7 @@ const CoinzillaTextAd = ({ className }: { className?: string }) => { ...@@ -52,6 +53,7 @@ const CoinzillaTextAd = ({ className }: { className?: string }) => {
if (isLoading) { if (isLoading) {
return ( return (
<Skeleton <Skeleton
loading
className={ className } className={ className }
h={{ base: 12, lg: 6 }} h={{ base: 12, lg: 6 }}
w="100%" w="100%"
...@@ -83,12 +85,16 @@ const CoinzillaTextAd = ({ className }: { className?: string }) => { ...@@ -83,12 +85,16 @@ const CoinzillaTextAd = ({ className }: { className?: string }) => {
<Text as="span" mr={ 1 }>🎨</Text> : ( <Text as="span" mr={ 1 }>🎨</Text> : (
<Image <Image
src={ adData.ad.thumbnail } src={ adData.ad.thumbnail }
containerProps={{ width: '20px', height: '20px', mb: '-4px', mr: 1, display: 'inline-block' }} width="20px"
height="20px"
mb="-4px"
mr={ 1 }
display="inline-block"
alt="" alt=""
/> />
) } ) }
<Text as="span" whiteSpace="pre-wrap">{ `${ adData.ad.name } ${ ndash } ${ adData.ad.description_short } ` }</Text> <Text as="span" whiteSpace="pre-wrap">{ `${ adData.ad.name } ${ ndash } ${ adData.ad.description_short } ` }</Text>
<Link href={ adData.ad.url }>{ adData.ad.cta_button }</Link> <Link href={ adData.ad.url } external noIcon>{ adData.ad.cta_button }</Link>
</Box> </Box>
); );
}; };
......
...@@ -154,7 +154,7 @@ export interface EntityProps extends EntityBase.EntityBaseProps { ...@@ -154,7 +154,7 @@ export interface EntityProps extends EntityBase.EntityBaseProps {
noAltHash?: boolean; noAltHash?: boolean;
} }
const AddressEntry = (props: EntityProps) => { const AddressEntity = (props: EntityProps) => {
const partsProps = distributeEntityProps(props); const partsProps = distributeEntityProps(props);
const highlightContext = useAddressHighlightContext(props.noHighlight); const highlightContext = useAddressHighlightContext(props.noHighlight);
const settingsContext = useSettingsContext(); const settingsContext = useSettingsContext();
...@@ -180,7 +180,7 @@ const AddressEntry = (props: EntityProps) => { ...@@ -180,7 +180,7 @@ const AddressEntry = (props: EntityProps) => {
); );
}; };
export default React.memo(chakra(AddressEntry)); export default React.memo(chakra(AddressEntity));
export { export {
Container, Container,
......
...@@ -49,10 +49,7 @@ const Icon = (props: IconProps) => { ...@@ -49,10 +49,7 @@ const Icon = (props: IconProps) => {
return ( return (
<Image <Image
{ ...styles } { ...styles }
containerProps={{ className={ props.className }
className: props.className,
...styles,
}}
src={ props.token.icon_url ?? undefined } src={ props.token.icon_url ?? undefined }
alt={ `${ props.token.name || 'token' } logo` } alt={ `${ props.token.name || 'token' } logo` }
fallback={ <TokenLogoPlaceholder { ...styles }/> } fallback={ <TokenLogoPlaceholder { ...styles }/> }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment