diff --git a/app/endowment/components/EndowmentTopBar.tsx b/app/endowment/components/EndowmentTopBar.tsx index 339e7193b..b74cd23d4 100644 --- a/app/endowment/components/EndowmentTopBar.tsx +++ b/app/endowment/components/EndowmentTopBar.tsx @@ -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; diff --git a/app/endowment/layout.tsx b/app/endowment/layout.tsx index 6216c5233..11efc302f 100644 --- a/app/endowment/layout.tsx +++ b/app/endowment/layout.tsx @@ -19,10 +19,10 @@ export default function EndowmentLayout({ children }: Readonly<{ children: React return (
{/* 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. */}
{children}
diff --git a/app/globals.css b/app/globals.css index ba79ec5ef..83e0a4558 100644 --- a/app/globals.css +++ b/app/globals.css @@ -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 { diff --git a/app/layouts/LeftSidebar.tsx b/app/layouts/LeftSidebar.tsx index 7d6590b52..707f92df2 100644 --- a/app/layouts/LeftSidebar.tsx +++ b/app/layouts/LeftSidebar.tsx @@ -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; } @@ -49,39 +49,32 @@ export const LeftSidebar: React.FC = ({ forceMinimize = false, return (
-
- -
+
+ +
-
+
-
+
{/* 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. */}
- +
{/* The scroll area above is flex-1, so this sits at the bottom of the column. @@ -92,7 +85,7 @@ export const LeftSidebar: React.FC = ({ forceMinimize = false,
-
+
diff --git a/app/layouts/Navigation.tsx b/app/layouts/Navigation.tsx index 1917c5614..eb57e5165 100644 --- a/app/layouts/Navigation.tsx +++ b/app/layouts/Navigation.tsx @@ -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 = { @@ -60,6 +62,7 @@ export const Navigation: React.FC = ({ onUnimplementedFeature, forceMinimize = false, inScrollArea = false, + onNavigate, }) => { const navigationItems: NavigationItem[] = [ { @@ -100,9 +103,7 @@ export const Navigation: React.FC = ({ 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' @@ -156,21 +157,23 @@ export const Navigation: React.FC = ({ 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 = {item.label}; @@ -215,7 +218,7 @@ export const Navigation: React.FC = ({ className={cn( 'px-3 pt-6', !inScrollArea && 'flex-1 overflow-y-auto', - forceMinimize ? '!px-2' : 'tablet:max-sidebar-compact:!px-2' + forceMinimize && '!px-2' )} >
diff --git a/app/layouts/PageLayout.tsx b/app/layouts/PageLayout.tsx index e650654cf..f1ff73560 100644 --- a/app/layouts/PageLayout.tsx +++ b/app/layouts/PageLayout.tsx @@ -109,7 +109,11 @@ function PageLayoutInner({ - + {fullBleed ? ( // Offset from the fixed top bar exactly as the scrollport below is, diff --git a/app/layouts/PublishMenu.tsx b/app/layouts/PublishMenu.tsx index d34699fc9..9c65b8f66 100644 --- a/app/layouts/PublishMenu.tsx +++ b/app/layouts/PublishMenu.tsx @@ -47,7 +47,7 @@ export const PublishMenu: React.FC = ({ // Regular trigger for standard mode const standardTrigger = ( ); - // Compact trigger for minimized sidebar + // Compact trigger for a sidebar forced into the icon rail const compactTrigger = ( {leftSlot ? (
{leftSlot}
@@ -110,8 +115,6 @@ export function TopBar({ onMenuClick }: TopBarProps) { {showBackButton && } - {leading} - {pageInfo && ( 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) { return (
diff --git a/app/layouts/components/MobileOverlay.tsx b/app/layouts/components/MobileOverlay.tsx index e198ae091..e94ea84e0 100644 --- a/app/layouts/components/MobileOverlay.tsx +++ b/app/layouts/components/MobileOverlay.tsx @@ -13,7 +13,7 @@ export function MobileOverlay({ show, visible, onClose }: MobileOverlayProps) {
); diff --git a/app/layouts/components/SidebarDocuments.tsx b/app/layouts/components/SidebarDocuments.tsx index a2c5e63af..9cfba53cd 100644 --- a/app/layouts/components/SidebarDocuments.tsx +++ b/app/layouts/components/SidebarDocuments.tsx @@ -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; } @@ -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; @@ -61,11 +59,7 @@ export function SidebarDocuments({ onNavigate, divider = true, className }: Side return (
- {divider ? ( -