Skip to content
27 changes: 20 additions & 7 deletions packages/shared/src/components/MainFeedLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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) {
Expand All @@ -694,7 +701,13 @@ export default function MainFeedLayout({
<FeedExploreHeader
tab={tab}
setTab={onTabChange}
className={{ tabWrapper: 'my-4' }}
// The breadcrumbs used to start flush against the header
// with 0px above them, and then sat 16px off the tab strip —
// spacing that read as one loose block rather than a
// heading and its tabs. Give the group room above and pull
// the tabs up under the breadcrumbs they belong to; the
// 16px down to the cards is unchanged.
className={{ container: feedGutter, tabWrapper: 'mb-4 mt-2' }}
/>
);
}
Expand All @@ -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',
}}
Expand Down Expand Up @@ -835,9 +850,7 @@ export default function MainFeedLayout({
</div>
) : undefined
}
className={classNames(
shouldUseListFeedLayout && !isFinder && 'laptop:px-6',
)}
className={classNames(!isFinder && feedGutter)}
/>
)
)}
Expand Down
44 changes: 28 additions & 16 deletions packages/shared/src/components/utilities/common.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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<HTMLElement>): ReactElement => {
const { isV2 } = useLayoutVariant();
return (
<BaseFeedPage
{...props}
className={classNames(!isV2 && pageMainClassNames, className)}
/>
);
};
}: HTMLAttributes<HTMLElement>): ReactElement => (
<BaseFeedPage
{...props}
className={classNames(feedPageVerticalPadding, className)}
/>
);
export const FeedPageLayoutList = classed(
BasePageContainer,
pageContainerClassNames,
Expand Down
Loading