From 8bc6f5b35da8fa225a5068f5893da3910634b4c3 Mon Sep 17 00:00:00 2001 From: feruzm Date: Mon, 7 Sep 2026 21:08:37 +0000 Subject: [PATCH 1/7] Redesign feed layout for clearer reading and navigation --- .../app/(dynamicPages)/feed/feed-reading.scss | 480 ++++++++++++++++++ .../src/app/(dynamicPages)/feed/layout.tsx | 9 +- .../_components/entry-index-menu/index.tsx | 183 +++---- .../_components/my-favorites-widget/index.tsx | 22 +- .../top-communities-widget/index.tsx | 105 ++-- .../_components/trending-tags-card/index.tsx | 112 ++-- apps/web/src/features/i18n/locales/en-US.json | 5 + .../entry-list-item-muted-content.tsx | 2 +- .../features/shared/entry-list-item/index.tsx | 4 +- apps/web/src/features/shared/navbar/index.tsx | 16 +- .../features/shared/navbar/navbar-desktop.tsx | 35 +- .../shared/navbar/navbar-perks-button.tsx | 19 +- .../shared/navbar/navbar-text-menu.tsx | 8 +- .../src/specs/app/entry-index-menu.spec.tsx | 70 ++- .../specs/app/top-communities-widget.spec.tsx | 135 +++++ .../src/specs/app/trending-tags-card.spec.tsx | 49 +- .../shared/navbar-perks-button.spec.tsx | 17 +- 17 files changed, 998 insertions(+), 273 deletions(-) create mode 100644 apps/web/src/app/(dynamicPages)/feed/feed-reading.scss create mode 100644 apps/web/src/specs/app/top-communities-widget.spec.tsx diff --git a/apps/web/src/app/(dynamicPages)/feed/feed-reading.scss b/apps/web/src/app/(dynamicPages)/feed/feed-reading.scss new file mode 100644 index 0000000000..f0469cae56 --- /dev/null +++ b/apps/web/src/app/(dynamicPages)/feed/feed-reading.scss @@ -0,0 +1,480 @@ +@import "../../../styles/vars_mixins"; + +.feed-page { + min-height: 100vh; + display: flow-root; + --feed-muted: #606a74; + + @include themify(night) { + background: #15171b; + --feed-muted: #a8adb6; + --border-color: #30343b; + + .ecency-navbar-desktop { + background: #15171b; + } + .entry-list-body:not(.grid-view) .entry-list-item { + border-bottom-color: #292d33; + } + } +} + +// Keep this treatment on feed routes. Profile lists, Decks, post pages and +// the reader's grid preference continue to use their existing layouts. +.feed-reading-layout { + max-width: 1440px; + gap: 28px; + padding-inline: 24px; + + .tags-side { + display: none; + width: 180px; + margin-right: 0; + padding-top: 16px; + + @media (min-width: 1024px) { + display: block; + } + } + + .entry-page-content { + width: 100%; + max-width: 840px; + margin-inline: auto; + } + + .side-menu { + width: 280px; + margin-left: 0; + padding-top: 16px; + } + + .feed-sidebar-heading { + color: var(--feed-muted); + font-size: 13px; + font-weight: 600; + line-height: 1.5; + margin-bottom: 16px; + } + + .feed-sidebar-link { + @apply text-blue-dark-sky; + display: inline-flex; + align-items: center; + min-height: 40px; + margin-top: 12px; + padding: 0; + font-size: 14px; + + &:hover { + text-decoration: underline; + } + } + + .feed-favorites a:not(.feed-sidebar-link) { + @apply text-gray-700 dark:text-gray-300; + } + + .feed-topics { + margin-top: 24px; + padding-top: 24px; + border-top: 1px solid var(--border-color); + } + + .feed-topics:first-child { + margin-top: 0; + padding-top: 0; + border-top: 0; + } + + .feed-topic-list { + display: flex; + flex-direction: column; + gap: 4px; + margin: 0; + padding: 0; + list-style: none; + } + + .feed-topic-row { + display: flex; + align-items: center; + min-width: 0; + gap: 4px; + + > a { + @apply text-gray-700 dark:text-gray-300; + display: flex; + align-items: center; + gap: 10px; + min-width: 0; + min-height: 36px; + flex: 1; + font-size: 14px; + + &:hover, + &[aria-current="page"] { + @apply text-blue-dark-sky; + } + } + + > [role="button"], + .feed-topic-dismiss { + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 28px; + height: 32px; + margin: 0; + } + } + + .feed-community-heading { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; + + > button { + flex-shrink: 0; + } + } + + .feed-community-name { + @apply text-gray-800 dark:text-gray-200; + display: flex; + align-items: center; + gap: 8px; + flex: 1; + min-width: 120px; + font-size: 15px; + font-weight: 600; + line-height: 1.4; + overflow-wrap: anywhere; + + .user-avatar { + flex-shrink: 0; + } + &:hover { + @apply text-blue-dark-sky; + } + } + + .feed-community-about { + @apply line-clamp-2; + color: var(--feed-muted); + margin: 8px 0 4px; + font-size: 14px; + line-height: 1.5; + overflow-wrap: anywhere; + } + + .feed-community-members { + color: var(--feed-muted); + font-size: 12px; + line-height: 1.5; + } + + .feed-community-create { + margin-top: 16px; + border-top: 1px solid var(--border-color); + } + + .entry-list-body:not(.grid-view) { + .entry-list-item { + margin-top: 0; + padding: 24px 0; + + .item-header { + margin-bottom: 10px; + flex-wrap: wrap; + overflow: visible; + gap: 4px 12px; + } + + .item-header-main { + flex-shrink: 1; + flex-wrap: wrap; + min-width: 0; + row-gap: 4px; + + .author-part .author-avatar { + min-width: 32px; + min-height: 36px; + margin-right: 8px; + } + .author-part .author, + .author-part .author-name { + font-weight: 500; + } + > a { + min-height: 32px; + max-width: 100%; + } + > a > div { + color: var(--feed-muted); + background: transparent; + border: 0; + padding-inline: 4px; + white-space: normal; + overflow-wrap: anywhere; + } + .date { + color: var(--feed-muted); + } + } + + .item-header-features { + flex-wrap: wrap; + min-width: 0; + margin-bottom: 0; + .reblogged { + flex-shrink: 1; + flex-wrap: wrap; + opacity: 1; + } + &:empty { + display: none; + } + } + + .item-summary { + min-width: 0; + .item-title { + @apply text-gray-900 dark:text-gray-100; + font-size: 22px; + line-height: 1.35; + max-height: none; + font-weight: 600; + overflow-wrap: anywhere; + @apply line-clamp-2; + } + .item-body { + @apply line-clamp-2; + color: var(--feed-muted); + font-size: 16px; + line-height: 1.55; + max-height: none; + margin: 6px 0 0; + overflow-wrap: anywhere; + } + } + + .entry-actions { + width: 100%; + max-width: 100%; + flex-wrap: wrap; + gap: 6px 12px; + border: 0; + border-radius: 0; + padding: 8px 0 0; + min-height: 44px; + + .entry-payout:not(.payout-declined):not(.payout-limit-hit), + .entry-votes .inner-btn, + .entry-reblog-btn:not(.reblogged):not(:hover) .inner-btn, + .entry-menu > button:not(:hover), + .entry-menu .dropdown-btn:not(:hover) { + color: var(--feed-muted); + } + + > .border-r { + display: none; + } + > .text-gray-steel { + color: var(--feed-muted); + } + > * { + flex-shrink: 0; + } + } + } + + @media (min-width: 640px) { + .entry-preview { + display: flex; + flex-direction: row-reverse; + align-items: flex-start; + gap: 24px; + } + + .entry-list-item .item-body { + .item-image { + float: right; + flex: 0 0 150px; + width: 150px; + height: 104px; + margin: 0 0 0 24px; + border-radius: 8px; + } + .entry-preview .item-image { + margin: 0; + } + .entry-preview .item-image.noImage { + display: none; + } + .item-summary { + width: auto; + flex: 1; + } + } + + .entry-list-loading-item .item-image { + float: right; + margin-left: 24px; + margin-right: 0; + } + } + } + + a:focus-visible, + button:focus-visible, + [tabindex="0"]:focus-visible { + outline: 2px solid #357ce6 !important; + outline-offset: 3px; + } + + @media (max-width: 639px) { + padding-inline: 16px; + .entry-list-body:not(.grid-view) .entry-list-item { + padding-block: 20px; + .item-summary .item-title { + font-size: 20px; + } + .entry-actions { + column-gap: 8px; + } + } + } +} + +.feed-navigation { + display: flex; + align-items: flex-start; + justify-content: space-between; + flex-wrap: wrap; + gap: 8px 16px; + min-height: 56px; + padding-block: 4px; + border-bottom: 1px solid var(--border-color); + + .feed-navigation-desktop { + flex: 1; + min-width: 0; + flex-direction: column; + } + .feed-source-tabs, + .feed-sort-tabs { + display: flex; + flex-wrap: wrap; + gap: 0 4px; + margin: 0; + padding: 0; + list-style: none; + li { + min-width: 0; + } + } + .feed-sort-controls, + .feed-navigation-mobile { + align-items: center; + flex-wrap: wrap; + } + .feed-sort-controls { + display: flex; + } + .feed-tab { + border-bottom: 2px solid transparent; + overflow-wrap: anywhere; + } + .feed-tab-selected { + @apply text-blue-dark-sky dark:text-blue-dark-sky; + border-bottom-color: currentColor; + font-weight: 600; + } + .feed-reblog-toggle { + color: var(--feed-muted); + display: flex; + align-items: center; + gap: 10px; + min-height: 44px; + padding: 4px 0; + font-size: 13px; + cursor: pointer; + margin-left: auto; + } + .feed-switch-track { + @apply bg-gray-300 dark:bg-gray-600; + display: inline-flex; + align-items: center; + flex-shrink: 0; + width: 34px; + height: 20px; + padding: 2px; + border-radius: 20px; + } + .feed-switch-thumb { + width: 16px; + height: 16px; + border-radius: 50%; + background: white; + } + [aria-checked="true"] { + .feed-switch-track { + @apply bg-blue-dark-sky; + } + .feed-switch-thumb { + transform: translateX(14px); + } + } +} + +.feed-navbar { + .ecency-navbar-desktop { + max-width: 1440px; + gap: 12px; + padding-inline: 24px; + } + .navbar-search-slot { + max-width: 320px; + min-width: 120px; + flex: 1; + } + .navbar-actions { + gap: 8px; + margin-left: 0; + flex-shrink: 0; + } + .text-menu { + gap: 8px; + flex-shrink: 0; + } + .text-menu a { + border-radius: 0; + padding: 12px 8px; + } + .text-menu a[aria-current="page"] { + background: transparent; + border-bottom: 2px solid #357ce6; + } + .feed-write-button { + white-space: nowrap; + } + + a:focus-visible, + button:focus-visible { + outline: 2px solid #357ce6 !important; + outline-offset: 3px; + } + + @media (max-width: 1279px) { + .ecency-navbar-desktop { + padding-inline: 16px; + gap: 8px; + } + .navbar-actions { + gap: 4px; + } + } +} diff --git a/apps/web/src/app/(dynamicPages)/feed/layout.tsx b/apps/web/src/app/(dynamicPages)/feed/layout.tsx index 545ea64947..070811e60f 100644 --- a/apps/web/src/app/(dynamicPages)/feed/layout.tsx +++ b/apps/web/src/app/(dynamicPages)/feed/layout.tsx @@ -9,15 +9,16 @@ import { Feedback } from "@/features/shared/feedback"; import { Navbar } from "@/features/shared/navbar"; import { ScrollToTop } from "@/features/shared/scroll-to-top"; import { Theme } from "@/features/shared/theme"; +import "./feed-reading.scss"; export default function FeedLayout({ children }: PropsWithChildren) { return ( - <> +
- -
+ +
@@ -33,6 +34,6 @@ export default function FeedLayout({ children }: PropsWithChildren) {
- +
); } diff --git a/apps/web/src/app/_components/entry-index-menu/index.tsx b/apps/web/src/app/_components/entry-index-menu/index.tsx index 4a97750028..c9cf0ce349 100644 --- a/apps/web/src/app/_components/entry-index-menu/index.tsx +++ b/apps/web/src/app/_components/entry-index-menu/index.tsx @@ -18,9 +18,9 @@ import { useFeedMenu } from "@/app/_components/entry-index-menu/use-feed-menu"; -const PILL_CLASS = - "text-gray-steel hover:text-blue-dark-sky rounded-full flex items-center px-3 py-1.5"; -const PILL_SELECTED_CLASS = "bg-blue-dark-sky text-white hover:text-white"; +const TAB_CLASS = + "feed-tab flex items-center px-3 py-3 text-sm text-gray-600 dark:text-gray-400 hover:text-blue-dark-sky"; +const TAB_SELECTED_CLASS = "feed-tab-selected"; export function EntryIndexMenu() { const router = useRouter(); @@ -49,10 +49,6 @@ export function EntryIndexMenu() { [sorts, overflow] ); - const reblogLabel = noReblog - ? i18next.t("entry-filter.filter-with-reblog") - : i18next.t("entry-filter.filter-no-reblog"); - // Logged-out users can't view a community ("/my") feed — fall back to global. useEffect(() => { const fallback = globalFeedFallbackPath(pathname); @@ -85,15 +81,15 @@ export function EntryIndexMenu() { router.push(qs ? `${pathname}?${qs}` : pathname); }, [noReblog, pathname, router, searchParams]); - const renderPill = (item: FeedMenuItem) => ( + const renderTab = (item: FeedMenuItem) => (
  • @@ -103,100 +99,89 @@ export function EntryIndexMenu() { ); const reblogToggle = ( - + ); return ( -
    -
    -
    -
    - {/* Desktop: Source group + Sort group (or reblog toggle for Following) */} -
    - {showSources && ( -
      - {sources.map(renderPill)} -
    - )} - {showSources && ( -
    - )} - {isFollowing ? ( - reblogToggle - ) : ( - <> -
      - {sorts.map(renderPill)} -
    -
    - - -
    - - )} -
    - - {/* Mobile/tablet: Source dropdown + Sort dropdown (or reblog toggle) */} -
    - {showSources && ( - - - - - - {sources.map((item, i) => ( - - {item.label} - - ))} - - - )} - {showSources && ( -
    - )} - {isFollowing ? ( - reblogToggle - ) : ( - - - - - - {[...sorts, ...overflow].map((item, i) => ( - - {item.label} - - ))} - - - )} -
    +
    +
    + {showSources && ( +
      + {sources.map(renderTab)} +
    + )} + {!isFollowing && ( +
    +
      + {sorts.map(renderTab)} +
    + + +
    -
    + )} +
    +
    + {showSources && ( + + + + + + {sources.map((item) => ( + + {item.label} + + ))} + + + )} + {!isFollowing && ( + + + + + + {[...sorts, ...overflow].map((item) => ( + + {item.label} + + ))} + + + )}
    + {isFollowing && reblogToggle}
    ); } diff --git a/apps/web/src/app/_components/my-favorites-widget/index.tsx b/apps/web/src/app/_components/my-favorites-widget/index.tsx index 8d003733d3..ed53b0ac31 100644 --- a/apps/web/src/app/_components/my-favorites-widget/index.tsx +++ b/apps/web/src/app/_components/my-favorites-widget/index.tsx @@ -16,14 +16,13 @@ const MAX_VISIBLE = 5; export function MyFavoritesWidget() { const { activeUser } = useActiveAccount(); const username = activeUser?.username; - const accessToken = useMemo( - () => (username ? getAccessToken(username) : undefined), - [username] - ); + const accessToken = useMemo(() => (username ? getAccessToken(username) : undefined), [username]); - const { data: favorites, isLoading, isError } = useQuery( - getFavoritesQueryOptions(username, accessToken) - ); + const { + data: favorites, + isLoading, + isError + } = useQuery(getFavoritesQueryOptions(username, accessToken)); const [showDialog, setShowDialog] = useState(false); @@ -32,8 +31,8 @@ export function MyFavoritesWidget() { } return ( -
    -
    {i18next.t("my-favorites-widget.title")}
    +
    +

    {i18next.t("my-favorites-widget.title")}

    {isLoading && new Array(MAX_VISIBLE).fill(0).map((_, i) => ( @@ -67,10 +66,9 @@ export function MyFavoritesWidget() {
    {!isLoading && favorites && favorites.length > MAX_VISIBLE && ( + + {i18next.t("top-communities.explore")} +
    + + {i18next.t("top-communities.create-button")} + +
    ); }; diff --git a/apps/web/src/app/_components/trending-tags-card/index.tsx b/apps/web/src/app/_components/trending-tags-card/index.tsx index e5eecb7794..eae6b59458 100644 --- a/apps/web/src/app/_components/trending-tags-card/index.tsx +++ b/apps/web/src/app/_components/trending-tags-card/index.tsx @@ -3,16 +3,16 @@ import { useActiveAccount } from "@/core/hooks/use-active-account"; import { FollowTagChipToggle, useFollowedTags } from "@/features/shared/follow-tag-btn"; -import { TagLink } from "@/features/shared/tag"; +import { makePathTag } from "@/features/shared/tag"; import { getAccessToken } from "@/utils"; -import { Button } from "@ui/button"; import { getTrendingTagsQueryOptions } from "@ecency/sdk"; import { useInfiniteQuery } from "@tanstack/react-query"; import { UilMultiply } from "@tooni/iconscout-unicons-react"; import i18next from "i18next"; import Link from "next/link"; +import { IntentLink } from "@/features/shared/intent-link"; import { useParams, useRouter } from "next/navigation"; -import { Fragment, useCallback, useMemo } from "react"; +import { useCallback, useMemo } from "react"; import "./_index.scss"; export function TrendingTagsCard() { @@ -27,12 +27,13 @@ export function TrendingTagsCard() { const { activeUser } = useActiveAccount(); const username = activeUser?.username; - const accessToken = useMemo( - () => (username ? getAccessToken(username) : undefined), - [username] - ); + const accessToken = useMemo(() => (username ? getAccessToken(username) : undefined), [username]); - const { data: trendingTagsPages } = useInfiniteQuery(getTrendingTagsQueryOptions(250)); + const { + data: trendingTagsPages, + isLoading, + isError + } = useInfiniteQuery(getTrendingTagsQueryOptions(250)); // The user's followed tags come first, then the trending list without them, // so a followed topic is one click away and never listed twice. const { tags: favoriteTags } = useFollowedTags(username, accessToken); @@ -40,7 +41,7 @@ export function TrendingTagsCard() { const trendingTags = useMemo(() => { const first = trendingTagsPages?.pages[0]; if (!first) { - return first; + return pinnedTags; } const pinned = new Set(pinnedTags); return [...pinnedTags, ...first.filter((t) => !pinned.has(t))]; @@ -50,63 +51,54 @@ export function TrendingTagsCard() { router.push("/" + filter + ((activeUser && activeUser.username && "/my") || "")); }, [activeUser, filter, router]); - return ( -
    -

    {i18next.t("trending-tags.title")}

    + // Keep the current topic reachable even when it falls outside the short list. + const visibleTags = useMemo(() => { + const selected = trendingTags?.includes(tag) ? tag : undefined; + return [...new Set([...(selected ? [selected] : []), ...(trendingTags ?? [])])].slice(0, 6); + }, [tag, trendingTags]); -
    - {trendingTags?.slice(0, 30).map((t) => ( - -
    - - <> - {t} - {activeUser && } - {tag === t && ( -
    { - e.stopPropagation(); - e.preventDefault(); - handleUnselection(); - }} - onKeyDown={(e) => { - if (e.key === "Enter" || e.key === " ") { - e.stopPropagation(); - e.preventDefault(); - handleUnselection(); - } - }} - > - -
    - )} - -
    -
    -
    + return ( +
    +

    {i18next.t("trending-tags.title")}

    +
      + {visibleTags.map((t) => ( +
    • + + + {t} + + {activeUser && } + {tag === t && ( + + )} +
    • ))} - {trendingTags?.length === 0 && - Array.from(new Array(30).keys()).map((i) => ( -
      ( +
      - {trendingTags && trendingTags.length > 0 && ( - - - +
    + {isError && visibleTags.length === 0 && ( +

    {i18next.t("g.server-error")}

    )} + + {i18next.t("trending-tags.explore")} +
    ); } diff --git a/apps/web/src/features/i18n/locales/en-US.json b/apps/web/src/features/i18n/locales/en-US.json index 51acc044a6..dac8ecd506 100644 --- a/apps/web/src/features/i18n/locales/en-US.json +++ b/apps/web/src/features/i18n/locales/en-US.json @@ -362,12 +362,14 @@ "filter-year": "This year", "filter-alltime": "All time", "filter-no-reblog": "Exclude reblog", + "show-reblogs": "Show reblogs", "filter-with-reblog": "Include reblog" }, "navbar": { "discover": "Discover", "decks": "Decks", "post": "Create Post", + "write": "Write", "day-theme": "Day Theme", "night-theme": "Night Theme", "address-placeholder": "enter url or search query", @@ -668,6 +670,7 @@ }, "trending-tags": { "title": "Topics", + "explore": "Explore topics", "view-more": "View more" }, "sort-trending-tags": { @@ -3186,6 +3189,8 @@ }, "top-communities": { "title": "Top communities", + "discover-title": "Discover communities", + "explore": "Explore communities", "create-button": "Create your community" }, "feedback-modal": { diff --git a/apps/web/src/features/shared/entry-list-item/entry-list-item-muted-content.tsx b/apps/web/src/features/shared/entry-list-item/entry-list-item-muted-content.tsx index b75c3f8889..3159cf8287 100644 --- a/apps/web/src/features/shared/entry-list-item/entry-list-item-muted-content.tsx +++ b/apps/web/src/features/shared/entry-list-item/entry-list-item-muted-content.tsx @@ -81,7 +81,7 @@ export function EntryListItemMutedContent({ entry: entryProp, isThumbLcp }: Prop
    )} -
    +
    {(!nsfw || showNsfw || globalNsfw) && (
    } > -
    +
    diff --git a/apps/web/src/features/shared/navbar/index.tsx b/apps/web/src/features/shared/navbar/index.tsx index 10d7233395..cb06840834 100644 --- a/apps/web/src/features/shared/navbar/index.tsx +++ b/apps/web/src/features/shared/navbar/index.tsx @@ -25,9 +25,16 @@ interface Props { setStepOne?: () => void; setStepTwo?: () => void; experimental?: boolean; // Use this flag for testing something + readingLayout?: boolean; } -export function Navbar({ setStepOne, setStepTwo, step, experimental = false }: Props) { +export function Navbar({ + setStepOne, + setStepTwo, + step, + experimental = false, + readingLayout = false +}: Props) { const { activeUser } = useActiveAccount(); const [theme, toggleTheme] = useClientTheme(); @@ -103,9 +110,9 @@ export function Navbar({ setStepOne, setStepTwo, step, experimental = false }: P return (
    @@ -124,6 +131,7 @@ export function Navbar({ setStepOne, setStepTwo, step, experimental = false }: P setStepOne={setStepOne} setSmVisible={setSmVisible} experimental={experimental} + readingLayout={readingLayout} /> diff --git a/apps/web/src/features/shared/navbar/navbar-desktop.tsx b/apps/web/src/features/shared/navbar/navbar-desktop.tsx index ef410a9d4d..18beb7708b 100644 --- a/apps/web/src/features/shared/navbar/navbar-desktop.tsx +++ b/apps/web/src/features/shared/navbar/navbar-desktop.tsx @@ -28,6 +28,7 @@ interface Props { mainBarExpanded: boolean; setMainBarExpanded: (v: boolean) => void; experimental?: boolean; // Use this flag for testing something + readingLayout?: boolean; } export function NavbarDesktop({ @@ -36,7 +37,8 @@ export function NavbarDesktop({ setStepOne, mainBarExpanded, setMainBarExpanded, - experimental = false + experimental = false, + readingLayout = false }: Props) { const { activeUser } = useActiveAccount(); const hydrated = useHydrated(); @@ -66,8 +68,7 @@ export function NavbarDesktop({ >
    setMainBarExpanded(true)} />
    - +
    {hydrated && activeUser && ( @@ -150,7 +155,11 @@ export function NavbarDesktop({
    {hydrated && activeUser && ( - + )} 0 ? ( ) : (
    )}
    @@ -172,7 +154,7 @@ export function EntryIndexMenu() { - {[...sorts, ...overflow].map((item) => ( + {[...sorts, ...additionalFilters].map((item) => ( {item.label} diff --git a/apps/web/src/app/_components/entry-index-menu/use-feed-menu.ts b/apps/web/src/app/_components/entry-index-menu/use-feed-menu.ts index 7c686b855e..9f3c34972d 100644 --- a/apps/web/src/app/_components/entry-index-menu/use-feed-menu.ts +++ b/apps/web/src/app/_components/entry-index-menu/use-feed-menu.ts @@ -25,8 +25,8 @@ const SORT_FILTERS: EntryFilter[] = [ * Splits the feed filter bar into two orthogonal axes: * - `sources`: where posts come from — Following, Communities, Global * (Following/Communities require an active user; Global is always present) - * - `sorts`: how Communities/Global posts are ranked — Trending, Hot, New, Top - * - `overflow`: niche views surfaced behind a "more" menu — Muted, Promoted + * - `sorts`: how Communities/Global posts are ranked — Trending, Hot, New, Payout + * - `additionalFilters`: additional feed views — Muted, Promoted * * URL scheme is unchanged: Following → /@user/feed (rewritten to the feed * route), Communities → /{sort}/my, Global → /{sort}. @@ -77,7 +77,7 @@ export function useFeedMenu() { const isGlobal = !isFollowing && !isCommunities && !hasTag; // Sort to carry when switching between Communities and Global. Following has no - // sort, and overflow views (muted/promoted) aren't real sorts, so default to Hot. + // sort, and additional views (muted/promoted) aren't real sorts, so default to Hot. const currentSort = SORT_FILTERS.includes(filter as EntryFilter) ? (filter as EntryFilter) : EntryFilter.hot; @@ -128,7 +128,16 @@ export function useFeedMenu() { }); return items; - }, [activeUser, currentSort, hasTag, isCommunities, isFollowing, isGlobal, normalizedTag, router]); + }, [ + activeUser, + currentSort, + hasTag, + isCommunities, + isFollowing, + isGlobal, + normalizedTag, + router + ]); const sorts: FeedMenuItem[] = useMemo(() => { // Preserve the current source context when changing sort: @@ -145,9 +154,7 @@ export function useFeedMenu() { const href = `/${x}${tagSegment ? `/${tagSegment}` : ""}`; return { label: - x === EntryFilter.payout - ? i18next.t("entry-filter.filter-top") - : i18next.t(`entry-filter.filter-${x}`), + x === EntryFilter.payout ? i18next.t("g.payout") : i18next.t(`entry-filter.filter-${x}`), href, selected: (filter as EntryFilter) === x, id: x, @@ -156,7 +163,7 @@ export function useFeedMenu() { }); }, [filter, isCommunities, normalizedTag, router]); - const overflow: FeedMenuItem[] = useMemo( + const additionalFilters: FeedMenuItem[] = useMemo( () => [ { label: i18next.t("entry-filter.filter-muted"), @@ -177,7 +184,7 @@ export function useFeedMenu() { ); return useMemo( - () => ({ sources, sorts, overflow, isFollowing }), - [isFollowing, overflow, sorts, sources] + () => ({ sources, sorts, additionalFilters, isFollowing }), + [isFollowing, additionalFilters, sorts, sources] ); } diff --git a/apps/web/src/specs/app/entry-index-menu.spec.tsx b/apps/web/src/specs/app/entry-index-menu.spec.tsx index f46d570613..0420bb7356 100644 --- a/apps/web/src/specs/app/entry-index-menu.spec.tsx +++ b/apps/web/src/specs/app/entry-index-menu.spec.tsx @@ -44,7 +44,7 @@ const SORT = { trending: "entry-filter.filter-trending", hot: "entry-filter.filter-hot", created: "entry-filter.filter-created", - top: "entry-filter.filter-top" + payout: "g.payout" }; describe("EntryIndexMenu — Source × Sort filter bar", () => { @@ -66,8 +66,8 @@ describe("EntryIndexMenu — Source × Sort filter bar", () => { ); expect(screen.getByRole("link", { name: SORT.hot }).getAttribute("href")).toBe("/hot"); expect(screen.getByRole("link", { name: SORT.created }).getAttribute("href")).toBe("/created"); - // "Top" surfaces the payout sort out of the overflow menu. - expect(screen.getByRole("link", { name: SORT.top }).getAttribute("href")).toBe("/payout"); + // The payout label uses the existing singular translation. + expect(screen.getByRole("link", { name: SORT.payout }).getAttribute("href")).toBe("/payout"); }); it("shows Following, Communities and Global as sources once logged in", () => { @@ -99,7 +99,7 @@ describe("EntryIndexMenu — Source × Sort filter bar", () => { expect(screen.getByRole("link", { name: SORT.trending }).getAttribute("href")).toBe( "/trending/my" ); - expect(screen.getByRole("link", { name: SORT.top }).getAttribute("href")).toBe("/payout/my"); + expect(screen.getByRole("link", { name: SORT.payout }).getAttribute("href")).toBe("/payout/my"); // Global drops the tag, keeping the current sort. expect(screen.getByRole("link", { name: SOURCE.global }).getAttribute("href")).toBe( "/trending" @@ -112,7 +112,7 @@ describe("EntryIndexMenu — Source × Sort filter bar", () => { render(); expect(screen.queryByRole("link", { name: SORT.trending })).toBeNull(); - expect(screen.queryByRole("link", { name: SORT.top })).toBeNull(); + expect(screen.queryByRole("link", { name: SORT.payout })).toBeNull(); expect(screen.getByRole("link", { name: SOURCE.following }).getAttribute("aria-current")).toBe( "page" @@ -166,13 +166,18 @@ describe("EntryIndexMenu — Source × Sort filter bar", () => { expect(mockPush).toHaveBeenCalledWith("/feed/@alice"); }); - it("labels the mobile sort trigger with the active overflow filter", () => { - mockSections = ["muted"]; + it.each([ + ["muted", "entry-filter.filter-muted"], + ["promoted", "entry-filter.filter-promoted"], + ["payout", "g.payout"] + ])("marks %s as active in desktop tabs and the mobile trigger", (filter, label) => { + mockSections = [filter]; render(); - // Muted lives in the overflow menu, but the closed mobile trigger must still + // The closed mobile trigger must still // reflect it rather than falling back to the generic "Sort by" label. - expect(screen.getByRole("button", { name: "entry-filter.filter-muted" })).toBeTruthy(); + expect(screen.getByRole("button", { name: label })).toBeTruthy(); + expect(screen.getByRole("link", { name: label })).toHaveAttribute("aria-current", "page"); }); it("shows a hashtag chip, leaves Global unselected and keeps the tag on sorts", () => { @@ -195,7 +200,7 @@ describe("EntryIndexMenu — Source × Sort filter bar", () => { expect(screen.getByRole("link", { name: SORT.hot }).getAttribute("href")).toBe( "/hot/photography" ); - expect(screen.getByRole("link", { name: SORT.top }).getAttribute("href")).toBe( + expect(screen.getByRole("link", { name: SORT.payout }).getAttribute("href")).toBe( "/payout/photography" ); }); @@ -213,12 +218,12 @@ describe("EntryIndexMenu — Source × Sort filter bar", () => { expect(screen.queryByRole("link", { name: SOURCE.communities })).toBeNull(); }); - it("exposes Muted and Promoted behind the More filters menu", () => { + it("exposes Muted and Promoted directly without a More filters menu", () => { setLoggedIn(true); mockSections = ["hot"]; render(); - fireEvent.click(screen.getByRole("button", { name: "entry-filter.more-filters" })); + expect(screen.queryByRole("button", { name: "entry-filter.more-filters" })).toBeNull(); expect( screen.getByRole("link", { name: "entry-filter.filter-muted" }).getAttribute("href") From 6615951f55dee720ecaa92a2ac1059414aa0ebc4 Mon Sep 17 00:00:00 2001 From: feruzm Date: Tue, 8 Sep 2026 06:51:02 +0000 Subject: [PATCH 4/7] Unify profile and community reading layouts with responsive grids --- .../community/[community]/[tag]/loading.tsx | 8 +- .../_components/community-menu/index.tsx | 26 +- .../community/[community]/community.scss | 11 - .../community/[community]/layout.tsx | 13 +- .../feed/[...sections]/entry-index.scss | 14 +- .../feed/[...sections]/loading.tsx | 12 +- .../app/(dynamicPages)/feed/feed-reading.scss | 226 +--------- .../src/app/(dynamicPages)/feed/layout.tsx | 5 +- .../profile/[username]/[section]/loading.tsx | 8 +- .../_components/profile-info/_index.scss | 2 +- .../_components/profile-menu/index.tsx | 24 +- .../[username]/_components/profile-search.tsx | 2 +- .../profile/[username]/layout.tsx | 15 +- .../profile/[username]/loading.tsx | 8 +- .../shared/reading-layout/reading-layout.scss | 416 ++++++++++++++++++ .../reading-layout/reading-list-loading.tsx | 19 + .../features/ui/page-menu/page-menu-link.tsx | 1 + apps/web/src/features/ui/tooltip.tsx | 10 +- .../shared/reading-list-loading.spec.tsx | 21 + 19 files changed, 516 insertions(+), 325 deletions(-) create mode 100644 apps/web/src/features/shared/reading-layout/reading-layout.scss create mode 100644 apps/web/src/features/shared/reading-layout/reading-list-loading.tsx create mode 100644 apps/web/src/specs/features/shared/reading-list-loading.spec.tsx diff --git a/apps/web/src/app/(dynamicPages)/community/[community]/[tag]/loading.tsx b/apps/web/src/app/(dynamicPages)/community/[community]/[tag]/loading.tsx index 55e0116f68..d0a15f11e7 100644 --- a/apps/web/src/app/(dynamicPages)/community/[community]/[tag]/loading.tsx +++ b/apps/web/src/app/(dynamicPages)/community/[community]/[tag]/loading.tsx @@ -1,9 +1,5 @@ -import { EntryListLoadingItem } from "@/features/shared/entry-list-loading-item"; +import { ReadingListLoading } from "@/features/shared/reading-layout/reading-list-loading"; export default function Loading() { - return ( -
    - -
    - ); + return ; } diff --git a/apps/web/src/app/(dynamicPages)/community/[community]/_components/community-menu/index.tsx b/apps/web/src/app/(dynamicPages)/community/[community]/_components/community-menu/index.tsx index 8e14977a07..4d5cafd692 100644 --- a/apps/web/src/app/(dynamicPages)/community/[community]/_components/community-menu/index.tsx +++ b/apps/web/src/app/(dynamicPages)/community/[community]/_components/community-menu/index.tsx @@ -33,7 +33,10 @@ export const CommunityMenu = (props: Props) => { if (filter === EntryFilter.trending) { newLabel = i18next.t("community.posts"); } else if (menuItems.some((item) => item === filter)) { - newLabel = i18next.t(`entry-filter.filter-${filter}`); + newLabel = + filter === EntryFilter.payout + ? i18next.t("g.payout") + : i18next.t(`entry-filter.filter-${filter}`); } else if (label && !newLabel) { newLabel = label; } else { @@ -45,22 +48,23 @@ export const CommunityMenu = (props: Props) => { const isFilterInItems = () => menuItems.some((item) => filter === item); return ( - + {menuItems.map((x) => ( - - {i18next.t(`entry-filter.filter-${x}`)} - + + {x === EntryFilter.payout + ? i18next.t("g.payout") + : i18next.t(`entry-filter.filter-${x}`)} + ))} - + {menuItems .map((x) => ({ - label: i18next.t(`entry-filter.filter-${x}`), + label: + x === EntryFilter.payout + ? i18next.t("g.payout") + : i18next.t(`entry-filter.filter-${x}`), href: `/${x}/${props.community.name}`, selected: filter === x })) diff --git a/apps/web/src/app/(dynamicPages)/community/[community]/community.scss b/apps/web/src/app/(dynamicPages)/community/[community]/community.scss index 25e04ec530..b25e5510e4 100644 --- a/apps/web/src/app/(dynamicPages)/community/[community]/community.scss +++ b/apps/web/src/app/(dynamicPages)/community/[community]/community.scss @@ -22,16 +22,5 @@ .content-side { flex-grow: 1; padding-bottom: 60px; - - @media (min-width: $lg-break) { - .entry-list { - .entry-list-body.grid-view { - align-content: stretch; - display: flex; - flex-wrap: wrap; - justify-content: flex-start; - } - } - } } } diff --git a/apps/web/src/app/(dynamicPages)/community/[community]/layout.tsx b/apps/web/src/app/(dynamicPages)/community/[community]/layout.tsx index e658b1e592..2cf6a1fde8 100644 --- a/apps/web/src/app/(dynamicPages)/community/[community]/layout.tsx +++ b/apps/web/src/app/(dynamicPages)/community/[community]/layout.tsx @@ -1,3 +1,4 @@ +import "@/features/shared/reading-layout/reading-layout.scss"; import { Feedback } from "@/features/shared/feedback"; import { Navbar } from "@/features/shared/navbar"; import { ScrollToTop } from "@/features/shared/scroll-to-top"; @@ -28,12 +29,12 @@ export default async function CommunityPageLayout({ children, params }: PropsWit ]); return ( - <> +
    - -
    + +
    {account && communityData && ( @@ -50,10 +51,12 @@ export default async function CommunityPageLayout({ children, params }: PropsWit )}
    {communityData && } - {communityData && account && } + {communityData && account && ( + + )} {children}
    - +
    ); } diff --git a/apps/web/src/app/(dynamicPages)/feed/[...sections]/entry-index.scss b/apps/web/src/app/(dynamicPages)/feed/[...sections]/entry-index.scss index bce6960b75..74ac1e99f6 100644 --- a/apps/web/src/app/(dynamicPages)/feed/[...sections]/entry-index.scss +++ b/apps/web/src/app/(dynamicPages)/feed/[...sections]/entry-index.scss @@ -13,7 +13,6 @@ } @media (min-width: $lg-break) { - } } @@ -33,17 +32,6 @@ border-bottom-color: transparent; } } - - @media (min-width: $lg-break) { - .entry-list { - .entry-list-body.grid-view { - align-content: stretch; - display: flex; - flex-wrap: wrap; - justify-content: flex-start; - } - } - } } .side-menu { @@ -133,4 +121,4 @@ .pt-6 { padding-top: 6rem !important; -} \ No newline at end of file +} diff --git a/apps/web/src/app/(dynamicPages)/feed/[...sections]/loading.tsx b/apps/web/src/app/(dynamicPages)/feed/[...sections]/loading.tsx index 901feea06e..2624ca2ea7 100644 --- a/apps/web/src/app/(dynamicPages)/feed/[...sections]/loading.tsx +++ b/apps/web/src/app/(dynamicPages)/feed/[...sections]/loading.tsx @@ -1,13 +1,5 @@ -import { EntryListLoadingItem } from "@/features/shared/entry-list-loading-item"; -import { LinearProgress } from "@/features/shared/linear-progress"; +import { ReadingListLoading } from "@/features/shared/reading-layout/reading-list-loading"; export default function Loading() { - return ( -
    -
    - - -
    -
    - ); + return ; } diff --git a/apps/web/src/app/(dynamicPages)/feed/feed-reading.scss b/apps/web/src/app/(dynamicPages)/feed/feed-reading.scss index 94685cbebd..fdea5f10de 100644 --- a/apps/web/src/app/(dynamicPages)/feed/feed-reading.scss +++ b/apps/web/src/app/(dynamicPages)/feed/feed-reading.scss @@ -1,26 +1,6 @@ @import "../../../styles/vars_mixins"; -.feed-page { - min-height: 100vh; - display: flow-root; - --feed-muted: #606a74; - - @include themify(night) { - background: #15171b; - --feed-muted: #a8adb6; - --border-color: #30343b; - - .ecency-navbar-desktop { - background: #15171b; - } - .entry-list-body:not(.grid-view) .entry-list-item { - border-bottom-color: #292d33; - } - } -} - -// Keep this treatment on feed routes. Profile lists, Decks, post pages and -// the reader's grid preference continue to use their existing layouts. +// Feed-specific column widths and sidebar treatments. .feed-reading-layout { max-width: 1440px; gap: 28px; @@ -185,155 +165,6 @@ border-top: 1px solid var(--border-color); } - .entry-list-body:not(.grid-view) { - .entry-list-item { - margin-top: 0; - padding: 20px 0; - - .item-header { - margin-bottom: 8px; - flex-wrap: wrap; - overflow: visible; - gap: 4px 12px; - } - - .item-header-main { - flex-shrink: 1; - flex-wrap: wrap; - min-width: 0; - row-gap: 4px; - - .author-part .author-avatar { - min-width: 32px; - min-height: 36px; - margin-right: 8px; - } - .author-part .author, - .author-part .author-name { - font-weight: 500; - } - > a { - min-height: 32px; - max-width: 100%; - } - > a > div { - color: var(--feed-muted); - background: transparent; - border: 0; - padding-inline: 4px; - white-space: normal; - overflow-wrap: anywhere; - } - .date { - color: var(--feed-muted); - } - } - - .item-header-features { - flex-wrap: wrap; - min-width: 0; - margin-bottom: 0; - .reblogged { - flex-shrink: 1; - flex-wrap: wrap; - opacity: 1; - } - &:empty { - display: none; - } - } - - .item-summary { - min-width: 0; - .item-title { - @apply text-gray-900 dark:text-gray-100; - - font-size: 22px; - line-height: 1.35; - max-height: none; - font-weight: 600; - overflow-wrap: anywhere; - @apply line-clamp-2; - } - .item-body { - @apply line-clamp-2; - - color: var(--feed-muted); - font-size: 16px; - line-height: 1.55; - max-height: none; - margin: 6px 0 0; - overflow-wrap: anywhere; - } - } - - .entry-actions { - width: 100%; - max-width: 100%; - flex-wrap: wrap; - gap: 6px 12px; - border: 0; - border-radius: 0; - padding: 8px 0 0; - min-height: 44px; - - .entry-payout:not(.payout-declined):not(.payout-limit-hit), - .entry-votes .inner-btn, - .entry-reblog-btn:not(.reblogged):not(:hover) .inner-btn, - .entry-menu > button:not(:hover), - .entry-menu .dropdown-btn:not(:hover) { - color: var(--feed-muted); - } - - > .border-r { - display: none; - } - > .text-gray-steel { - color: var(--feed-muted); - } - > * { - flex-shrink: 0; - } - } - } - - @media (min-width: 640px) { - .entry-preview { - display: flex; - flex-direction: row; - align-items: flex-start; - gap: 20px; - } - - .entry-list-item .item-body { - .item-image { - float: left; - flex: 0 0 150px; - width: 150px; - height: 104px; - margin: 0 20px 0 0; - border-radius: 8px; - } - .entry-preview .item-image { - margin: 0; - } - .entry-preview .item-image.noImage { - display: none; - } - .item-summary { - width: auto; - flex: 1; - } - } - - .entry-list-loading-item .item-image { - float: left; - margin-left: 0; - margin-right: 20px; - } - } - } - a:focus-visible, button:focus-visible, [tabindex="0"]:focus-visible { @@ -343,15 +174,6 @@ @media (max-width: 639px) { padding-inline: 16px; - .entry-list-body:not(.grid-view) .entry-list-item { - padding-block: 20px; - .item-summary .item-title { - font-size: 20px; - } - .entry-actions { - column-gap: 8px; - } - } } } @@ -436,49 +258,3 @@ } } } - -.feed-navbar { - .ecency-navbar-desktop { - gap: 12px; - } - .navbar-search-slot { - max-width: 320px; - min-width: 120px; - flex: 1; - } - .navbar-actions { - gap: 8px; - margin-left: 0; - flex-shrink: 0; - } - .text-menu { - gap: 8px; - flex-shrink: 0; - } - .text-menu a { - border-radius: 0; - padding: 12px 8px; - } - .text-menu a[aria-current="page"] { - background: transparent; - border-bottom: 2px solid #357ce6; - } - .feed-write-button { - white-space: nowrap; - } - - a:focus-visible, - button:focus-visible { - outline: 2px solid #357ce6 !important; - outline-offset: 3px; - } - - @media (max-width: 1279px) { - .ecency-navbar-desktop { - gap: 8px; - } - .navbar-actions { - gap: 4px; - } - } -} diff --git a/apps/web/src/app/(dynamicPages)/feed/layout.tsx b/apps/web/src/app/(dynamicPages)/feed/layout.tsx index 070811e60f..5a333d00f1 100644 --- a/apps/web/src/app/(dynamicPages)/feed/layout.tsx +++ b/apps/web/src/app/(dynamicPages)/feed/layout.tsx @@ -1,3 +1,4 @@ +import "@/features/shared/reading-layout/reading-layout.scss"; import { TrendingTagsCard } from "@/app/_components/trending-tags-card"; import { EntryIndexMenu } from "@/app/_components/entry-index-menu"; import React, { PropsWithChildren } from "react"; @@ -13,12 +14,12 @@ import "./feed-reading.scss"; export default function FeedLayout({ children }: PropsWithChildren) { return ( -
    +
    -
    +
    diff --git a/apps/web/src/app/(dynamicPages)/profile/[username]/[section]/loading.tsx b/apps/web/src/app/(dynamicPages)/profile/[username]/[section]/loading.tsx index 55e0116f68..d0a15f11e7 100644 --- a/apps/web/src/app/(dynamicPages)/profile/[username]/[section]/loading.tsx +++ b/apps/web/src/app/(dynamicPages)/profile/[username]/[section]/loading.tsx @@ -1,9 +1,5 @@ -import { EntryListLoadingItem } from "@/features/shared/entry-list-loading-item"; +import { ReadingListLoading } from "@/features/shared/reading-layout/reading-list-loading"; export default function Loading() { - return ( -
    - -
    - ); + return ; } diff --git a/apps/web/src/app/(dynamicPages)/profile/[username]/_components/profile-info/_index.scss b/apps/web/src/app/(dynamicPages)/profile/[username]/_components/profile-info/_index.scss index cd78a58e78..97bdd36cbc 100644 --- a/apps/web/src/app/(dynamicPages)/profile/[username]/_components/profile-info/_index.scss +++ b/apps/web/src/app/(dynamicPages)/profile/[username]/_components/profile-info/_index.scss @@ -9,7 +9,7 @@ margin-bottom: 6px; display: flex; align-items: center; - flex-flow: row nowrap; + flex-flow: row wrap; svg { @apply text-white; diff --git a/apps/web/src/app/(dynamicPages)/profile/[username]/_components/profile-menu/index.tsx b/apps/web/src/app/(dynamicPages)/profile/[username]/_components/profile-menu/index.tsx index 1081c5cbbf..066aca416f 100644 --- a/apps/web/src/app/(dynamicPages)/profile/[username]/_components/profile-menu/index.tsx +++ b/apps/web/src/app/(dynamicPages)/profile/[username]/_components/profile-menu/index.tsx @@ -77,7 +77,7 @@ export function ProfileMenu({ username }: Props) { const allMenuItems = [...menuItems, ...kebabMenuItemsAll]; return ( - + item.id === section) @@ -87,16 +87,12 @@ export function ProfileMenu({ username }: Props) { isSelected={false} > {dropDownMenuItems.map((item) => ( - - {item.label} - + + {item.label} + ))} - + {menuItems.map((menuItem) => ( {kebabMenuItems.map((item) => ( - - {item.label} - + + {item.label} + ))} diff --git a/apps/web/src/app/(dynamicPages)/profile/[username]/_components/profile-search.tsx b/apps/web/src/app/(dynamicPages)/profile/[username]/_components/profile-search.tsx index 0666e67fdf..433293ae40 100644 --- a/apps/web/src/app/(dynamicPages)/profile/[username]/_components/profile-search.tsx +++ b/apps/web/src/app/(dynamicPages)/profile/[username]/_components/profile-search.tsx @@ -57,7 +57,7 @@ export function ProfileSearch({ username }: Props) { return (
    diff --git a/apps/web/src/app/(dynamicPages)/profile/[username]/layout.tsx b/apps/web/src/app/(dynamicPages)/profile/[username]/layout.tsx index bd96be4270..7f8d04eafb 100644 --- a/apps/web/src/app/(dynamicPages)/profile/[username]/layout.tsx +++ b/apps/web/src/app/(dynamicPages)/profile/[username]/layout.tsx @@ -1,3 +1,4 @@ +import "@/features/shared/reading-layout/reading-layout.scss"; import { ProfileCard, ProfileMenu, ProfileSearch } from "./_components"; import { PropsWithChildren } from "react"; import { getAccountFullQueryOptions } from "@ecency/sdk"; @@ -21,17 +22,13 @@ export default async function ProfileLayout({ children, params }: Props) { const account = await prefetchQuery(getAccountFullQueryOptions(username)); return ( - <> +
    - -
    -
    -
    -
    + +
    +
    {account ? : } {account && } @@ -43,6 +40,6 @@ export default async function ProfileLayout({ children, params }: Props) { {children}
    - +
    ); } diff --git a/apps/web/src/app/(dynamicPages)/profile/[username]/loading.tsx b/apps/web/src/app/(dynamicPages)/profile/[username]/loading.tsx index 55e0116f68..d0a15f11e7 100644 --- a/apps/web/src/app/(dynamicPages)/profile/[username]/loading.tsx +++ b/apps/web/src/app/(dynamicPages)/profile/[username]/loading.tsx @@ -1,9 +1,5 @@ -import { EntryListLoadingItem } from "@/features/shared/entry-list-loading-item"; +import { ReadingListLoading } from "@/features/shared/reading-layout/reading-list-loading"; export default function Loading() { - return ( -
    - -
    - ); + return ; } diff --git a/apps/web/src/features/shared/reading-layout/reading-layout.scss b/apps/web/src/features/shared/reading-layout/reading-layout.scss new file mode 100644 index 0000000000..20e3e5d1d8 --- /dev/null +++ b/apps/web/src/features/shared/reading-layout/reading-layout.scss @@ -0,0 +1,416 @@ +@import "../../../styles/vars_mixins"; + +.reading-page { + .profile-page, + .community-page { + max-width: 1200px; + padding-inline: 24px; + gap: 28px; + } + + .profile-page { + padding-top: 96px; + } + + .community-page { + .profile-side { + margin-right: 0; + } + .content-side { + min-width: 0; + width: 100%; + } + } + + .reading-page-menu { + position: relative; + z-index: 15; + flex-wrap: wrap; + align-items: flex-start; + gap: 8px 16px; + padding-block: 4px; + margin-bottom: 16px; + border-bottom: 1px solid var(--border-color); + } + + .reading-profile-search { + background: transparent; + padding: 16px 0; + border-radius: 0; + } + + .reading-page-tabs { + flex-wrap: wrap; + gap: 4px; + padding: 0; + background: transparent; + border-radius: 0; + + > a { + padding: 12px 8px; + border-bottom: 2px solid transparent; + border-radius: 0; + overflow-wrap: anywhere; + } + > a[aria-current="page"] { + @apply text-blue-dark-sky; + + background: transparent; + border-bottom-color: currentcolor; + } + } + + @media (max-width: 767px) { + .profile-page, + .community-page { + padding-inline: 16px; + gap: 20px; + } + .profile-page { + padding-top: 72px; + } + } +} + +.reading-page { + min-height: 100vh; + display: flow-root; + --feed-muted: #606a74; + + @include themify(night) { + background: #15171b; + --feed-muted: #a8adb6; + --border-color: #30343b; + + .ecency-navbar-desktop { + background: #15171b; + } + .entry-list-body:not(.grid-view) .entry-list-item { + border-bottom-color: #292d33; + } + } +} + +// Shared post and loading layouts, using the same row/grid preference. +.reading-page .reading-list-layout { + .entry-list-body.grid-view { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 16px; + + > .linear-progress { + grid-column: 1 / -1; + } + + .entry-list-item, + .entry-list-loading-item { + width: 100%; + min-width: 0; + margin: 0; + padding: 16px; + border: 1px solid var(--border-color); + border-radius: 12px; + } + + .entry-list-item { + background: transparent; + } + + .entry-list-item .item-image, + .entry-list-loading-item .item-image { + float: none; + width: 100%; + height: 200px; + min-height: 0; + margin: 0 0 10px; + } + + .entry-list-loading-item { + .item-header, + .item-summary .item-title { + max-width: 100%; + } + } + + .entry-actions { + flex-wrap: wrap; + width: 100%; + max-width: 100%; + gap: 6px 8px; + border: 0; + padding: 8px 0 0; + } + + @media (max-width: 639px) { + grid-template-columns: minmax(0, 1fr); + } + } + + .entry-list-body:not(.grid-view) { + .entry-list-item { + background: transparent; + border-radius: 0; + margin-top: 0; + padding: 20px 0; + + .item-header { + margin-bottom: 8px; + flex-wrap: wrap; + overflow: visible; + gap: 4px 12px; + } + + .item-header-main { + flex-shrink: 1; + flex-wrap: wrap; + min-width: 0; + row-gap: 4px; + + .author-part .author-avatar { + min-width: 32px; + min-height: 36px; + margin-right: 8px; + } + .author-part .author, + .author-part .author-name { + font-weight: 500; + } + > a { + min-height: 32px; + max-width: 100%; + } + > a > div { + color: var(--feed-muted); + background: transparent; + border: 0; + padding-inline: 4px; + white-space: normal; + overflow-wrap: anywhere; + } + .date { + color: var(--feed-muted); + } + } + + .item-header-features { + flex-wrap: wrap; + min-width: 0; + margin-bottom: 0; + .reblogged { + flex-shrink: 1; + flex-wrap: wrap; + opacity: 1; + } + &:empty { + display: none; + } + } + + .item-summary { + min-width: 0; + .item-title { + @apply text-gray-900 dark:text-gray-100; + + font-size: 22px; + line-height: 1.35; + max-height: none; + font-weight: 600; + overflow-wrap: anywhere; + @apply line-clamp-2; + } + .item-body { + @apply line-clamp-2; + + color: var(--feed-muted); + font-size: 16px; + line-height: 1.55; + max-height: none; + margin: 6px 0 0; + overflow-wrap: anywhere; + } + } + + .entry-actions { + width: 100%; + max-width: 100%; + flex-wrap: wrap; + gap: 6px 12px; + border: 0; + border-radius: 0; + padding: 8px 0 0; + min-height: 44px; + + .entry-payout:not(.payout-declined):not(.payout-limit-hit), + .entry-votes .inner-btn, + .entry-reblog-btn:not(.reblogged):not(:hover) .inner-btn, + .entry-menu > button:not(:hover), + .entry-menu .dropdown-btn:not(:hover) { + color: var(--feed-muted); + } + + > .border-r { + display: none; + } + > .text-gray-steel { + color: var(--feed-muted); + } + > * { + flex-shrink: 0; + } + } + } + + @media (min-width: 640px) { + .entry-preview { + display: flex; + flex-direction: row; + align-items: flex-start; + gap: 20px; + } + + .entry-list-item .item-body { + .item-image { + float: left; + flex: 0 0 150px; + width: 150px; + height: 104px; + margin: 0 20px 0 0; + border-radius: 8px; + } + .entry-preview .item-image { + margin: 0; + } + .entry-preview .item-image.noImage { + display: none; + } + .item-summary { + width: auto; + flex: 1; + } + } + } + + .entry-list-loading-item { + margin-top: 0; + padding: 20px 0; + + .item-header { + width: min(300px, 70%); + height: 36px; + margin-bottom: 8px; + } + + > .item-body { + display: grid; + grid-template-columns: minmax(0, 1fr); + + > .item-image { + float: none; + width: 100%; + height: auto; + aspect-ratio: 16 / 9; + margin: 0 0 10px; + border-radius: 8px; + } + + > .item-summary { + float: none; + width: auto; + min-width: 0; + + .item-title { + width: 70%; + height: 27px; + } + } + + > .item-controls { + float: none; + grid-column: 1 / -1; + display: flex; + align-items: center; + gap: 12px; + width: 100%; + min-height: 44px; + padding-top: 8px; + + > div { + float: none; + width: min(85px, 20%); + margin: 0; + } + } + } + + @media (min-width: 640px) { + > .item-body { + grid-template-columns: 150px minmax(0, 1fr); + column-gap: 20px; + + > .item-image { + height: 104px; + margin: 0; + } + } + } + } + } + + @media (max-width: 639px) { + .entry-list-body:not(.grid-view) .entry-list-item { + padding-block: 20px; + .item-summary .item-title { + font-size: 20px; + } + .entry-actions { + column-gap: 8px; + } + } + } +} + +.feed-navbar { + .ecency-navbar-desktop { + gap: 12px; + } + .navbar-search-slot { + max-width: 320px; + min-width: 120px; + flex: 1; + } + .navbar-actions { + gap: 8px; + margin-left: 0; + flex-shrink: 0; + } + .text-menu { + gap: 8px; + flex-shrink: 0; + } + .text-menu a { + border-radius: 0; + padding: 12px 8px; + } + .text-menu a[aria-current="page"] { + background: transparent; + border-bottom: 2px solid #357ce6; + } + .feed-write-button { + white-space: nowrap; + } + + a:focus-visible, + button:focus-visible { + outline: 2px solid #357ce6 !important; + outline-offset: 3px; + } + + @media (max-width: 1279px) { + .ecency-navbar-desktop { + gap: 8px; + } + .navbar-actions { + gap: 4px; + } + } +} diff --git a/apps/web/src/features/shared/reading-layout/reading-list-loading.tsx b/apps/web/src/features/shared/reading-layout/reading-list-loading.tsx new file mode 100644 index 0000000000..4182a1df7e --- /dev/null +++ b/apps/web/src/features/shared/reading-layout/reading-list-loading.tsx @@ -0,0 +1,19 @@ +"use client"; + +import { useGlobalStore } from "@/core/global-store"; +import { ListStyle } from "@/enums"; +import { EntryListLoadingItem } from "@/features/shared/entry-list-loading-item"; +import { LinearProgress } from "@/features/shared/linear-progress"; + +export function ReadingListLoading({ showProgress = false }: { showProgress?: boolean }) { + const listStyle = useGlobalStore((state) => state.listStyle); + + return ( +
    +
    + {showProgress && } + +
    +
    + ); +} diff --git a/apps/web/src/features/ui/page-menu/page-menu-link.tsx b/apps/web/src/features/ui/page-menu/page-menu-link.tsx index 03a29c2e2a..4242135d4e 100644 --- a/apps/web/src/features/ui/page-menu/page-menu-link.tsx +++ b/apps/web/src/features/ui/page-menu/page-menu-link.tsx @@ -15,6 +15,7 @@ export function PageMenuLink({ isSelected, href, label }: Props) { "text-gray-steel py-1 px-2 text-sm items-center hover:text-blue-dark-sky flex": true, "bg-blue-dark-sky text-white rounded-xl hover:text-white": isSelected })} + aria-current={isSelected ? "page" : undefined} href={href!} > {label} diff --git a/apps/web/src/features/ui/tooltip.tsx b/apps/web/src/features/ui/tooltip.tsx index 147105ded1..5564c83ec2 100644 --- a/apps/web/src/features/ui/tooltip.tsx +++ b/apps/web/src/features/ui/tooltip.tsx @@ -1,7 +1,7 @@ "use client"; import { offset } from "@floating-ui/dom"; -import { flip, useFloating } from "@floating-ui/react-dom"; +import { flip, shift, useFloating } from "@floating-ui/react-dom"; import { safeAutoUpdate } from "@ui/util"; import clsx from "clsx"; import React, { JSX, ReactNode, useEffect, useState } from "react"; @@ -49,7 +49,7 @@ export function StyledTooltip({ const { refs, floatingStyles } = useFloating({ whileElementsMounted: safeAutoUpdate, placement: "bottom", - middleware: [flip(), offset({ mainAxis: 4 })] + middleware: [flip(), offset({ mainAxis: 4 }), shift({ padding: 8 })] }); const [portalContainer, setPortalContainer] = useState(null); @@ -83,7 +83,11 @@ export function StyledTooltip({
    { + afterEach(() => useGlobalStore.getState().setListStyle(ListStyle.row)); + + it("uses the selected grid preference and follows changes back to rows", () => { + useGlobalStore.getState().setListStyle(ListStyle.grid); + const { container } = render(); + expect(container.querySelector(".entry-list-body.grid-view")).not.toBeNull(); + expect(container.querySelectorAll(".entry-list-loading-item")).toHaveLength(6); + expect(container.querySelector(".linear-progress")).not.toBeNull(); + act(() => useGlobalStore.getState().setListStyle(ListStyle.row)); + expect(container.querySelector(".entry-list-body.grid-view")).toBeNull(); + expect(container.querySelectorAll(".entry-list-loading-item")).toHaveLength(6); + }); +}); From 1a1f9c1e8bc036e03783e082a749939d879783fe Mon Sep 17 00:00:00 2001 From: feruzm Date: Tue, 8 Sep 2026 07:14:30 +0000 Subject: [PATCH 5/7] Reduce outer margins on profile and community pages --- .../src/features/shared/reading-layout/reading-layout.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/src/features/shared/reading-layout/reading-layout.scss b/apps/web/src/features/shared/reading-layout/reading-layout.scss index 20e3e5d1d8..f2cc1547a3 100644 --- a/apps/web/src/features/shared/reading-layout/reading-layout.scss +++ b/apps/web/src/features/shared/reading-layout/reading-layout.scss @@ -3,8 +3,8 @@ .reading-page { .profile-page, .community-page { - max-width: 1200px; - padding-inline: 24px; + max-width: 1600px; + padding-inline: 16px; gap: 28px; } From 60a9194d73b7d0bd105bdd6cdacaa3c73032a510 Mon Sep 17 00:00:00 2001 From: feruzm Date: Tue, 8 Sep 2026 07:39:15 +0000 Subject: [PATCH 6/7] Clean up obsolete feed styles and strengthen accessible label tests --- .../profile/[username]/layout.tsx | 2 +- apps/web/src/features/i18n/locales/en-US.json | 1 - .../src/specs/app/trending-tags-card.spec.tsx | 4 +-- .../shared/navbar-perks-button.spec.tsx | 29 +++++++++++-------- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/apps/web/src/app/(dynamicPages)/profile/[username]/layout.tsx b/apps/web/src/app/(dynamicPages)/profile/[username]/layout.tsx index 7f8d04eafb..917ec24074 100644 --- a/apps/web/src/app/(dynamicPages)/profile/[username]/layout.tsx +++ b/apps/web/src/app/(dynamicPages)/profile/[username]/layout.tsx @@ -27,7 +27,7 @@ export default async function ProfileLayout({ children, params }: Props) { -
    +
    {account ? : } diff --git a/apps/web/src/features/i18n/locales/en-US.json b/apps/web/src/features/i18n/locales/en-US.json index f410185d5b..8c78b172e1 100644 --- a/apps/web/src/features/i18n/locales/en-US.json +++ b/apps/web/src/features/i18n/locales/en-US.json @@ -348,7 +348,6 @@ "filter-community": "My Community", "source-label": "Feed source", "sort-label": "Sort by", - "more-filters": "More filters", "filter-controversial": "Controversial", "filter-rising": "Rising", "filter-global-part1": "This feed contains ", diff --git a/apps/web/src/specs/app/trending-tags-card.spec.tsx b/apps/web/src/specs/app/trending-tags-card.spec.tsx index 0034f8d891..eab9c20507 100644 --- a/apps/web/src/specs/app/trending-tags-card.spec.tsx +++ b/apps/web/src/specs/app/trending-tags-card.spec.tsx @@ -1,4 +1,4 @@ -import { screen, fireEvent, waitFor } from "@testing-library/react"; +import { screen, fireEvent, waitFor, within } from "@testing-library/react"; import "@testing-library/jest-dom"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { renderWithQueryClient } from "@/specs/test-utils"; @@ -63,7 +63,7 @@ vi.mock("@ecency/sdk", async () => { // Topic links are separate from their follow and dismiss actions. const chipTexts = () => - Array.from(document.querySelectorAll(".feed-topic-list a")).map((a) => + within(screen.getByRole("list")).queryAllByRole("link").map((a) => (a.textContent ?? "").replace(/^#/, "").trim() ); diff --git a/apps/web/src/specs/features/shared/navbar-perks-button.spec.tsx b/apps/web/src/specs/features/shared/navbar-perks-button.spec.tsx index f31de4e0ce..890602a459 100644 --- a/apps/web/src/specs/features/shared/navbar-perks-button.spec.tsx +++ b/apps/web/src/specs/features/shared/navbar-perks-button.spec.tsx @@ -1,7 +1,8 @@ import { vi } from "vitest"; import React from "react"; import i18next from "i18next"; -import { render } from "@testing-library/react"; +import { render, screen } from "@testing-library/react"; +import english from "@/features/i18n/locales/en-US.json"; import "@testing-library/jest-dom"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; @@ -99,17 +100,21 @@ describe("NavbarPerksButton", () => { const { container } = renderWith({ current: 6, best: 6, at_risk: false }); expect(dot(container)).toBeNull(); }); - test("the subdued feed variant preserves the streak, destination, and at-risk signal", () => { + test("the subdued feed variant preserves the streak, destination, and at-risk signal", async () => { markPerksSeen(); - const { container } = renderWith({ current: 150, best: 200, at_risk: true }, true); - expect(control(container)).toHaveAttribute("href", "/perks"); - expect(control(container)).toHaveAttribute("aria-label", "navbar.perks-streak"); - expect(i18next.t).toHaveBeenCalledWith("navbar.perks-streak", { - label: "user-nav.perks", - streak: "perks.quests.streak" - }); - expect(container.textContent).toContain("99+"); - expect(control(container).className).toContain("text-orange-500"); - expect(dot(container)).not.toBeNull(); + const { createInstance } = await vi.importActual("i18next"); + const translations = createInstance(); + await translations.init({ lng: "en-US", resources: { "en-US": { translation: english } } }); + const translate = vi.spyOn(i18next, "t").mockImplementation(translations.t); + try { + const { container } = renderWith({ current: 150, best: 200, at_risk: true }, true); + const link = screen.getByRole("link", { name: "Perks: 150-day streak", exact: true }); + expect(link).toHaveAttribute("href", "/perks"); + expect(link).toHaveTextContent("99+"); + expect(link).toHaveClass("text-orange-500"); + expect(dot(container)).not.toBeNull(); + } finally { + translate.mockRestore(); + } }); }); From c5bf116d6670dfc46fcff307209eb63cb16517a7 Mon Sep 17 00:00:00 2001 From: feruzm Date: Tue, 8 Sep 2026 07:50:37 +0000 Subject: [PATCH 7/7] Use a consistent shared navbar across application pages --- .../community/[community]/layout.tsx | 2 +- .../[category]/[author]/[permlink]/layout.tsx | 2 +- .../src/app/(dynamicPages)/feed/layout.tsx | 2 +- .../profile/[username]/layout.tsx | 2 +- apps/web/src/app/communities/layout.tsx | 2 +- apps/web/src/app/discover/layout.tsx | 2 +- apps/web/src/app/gift/layout.tsx | 2 +- apps/web/src/app/hosting/layout.tsx | 2 +- apps/web/src/app/newsletter/layout.tsx | 2 +- apps/web/src/app/perks/layout.tsx | 2 +- apps/web/src/app/publish/layout.tsx | 2 +- .../_components/signup-layout-client.tsx | 2 +- .../src/app/wallet/setup-external/page.tsx | 2 +- apps/web/src/app/waves/layout.tsx | 2 +- .../src/features/shared/navbar/_index.scss | 57 +++++++++++++++++++ apps/web/src/features/shared/navbar/index.tsx | 19 +------ .../features/shared/navbar/navbar-desktop.tsx | 25 +++----- .../shared/navbar/navbar-text-menu.tsx | 42 ++++++-------- .../shared/reading-layout/reading-layout.scss | 49 ---------------- 19 files changed, 98 insertions(+), 122 deletions(-) diff --git a/apps/web/src/app/(dynamicPages)/community/[community]/layout.tsx b/apps/web/src/app/(dynamicPages)/community/[community]/layout.tsx index 2cf6a1fde8..c49e83b31e 100644 --- a/apps/web/src/app/(dynamicPages)/community/[community]/layout.tsx +++ b/apps/web/src/app/(dynamicPages)/community/[community]/layout.tsx @@ -33,7 +33,7 @@ export default async function CommunityPageLayout({ children, params }: PropsWit - +
    {account && communityData && ( diff --git a/apps/web/src/app/(dynamicPages)/entry/[category]/[author]/[permlink]/layout.tsx b/apps/web/src/app/(dynamicPages)/entry/[category]/[author]/[permlink]/layout.tsx index cc9f1b4a88..810c398da7 100644 --- a/apps/web/src/app/(dynamicPages)/entry/[category]/[author]/[permlink]/layout.tsx +++ b/apps/web/src/app/(dynamicPages)/entry/[category]/[author]/[permlink]/layout.tsx @@ -11,7 +11,7 @@ export default function EntryPageLayout(props: PropsWithChildren) { - + {props.children} ); diff --git a/apps/web/src/app/(dynamicPages)/feed/layout.tsx b/apps/web/src/app/(dynamicPages)/feed/layout.tsx index 5a333d00f1..f1d07fe5bb 100644 --- a/apps/web/src/app/(dynamicPages)/feed/layout.tsx +++ b/apps/web/src/app/(dynamicPages)/feed/layout.tsx @@ -18,7 +18,7 @@ export default function FeedLayout({ children }: PropsWithChildren) { - +
    diff --git a/apps/web/src/app/(dynamicPages)/profile/[username]/layout.tsx b/apps/web/src/app/(dynamicPages)/profile/[username]/layout.tsx index 917ec24074..6e6e86aba1 100644 --- a/apps/web/src/app/(dynamicPages)/profile/[username]/layout.tsx +++ b/apps/web/src/app/(dynamicPages)/profile/[username]/layout.tsx @@ -26,7 +26,7 @@ export default async function ProfileLayout({ children, params }: Props) { - +
    {account ? : } diff --git a/apps/web/src/app/communities/layout.tsx b/apps/web/src/app/communities/layout.tsx index b51c037e0f..9df68b1335 100644 --- a/apps/web/src/app/communities/layout.tsx +++ b/apps/web/src/app/communities/layout.tsx @@ -17,7 +17,7 @@ export default function Layout(props: PropsWithChildren) { <> - +
    diff --git a/apps/web/src/app/discover/layout.tsx b/apps/web/src/app/discover/layout.tsx index 3eb9e78a0e..1022f9dfdc 100644 --- a/apps/web/src/app/discover/layout.tsx +++ b/apps/web/src/app/discover/layout.tsx @@ -23,7 +23,7 @@ export default function Layout( - +
    - +
    {children}
    ); diff --git a/apps/web/src/app/hosting/layout.tsx b/apps/web/src/app/hosting/layout.tsx index e6a47d7696..1f96d00c81 100644 --- a/apps/web/src/app/hosting/layout.tsx +++ b/apps/web/src/app/hosting/layout.tsx @@ -10,7 +10,7 @@ export default function Layout({ children }: PropsWithChildren) { return (
    - +
    {children}
    ); diff --git a/apps/web/src/app/newsletter/layout.tsx b/apps/web/src/app/newsletter/layout.tsx index c383ec9208..fc184e18c6 100644 --- a/apps/web/src/app/newsletter/layout.tsx +++ b/apps/web/src/app/newsletter/layout.tsx @@ -7,7 +7,7 @@ export default function Layout({ children }: PropsWithChildren) { return (
    - +
    {children}
    ); diff --git a/apps/web/src/app/perks/layout.tsx b/apps/web/src/app/perks/layout.tsx index 68a57561fd..8a4bca33cd 100644 --- a/apps/web/src/app/perks/layout.tsx +++ b/apps/web/src/app/perks/layout.tsx @@ -7,7 +7,7 @@ export default function Layout({ children }: PropsWithChildren) { return (
    - +
    {children} diff --git a/apps/web/src/app/publish/layout.tsx b/apps/web/src/app/publish/layout.tsx index b23464a687..8802f5ac57 100644 --- a/apps/web/src/app/publish/layout.tsx +++ b/apps/web/src/app/publish/layout.tsx @@ -14,7 +14,7 @@ export default function Layout({ children }: PropsWithChildren) { - +
    {children}
    diff --git a/apps/web/src/app/signup/_components/signup-layout-client.tsx b/apps/web/src/app/signup/_components/signup-layout-client.tsx index 21655fcef9..bb1a19e1d2 100644 --- a/apps/web/src/app/signup/_components/signup-layout-client.tsx +++ b/apps/web/src/app/signup/_components/signup-layout-client.tsx @@ -31,7 +31,7 @@ export function SignupLayoutClient({ children }: PropsWithChildren) { return (
    - + {/* Hoisted into by React. The hero below must stay lazy: its container is display:none under md, and a lazy image with no box diff --git a/apps/web/src/app/wallet/setup-external/page.tsx b/apps/web/src/app/wallet/setup-external/page.tsx index 8653f42ff3..9fcec79a81 100644 --- a/apps/web/src/app/wallet/setup-external/page.tsx +++ b/apps/web/src/app/wallet/setup-external/page.tsx @@ -50,7 +50,7 @@ export default function WalletSetupExternalPage() { - +
    diff --git a/apps/web/src/app/waves/layout.tsx b/apps/web/src/app/waves/layout.tsx index b53686de46..94c364c041 100644 --- a/apps/web/src/app/waves/layout.tsx +++ b/apps/web/src/app/waves/layout.tsx @@ -64,7 +64,7 @@ export default function WavesLayout(props: PropsWithChildren) {
    - +
    {isWaveDetails && waveAuthor ? ( diff --git a/apps/web/src/features/shared/navbar/_index.scss b/apps/web/src/features/shared/navbar/_index.scss index 3891c92fb2..2757503c8c 100644 --- a/apps/web/src/features/shared/navbar/_index.scss +++ b/apps/web/src/features/shared/navbar/_index.scss @@ -52,3 +52,60 @@ body.is-inapp-browser .profile-page { padding-bottom: 0; } } + +.ecency-navbar { + .navbar-desktop-shell { + background: #fff; + border-bottom: 1px solid #e5e7eb; + + @include themify(night) { + background: #15171b; + border-bottom-color: #30343b; + } + } + + .ecency-navbar-desktop { + gap: 12px; + } + .navbar-search-slot { + max-width: 320px; + min-width: 120px; + flex: 1; + } + .navbar-actions { + gap: 8px; + margin-left: 0; + flex-shrink: 0; + } + .text-menu { + gap: 8px; + flex-shrink: 0; + } + .text-menu a { + border-radius: 0; + padding: 11px 8px; + border-bottom: 2px solid transparent; + } + .text-menu a[aria-current="page"] { + background: transparent; + border-bottom: 2px solid #357ce6; + } + .navbar-write-button { + white-space: nowrap; + } + + a:focus-visible, + button:focus-visible { + outline: 2px solid #357ce6 !important; + outline-offset: 3px; + } + + @media (max-width: 1279px) { + .ecency-navbar-desktop { + gap: 8px; + } + .navbar-actions { + gap: 4px; + } + } +} diff --git a/apps/web/src/features/shared/navbar/index.tsx b/apps/web/src/features/shared/navbar/index.tsx index cb06840834..60a72da06e 100644 --- a/apps/web/src/features/shared/navbar/index.tsx +++ b/apps/web/src/features/shared/navbar/index.tsx @@ -8,7 +8,6 @@ import { NavbarMobile } from "./navbar-mobile"; import { NavbarDesktop } from "./navbar-desktop"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { Theme } from "@/enums"; -import { classNameObject } from "@ui/util"; import { useClientTheme } from "@/api/queries"; import { useActiveAccount } from "@/core/hooks/use-active-account"; @@ -24,17 +23,9 @@ interface Props { step?: number; setStepOne?: () => void; setStepTwo?: () => void; - experimental?: boolean; // Use this flag for testing something - readingLayout?: boolean; } -export function Navbar({ - setStepOne, - setStepTwo, - step, - experimental = false, - readingLayout = false -}: Props) { +export function Navbar({ setStepOne, setStepTwo, step }: Props) { const { activeUser } = useActiveAccount(); const [theme, toggleTheme] = useClientTheme(); @@ -109,11 +100,7 @@ export function Navbar({ return (
    diff --git a/apps/web/src/features/shared/navbar/navbar-desktop.tsx b/apps/web/src/features/shared/navbar/navbar-desktop.tsx index d2a11f3607..6beac68ec7 100644 --- a/apps/web/src/features/shared/navbar/navbar-desktop.tsx +++ b/apps/web/src/features/shared/navbar/navbar-desktop.tsx @@ -27,8 +27,6 @@ interface Props { setSmVisible: (v: boolean) => void; mainBarExpanded: boolean; setMainBarExpanded: (v: boolean) => void; - experimental?: boolean; // Use this flag for testing something - readingLayout?: boolean; } export function NavbarDesktop({ @@ -36,9 +34,7 @@ export function NavbarDesktop({ transparentVerify, setStepOne, mainBarExpanded, - setMainBarExpanded, - experimental = false, - readingLayout = false + setMainBarExpanded }: Props) { const { activeUser } = useActiveAccount(); const hydrated = useHydrated(); @@ -62,22 +58,19 @@ export function NavbarDesktop({ return (