diff --git a/packages/extension-polkagate/src/fullscreen/homeFullScreen/partials/ProfileTabFullScreen.tsx b/packages/extension-polkagate/src/fullscreen/homeFullScreen/partials/ProfileTabFullScreen.tsx index 9469a5f92..df2d97c83 100644 --- a/packages/extension-polkagate/src/fullscreen/homeFullScreen/partials/ProfileTabFullScreen.tsx +++ b/packages/extension-polkagate/src/fullscreen/homeFullScreen/partials/ProfileTabFullScreen.tsx @@ -25,7 +25,7 @@ interface Props { index: number; } -export default function ProfileTabFullScreen ({ index, isHovered, orderedAccounts, selectedProfile, setSelectedProfile, text }: Props): React.ReactElement { +function ProfileTabFullScreen ({ index, isHovered, orderedAccounts, selectedProfile, setSelectedProfile, text }: Props): React.ReactElement { const { t } = useTranslation(); const theme = useTheme(); const { alerts, notify } = useAlerts(); @@ -114,6 +114,7 @@ export default function ProfileTabFullScreen ({ index, isHovered, orderedAccount cursor: 'pointer', flexShrink: 0, minWidth: '100px', + mr: '5px', mt: '2px', opacity: isDarkMode ? (visibleContent ? 1 : 0.3) : undefined, position: 'relative', @@ -151,3 +152,5 @@ export default function ProfileTabFullScreen ({ index, isHovered, orderedAccount ); } + +export default React.memo(ProfileTabFullScreen); diff --git a/packages/extension-polkagate/src/fullscreen/homeFullScreen/partials/ProfileTabsFullScreen.tsx b/packages/extension-polkagate/src/fullscreen/homeFullScreen/partials/ProfileTabsFullScreen.tsx index 566eca739..179820f34 100644 --- a/packages/extension-polkagate/src/fullscreen/homeFullScreen/partials/ProfileTabsFullScreen.tsx +++ b/packages/extension-polkagate/src/fullscreen/homeFullScreen/partials/ProfileTabsFullScreen.tsx @@ -19,15 +19,18 @@ interface Props { export const HIDDEN_PERCENT = '50%'; -export default function ProfileTabsFullScreen ({ orderedAccounts }: Props): React.ReactElement { +function ProfileTabsFullScreen ({ orderedAccounts }: Props): React.ReactElement { const { defaultProfiles, userDefinedProfiles } = useProfiles(); const [selectedProfile, setSelectedProfile] = useState(); const [isHovered, setIsHovered] = useState(); const [showLeftMore, setShowLeftMore] = useState(false); - const [showRightMore, setShowRightMore] = useState(true); + const [showRightMore, setShowRightMore] = useState(false); + const [isContentReady, setIsContentReady] = useState(false); + const scrollContainerRef = useRef(null); + const checkTimeoutRef = useRef>(); const profilesToShow = useMemo(() => { if (defaultProfiles.length === 0 && userDefinedProfiles.length === 0) { @@ -35,53 +38,117 @@ export default function ProfileTabsFullScreen ({ orderedAccounts }: Props): Reac } return defaultProfiles.concat(userDefinedProfiles); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [defaultProfiles.length, userDefinedProfiles.length]); + }, [defaultProfiles, userDefinedProfiles]); const onMouseEnter = useCallback(() => setIsHovered(true), []); const onMouseLeave = useCallback(() => setIsHovered(false), []); - const handleScroll = () => { - if (scrollContainerRef.current) { - const { clientWidth, scrollLeft, scrollWidth } = scrollContainerRef.current; + const handleScroll = useCallback(() => { + const container = scrollContainerRef.current; + + if (container && isContentReady) { + let { clientWidth, scrollLeft, scrollWidth } = container; + + scrollLeft = Math.round(scrollLeft); + // Check if content is actually scrollable const isScrollable = scrollWidth > clientWidth; - const tolerance = 10; - setShowLeftMore(scrollLeft > 0); - setShowRightMore(scrollLeft + clientWidth < scrollWidth - tolerance && isScrollable); + // Show left arrow if we've scrolled at least 1px + setShowLeftMore(scrollLeft > 1); + + // Show right arrow if there's more content to scroll to + // We add a small buffer (1px) to account for rounding errors + const remainingScroll = scrollWidth - (scrollLeft + clientWidth); + + setShowRightMore(isScrollable && remainingScroll > 1); } - }; + }, [isContentReady]); const handleWheel = useCallback((event: WheelEvent) => { if (scrollContainerRef.current) { event.preventDefault(); - scrollContainerRef.current.scrollLeft += (event.deltaY || event.deltaX); + + // Make the scroll amount more controlled + const scrollAmount = event.deltaY || event.deltaX; + const normalizedScroll = Math.sign(scrollAmount) * Math.min(Math.abs(scrollAmount), 50); + + scrollContainerRef.current.scrollLeft += normalizedScroll; handleScroll(); } - }, []); + }, [handleScroll]); + // Initial setup and content ready check useEffect(() => { - handleScroll(); // Set initial shadow states on mount - const ref = scrollContainerRef.current; + const container = scrollContainerRef.current; + + if (container) { + // Clear any existing timeout + if (checkTimeoutRef.current) { + clearTimeout(checkTimeoutRef.current); + } + + // Function to check if content is stable + const checkContentStability = () => { + const { clientWidth, scrollWidth } = container; + + if (scrollWidth && clientWidth) { + setIsContentReady(true); + handleScroll(); + } else { + // If content isn't ready, check again in a moment + checkTimeoutRef.current = setTimeout(checkContentStability, 50); + } + }; - if (ref) { - ref.addEventListener('scroll', handleScroll); - ref.addEventListener('wheel', handleWheel, { passive: false }); + // Start checking content stability + checkContentStability(); return () => { - ref.removeEventListener('scroll', handleScroll); - ref.removeEventListener('wheel', handleWheel); + if (checkTimeoutRef.current) { + clearTimeout(checkTimeoutRef.current); + } }; } return undefined; - }, [handleWheel, profilesToShow.length]); + }, [handleScroll, profilesToShow]); + + // Scroll and resize handlers + useEffect(() => { + const container = scrollContainerRef.current; + + if (container && isContentReady) { + // Add scroll event listener + const scrollListener = () => { + requestAnimationFrame(handleScroll); + }; + + container.addEventListener('scroll', scrollListener); + container.addEventListener('wheel', handleWheel, { passive: false }); + + // Check on resize + const resizeObserver = new ResizeObserver(() => { + requestAnimationFrame(handleScroll); + }); + + resizeObserver.observe(container); + + return () => { + container.removeEventListener('scroll', scrollListener); + container.removeEventListener('wheel', handleWheel); + resizeObserver.disconnect(); + }; + } + + return undefined; + }, [handleScroll, handleWheel, isContentReady]); return ( - {showLeftMore && } - } + - {showRightMore && } + {isHovered && isContentReady && showRightMore && } ); } + +export default React.memo(ProfileTabsFullScreen);