Skip to content

Commit

Permalink
feat: mobile feed layout & nav bar
Browse files Browse the repository at this point in the history
- adding a `FeedNav` at the top of the feed layout
- updating footer nav items
- moving reading streaks to the top of the `for you` feed

TODO:
- go through all pages and make sure the layout is not broken
- validate the previous layout still looks the same if the feature flag is disabled
  • Loading branch information
DragosIorgulescu committed Mar 25, 2024
1 parent 7a30463 commit ce3f16b
Show file tree
Hide file tree
Showing 9 changed files with 238 additions and 112 deletions.
9 changes: 8 additions & 1 deletion packages/shared/src/components/MainFeedLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ import CommentFeed from './CommentFeed';
import { COMMENT_FEED_QUERY } from '../graphql/comments';
import { ProfileEmptyScreen } from './profile/ProfileEmptyScreen';
import { Origin } from '../lib/analytics';
import FeedNav from './feeds/FeedNav';
import { useMobileUxExperiment } from '../hooks/useMobileUxExperiment';

const SearchEmptyScreen = dynamic(
() =>
Expand Down Expand Up @@ -140,6 +142,7 @@ export default function MainFeedLayout({
const hasCommentFeed = useFeature(feature.commentFeed);
const { isUpvoted, isSortableFeed } = useFeedName({ feedName });
const { shouldUseMobileFeedLayout } = useFeedLayout();
const { useNewMobileLayout } = useMobileUxExperiment();
const shouldUseCommentFeedLayout =
hasCommentFeed && feedName === SharedFeedPage.Discussed;
let query: { query: string; variables?: Record<string, unknown> };
Expand Down Expand Up @@ -270,7 +273,11 @@ export default function MainFeedLayout({

return (
<FeedPageComponent
className={classNames('relative', disableTopPadding && '!pt-0')}
className={classNames(
'relative',
disableTopPadding && '!pt-0',
useNewMobileLayout && 'tablet:pl-22',
)}
>
{isSearchOn && search}
{shouldUseCommentFeedLayout ? (
Expand Down
3 changes: 3 additions & 0 deletions packages/shared/src/components/MainLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { ReputationPrivilegesModalTrigger } from './modals';
import { MarketingCtaVariant } from './cards/MarketingCta/common';
import { useLazyModal } from '../hooks/useLazyModal';
import { LazyModal } from './modals/common/types';
import { useMobileUxExperiment } from '../hooks/useMobileUxExperiment';

export interface MainLayoutProps
extends Omit<MainLayoutHeaderProps, 'onMobileSidebarToggle'>,
Expand Down Expand Up @@ -91,6 +92,7 @@ function MainLayoutComponent({

const isLaptopXL = useViewSize(ViewSize.LaptopXL);
const { shouldUseMobileFeedLayout } = useFeedLayout();
const { useNewMobileLayout } = useMobileUxExperiment();

const { isNotificationsReady, unreadCount } = useNotificationContext();
useAuthErrors();
Expand Down Expand Up @@ -224,6 +226,7 @@ function MainLayoutComponent({
!isScreenCentered && sidebarExpanded
? 'laptop:pl-60'
: 'laptop:pl-11',

isBannerAvailable && 'laptop:pt-8',
)}
>
Expand Down
21 changes: 16 additions & 5 deletions packages/shared/src/components/feeds/FeedContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { useFeedLayout, ToastSubject, useToastNotification } from '../../hooks';
import ConditionalWrapper from '../ConditionalWrapper';
import { SharedFeedPage } from '../utilities';
import { useActiveFeedNameContext } from '../../contexts';
import { useMobileUxExperiment } from '../../hooks/useMobileUxExperiment';

export interface FeedContainerProps {
children: ReactNode;
Expand Down Expand Up @@ -107,6 +108,7 @@ export const FeedContainer = ({
loadedSettings,
} = useContext(SettingsContext);
const { shouldUseMobileFeedLayout } = useFeedLayout();
const { useNewMobileLayout } = useMobileUxExperiment();
const { feedName } = useActiveFeedNameContext();
const router = useRouter();
const numCards = currentSettings.numCards[spaciness ?? 'eco'];
Expand Down Expand Up @@ -166,7 +168,7 @@ export const FeedContainer = ({
{isSearch && !shouldUseMobileFeedLayout && (
<span className="flex flex-1 flex-row items-center">
{!!actionButtons && (
<span className="mr-auto flex flex-row gap-3 border-theme-divider-tertiary pr-3">
<span className="mr-auto flex w-full flex-row gap-3 border-theme-divider-tertiary pr-3">
{actionButtons}
</span>
)}
Expand All @@ -184,10 +186,19 @@ export const FeedContainer = ({
)}
>
<span className="flex w-full flex-row items-center justify-between px-6 py-4">
<strong className="typo-title3">
{feedNameToHeading[feedName] ?? ''}
</strong>
<span className="flex flex-row gap-3">{actionButtons}</span>
{!useNewMobileLayout && (
<strong className="typo-title3">
{feedNameToHeading[feedName] ?? ''}
</strong>
)}
<span
className={classNames(
'flex flex-row gap-3',
useNewMobileLayout && 'w-full',
)}
>
{actionButtons}
</span>
</span>
{child}
</div>
Expand Down
52 changes: 52 additions & 0 deletions packages/shared/src/components/feeds/FeedNav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import classNames from 'classnames';
import React, { ReactElement, useMemo } from 'react';
import { useRouter } from 'next/router';
import { Tab, TabContainer } from '../tabs/TabContainer';
import NotificationsBell from '../notifications/NotificationsBell';

enum FeedNavTab {
ForYou = 'For you',
Popular = 'Popular',
Bookmarks = 'Bookmarks',
History = 'History',
}

function FeedNav(): ReactElement {
const router = useRouter();
const shouldRenderNav = useMemo(() => {
return /(\/|\/popular|\/bookmarks|\/history|\/notifications)$/.test(
router?.pathname,
);
}, [router?.pathname]);

if (!shouldRenderNav) {
return null;
}

return (
<div
className={classNames(
'sticky top-0 z-header h-14 w-full bg-background-default tablet:pl-16',
)}
>
<TabContainer
shouldMountInactive
className={{
header: 'no-scrollbar overflow-x-auto px-1',
}}
tabListProps={{ className: { indicator: '!w-6' } }}
>
<Tab label={FeedNavTab.ForYou} url="/" />
<Tab label={FeedNavTab.Popular} url="/popular" />
<Tab label={FeedNavTab.Bookmarks} url="/bookmarks" />
<Tab label={FeedNavTab.History} url="/history" />
</TabContainer>

<div className="fixed right-0 top-0 my-1 flex h-12 w-36 items-center justify-end bg-gradient-to-r from-transparent via-background-default via-60% to-background-default">
<NotificationsBell />
</div>
</div>
);
}

export default FeedNav;
104 changes: 64 additions & 40 deletions packages/shared/src/components/filters/MyFeedHeading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ import { feature } from '../../lib/featureManagement';
import { useFeature } from '../GrowthBookProvider';
import { setShouldRefreshFeed } from '../../lib/refreshFeed';
import { SharedFeedPage } from '../utilities';
import { useMobileUxExperiment } from '../../hooks/useMobileUxExperiment';
import ConditionalWrapper from '../ConditionalWrapper';
import { useReadingStreak } from '../../hooks/streaks';
import { ReadingStreakButton } from '../streak/ReadingStreakButton';

export const filterAlertMessage = 'Edit your personal feed preferences here';

Expand All @@ -45,6 +49,8 @@ function MyFeedHeading({
const { shouldUseMobileFeedLayout } = useFeedLayout();
const queryClient = useQueryClient();
const forceRefresh = useFeature(feature.forceRefresh);
const { useNewMobileLayout } = useMobileUxExperiment();
const { streak, isEnabled: isStreaksEnabled, isLoading } = useReadingStreak();

const onClick = () => {
trackEvent({ event_name: AnalyticsEvent.ManageTags });
Expand Down Expand Up @@ -103,46 +109,64 @@ function MyFeedHeading({
});
const isRefreshing = feedQueryState?.status === 'success' && isFetchingFeed;

return (
<>
{forceRefresh && (
<Button
size={
shouldUseMobileFeedLayout ? ButtonSize.Small : ButtonSize.Medium
}
variant={ButtonVariant.Float}
className="mr-auto"
onClick={onRefresh}
icon={<RefreshIcon />}
iconPosition={
shouldUseMobileFeedLayout ? ButtonIconPosition.Right : undefined
}
loading={isRefreshing}
>
{!isMobile ? 'Refresh feed' : null}
</Button>
)}
<AlertPointer {...alertProps}>
<Button
size={
shouldUseMobileFeedLayout ? ButtonSize.Small : ButtonSize.Medium
}
variant={ButtonVariant.Float}
className={classNames(
'mr-auto',
shouldHighlightFeedSettings && 'highlight-pulse',
)}
onClick={onClick}
icon={<FilterIcon />}
iconPosition={
shouldUseMobileFeedLayout ? ButtonIconPosition.Right : undefined
}
>
{!isMobile ? 'Feed settings' : null}
</Button>
</AlertPointer>
</>
);
const RenderFeedActions = () => {
return (
<>
{forceRefresh && (
<Button
size={
shouldUseMobileFeedLayout ? ButtonSize.Small : ButtonSize.Medium
}
variant={ButtonVariant.Float}
className="mr-auto"
onClick={onRefresh}
icon={<RefreshIcon />}
iconPosition={
shouldUseMobileFeedLayout ? ButtonIconPosition.Right : undefined
}
loading={isRefreshing}
>
{!isMobile && !useNewMobileLayout ? 'Refresh feed' : null}
</Button>
)}
<AlertPointer {...alertProps}>
<Button
size={
shouldUseMobileFeedLayout ? ButtonSize.Small : ButtonSize.Medium
}
variant={ButtonVariant.Float}
className={classNames(
'mr-auto',
shouldHighlightFeedSettings && 'highlight-pulse',
)}
onClick={onClick}
icon={<FilterIcon />}
iconPosition={
shouldUseMobileFeedLayout ? ButtonIconPosition.Right : undefined
}
>
{!isMobile && !useNewMobileLayout ? 'Feed settings' : null}
</Button>
</AlertPointer>
</>
);
};

if (useNewMobileLayout) {
return (
<div className="flex w-full justify-between">
{isStreaksEnabled && (
<ReadingStreakButton streak={streak} isLoading={isLoading} />
)}

<div>
<RenderFeedActions />
</div>
</div>
);
}

return <RenderFeedActions />;
}

export default MyFeedHeading;
Loading

0 comments on commit ce3f16b

Please sign in to comment.