Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
7757be9
feat: add layout_v2 feature flag and useLayoutVariant hook
rebelchris May 18, 2026
a777e31
refactor: simplify featureLayoutV2 to boolean flag
rebelchris May 18, 2026
0cf1f0e
feat: add v2 dual-sidebar layout behind featureLayoutV2 flag
rebelchris May 18, 2026
3fd9765
feat: pixel-match dual-sidebar v2 layout against designer mockup
rebelchris May 19, 2026
3187bc8
fix(v2): match designer panel — MagicIcon for For You, drop Game Center
rebelchris May 19, 2026
b15c4d4
fix(v2): widen sidebar item horizontal margin to match designer
rebelchris May 19, 2026
508a530
fix(v2): bordered page-header strip and grid inset for feed content
rebelchris May 19, 2026
bb6cf3a
fix(v2): compact ghost buttons + tighter feed/header insets
rebelchris May 19, 2026
c017a29
fix(v2): drop FeedPage 40px padding, restore grid inset, inline compa…
rebelchris May 19, 2026
d1dcb34
fix(v2): tighter header strip + smaller icons, snug card-to-strip gap
rebelchris May 19, 2026
4255f12
refactor(v2): align feed-container layout with designer's list-frame …
rebelchris May 19, 2026
19fefb6
fix(v2): drop feedHeading title in PageHeader to match mock
rebelchris May 19, 2026
8b6c09c
fix(v2): remove duplicate action strip — restyle isSearch branch instead
rebelchris May 19, 2026
8937a0a
fix(v2): tighter text buttons and snug grid top inset
rebelchris May 19, 2026
2f7f4f4
refactor(v2): drop button-style overrides, use Small + Tertiary natively
rebelchris May 19, 2026
7ed6d19
feat(v2): squads directory page-header strip
rebelchris May 19, 2026
ff70c54
fix(v2): NotificationsBell rail variant — match other rail icons
rebelchris May 19, 2026
470ad7e
feat(v2): port designer page-header pattern to remaining pages
rebelchris May 19, 2026
cdf36d9
fix(v2): full-width PageHeader on max-width pages + minimal game-cent…
rebelchris May 19, 2026
05cc552
feat(v2): port PageHeader pattern to bookmarks + settings AccountPage…
rebelchris May 19, 2026
c5b9aa3
fix(v2): unify bookmarks header — title + search + actions in one strip
rebelchris May 19, 2026
4ae42f8
fix(v2): show sidebar on /settings/* so the v2 rail appears
rebelchris May 19, 2026
4e20920
fix(v2): hide ProfileSettingsMenuDesktop — the v2 sidebar panel is th…
rebelchris May 19, 2026
cc79cfb
fix(v2): portal settings PageHeader to span full floating-card width
rebelchris May 19, 2026
831394b
feat(v2): settings/notifications — port FindSquad-style tabs into Pag…
rebelchris May 19, 2026
1b7f6e4
fix: some more changes to old pages
rebelchris May 19, 2026
f1a26b9
refactor(v2): isV2 is now laptop-gated — drop redundant isLaptop checks
rebelchris May 19, 2026
bad8b6a
feat(v2): route-loading progress bar at top of floating card
rebelchris May 19, 2026
87cc321
fix(v2): prettier + Button typecheck — unblock CI build
rebelchris May 19, 2026
c6dbbf3
chore: ping CI
rebelchris May 20, 2026
a510be5
Merge remote-tracking branch 'origin/main' into feat-layout-v2-flag
rebelchris May 20, 2026
8061937
feat(v2): render full Quest panel in Game Center sidebar
rebelchris May 20, 2026
a0990e8
Merge remote-tracking branch 'origin/main' into feat-layout-v2-flag
rebelchris May 21, 2026
8cd169d
fix: add top banner
rebelchris May 21, 2026
15e1ea1
fix: feedback and post page
rebelchris May 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions packages/extension/src/newtab/ExtensionTopBanners.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { ReactElement } from 'react';
import React from 'react';
import { TopHero } from '@dailydotdev/shared/src/components/marketing/banners/HeroBottomBanner';
import { useReadingReminderHero } from '@dailydotdev/shared/src/hooks/notifications/useReadingReminderHero';

export const ExtensionTopBanners = (): ReactElement | null => {
// The extension's top hero row is the only place this card appears
// on the new tab, so we evaluate the reminder regardless of viewport
// (the webapp-only `requireMobile` heuristic would hide it on desktop
// new tabs, which is where the extension lives).
const reminder = useReadingReminderHero({ requireMobile: false });

if (!reminder.shouldShow) {
return null;
}

return (
<div className="mx-4 mb-3 grid grid-cols-1 gap-3 laptop:mx-0">
<TopHero
title={reminder.title}
subtitle={reminder.subtitle}
onCtaClick={() => {
reminder.onEnable();
}}
onClose={() => {
reminder.onDismiss();
}}
/>
</div>
);
};
2 changes: 2 additions & 0 deletions packages/extension/src/newtab/MainFeedPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { isFocusActiveAt } from '@dailydotdev/shared/src/features/customizeNewTa
import { normaliseNewTabMode } from '@dailydotdev/shared/src/features/customizeNewTab/lib/newTabMode';
import { DndBanner } from '@dailydotdev/shared/src/components/DndBanner';
import ShortcutLinks from './ShortcutLinks/ShortcutLinks';
import { ExtensionTopBanners } from './ExtensionTopBanners';
import { CompanionPopupButton } from '../companion/CompanionPopupButton';
import { useCompanionSettings } from '../companion/useCompanionSettings';
import { getDefaultLink } from './dnd';
Expand Down Expand Up @@ -198,6 +199,7 @@ const MainFeedPageInner = ({
additionalButtons={
!loadingUser && !optOutCompanion && <CompanionPopupButton />
}
topBanner={<ExtensionTopBanners />}
>
<FeedLayoutProvider>
<MainFeedLayout
Expand Down
161 changes: 104 additions & 57 deletions packages/shared/src/components/BookmarkFeedLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { PropsWithChildren, ReactElement, ReactNode } from 'react';
import type { ReactElement, ReactNode } from 'react';
import React, {
useCallback,
useContext,
Expand All @@ -21,11 +21,12 @@ import SearchEmptyScreen from './SearchEmptyScreen';
import type { FeedProps } from './Feed';
import Feed from './Feed';
import BookmarkEmptyScreen from './BookmarkEmptyScreen';
import type { ButtonProps } from './buttons/Button';
import { Button, ButtonSize, ButtonVariant } from './buttons/Button';
import { ShareIcon, SortIcon } from './icons';
import { generateQueryKey, OtherFeedPage, RequestKey } from '../lib/query';
import { useFeedLayout, useViewSize, ViewSize } from '../hooks';
import { useLayoutVariant } from '../hooks/layout/useLayoutVariant';
import { PageHeader } from './layout/PageHeader';
import { BookmarkSection } from './sidebar/sections/BookmarkSection';
import PlusMobileEntryBanner from './marketing/banners/PlusMobileEntryBanner';
import { DigestBookmarkBanner } from './marketing/banners/DigestBookmarkBanner';
Expand Down Expand Up @@ -60,17 +61,6 @@ const SharedBookmarksModal = dynamic(
),
);

const ShareBookmarksButton = ({
children,
...props
}: PropsWithChildren<
Pick<ButtonProps<'button'>, 'className' | 'onClick' | 'icon'>
>) => (
<Button variant={ButtonVariant.Secondary} {...props}>
{children}
</Button>
);

const bookmarkSortOptions = [
{ label: 'Newest first', value: BookmarkSort.TimeDesc },
{ label: 'Oldest first', value: BookmarkSort.TimeAsc },
Expand Down Expand Up @@ -103,6 +93,8 @@ export default function BookmarkFeedLayout({
DEFAULT_BOOKMARK_SORT_INDEX,
);
const isLaptop = useViewSize(ViewSize.Laptop);
const { isV2 } = useLayoutVariant();
const isV2Laptop = isV2;
const isSearchResults = !!searchQuery;
const isFolderPage = !!folder || isReminderOnly;
const listId = folder?.id;
Expand Down Expand Up @@ -190,54 +182,109 @@ export default function BookmarkFeedLayout({
return null;
}

const sortDropdown = !isSearchResults && (
<Dropdown
className={{
label: 'hidden',
chevron: 'hidden',
button: isV2Laptop ? undefined : '!px-1',
container: isV2Laptop ? 'flex' : 'ml-4 flex',
}}
shouldIndicateSelected
icon={<SortIcon size={isV2Laptop ? IconSize.XSmall : IconSize.Medium} />}
iconOnly
selectedIndex={selectedSort}
options={bookmarkSortOptionLabels}
onChange={(_, index) => setSelectedSort(index)}
buttonVariant={isV2Laptop ? ButtonVariant.Tertiary : ButtonVariant.Float}
buttonSize={isV2Laptop ? ButtonSize.Small : ButtonSize.Medium}
drawerProps={{ displayCloseButton: true }}
/>
);
const shareButton = !isFolderPage && (
<Button
aria-label="Share bookmarks"
className={isV2Laptop ? undefined : 'ml-4 flex'}
icon={
<ShareIcon
size={isV2Laptop ? IconSize.XSmall : IconSize.Medium}
secondary={showSharedBookmarks}
aria-hidden
/>
}
onClick={() => setShowSharedBookmarks(true)}
size={isV2Laptop ? ButtonSize.Small : ButtonSize.Medium}
variant={isV2Laptop ? ButtonVariant.Tertiary : ButtonVariant.Secondary}
>
{isLaptop ? <span>Share bookmarks</span> : null}
</Button>
);
const folderMenu = folder && !isReminderOnly && (
<BookmarkFolderContextMenu
folder={folder}
buttonProps={
isV2Laptop
? {
className: 'flex',
size: ButtonSize.Small,
variant: ButtonVariant.Tertiary,
}
: undefined
}
/>
);
const headerTitleSlot = isV2Laptop ? (
<div className="flex min-w-0 flex-1 items-center gap-3">
<Typography
bold
type={TypographyType.Callout}
tag={TypographyTag.H1}
truncate
className="min-w-0 shrink"
>
{title}
</Typography>
{searchChildren && (
<div className="min-w-0 max-w-[20rem] flex-1">{searchChildren}</div>
)}
</div>
) : (
title
);

return (
<FeedPageLayoutComponent>
{children}
<FeedPageHeader className="mb-5">
<Typography bold type={TypographyType.Title3} tag={TypographyTag.H1}>
{title}
</Typography>
</FeedPageHeader>
<CustomFeedHeader
className={classNames(
'mb-6',
shouldUseListFeedLayout && !shouldUseListMode && 'px-4',
)}
>
{searchChildren}
{!isSearchResults && (
<Dropdown
className={{
label: 'hidden',
chevron: 'hidden',
button: '!px-1',
container: 'ml-4 flex',
}}
shouldIndicateSelected
icon={<SortIcon size={IconSize.Medium} />}
iconOnly
selectedIndex={selectedSort}
options={bookmarkSortOptionLabels}
onChange={(_, index) => setSelectedSort(index)}
buttonVariant={ButtonVariant.Float}
buttonSize={ButtonSize.Medium}
drawerProps={{ displayCloseButton: true }}
/>
)}
{!isFolderPage && (
<ShareBookmarksButton
aria-label="Share bookmarks"
className="ml-4 flex"
icon={<ShareIcon secondary={showSharedBookmarks} aria-hidden />}
onClick={() => setShowSharedBookmarks(true)}
{isV2Laptop ? (
<PageHeader title={headerTitleSlot}>
{sortDropdown}
{shareButton}
{folderMenu}
</PageHeader>
) : (
<>
<FeedPageHeader className="mb-5">
<Typography
bold
type={TypographyType.Title3}
tag={TypographyTag.H1}
>
{title}
</Typography>
</FeedPageHeader>
<CustomFeedHeader
className={classNames(
'mb-6',
shouldUseListFeedLayout && !shouldUseListMode && 'px-4',
)}
>
{isLaptop ? <span>Share bookmarks</span> : null}
</ShareBookmarksButton>
)}
{folder && !isReminderOnly && (
<BookmarkFolderContextMenu folder={folder} />
)}
</CustomFeedHeader>
{searchChildren}
{sortDropdown}
{shareButton}
{folderMenu}
</CustomFeedHeader>
</>
)}

{showSharedBookmarks && (
<SharedBookmarksModal
Expand Down
5 changes: 5 additions & 0 deletions packages/shared/src/components/Feed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ import { BriefBannerFeed } from './cards/brief/BriefBanner/BriefBannerFeed';
import { ActionType } from '../graphql/actions';
import { TopHero } from './marketing/banners/HeroBottomBanner';
import { useReadingReminderFeedHero } from '../hooks/notifications/useReadingReminderFeedHero';
import { useLayoutVariant } from '../hooks/layout/useLayoutVariant';
import { useLegacyPostLayoutOptOut } from './post/reader/hooks/useLegacyPostLayoutOptOut';
import { useReaderModalEligibility } from './post/reader/hooks/useReaderModalEligibility';

Expand Down Expand Up @@ -365,6 +366,7 @@ export default function Feed<T>({
readerEligiblePostTypes.has(post.type),
[isReaderModalFeatureReady, isReaderModalOn, readerEligiblePostTypes],
);
const { isV2 } = useLayoutVariant();
const {
adjustedHeroInsertIndex,
shouldShowTopHero,
Expand All @@ -377,6 +379,9 @@ export default function Feed<T>({
itemCount: items.length,
itemsPerRow: virtualizedNumCards,
firstSlotOffset: Number(showProfileCompletionCard || showBriefCard),
// Layout v2 hoists the top hero into MainLayout above the floating
// feed card, so the feed must not render or measure it here.
disableTopHero: isV2,
});

useMutationSubscription({
Expand Down
Loading
Loading