components/mobile-shell.tsx
"use client";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Card from "@mui/material/Card";
import CardContent from "@mui/material/CardContent";
import Grid from "@mui/material/Grid";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { ThemeProvider, createTheme } from "@mui/material/styles";
import { LiquidGlassTabBar, TabScreens } from "@/components/LiquidGlassTabBar";
import NW, { type NorthwindData } from "@/data/northwind";
// The glass is dark, so the screens get a dark MUI theme of their own.
const glassTheme = createTheme({
palette: { mode: "dark", background: { paper: "#16171F" } },
});
// Labels are yours. Keep the five ids: each id picks its icon in the component's ICON map.
const data: NorthwindData = {
...NW,
music: {
...NW.music,
tabs: [
{ id: "home", label: "Home" },
{ id: "explore", label: "Search" },
{ id: "library", label: "Saved" },
{ id: "inbox", label: "Inbox", badge: 2 },
{ id: "profile", label: "Account" },
],
},
};
function Screen({ title, children }: { title: string; children: React.ReactNode }) {
return (
<Stack spacing={2}>
<Typography variant="h4" component="h2" sx={{ fontWeight: 700 }}>
{title}
</Typography>
{children}
</Stack>
);
}
// One screen per tab, in tab order.
const screens = [
<Screen key="home" title="Home">
<Grid container spacing={1.5}>
{["Runs", "Streak", "Distance", "Pace"].map((label) => (
<Grid key={label} size={6}>
<Card variant="outlined">
<CardContent>
<Typography variant="body2" color="text.secondary">{label}</Typography>
<Typography variant="h5">12</Typography>
</CardContent>
</Card>
</Grid>
))}
</Grid>
<Button variant="contained" fullWidth onClick={() => console.info("log a run")}>
Log a run
</Button>
</Screen>,
<Screen key="search" title="Search">
<Typography color="text.secondary">Your search screen.</Typography>
</Screen>,
<Screen key="saved" title="Saved">
<Typography color="text.secondary">Saved items.</Typography>
</Screen>,
<Screen key="inbox" title="Inbox">
<Typography color="text.secondary">Two new messages.</Typography>
</Screen>,
<Screen key="account" title="Account">
<Button variant="outlined" fullWidth>Sign out</Button>
</Screen>,
];
export function MobileShell() {
return (
<ThemeProvider theme={glassTheme}>
<Box className="qd-tabbar" sx={{ position: "relative", height: "100%" }}>
<div className="native">
<TabScreens value={screens}>
<LiquidGlassTabBar
data={data}
autoplayDemo={false}
// The bar swaps its own screens; use this for analytics or to load a tab's data.
onTabChange={(id) => console.info("tab", id)}
onOpenPlayer={() => console.info("mini player opened")}
/>
</TabScreens>
</div>
</Box>
</ThemeProvider>
);
}
app/phone/page.tsx
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import { MobileShell } from "@/components/mobile-shell";
export default function Page() {
return (
<>
{/* Phones: the glass tab bar and its five screens fill the viewport. */}
<Box sx={{ display: { xs: "block", md: "none" }, height: "100dvh" }}>
<MobileShell />
</Box>
{/* md and up: your usual MUI layout (AppBar, Drawer). */}
<Box component="main" sx={{ display: { xs: "none", md: "block" }, p: 5 }}>
<Typography variant="h4" component="h1">Dashboard</Typography>
</Box>
</>
);
}
components/tabbar-liquid.css
/* components/tabbar-liquid.css (your copy). Edit 1: the file's first rule was ":root{". */
@scope (.qd-tabbar) to (.qd-screen) {
:scope{font-family:var(--font-ui);color:var(--text);
--ink:#0A0A0D;--surface:#111218; /* ...every variable that was on :root stays here... */
}
/* ...the rest of the file stays as it is... */
/* Edit 2: hide the demo's 9:41 status bar, then close the @scope block. */
.sb{display:none}
}
components/LiquidGlassTabBar.tsx
// components/LiquidGlassTabBar.tsx (your copy): three small edits.
// 1. The React import gains createContext, useContext and ReactNode.
import { createContext, useContext, useEffect, useLayoutEffect, useRef, type CSSProperties, type ReactNode } from 'react';
// 2. Above Pages: a context for your screens. Pages renders them when a provider is set.
export const TabScreens = createContext<ReactNode[] | null>(null);
function Pages({ data }: { data: NorthwindData }) {
const screens = useContext(TabScreens);
if (screens) return <>{screens.map((s, i) => <div className="pg" data-pg={i} key={i}><div className="pgc"><div className="qd-screen">{s}</div></div></div>)}</>;
// ...the demo screens below stay as they are...
}
// 3. In LensOrb: the drop draws a copy of the screens, so keep that copy out of the tab order.
<div className="orb" data-k="orb" aria-hidden="true" inert>