diff --git a/packages/shared/src/components/Feed.module.css b/packages/shared/src/components/Feed.module.css index 89c074beb5..186ea669df 100644 --- a/packages/shared/src/components/Feed.module.css +++ b/packages/shared/src/components/Feed.module.css @@ -2,12 +2,16 @@ grid-template-columns: 100%; } .container { - @screen laptopL { + max-width: 100%; + + @screen desktopL { max-width: calc(20rem * var(--num-cards) + var(--feed-gap) * (var(--num-cards) - 1)); } } .cards { - @screen mobileL { + max-width: 100%; + + @screen desktopL { max-width: calc(20rem * var(--num-cards) + var(--feed-gap) * (var(--num-cards) - 1)); } } diff --git a/packages/shared/src/components/Feed.tsx b/packages/shared/src/components/Feed.tsx index 2a583cf955..60a43f51d9 100644 --- a/packages/shared/src/components/Feed.tsx +++ b/packages/shared/src/components/Feed.tsx @@ -179,9 +179,9 @@ export default function Feed({ const currentSettings = useContext(FeedContext); const { user } = useContext(AuthContext); const { isFallback, query: routerQuery } = useRouter(); - const { openNewTab, spaciness, loadedSettings } = useContext(SettingsContext); + const { openNewTab, loadedSettings } = useContext(SettingsContext); const { isListMode } = useFeedLayout(); - const numCards = currentSettings.numCards[spaciness ?? 'eco']; + const numCards = currentSettings.numCards.eco; const isSquadFeed = feedName === OtherFeedPage.Squad; const { shouldUseListFeedLayout } = useFeedLayout(); const trackedFeedFinish = useRef(false); diff --git a/packages/shared/src/components/MainLayout.tsx b/packages/shared/src/components/MainLayout.tsx index 7c130bee0e..fa11dc460c 100644 --- a/packages/shared/src/components/MainLayout.tsx +++ b/packages/shared/src/components/MainLayout.tsx @@ -196,14 +196,14 @@ function MainLayoutComponent({ />
{isAuthReady && showSidebar && ( diff --git a/packages/shared/src/components/feeds/FeedContainer.tsx b/packages/shared/src/components/feeds/FeedContainer.tsx index ea3ba62fe4..78387222b9 100644 --- a/packages/shared/src/components/feeds/FeedContainer.tsx +++ b/packages/shared/src/components/feeds/FeedContainer.tsx @@ -69,7 +69,9 @@ const cardListClass = { export const getFeedGapPx = { 'gap-2': 8, 'gap-3': 12, + 'gap-4': 16, 'gap-5': 20, + 'gap-6': 24, 'gap-8': 32, 'gap-12': 48, 'gap-14': 56, @@ -87,7 +89,7 @@ export const gapClass = ({ if (isFeedLayoutList) { return ''; } - return isList ? listGaps[space] ?? 'gap-2' : gridGaps[space] ?? 'gap-8'; + return isList ? listGaps[space] ?? 'gap-2' : gridGaps[space] ?? 'gap-4'; }; const cardClass = ({ @@ -149,7 +151,7 @@ export const FeedContainer = ({ }: FeedContainerProps): ReactElement => { const currentSettings = useContext(FeedContext); const { subject } = useToastNotification(); - const { spaciness, loadedSettings } = useContext(SettingsContext); + const { loadedSettings } = useContext(SettingsContext); const { shouldUseListFeedLayout, isListMode } = useFeedLayout(); const isLaptop = useViewSize(ViewSize.Laptop); const { feedName } = useActiveFeedNameContext(); @@ -157,7 +159,7 @@ export const FeedContainer = ({ feedName, }); const router = useRouter(); - const numCards = currentSettings.numCards[spaciness ?? 'eco']; + const numCards = currentSettings.numCards.eco; const isList = (isHorizontal || isListMode) && !shouldUseListFeedLayout ? false @@ -167,14 +169,14 @@ export const FeedContainer = ({ gapClass({ isList, isFeedLayoutList: shouldUseListFeedLayout, - space: spaciness, + space: 'eco', }) ]; const style = { '--num-cards': isHorizontal && isListMode && numCards >= 2 ? 2 : numCards, '--feed-gap': `${feedGapPx / 16}rem`, } as CSSProperties; - const cardContainerStyle = { ...getStyle(isList, spaciness) }; + const cardContainerStyle = { ...getStyle(isList, 'eco') }; const isFinder = router.pathname === '/search/posts'; const isSearch = showSearch && !isFinder; @@ -322,7 +324,7 @@ export const FeedContainer = ({ gapClass({ isList, isFeedLayoutList: shouldUseListFeedLayout, - space: spaciness, + space: 'eco', }), cardClass({ isList, numberOfCards: numCards, isHorizontal }), )} diff --git a/packages/shared/src/components/feeds/FeedNav.tsx b/packages/shared/src/components/feeds/FeedNav.tsx index 81a480c03d..e52421fd12 100644 --- a/packages/shared/src/components/feeds/FeedNav.tsx +++ b/packages/shared/src/components/feeds/FeedNav.tsx @@ -1,11 +1,11 @@ import classNames from 'classnames'; import type { ReactElement } from 'react'; -import React, { useMemo, useState, useTransition } from 'react'; +import React, { useMemo } from 'react'; import { useRouter } from 'next/router'; import { Tab, TabContainer } from '../tabs/TabContainer'; import { useActiveFeedNameContext } from '../../contexts'; import useActiveNav from '../../hooks/useActiveNav'; -import { useEventListener, useFeeds, useViewSize, ViewSize } from '../../hooks'; +import { useFeeds, useViewSize, ViewSize } from '../../hooks'; import usePersistentContext from '../../hooks/usePersistentContext'; import { algorithmsList, @@ -31,7 +31,6 @@ import { SharedFeedPage } from '../utilities'; import PlusMobileEntryBanner from '../banners/PlusMobileEntryBanner'; import { TargetType } from '../../lib/log'; import usePlusEntry from '../../hooks/usePlusEntry'; -import { useAlertsContext } from '../../contexts/AlertContext'; enum FeedNavTab { ForYou = 'For you', @@ -52,17 +51,12 @@ const StickyNavIconWrapper = classed( 'sticky flex h-14 pt-1 -translate-y-16 items-center justify-end bg-gradient-to-r from-transparent via-background-default via-40% to-background-default pr-4', ); -const MIN_SCROLL_BEFORE_HIDING = 60; - function FeedNav(): ReactElement { const router = useRouter(); - const [, startTransition] = useTransition(); - const [isHeaderVisible, setIsHeaderVisible] = useState(true); const { feedName } = useActiveFeedNameContext(); const { sortingEnabled } = useSettingsContext(); const { isSortableFeed } = useFeedName({ feedName }); const { home, bookmarks } = useActiveNav(feedName); - const { alerts } = useAlertsContext(); const isMobile = useViewSize(ViewSize.MobileL); const [selectedAlgo, setSelectedAlgo] = usePersistentContext( DEFAULT_ALGORITHM_KEY, @@ -82,8 +76,6 @@ function FeedNav(): ReactElement { isMobile && ((sortingEnabled && isSortableFeed) || feedName === SharedFeedPage.Custom); - const hasOpportunityAlert = !!alerts.opportunityId; - const urlToTab: Record = useMemo(() => { const customFeeds = sortedFeeds.reduce((acc, { node: feed }) => { const isEditingFeed = @@ -127,45 +119,16 @@ function FeedNav(): ReactElement { isCustomDefaultFeed, ]); - const previousScrollY = React.useRef(0); - - useEventListener(globalThis, 'scroll', () => { - // when scrolled down we should hide the header - // when scrolled up, we should bring it back - const { scrollY } = window; - const shouldHeaderBeVisible = scrollY < previousScrollY.current; - - previousScrollY.current = scrollY; - - if (shouldHeaderBeVisible === isHeaderVisible) { - return; - } - - if (!shouldHeaderBeVisible && scrollY < MIN_SCROLL_BEFORE_HIDING) { - return; - } - - startTransition(() => { - setIsHeaderVisible(shouldHeaderBeVisible); - }); - }); const shouldRenderNav = home || (isMobile && bookmarks); if (!shouldRenderNav || router?.pathname?.startsWith('/posts/[id]')) { return null; } - const headerTransitionClasses = - isMobile && hasOpportunityAlert - ? '-translate-y-[7.5rem] duration-[800ms]' - : '-translate-y-26 duration-[800ms]'; - return (
{isMobile && } diff --git a/packages/shared/src/components/layout/MainLayoutHeader.tsx b/packages/shared/src/components/layout/MainLayoutHeader.tsx index ecb3331fe5..e2c0a30466 100644 --- a/packages/shared/src/components/layout/MainLayoutHeader.tsx +++ b/packages/shared/src/components/layout/MainLayoutHeader.tsx @@ -76,7 +76,7 @@ function MainLayoutHeader({ if (loadedSettings && !isLaptop) { if (isSearchPage) { return ( -
+
{!isSearch && }
@@ -93,7 +93,7 @@ function MainLayoutHeader({ return (
{ const { children } = props; const { isListMode } = useFeedLayout(); - const { spaciness } = useContext(SettingsContext); const { isSearchPageLaptop } = useSearchResultsLayout(); const { @@ -103,7 +101,7 @@ export const SearchResultsLayout = ( gapClass({ isList: true, isFeedLayoutList: false, - space: spaciness, + space: 'eco', }), isListMode ? `flex flex-col` diff --git a/packages/shared/src/components/sidebar/Section.tsx b/packages/shared/src/components/sidebar/Section.tsx index 7a33d1a9b6..7b916085cf 100644 --- a/packages/shared/src/components/sidebar/Section.tsx +++ b/packages/shared/src/components/sidebar/Section.tsx @@ -4,12 +4,12 @@ import React, { useRef } from 'react'; import type { ItemInnerProps, SidebarMenuItem } from './common'; import { NavHeader, NavSection } from './common'; import { SidebarItem } from './SidebarItem'; -import { Button, ButtonSize, ButtonVariant } from '../buttons/Button'; -import { ArrowIcon } from '../icons'; +import { ArrowIcon, PlusIcon } from '../icons'; import type { SettingsFlags } from '../../graphql/settings'; import { useSettingsContext } from '../../contexts/SettingsContext'; import { isNullOrUndefined } from '../../lib/func'; import useSidebarRendered from '../../hooks/useSidebarRendered'; +import Link from '../utilities/Link'; export interface SectionCommonProps extends Pick { @@ -24,6 +24,8 @@ interface SectionProps extends SectionCommonProps { items: SidebarMenuItem[]; isItemsButton: boolean; isAlwaysOpenOnMobile?: boolean; + onAdd?: () => void; + addHref?: string; } export function Section({ @@ -36,6 +38,8 @@ export function Section({ className, flag, isAlwaysOpenOnMobile, + onAdd, + addHref, }: SectionProps): ReactElement { const { flags, updateFlag } = useSettingsContext(); const { sidebarRendered } = useSidebarRendered(); @@ -48,31 +52,72 @@ export function Section({ isVisible.current = !isVisible.current; }; - return ( - - {title && ( - + + {addHref && ( + + + + + + )} + {!addHref && onAdd && ( + + )} +
+ ); + + return ( + + {title && ( + {headerContent} )} - {(isVisible.current || shouldAlwaysBeVisible) && - items.map((item) => ( +
+ {items.map((item) => ( ))} +
); } diff --git a/packages/shared/src/components/sidebar/Sidebar.spec.tsx b/packages/shared/src/components/sidebar/Sidebar.spec.tsx index a79862683d..343671d3f0 100644 --- a/packages/shared/src/components/sidebar/Sidebar.spec.tsx +++ b/packages/shared/src/components/sidebar/Sidebar.spec.tsx @@ -105,7 +105,7 @@ it('should render the sidebar as open by default', async () => { renderComponent(); const section = await screen.findByText('Discover'); expect(section).toBeInTheDocument(); - const sectionTwo = await screen.findByText('Network'); + const sectionTwo = await screen.findByText('Squads'); expect(sectionTwo).toBeInTheDocument(); }); diff --git a/packages/shared/src/components/sidebar/SidebarDesktop.tsx b/packages/shared/src/components/sidebar/SidebarDesktop.tsx index 38851c1850..e5a8554b96 100644 --- a/packages/shared/src/components/sidebar/SidebarDesktop.tsx +++ b/packages/shared/src/components/sidebar/SidebarDesktop.tsx @@ -57,38 +57,52 @@ export const SidebarDesktop = ({
+ + {/* Primary Navigation - Always visible */} + + {/* User Content Sections */} + + {/* Discovery Section */} + { return (
- Menu - +
diff --git a/packages/shared/src/components/sidebar/common.tsx b/packages/shared/src/components/sidebar/common.tsx index 4af7dd80f0..590860ef89 100644 --- a/packages/shared/src/components/sidebar/common.tsx +++ b/packages/shared/src/components/sidebar/common.tsx @@ -48,25 +48,25 @@ interface NavItemProps { } export const navBtnClass = - 'flex flex-1 items-center pl-2 laptop:pl-0 pr-5 laptop:pr-3 h-10 laptop:h-8'; + 'flex flex-1 items-center pl-2 laptop:pl-0 pr-5 laptop:pr-3 h-10 laptop:h-9'; export const SidebarAside = classed( 'aside', - 'flex flex-col z-sidebarOverlay laptop:z-sidebar laptop:-translate-x-0 left-0 bg-background-default border-r border-border-subtlest-tertiary transition-[width,transform] duration-300 ease-in-out group fixed top-0 h-full ', + 'flex flex-col z-sidebarOverlay laptop:z-sidebar laptop:-translate-x-0 left-0 bg-background-default border-r border-border-subtlest-tertiary transition-[width,transform] duration-300 ease-in-out group fixed top-0 h-full', ); export const SidebarScrollWrapper = classed( 'div', 'flex overflow-x-hidden overflow-y-auto flex-col h-full no-scrollbar', ); -export const Nav = classed('nav', 'mb-4'); -export const NavSection = classed('ul', 'mt-0 laptop:mt-4'); +export const Nav = classed('nav', 'flex flex-col pt-1 pb-3'); +export const NavSection = classed('ul', 'flex flex-col'); export const NavHeader = classed( 'li', - 'typo-callout text-text-quaternary h-8 flex items-center font-bold transition-opacity', + 'h-9 flex items-center transition-opacity duration-300', ); const RawNavItem = classed( 'li', - 'flex items-center typo-callout hover:bg-theme-active', + 'flex items-center typo-callout relative transition-colors duration-150', ); export const ListIcon = ({ Icon }: ListIconProps): ReactElement => ( @@ -80,7 +80,7 @@ const ItemInnerIcon = ({ alert, icon, active, - iconClassName = 'relative px-3', + iconClassName = 'relative flex items-center justify-center w-9 h-9', }: ItemInnerIconProps) => { return ( @@ -100,7 +100,7 @@ const ItemInnerIconTooltip = ({ @@ -133,19 +133,19 @@ export const ItemInner = ({ )} {item.title} - {item.rightIcon && ( + {shouldShowLabel && item.rightIcon && ( )} @@ -164,7 +164,7 @@ export const NavItem = forwardRef( className={classNames( className, color || baseClasses, - active && 'bg-theme-active', + active ? 'bg-surface-hover' : 'hover:bg-surface-hover', )} > {children} diff --git a/packages/shared/src/components/sidebar/sections/BookmarkSection.tsx b/packages/shared/src/components/sidebar/sections/BookmarkSection.tsx index 38185ebe63..9cf1cc10bd 100644 --- a/packages/shared/src/components/sidebar/sections/BookmarkSection.tsx +++ b/packages/shared/src/components/sidebar/sections/BookmarkSection.tsx @@ -1,8 +1,8 @@ import type { ReactElement } from 'react'; -import React from 'react'; +import React, { useCallback } from 'react'; import type { SidebarMenuItem } from '../common'; import { ListIcon } from '../common'; -import { ArrowIcon, BookmarkIcon, BriefIcon, PlusIcon } from '../../icons'; +import { ArrowIcon, BookmarkIcon, BriefIcon } from '../../icons'; import { Section } from '../Section'; import { briefingUrl, webappUrl } from '../../../lib/constants'; import { SidebarSettingsFlags } from '../../../graphql/settings'; @@ -31,10 +31,7 @@ export const BookmarkSection = ({ const isLaptop = useViewSize(ViewSize.Laptop); const rightIcon = !isLaptop && (() => ); - const onAddFolderClick = ( - event: React.MouseEvent, - ) => { - event.preventDefault(); + const handleAddFolder = useCallback(() => { openModal({ type: LazyModal.BookmarkFolder, props: { @@ -44,7 +41,7 @@ export const BookmarkSection = ({ }, }, }); - }; + }, [openModal, closeModal, createFolder]); const menuItems: SidebarMenuItem[] = [ briefUIFeatureValue && { @@ -89,16 +86,6 @@ export const BookmarkSection = ({ requiresLogin: true, rightIcon, })), - { - icon: () => ( -
- -
- ), - title: 'New folder', - requiresLogin: true, - action: onAddFolderClick, - }, ].filter(Boolean); return ( @@ -108,6 +95,7 @@ export const BookmarkSection = ({ isItemsButton={isItemsButton} flag={SidebarSettingsFlags.BookmarksExpanded} isAlwaysOpenOnMobile + onAdd={handleAddFolder} /> ); }; diff --git a/packages/shared/src/components/sidebar/sections/CustomFeedSection.tsx b/packages/shared/src/components/sidebar/sections/CustomFeedSection.tsx index f05605521b..1cb8fb5c83 100644 --- a/packages/shared/src/components/sidebar/sections/CustomFeedSection.tsx +++ b/packages/shared/src/components/sidebar/sections/CustomFeedSection.tsx @@ -1,7 +1,7 @@ import type { ReactElement } from 'react'; import React, { useMemo } from 'react'; import type { SidebarMenuItem } from '../common'; -import { HashtagIcon, PlusIcon, StarIcon } from '../../icons'; +import { HashtagIcon, StarIcon } from '../../icons'; import { Section } from '../Section'; import { webappUrl } from '../../../lib/constants'; import { useFeeds } from '../../../hooks'; @@ -60,20 +60,7 @@ export const CustomFeedSection = ({ }; }) ?? []; - return [ - ...customFeeds, - { - icon: () => ( -
- -
- ), - title: 'Custom feed', - path: `${webappUrl}feeds/new`, - requiresLogin: true, - isForcedClickable: true, - }, - ].filter(Boolean); + return customFeeds.filter(Boolean); }, [ defaultRenderSectionProps.activePage, sortedFeeds, @@ -87,6 +74,7 @@ export const CustomFeedSection = ({ items={menuItems} isItemsButton={isItemsButton} flag={SidebarSettingsFlags.CustomFeedsExpanded} + addHref={`${webappUrl}feeds/new`} /> ); }; diff --git a/packages/shared/src/components/sidebar/sections/MainSection.tsx b/packages/shared/src/components/sidebar/sections/MainSection.tsx index 62091202f5..a79b3cb8ea 100644 --- a/packages/shared/src/components/sidebar/sections/MainSection.tsx +++ b/packages/shared/src/components/sidebar/sections/MainSection.tsx @@ -6,6 +6,7 @@ import { ListIcon } from '../common'; import { DevPlusIcon, EyeIcon, + HomeIcon, HotIcon, SquadIcon, YearInReviewIcon, @@ -60,7 +61,14 @@ export const MainSection = ({ ), } - : undefined; + : { + title: 'Home', + path: '/', + action: () => onNavTabClick?.('/'), + icon: (active: boolean) => ( + } /> + ), + }; const plusButton = !isPlus ? { @@ -71,7 +79,8 @@ export const MainSection = ({ path: plusUrl, isForcedLink: true, requiresLogin: true, - color: 'text-accent-avocado-default !bg-action-upvote-float mb-4', + color: + 'text-accent-avocado-default hover:!bg-action-upvote-float !bg-action-upvote-float/50', } : undefined; @@ -80,15 +89,13 @@ export const MainSection = ({ icon: () => } />, title: 'Your 2025 in Review', titleClassName: - 'text-transparent bg-clip-text bg-gradient-to-b from-accent-lettuce-default to-accent-cabbage-default', + 'text-transparent bg-clip-text bg-gradient-to-b from-accent-lettuce-default to-accent-cabbage-default font-bold', path: `${webappUrl}log`, isForcedLink: true, } : undefined; return [ - plusButton, - yearInReview, myFeed, { title: 'Following', @@ -117,6 +124,8 @@ export const MainSection = ({ isForcedLink: true, requiresLogin: true, }, + yearInReview, + plusButton, ].filter(Boolean); }, [ ctaCopy, diff --git a/packages/shared/src/components/sidebar/sections/NetworkSection.tsx b/packages/shared/src/components/sidebar/sections/NetworkSection.tsx index 7d7b2a17da..ca3c246b2e 100644 --- a/packages/shared/src/components/sidebar/sections/NetworkSection.tsx +++ b/packages/shared/src/components/sidebar/sections/NetworkSection.tsx @@ -1,13 +1,8 @@ import type { ReactElement } from 'react'; -import React, { useMemo } from 'react'; +import React, { useCallback, useMemo } from 'react'; import type { SidebarMenuItem } from '../common'; import { ListIcon } from '../common'; -import { - DefaultSquadIcon, - NewSquadIcon, - SourceIcon, - TimerIcon, -} from '../../icons'; +import { DefaultSquadIcon, SourceIcon, TimerIcon } from '../../icons'; import { Section } from '../Section'; import { Origin } from '../../../lib/log'; import { useSquadNavigation } from '../../../hooks'; @@ -30,6 +25,10 @@ export const NetworkSection = ({ status: [SourcePostModerationStatus.Pending], }); + const handleAddSquad = useCallback(() => { + openNewSquad({ origin: Origin.Sidebar }); + }, [openNewSquad]); + const menuItems: SidebarMenuItem[] = useMemo(() => { const squadItems = squads?.map((squad) => { @@ -70,14 +69,8 @@ export const NetworkSection = ({ isForcedLink: true, }, ...squadItems, - { - icon: () => , - title: 'New Squad', - action: () => openNewSquad({ origin: Origin.Sidebar }), - requiresLogin: true, - }, ].filter(Boolean); - }, [squads, isModeratorInAnySquad, count, openNewSquad]); + }, [squads, isModeratorInAnySquad, count]); return (
); }; diff --git a/packages/shared/src/contexts/FeedContext.tsx b/packages/shared/src/contexts/FeedContext.tsx index a2a743348b..a37ceda143 100644 --- a/packages/shared/src/contexts/FeedContext.tsx +++ b/packages/shared/src/contexts/FeedContext.tsx @@ -1,5 +1,5 @@ import type { ReactElement, PropsWithChildren } from 'react'; -import React, { useMemo } from 'react'; +import React, { useMemo, useState, useEffect } from 'react'; import { desktop, laptop, laptopL, laptopXL, tablet } from '../styles/media'; import { useConditionalFeature, useMedia, usePlusSubscription } from '../hooks'; import { useSettingsContext } from './SettingsContext'; @@ -9,6 +9,9 @@ import type { Spaciness } from '../graphql/settings'; import { featureFeedAdTemplate } from '../lib/featureManagement'; import type { FeedAdTemplate } from '../lib/feed'; +// Sidebar animation duration in ms (matches CSS transition in MainLayout) +const SIDEBAR_TRANSITION_DURATION = 300; + export type FeedContextData = { pageSize: number; numCards: Record; @@ -119,6 +122,19 @@ export function FeedLayoutProvider({ shouldEvaluate: !isPlus, }); + // Debounce sidebar expanded state to sync with sidebar CSS transition + // This prevents the feed layout from jumping mid-animation + const [debouncedSidebarExpanded, setDebouncedSidebarExpanded] = + useState(sidebarExpanded); + + useEffect(() => { + const timer = setTimeout(() => { + setDebouncedSidebarExpanded(sidebarExpanded); + }, SIDEBAR_TRANSITION_DURATION); + + return () => clearTimeout(timer); + }, [sidebarExpanded]); + const { feedSettings, defaultFeedSettings } = useMemo(() => { const enhancedFeedSettings = Object.entries(baseFeedSettings).reduce( (acc, [feedSettingsKey, feedSettingsValue]) => { @@ -147,6 +163,7 @@ export function FeedLayoutProvider({ }, [feedAdTemplateFeature.value]); // Generate the breakpoints for the feed settings + // Uses debounced sidebar state to sync layout change with sidebar animation const feedBreakpoints = useMemo(() => { const breakpoints = feedSettings.map((setting) => setting.breakpoint.replace('@media ', ''), @@ -156,7 +173,7 @@ export function FeedLayoutProvider({ return breakpoints; } - if (sidebarExpanded) { + if (debouncedSidebarExpanded) { return breakpoints.map((breakpoint) => replaceDigitsWithIncrement(breakpoint, sidebarOpenWidth), ); @@ -165,7 +182,7 @@ export function FeedLayoutProvider({ return breakpoints.map((breakpoint) => replaceDigitsWithIncrement(breakpoint, sidebarRenderedWidth), ); - }, [feedSettings, sidebarExpanded, sidebarRendered]); + }, [feedSettings, debouncedSidebarExpanded, sidebarRendered]); const currentSettings = useMedia( feedBreakpoints, diff --git a/packages/shared/src/styles/base.css b/packages/shared/src/styles/base.css index f8fd84d37c..c091f996cf 100644 --- a/packages/shared/src/styles/base.css +++ b/packages/shared/src/styles/base.css @@ -730,6 +730,7 @@ img.lazyload:not([src]) { } } +/* Scrollbar - hidden by default, visible on hover (like default macOS behavior) */ ::-webkit-scrollbar { width: 0.5rem; background-color: transparent; @@ -741,17 +742,33 @@ img.lazyload:not([src]) { ::-webkit-scrollbar-thumb { border-radius: 0.25rem; + background: transparent; + transition: background 0.2s ease; +} + +/* Show scrollbar thumb on hover of scrollable container */ +*:hover::-webkit-scrollbar-thumb { background: var(--theme-text-tertiary); } -::-webkit-scrollbar-thumb:hover { +*:hover::-webkit-scrollbar-thumb:hover { background: var(--theme-text-primary); } -::-webkit-scrollbar-thumb:active { +*:hover::-webkit-scrollbar-thumb:active { background: var(--theme-text-primary); } +/* Firefox scrollbar - auto-hide behavior */ +* { + scrollbar-width: thin; + scrollbar-color: transparent transparent; +} + +*:hover { + scrollbar-color: var(--theme-text-tertiary) transparent; +} + summary { list-style: none; diff --git a/packages/webapp/pages/settings/appearance.tsx b/packages/webapp/pages/settings/appearance.tsx index efc58501f7..c44dfe302c 100644 --- a/packages/webapp/pages/settings/appearance.tsx +++ b/packages/webapp/pages/settings/appearance.tsx @@ -7,11 +7,8 @@ import { useSettingsContext } from '@dailydotdev/shared/src/contexts/SettingsCon import { useViewSize, ViewSize } from '@dailydotdev/shared/src/hooks'; import { Typography, - TypographyColor, TypographyType, } from '@dailydotdev/shared/src/components/typography/Typography'; -import type { RadioItemProps } from '@dailydotdev/shared/src/components/fields/Radio'; -import { Radio } from '@dailydotdev/shared/src/components/fields/Radio'; import { ToggleRadio } from '@dailydotdev/shared/src/components/fields/ToggleRadio'; import { useLogContext } from '@dailydotdev/shared/src/contexts/LogContext'; import { @@ -19,7 +16,6 @@ import { TargetId, TargetType, } from '@dailydotdev/shared/src/lib/log'; -import classNames from 'classnames'; import { FlexCol } from '@dailydotdev/shared/src/components/utilities'; import { AccountPageContainer } from '../../components/layouts/SettingsLayout/AccountPageContainer'; import { getSettingsLayout } from '../../components/layouts/SettingsLayout'; @@ -27,19 +23,11 @@ import { defaultSeo } from '../../next-seo'; import { getTemplatedTitle } from '../../components/layouts/utils'; import { SettingsSwitch } from '../../components/layouts/SettingsLayout/common'; -const densities: RadioItemProps[] = [ - { label: 'Eco', value: 'eco' }, - { label: 'Roomy', value: 'roomy' }, - { label: 'Cozy', value: 'cozy' }, -]; - const AccountManageSubscriptionPage = (): ReactElement => { const isLaptop = useViewSize(ViewSize.Laptop); const { logEvent } = useLogContext(); const { - spaciness, - setSpaciness, openNewTab, toggleOpenNewTab, insaneMode, @@ -91,38 +79,6 @@ const AccountManageSubscriptionPage = (): ReactElement => { )} - - - Density - - - {insaneMode && ( - - Not available in list layout - - )} - - - - Preferences