Commit 5e920bb5 authored by tom's avatar tom

improve loading state of tabs list

parent 443d1853
......@@ -71,6 +71,7 @@ const AdaptiveTabs = (props: Props) => {
stickyEnabled={ stickyEnabled }
activeTab={ activeTab }
isLoading={ isLoading }
variant={ variant }
/>
{ tabs.map((tab) => {
const value = getTabValue(tab);
......
......@@ -9,6 +9,7 @@ import useIsMobile from 'lib/hooks/useIsMobile';
import { useIsSticky } from '../..//hooks/useIsSticky';
import { Skeleton } from '../../chakra/skeleton';
import type { TabsProps } from '../../chakra/tabs';
import { TabsCounter, TabsList, TabsTrigger } from '../../chakra/tabs';
import AdaptiveTabsMenu from './AdaptiveTabsMenu';
import useAdaptiveTabs from './useAdaptiveTabs';
......@@ -32,6 +33,7 @@ export interface BaseProps {
interface Props extends BaseProps {
activeTab: string;
variant: TabsProps['variant'];
}
const HIDDEN_ITEM_STYLES: HTMLChakraProps<'button'> = {
......@@ -41,19 +43,17 @@ const HIDDEN_ITEM_STYLES: HTMLChakraProps<'button'> = {
visibility: 'hidden',
};
const getItemStyles = (index: number, tabsCut: number | undefined) => {
if (tabsCut === undefined) {
const getItemStyles = (index: number, tabsCut: number | undefined, isLoading: boolean | undefined) => {
if (tabsCut === undefined || isLoading) {
return HIDDEN_ITEM_STYLES as never;
}
return index < tabsCut ? {} : HIDDEN_ITEM_STYLES as never;
};
const getMenuStyles = (tabsLength: number, tabsCut: number | undefined) => {
if (tabsCut === undefined) {
return {
opacity: 0,
};
const getMenuStyles = (tabsLength: number, tabsCut: number | undefined, isLoading: boolean | undefined) => {
if (tabsCut === undefined || isLoading) {
return HIDDEN_ITEM_STYLES;
}
return tabsCut >= tabsLength ? HIDDEN_ITEM_STYLES : {};
......@@ -71,6 +71,7 @@ const AdaptiveTabsList = (props: Props) => {
leftSlotProps,
stickyEnabled,
isLoading,
variant,
} = props;
const scrollDirection = useScrollDirection();
......@@ -80,11 +81,13 @@ const AdaptiveTabsList = (props: Props) => {
return [ ...tabs, menuButton ];
}, [ tabs ]);
const { tabsCut, tabsRefs, listRef, rightSlotRef, leftSlotRef } = useAdaptiveTabs(tabsList, isMobile);
const { tabsCut, tabsRefs, listRef, rightSlotRef, leftSlotRef } = useAdaptiveTabs(tabsList, isLoading || isMobile);
const isSticky = useIsSticky(listRef, 5, stickyEnabled);
const activeTabIndex = tabsList.findIndex((tab) => getTabValue(tab) === activeTab) ?? 0;
useScrollToActiveTab({ activeTabIndex, listRef, tabsRefs, isMobile, isLoading });
const isReady = !isLoading && tabsCut !== undefined;
return (
<TabsList
ref={ listRef }
......@@ -92,10 +95,6 @@ const AdaptiveTabsList = (props: Props) => {
alignItems="center"
whiteSpace="nowrap"
bgColor={{ _light: 'white', _dark: 'black' }}
// initially our cut is 0 and we don't want to show the list
// but we want to keep all items in the tabs row so it won't collapse
// that's why we only change opacity but not the position itself
opacity={ tabsCut ? 1 : 0 }
marginBottom={ 6 }
mx={{ base: '-12px', lg: 'unset' }}
px={{ base: '12px', lg: 'unset' }}
......@@ -134,15 +133,11 @@ const AdaptiveTabsList = (props: Props) => {
</Box>
)
}
{ tabsList.slice(0, isLoading ? 5 : Infinity).map((tab, index) => {
{ tabsList.map((tab, index) => {
const value = getTabValue(tab);
const ref = tabsRefs[index];
if (tab.id === 'menu') {
if (isLoading) {
return null;
}
return (
<AdaptiveTabsMenu
key="menu"
......@@ -150,7 +145,7 @@ const AdaptiveTabsList = (props: Props) => {
tabs={ tabs }
tabsCut={ tabsCut ?? 0 }
isActive={ activeTabIndex > 0 && tabsCut !== undefined && tabsCut > 0 && activeTabIndex >= tabsCut }
{ ...getMenuStyles(tabs.length, tabsCut) }
{ ...getMenuStyles(tabs.length, tabsCut, isLoading) }
/>
);
}
......@@ -162,19 +157,30 @@ const AdaptiveTabsList = (props: Props) => {
ref={ ref }
scrollSnapAlign="start"
flexShrink={ 0 }
{ ...getItemStyles(index, tabsCut) }
{ ...getItemStyles(index, tabsCut, isLoading) }
>
{ isLoading ? (
<Skeleton loading>
{ typeof tab.title === 'function' ? tab.title() : tab.title }
<TabsCounter count={ tab.count }/>
</Skeleton>
) : (
<>
</TabsTrigger>
);
}) }
{ tabs.slice(0, isReady ? 0 : 5).map((tab, index) => {
const value = `${ getTabValue(tab) }-pre`;
return (
<TabsTrigger
key={ value }
value={ value }
flexShrink={ 0 }
bgColor={
activeTabIndex === index && (variant === 'solid' || variant === undefined) ?
{ _light: 'blackAlpha.50', _dark: 'whiteAlpha.50' } :
undefined
}
>
<Skeleton loading>
{ typeof tab.title === 'function' ? tab.title() : tab.title }
<TabsCounter count={ tab.count }/>
</>
) }
</Skeleton>
</TabsTrigger>
);
}) }
......
import { Flex, chakra, Box } from '@chakra-ui/react';
import React from 'react';
import type { TabItemRegular } from '../AdaptiveTabs/types';
import { Skeleton } from '../../chakra/skeleton';
import type { TabsProps } from '../../chakra/tabs';
import useActiveTabFromQuery from './useActiveTabFromQuery';
const SkeletonTabText = ({ size, title }: { size: TabsProps['size']; title: TabItemRegular['title'] }) => (
<Skeleton
borderRadius="base"
borderWidth={ size === 'sm' ? '2px' : 0 }
fontWeight={ 600 }
mx={ size === 'sm' ? 3 : 4 }
flexShrink={ 0 }
loading
>
{ typeof title === 'string' ? title : title() }
</Skeleton>
);
interface Props {
className?: string;
tabs: Array<TabItemRegular>;
size?: 'sm' | 'md';
}
const RoutedTabsSkeleton = ({ className, tabs, size = 'md' }: Props) => {
const activeTab = useActiveTabFromQuery(tabs);
if (tabs.length === 1) {
return null;
}
const tabIndex = activeTab ? tabs.findIndex((tab) => tab.id === activeTab.id) : 0;
return (
<Flex className={ className } my={ 8 } alignItems="center" overflow="hidden">
{ tabs.slice(0, tabIndex).map(({ title, id }) => (
<SkeletonTabText
key={ id.toString() }
title={ title }
size={ size }
/>
)) }
{ tabs.slice(tabIndex, tabIndex + 1).map(({ title, id }) => (
<Box
key={ id.toString() }
bgColor={{ _light: 'blackAlpha.50', _dark: 'whiteAlpha.50' }}
py={ size === 'sm' ? 1 : 2 }
borderRadius="base"
flexShrink={ 0 }
>
<SkeletonTabText
key={ id.toString() }
title={ title }
size={ size }
/>
</Box>
)) }
{ tabs.slice(tabIndex + 1).map(({ title, id }) => (
<SkeletonTabText
key={ id.toString() }
title={ title }
size={ size }
/>
)) }
</Flex>
);
};
export default chakra(RoutedTabsSkeleton);
export { default as RoutedTabs } from './RoutedTabs';
export { default as RoutedTabsSkeleton } from './RoutedTabsSkeleton';
export { default as useActiveTabFromQuery } from './useActiveTabFromQuery';
......@@ -12,7 +12,6 @@ import { BLOCK } from 'stubs/block';
import { TX } from 'stubs/tx';
import { generateListStub } from 'stubs/utils';
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
import RoutedTabsSkeleton from 'toolkit/components/RoutedTabs/RoutedTabsSkeleton';
import BlocksContent from 'ui/blocks/BlocksContent';
import TextAd from 'ui/shared/ad/TextAd';
import PageTitle from 'ui/shared/Page/PageTitle';
......@@ -116,15 +115,13 @@ const ArbitrumL2TxnBatch = () => {
backLink={ backLink }
isLoading={ batchQuery.isPlaceholderData }
/>
{ batchQuery.isPlaceholderData ?
<RoutedTabsSkeleton tabs={ tabs }/> : (
<RoutedTabs
tabs={ tabs }
isLoading={ batchQuery.isPlaceholderData }
listProps={ isMobile ? undefined : TAB_LIST_PROPS }
rightSlot={ hasPagination && pagination ? <Pagination { ...(pagination) }/> : null }
stickyEnabled={ hasPagination }
/>
) }
</>
);
};
......
......@@ -15,7 +15,6 @@ import getNetworkValidationActionText from 'lib/networks/getNetworkValidationAct
import getQueryParamString from 'lib/router/getQueryParamString';
import { Skeleton } from 'toolkit/chakra/skeleton';
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
import RoutedTabsSkeleton from 'toolkit/components/RoutedTabs/RoutedTabsSkeleton';
import BlockCeloEpochTag from 'ui/block/BlockCeloEpochTag';
import BlockDetails from 'ui/block/BlockDetails';
import BlockEpochRewards from 'ui/block/BlockEpochRewards';
......@@ -183,14 +182,13 @@ const BlockPageContent = () => {
secondRow={ titleSecondRow }
isLoading={ blockQuery.isPlaceholderData }
/>
{ blockQuery.isPlaceholderData ? <RoutedTabsSkeleton tabs={ tabs }/> : (
<RoutedTabs
tabs={ tabs }
isLoading={ blockQuery.isPlaceholderData }
listProps={ isMobile ? undefined : TAB_LIST_PROPS }
rightSlot={ hasPagination ? <Pagination { ...(pagination as PaginationParams) }/> : null }
stickyEnabled={ hasPagination }
/>
) }
</>
);
};
......
......@@ -14,8 +14,6 @@ import { ENS_DOMAIN } from 'stubs/ENS';
import { Link } from 'toolkit/chakra/link';
import { Tooltip } from 'toolkit/chakra/tooltip';
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
import RoutedTabsSkeleton from 'toolkit/components/RoutedTabs/RoutedTabsSkeleton';
import useActiveTabFromQuery from 'toolkit/components/RoutedTabs/useActiveTabFromQuery';
import NameDomainDetails from 'ui/nameDomain/NameDomainDetails';
import NameDomainHistory from 'ui/nameDomain/NameDomainHistory';
import TextAd from 'ui/shared/ad/TextAd';
......@@ -40,8 +38,6 @@ const NameDomain = () => {
{ id: 'history', title: 'History', component: <NameDomainHistory domain={ infoQuery.data }/> },
];
const activeTab = useActiveTabFromQuery(tabs);
throwOnResourceLoadError(infoQuery);
const isLoading = infoQuery.isPlaceholderData;
......@@ -87,12 +83,7 @@ const NameDomain = () => {
<>
<TextAd mb={ 6 }/>
<PageTitle title="Name details" secondRow={ titleSecondRow }/>
{ infoQuery.isPlaceholderData ? (
<>
<RoutedTabsSkeleton tabs={ tabs } mt={ 6 }/>
{ activeTab?.component }
</>
) : <RoutedTabs tabs={ tabs }/> }
<RoutedTabs tabs={ tabs } isLoading={ infoQuery.isPlaceholderData }/>
</>
);
};
......
......@@ -12,7 +12,6 @@ import { BLOCK } from 'stubs/block';
import { TX } from 'stubs/tx';
import { generateListStub } from 'stubs/utils';
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
import RoutedTabsSkeleton from 'toolkit/components/RoutedTabs/RoutedTabsSkeleton';
import BlocksContent from 'ui/blocks/BlocksContent';
import TextAd from 'ui/shared/ad/TextAd';
import PageTitle from 'ui/shared/Page/PageTitle';
......@@ -114,15 +113,13 @@ const OptimisticL2TxnBatch = () => {
backLink={ backLink }
isLoading={ batchQuery.isPlaceholderData }
/>
{ batchQuery.isPlaceholderData ?
<RoutedTabsSkeleton tabs={ tabs }/> : (
<RoutedTabs
tabs={ tabs }
isLoading={ batchQuery.isPlaceholderData }
listProps={ isMobile ? undefined : TAB_LIST_PROPS }
rightSlot={ hasPagination && pagination ? <Pagination { ...(pagination) }/> : null }
stickyEnabled={ hasPagination }
/>
) }
</>
);
};
......
......@@ -14,7 +14,6 @@ import { SCROLL_L2_TXN_BATCH } from 'stubs/scrollL2';
import { TX } from 'stubs/tx';
import { generateListStub } from 'stubs/utils';
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
import RoutedTabsSkeleton from 'toolkit/components/RoutedTabs/RoutedTabsSkeleton';
import BlocksContent from 'ui/blocks/BlocksContent';
import TextAd from 'ui/shared/ad/TextAd';
import PageTitle from 'ui/shared/Page/PageTitle';
......@@ -120,15 +119,13 @@ const ScrollL2TxnBatch = () => {
title={ `Txn batch #${ number }` }
backLink={ backLink }
/>
{ batchQuery.isPlaceholderData ?
<RoutedTabsSkeleton tabs={ tabs }/> : (
<RoutedTabs
tabs={ tabs }
isLoading={ batchQuery.isPlaceholderData }
listProps={ isMobile ? undefined : TAB_LIST_PROPS }
rightSlot={ hasPagination && pagination ? <Pagination { ...(pagination) }/> : null }
stickyEnabled={ hasPagination }
/>
) }
</>
);
};
......
......@@ -10,8 +10,6 @@ import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
import getQueryParamString from 'lib/router/getQueryParamString';
import { publicClient } from 'lib/web3/client';
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
import RoutedTabsSkeleton from 'toolkit/components/RoutedTabs/RoutedTabsSkeleton';
import useActiveTabFromQuery from 'toolkit/components/RoutedTabs/useActiveTabFromQuery';
import TextAd from 'ui/shared/ad/TextAd';
import isCustomAppError from 'ui/shared/AppError/isCustomAppError';
import EntityTags from 'ui/shared/EntityTags/EntityTags';
......@@ -78,8 +76,6 @@ const TransactionPageContent = () => {
].filter(Boolean);
})();
const activeTab = useActiveTabFromQuery(tabs);
const txTags: Array<TEntityTag> = data?.transaction_tag ?
[ { slug: data.transaction_tag, name: data.transaction_tag, tagType: 'private_tag' as const, ordinal: 1 } ] : [];
if (rollupFeature.isEnabled && rollupFeature.interopEnabled && data?.op_interop) {
......@@ -113,19 +109,6 @@ const TransactionPageContent = () => {
const titleSecondRow = <TxSubHeading hash={ hash } hasTag={ Boolean(data?.transaction_tag) } txQuery={ txQuery }/>;
const content = (() => {
if (isPlaceholderData && !showDegradedView) {
return (
<>
<RoutedTabsSkeleton tabs={ tabs } mt={ 6 }/>
{ activeTab?.component }
</>
);
}
return <RoutedTabs tabs={ tabs }/>;
})();
if (isError && !showDegradedView) {
if (isCustomAppError(error)) {
throwOnResourceLoadError({ resource: 'tx', error, isError: true });
......@@ -141,7 +124,7 @@ const TransactionPageContent = () => {
contentAfter={ tags }
secondRow={ titleSecondRow }
/>
{ content }
<RoutedTabs tabs={ tabs } isLoading={ isPlaceholderData }/>
</>
);
};
......
......@@ -13,8 +13,6 @@ import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
import getQueryParamString from 'lib/router/getQueryParamString';
import { USER_OP } from 'stubs/userOps';
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
import RoutedTabsSkeleton from 'toolkit/components/RoutedTabs/RoutedTabsSkeleton';
import useActiveTabFromQuery from 'toolkit/components/RoutedTabs/useActiveTabFromQuery';
import TextAd from 'ui/shared/ad/TextAd';
import PageTitle from 'ui/shared/Page/PageTitle';
import TxLogs from 'ui/tx/TxLogs';
......@@ -82,8 +80,6 @@ const UserOp = () => {
{ id: 'raw', title: 'Raw', component: <UserOpRaw rawData={ userOpQuery.data?.raw } isLoading={ userOpQuery.isPlaceholderData }/> },
]), [ userOpQuery, txQuery, filterTokenTransfersByLogIndex, filterLogsByLogIndex ]);
const activeTab = useActiveTabFromQuery(tabs);
const backLink = React.useMemo(() => {
const hasGoBackLink = appProps.referrer && appProps.referrer.includes('/ops');
......@@ -110,13 +106,7 @@ const UserOp = () => {
backLink={ backLink }
secondRow={ titleSecondRow }
/>
{ userOpQuery.isPlaceholderData ? (
<>
<RoutedTabsSkeleton tabs={ tabs } mt={ 6 }/>
{ activeTab?.component }
</>
) :
<RoutedTabs tabs={ tabs }/> }
<RoutedTabs tabs={ tabs } isLoading={ userOpQuery.isPlaceholderData }/>
</>
);
};
......
......@@ -12,7 +12,6 @@ import { TX_ZKEVM_L2 } from 'stubs/tx';
import { generateListStub } from 'stubs/utils';
import { ZKEVM_L2_TXN_BATCH } from 'stubs/zkEvmL2';
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
import RoutedTabsSkeleton from 'toolkit/components/RoutedTabs/RoutedTabsSkeleton';
import TextAd from 'ui/shared/ad/TextAd';
import PageTitle from 'ui/shared/Page/PageTitle';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
......@@ -71,11 +70,10 @@ const ZkEvmL2TxnBatch = () => {
title={ `Txn batch #${ number }` }
backLink={ backLink }
/>
{ batchQuery.isPlaceholderData ? <RoutedTabsSkeleton tabs={ tabs }/> : (
<RoutedTabs
tabs={ tabs }
isLoading={ batchQuery.isPlaceholderData }
/>
) }
</>
);
};
......
......@@ -13,7 +13,6 @@ import { TX } from 'stubs/tx';
import { generateListStub } from 'stubs/utils';
import { ZKSYNC_L2_TXN_BATCH } from 'stubs/zkSyncL2';
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
import RoutedTabsSkeleton from 'toolkit/components/RoutedTabs/RoutedTabsSkeleton';
import TextAd from 'ui/shared/ad/TextAd';
import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/pagination/Pagination';
......@@ -92,15 +91,13 @@ const ZkSyncL2TxnBatch = () => {
title={ `Txn batch #${ number }` }
backLink={ backLink }
/>
{ batchQuery.isPlaceholderData ?
<RoutedTabsSkeleton tabs={ tabs }/> : (
<RoutedTabs
tabs={ tabs }
isLoading={ batchQuery.isPlaceholderData }
listProps={ isMobile ? undefined : TAB_LIST_PROPS }
rightSlot={ hasPagination ? <Pagination { ...(batchTxsQuery.pagination) }/> : null }
stickyEnabled={ hasPagination }
/>
) }
</>
);
};
......
......@@ -23,9 +23,8 @@ const ButtonItem = ({ className, label, onClick, icon, isDisabled }: Props) => {
disabled={ isDisabled }
variant="icon_secondary"
boxSize={ 8 }
_icon={{ boxSize: 6 }}
>
{ typeof icon === 'string' ? <IconSvg name={ icon }/> : icon }
{ typeof icon === 'string' ? <IconSvg name={ icon } boxSize={ 6 }/> : icon }
</IconButton>
</Tooltip>
);
......
......@@ -3,7 +3,6 @@ import React from 'react';
import { TabsContent, TabsList, TabsRoot, TabsTrigger } from 'toolkit/chakra/tabs';
import AdaptiveTabs from 'toolkit/components/AdaptiveTabs/AdaptiveTabs';
import RoutedTabsSkeleton from 'toolkit/components/RoutedTabs/RoutedTabsSkeleton';
import { Section, Container, SectionHeader, SamplesStack, Sample, SectionSubHeader } from './parts';
......@@ -74,13 +73,6 @@ const TabsShowcase = () => {
/>
</Sample>
</SamplesStack>
<SectionSubHeader>Tabs skeleton</SectionSubHeader>
<SamplesStack>
<Sample>
<RoutedTabsSkeleton tabs={ tabs }/>
</Sample>
</SamplesStack>
</Section>
</Container>
);
......
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