Files
petfeed/src/components/notifications/NotificationBell.tsx
T
admin 5fbf39593d feat(ui): initials-color avatars and collapsible desktop sidebar
Adds a PetAvatar wrapper (hash(name) mod 360 -> HSL background) and
migrates the four most-visible avatar call sites (sidebar, mobile nav,
post card, post detail incl. comments) to use it instead of duplicated
inline initials logic.

Desktop sidebar can now collapse to an icon-only 76px rail, persisted
in localStorage (same client-init pattern as ActivePetContext). All
nav rows, the new-post button, and the owner row adapt; secondary
sections (language switcher, legal footer) hide when collapsed.

Gitea #12, #14
2026-08-12 12:52:21 +02:00

80 lines
2.7 KiB
TypeScript

"use client";
import Link from "next/link";
import { Bell } from "lucide-react";
import { useQuery } from "@tanstack/react-query";
import { useTRPC } from "@/trpc/client";
import { useActivePet } from "@/context/ActivePetContext";
import { useTranslations } from "next-intl";
interface NotificationBellProps {
/** "sidebar" = full row with label; "mobile" = icon + label stacked */
variant: "sidebar" | "mobile";
isActive?: boolean;
/** Sidebar variant only: icon-only row when the sidebar is collapsed */
collapsed?: boolean;
}
export function NotificationBell({ variant, isActive, collapsed }: NotificationBellProps) {
const trpc = useTRPC();
const { activePetId } = useActivePet();
const t = useTranslations("Nav");
const { data } = useQuery({
...trpc.notifications.unreadCount.queryOptions({ petId: activePetId ?? "" }),
enabled: !!activePetId,
refetchInterval: 10_000,
refetchOnWindowFocus: true,
});
const count = data?.count ?? 0;
const label = count > 99 ? "99+" : count > 0 ? String(count) : null;
if (variant === "sidebar") {
return (
<Link
href="/notifications"
title={collapsed ? t("notifications") : undefined}
className={`flex items-center gap-3 px-3 py-2 rounded-md text-sm transition-colors ${
collapsed ? "justify-center px-0" : ""
} ${
isActive
? collapsed
? "text-foreground font-medium bg-muted"
: "border-l-2 border-orange-500 text-foreground font-medium bg-muted pl-[10px]"
: "text-muted-foreground hover:text-foreground hover:bg-muted"
}`}
>
<span className="relative shrink-0">
<Bell className="h-4 w-4" aria-hidden="true" />
{label && (
<span className="absolute -top-1.5 -right-1.5 min-w-[16px] h-4 px-0.5 rounded-full bg-orange-500 text-[10px] text-white font-semibold flex items-center justify-center leading-none">
{label}
</span>
)}
</span>
{!collapsed && <span>{t("notifications")}</span>}
</Link>
);
}
return (
<Link
href="/notifications"
className={`flex flex-col items-center gap-0.5 px-3 py-2 text-xs ${
isActive ? "text-orange-500" : "text-muted-foreground"
}`}
>
<span className="relative">
<Bell className="h-5 w-5" aria-hidden="true" />
{label && (
<span className="absolute -top-1.5 -right-1.5 min-w-[16px] h-4 px-0.5 rounded-full bg-orange-500 text-[10px] text-white font-semibold flex items-center justify-center leading-none">
{label}
</span>
)}
</span>
<span>{t("notifications")}</span>
</Link>
);
}