diff --git a/web/src/components/Layout.tsx b/web/src/components/Layout.tsx index a78823f..e003bd5 100644 --- a/web/src/components/Layout.tsx +++ b/web/src/components/Layout.tsx @@ -7,6 +7,8 @@ import { LayoutSidebars, LayoutWorkspace, } from './LayoutSections' +import { MobileBottomNav } from './MobileBottomNav' +import { shouldShowMobileBottomNav } from './layoutNavigation' import { useLayoutPermissions } from './useLayoutPermissions' import { useLayoutProfiles } from './useLayoutProfiles' import { useLayoutSidebar } from './useLayoutSidebar' @@ -51,6 +53,7 @@ export function Layout() { const showSidebar = !isMediaView(location.pathname, location.search) const hideSearch = location.pathname.startsWith('/settings') const isPlayPage = location.pathname.startsWith('/play') + const showMobileBottomNav = shouldShowMobileBottomNav(location.pathname) return (
@@ -59,6 +62,7 @@ export function Layout() { isAdmin={permissions.isAdmin} can={permissions.can} showSidebar={showSidebar} + sidebarVariant={showSidebar ? 'admin' : 'media'} />
{!isPlayPage && ( @@ -72,9 +76,13 @@ export function Layout() { onLogout={closeProfileAndLogout} showSidebar={showSidebar} hideSearch={hideSearch} + pathname={location.pathname} /> )} - + + {showMobileBottomNav && ( + sidebar.setIsMobileDrawerOpen(true)} /> + )}
) diff --git a/web/src/components/LayoutHeaderSections.tsx b/web/src/components/LayoutHeaderSections.tsx index a04b1e3..c69585a 100644 --- a/web/src/components/LayoutHeaderSections.tsx +++ b/web/src/components/LayoutHeaderSections.tsx @@ -1,11 +1,12 @@ import { useEffect, useRef, useState } from 'react' import { Link, useNavigate } from 'react-router-dom' import { AnimatePresence, motion } from 'framer-motion' -import { Film, LoaderCircle, Menu, Search, Star, X } from 'lucide-react' +import { ArrowLeft, Film, LoaderCircle, Menu, Search, Star, X } from 'lucide-react' import { imageURL } from '../api/client' import { mediaAPI } from '../api/library' import type { Media, PlayProfile, User } from '../types' +import { resolveHeaderBack } from './layoutNavigation' import { LayoutThemeToggle } from './LayoutThemeToggle' import { LayoutUserMenu } from './LayoutUserMenu' import type { useLayoutProfiles } from './useLayoutProfiles' @@ -29,6 +30,7 @@ type LayoutHeaderProps = { onLogout: () => void showSidebar?: boolean hideSearch?: boolean + pathname?: string } export function LayoutHeader({ @@ -41,11 +43,26 @@ export function LayoutHeader({ onLogout, showSidebar, hideSearch, + pathname = '', }: LayoutHeaderProps) { + const navigate = useNavigate() + const headerBack = resolveHeaderBack(pathname) + return (
- {/* Left: Mobile Menu button or Brand Logo */} -
+ {/* Left: back / menu / brand */} +
+ {headerBack ? ( + + ) : null} - + MMTL
) diff --git a/web/src/components/LayoutSections.tsx b/web/src/components/LayoutSections.tsx index 5f30736..fa592af 100644 --- a/web/src/components/LayoutSections.tsx +++ b/web/src/components/LayoutSections.tsx @@ -25,10 +25,12 @@ type LayoutSidebarsProps = Omit< > & { sidebar: LayoutSidebarState showSidebar: boolean + sidebarVariant?: 'media' | 'admin' } type LayoutWorkspaceProps = { routeKey: string + showMobileBottomNav?: boolean } export { LayoutHeader } from './LayoutHeaderSections' @@ -78,6 +80,7 @@ export function LayoutSidebars({ isAdmin, can, showSidebar, + sidebarVariant = 'admin', }: LayoutSidebarsProps) { const content = ( sidebar.setIsMobileDrawerOpen(false)} + variant={sidebarVariant} /> ) @@ -116,7 +120,11 @@ export function LayoutSidebars({ ) } -export function LayoutWorkspace({ routeKey }: LayoutWorkspaceProps) { +export function LayoutWorkspace({ routeKey, showMobileBottomNav = false }: LayoutWorkspaceProps) { + const bottomPad = showMobileBottomNav + ? 'pb-[calc(3.75rem+env(safe-area-inset-bottom,0px))] lg:pb-10' + : '' + if (routeKey.startsWith('/play')) { return (
@@ -128,7 +136,7 @@ export function LayoutWorkspace({ routeKey }: LayoutWorkspaceProps) { } return ( -
+
boolean onToggleSidebar: () => void onCloseMobileDrawer: () => void + variant?: 'media' | 'admin' } export function LayoutSidebarContent({ @@ -21,9 +27,11 @@ export function LayoutSidebarContent({ can, onToggleSidebar, onCloseMobileDrawer, + variant = 'admin', }: LayoutSidebarContentProps) { const sidebarExpanded = isSidebarOpen || isMobileDrawerOpen - const visibleItems = visibleSidebarItems({ isAdmin, can }) + const mediaItems = visibleSidebarItems({ isAdmin, can, items: MEDIA_NAV_ITEMS }) + const adminItems = visibleSidebarItems({ isAdmin, can, items: LAYOUT_NAV_ITEMS }) return (
@@ -32,7 +40,19 @@ export function LayoutSidebarContent({ onToggleSidebar={onToggleSidebar} onCloseMobileDrawer={onCloseMobileDrawer} /> - + {variant === 'media' ? ( + <> + + {adminItems.length > 0 && ( +
+ + +
+ )} + + ) : ( + + )}
) @@ -41,8 +61,9 @@ export function LayoutSidebarContent({ function visibleSidebarItems({ isAdmin, can, -}: Pick): LayoutNavItem[] { - return LAYOUT_NAV_ITEMS.filter( + items, +}: Pick & { items: LayoutNavItem[] }): LayoutNavItem[] { + return items.filter( (item) => (!item.adminOnly || isAdmin) && (!item.permission || can(item.permission)), ) } @@ -84,15 +105,37 @@ function LayoutSidebarHeader({ ) } +function LayoutSidebarSectionLabel({ + sidebarExpanded, + label, +}: { + sidebarExpanded: boolean + label: string +}) { + if (!sidebarExpanded) return null + return ( +

+ {label} +

+ ) +} + function LayoutSidebarNav({ items, sidebarExpanded, + grow = false, }: { items: LayoutNavItem[] sidebarExpanded: boolean + grow?: boolean }) { return ( -