All files / src/components AppLayout.tsx

0% Statements 0/111
0% Branches 0/1
0% Functions 0/1
0% Lines 0/111

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136                                                                                                                                                                                                                                                                               
import { Outlet, NavLink, useLocation } from "react-router-dom";
import { usePlatform } from "@/hooks/usePlatform";
import { cn } from "@/lib/utils";
import { useEffect } from "react";
import { mpvSetVisible } from "@/lib/tauri";
import { useFullscreen } from "@/lib/fullscreen-context";
import { Tv, List, FolderOpen, Settings as SettingsIcon } from "lucide-react";
 
const navItems = [
	{ to: "/", label: "Channels", icon: Tv },
	{ to: "/player", label: "Player", icon: List },
	{ to: "/playlists", label: "Playlists", icon: FolderOpen },
	{ to: "/settings", label: "Settings", icon: SettingsIcon },
];
 
export const AppLayout = () => {
	const { layoutMode } = usePlatform();
 
	if (layoutMode === "tv") {
		return <TvLayout />;
	}
 
	if (layoutMode === "mobile") {
		return <MobileLayout />;
	}
 
	return <DesktopLayout />;
};
 
const DesktopLayout = () => {
	const { pathname } = useLocation();
	const isPlayer = pathname === "/player";
	const { isFullscreen } = useFullscreen();
 
	// Hide the native NSOpenGLView when not on the player route so it doesn't
	// bleed through transparent areas on other pages.
	useEffect(() => {
		mpvSetVisible(isPlayer).catch(() => {});
	}, [isPlayer]);
 
	return (
		<div className="flex h-screen overflow-hidden">
			<aside
				className={cn(
					"w-16 flex flex-col items-center py-3 gap-0.5 border-r border-border bg-card shrink-0",
					isFullscreen && "hidden"
				)}
			>
				{navItems.map(({ to, label, icon: Icon }) => (
					<NavLink
						key={to}
						to={to}
						className={({ isActive }) =>
							cn(
								"relative flex flex-col items-center justify-center w-full py-3 gap-1 text-muted-foreground transition-colors",
								isActive ? "text-primary" : "hover:text-foreground"
							)
						}
						title={label}
					>
						{({ isActive }) => (
							<>
								{isActive && (
									<span className="absolute left-0 top-2 bottom-2 w-0.5 rounded-r bg-primary" />
								)}
								<Icon className="h-5 w-5" />
								<span className="text-[9px] font-medium leading-none">{label}</span>
							</>
						)}
					</NavLink>
				))}
			</aside>
			<main className="flex-1 overflow-hidden">
				<Outlet />
			</main>
		</div>
	);
};
 
const MobileLayout = () => {
	return (
		<div className="flex flex-col h-screen">
			<main className="flex-1 overflow-auto">
				<Outlet />
			</main>
			<nav className="flex items-center justify-around border-t border-border bg-card/80 backdrop-blur-sm pb-safe">
				{navItems.map(({ to, label, icon: Icon }) => (
					<NavLink
						key={to}
						to={to}
						className={({ isActive }) =>
							cn(
								"flex flex-col items-center py-2 px-3 text-muted-foreground transition-colors",
								isActive ? "text-primary" : ""
							)
						}
					>
						<Icon className="h-5 w-5" />
						<span className="text-[10px] mt-0.5">{label}</span>
					</NavLink>
				))}
			</nav>
		</div>
	);
};
 
const TvLayout = () => {
	return (
		<div className="flex h-screen overflow-hidden">
			<aside className="w-20 flex flex-col items-center py-6 gap-2 border-r border-border bg-card/50">
				{navItems.map(({ to, label, icon: Icon }) => (
					<NavLink
						key={to}
						to={to}
						className={({ isActive }) =>
							cn(
								"flex flex-col items-center justify-center w-16 h-16 rounded-xl text-muted-foreground transition-colors focus:outline-none focus:ring-2 focus:ring-primary",
								isActive
									? "bg-primary/10 text-primary"
									: "hover:bg-accent hover:text-accent-foreground"
							)
						}
						tabIndex={0}
					>
						<Icon className="h-6 w-6" />
						<span className="text-xs mt-1">{label}</span>
					</NavLink>
				))}
			</aside>
			<main className="flex-1 overflow-hidden">
				<Outlet />
			</main>
		</div>
	);
};