diff --git a/packages/shared/src/components/MainFeedLayout.tsx b/packages/shared/src/components/MainFeedLayout.tsx index 6b863d8fc04..f1132426f89 100644 --- a/packages/shared/src/components/MainFeedLayout.tsx +++ b/packages/shared/src/components/MainFeedLayout.tsx @@ -17,7 +17,7 @@ import classNames from 'classnames'; import { useRouter } from 'next/router'; import type { FeedProps } from './Feed'; import Feed from './Feed'; -import { FeedPageLayoutMobile } from './utilities/common'; +import { FeedPageLayoutMobile, feedGutter } from './utilities/common'; import { ExploreChipsBar } from './feeds/ExploreChipsBar'; import { buildPersonalizedCategories } from './feeds/exploreCategories'; import { useFeeds } from '../hooks/feed/useFeeds'; @@ -671,7 +671,14 @@ export default function MainFeedLayout({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [sortingEnabled, selectedAlgo, loadedSettings, loadedAlgo]); - const disableTopPadding = isFinder || shouldUseListFeedLayout; + // Explore keeps the page's top padding in both layouts. It renders a + // breadcrumb and tab header above the feed, and zeroing the padding + // leaves that header jammed under the site header — while + // `shouldUseListFeedLayout` flips between first paint and mount + // (see `enableSsrSafeLayout`), so keying the spacing to it made the + // gap change size on navigation and settle differently on reload. + const disableTopPadding = + isFinder || (shouldUseListFeedLayout && !isAnyExplore); const onTabChange = useCallback( (clickedTab: ExploreTabs) => { if (clickedTab === ExploreTabs.BestOf && isExtension) { @@ -694,7 +701,13 @@ export default function MainFeedLayout({ ); } @@ -705,8 +718,10 @@ export default function MainFeedLayout({ setTab={onTabChange} showBreadcrumbs={false} className={{ - container: + container: classNames( 'sticky top-[4.5rem] z-header w-full border-b border-border-subtlest-tertiary bg-background-default', + feedGutter, + ), tabBarHeader: 'no-scrollbar overflow-x-auto', tabBarContainer: 'min-w-0 flex-1', }} @@ -835,9 +850,7 @@ export default function MainFeedLayout({ ) : undefined } - className={classNames( - shouldUseListFeedLayout && !isFinder && 'laptop:px-6', - )} + className={classNames(!isFinder && feedGutter)} /> ) )} diff --git a/packages/shared/src/components/utilities/common.tsx b/packages/shared/src/components/utilities/common.tsx index c10e822865d..04bccb732f2 100644 --- a/packages/shared/src/components/utilities/common.tsx +++ b/packages/shared/src/components/utilities/common.tsx @@ -4,8 +4,6 @@ import classNames from 'classnames'; import classed from '../../lib/classed'; import styles from './utilities.module.css'; import { ArrowIcon } from '../icons'; -import { pageMainClassNames } from '../layout/PageWrapperLayout'; -import { useLayoutVariant } from '../../hooks/layout/useLayoutVariant'; import { SourceMemberRole } from '../../graphql/sources'; import type { OrganizationMemberRole } from '../../features/organizations/types'; @@ -99,23 +97,37 @@ export const BaseFeedPage = classed( styles.feedPage, ); -// v2 (dual-sidebar layout) ships the feed inside the floating-card chrome, -// which provides its own outer inset. The legacy `pageMainClassNames` -// (`laptop:p-10`) adds another 40px on top, which reads as way too much -// side spacing inside the card. Drop that padding under v2; control keeps -// the existing behavior unchanged. +/** + * The feed's horizontal inset — the single source of it. + * + * It lives on FeedContainer rather than on a page container because + * the feed renders through two different ones depending on layout and + * route: FeedPage, which carries `pageMainClassNames`, and + * FeedPageLayoutList, which forces `!px-0`. FeedContainer is the only + * element common to both, so it is the only place an inset applies + * everywhere and can never stack with another. + * + * Chrome outside the container — the breadcrumbs and the tab strip — + * uses the same constant to line up with the cards. + */ +export const feedGutter = 'px-4 tablet:px-6 laptop:px-10'; + +// Vertical padding only. The horizontal inset moved to FeedContainer +// (see `feedGutter`) because this component is not in the tree on +// every feed route — FeedPageLayoutList is used instead on some — and +// an inset here would both miss those routes and stack with the one +// that covers them. +const feedPageVerticalPadding = 'tablet:py-4 laptop:py-10'; + export const FeedPage = ({ className, ...props -}: HTMLAttributes): ReactElement => { - const { isV2 } = useLayoutVariant(); - return ( - - ); -}; +}: HTMLAttributes): ReactElement => ( + +); export const FeedPageLayoutList = classed( BasePageContainer, pageContainerClassNames,