Replace the hello-world shell with the elevated design system, bilingual screens, recipe/extras data, and the interactive Mein Teller view. Co-authored-by: Cursor <cursoragent@cursor.com>
89 lines
2.9 KiB
TypeScript
89 lines
2.9 KiB
TypeScript
import type { ReactNode } from "react";
|
|
import { cx } from "../lib/cx";
|
|
|
|
export interface NavItem {
|
|
key: string;
|
|
icon: ReactNode;
|
|
label: string;
|
|
active?: boolean;
|
|
onClick?: () => void;
|
|
href?: string;
|
|
}
|
|
|
|
export interface BottomNavProps {
|
|
items: NavItem[];
|
|
className?: string;
|
|
}
|
|
|
|
/**
|
|
* Floating Glass Pill bottom navigation.
|
|
*
|
|
* - Fixed to the bottom, centered, floats above content (not edge-to-edge).
|
|
* - Respects `env(safe-area-inset-bottom)` so it clears the iOS home indicator.
|
|
* - Active item gets the accent gradient applied directly to icon + label
|
|
* (bg-clip-text) rather than a pill behind it, so it reads against the
|
|
* glass background instead of competing with it.
|
|
*/
|
|
export function BottomNav({ items, className }: BottomNavProps) {
|
|
return (
|
|
<nav
|
|
className={cx("fixed inset-x-0 bottom-0 z-50 flex justify-center pointer-events-none", className)}
|
|
style={{ paddingBottom: "calc(env(safe-area-inset-bottom) + 12px)" }}
|
|
>
|
|
<div
|
|
className="relative flex items-center gap-1 rounded-full px-2 py-2 pointer-events-auto"
|
|
style={{ boxShadow: "var(--shadow-ambient)" }}
|
|
>
|
|
<div
|
|
className="absolute inset-0 rounded-full"
|
|
style={{
|
|
background: "var(--glass-fill)",
|
|
backdropFilter: "blur(var(--glass-blur))",
|
|
WebkitBackdropFilter: "blur(var(--glass-blur))",
|
|
}}
|
|
/>
|
|
<div className="glass-border pointer-events-none absolute inset-0 rounded-full" />
|
|
|
|
{items.map((item) => {
|
|
const content = (
|
|
<>
|
|
<span
|
|
aria-hidden
|
|
className={cx(
|
|
"text-xl leading-none",
|
|
item.active ? "bg-clip-text text-transparent" : "text-[var(--ink-soft)]"
|
|
)}
|
|
style={item.active ? { backgroundImage: "var(--gradient-accent)" } : undefined}
|
|
>
|
|
{item.icon}
|
|
</span>
|
|
<span
|
|
className={cx(
|
|
"font-body text-[10px] font-bold leading-none",
|
|
item.active ? "bg-clip-text text-transparent" : "text-[var(--ink-soft)]"
|
|
)}
|
|
style={item.active ? { backgroundImage: "var(--gradient-accent)" } : undefined}
|
|
>
|
|
{item.label}
|
|
</span>
|
|
</>
|
|
);
|
|
const itemClass = cx(
|
|
"relative z-10 flex min-w-[48px] min-h-[48px] flex-col items-center justify-center gap-0.5",
|
|
"elevation-transition rounded-full px-3 no-select active:translate-y-px active:brightness-95"
|
|
);
|
|
return item.href ? (
|
|
<a key={item.key} href={item.href} className={itemClass}>
|
|
{content}
|
|
</a>
|
|
) : (
|
|
<button key={item.key} type="button" onClick={item.onClick} className={itemClass}>
|
|
{content}
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
</nav>
|
|
);
|
|
}
|