Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
140 commits
Select commit Hold shift + click to select a range
9a252f0
feat(layout-v2): label rail icons and pin streak/cores/reputation
tsahimatsliah Jun 10, 2026
7e3f926
fix(layout-v2): allow null stat value in SidebarRailStats
tsahimatsliah Jun 10, 2026
fc2c88e
refactor(layout-v2): move profile to rail-bottom avatar, tighten padding
tsahimatsliah Jun 10, 2026
f73a26a
feat(layout-v2): compact sidebar mode + uniform rail hover
tsahimatsliah Jun 10, 2026
08970e8
refactor(layout-v2): drop rail compact toggle, add Home panel title
tsahimatsliah Jun 10, 2026
d1b33d3
feat(layout-v2): fold Discover into Home with an Explore entry
tsahimatsliah Jun 10, 2026
0f1c429
feat(layout-v2): reorder rail, lift Settings up, merge stats + avatar
tsahimatsliah Jun 10, 2026
fd785b9
feat(layout-v2): responsive "More" overflow menu on the rail
tsahimatsliah Jun 10, 2026
3523955
style(layout-v2): icon-over-label stats, drop the stats card box
tsahimatsliah Jun 10, 2026
b57dcbc
feat(layout-v2): grouped Explore tab hub across sections
tsahimatsliah Jun 10, 2026
39086b8
feat(layout-v2): pin squads into a Home "Pinned" section
tsahimatsliah Jun 10, 2026
63543e6
fix(layout-v2): pass pin variant as a prop, not via layout hook
tsahimatsliah Jun 10, 2026
f4c2de7
feat(layout-v2): hover-only pin icon, always-show Pinned section
tsahimatsliah Jun 10, 2026
196103b
feat(layout-v2): Recent pages section in the Home panel
tsahimatsliah Jun 10, 2026
4e39fdd
feat(layout-v2): profile avatar opens a dropdown; drop Settings rail tab
tsahimatsliah Jun 10, 2026
1ce7f19
feat(layout-v2): Explore ranking via a Sort dropdown, not a second ta…
tsahimatsliah Jun 10, 2026
7a66b75
style(layout-v2): 1px gap between sidebar section items
tsahimatsliah Jun 10, 2026
0749ee0
fix(layout-v2): consistent Explore header height; keep tabs on Discus…
tsahimatsliah Jun 10, 2026
12b6a93
feat(layout-v2): new calendar icon, sized sort button, Best of header
tsahimatsliah Jun 10, 2026
2385a0b
style(layout-v2): smaller New post button, fix quest badge overlap
tsahimatsliah Jun 10, 2026
f2b90b9
fix(layout-v2): square icon-only dropdown, smaller plus icon
tsahimatsliah Jun 11, 2026
91e0165
refactor(layout-v2): apply PR review fixes
tsahimatsliah Jun 11, 2026
4ee3018
fix(layout-v2): pin quest badge to button corner, clear of icon
tsahimatsliah Jun 11, 2026
ec3cfbc
fix(layout-v2): align Home panel list top with other panels
tsahimatsliah Jun 11, 2026
9adb257
feat(layout-v2): move reputation + cores into profile dropdown header
tsahimatsliah Jun 11, 2026
d0b0ec7
feat(layout-v2): Reddit-style hover-to-peek collapsed sidebar
tsahimatsliah Jun 11, 2026
799c863
feat(layout-v2): resize cursor + click-empty-to-pin on collapsed sidebar
tsahimatsliah Jun 11, 2026
0b1940b
style(layout-v2): match peek overlay color, bigger streak, prod-style…
tsahimatsliah Jun 11, 2026
bf03b19
feat(layout-v2): Plus badge on the sidebar profile avatar
tsahimatsliah Jun 11, 2026
2231349
feat(layout-v2): match production profile menu in sidebar dropdown
tsahimatsliah Jun 11, 2026
e5b3c20
fix(layout-v2): collapse click no longer re-peeks under the cursor
tsahimatsliah Jun 11, 2026
7aa50b5
fix(layout-v2): keep sidebar one solid color across collapse/hover/pin
tsahimatsliah Jun 12, 2026
6be08cc
feat(layout-v2): hovering a rail tab previews its panel
tsahimatsliah Jun 12, 2026
e4cd888
feat(layout-v2): curate profile dropdown items, hover-only external icon
tsahimatsliah Jun 12, 2026
500c002
feat(layout-v2): rework support menu, compact stats, hover-only link …
tsahimatsliah Jun 12, 2026
ac381bf
fix(layout-v2): use popover surface color for the sidebar + panel
tsahimatsliah Jun 12, 2026
5ba7625
feat(layout-v2): reorder profile + support menus
tsahimatsliah Jun 12, 2026
0878481
feat(layout-v2): rail New post button opens a create menu
tsahimatsliah Jun 12, 2026
1e81534
style(layout-v2): move settings section above billing in profile menu
tsahimatsliah Jun 12, 2026
78cdf84
style(layout-v2): drop Log out from settings panel (now in profile menu)
tsahimatsliah Jun 12, 2026
8275b1b
feat(layout-v2): New post hovers a panel, clicks open the composer modal
tsahimatsliah Jun 13, 2026
235f73c
style: tighten name/handle gap in profile menu header
tsahimatsliah Jun 13, 2026
57e508d
style(layout-v2): match sidebar bg to the V2 page background color-mix
tsahimatsliah Jun 13, 2026
26753a5
style(layout-v2): right border on the hover-peek panel
tsahimatsliah Jun 13, 2026
83c096a
fix: guard undefined user in ProfileMenuHeader for strict typecheck
tsahimatsliah Jun 13, 2026
24d0b20
style(layout-v2): render new-post panel with the shared Section list
tsahimatsliah Jun 13, 2026
53c8c31
fix(layout-v2): match document background to tinted page on overscroll
tsahimatsliah Jun 13, 2026
0eb07d3
feat(layout-v2): add Share a link to the new-post panel
tsahimatsliah Jun 13, 2026
97a17a7
feat(layout-v2): new-post options open the composer modal with the ri…
tsahimatsliah Jun 13, 2026
4ecb568
feat(layout-v2): full-width squad discover, drop My Squads tab
tsahimatsliah Jun 13, 2026
d6d398c
style(layout-v2): smaller New Squad button in the squad directory header
tsahimatsliah Jun 13, 2026
be8d6b6
fix(layout-v2): restore sidebar Squads panel to original behavior
tsahimatsliah Jun 13, 2026
6267290
fix(layout-v2): crash when hovering New post (path-less onClick item)
tsahimatsliah Jun 13, 2026
c85dec8
feat(layout-v2): prediction-cone hover intent for sidebar panel preview
tsahimatsliah Jun 13, 2026
383a320
Revert "feat(layout-v2): prediction-cone hover intent for sidebar pan…
tsahimatsliah Jun 13, 2026
813559d
feat(icons): add NewPost icon, use it for the rail New post button
tsahimatsliah Jun 13, 2026
ebac3a1
style(layout-v2): size New post icon to match rail nav icons (24px)
tsahimatsliah Jun 13, 2026
81549f0
style(layout-v2): smaller, hover-only section collapse arrow
tsahimatsliah Jun 13, 2026
0d16f52
chore(deps): add @floating-ui/react for sidebar hover-intent (safePol…
tsahimatsliah Jun 13, 2026
450d79b
feat(layout-v2): pointer-events safe zone for rail->panel hover intent
tsahimatsliah Jun 13, 2026
8e9aee9
style(layout-v2): add top spacing above the New post button
tsahimatsliah Jun 13, 2026
bfa7c8b
fix(layout-v2): always show items in title-less sidebar sections
tsahimatsliah Jun 13, 2026
0f9bcd3
feat(layout-v2): restore add buttons on title-less sidebar sections +…
tsahimatsliah Jun 13, 2026
2bc3986
perf(layout-v2): make profile-dropdown navigation feel instant
tsahimatsliah Jun 13, 2026
e25198b
fix(layout-v2): actually shrink the section collapse arrow
tsahimatsliah Jun 13, 2026
ea84875
fix(layout-v2): move Saved/Squads "+" into the panel title strip
tsahimatsliah Jun 13, 2026
aacbad6
feat(layout-v2): single-panel settings sidebar with "Back to app"
tsahimatsliah Jun 13, 2026
32a9c07
style(layout-v2): cap settings content to a 768px column
tsahimatsliah Jun 13, 2026
7750afe
style(layout-v2): drop the heavy bottom shadow on the content card
tsahimatsliah Jun 13, 2026
72f53d9
style(layout-v2): use a Subtle Button + MoveTo icon for "Back to app"
tsahimatsliah Jun 13, 2026
87eec8c
style(layout-v2): smaller, left-pointing MoveTo icon on "Back to app"
tsahimatsliah Jun 14, 2026
899f473
feat(layout-v2): move theme toggle into profile dropdown, gift shortc…
tsahimatsliah Jun 14, 2026
5908f0d
feat(layout-v2): always-visible reading streak HUD on the content card
tsahimatsliah Jun 14, 2026
3e622ae
Merge remote-tracking branch 'origin/main' into claude/strange-jang-4…
tsahimatsliah Jun 14, 2026
2322299
style(layout-v2): slim down the reading streak HUD to a single line
tsahimatsliah Jun 14, 2026
cb45296
feat(layout-v2): float the reading streak HUD instead of taking layou…
tsahimatsliah Jun 15, 2026
c899303
feat(layout-v2): two zero-space streak options to compare (avatar rin…
tsahimatsliah Jun 15, 2026
f0f64bf
feat(layout-v2): right-side activity drawer for streak + quests (opti…
tsahimatsliah Jun 15, 2026
63ca915
feat(icons): add Help icon and use it for the rail support button
tsahimatsliah Jun 15, 2026
a6ebbf5
feat(layout-v2): move profile avatar to top of rail, logo to bottom
tsahimatsliah Jun 15, 2026
8206645
style(layout-v2): rounded-square streak status ring on the profile av…
tsahimatsliah Jun 15, 2026
d9ff440
style(layout-v2): flat rounded-square streak chip + clearance from se…
tsahimatsliah Jun 15, 2026
22002fb
style(layout-v2): refine avatar streak ring, chip, and logo size
tsahimatsliah Jun 15, 2026
a2b8ab8
style(icons): update gift icon to the new design
tsahimatsliah Jun 15, 2026
1addeae
style(layout-v2): concentric, bolder avatar streak ring
tsahimatsliah Jun 15, 2026
8485d2e
revert(layout-v2): remove the right-side activity dock (streak/quests…
tsahimatsliah Jun 15, 2026
cbab6ff
style(layout-v2): square hover area for rail gift/support buttons
tsahimatsliah Jun 15, 2026
e66fb06
fix(settings): keep the v2 page header full-width, cap only the conte…
tsahimatsliah Jun 15, 2026
7e113f4
feat(layout-v2): show the Plus star on the daily.dev logo for Plus me…
tsahimatsliah Jun 15, 2026
bf4a0a6
feat(layout-v2): reading-streak status ring state machine on the avatar
tsahimatsliah Jun 15, 2026
40f3134
fix(layout-v2): Plus badge on the logo top-right, matching production
tsahimatsliah Jun 15, 2026
f868106
feat(layout-v2): refine streak ring — drop pending pulse + milestone,…
tsahimatsliah Jun 15, 2026
8ea2b32
fix(layout-v2): don't replay the streak earn pop on page refresh
tsahimatsliah Jun 15, 2026
0eb8928
feat(layout-v2): connected colored streak card on the avatar
tsahimatsliah Jun 15, 2026
eef1995
fix(layout-v2): bigger streak tab + flame matches the count color
tsahimatsliah Jun 15, 2026
668304b
refactor(layout-v2): split streak surround into dashed frame + backgr…
tsahimatsliah Jun 15, 2026
56f7752
feat(layout-v2): variant 2 streak — slim border, transparent fill, fa…
tsahimatsliah Jun 15, 2026
f75e4d9
feat(layout-v2): neutral-gray calm streak states + V1-style earn pop
tsahimatsliah Jun 15, 2026
8188d18
feat(layout-v2): pink-on-read flame, blue rest day, count aligned to sim
tsahimatsliah Jun 15, 2026
7bb9b69
feat(layout-v2): streak hover state + sidebar-matched border colour
tsahimatsliah Jun 15, 2026
2b906aa
feat(layout-v2): more avatar↔streak gap + compact large streak counts
tsahimatsliah Jun 15, 2026
f259d78
feat(layout-v2): bigger streak ring/padding + calmer critical/rest bo…
tsahimatsliah Jun 15, 2026
db0b3ae
feat(layout-v2): critical streak — slower pulse + breathing dashed re…
tsahimatsliah Jun 15, 2026
ed164cb
feat(layout-v2): split streak hover into two distinct buttons
tsahimatsliah Jun 15, 2026
eefbfec
feat(layout-v2): widen streak component + uniform rail top/side spacing
tsahimatsliah Jun 15, 2026
461ed53
feat(layout-v2): photo-friendly avatar hover + more avatar-to-number gap
tsahimatsliah Jun 15, 2026
a2e025c
feat(layout-v2): streak tile fills rail width, reduced radii, centred…
tsahimatsliah Jun 15, 2026
18db8f4
feat(layout-v2): logo dropdown (apps/changelog/docs), drop plus badge…
tsahimatsliah Jun 15, 2026
c6b22a4
fix(layout-v2): streak frame radius matches the nav tabs exactly (rou…
tsahimatsliah Jun 15, 2026
2a0ace6
revert(layout-v2): move apps/changelog/docs back to Support, logo bac…
tsahimatsliah Jun 15, 2026
c1eb4d4
Merge branch 'main' into claude/strange-jang-4a3ea2
tsahimatsliah Jun 15, 2026
1389d0c
feat(layout-v2): streak tile back to the boxed avatar+number variation
tsahimatsliah Jun 15, 2026
e38a625
fix(layout-v2): even out streak tile top gap, tighten gap to search
tsahimatsliah Jun 15, 2026
b882385
feat(layout-v2): widen streak box to 62px so the number clears the bo…
tsahimatsliah Jun 15, 2026
5660443
revert(layout-v2): streak box back to 54px (drop approach #2 widen)
tsahimatsliah Jun 15, 2026
57d9545
fix(layout-v2): square streak avatar + un-break its tooltip
tsahimatsliah Jun 15, 2026
e541dc5
fix(layout-v2): streak calm border matches the rail/panel separator
tsahimatsliah Jun 15, 2026
93004b2
feat(layout-v2): streak "border legend" layout (option 10)
tsahimatsliah Jun 15, 2026
54a5827
fix(layout-v2): equal top/left/right spacing for the streak tile
tsahimatsliah Jun 15, 2026
673a422
fix(layout-v2): rail popups are mutually exclusive + drop avatar hove…
tsahimatsliah Jun 15, 2026
8ba46d6
fix(layout-v2): streak chip lower + taller box, lighter calm border
tsahimatsliah Jun 15, 2026
3ebbb04
feat(layout-v2): extract shared StreakRing + Storybook story
tsahimatsliah Jun 15, 2026
d92ce8c
fix(layout-v2): tighten streak avatar-to-chip gap (box 66 -> 62)
tsahimatsliah Jun 15, 2026
9c2cc5c
fix(storybook): declare *.svg modules so the strict typecheck passes
tsahimatsliah Jun 15, 2026
74fdf25
feat(layout-v2): frozen flame filled + reworked earn-pop animation
tsahimatsliah Jun 15, 2026
04c7303
fix(layout-v2): streak chip bg matches the v2 rail tint exactly
tsahimatsliah Jun 15, 2026
72ff98c
fix(layout-v2): reserve streak space + skeleton while loading (no lay…
tsahimatsliah Jun 15, 2026
2f2e70a
feat(layout-v2): gong-feel earn pop + auto-open urgency tooltip
tsahimatsliah Jun 15, 2026
022abd7
chore(layout-v2): prettier-wrap streak-earn-fill animation line
tsahimatsliah Jun 15, 2026
b1ea756
feat(layout-v2): smoother breathe earn pop + critical-only 5s urgency…
tsahimatsliah Jun 15, 2026
75e0a34
fix(storybook): wrap StreakRing story in QueryClientProvider
tsahimatsliah Jun 15, 2026
b3570a0
feat(layout-v2): punchy "gong" earn pop (replace the slow breathe)
tsahimatsliah Jun 15, 2026
6754def
feat(layout-v2): streak chip bg picks up the state's fill colour
tsahimatsliah Jun 15, 2026
1eb59c8
fix(layout-v2): streak chip picks up the state colour on hover only
tsahimatsliah Jun 15, 2026
362c41a
feat(layout-v2): faster earn pop (~0.3s)
tsahimatsliah Jun 15, 2026
b93eb1e
chore(storybook): add earn-pop variations lab to StreakRing stories
tsahimatsliah Jun 15, 2026
341c18f
chore(storybook): sweep earn-pop lab across 6 durations (0.15s-1s)
tsahimatsliah Jun 15, 2026
030a724
feat(layout-v2): 0.6s earn pop with delayed fill wash-in
tsahimatsliah Jun 15, 2026
53a222f
refactor(layout-v2): drop dead streak code surfaced by PR review
tsahimatsliah Jun 15, 2026
202cc74
Merge branch 'main' into claude/strange-jang-4a3ea2
tsahimatsliah Jun 16, 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
17 changes: 9 additions & 8 deletions packages/shared/src/components/MainFeedLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ import { useReadingReminderHero } from '../hooks/notifications/useReadingReminde
import { useTrackQuestClientEvent } from '../hooks/useTrackQuestClientEvent';
import { useReadingReminderVariation } from '../hooks/notifications/useReadingReminderVariation';
import { useLayoutVariant } from '../hooks/layout/useLayoutVariant';
import { ExploreSectionTabs } from './header/ExploreSectionTabs';
import { ExploreSortDropdown } from './header/ExploreSortDropdown';

const FeedExploreHeader = dynamic(
() =>
Expand Down Expand Up @@ -263,6 +265,7 @@ export default function MainFeedLayout({
isPopular,
isAnyExplore,
isExploreLatest,
isDiscussed,
isSortableFeed,
isCustomFeed,
isSearch: isSearchPage,
Expand Down Expand Up @@ -726,7 +729,10 @@ export default function MainFeedLayout({
// page-header strip (matching the SquadDirectoryLayout pattern). The
// inline FeedExploreComponent is suppressed below to avoid showing
// the same tabs twice.
const showExploreV2PageHeader = isAnyExplore && isV2;
// The Discussions feed (/discussed) is part of the Explore hub — show the
// same section tabs there so the hub persists. The Sort dropdown is only
// for the actual Explore sorts, so it stays gated on isAnyExplore.
const showExploreV2PageHeader = (isAnyExplore || isDiscussed) && isV2;

// v2 also hoists the regular page-header strip up here, OUTSIDE
// `FeedPageLayoutComponent`, so it can span the full floating-card
Expand Down Expand Up @@ -765,13 +771,8 @@ export default function MainFeedLayout({
<>
{showExploreV2PageHeader && (
<header className={classNames(pageHeaderClassName, '!py-0')}>
<FeedExploreHeader
directoryTabs
tab={tab}
setTab={onTabChange}
showBreadcrumbs={false}
className={{ container: 'min-w-0 flex-1' }}
/>
<ExploreSectionTabs />
{isAnyExplore && <ExploreSortDropdown />}
</header>
)}
{showFeedV2PageHeader && (
Expand Down
33 changes: 27 additions & 6 deletions packages/shared/src/components/MainLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { SpotlightHost } from './spotlight/SpotlightHost';
import { FeedbackWidget } from './feedback';
import { isExtension } from '../lib/func';
import { useLayoutVariant } from '../hooks/layout/useLayoutVariant';
import { useRecordRecentPages } from '../hooks/useRecentPages';
import { isSidebarSettingsPath } from './sidebar/sidebarCategory';
import {
HomepageTopBanners,
Expand Down Expand Up @@ -105,8 +106,15 @@ function MainLayoutComponent({
const { growthbook } = useGrowthBookContext();
const { sidebarRendered } = useSidebarRendered();
const { isAvailable: isBannerAvailable } = useBanner();
const { sidebarExpanded, autoDismissNotifications, loadedSettings } =
const { sidebarExpanded, autoDismissNotifications, loadedSettings, flags } =
useContext(SettingsContext);
const isSidebarCompact = !!flags?.sidebarCompact;
const v2CollapsedPadding = isSidebarCompact
? 'tablet:pl-16 laptop:pl-16'
: 'tablet:pl-16 laptop:pl-20';
const v2ExpandedPadding = isSidebarCompact
? 'laptop:!pl-[19rem]'
: 'laptop:!pl-[20rem]';
const [hasLoggedImpression, setHasLoggedImpression] = useState(false);
const { feedName } = useActiveFeedNameContext();
const page = router?.route?.substring(1).trim() as SharedFeedPage;
Expand All @@ -120,6 +128,7 @@ function MainLayoutComponent({
const { screenCenteredOnMobileLayout } = useFeedLayout();
const { isNotificationsReady, unreadCount } = useNotificationContext();
const { isV2, isLoading: isLayoutVariantLoading } = useLayoutVariant();
useRecordRecentPages(isV2);
useNotificationParams();

// Settings pages render their navigation only inside the v2 context panel,
Expand Down Expand Up @@ -148,6 +157,18 @@ function MainLayoutComponent({
setContentTransitionsEnabled(true);
}
}, [layoutSettled]);
// The v2 page uses a tinted background; the document root stays
// `background-default`, so overscroll past the feed reveals a darker strip.
// Flag the root while v2 is active so it can paint the same tint (laptop+,
// matching where the tinted page background applies — see base.css).
useEffect(() => {
if (!isV2) {
return undefined;
}
const root = globalThis.document?.documentElement;
root?.classList.add('layout-v2');
return () => root?.classList.remove('layout-v2');
}, [isV2]);
// v2 (experiment) snaps the initial settle into place (transitions enable
// one commit later, so only genuine toggles animate). The control variant
// keeps animating on `layoutSettled` exactly as before.
Expand Down Expand Up @@ -307,14 +328,12 @@ function MainLayoutComponent({
'transition-[padding] duration-300 ease-in-out',
!sidebarOwnsHeader && 'laptop:pt-16',
showSidebar &&
(isV2 ? 'tablet:pl-16 laptop:pl-16' : 'tablet:pl-16 laptop:pl-11'),
(isV2 ? v2CollapsedPadding : 'tablet:pl-16 laptop:pl-11'),
className,
isAuthReady &&
showSidebar &&
(sidebarExpanded || forceSidebarExpanded) &&
(isV2
? 'laptop:!pl-[19rem]'
: !isScreenCentered && 'laptop:!pl-60'),
(isV2 ? v2ExpandedPadding : !isScreenCentered && 'laptop:!pl-60'),
isBannerAvailable && !sidebarOwnsHeader && 'laptop:pt-24',
)}
>
Expand All @@ -341,7 +360,9 @@ function MainLayoutComponent({
// card without establishing a scroll container, so descendant
// `position: sticky` elements (e.g. the post action bar) stick
// to the viewport instead of being inert.
'laptop:overflow-clip laptop:rounded-24 laptop:border laptop:border-border-subtlest-quaternary laptop:bg-background-default laptop:p-0.5 laptop:shadow-2',
// No drop shadow — the subtle border defines the floating card
// in both themes; shadow-2 cast a heavy bottom shadow.
'laptop:overflow-clip laptop:rounded-24 laptop:border laptop:border-border-subtlest-quaternary laptop:bg-background-default laptop:p-0.5',
!hasTopBanners &&
!topBanner &&
'laptop:min-h-[calc(100vh-1.5rem)]',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,14 @@ export const ProfileMenuHeader = ({
className,
shouldOpenProfile = false,
profileImageSize = ProfileImageSize.Large,
}: Props): ReactElement => {
}: Props): ReactElement | null => {
const { user } = useAuthContext();
const { isPlus } = usePlusSubscription();

if (!user) {
return null;
}

return (
<ConditionalWrapper
condition={shouldOpenProfile}
Expand All @@ -51,7 +55,7 @@ export const ProfileMenuHeader = ({
className="!rounded-10 border-background-default"
/>

<div className="flex min-w-0 flex-1 flex-col gap-1">
<div className="flex min-w-0 flex-1 flex-col gap-0.5">
<div className="flex items-center gap-1">
<Typography
type={TypographyType.Subhead}
Expand Down
17 changes: 15 additions & 2 deletions packages/shared/src/components/ProfileMenu/ProfileSectionItem.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import type { ReactElement } from 'react';
import classNames from 'classnames';
import { useRouter } from 'next/router';
import type { WithClassNameProps } from '../utilities';
import Link from '../utilities/Link';
import {
Expand Down Expand Up @@ -53,23 +54,35 @@ export const ProfileSectionItem = ({
isActive,
typography,
}: ProfileSectionItemProps): ReactElement => {
const router = useRouter();
const isMobile = useViewSize(ViewSize.MobileL);
const tag = href ? TypographyTag.Link : TypographyTag.Button;
const showLinkIcon = href && external;
const openNewTab = showLinkIcon && !href.startsWith(webappUrl);
// Warm the destination chunk while the cursor is on the row so the click
// resolves fast. The dropdown is conditionally mounted, so Next's default
// viewport prefetch never gets a chance before the click otherwise.
const isInternal = !!href && !external && href.startsWith(webappUrl);
const prefetch = () => {
if (isInternal) {
router.prefetch(href).catch(() => undefined);
}
};
const content = (
<Typography<typeof tag>
tag={tag}
color={typography?.color ?? TypographyColor.Tertiary}
type={typography?.type ?? TypographyType.Subhead}
className={classNames(
'flex h-10 cursor-pointer items-center gap-2 rounded-10 px-1 tablet:h-8',
'group flex h-10 cursor-pointer items-center gap-2 rounded-10 px-1 tablet:h-8',
(href || onClick) && 'hover:bg-surface-float',
isActive ? 'bg-surface-active' : undefined,
className,
)}
{...combinedClicks(() => onClick?.())}
{...(openNewTab && { target: '_blank', rel: anchorDefaultRel })}
onMouseEnter={prefetch}
onFocus={prefetch}
>
{Icon && (
<Icon
Expand All @@ -81,7 +94,7 @@ export const ProfileSectionItem = ({

{!isMobile && showLinkIcon && (
<OpenLinkIcon
className="ml-auto text-text-quaternary"
className="ml-auto text-text-quaternary opacity-0 transition-opacity group-focus-within:opacity-100 group-hover:opacity-100"
size={IconSize.Size16}
/>
)}
Expand Down
25 changes: 18 additions & 7 deletions packages/shared/src/components/cards/squad/SquadsDirectoryFeed.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { ReactElement, ReactNode } from 'react';
import React, { useMemo } from 'react';
import classNames from 'classnames';
import { useInView } from 'react-intersection-observer';
import type { Squad } from '../../../graphql/sources';
import type { SourcesQueryProps } from '../../../hooks/source/useSources';
Expand All @@ -26,6 +27,7 @@ import type { Ad } from '../../../graphql/posts';
import { AdPixel } from '../ad/common/AdPixel';
import { TargetType } from '../../../lib/log';
import { useAdQuery } from '../../../features/monetization/useAdQuery';
import { useLayoutVariant } from '../../../hooks/layout/useLayoutVariant';

interface SquadHorizontalListProps {
title: HorizontalScrollTitleProps;
Expand Down Expand Up @@ -88,11 +90,12 @@ export function SquadsDirectoryFeed({
className,
children,
firstItemShouldBeAd = false,
}: SquadHorizontalListProps): ReactElement {
}: SquadHorizontalListProps): ReactElement | null {
const { ref, inView } = useInView({
triggerOnce: true,
});
const { user, isAuthReady } = useAuthContext();
const { isV2 } = useLayoutVariant();
const { result } = useSources<Squad>({ query, isEnabled: inView });
const { isFetched } = result;
const isMobile = useViewSize(ViewSize.MobileL);
Expand All @@ -107,7 +110,9 @@ export function SquadsDirectoryFeed({
),
enabled: firstItemShouldBeAd && isAuthReady && !user?.isPlus,
});
const { squad: squadAd } = useSquad({ handle: ad?.data?.source?.handle });
const { squad: squadAd } = useSquad({
handle: ad?.data?.source?.handle ?? '',
});
const flatSources = useMemo(() => {
const map = getFlatteredNodes(result);

Expand Down Expand Up @@ -148,8 +153,8 @@ export function SquadsDirectoryFeed({
const isAd = ad && index === 0;

return (
<SquadItemLogExtraContext key={node.id} ad={ad}>
<SquadList squad={node} ad={isAd ? ad : undefined}>
<SquadItemLogExtraContext key={node.id} ad={ad ?? undefined}>
<SquadList squad={node} ad={isAd ? ad ?? undefined : undefined}>
{!!ad?.pixel && <AdPixel pixel={ad.pixel} />}
</SquadList>
</SquadItemLogExtraContext>
Expand All @@ -163,7 +168,13 @@ export function SquadsDirectoryFeed({
return (
<HorizontalScroll
ref={ref}
className={{ container: className, scroll: 'gap-6' }}
className={{
container: className,
// v2: bleed the scroll row past the page gutters (laptop:px-6) so the
// cards run continuously to the edge of the feed area instead of being
// clipped inside the padding. Re-pad so the first card stays aligned.
scroll: classNames('gap-6', isV2 && 'laptop:-mx-6 laptop:px-6'),
}}
scrollProps={{ title, linkToSeeAll }}
>
{children}
Expand All @@ -175,12 +186,12 @@ export function SquadsDirectoryFeed({
(node.flags?.featured && linkToSeeAll.includes('featured'));

return showFeaturedCard ? (
<SquadItemLogExtraContext key={node.id} ad={ad}>
<SquadItemLogExtraContext key={node.id} ad={ad ?? undefined}>
<SquadGrid
source={node}
className="w-80"
border={shouldShowAd ? SourceCardBorderColor.Pepper : undefined}
ad={shouldShowAd ? ad : undefined}
ad={shouldShowAd ? ad ?? undefined : undefined}
>
{!!ad?.pixel && <AdPixel pixel={ad.pixel} />}
</SquadGrid>
Expand Down
12 changes: 7 additions & 5 deletions packages/shared/src/components/fields/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,14 @@ export function Dropdown({
size={buttonSize}
disabled={disabled}
className={classNames(
// `!pl-4 !pr-2.5` overrides the Button's built-in Large padding (px-6)
// so the value lines up with the other fields' 16px text inset and the
// chevron sits tight to the right edge instead of floating 24px in.
'group flex w-full items-center !pl-4 !pr-2.5 font-normal text-text-secondary typo-body hover:bg-surface-hover hover:text-text-primary',
'group flex items-center font-normal text-text-secondary typo-body hover:bg-surface-hover hover:text-text-primary',
// Value dropdowns get `!pl-4 !pr-2.5` so the value lines up with other
// fields' 16px text inset and the chevron sits tight to the right edge.
// Icon-only dropdowns drop that and render as a square instead.
iconOnly
? 'aspect-square justify-center !px-0'
: 'w-full !pl-4 !pr-2.5',
className?.button,
iconOnly && 'items-center justify-center',
)}
onClick={fullScreen ? handleMenuTrigger : undefined}
onKeyDown={handleKeyboard}
Expand Down
20 changes: 20 additions & 0 deletions packages/shared/src/components/header/ExploreHubHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { ReactElement, ReactNode } from 'react';
import React from 'react';
import { PageHeader } from '../layout/PageHeader';
import { ExploreSectionTabs } from './ExploreSectionTabs';

// Shared v2 header for the Explore hub's directory pages (Tags, Sources,
// Leaderboard, Best of). Keeps the section-tab strip and its height
// (`!py-0`) consistent in one place. Optional children render as header
// actions (e.g. the "Suggest source" button).
export function ExploreHubHeader({
children,
}: {
children?: ReactNode;
}): ReactElement {
return (
<PageHeader title={<ExploreSectionTabs />} className="!py-0">
{children}
</PageHeader>
);
}
53 changes: 53 additions & 0 deletions packages/shared/src/components/header/ExploreSectionTabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { ReactElement } from 'react';
import React from 'react';
import { useRouter } from 'next/router';
import {
SquadDirectoryNavbar,
SquadDirectoryNavbarItem,
} from '../squads/layout/SquadDirectoryNavbar';
import { ButtonSize } from '../buttons/Button';

type ExploreSection = {
label: string;
path: string;
// The tab is active when the current path equals `match` or sits under it
// (e.g. /tags/react keeps the Tags tab active).
match: string;
};

const sections: ExploreSection[] = [
{ label: 'Explore', path: '/posts', match: '/posts' },
{ label: 'Tags', path: '/tags', match: '/tags' },
{ label: 'Sources', path: '/sources', match: '/sources' },
{ label: 'Leaderboard', path: '/users', match: '/users' },
{ label: 'Discussions', path: '/discussed', match: '/discussed' },
];

// Primary navbar for the unified Explore hub (v2). Sits above the Explore
// feed's sort tabs and on the Tags/Sources/Leaderboard/Discussions pages so
// the sections stay one click apart after Discover was folded into Home.
export function ExploreSectionTabs(): ReactElement {
const router = useRouter();
const currentPath = (router.asPath || router.pathname).split('?')[0];

return (
<SquadDirectoryNavbar
aria-label="Explore sections"
className="!mx-0 min-w-0 flex-1 !border-0 !px-0"
>
{sections.map((section) => (
<SquadDirectoryNavbarItem
key={section.label}
buttonSize={ButtonSize.Small}
isActive={
currentPath === section.match ||
currentPath.startsWith(`${section.match}/`)
}
label={section.label}
ariaLabel={`Show ${section.label}`}
path={section.path}
/>
))}
</SquadDirectoryNavbar>
);
}
Loading
Loading