Files
fotospiel-app/resources/js/guest/components/BottomNav.tsx

120 lines
4.4 KiB
TypeScript

import React from 'react';
import { NavLink, useParams, useLocation, Link } from 'react-router-dom';
import { CheckSquare, GalleryHorizontal, Home, Trophy, Camera } from 'lucide-react';
import { useEventData } from '../hooks/useEventData';
import { useTranslation } from '../i18n/useTranslation';
import { useEventBranding } from '../context/EventBrandingContext';
function TabLink({
to,
children,
isActive,
accentColor,
}: {
to: string;
children: React.ReactNode;
isActive: boolean;
accentColor: string;
}) {
const activeStyle = isActive
? {
background: `linear-gradient(135deg, ${accentColor}, ${accentColor}cc)`,
color: '#ffffff',
boxShadow: `0 12px 30px ${accentColor}33`,
}
: undefined;
return (
<NavLink
to={to}
className={`
flex h-14 flex-col items-center justify-center gap-1 rounded-lg border border-transparent p-2 text-xs font-medium transition-all duration-200 ease-out
touch-manipulation backdrop-blur-md
${isActive ? 'scale-[1.04]' : 'text-white/70 hover:text-white'}
`}
style={activeStyle}
>
{children}
</NavLink>
);
}
export default function BottomNav() {
const { token } = useParams();
const location = useLocation();
const { event, status } = useEventData();
const { t } = useTranslation();
const { branding } = useEventBranding();
const isReady = status === 'ready' && !!event;
if (!token || !isReady) return null;
const base = `/e/${encodeURIComponent(token)}`;
const currentPath = location.pathname;
const labels = {
home: t('navigation.home'),
tasks: t('navigation.tasks'),
achievements: t('navigation.achievements'),
gallery: t('navigation.gallery'),
upload: t('home.actions.items.upload.label'),
};
const isHomeActive = currentPath === base || currentPath === `/${token}`;
const isTasksActive = currentPath.startsWith(`${base}/tasks`);
const isAchievementsActive = currentPath.startsWith(`${base}/achievements`);
const isGalleryActive = currentPath.startsWith(`${base}/gallery`) || currentPath.startsWith(`${base}/photos`);
const isUploadActive = currentPath.startsWith(`${base}/upload`);
return (
<div className="fixed inset-x-0 bottom-0 z-30 border-t border-white/20 bg-gradient-to-t from-black/40 via-black/20 to-transparent px-4 pb-3 pt-2 shadow-xl backdrop-blur-2xl dark:border-white/10 dark:from-gray-950/90 dark:via-gray-900/70 dark:to-gray-900/35">
<div className="mx-auto flex max-w-lg items-center gap-3">
<div className="flex flex-1 justify-evenly gap-2">
<TabLink to={`${base}`} isActive={isHomeActive} accentColor={branding.primaryColor}>
<div className="flex flex-col items-center gap-1">
<Home className="h-5 w-5" aria-hidden />
<span>{labels.home}</span>
</div>
</TabLink>
<TabLink to={`${base}/tasks`} isActive={isTasksActive} accentColor={branding.primaryColor}>
<div className="flex flex-col items-center gap-1">
<CheckSquare className="h-5 w-5" aria-hidden />
<span>{labels.tasks}</span>
</div>
</TabLink>
</div>
<Link
to={`${base}/upload`}
aria-label={labels.upload}
className={`relative flex h-16 w-16 items-center justify-center rounded-full text-white shadow-2xl transition ${
isUploadActive ? 'scale-105' : 'hover:scale-105'
}`}
style={{
background: `radial-gradient(circle at 20% 20%, ${branding.secondaryColor}, ${branding.primaryColor})`,
boxShadow: `0 20px 35px ${branding.primaryColor}44`,
}}
>
<Camera className="h-6 w-6" aria-hidden />
</Link>
<div className="flex flex-1 justify-evenly gap-2">
<TabLink to={`${base}/achievements`} isActive={isAchievementsActive} accentColor={branding.primaryColor}>
<div className="flex flex-col items-center gap-1">
<Trophy className="h-5 w-5" aria-hidden />
<span>{labels.achievements}</span>
</div>
</TabLink>
<TabLink to={`${base}/gallery`} isActive={isGalleryActive} accentColor={branding.primaryColor}>
<div className="flex flex-col items-center gap-1">
<GalleryHorizontal className="h-5 w-5" aria-hidden />
<span>{labels.gallery}</span>
</div>
</TabLink>
</div>
</div>
</div>
);
}