diff --git a/ui/shared/Tabs/AdaptiveTabsList.tsx b/ui/shared/Tabs/AdaptiveTabsList.tsx
new file mode 100644
index 0000000000..5f5ff81f1f
--- /dev/null
+++ b/ui/shared/Tabs/AdaptiveTabsList.tsx
@@ -0,0 +1,130 @@
+import type { StyleProps, ThemingProps } from '@chakra-ui/react';
+import { Box, Tab, TabList, useColorModeValue } 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 TabCounter from './TabCounter';
+import TabsMenu from './TabsMenu';
+import type { Props as TabsProps } from './TabsWithScroll';
+import useAdaptiveTabs from './useAdaptiveTabs';
+import useScrollToActiveTab from './useScrollToActiveTab';
+import { menuButton } from './utils';
+
+const hiddenItemStyles: StyleProps = {
+ position: 'absolute',
+ top: '-9999px',
+ left: '-9999px',
+ visibility: 'hidden',
+};
+
+interface Props extends TabsProps {
+ activeTabIndex: number;
+ onItemClick: (index: number) => void;
+ themeProps: ThemingProps<'Tabs'>;
+}
+
+const AdaptiveTabsList = (props: Props) => {
+
+ const scrollDirection = useScrollDirection();
+ const listBgColor = useColorModeValue('white', 'black');
+ const isMobile = useIsMobile();
+
+ const tabsList = React.useMemo(() => {
+ return [ ...props.tabs, menuButton ];
+ }, [ props.tabs ]);
+
+ const { tabsCut, tabsRefs, listRef, rightSlotRef } = useAdaptiveTabs(tabsList, isMobile);
+ const isSticky = useIsSticky(listRef, 5, props.stickyEnabled);
+ useScrollToActiveTab({ activeTabIndex: props.activeTabIndex, listRef, tabsRefs, isMobile });
+
+ return (
+
+ { tabsList.map((tab, index) => {
+ if (!tab.id) {
+ return (
+ = 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 (
+
+ { typeof tab.title === 'function' ? tab.title() : tab.title }
+
+
+ );
+ }) }
+ {
+ props.rightSlot && tabsCut > 0 ?
+ { props.rightSlot } :
+ null
+ }
+
+ );
+};
+
+export default React.memo(AdaptiveTabsList);
diff --git a/ui/shared/Tabs/TabCounter.tsx b/ui/shared/Tabs/TabCounter.tsx
index 139e98a4b3..691af5ba70 100644
--- a/ui/shared/Tabs/TabCounter.tsx
+++ b/ui/shared/Tabs/TabCounter.tsx
@@ -1,15 +1,15 @@
-import type { SystemStyleObject } from '@chakra-ui/react';
-import { Text, useColorModeValue } from '@chakra-ui/react';
+import { chakra, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
+import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps';
+
const COUNTER_OVERLOAD = 50;
type Props = {
count?: number | null;
- parentClassName: string;
}
-const TabCounter = ({ count, parentClassName }: Props) => {
+const TabCounter = ({ count }: Props) => {
const zeroCountColor = useColorModeValue('blackAlpha.400', 'whiteAlpha.400');
@@ -17,21 +17,14 @@ const TabCounter = ({ count, parentClassName }: Props) => {
return null;
}
- const sx: SystemStyleObject = {
- [`.${ parentClassName }:hover &`]: { color: 'inherit' },
- };
-
return (
- 0 ? 'text_secondary' : zeroCountColor }
ml={ 1 }
- sx={ sx }
- transitionProperty="color"
- transitionDuration="normal"
- transitionTimingFunction="ease"
+ { ...getDefaultTransitionProps() }
>
{ count > COUNTER_OVERLOAD ? `${ COUNTER_OVERLOAD }+` : count }
-
+
);
};
diff --git a/ui/shared/Tabs/TabsMenu.tsx b/ui/shared/Tabs/TabsMenu.tsx
index 57b4b5bcd9..36fd2fd81a 100644
--- a/ui/shared/Tabs/TabsMenu.tsx
+++ b/ui/shared/Tabs/TabsMenu.tsx
@@ -15,8 +15,6 @@ import type { MenuButton, TabItem } from './types';
import TabCounter from './TabCounter';
import { menuButton } from './utils';
-const BUTTON_CLASSNAME = 'button-item';
-
interface Props {
tabs: Array;
activeTab?: TabItem;
@@ -62,10 +60,14 @@ const TabsMenu = ({ tabs, tabsCut, isActive, styles, onItemClick, buttonRef, act
isActive={ activeTab ? activeTab.id === tab.id : false }
justifyContent="left"
data-index={ index }
- className={ BUTTON_CLASSNAME }
+ sx={{
+ '&:hover span': {
+ color: 'inherit',
+ },
+ }}
>
{ typeof tab.title === 'function' ? tab.title() : tab.title }
-
+
)) }
diff --git a/ui/shared/Tabs/TabsWithScroll.tsx b/ui/shared/Tabs/TabsWithScroll.tsx
index 9c19ffa598..a4a5fc4bb1 100644
--- a/ui/shared/Tabs/TabsWithScroll.tsx
+++ b/ui/shared/Tabs/TabsWithScroll.tsx
@@ -1,39 +1,20 @@
import type { LazyMode } from '@chakra-ui/lazy-utils';
import type { ChakraProps, ThemingProps } from '@chakra-ui/react';
import {
- Tab,
Tabs,
- TabList,
TabPanel,
TabPanels,
- Box,
- useColorModeValue,
chakra,
} from '@chakra-ui/react';
-import type { StyleProps } from '@chakra-ui/styled-system';
+import _debounce from 'lodash/debounce';
import React, { useEffect, useRef, useState } from 'react';
import type { TabItem } from './types';
-import { useScrollDirection } from 'lib/contexts/scrollDirection';
-import useIsMobile from 'lib/hooks/useIsMobile';
-import useIsSticky from 'lib/hooks/useIsSticky';
-
-import TabCounter from './TabCounter';
-import TabsMenu from './TabsMenu';
-import useAdaptiveTabs from './useAdaptiveTabs';
+import AdaptiveTabsList from './AdaptiveTabsList';
import { menuButton } from './utils';
-const TAB_CLASSNAME = 'tab-item';
-
-const hiddenItemStyles: StyleProps = {
- position: 'absolute',
- top: '-9999px',
- left: '-9999px',
- visibility: 'hidden',
-};
-
-interface Props extends ThemingProps<'Tabs'> {
+export interface Props extends ThemingProps<'Tabs'> {
tabs: Array;
lazyBehavior?: LazyMode;
tabListProps?: ChakraProps | (({ isSticky, activeTabIndex }: { isSticky: boolean; activeTabIndex: number }) => ChakraProps);
@@ -57,19 +38,15 @@ const TabsWithScroll = ({
className,
...themeProps
}: Props) => {
- const scrollDirection = useScrollDirection();
const [ activeTabIndex, setActiveTabIndex ] = useState(defaultTabIndex || 0);
- const isMobile = useIsMobile();
+ const [ screenWidth, setScreenWidth ] = React.useState(0);
+
const tabsRef = useRef(null);
const tabsList = React.useMemo(() => {
return [ ...tabs, menuButton ];
}, [ tabs ]);
- const { tabsCut, tabsRefs, listRef, rightSlotRef } = useAdaptiveTabs(tabsList, isMobile);
- const isSticky = useIsSticky(listRef, 5, stickyEnabled);
- const listBgColor = useColorModeValue('white', 'black');
-
const handleTabChange = React.useCallback((index: number) => {
onTabChange ? onTabChange(index) : setActiveTabIndex(index);
}, [ onTabChange ]);
@@ -80,23 +57,17 @@ const TabsWithScroll = ({
}
}, [ defaultTabIndex ]);
- useEffect(() => {
- if (activeTabIndex < tabs.length && isMobile) {
- window.setTimeout(() => {
- const activeTabRef = tabsRefs[activeTabIndex];
- if (activeTabRef.current && listRef.current) {
- const activeTabRect = activeTabRef.current.getBoundingClientRect();
- listRef.current.scrollTo({
- left: activeTabRect.left + listRef.current.scrollLeft - 16,
- 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 updated
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [ activeTabIndex, isMobile ]);
+ 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 { tabs[0].component }
;
@@ -115,77 +86,20 @@ const TabsWithScroll = ({
ref={ tabsRef }
lazyBehavior={ lazyBehavior }
>
-
- { tabsList.map((tab, index) => {
- if (!tab.id) {
- return (
- = tabsCut }
- styles={ tabsCut < 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={ handleTabChange }
- buttonRef={ tabsRefs[index] }
- size={ themeProps.size || 'md' }
- />
- );
- }
-
- return (
-
- { typeof tab.title === 'function' ? tab.title() : tab.title }
-
-
- );
- }) }
- { rightSlot && tabsCut > 0 ? { rightSlot } : null }
-
+
{ tabsList.map((tab) => { tab.component }) }
diff --git a/ui/shared/Tabs/useAdaptiveTabs.tsx b/ui/shared/Tabs/useAdaptiveTabs.tsx
index f9d8ca7fb9..4b4deea826 100644
--- a/ui/shared/Tabs/useAdaptiveTabs.tsx
+++ b/ui/shared/Tabs/useAdaptiveTabs.tsx
@@ -1,4 +1,3 @@
-import _debounce from 'lodash/debounce';
import React from 'react';
import type { MenuButton, RoutedTab } from './types';
@@ -28,7 +27,7 @@ export default function useAdaptiveTabs(tabs: Array, dis
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 rest of the loop
+ // we skip now till the end of the loop
return result;
}
@@ -62,22 +61,6 @@ export default function useAdaptiveTabs(tabs: Array, dis
}
}, [ calculateCut, disabled, tabsRefs ]);
- React.useEffect(() => {
- if (tabsRefs.length === 0 || disabled) {
- return;
- }
-
- const resizeHandler = _debounce(() => {
- setTabsCut(calculateCut());
- }, 100);
- const resizeObserver = new ResizeObserver(resizeHandler);
-
- resizeObserver.observe(document.body);
- return function cleanup() {
- resizeObserver.unobserve(document.body);
- };
- }, [ calculateCut, disabled, tabsRefs.length ]);
-
return React.useMemo(() => {
return {
tabsCut,
diff --git a/ui/shared/Tabs/useScrollToActiveTab.tsx b/ui/shared/Tabs/useScrollToActiveTab.tsx
new file mode 100644
index 0000000000..39cd0e491b
--- /dev/null
+++ b/ui/shared/Tabs/useScrollToActiveTab.tsx
@@ -0,0 +1,31 @@
+import React from 'react';
+
+interface Props {
+ activeTabIndex: number;
+ tabsRefs: Array>;
+ listRef: React.RefObject;
+ isMobile?: boolean;
+}
+
+export default function useScrollToActiveTab({ activeTabIndex, tabsRefs, listRef, isMobile }: Props) {
+ React.useEffect(() => {
+ if (activeTabIndex < tabsRefs.length && isMobile) {
+ window.setTimeout(() => {
+ const activeTabRef = tabsRefs[activeTabIndex];
+
+ if (activeTabRef.current && listRef.current) {
+ const activeTabRect = activeTabRef.current.getBoundingClientRect();
+
+ listRef.current.scrollTo({
+ left: activeTabRect.left + listRef.current.scrollLeft - 16,
+ 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 ]);
+}
diff --git a/ui/shared/layout/__screenshots__/Layout.pw.tsx_default_base-view-mobile-1.png b/ui/shared/layout/__screenshots__/Layout.pw.tsx_default_base-view-mobile-1.png
index b138a5892e..0f7f7e0759 100644
Binary files a/ui/shared/layout/__screenshots__/Layout.pw.tsx_default_base-view-mobile-1.png and b/ui/shared/layout/__screenshots__/Layout.pw.tsx_default_base-view-mobile-1.png differ
diff --git a/ui/shared/layout/__screenshots__/LayoutError.pw.tsx_default_base-view-mobile-1.png b/ui/shared/layout/__screenshots__/LayoutError.pw.tsx_default_base-view-mobile-1.png
index ce15bd1e57..1a7a26cf30 100644
Binary files a/ui/shared/layout/__screenshots__/LayoutError.pw.tsx_default_base-view-mobile-1.png and b/ui/shared/layout/__screenshots__/LayoutError.pw.tsx_default_base-view-mobile-1.png differ
diff --git a/ui/shared/layout/__screenshots__/LayoutHome.pw.tsx_default_base-view-mobile-1.png b/ui/shared/layout/__screenshots__/LayoutHome.pw.tsx_default_base-view-mobile-1.png
index 3cb2e73e65..39a96a84a9 100644
Binary files a/ui/shared/layout/__screenshots__/LayoutHome.pw.tsx_default_base-view-mobile-1.png and b/ui/shared/layout/__screenshots__/LayoutHome.pw.tsx_default_base-view-mobile-1.png differ
diff --git a/ui/snippets/footer/Footer.tsx b/ui/snippets/footer/Footer.tsx
index 8272d93d10..e6982c9c35 100644
--- a/ui/snippets/footer/Footer.tsx
+++ b/ui/snippets/footer/Footer.tsx
@@ -107,7 +107,8 @@ const Footer = () => {
return (
{
{ config.UI.footer.links && Blockscout }
diff --git a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_with-custom-links-2-cols-base-view-dark-mode-mobile-1.png b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_with-custom-links-2-cols-base-view-dark-mode-mobile-1.png
index 37b01d108f..ff1d4ea7ac 100644
Binary files a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_with-custom-links-2-cols-base-view-dark-mode-mobile-1.png and b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_with-custom-links-2-cols-base-view-dark-mode-mobile-1.png differ
diff --git a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_with-custom-links-4-cols-mobile-dark-mode-1.png b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_with-custom-links-4-cols-mobile-dark-mode-1.png
index a00c14b91e..53b597a4f1 100644
Binary files a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_with-custom-links-4-cols-mobile-dark-mode-1.png and b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_with-custom-links-4-cols-mobile-dark-mode-1.png differ
diff --git a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_without-custom-links-base-view-dark-mode-mobile-1.png b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_without-custom-links-base-view-dark-mode-mobile-1.png
index f4e3281c2f..850931017d 100644
Binary files a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_without-custom-links-base-view-dark-mode-mobile-1.png and b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_without-custom-links-base-view-dark-mode-mobile-1.png differ
diff --git a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_without-custom-links-with-indexing-alert-dark-mode-mobile-1.png b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_without-custom-links-with-indexing-alert-dark-mode-mobile-1.png
index 503426ee65..4e4f626c76 100644
Binary files a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_without-custom-links-with-indexing-alert-dark-mode-mobile-1.png and b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_without-custom-links-with-indexing-alert-dark-mode-mobile-1.png differ
diff --git a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_with-custom-links-2-cols-base-view-dark-mode-mobile-1.png b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_with-custom-links-2-cols-base-view-dark-mode-mobile-1.png
index 35a8c563eb..dcb30d18df 100644
Binary files a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_with-custom-links-2-cols-base-view-dark-mode-mobile-1.png and b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_with-custom-links-2-cols-base-view-dark-mode-mobile-1.png differ
diff --git a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_with-custom-links-4-cols-mobile-dark-mode-1.png b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_with-custom-links-4-cols-mobile-dark-mode-1.png
index 605dad1ea1..1e2bab604a 100644
Binary files a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_with-custom-links-4-cols-mobile-dark-mode-1.png and b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_with-custom-links-4-cols-mobile-dark-mode-1.png differ
diff --git a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_with-custom-links-4-cols-screen-xl-1.png b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_with-custom-links-4-cols-screen-xl-1.png
index d156bb1403..04bef25558 100644
Binary files a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_with-custom-links-4-cols-screen-xl-1.png and b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_with-custom-links-4-cols-screen-xl-1.png differ
diff --git a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_without-custom-links-base-view-dark-mode-mobile-1.png b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_without-custom-links-base-view-dark-mode-mobile-1.png
index 301fa2fadc..0fa981b4de 100644
Binary files a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_without-custom-links-base-view-dark-mode-mobile-1.png and b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_without-custom-links-base-view-dark-mode-mobile-1.png differ
diff --git a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_without-custom-links-with-indexing-alert-dark-mode-mobile-1.png b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_without-custom-links-with-indexing-alert-dark-mode-mobile-1.png
index 4be98fd4f8..6254c00c48 100644
Binary files a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_without-custom-links-with-indexing-alert-dark-mode-mobile-1.png and b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_without-custom-links-with-indexing-alert-dark-mode-mobile-1.png differ