feat(nav): add full-width mobile glass dock
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata, Viewport } from "next";
|
||||||
import localFont from "next/font/local";
|
import localFont from "next/font/local";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
import MobileBottomNav from "@/components/MobileBottomNav";
|
import MobileBottomNav from "@/components/MobileBottomNav";
|
||||||
@@ -18,6 +18,12 @@ const vazirmatn = localFont({
|
|||||||
fallback: ["Tahoma", "Arial", "sans-serif"],
|
fallback: ["Tahoma", "Arial", "sans-serif"],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const viewport: Viewport = {
|
||||||
|
width: "device-width",
|
||||||
|
initialScale: 1,
|
||||||
|
viewportFit: "cover",
|
||||||
|
};
|
||||||
|
|
||||||
export async function generateMetadata(): Promise<Metadata> {
|
export async function generateMetadata(): Promise<Metadata> {
|
||||||
const siteContent = await getCmsSiteContent();
|
const siteContent = await getCmsSiteContent();
|
||||||
const title = siteContent.seo.title || siteContent.official_name;
|
const title = siteContent.seo.title || siteContent.official_name;
|
||||||
@@ -61,7 +67,7 @@ export default async function RootLayout({
|
|||||||
<RouteProgress />
|
<RouteProgress />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
<Navbar siteContent={siteContent} />
|
<Navbar siteContent={siteContent} />
|
||||||
<div className="flex min-h-[calc(100svh-4.5rem)] flex-1 flex-col pb-28 md:pb-0">
|
<div className="flex min-h-[calc(100svh-4.5rem)] flex-1 flex-col pb-[calc(5.5rem+env(safe-area-inset-bottom))] md:pb-0">
|
||||||
<div id="main-content" tabIndex={-1} className="flex-1 outline-none">
|
<div id="main-content" tabIndex={-1} className="flex-1 outline-none">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export default function Footer({ siteContent }: { siteContent: SiteContent }) {
|
|||||||
].filter((contact) => contact !== null);
|
].filter((contact) => contact !== null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className="bg-footer text-footer-foreground" dir="rtl">
|
<footer className="bg-footer pb-[calc(5.5rem+env(safe-area-inset-bottom))] text-footer-foreground md:pb-0" dir="rtl">
|
||||||
<div className="container mx-auto px-4 py-10 md:py-12">
|
<div className="container mx-auto px-4 py-10 md:py-12">
|
||||||
<div className="grid gap-9 sm:grid-cols-2 lg:grid-cols-[1.5fr_0.8fr_1fr]">
|
<div className="grid gap-9 sm:grid-cols-2 lg:grid-cols-[1.5fr_0.8fr_1fr]">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
|||||||
@@ -63,13 +63,10 @@ export default function MobileBottomNav({ siteContent }: { siteContent: SiteCont
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="fixed inset-x-0 bottom-0 z-50 md:hidden">
|
||||||
className="fixed inset-x-0 z-50 px-4 md:hidden"
|
|
||||||
style={{ bottom: "calc(env(safe-area-inset-bottom) + 0.9rem)" }}
|
|
||||||
>
|
|
||||||
<nav
|
<nav
|
||||||
aria-label="ناوبری اصلی موبایل"
|
aria-label="ناوبری اصلی موبایل"
|
||||||
className="mx-auto flex w-full max-w-md items-center justify-between rounded-2xl border border-border bg-background/95 px-1.5 py-1.5 shadow-lg backdrop-blur-xl"
|
className="mobile-bottom-nav flex w-full items-stretch justify-around px-2 pt-2 pb-[calc(env(safe-area-inset-bottom)+0.5rem)]"
|
||||||
dir="rtl"
|
dir="rtl"
|
||||||
>
|
>
|
||||||
{items.map((item) => {
|
{items.map((item) => {
|
||||||
@@ -81,14 +78,21 @@ export default function MobileBottomNav({ siteContent }: { siteContent: SiteCont
|
|||||||
key={item.key}
|
key={item.key}
|
||||||
to={item.href}
|
to={item.href}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex min-h-11 min-w-0 flex-1 flex-col items-center justify-center gap-0.5 rounded-xl px-1 py-1.5 text-[10px] font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
"relative flex min-h-[4.25rem] min-w-0 flex-1 flex-col items-center justify-center gap-1 rounded-xl px-1 pt-2 pb-1 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
||||||
active
|
active
|
||||||
? "bg-secondary text-primary"
|
? "text-primary"
|
||||||
: "text-muted-foreground hover:bg-secondary hover:text-foreground",
|
: "text-foreground/75 hover:text-foreground",
|
||||||
)}
|
)}
|
||||||
aria-current={active ? "page" : undefined}
|
aria-current={active ? "page" : undefined}
|
||||||
>
|
>
|
||||||
<Icon aria-hidden="true" className="h-4 w-4" />
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
className={cn(
|
||||||
|
"absolute top-0.5 h-0.5 w-5 rounded-full transition-colors",
|
||||||
|
active ? "bg-primary" : "bg-transparent",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Icon aria-hidden="true" className="h-6 w-6 shrink-0" />
|
||||||
<span className="max-w-full truncate">{item.label}</span>
|
<span className="max-w-full truncate">{item.label}</span>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ export default function Navbar({ siteContent }: { siteContent: SiteContent }) {
|
|||||||
return (
|
return (
|
||||||
<nav
|
<nav
|
||||||
aria-label="ناوبری اصلی"
|
aria-label="ناوبری اصلی"
|
||||||
className="sticky top-0 z-40 border-b border-border bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/85"
|
className="sticky top-0 z-40 border-b border-border bg-background/95 pt-[env(safe-area-inset-top)] backdrop-blur supports-[backdrop-filter]:bg-background/85 md:pt-0"
|
||||||
dir="rtl"
|
dir="rtl"
|
||||||
>
|
>
|
||||||
<div className="container mx-auto px-4 py-2">
|
<div className="container mx-auto px-4 py-2">
|
||||||
|
|||||||
@@ -127,6 +127,28 @@ All colors MUST be HSL.
|
|||||||
@apply bg-background font-sans text-foreground;
|
@apply bg-background font-sans text-foreground;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mobile-bottom-nav {
|
||||||
|
border-top: 1px solid hsl(var(--border) / 0.7);
|
||||||
|
background-color: hsl(var(--background) / 0.82);
|
||||||
|
box-shadow: 0 -0.75rem 2rem hsl(var(--foreground) / 0.08);
|
||||||
|
-webkit-backdrop-filter: blur(1.25rem) saturate(1.35);
|
||||||
|
backdrop-filter: blur(1.25rem) saturate(1.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports not (backdrop-filter: blur(1px)) {
|
||||||
|
.mobile-bottom-nav {
|
||||||
|
background-color: hsl(var(--background));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-transparency: reduce) {
|
||||||
|
.mobile-bottom-nav {
|
||||||
|
background-color: hsl(var(--background));
|
||||||
|
-webkit-backdrop-filter: none;
|
||||||
|
backdrop-filter: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
:focus-visible {
|
:focus-visible {
|
||||||
outline: 2px solid hsl(var(--ring));
|
outline: 2px solid hsl(var(--ring));
|
||||||
outline-offset: 3px;
|
outline-offset: 3px;
|
||||||
|
|||||||
Reference in New Issue
Block a user