Skip to content
Merged
4 changes: 2 additions & 2 deletions packages/shared/src/components/MainLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -196,14 +196,14 @@ function MainLayoutComponent({
/>
<main
className={classNames(
'flex flex-col',
'flex flex-col transition-[padding] duration-300 ease-in-out laptop:pt-16',
showSidebar && 'tablet:pl-16 laptop:pl-11',
className,
isAuthReady &&
!isScreenCentered &&
sidebarExpanded &&
'laptop:!pl-60',
isBannerAvailable && 'laptop:pt-8',
isBannerAvailable && 'laptop:pt-24',
)}
>
{isAuthReady && showSidebar && (
Expand Down
43 changes: 3 additions & 40 deletions packages/shared/src/components/feeds/FeedNav.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import classNames from 'classnames';
import type { ReactElement } from 'react';
import React, { useMemo, useState, useTransition } from 'react';
import React, { useMemo } from 'react';
import { useRouter } from 'next/router';
import { Tab, TabContainer } from '../tabs/TabContainer';
import { useActiveFeedNameContext } from '../../contexts';
import useActiveNav from '../../hooks/useActiveNav';
import { useEventListener, useFeeds, useViewSize, ViewSize } from '../../hooks';
import { useFeeds, useViewSize, ViewSize } from '../../hooks';
import usePersistentContext from '../../hooks/usePersistentContext';
import {
algorithmsList,
Expand All @@ -31,7 +31,6 @@ import { SharedFeedPage } from '../utilities';
import PlusMobileEntryBanner from '../banners/PlusMobileEntryBanner';
import { TargetType } from '../../lib/log';
import usePlusEntry from '../../hooks/usePlusEntry';
import { useAlertsContext } from '../../contexts/AlertContext';

enum FeedNavTab {
ForYou = 'For you',
Expand All @@ -52,17 +51,12 @@ const StickyNavIconWrapper = classed(
'sticky flex h-14 pt-1 -translate-y-16 items-center justify-end bg-gradient-to-r from-transparent via-background-default via-40% to-background-default pr-4',
);

const MIN_SCROLL_BEFORE_HIDING = 60;

function FeedNav(): ReactElement {
const router = useRouter();
const [, startTransition] = useTransition();
const [isHeaderVisible, setIsHeaderVisible] = useState(true);
const { feedName } = useActiveFeedNameContext();
const { sortingEnabled } = useSettingsContext();
const { isSortableFeed } = useFeedName({ feedName });
const { home, bookmarks } = useActiveNav(feedName);
const { alerts } = useAlertsContext();
const isMobile = useViewSize(ViewSize.MobileL);
const [selectedAlgo, setSelectedAlgo] = usePersistentContext(
DEFAULT_ALGORITHM_KEY,
Expand All @@ -82,8 +76,6 @@ function FeedNav(): ReactElement {
isMobile &&
((sortingEnabled && isSortableFeed) || feedName === SharedFeedPage.Custom);

const hasOpportunityAlert = !!alerts.opportunityId;

const urlToTab: Record<string, FeedNavTab> = useMemo(() => {
const customFeeds = sortedFeeds.reduce((acc, { node: feed }) => {
const isEditingFeed =
Expand Down Expand Up @@ -127,45 +119,16 @@ function FeedNav(): ReactElement {
isCustomDefaultFeed,
]);

const previousScrollY = React.useRef(0);

useEventListener(globalThis, 'scroll', () => {
// when scrolled down we should hide the header
// when scrolled up, we should bring it back
const { scrollY } = window;
const shouldHeaderBeVisible = scrollY < previousScrollY.current;

previousScrollY.current = scrollY;

if (shouldHeaderBeVisible === isHeaderVisible) {
return;
}

if (!shouldHeaderBeVisible && scrollY < MIN_SCROLL_BEFORE_HIDING) {
return;
}

startTransition(() => {
setIsHeaderVisible(shouldHeaderBeVisible);
});
});
const shouldRenderNav = home || (isMobile && bookmarks);
if (!shouldRenderNav || router?.pathname?.startsWith('/posts/[id]')) {
return null;
}

const headerTransitionClasses =
isMobile && hasOpportunityAlert
? '-translate-y-[7.5rem] duration-[800ms]'
: '-translate-y-26 duration-[800ms]';

return (
<div
className={classNames(
'sticky top-0 z-header w-full transition-transform tablet:pl-16',
'sticky top-0 z-header w-full bg-background-default tablet:pl-16',
scrollClassName,
isHeaderVisible && 'translate-y-0 duration-200',
!isHeaderVisible && headerTransitionClasses,
)}
>
{isMobile && <MobileFeedActions />}
Expand Down
2 changes: 1 addition & 1 deletion packages/shared/src/components/layout/MainLayoutHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ function MainLayoutHeader({
return (
<header
className={classNames(
'sticky top-0 z-header h-14 flex-row content-center items-center justify-center gap-3 border-b border-border-subtlest-tertiary px-4 py-3 tablet:px-8 laptop:left-0 laptop:h-16 laptop:w-full laptop:px-4',
'fixed top-0 z-header h-14 flex-row content-center items-center justify-center gap-3 border-b border-border-subtlest-tertiary bg-background-default px-4 py-3 tablet:px-8 laptop:left-0 laptop:h-16 laptop:w-full laptop:px-4',
isMobileProfile ? 'hidden laptop:flex' : 'flex',
hasBanner && 'laptop:top-8',
isSearchPage && 'mb-16 laptop:mb-0',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { ComponentProps, PropsWithChildren, ReactElement } from 'react';
import React from 'react';
import classNames from 'classnames';

export const pageMainClassNames = 'tablet:p-4 laptop:px-10 laptop:py-5';
export const pageMainClassNames = 'tablet:p-4 laptop:p-10';

export const PageWrapperLayout = ({
children,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
bottom: 0;
min-height: 100vh;
padding: 0;
overflow-y: scroll;
overflow-y: auto;
z-index: 100;
}
}
98 changes: 77 additions & 21 deletions packages/shared/src/components/sidebar/Section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import React, { useRef } from 'react';
import type { ItemInnerProps, SidebarMenuItem } from './common';
import { NavHeader, NavSection } from './common';
import { SidebarItem } from './SidebarItem';
import { Button, ButtonSize, ButtonVariant } from '../buttons/Button';
import { ArrowIcon } from '../icons';
import { ArrowIcon, PlusIcon } from '../icons';
import type { SettingsFlags } from '../../graphql/settings';
import { useSettingsContext } from '../../contexts/SettingsContext';
import { isNullOrUndefined } from '../../lib/func';
import useSidebarRendered from '../../hooks/useSidebarRendered';
import Link from '../utilities/Link';

export interface SectionCommonProps
extends Pick<ItemInnerProps, 'shouldShowLabel'> {
Expand All @@ -24,6 +24,8 @@ interface SectionProps extends SectionCommonProps {
items: SidebarMenuItem[];
isItemsButton: boolean;
isAlwaysOpenOnMobile?: boolean;
onAdd?: () => void;
addHref?: string;
}

export function Section({
Expand All @@ -36,6 +38,8 @@ export function Section({
className,
flag,
isAlwaysOpenOnMobile,
onAdd,
addHref,
}: SectionProps): ReactElement {
const { flags, updateFlag } = useSettingsContext();
const { sidebarRendered } = useSidebarRendered();
Expand All @@ -49,30 +53,81 @@ export function Section({
};

return (
<NavSection className={className}>
<NavSection className={classNames('mt-1', className)}>
{title && (
<NavHeader
className={classNames(
'hidden justify-between laptop:flex',
sidebarExpanded ? 'px-3 opacity-100' : 'px-0 opacity-0',
)}
>
{title}
<Button
variant={ButtonVariant.Tertiary}
onClick={toggleFlag}
size={ButtonSize.XSmall}
aria-label={`Toggle ${title}`}
icon={
<NavHeader className="relative hidden laptop:flex">
{/* Divider shown when sidebar is collapsed */}
<div
className={classNames(
'absolute inset-x-0 flex items-center justify-center px-2 transition-opacity duration-300',
sidebarExpanded ? 'opacity-0' : 'opacity-100',
)}
>
<hr className="w-full border-t border-border-subtlest-tertiary" />
</div>
{/* Header content shown when sidebar is expanded */}
<div
className={classNames(
'group/section flex min-h-9 w-full items-center justify-between px-2 py-1.5 transition-opacity duration-300',
sidebarExpanded ? 'opacity-100' : 'pointer-events-none opacity-0',
)}
>
<button
type="button"
onClick={toggleFlag}
aria-label={`Toggle ${title}`}
aria-expanded={!!isVisible.current}
aria-controls={flag ? `section-${flag}` : undefined}
className="flex items-center gap-1 rounded-6 px-1 py-0.5 transition-colors hover:bg-surface-hover hover:text-text-primary"
>
<span
className={classNames(
'text-text-quaternary typo-callout',
!sidebarExpanded && 'opacity-0',
)}
>
{title}
</span>
<ArrowIcon
className={isVisible.current ? 'rotate-360' : 'rotate-180'}
className={classNames(
'h-2.5 w-2.5 text-text-quaternary transition-transform duration-200',
isVisible.current ? 'rotate-180' : 'rotate-90',
)}
/>
}
/>
</button>
{addHref && (
<Link href={addHref}>
<a
aria-label={`Add to ${title}`}
className="flex h-6 w-6 items-center justify-center rounded-6 text-text-tertiary transition-all hover:bg-surface-hover hover:text-text-primary"
>
<PlusIcon className="h-4 w-4" />
</a>
</Link>
)}
{!addHref && onAdd && (
<button
type="button"
onClick={onAdd}
aria-label={`Add to ${title}`}
className="flex h-6 w-6 items-center justify-center rounded-6 text-text-tertiary transition-all hover:bg-surface-hover hover:text-text-primary"
>
<PlusIcon className="h-4 w-4" />
</button>
)}
</div>
</NavHeader>
)}
{(isVisible.current || shouldAlwaysBeVisible) &&
items.map((item) => (
<div
id={flag ? `section-${flag}` : undefined}
className={classNames(
'flex flex-col overflow-hidden transition-all duration-300',
isVisible.current || shouldAlwaysBeVisible
? 'max-h-[2000px] opacity-100'
: 'max-h-0 opacity-0',
)}
>
{items.map((item) => (
<SidebarItem
key={`${item.title}-${item.path}`}
item={item}
Expand All @@ -81,6 +136,7 @@ export function Section({
shouldShowLabel={shouldShowLabel}
/>
))}
</div>
</NavSection>
);
}
2 changes: 1 addition & 1 deletion packages/shared/src/components/sidebar/Sidebar.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ it('should render the sidebar as open by default', async () => {
renderComponent();
const section = await screen.findByText('Discover');
expect(section).toBeInTheDocument();
const sectionTwo = await screen.findByText('Network');
const sectionTwo = await screen.findByText('Squads');
expect(sectionTwo).toBeInTheDocument();
});

Expand Down
34 changes: 24 additions & 10 deletions packages/shared/src/components/sidebar/SidebarDesktop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,38 +57,52 @@ export const SidebarDesktop = ({
<SidebarScrollWrapper>
<Nav>
<SidebarMenuIcon />
<CreatePostButton
{/* Primary Action */}
<div
className={classNames(
'mb-4 !flex whitespace-nowrap',
sidebarExpanded ? 'mx-4' : 'mx-auto',
'mb-2 flex items-center justify-center transition-[padding] duration-300',
sidebarExpanded ? 'px-2' : 'px-1',
)}
compact={!sidebarExpanded}
size={sidebarExpanded ? ButtonSize.Small : ButtonSize.XSmall}
showIcon
/>
>
<CreatePostButton
className={classNames(
'!flex whitespace-nowrap',
sidebarExpanded ? 'w-full justify-start' : 'justify-center',
)}
compact={!sidebarExpanded}
size={ButtonSize.Small}
showIcon
/>
</div>

{/* Primary Navigation - Always visible */}
<MainSection
{...defaultRenderSectionProps}
onNavTabClick={onNavTabClick}
isItemsButton={isNavButtons}
/>

{/* User Content Sections */}
<CustomFeedSection
{...defaultRenderSectionProps}
onNavTabClick={onNavTabClick}
title="Custom feeds"
title="Feeds"
isItemsButton={false}
/>
<NetworkSection
{...defaultRenderSectionProps}
title="Network"
title="Squads"
isItemsButton={isNavButtons}
key="network-section"
/>
<BookmarkSection
{...defaultRenderSectionProps}
title="Bookmarks"
title="Saved"
isItemsButton={false}
key="bookmark-section"
/>

{/* Discovery Section */}
<DiscoverSection
{...defaultRenderSectionProps}
title="Discover"
Expand Down
13 changes: 12 additions & 1 deletion packages/shared/src/components/sidebar/SidebarItem.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { ReactElement } from 'react';
import React, { useContext } from 'react';
import classNames from 'classnames';
import { ClickableNavItem } from './ClickableNavItem';
import type { AuthTriggersType } from '../../lib/auth';
import type { SidebarMenuItem } from './common';
Expand Down Expand Up @@ -27,7 +28,17 @@ export const SidebarItem = ({
};

return (
<NavItem active={isActive(item)} ref={item.navItemRef} color={item.color}>
<NavItem
active={isActive(item)}
ref={item.navItemRef}
color={item.color}
disableDefaultBackground={item.disableDefaultBackground}
className={classNames(
'mx-1 rounded-10',
item.itemClassName,
!shouldShowLabel && 'justify-center',
)}
>
<ClickableNavItem
item={item}
showLogin={
Expand Down
Loading