Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 3 additions & 8 deletions app/endowment/components/EndowmentTopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,9 @@ export function EndowmentTopBar() {
opacity 0.25s ease,
transform 0.25s ease;
}
/* Clear the app nav rail, which is 70px from the tablet breakpoint and
240px once it expands at sidebar-compact. Below tablet the rail is
off-canvas, so the bar spans the full width. */
@media (min-width: 768px) {
.endowment-topbar {
left: 70px;
}
}
/* Clear the app's left column, which is 240px from sidebar-compact
up. Narrower, the sidebar is off-canvas, so the bar spans the full
width. */
@media (min-width: 1240px) {
.endowment-topbar {
left: 240px;
Expand Down
8 changes: 4 additions & 4 deletions app/endowment/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ export default function EndowmentLayout({ children }: Readonly<{ children: React
return (
<div className="flex min-h-screen bg-white">
{/* The landing page keeps its own top bar and footer, so it stays outside
PageLayout; the nav rail is mounted directly to preserve continuity
with the rest of the app. Passing `isOpen={false}` leaves it
off-canvas below the tablet breakpoint, where there is no menu button
to reopen it. */}
PageLayout; the app's left column is mounted directly to preserve
continuity with the rest of the app. Passing `isOpen={false}` leaves
it off-canvas below 1240px, where this page has no menu button to
open it. */}
<LeftSidebarContainer isOpen={false} />
<main className="relative min-w-0 flex-1">{children}</main>
</div>
Expand Down
6 changes: 6 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,12 @@ mark {
padding-top: calc(56px + var(--top-bar-height)) !important;
}

/* The menu hangs from the bottom of the same stack. */
.left-sidebar-with-promo-banner {
top: calc(56px + var(--top-bar-height)) !important;
height: calc(100vh - 56px - var(--top-bar-height)) !important;
}

/* Reserve space for the fixed MobileBottomNav (h-16 + safe-area inset), so
the last of the page's content isn't stuck behind it. */
.page-layout-with-mobile-bottom-nav {
Expand Down
27 changes: 10 additions & 17 deletions app/layouts/LeftSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import { SidebarDocuments } from './components/SidebarDocuments';
interface LeftSidebarProps {
forceMinimize?: boolean;
/**
* Runs once a document or a new draft is picked: on a phone the column is a
* menu, and a pick that changes the workspace in place must still close it.
* Runs once something in it is picked: below 1240px the sidebar is a menu,
* and a pick that changes the page in place must still close it.
*/
onNavigate?: () => void;
}
Expand Down Expand Up @@ -49,39 +49,32 @@ export const LeftSidebar: React.FC<LeftSidebarProps> = ({ forceMinimize = false,

return (
<div className={`h-full flex flex-col z-50 bg-white overflow-hidden ${minimizeClass}`}>
<div
className={`p-4 pl-4 ${forceMinimize ? '!flex !justify-center' : 'tablet:max-sidebar-compact:!flex tablet:max-sidebar-compact:!justify-center'} pt-[10px]`}
>
<Link href="/">
<div className={forceMinimize ? '!hidden' : 'tablet:max-sidebar-compact:!hidden ml-1'}>
<div className={`p-4 pl-4 ${forceMinimize ? '!flex !justify-center' : ''} pt-[10px]`}>
<Link href="/" onClick={onNavigate}>
<div className={forceMinimize ? '!hidden' : 'ml-1'}>
<Logo size={38} color="text-primary-600" />
</div>
<div className={forceMinimize ? '!block' : 'hidden tablet:max-sidebar-compact:!block'}>
<div className={forceMinimize ? '!block' : 'hidden'}>
<Icon name="flaskFrame" size={38} color="#3971ff" />
</div>
</Link>
</div>

<div
className={`mt-6 px-3 ${forceMinimize ? '!flex !justify-center !px-2' : 'tablet:max-sidebar-compact:!flex tablet:max-sidebar-compact:!justify-center tablet:max-sidebar-compact:!px-2'}`}
>
<div className={`mt-6 px-3 ${forceMinimize ? '!flex !justify-center !px-2' : ''}`}>
<PublishMenu forceMinimize={forceMinimize} onItemSelected={onNavigate} />
</div>

{/* The nav and the user's documents scroll together, so a long list
never squeezes the nav. The documents hide while the column is the
icon rail; the workspace has its own way to them there. */}
never squeezes the nav. A forced icon rail has no room for them. */}
<div className="min-h-0 flex-1 overflow-y-auto">
<Navigation
currentPath={pathname || ''}
onUnimplementedFeature={handleUnimplementedFeature}
forceMinimize={forceMinimize}
inScrollArea
/>
<SidebarDocuments
onNavigate={onNavigate}
className={forceMinimize ? '!hidden' : 'tablet:max-sidebar-compact:!hidden'}
/>
<SidebarDocuments onNavigate={onNavigate} className={forceMinimize ? '!hidden' : ''} />
</div>

{/* The scroll area above is flex-1, so this sits at the bottom of the column.
Expand All @@ -92,7 +85,7 @@ export const LeftSidebar: React.FC<LeftSidebarProps> = ({ forceMinimize = false,
<FundingPowerRailButton />
</div>

<div className={forceMinimize ? '!hidden' : 'tablet:max-sidebar-compact:!hidden'}>
<div className={forceMinimize ? '!hidden' : ''}>
<FooterLinks />
</div>
</div>
Expand Down
21 changes: 12 additions & 9 deletions app/layouts/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ interface NavigationProps {
forceMinimize?: boolean;
/** A parent scrolls the nav along with what follows it, so it neither grows nor scrolls itself. */
inScrollArea?: boolean;
/** Runs once a link is followed: the menu holding the nav closes. */
onNavigate?: () => void;
}

const navIconMap: Record<NavIconKey, NavIcon> = {
Expand All @@ -60,6 +62,7 @@ export const Navigation: React.FC<NavigationProps> = ({
onUnimplementedFeature,
forceMinimize = false,
inScrollArea = false,
onNavigate,
}) => {
const navigationItems: NavigationItem[] = [
{
Expand Down Expand Up @@ -100,9 +103,7 @@ export const Navigation: React.FC<NavigationProps> = ({
const getButtonStyles = (isActive: boolean) => {
return cn(
'flex w-full items-center rounded-lg px-3 py-2.5 text-[15px] font-medium transition-colors',
forceMinimize
? '!justify-center !px-2'
: 'tablet:max-sidebar-compact:!justify-center tablet:max-sidebar-compact:!px-2',
forceMinimize && '!justify-center !px-2',
isActive
? 'bg-primary-50 font-semibold text-primary-600'
: 'font-medium text-gray-700 hover:bg-gray-50'
Expand Down Expand Up @@ -156,21 +157,23 @@ export const Navigation: React.FC<NavigationProps> = ({

if (item.requiresAuth) {
e.preventDefault();
executeAuthenticatedAction(() => router.push(item.href));
executeAuthenticatedAction(() => {
router.push(item.href);
onNavigate?.();
});
return;
}
onNavigate?.();
};

const isHomeIcon = item.isFontAwesome && item.iconKey === 'home';

const iconContainerClass = cn(
'flex h-[26px] w-[26px] flex-shrink-0 items-center justify-center',
forceMinimize ? 'mr-0' : 'mr-3.5 tablet:max-sidebar-compact:!mr-0'
forceMinimize ? 'mr-0' : 'mr-3.5'
);

const textContainerClass = forceMinimize
? 'hidden'
: 'flex w-full min-w-0 items-center tablet:max-sidebar-compact:!hidden';
const textContainerClass = forceMinimize ? 'hidden' : 'flex w-full min-w-0 items-center';

const label = <span className="min-w-0 truncate">{item.label}</span>;

Expand Down Expand Up @@ -215,7 +218,7 @@ export const Navigation: React.FC<NavigationProps> = ({
className={cn(
'px-3 pt-6',
!inScrollArea && 'flex-1 overflow-y-auto',
forceMinimize ? '!px-2' : 'tablet:max-sidebar-compact:!px-2'
forceMinimize && '!px-2'
)}
>
<div className="space-y-2">
Expand Down
6 changes: 5 additions & 1 deletion app/layouts/PageLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,11 @@ function PageLayoutInner({

<MobileOverlay show={showOverlay} visible={overlayVisible} onClose={closeLeftSidebar} />

<LeftSidebarContainer isOpen={isLeftSidebarOpen} onClose={closeLeftSidebar} />
<LeftSidebarContainer
isOpen={isLeftSidebarOpen}
onClose={closeLeftSidebar}
withPromoBanner={isPromoBannerVisible}
/>

{fullBleed ? (
// Offset from the fixed top bar exactly as the scrollport below is,
Expand Down
6 changes: 3 additions & 3 deletions app/layouts/PublishMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const PublishMenu: React.FC<PublishMenuProps> = ({
// Regular trigger for standard mode
const standardTrigger = (
<button
className={`flex items-center px-5 py-3.5 gap-2.5 text-[15px] font-medium rounded-lg bg-gray-100 hover:bg-gray-50 text-gray-800 shadow-[rgba(0,_0,_0,_0.15)_1.95px_1.95px_2.6px] ${forceMinimize ? '!hidden' : 'tablet:max-sidebar-compact:!hidden'}`}
className={`flex items-center px-5 py-3.5 gap-2.5 text-[15px] font-medium rounded-lg bg-gray-100 hover:bg-gray-50 text-gray-800 shadow-[rgba(0,_0,_0,_0.15)_1.95px_1.95px_2.6px] ${forceMinimize ? '!hidden' : ''}`}
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
Expand All @@ -62,10 +62,10 @@ export const PublishMenu: React.FC<PublishMenuProps> = ({
</button>
);

// Compact trigger for minimized sidebar
// Compact trigger for a sidebar forced into the icon rail
const compactTrigger = (
<button
className={`${forceMinimize ? '' : 'hidden'} tablet:max-sidebar-compact:!flex items-center justify-center p-3 rounded-lg bg-gray-100 hover:bg-gray-50 text-gray-800 shadow-[rgba(0,_0,_0,_0.15)_1.95px_1.95px_2.6px] mx-auto`}
className={`${forceMinimize ? 'flex' : 'hidden'} items-center justify-center p-3 rounded-lg bg-gray-100 hover:bg-gray-50 text-gray-800 shadow-[rgba(0,_0,_0,_0.15)_1.95px_1.95px_2.6px] mx-auto`}
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
Expand Down
25 changes: 14 additions & 11 deletions app/layouts/TopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@
import { useRouter, usePathname } from 'next/navigation';
import { useAuthModalContext } from '@/contexts/AuthModalContext';
import { useNotifications } from '@/contexts/NotificationContext';
import Link from 'next/link';
import { Menu } from 'lucide-react';
import { calculateProfileCompletion } from '@/utils/profileCompletion';
import { Logo } from '@/components/ui/Logo';
import { Tabs } from '@/components/ui/Tabs';
import { useFundTabs } from '@/hooks/useFundTabs';
import { useFeedTabsVisibility } from '@/contexts/FeedTabsVisibilityContext';
Expand All @@ -22,10 +21,12 @@
import { TopBarUserControls } from './topbar/TopBarUserControls';

interface TopBarProps {
/** Opens or closes the app's menu: the left sidebar, below the width at which it is a column. */
onMenuClick: () => void;
readonly isMenuOpen?: boolean;
}

export function TopBar({ onMenuClick }: TopBarProps) {
export function TopBar({ onMenuClick, isMenuOpen = false }: TopBarProps) {

Check warning on line 29 in app/layouts/TopBar.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Mark the props of the component as read-only.

See more on https://sonarcloud.io/project/issues?id=ResearchHub_web&issues=AaEUCTSQ1KXi02Z05J4T&open=AaEUCTSQ1KXi02Z05J4T&pullRequest=1147
const { user, isLoading } = useUser();
const router = useRouter();
const pathname = usePathname();
Expand All @@ -50,7 +51,6 @@
const leftSlot = topBarSlot?.leftSlot;
// Or keep the back arrow and name what the page has open instead of the page.
const openTitle = topBarSlot?.title ?? null;
const leading = topBarSlot?.leading;

const routePageInfo = getPageInfo(pathname);
const pageInfo =
Expand Down Expand Up @@ -89,11 +89,16 @@
<div className="relative flex items-center justify-between px-4 lg:px-8 h-[var(--top-bar-height)]">
{/* Left side */}
<div className="flex items-center min-w-0 flex-1 mr-4 h-full">
<Link href="/" className="block tablet:!hidden mr-2">
<div className="rounded-full bg-gray-100 flex items-center justify-center w-11 h-11">
<Logo noText size={36} className="mt-[-2px]" />
</div>
</Link>
{/* The left sidebar is a column from 1240px up; narrower, this opens it. */}
<button
type="button"
onClick={onMenuClick}
aria-expanded={isMenuOpen}
aria-label={isMenuOpen ? 'Close menu' : 'Open menu'}
className="-ml-2 mr-1 flex h-11 w-11 shrink-0 items-center justify-center rounded-lg text-gray-700 transition-colors hover:bg-gray-100 hover:text-gray-900 sidebar-compact:!hidden"
>
<Menu className="h-6 w-6" aria-hidden="true" />
</button>

{leftSlot ? (
<div className="flex min-w-0 items-center">{leftSlot}</div>
Expand All @@ -110,8 +115,6 @@

{showBackButton && <TopBarBackButton onClick={goBack} variant="desktop" />}

{leading}

{pageInfo && (
<TopBarBreadcrumb
pageInfo={showTopBarFundTabs ? { ...pageInfo, title: 'Fund' } : pageInfo}
Expand Down
35 changes: 22 additions & 13 deletions app/layouts/components/LeftSidebarContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,34 @@

interface LeftSidebarContainerProps {
isOpen: boolean;
/** Closes the phone's menu; see `LeftSidebar`'s `onNavigate`. */
/** Closes the menu once something in it is picked; see `LeftSidebar`'s `onNavigate`. */
onClose?: () => void;
/** The phone's promo banner sits above the top bar, so the menu starts lower. */
withPromoBanner?: boolean;
}

export function LeftSidebarContainer({ isOpen, onClose }: LeftSidebarContainerProps) {
/**
* The app's left column from 1240px up. Narrower than that there is no
* column: the top bar's menu button slides the whole sidebar in, under the
* top bar, over the page.
*/
export function LeftSidebarContainer({
isOpen,
onClose,
withPromoBanner = false,
}: LeftSidebarContainerProps) {

Check warning on line 23 in app/layouts/components/LeftSidebarContainer.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Mark the props of the component as read-only.

See more on https://sonarcloud.io/project/issues?id=ResearchHub_web&issues=AaEUCTQL1KXi02Z05J4S&open=AaEUCTQL1KXi02Z05J4S&pullRequest=1147
return (
<div
className={cn(
// Shared base styles
'bg-white border-r border-gray-200 flex-shrink-0 z-50',
'transition-all duration-300 ease-in-out',
// Desktop: sticky, no transform transition
'tablet:!sticky tablet:!top-0 tablet:!h-screen tablet:!z-30',
'tablet:!transition-none tablet:!translate-x-0',
'tablet:sidebar-compact:!w-[240px] tablet:max-sidebar-compact:!w-[70px]',
'tablet:!block tablet:!w-[240px]',
// Mobile: fixed, slides in/out below the constant-height top bar
'fixed top-[var(--top-bar-height)] h-[calc(100vh-var(--top-bar-height))] duration-150 w-[240px]',
isOpen ? '!translate-x-0' : '!-translate-x-full'
'bg-white border-r border-gray-200 flex-shrink-0 z-50 w-[240px]',
// The menu: fixed under the constant-height top bar, sliding in and out.
'fixed top-[var(--top-bar-height)] h-[calc(100vh-var(--top-bar-height))]',
withPromoBanner && 'left-sidebar-with-promo-banner',
'transition-transform duration-200 ease-out',
isOpen ? '!translate-x-0' : '!-translate-x-full',
// The column: in the page's row, always there.
'sidebar-compact:!sticky sidebar-compact:!top-0 sidebar-compact:!h-screen sidebar-compact:!z-30',
'sidebar-compact:!transition-none sidebar-compact:!translate-x-0'
)}
>
<LeftSidebar onNavigate={onClose} />
Expand Down
2 changes: 1 addition & 1 deletion app/layouts/components/MobileOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function MobileOverlay({ show, visible, onClose }: MobileOverlayProps) {
<div
className={`fixed inset-0 bg-black ${
visible ? 'opacity-50' : 'opacity-0'
} z-40 tablet:!hidden transition-opacity duration-300 ease-in-out`}
} z-40 sidebar-compact:!hidden transition-opacity duration-300 ease-in-out`}
onClick={onClose}
/>
);
Expand Down
10 changes: 2 additions & 8 deletions app/layouts/components/SidebarDocuments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ const PUBLISHED_SHOWN = 5;
interface SidebarDocumentsProps {
/** Runs after any choice that should put a drawer or an overlay away. */
readonly onNavigate?: () => void;
/** A rule above the first heading, setting the documents apart from the nav. */
readonly divider?: boolean;
readonly className?: string;
}

Expand All @@ -29,7 +27,7 @@ interface SidebarDocumentsProps {
* still a draft, with a "+" to start another, and what has been published.
* Each opens in the workspace; the one open there is highlighted.
*/
export function SidebarDocuments({ onNavigate, divider = true, className }: SidebarDocumentsProps) {
export function SidebarDocuments({ onNavigate, className }: SidebarDocumentsProps) {
const { drafts, published, status, refresh } = useFundingDocuments();
const { startNew, openDraft } = useFundingDrafting();
const target = useOptionalAIMode()?.target;
Expand Down Expand Up @@ -61,11 +59,7 @@ export function SidebarDocuments({ onNavigate, divider = true, className }: Side

return (
<div className={cn('px-3 pb-3', className)}>
{divider ? (
<div aria-hidden="true" className="mx-2 mb-3 mt-3.5 h-px bg-gray-200" />
) : (
<div className="h-3" />
)}
<div aria-hidden="true" className="mx-2 mb-3 mt-3.5 h-px bg-gray-200" />

<section aria-label="Drafts">
<SectionHeading
Expand Down
4 changes: 2 additions & 2 deletions app/layouts/components/TopBarContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ export function TopBarContainer({
<div
data-top-bar-container
className={`fixed top-0 right-0 z-[60] tablet:!z-50 tablet:!bg-white
left-0 tablet:!left-[240px] tablet:sidebar-compact:!left-[240px] tablet:max-sidebar-compact:!left-[70px]
left-0 sidebar-compact:!left-[240px]
transition-transform duration-300 ease-in-out tablet:!transform-none
${shouldHide ? '-translate-y-full' : 'translate-y-0'}`}
>
<EndowmentPromoBanner />
<TopBar onMenuClick={onMenuClick} />
<TopBar onMenuClick={onMenuClick} isMenuOpen={isLeftSidebarOpen} />
</div>
);
}
Loading
Loading