mirror of https://github.com/lukechilds/docs.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
326 lines
9.0 KiB
326 lines
9.0 KiB
import React from 'react';
|
|
import { Box, Flex, BoxProps, color, Grid, space, StxInline } from '@stacks/ui';
|
|
import { BlockstackLogo } from '@components/icons/blockstack-logo';
|
|
import { StackIcon } from '@components/icons/stack';
|
|
import { SitemapIcon } from '@components/icons/sitemap';
|
|
import { border, onlyText, transition } from '@common/utils';
|
|
import { useTouchable } from '@common/hooks/use-touchable';
|
|
import { Text } from '@components/typography';
|
|
import Link from 'next/link';
|
|
import { useAppState } from '@common/hooks/use-app-state';
|
|
import { Img } from '@components/mdx/image';
|
|
import { getCapsizeStyles, getHeadingStyles } from '@components/mdx/typography';
|
|
|
|
const Image = ({
|
|
src,
|
|
isHovered,
|
|
size,
|
|
alt,
|
|
...rest
|
|
}: BoxProps & { src?: string; isHovered?: boolean; alt?: string }) => (
|
|
<Box
|
|
flexShrink={0}
|
|
style={{
|
|
willChange: 'transform',
|
|
}}
|
|
width="100%"
|
|
size={size}
|
|
{...rest}
|
|
>
|
|
<Img
|
|
flexShrink={0}
|
|
borderRadius="12px"
|
|
src={src}
|
|
width="100%"
|
|
minWidth={size}
|
|
size={size}
|
|
alt={alt}
|
|
mx="0 !important"
|
|
my="0 !important"
|
|
/>
|
|
</Box>
|
|
);
|
|
|
|
const Title: React.FC<BoxProps> = ({ children, ...props }) => (
|
|
<Text {...getHeadingStyles('h3')} {...props}>
|
|
{children}
|
|
</Text>
|
|
);
|
|
|
|
const Description = ({ children, ...props }) => (
|
|
<Text
|
|
{...props}
|
|
{...getCapsizeStyles(16, 26)}
|
|
mt={space('base-tight')}
|
|
color={color('text-body')}
|
|
>
|
|
{children}
|
|
</Text>
|
|
);
|
|
|
|
const FloatingLink = ({ href, contents, ...props }: any) => (
|
|
<Link href={href} passHref>
|
|
<Box
|
|
as="a"
|
|
position="absolute"
|
|
size="100%"
|
|
opacity={0}
|
|
color="transparent"
|
|
style={{
|
|
userSelect: 'none',
|
|
}}
|
|
left={0}
|
|
top={0}
|
|
>
|
|
{contents}
|
|
</Box>
|
|
</Link>
|
|
);
|
|
const InlineCard = ({ page }) => {
|
|
const { hover, active, bind } = useTouchable({
|
|
behavior: 'link',
|
|
});
|
|
return (
|
|
<Flex
|
|
border={border()}
|
|
flexDirection={['column', 'row', 'row', 'row']}
|
|
p={space('base-loose')}
|
|
borderRadius="12px"
|
|
alignItems="center"
|
|
transition={transition()}
|
|
boxShadow={hover ? 'mid' : 'none'}
|
|
position="relative"
|
|
{...bind}
|
|
>
|
|
<Box
|
|
flexShrink={0}
|
|
position="relative"
|
|
size="64px"
|
|
overflow="hidden"
|
|
bg="#9985FF"
|
|
borderRadius={'12px'}
|
|
>
|
|
<Image
|
|
size="102%"
|
|
left={'-2%'}
|
|
top={'-2%'}
|
|
position="absolute"
|
|
transition={transition('0.45s')}
|
|
transform={(hover || active) && 'scale(1.18)'}
|
|
style={{ willChange: 'transform' }}
|
|
src={page?.images?.sm}
|
|
alt={`Graphic for: ${page.title || page.headings[0]}`}
|
|
/>
|
|
</Box>
|
|
<Flex
|
|
flexDirection="column"
|
|
ml={space(['none', 'base', 'base', 'base'])}
|
|
mt={space(['base', 'none', 'none', 'none'])}
|
|
textAlign={['center', 'left', 'left', 'left']}
|
|
>
|
|
<Flex alignItems="baseline">
|
|
<Title
|
|
width={['100%', 'unset', 'unset', 'unset']}
|
|
color={hover ? color('accent') : color('text-title')}
|
|
mb={space('extra-tight')}
|
|
>
|
|
{page.title || page.headings[0]}
|
|
</Title>
|
|
{page.tags?.length ? (
|
|
<Flex
|
|
position={['absolute', 'static', 'static', 'static']}
|
|
top={space('base-loose')}
|
|
right={space('base-loose')}
|
|
>
|
|
{page.tags.map((tag, key) => (
|
|
<Flex
|
|
ml={space('tight')}
|
|
borderRadius="18px"
|
|
px={space('base-tight')}
|
|
height="20px"
|
|
alignItems="center"
|
|
justify="center"
|
|
fontSize="12px"
|
|
bg={color('border')}
|
|
textTransform="capitalize"
|
|
color={color('invert')}
|
|
transition={transition()}
|
|
key={key}
|
|
>
|
|
{tag}
|
|
</Flex>
|
|
))}
|
|
</Flex>
|
|
) : null}
|
|
</Flex>
|
|
<Description>{page.description}</Description>
|
|
</Flex>
|
|
<FloatingLink href={`${page.path}`} />
|
|
</Flex>
|
|
);
|
|
};
|
|
|
|
const GridCardImage: React.FC<
|
|
BoxProps & { isHovered?: boolean; src?: string; alt?: string }
|
|
> = React.memo(({ isHovered, src, alt, ...props }) => (
|
|
<Box
|
|
bg="#9985FF"
|
|
position="relative"
|
|
borderRadius="12px"
|
|
marginBottom="base"
|
|
overflow="hidden"
|
|
{...props}
|
|
>
|
|
<Grid style={{ placeItems: 'center' }} height="0px" paddingTop="56.25%">
|
|
<Image
|
|
size="102%"
|
|
left={'-2%'}
|
|
top={'-2%'}
|
|
position="absolute"
|
|
transition={transition('0.45s')}
|
|
transform={isHovered && 'scale(1.08)'}
|
|
style={{ willChange: 'transform' }}
|
|
src={src}
|
|
alt={alt}
|
|
/>
|
|
</Grid>
|
|
</Box>
|
|
));
|
|
|
|
const GridItemDetails: React.FC<BoxProps & { isHovered?: boolean; page: any }> = React.memo(
|
|
({ isHovered, page, ...props }) => (
|
|
<>
|
|
<Flex alignItems="flex-start" justifyContent="flex-start" flexDirection="column">
|
|
<Title color="currentColor" mb={space('tight')}>
|
|
{page.title || page.headings[0]}
|
|
</Title>
|
|
<Description>{page.description}</Description>
|
|
</Flex>
|
|
<FloatingLink href={`${page.path}`} contents={page.title || page.headings[0]} />
|
|
</>
|
|
)
|
|
);
|
|
|
|
const GridCard: React.FC<BoxProps & { page?: any }> = React.memo(({ page, ...rest }) => {
|
|
const { hover, active, bind } = useTouchable({
|
|
behavior: 'link',
|
|
});
|
|
return (
|
|
<Box
|
|
position="relative"
|
|
color={color('text-title')}
|
|
_hover={{ color: color('accent') }}
|
|
{...rest}
|
|
{...bind}
|
|
>
|
|
<GridCardImage
|
|
alt={`Graphic for: ${page.title || page.headings[0]}`}
|
|
src={page?.images?.large}
|
|
isHovered={hover || active}
|
|
mb={'loose'}
|
|
/>
|
|
<GridItemDetails page={page} />
|
|
</Box>
|
|
);
|
|
});
|
|
|
|
const getIcon = (icon: string) => {
|
|
switch (icon) {
|
|
case 'BlockstackIcon':
|
|
return (p: BoxProps) => (
|
|
<Grid borderRadius="6px" placeItems="center" bg="#9985FF" size="32px" {...p}>
|
|
<StxInline size="20px" color={color('bg')} />
|
|
</Grid>
|
|
);
|
|
case 'StacksIcon':
|
|
return (p: BoxProps) => (
|
|
<Grid borderRadius="6px" style={{ placeItems: 'center' }} bg="#9985FF" size="32px" {...p}>
|
|
<StackIcon size="24px" color={color('bg')} />
|
|
</Grid>
|
|
);
|
|
case 'RegtestIcon':
|
|
return (p: BoxProps) => (
|
|
<Grid borderRadius="6px" style={{ placeItems: 'center' }} bg="#9985FF" size="32px" {...p}>
|
|
<SitemapIcon size="24px" color={color('bg')} />
|
|
</Grid>
|
|
);
|
|
case 'TestnetIcon':
|
|
return (p: BoxProps) => (
|
|
<Grid borderRadius="6px" style={{ placeItems: 'center' }} bg="#9985FF" size="32px" {...p}>
|
|
<SitemapIcon size="24px" color={color('bg')} />
|
|
</Grid>
|
|
);
|
|
case 'MainnetIcon':
|
|
return (p: BoxProps) => (
|
|
<Grid borderRadius="6px" style={{ placeItems: 'center' }} bg="#9985FF" size="32px" {...p}>
|
|
<SitemapIcon size="24px" color={color('bg')} />
|
|
</Grid>
|
|
);
|
|
default:
|
|
return (p: BoxProps) => <BlockstackLogo size="32px" color={color('accent')} {...p} />;
|
|
}
|
|
};
|
|
const GridSmallItem: React.FC<BoxProps & { page?: any }> = ({ page, ...rest }) => {
|
|
const { hover, active, bind } = useTouchable({
|
|
behavior: 'link',
|
|
});
|
|
const Icon = getIcon(page.icon);
|
|
return (
|
|
<Box
|
|
position="relative"
|
|
color={color('text-title')}
|
|
_hover={{ color: color('accent') }}
|
|
{...rest}
|
|
{...bind}
|
|
>
|
|
{page.icon ? <Icon mb={space('loose')} /> : null}
|
|
<GridItemDetails page={page} />
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
const getComponent = (type: 'default' | 'inline' | 'grid' | 'grid-small') => {
|
|
switch (type) {
|
|
case 'inline':
|
|
return InlineCard;
|
|
case 'grid':
|
|
return GridCard;
|
|
case 'grid-small':
|
|
return GridSmallItem;
|
|
default:
|
|
return InlineCard;
|
|
}
|
|
};
|
|
|
|
export const PageReference: React.FC<BoxProps> = React.memo(({ children, ...rest }) => {
|
|
const content = onlyText(children).trim();
|
|
const [variant, _paths] = content.includes('\n') ? content.split('\n') : ['default', content];
|
|
const paths = _paths.includes(', ') ? _paths.split(', ') : [_paths];
|
|
const { routes } = useAppState();
|
|
|
|
if (!routes) return null;
|
|
|
|
const pages = paths.map(path => routes?.find(route => route.path === path)).filter(page => page);
|
|
|
|
const Comp = getComponent(variant as any);
|
|
return (
|
|
<Box {...rest}>
|
|
<Grid
|
|
width="100%"
|
|
gridColumnGap={space('extra-loose')}
|
|
gridRowGap={space('extra-loose')}
|
|
mt={space('extra-loose')}
|
|
gridTemplateColumns={[
|
|
`repeat(1, 1fr)`,
|
|
`repeat(${pages.length === 1 ? 1 : 2}, 1fr)`,
|
|
`repeat(${pages.length === 1 ? 1 : 2}, 1fr)`,
|
|
`repeat(${pages.length === 1 ? 1 : 3}, 1fr)`,
|
|
]}
|
|
>
|
|
{pages.map((page, key) => (
|
|
<Comp key={key} page={page} />
|
|
))}
|
|
</Grid>
|
|
</Box>
|
|
);
|
|
});
|
|
|