- {title ? (
-
- ) : null}
+
+
+ {title ? (
+
+ ) : null}
{children}
diff --git a/components/layout/app-top-bar.tsx b/components/layout/app-top-bar.tsx
new file mode 100644
index 0000000..edf0bce
--- /dev/null
+++ b/components/layout/app-top-bar.tsx
@@ -0,0 +1,36 @@
+"use client";
+
+import { usePathname } from "next/navigation";
+
+import { HomeLogoLink } from "@/components/layout/home-logo-link";
+import { routes } from "@/lib/routes";
+import { cn } from "@/lib/utils";
+
+type AppTopBarProps = {
+ className?: string;
+};
+
+export function AppTopBar({ className }: AppTopBarProps) {
+ const pathname = usePathname();
+ const isHome = pathname === routes.avatar;
+
+ return (
+
+ );
+}
diff --git a/components/layout/global-bottom-nav.tsx b/components/layout/global-bottom-nav.tsx
index 2b90b15..f62cb80 100644
--- a/components/layout/global-bottom-nav.tsx
+++ b/components/layout/global-bottom-nav.tsx
@@ -3,10 +3,20 @@
import { useEffect, useState } from "react";
import { AppNav } from "@/components/layout/app-nav";
+import {
+ NavOffsetProvider,
+ useNavOffsetRef,
+} from "@/components/layout/nav-offset-provider";
import { HABIT_PET_DATA_UPDATED_EVENT } from "@/lib/app-events";
import { getOnboardingStatusClient } from "@/lib/onboarding-status";
import { createClient } from "@/lib/supabase/client";
+function MeasuredAppNav() {
+ const navRef = useNavOffsetRef();
+
+ return
;
+}
+
export function GlobalBottomNav() {
const [showNav, setShowNav] = useState(false);
@@ -43,9 +53,9 @@ export function GlobalBottomNav() {
};
}, []);
- if (!showNav) {
- return null;
- }
-
- return
;
+ return (
+
+ {showNav ? : null}
+
+ );
}
diff --git a/components/layout/home-logo-link.tsx b/components/layout/home-logo-link.tsx
index 38ddf28..30720ea 100644
--- a/components/layout/home-logo-link.tsx
+++ b/components/layout/home-logo-link.tsx
@@ -5,13 +5,18 @@ import { cn } from "@/lib/utils";
type HomeLogoLinkProps = {
className?: string;
+ "aria-current"?: "page" | undefined;
};
-export function HomeLogoLink({ className }: HomeLogoLinkProps) {
+export function HomeLogoLink({
+ className,
+ "aria-current": ariaCurrent,
+}: HomeLogoLinkProps) {
return (
void;
+};
+
+const NavOffsetContext = createContext
(null);
+
+export function useNavOffsetRef() {
+ const context = useContext(NavOffsetContext);
+
+ if (!context) {
+ throw new Error("useNavOffsetRef must be used within NavOffsetProvider.");
+ }
+
+ const setNavElement = context.setNavElement;
+
+ return useCallback(
+ (node: T | null) => {
+ setNavElement(node);
+ },
+ [setNavElement],
+ ) as Ref;
+}
+
+type NavOffsetProviderProps = {
+ children: ReactNode;
+ enabled?: boolean;
+};
+
+export function NavOffsetProvider({
+ children,
+ enabled = true,
+}: NavOffsetProviderProps) {
+ const navElementRef = useRef(null);
+ const observerRef = useRef(null);
+
+ const syncOffset = useCallback(() => {
+ const root = document.documentElement;
+
+ if (!enabled || !navElementRef.current) {
+ root.style.setProperty("--app-nav-offset", "0px");
+ return;
+ }
+
+ root.style.setProperty(
+ "--app-nav-offset",
+ `${navElementRef.current.offsetHeight}px`,
+ );
+ }, [enabled]);
+
+ const setNavElement = useCallback(
+ (element: HTMLElement | null) => {
+ if (observerRef.current) {
+ observerRef.current.disconnect();
+ observerRef.current = null;
+ }
+
+ navElementRef.current = element;
+
+ if (!element || !enabled) {
+ syncOffset();
+ return;
+ }
+
+ const observer = new ResizeObserver(() => {
+ syncOffset();
+ });
+
+ observer.observe(element);
+ observerRef.current = observer;
+ syncOffset();
+ },
+ [enabled, syncOffset],
+ );
+
+ useEffect(() => {
+ syncOffset();
+ window.addEventListener("resize", syncOffset);
+
+ return () => {
+ window.removeEventListener("resize", syncOffset);
+ if (observerRef.current) {
+ observerRef.current.disconnect();
+ }
+ document.documentElement.style.setProperty("--app-nav-offset", "0px");
+ };
+ }, [syncOffset]);
+
+ useEffect(() => {
+ if (!enabled) {
+ setNavElement(null);
+ }
+ }, [enabled, setNavElement]);
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/components/reminders/in-app-reminder-banner.tsx b/components/reminders/in-app-reminder-banner.tsx
index 08cbbfa..969626e 100644
--- a/components/reminders/in-app-reminder-banner.tsx
+++ b/components/reminders/in-app-reminder-banner.tsx
@@ -36,7 +36,7 @@ export function InAppReminderBanner() {
role="status"
aria-live="polite"
className="fixed inset-x-0 z-[60] px-4 pb-[env(safe-area-inset-bottom,0px)]"
- style={{ bottom: "calc(var(--bottom-nav-height) + 0.5rem)" }}
+ style={{ bottom: "calc(var(--app-nav-offset, var(--bottom-nav-height)) + 0.5rem)" }}
>
diff --git a/components/shop/shop-content.tsx b/components/shop/shop-content.tsx
index 0f8c039..bf7f33e 100644
--- a/components/shop/shop-content.tsx
+++ b/components/shop/shop-content.tsx
@@ -202,7 +202,7 @@ export function ShopContent() {
const roomItems = items.filter((item) => item.type === "room");
const itemGridClassName =
- "grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-3";
+ "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3";
return (
<>
diff --git a/components/ui/toast-provider.tsx b/components/ui/toast-provider.tsx
index c88e297..e65b2cb 100644
--- a/components/ui/toast-provider.tsx
+++ b/components/ui/toast-provider.tsx
@@ -45,8 +45,8 @@ export function ToastProvider({ children }: { children: ReactNode }) {
{children}
{messages.map((message) => (