Commit 8bfb04b2 authored by tom goriunov's avatar tom goriunov Committed by GitHub

Merge pull request #83 from blockscout/skeletons

skeletons
parents 0c9eb30e 9225e72c
export default function delay(time: number) {
return new Promise((resolve) => window.setTimeout(resolve, time));
}
import { Skeleton as SkeletonComponent } from '@chakra-ui/react';
import { keyframes } from '@chakra-ui/system';
import type { SystemStyleFunction } from '@chakra-ui/theme-tools';
import { getColor, mode } from '@chakra-ui/theme-tools';
const shine = () =>
keyframes({
to: { backgroundPositionX: '-200%' },
});
const baseStyle: SystemStyleFunction = (props) => {
const defaultStartColor = mode('blackAlpha.50', 'whiteAlpha.50')(props);
const defaultEndColor = mode('blackAlpha.100', 'whiteAlpha.100')(props);
const {
startColor = defaultStartColor,
endColor = defaultEndColor,
speed,
theme,
} = props;
const start = getColor(theme, startColor);
const end = getColor(theme, endColor);
return {
opacity: 1,
borderRadius: 'base',
borderColor: start,
background: `linear-gradient(90deg, ${ start } 8%, ${ end } 18%, ${ start } 33%)`,
backgroundSize: '200% 100%',
animation: `${ speed }s linear infinite ${ shine() }`,
};
};
const Skeleton = {
baseStyle,
};
export default Skeleton;
SkeletonComponent.defaultProps = {
...SkeletonComponent.defaultProps,
speed: 1,
};
...@@ -7,6 +7,7 @@ import Link from './Link'; ...@@ -7,6 +7,7 @@ import Link from './Link';
import Modal from './Modal'; import Modal from './Modal';
import Popover from './Popover'; import Popover from './Popover';
import Radio from './Radio'; import Radio from './Radio';
import Skeleton from './Skeleton';
import Table from './Table'; import Table from './Table';
import Tabs from './Tabs'; import Tabs from './Tabs';
import Tag from './Tag'; import Tag from './Tag';
...@@ -24,6 +25,7 @@ const components = { ...@@ -24,6 +25,7 @@ const components = {
Modal, Modal,
Popover, Popover,
Radio, Radio,
Skeleton,
Tabs, Tabs,
Table, Table,
Tag, Tag,
......
...@@ -18,7 +18,7 @@ interface Props { ...@@ -18,7 +18,7 @@ interface Props {
onDeleteClick: (item: ApiKey) => void; onDeleteClick: (item: ApiKey) => void;
} }
const WatchlistTableItem = ({ item, onEditClick, onDeleteClick }: Props) => { const ApiKeyTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
const onItemEditClick = useCallback(() => { const onItemEditClick = useCallback(() => {
return onEditClick(item); return onEditClick(item);
...@@ -47,4 +47,4 @@ const WatchlistTableItem = ({ item, onEditClick, onDeleteClick }: Props) => { ...@@ -47,4 +47,4 @@ const WatchlistTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
); );
}; };
export default WatchlistTableItem; export default ApiKeyTableItem;
...@@ -22,8 +22,7 @@ import styles from './ColorModeToggler.module.css'; ...@@ -22,8 +22,7 @@ import styles from './ColorModeToggler.module.css';
export interface ColorModeTogglerProps export interface ColorModeTogglerProps
extends Omit<UseCheckboxProps, 'isIndeterminate'>, extends Omit<UseCheckboxProps, 'isIndeterminate'>,
Omit<HTMLChakraProps<'label'>, keyof UseCheckboxProps>, Omit<HTMLChakraProps<'label'>, keyof UseCheckboxProps>,
ThemingProps<'Switch'> { ThemingProps<'Switch'> {}
}
const ColorModeToggler = forwardRef<ColorModeTogglerProps, 'input'>((props, ref) => { const ColorModeToggler = forwardRef<ColorModeTogglerProps, 'input'>((props, ref) => {
const ownProps = omitThemingProps(props); const ownProps = omitThemingProps(props);
......
import { Box, Button, HStack, Link, Text, Spinner, useDisclosure } from '@chakra-ui/react'; import { Box, Button, HStack, Link, Text, Skeleton, useDisclosure } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
...@@ -10,6 +10,7 @@ import ApiKeyTable from 'ui/apiKey/ApiKeyTable/ApiKeyTable'; ...@@ -10,6 +10,7 @@ import ApiKeyTable from 'ui/apiKey/ApiKeyTable/ApiKeyTable';
import DeleteApiKeyModal from 'ui/apiKey/DeleteApiKeyModal'; import DeleteApiKeyModal from 'ui/apiKey/DeleteApiKeyModal';
import AccountPageHeader from 'ui/shared/AccountPageHeader'; import AccountPageHeader from 'ui/shared/AccountPageHeader';
import Page from 'ui/shared/Page/Page'; import Page from 'ui/shared/Page/Page';
import SkeletonTable from 'ui/shared/SkeletonTable';
const DATA_LIMIT = 3; const DATA_LIMIT = 3;
...@@ -50,20 +51,23 @@ const ApiKeysPage: React.FC = () => { ...@@ -50,20 +51,23 @@ const ApiKeysPage: React.FC = () => {
const content = (() => { const content = (() => {
if (isLoading || isError) { if (isLoading || isError) {
return <Spinner/>; return (
<>
<SkeletonTable columns={ [ '100%', '108px' ] }/>
<Skeleton height="44px" width="156px" marginTop={ 8 }/>
</>
);
} }
const canAdd = data.length < DATA_LIMIT; const canAdd = data.length < DATA_LIMIT;
return ( return (
<> <>
{ data.length > 0 && ( <ApiKeyTable
<ApiKeyTable data={ data }
data={ data } onDeleteClick={ onDeleteClick }
onDeleteClick={ onDeleteClick } onEditClick={ onEditClick }
onEditClick={ onEditClick } limit={ DATA_LIMIT }
limit={ DATA_LIMIT } />
/>
) }
<HStack marginTop={ 8 } spacing={ 5 }> <HStack marginTop={ 8 } spacing={ 5 }>
<Button <Button
variant="primary" variant="primary"
......
import { Box, Button, Spinner, Text, useDisclosure } from '@chakra-ui/react'; import { Box, Button, Text, Skeleton, useDisclosure } from '@chakra-ui/react';
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
import type { AddressTags, AddressTag } from 'types/api/account'; import type { AddressTags, AddressTag } from 'types/api/account';
import SkeletonTable from 'ui/shared/SkeletonTable';
import AddressModal from './AddressModal/AddressModal'; import AddressModal from './AddressModal/AddressModal';
import AddressTagTable from './AddressTagTable/AddressTagTable'; import AddressTagTable from './AddressTagTable/AddressTagTable';
import DeletePrivateTagModal from './DeletePrivateTagModal'; import DeletePrivateTagModal from './DeletePrivateTagModal';
type Props = { type Props = {
addressTags: AddressTags; addressTags?: AddressTags;
} }
const PrivateAddressTags = ({ addressTags }: Props) => { const PrivateAddressTags = ({ addressTags }: Props) => {
...@@ -38,13 +40,26 @@ const PrivateAddressTags = ({ addressTags }: Props) => { ...@@ -38,13 +40,26 @@ const PrivateAddressTags = ({ addressTags }: Props) => {
deleteModalProps.onClose(); deleteModalProps.onClose();
}, [ deleteModalProps ]); }, [ deleteModalProps ]);
return ( const description = (
<> <Text marginBottom={ 12 }>
<Text marginBottom={ 12 }>
Use private transaction tags to label any transactions of interest. Use private transaction tags to label any transactions of interest.
Private tags are saved in your account and are only visible when you are logged in. Private tags are saved in your account and are only visible when you are logged in.
</Text> </Text>
{ !addressTags && <Spinner/> } );
if (!addressTags) {
return (
<>
{ description }
<SkeletonTable columns={ [ '60%', '40%', '108px' ] }/>
<Skeleton height="44px" width="156px" marginTop={ 8 }/>
</>
);
}
return (
<>
{ description }
{ Boolean(addressTags?.length) && ( { Boolean(addressTags?.length) && (
<AddressTagTable <AddressTagTable
data={ addressTags } data={ addressTags }
......
import { Box, Button, Text, useDisclosure } from '@chakra-ui/react'; import { Box, Button, Skeleton, Text, useDisclosure } from '@chakra-ui/react';
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
import type { TransactionTags, TransactionTag } from 'types/api/account'; import type { TransactionTags, TransactionTag } from 'types/api/account';
import SkeletonTable from 'ui/shared/SkeletonTable';
import DeletePrivateTagModal from './DeletePrivateTagModal'; import DeletePrivateTagModal from './DeletePrivateTagModal';
import TransactionModal from './TransactionModal/TransactionModal'; import TransactionModal from './TransactionModal/TransactionModal';
import TransactionTagTable from './TransactionTagTable/TransactionTagTable'; import TransactionTagTable from './TransactionTagTable/TransactionTagTable';
type Props = { type Props = {
transactionTags: TransactionTags; transactionTags?: TransactionTags;
} }
const PrivateTransactionTags = ({ transactionTags }: Props) => { const PrivateTransactionTags = ({ transactionTags }: Props) => {
...@@ -38,12 +40,26 @@ const PrivateTransactionTags = ({ transactionTags }: Props) => { ...@@ -38,12 +40,26 @@ const PrivateTransactionTags = ({ transactionTags }: Props) => {
deleteModalProps.onClose(); deleteModalProps.onClose();
}, [ deleteModalProps ]); }, [ deleteModalProps ]);
return ( const description = (
<> <Text marginBottom={ 12 }>
<Text marginBottom={ 12 }>
Use private transaction tags to label any transactions of interest. Use private transaction tags to label any transactions of interest.
Private tags are saved in your account and are only visible when you are logged in. Private tags are saved in your account and are only visible when you are logged in.
</Text> </Text>
);
if (!transactionTags) {
return (
<>
{ description }
<SkeletonTable columns={ [ '75%', '25%', '108px' ] }/>
<Skeleton height="44px" width="156px" marginTop={ 8 }/>
</>
);
}
return (
<>
{ description }
{ Boolean(transactionTags.length) && ( { Boolean(transactionTags.length) && (
<TransactionTagTable <TransactionTagTable
data={ transactionTags } data={ transactionTags }
......
import { Box, Text } from '@chakra-ui/react';
import { keyframes } from '@chakra-ui/system';
import React from 'react';
const runnerAnimation = keyframes`
0% { left: 0%; transform: translateX(-1%); }
100% { left: '100%'; transform: translateX(-99%); }
`;
const ContentLoader = () => {
return (
<Box display="inline-block">
<Box
width="100%"
height="6px"
position="relative"
_after={{
content: `" "`,
position: 'absolute',
width: '60px',
height: '6px',
animation: `${ runnerAnimation } 700ms ease-in-out infinite alternate`,
left: '100%',
top: 0,
backgroundColor: 'blue.300',
borderRadius: 'full',
}}
/>
<Text mt={ 6 } variant="secondary">Loading data, please wait... </Text>
</Box>
);
};
export default ContentLoader;
import { HStack, Skeleton } from '@chakra-ui/react';
import React from 'react';
interface Props {
columns: Array<string>;
}
const SkeletonTable = ({ columns }: Props) => {
return (
<div>
<Skeleton height={ 10 } width="100%" borderBottomLeftRadius="none" borderBottomRightRadius="none"/>
{ Array.from(Array(3)).map((item, index) => (
<HStack key={ index } spacing={ 6 } marginTop={ 8 }>
{ columns.map((width, index) => (
<Skeleton
key={ index }
height={ 5 }
width={ width }
flexShrink={ width.includes('%') ? 'initial' : 0 }
borderRadius="full"
/>
)) }
</HStack>
)) }
</div>
);
};
export default React.memo(SkeletonTable);
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