Commit 2b22bfc2 authored by tom's avatar tom

links refactoring and showcases

parent b740a53f
...@@ -26,6 +26,8 @@ const RESTRICTED_MODULES = { ...@@ -26,6 +26,8 @@ const RESTRICTED_MODULES = {
{ name: '@metamask/post-message-stream', message: 'Please lazy-load @metamask/post-message-stream or use useProvider hook instead' }, { name: '@metamask/post-message-stream', message: 'Please lazy-load @metamask/post-message-stream or use useProvider hook instead' },
{ name: 'playwright/TestApp', message: 'Please use render() fixture from test() function of playwright/lib module' }, { name: 'playwright/TestApp', message: 'Please use render() fixture from test() function of playwright/lib module' },
{ name: 'ui/shared/chakra/Skeleton', message: 'Please use Skeleton component from toolkit/chakra instead' }, { name: 'ui/shared/chakra/Skeleton', message: 'Please use Skeleton component from toolkit/chakra instead' },
{ name: 'ui/shared/links/LinkInternal', message: 'Please use Link component from toolkit/chakra instead' },
{ name: 'ui/shared/links/LinkExternal', message: 'Please use Link component from toolkit/chakra instead' },
{ {
name: '@chakra-ui/react', name: '@chakra-ui/react',
importNames: [ importNames: [
......
import type { LinkProps as ChakraLinkProps } from '@chakra-ui/react';
import { Link as ChakraLink } from '@chakra-ui/react';
import NextLink from 'next/link';
import type { LinkProps as NextLinkProps } from 'next/link';
import React from 'react';
import IconSvg from 'ui/shared/IconSvg';
import { Skeleton } from './skeleton';
export interface LinkProps extends ChakraLinkProps {
loading?: boolean;
external?: boolean;
scroll?: boolean;
iconColor?: ChakraLinkProps['color'];
}
export const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
function Link(props, ref) {
const { external, loading, href, children, scroll = true, iconColor, ...rest } = props;
if (external) {
return (
<Skeleton loading={ loading } asChild>
<ChakraLink
ref={ ref }
href={ href }
className="group"
target="_blank"
rel="noopener noreferrer" { ...rest }
>
{ children }
<IconSvg
name="link_external"
boxSize={ 3 }
verticalAlign="middle"
color={ iconColor ?? 'icon.externalLink' }
_groupHover={{
color: 'inherit',
}}
flexShrink={ 0 }
/>
</ChakraLink>
</Skeleton>
);
}
return (
<Skeleton loading={ loading } asChild>
<ChakraLink asChild ref={ ref } { ...rest }>
{ href ? <NextLink href={ href as NextLinkProps['href'] } scroll={ scroll }>{ children }</NextLink> : <span>{ children }</span> }
</ChakraLink>
</Skeleton>
);
},
);
...@@ -47,6 +47,6 @@ export const SkeletonText = React.forwardRef<HTMLDivElement, SkeletonTextProps>( ...@@ -47,6 +47,6 @@ export const SkeletonText = React.forwardRef<HTMLDivElement, SkeletonTextProps>(
export const Skeleton = React.forwardRef<HTMLDivElement, ChakraSkeletonProps>( export const Skeleton = React.forwardRef<HTMLDivElement, ChakraSkeletonProps>(
function Skeleton(props, ref) { function Skeleton(props, ref) {
const { loading = false, ...rest } = props; const { loading = false, ...rest } = props;
return <ChakraSkeleton loading={ loading } { ...rest } ref={ ref }/>; return <ChakraSkeleton loading={ loading } { ...(loading ? { 'data-loading': true } : {}) } { ...rest } ref={ ref }/>;
}, },
); );
import React from 'react';
import { scroller, Element } from 'react-scroll';
import { Link } from 'toolkit/chakra/link';
interface Props {
children: React.ReactNode;
id?: string;
onClick?: () => void;
isLoading?: boolean;
}
const ID = 'CutLink';
const CutLink = (props: Props) => {
const { children, id = ID, onClick, isLoading } = props;
const [ isExpanded, setIsExpanded ] = React.useState(false);
const handleClick = React.useCallback(() => {
setIsExpanded((flag) => !flag);
scroller.scrollTo(id, {
duration: 500,
smooth: true,
});
onClick?.();
}, [ id, onClick ]);
return (
<>
<Element name={ id }>
<Link
textStyle="sm"
textDecorationLine="underline"
textDecorationStyle="dashed"
onClick={ handleClick }
loading={ isLoading }
>
{ isExpanded ? 'Hide details' : 'View details' }
</Link>
</Element>
{ isExpanded && children }
</>
);
};
export default React.memo(CutLink);
...@@ -89,6 +89,9 @@ const semanticTokens: ThemingConfig['semanticTokens'] = { ...@@ -89,6 +89,9 @@ const semanticTokens: ThemingConfig['semanticTokens'] = {
secondary: { secondary: {
DEFAULT: { value: { _light: '{colors.gray.400}', _dark: '{colors.gray.500}' } }, DEFAULT: { value: { _light: '{colors.gray.400}', _dark: '{colors.gray.500}' } },
}, },
underlaid: {
bg: { value: { _light: '{colors.gray.100}', _dark: '{colors.gray.800}' } },
},
subtle: { subtle: {
DEFAULT: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.gray.400}' } }, DEFAULT: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.gray.400}' } },
hover: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.gray.400}' } }, hover: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.gray.400}' } },
...@@ -306,6 +309,7 @@ const semanticTokens: ThemingConfig['semanticTokens'] = { ...@@ -306,6 +309,7 @@ const semanticTokens: ThemingConfig['semanticTokens'] = {
}, },
icon: { icon: {
backTo: { value: '{colors.gray.400}' }, backTo: { value: '{colors.gray.400}' },
externalLink: { value: { _light: '{colors.gray.400}', _dark: '{colors.gray.400}' } },
}, },
global: { global: {
body: { body: {
......
...@@ -5,7 +5,7 @@ export const recipe = defineRecipe({ ...@@ -5,7 +5,7 @@ export const recipe = defineRecipe({
gap: 0, gap: 0,
}, },
variants: { variants: {
visual: { variant: {
primary: { primary: {
color: 'link.primary', color: 'link.primary',
_hover: { _hover: {
...@@ -24,9 +24,27 @@ export const recipe = defineRecipe({ ...@@ -24,9 +24,27 @@ export const recipe = defineRecipe({
color: 'link.subtle', color: 'link.subtle',
_hover: { _hover: {
color: 'link.subtle.hover', color: 'link.subtle.hover',
textDecorationLine: 'underline',
textDecorationColor: 'link.subtle.hover', textDecorationColor: 'link.subtle.hover',
}, },
}, },
underlaid: {
color: 'link.primary',
// css-var to override bg property on loaded skeleton
'--layer-bg': '{colors.link.underlaid.bg}',
bgColor: 'link.underlaid.bg',
px: '8px',
py: '6px',
borderRadius: 'base',
textStyle: 'sm',
_hover: {
color: 'link.primary.hover',
textDecoration: 'none',
},
_loading: {
bgColor: 'transparent',
},
},
navigation: { navigation: {
color: 'link.navigation.fg', color: 'link.navigation.fg',
bg: 'link.navigation.bg', bg: 'link.navigation.bg',
...@@ -47,6 +65,6 @@ export const recipe = defineRecipe({ ...@@ -47,6 +65,6 @@ export const recipe = defineRecipe({
}, },
}, },
defaultVariants: { defaultVariants: {
visual: 'primary', variant: 'primary',
}, },
}); });
...@@ -19,7 +19,7 @@ export const recipe = defineRecipe({ ...@@ -19,7 +19,7 @@ export const recipe = defineRecipe({
}, },
}, },
'false': { 'false': {
background: 'unset', background: 'var(--layer-bg)',
animation: 'fade-in var(--fade-duration, 0.1s) ease-out !important', animation: 'fade-in var(--fade-duration, 0.1s) ease-out !important',
}, },
}, },
......
...@@ -72,6 +72,8 @@ export const recipe = defineSlotRecipe({ ...@@ -72,6 +72,8 @@ export const recipe = defineSlotRecipe({
boxShadow: 'popover', boxShadow: 'popover',
boxShadowColor: 'popover.shadow', boxShadowColor: 'popover.shadow',
borderRadius: 'md', borderRadius: 'md',
textAlign: 'left',
fontWeight: 'normal',
}, },
}, },
}, },
......
...@@ -31,6 +31,12 @@ const customConfig = defineConfig({ ...@@ -31,6 +31,12 @@ const customConfig = defineConfig({
fonts, fonts,
shadows, shadows,
zIndex, zIndex,
fontWeights: {
normal: { value: '400' },
medium: { value: '500' },
semibold: { value: '600' },
bold: { value: '700' },
},
}, },
}, },
// components, // components,
......
...@@ -25,6 +25,7 @@ import PageTitle from 'ui/shared/Page/PageTitle'; ...@@ -25,6 +25,7 @@ import PageTitle from 'ui/shared/Page/PageTitle';
import AlertsShowcase from 'ui/showcases/Alerts'; import AlertsShowcase from 'ui/showcases/Alerts';
import BadgesShowcase from 'ui/showcases/Badges'; import BadgesShowcase from 'ui/showcases/Badges';
import ButtonShowcase from 'ui/showcases/Button'; import ButtonShowcase from 'ui/showcases/Button';
import LinksShowcase from 'ui/showcases/Links';
import PaginationShowcase from 'ui/showcases/Pagination'; import PaginationShowcase from 'ui/showcases/Pagination';
import TabsShowcase from 'ui/showcases/Tabs'; import TabsShowcase from 'ui/showcases/Tabs';
...@@ -54,6 +55,7 @@ const ChakraShowcases = () => { ...@@ -54,6 +55,7 @@ const ChakraShowcases = () => {
<TabsTrigger value="alerts">Alerts</TabsTrigger> <TabsTrigger value="alerts">Alerts</TabsTrigger>
<TabsTrigger value="badges">Badges</TabsTrigger> <TabsTrigger value="badges">Badges</TabsTrigger>
<TabsTrigger value="buttons">Buttons</TabsTrigger> <TabsTrigger value="buttons">Buttons</TabsTrigger>
<TabsTrigger value="links">Links</TabsTrigger>
<TabsTrigger value="pagination">Pagination</TabsTrigger> <TabsTrigger value="pagination">Pagination</TabsTrigger>
<TabsTrigger value="tabs">Tabs</TabsTrigger> <TabsTrigger value="tabs">Tabs</TabsTrigger>
<TabsTrigger value="unsorted">Unsorted</TabsTrigger> <TabsTrigger value="unsorted">Unsorted</TabsTrigger>
...@@ -61,6 +63,7 @@ const ChakraShowcases = () => { ...@@ -61,6 +63,7 @@ const ChakraShowcases = () => {
<AlertsShowcase/> <AlertsShowcase/>
<BadgesShowcase/> <BadgesShowcase/>
<ButtonShowcase/> <ButtonShowcase/>
<LinksShowcase/>
<TabsShowcase/> <TabsShowcase/>
<PaginationShowcase/> <PaginationShowcase/>
......
...@@ -48,7 +48,7 @@ const CopyToClipboard = ({ text, className, isLoading, onClick, size = 5, type, ...@@ -48,7 +48,7 @@ const CopyToClipboard = ({ text, className, isLoading, onClick, size = 5, type,
}, [ onClick, copy ]); }, [ onClick, copy ]);
if (isLoading) { if (isLoading) {
return <Skeleton boxSize={ size } className={ className } borderRadius="sm" flexShrink={ 0 } ml={ 2 } display="inline-block"/>; return <Skeleton boxSize={ size } className={ className } borderRadius="sm" flexShrink={ 0 } ml={ 2 } display="inline-block" loading/>;
} }
return ( return (
......
...@@ -48,7 +48,7 @@ const Icon = (props: IconProps) => { ...@@ -48,7 +48,7 @@ const Icon = (props: IconProps) => {
}; };
if (props.isLoading) { if (props.isLoading) {
return <Skeleton { ...styles } borderRadius="full" flexShrink={ 0 }/>; return <Skeleton { ...styles } loading borderRadius="full" flexShrink={ 0 }/>;
} }
if (props.address.is_contract) { if (props.address.is_contract) {
......
import { Box } from '@chakra-ui/react'; import { Box, Flex } from '@chakra-ui/react';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import React from 'react'; import React from 'react';
...@@ -53,7 +53,7 @@ const Icon = dynamic( ...@@ -53,7 +53,7 @@ const Icon = dynamic(
return (props: IconProps) => { return (props: IconProps) => {
const svg = GradientAvatar(props.hash, props.size, 'circle'); const svg = GradientAvatar(props.hash, props.size, 'circle');
return <div dangerouslySetInnerHTML={{ __html: svg }}/>; return <Flex dangerouslySetInnerHTML={{ __html: svg }}/>;
}; };
} }
......
...@@ -31,6 +31,7 @@ export interface EntityBaseProps { ...@@ -31,6 +31,7 @@ export interface EntityBaseProps {
tailLength?: number; tailLength?: number;
target?: React.HTMLAttributeAnchorTarget; target?: React.HTMLAttributeAnchorTarget;
truncation?: Truncation; truncation?: Truncation;
size?: 'md' | 'lg';
} }
export interface ContainerBaseProps extends Pick<EntityBaseProps, 'className'> { export interface ContainerBaseProps extends Pick<EntityBaseProps, 'className'> {
...@@ -165,9 +166,9 @@ const Content = chakra(({ className, isLoading, asProp, text, truncation = 'dyna ...@@ -165,9 +166,9 @@ const Content = chakra(({ className, isLoading, asProp, text, truncation = 'dyna
); );
}); });
export type CopyBaseProps = Pick<CopyToClipboardProps, 'isLoading' | 'text'> & Pick<EntityBaseProps, 'noCopy'>; export type CopyBaseProps = Pick<CopyToClipboardProps, 'isLoading' | 'text'> & Pick<EntityBaseProps, 'noCopy' | 'size'>;
const Copy = (props: CopyBaseProps) => { const Copy = ({ size, ...props }: CopyBaseProps) => {
if (props.noCopy) { if (props.noCopy) {
return null; return null;
} }
......
import type { As } from '@chakra-ui/react';
import { chakra } from '@chakra-ui/react'; import { chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
...@@ -76,7 +75,7 @@ const BlobEntity = (props: EntityProps) => { ...@@ -76,7 +75,7 @@ const BlobEntity = (props: EntityProps) => {
); );
}; };
export default React.memo(chakra<As, EntityProps>(BlobEntity)); export default React.memo(chakra(BlobEntity));
export { export {
Container, Container,
......
import type { As } from '@chakra-ui/react'; import { chakra, Flex, Text } from '@chakra-ui/react';
import { Box, chakra, Flex, Image, PopoverBody, PopoverContent, PopoverTrigger, Portal, Text } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type * as bens from '@blockscout/bens-types'; import type * as bens from '@blockscout/bens-types';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import Popover from 'ui/shared/chakra/Popover'; import { Image } from 'toolkit/chakra/image';
import Skeleton from 'ui/shared/chakra/Skeleton'; import { Link as LinkToolkit } from 'toolkit/chakra/link';
import { Skeleton } from 'toolkit/chakra/skeleton';
import { Tooltip } from 'toolkit/chakra/tooltip';
import * as EntityBase from 'ui/shared/entities/base/components'; import * as EntityBase from 'ui/shared/entities/base/components';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import LinkExternal from 'ui/shared/links/LinkExternal';
import TruncatedValue from 'ui/shared/TruncatedValue'; import TruncatedValue from 'ui/shared/TruncatedValue';
import { distributeEntityProps, getIconProps } from '../base/utils'; import { distributeEntityProps, getIconProps } from '../base/utils';
...@@ -39,58 +39,71 @@ const Icon = (props: IconProps) => { ...@@ -39,58 +39,71 @@ const Icon = (props: IconProps) => {
const styles = getIconProps(props.size); const styles = getIconProps(props.size);
if (props.isLoading) { if (props.isLoading) {
return <Skeleton boxSize={ styles.boxSize } borderRadius="sm" mr={ 2 }/>; return <Skeleton loading boxSize={ styles.boxSize } borderRadius="sm" mr={ 2 }/>;
} }
const content = (
<>
<Flex alignItems="center" textStyle="md">
<Image
src={ props.protocol.icon_url }
boxSize={ 5 }
borderRadius="sm"
mr={ 2 }
alt={ `${ props.protocol.title } protocol icon` }
fallback={ icon }
// fallbackStrategy={ props.protocol.icon_url ? 'onError' : 'beforeLoadOrError' }
/>
<div>
<span>{ props.protocol.short_name }</span>
<chakra.span color="text_secondary" whiteSpace="pre"> { props.protocol.tld_list.map((tld) => `.${ tld }`).join((' ')) }</chakra.span>
</div>
</Flex>
<Text>{ props.protocol.description }</Text>
{ props.protocol.docs_url && (
<LinkToolkit
href={ props.protocol.docs_url }
display="inline-flex"
alignItems="center"
external
>
{ /* TODO @tom2drum maybe refactor this to pass icon as a prop */ }
<IconSvg name="docs" boxSize={ 5 } color="text.secondary" mr={ 2 }/>
<span>Documentation</span>
</LinkToolkit>
) }
</>
);
return ( return (
<Popover trigger="hover" isLazy placement="bottom-start"> <Tooltip
<PopoverTrigger> content={ content }
<Box flexShrink={ 0 }> visual="popover"
<Image positioning={{
src={ props.protocol.icon_url } placement: 'bottom-start',
boxSize={ styles.boxSize } }}
borderRadius="sm" contentProps={{
mr={ 2 } maxW: { base: '100vw', lg: '440px' },
alt={ `${ props.protocol.title } protocol icon` } minW: '250px',
fallback={ icon } w: 'fit-content',
fallbackStrategy={ props.protocol.icon_url ? 'onError' : 'beforeLoadOrError' } display: 'flex',
/> flexDir: 'column',
</Box> rowGap: 3,
</PopoverTrigger> alignItems: 'flex-start',
<Portal> }}
<PopoverContent maxW={{ base: '100vw', lg: '440px' }} minW="250px" w="fit-content"> interactive
<PopoverBody display="flex" flexDir="column" rowGap={ 3 }> >
<Flex alignItems="center"> <Image
<Image src={ props.protocol.icon_url }
src={ props.protocol.icon_url } boxSize={ styles.boxSize }
boxSize={ 5 } borderRadius="sm"
borderRadius="sm" mr={ 2 }
mr={ 2 } flexShrink={ 0 }
alt={ `${ props.protocol.title } protocol icon` } alt={ `${ props.protocol.title } protocol icon` }
fallback={ icon } fallback={ icon }
fallbackStrategy={ props.protocol.icon_url ? 'onError' : 'beforeLoadOrError' } // fallbackStrategy={ props.protocol.icon_url ? 'onError' : 'beforeLoadOrError' }
/> />
<div> </Tooltip>
<span>{ props.protocol.short_name }</span>
<chakra.span color="text_secondary" whiteSpace="pre"> { props.protocol.tld_list.map((tld) => `.${ tld }`).join((' ')) }</chakra.span>
</div>
</Flex>
<Text fontSize="sm">{ props.protocol.description }</Text>
{ props.protocol.docs_url && (
<LinkExternal
href={ props.protocol.docs_url }
display="inline-flex"
alignItems="center"
fontSize="sm"
>
<IconSvg name="docs" boxSize={ 5 } color="text_secondary" mr={ 2 }/>
<span>Documentation</span>
</LinkExternal>
) }
</PopoverBody>
</PopoverContent>
</Portal>
</Popover>
); );
} }
...@@ -140,7 +153,7 @@ const EnsEntity = (props: EntityProps) => { ...@@ -140,7 +153,7 @@ const EnsEntity = (props: EntityProps) => {
); );
}; };
export default React.memo(chakra<As, EntityProps>(EnsEntity)); export default React.memo(chakra(EnsEntity));
export { export {
Container, Container,
......
import type { As } from '@chakra-ui/react';
import { chakra } from '@chakra-ui/react'; import { chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
...@@ -69,7 +68,7 @@ const NftEntity = (props: EntityProps) => { ...@@ -69,7 +68,7 @@ const NftEntity = (props: EntityProps) => {
); );
}; };
export default React.memo(chakra<As, EntityProps>(NftEntity)); export default React.memo(chakra(NftEntity));
export { export {
Container, Container,
......
import type { As } from '@chakra-ui/react'; import { Flex, chakra } from '@chakra-ui/react';
import { Flex, chakra, useColorModeValue } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { Pool } from 'types/api/pools'; import type { Pool } from 'types/api/pools';
...@@ -7,7 +6,7 @@ import type { Pool } from 'types/api/pools'; ...@@ -7,7 +6,7 @@ import type { Pool } from 'types/api/pools';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import { getPoolTitle } from 'lib/pools/getPoolTitle'; import { getPoolTitle } from 'lib/pools/getPoolTitle';
import Skeleton from 'ui/shared/chakra/Skeleton'; import { Skeleton } from 'toolkit/chakra/skeleton';
import * as EntityBase from 'ui/shared/entities/base/components'; import * as EntityBase from 'ui/shared/entities/base/components';
import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip'; import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
...@@ -32,8 +31,8 @@ const Link = chakra((props: LinkProps) => { ...@@ -32,8 +31,8 @@ const Link = chakra((props: LinkProps) => {
type IconProps = Pick<EntityProps, 'pool' | 'className'> & EntityBase.IconBaseProps; type IconProps = Pick<EntityProps, 'pool' | 'className'> & EntityBase.IconBaseProps;
const Icon = (props: IconProps) => { const Icon = (props: IconProps) => {
const bgColor = useColorModeValue('white', 'black'); const bgColor = { _light: 'white', _dark: 'black' };
const borderColor = useColorModeValue('whiteAlpha.800', 'blackAlpha.800'); const borderColor = { _light: 'whiteAlpha.800', _dark: 'blackAlpha.800' };
return ( return (
<Flex> <Flex>
<Flex <Flex
...@@ -87,7 +86,7 @@ const Content = chakra((props: ContentProps) => { ...@@ -87,7 +86,7 @@ const Content = chakra((props: ContentProps) => {
return ( return (
<TruncatedTextTooltip label={ nameString }> <TruncatedTextTooltip label={ nameString }>
<Skeleton <Skeleton
isLoaded={ !props.isLoading } loading={ props.isLoading }
display="inline-block" display="inline-block"
whiteSpace="nowrap" whiteSpace="nowrap"
overflow="hidden" overflow="hidden"
...@@ -119,7 +118,7 @@ const PoolEntity = (props: EntityProps) => { ...@@ -119,7 +118,7 @@ const PoolEntity = (props: EntityProps) => {
); );
}; };
export default React.memo(chakra<As, EntityProps>(PoolEntity)); export default React.memo(chakra(PoolEntity));
export { export {
Container, Container,
......
...@@ -43,7 +43,7 @@ const Icon = (props: IconProps) => { ...@@ -43,7 +43,7 @@ const Icon = (props: IconProps) => {
}; };
if (props.isLoading) { if (props.isLoading) {
return <Skeleton { ...styles } className={ props.className }/>; return <Skeleton { ...styles } loading className={ props.className }/>;
} }
return ( return (
......
import type { As } from '@chakra-ui/react';
import { chakra } from '@chakra-ui/react'; import { chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
...@@ -76,7 +75,7 @@ const UserOpEntity = (props: EntityProps) => { ...@@ -76,7 +75,7 @@ const UserOpEntity = (props: EntityProps) => {
); );
}; };
export default React.memo(chakra<As, EntityProps>(UserOpEntity)); export default React.memo(chakra(UserOpEntity));
export { export {
Container, Container,
......
...@@ -14,12 +14,11 @@ interface Props { ...@@ -14,12 +14,11 @@ interface Props {
children: React.ReactNode; children: React.ReactNode;
isLoading?: boolean; isLoading?: boolean;
variant?: Variants; variant?: Variants;
visual?: LinkProps['visual'];
iconColor?: LinkProps['color']; iconColor?: LinkProps['color'];
onClick?: LinkProps['onClick']; onClick?: LinkProps['onClick'];
} }
const LinkExternal = ({ href, children, className, isLoading, variant, visual, iconColor, onClick }: Props) => { const LinkExternal = ({ href, children, className, isLoading, variant, iconColor, onClick }: Props) => {
const commonProps = { const commonProps = {
display: 'inline-block', display: 'inline-block',
alignItems: 'center', alignItems: 'center',
...@@ -46,7 +45,7 @@ const LinkExternal = ({ href, children, className, isLoading, variant, visual, i ...@@ -46,7 +45,7 @@ const LinkExternal = ({ href, children, className, isLoading, variant, visual, i
} }
return ( return (
<Link className={ className } { ...styleProps } target="_blank" href={ href } onClick={ onClick } visual={ visual }> <Link className={ className } { ...styleProps } target="_blank" href={ href } onClick={ onClick } variant={ variant }>
{ children } { children }
<IconSvg name="link_external" boxSize={ 3 } verticalAlign="middle" color={ iconColor ?? 'icon_link_external' } flexShrink={ 0 }/> <IconSvg name="link_external" boxSize={ 3 } verticalAlign="middle" color={ iconColor ?? 'icon_link_external' } flexShrink={ 0 }/>
</Link> </Link>
......
/* eslint-disable max-len */
import { Box } from '@chakra-ui/react';
import React from 'react';
import * as addressMock from 'mocks/address/address';
import * as implementationsMock from 'mocks/address/implementations';
import * as blobsMock from 'mocks/blobs/blobs';
import * as blockMock from 'mocks/blocks/block';
import * as ensMock from 'mocks/ens/domain';
import * as poolMock from 'mocks/pools/pool';
import * as txMock from 'mocks/txs/tx';
import { Link } from 'toolkit/chakra/link';
import CutLink from 'toolkit/components/CutLink/CutLink';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import BlobEntity from 'ui/shared/entities/blob/BlobEntity';
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import EnsEntity from 'ui/shared/entities/ens/EnsEntity';
import NftEntity from 'ui/shared/entities/nft/NftEntity';
import PoolEntity from 'ui/shared/entities/pool/PoolEntity';
import TokenEntity from 'ui/shared/entities/token/TokenEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
import { Section, Container, SectionHeader, SamplesStack, Sample, SectionSubHeader } from './parts';
const TEXT = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.';
const TOKEN = {
address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
circulating_market_cap: '139446916652.6728',
decimals: '6',
exchange_rate: '0.999921',
holders: '8348277',
icon_url: 'https://assets.coingecko.com/coins/images/325/small/Tether.png?1696501661',
name: 'Tether',
symbol: 'USDT',
total_supply: '76923002799740785',
type: 'ERC-20' as const,
volume_24h: '82069586622.4918',
};
const LinksShowcase = () => {
return (
<Container value="links">
<Section>
<SectionHeader>Variants</SectionHeader>
<SamplesStack>
<Sample label="variant: primary">
<Link href="/blocks" variant="primary">Default</Link>
<Link href="/" variant="primary" data-hover>Hover</Link>
</Sample>
<Sample label="variant: secondary">
<Link href="/" variant="secondary">Default</Link>
<Link href="/" variant="secondary" data-hover>Hover</Link>
</Sample>
<Sample label="variant: subtle">
<Link href="/" variant="subtle">Default</Link>
<Link href="/" variant="subtle" data-hover>Hover</Link>
</Sample>
<Sample label="variant: navigation">
<Link href="/" variant="navigation" p={ 2 } borderRadius="base">Default</Link>
<Link href="/" variant="navigation" p={ 2 } borderRadius="base" data-hover>Hover</Link>
<Link href="/" variant="navigation" p={ 2 } borderRadius="base" data-selected>Selected</Link>
<Link href="/" variant="navigation" p={ 2 } borderRadius="base" data-active>Active</Link>
</Sample>
<Sample label="variant: underlaid">
<Link href="/" variant="underlaid" external>Default</Link>
<Link href="/" variant="underlaid" external data-hover>Hover</Link>
</Sample>
</SamplesStack>
</Section>
<Section>
<SectionHeader>Loading</SectionHeader>
<SamplesStack>
<Sample label="loading: true, variant: primary">
<Link href="/" loading>hello hello hello</Link>
<span>Within <Link loading>hello</Link> text</span>
</Sample>
<Sample label="loading: true, variant: underlaid">
<Link href="/" loading variant="underlaid">hello hello hello</Link>
<span>Within <Link loading variant="underlaid">hello hello</Link> text</span>
</Sample>
</SamplesStack>
</Section>
<Section>
<SectionHeader>Examples</SectionHeader>
<SectionSubHeader>Cut link</SectionSubHeader>
<SamplesStack>
<Sample label="Default" flexDirection="column" alignItems="flex-start">
<CutLink id="CutLink_1">
<Box maxW="500px">{ TEXT }</Box>
</CutLink>
</Sample>
<Sample label="Loading" flexDirection="column" alignItems="flex-start">
<CutLink id="CutLink_2" isLoading>
<Box maxW="500px">{ TEXT }</Box>
</CutLink>
</Sample>
</SamplesStack>
<SectionSubHeader>Address link</SectionSubHeader>
<SamplesStack>
<Sample label="Without name" vertical>
<AddressEntity address={ addressMock.withoutName }/>
<AddressEntity address={ addressMock.withoutName } isExternal/>
<AddressEntity address={{ ...addressMock.filecoin, name: null }}/>
<Box maxW="200px">
<AddressEntity address={ addressMock.withoutName }/>
</Box>
<AddressEntity address={ addressMock.withoutName } isLoading/>
</Sample>
<Sample label="Size: md, lg??? (heading)" vertical>
<AddressEntity address={ addressMock.withoutName } size="md"/>
<AddressEntity address={ addressMock.withoutName } size="lg" textStyle="heading.md"/>
</Sample>
<Sample label="With name" vertical>
<AddressEntity address={ addressMock.withName }/>
<AddressEntity address={ addressMock.withNameTag }/>
<AddressEntity address={ addressMock.withEns }/>
<Box maxW="150px">
<AddressEntity address={ addressMock.withEns }/>
</Box>
</Sample>
<Sample label="Contract" vertical>
<AddressEntity address={{ ...addressMock.contract, is_verified: false, name: null, implementations: [] }}/>
<AddressEntity address={{ ...addressMock.contract, implementations: [] }}/>
<AddressEntity address={{ ...addressMock.contract, implementations: implementationsMock.multiple }}/>
<AddressEntity address={ addressMock.contract }/>
</Sample>
</SamplesStack>
<SectionSubHeader>Token link</SectionSubHeader>
<SamplesStack>
<Sample label="With info" vertical w="100%">
<TokenEntity token={ TOKEN }/>
<Box maxW="200px">
<TokenEntity token={{ ...TOKEN, name: 'Very looooooooong name' }} noSymbol/>
</Box>
<Box maxW="300px">
<TokenEntity token={{ ...TOKEN, symbol: 'Very looooooooong symbol' }}/>
</Box>
<TokenEntity token={ TOKEN } jointSymbol/>
<TokenEntity token={ TOKEN } onlySymbol/>
<TokenEntity token={ TOKEN } isLoading/>
</Sample>
<Sample label="With partial info" vertical w="100%">
<TokenEntity token={{ ...TOKEN, icon_url: null }}/>
<TokenEntity token={{ ...TOKEN, icon_url: null, name: null, symbol: null }}/>
</Sample>
<Sample label="Size: md, lg??? (heading)" vertical w="100%">
<TokenEntity token={ TOKEN } size="md"/>
<TokenEntity token={ TOKEN } size="lg" textStyle="heading.md" jointSymbol/>
</Sample>
</SamplesStack>
<SectionSubHeader>Transaction link</SectionSubHeader>
<SamplesStack>
<Sample label="Default" vertical w="100%">
<TxEntity hash={ txMock.base.hash }/>
<TxEntity hash={ txMock.base.hash } isExternal/>
<Box maxW="200px">
<TxEntity hash={ txMock.base.hash } noCopy={ false }/>
</Box>
<TxEntity hash={ txMock.base.hash } isLoading noCopy={ false }/>
</Sample>
</SamplesStack>
<SectionSubHeader>Block link</SectionSubHeader>
<SamplesStack>
<Sample label="Default" vertical w="100%">
<BlockEntity number={ blockMock.base.height }/>
<BlockEntity number={ blockMock.base.height } isExternal icon={{ name: 'txn_batches_slim' }}/>
<Box maxW="150px">
<BlockEntity number={ 1234567890123456 }/>
</Box>
<BlockEntity number={ blockMock.base.height } isLoading/>
</Sample>
</SamplesStack>
<SectionSubHeader>NFT link</SectionSubHeader>
<SamplesStack>
<Sample label="Default" vertical w="100%">
<NftEntity id="42" hash={ TOKEN.address }/>
<Box maxW="250px">
<NftEntity id="32925298983216553915666621415831103694597106215670571463977478984525997408266" hash={ TOKEN.address }/>
</Box>
<NftEntity id="4200000" hash={ TOKEN.address } isLoading/>
</Sample>
</SamplesStack>
<SectionSubHeader>ENS link</SectionSubHeader>
<SamplesStack>
<Sample label="Default" vertical w="100%">
<EnsEntity domain={ ensMock.ensDomainA.name } protocol={ ensMock.ensDomainA.protocol }/>
<Box maxW="150px">
<EnsEntity domain={ ensMock.ensDomainB.name } protocol={ ensMock.ensDomainB.protocol }/>
</Box>
<EnsEntity domain={ ensMock.ensDomainA.name } protocol={ ensMock.ensDomainA.protocol } isLoading/>
</Sample>
<Sample label="Size: md, lg??? (heading)" vertical w="100%">
<EnsEntity domain={ ensMock.ensDomainA.name } protocol={ ensMock.ensDomainA.protocol } size="md"/>
<EnsEntity domain={ ensMock.ensDomainA.name } protocol={ ensMock.ensDomainA.protocol } size="lg" textStyle="heading.md"/>
</Sample>
</SamplesStack>
<SectionSubHeader>Blob link</SectionSubHeader>
<SamplesStack>
<Sample label="Default" vertical w="100%">
<BlobEntity hash={ blobsMock.base1.hash }/>
<Box maxW="200px">
<BlobEntity hash={ blobsMock.base1.hash } isExternal/>
</Box>
<BlobEntity hash={ blobsMock.base1.hash } isLoading/>
</Sample>
</SamplesStack>
<SectionSubHeader>Pool link</SectionSubHeader>
<SamplesStack>
<Sample label="Default" vertical w="100%">
<PoolEntity pool={{
...poolMock.base,
base_token_icon_url: 'https://coin-images.coingecko.com/coins/images/39926/large/usds.webp?1726666683',
quote_token_icon_url: 'https://coin-images.coingecko.com/coins/images/39925/large/sky.jpg?1724827980',
}}/>
<Box maxW="150px">
<PoolEntity pool={ poolMock.noIcons } isExternal/>
</Box>
<PoolEntity pool={ poolMock.noIcons } isLoading/>
</Sample>
</SamplesStack>
</Section>
</Container>
);
};
export default React.memo(LinksShowcase);
import type { StackProps, TabsContentProps } from '@chakra-ui/react'; import type { StackProps, TabsContentProps } from '@chakra-ui/react';
import { Code, Grid, HStack } from '@chakra-ui/react'; import { Code, Grid, HStack, VStack } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { Heading } from 'toolkit/chakra/heading'; import { Heading } from 'toolkit/chakra/heading';
...@@ -13,16 +13,28 @@ export const SamplesStack = ({ children }: { children: React.ReactNode }) => ( ...@@ -13,16 +13,28 @@ export const SamplesStack = ({ children }: { children: React.ReactNode }) => (
<Grid <Grid
rowGap={ 4 } rowGap={ 4 }
columnGap={ 8 } columnGap={ 8 }
gridTemplateColumns="fit-content(100%) fit-content(100%)" gridTemplateColumns="fit-content(100%) 1fr"
justifyItems="flex-start" justifyItems="flex-start"
alignItems="flex-start" alignItems="flex-start"
> >
{ children } { children }
</Grid> </Grid>
); );
export const Sample = ({ children, label, ...props }: { children: React.ReactNode; label?: string } & StackProps) => ( export const Sample = ({ children, label, vertical, ...props }: { children: React.ReactNode; vertical?: boolean; label?: string } & StackProps) => {
<> const Stack = vertical ? VStack : HStack;
{ label && <Code w="fit-content">{ label }</Code> } return (
<HStack gap={ 3 } whiteSpace="pre-wrap" flexWrap="wrap" columnSpan={ label ? '1' : '2' } { ...props }>{ children }</HStack> <>
</> { label && <Code w="fit-content">{ label }</Code> }
); <Stack
gap={ 3 }
whiteSpace="pre-wrap"
flexWrap="wrap"
columnSpan={ label ? '1' : '2' }
alignItems={ vertical ? 'flex-start' : 'center' }
{ ...props }
>
{ children }
</Stack>
</>
);
};
...@@ -19,7 +19,7 @@ const FooterLinkItem = ({ icon, iconSize, text, url, isLoading }: Props) => { ...@@ -19,7 +19,7 @@ const FooterLinkItem = ({ icon, iconSize, text, url, isLoading }: Props) => {
} }
return ( return (
<Link href={ url } display="flex" alignItems="center" h="30px" visual="subtle" target="_blank" textStyle="xs"> <Link href={ url } display="flex" alignItems="center" h="30px" variant="subtle" target="_blank" textStyle="xs">
{ icon && ( { icon && (
<Center minW={ 6 } mr={ 2 }> <Center minW={ 6 } mr={ 2 }>
<IconSvg boxSize={ iconSize || 5 } name={ icon }/> <IconSvg boxSize={ iconSize || 5 } name={ icon }/>
......
...@@ -37,7 +37,7 @@ const NavLink = ({ className, item, noIcon }: Props) => { ...@@ -37,7 +37,7 @@ const NavLink = ({ className, item, noIcon }: Props) => {
href={ href } href={ href }
display="flex" display="flex"
alignItems="center" alignItems="center"
visual="navigation" variant="navigation"
{ ...(isActive ? { 'data-selected': true } : {}) } { ...(isActive ? { 'data-selected': true } : {}) }
w="224px" w="224px"
px={ 2 } px={ 2 }
......
...@@ -50,7 +50,7 @@ const NavLinkGroup = ({ item }: Props) => { ...@@ -50,7 +50,7 @@ const NavLinkGroup = ({ item }: Props) => {
visual="popover" visual="popover"
content={ content } content={ content }
onOpenChange={ onOpenChange } onOpenChange={ onOpenChange }
lazyMount lazyMount={ false }
positioning={{ positioning={{
placement: 'bottom', placement: 'bottom',
offset: { mainAxis: 8 }, offset: { mainAxis: 8 },
...@@ -66,7 +66,7 @@ const NavLinkGroup = ({ item }: Props) => { ...@@ -66,7 +66,7 @@ const NavLinkGroup = ({ item }: Props) => {
py={ 1.5 } py={ 1.5 }
textStyle="sm" textStyle="sm"
fontWeight={ 500 } fontWeight={ 500 }
visual="navigation" variant="navigation"
{ ...(item.isActive ? { 'data-selected': true } : {}) } { ...(item.isActive ? { 'data-selected': true } : {}) }
{ ...(open ? { 'data-active': true } : {}) } { ...(open ? { 'data-active': true } : {}) }
borderRadius="base" borderRadius="base"
......
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