feat: chat reactions

This commit is contained in:
gosangam
2026-08-30 01:51:52 +05:30
parent 0f382ed885
commit 8c7afe596d
19 changed files with 662 additions and 53 deletions
+29
View File
@@ -0,0 +1,29 @@
"use client";
/**
* The column that holds the header, page and footer.
*
* Exists as a client component for one reason: the bottom padding that
* reserves space for the mobile tab bar has to disappear wherever the tab bar
* does, and that decision depends on the route. Left in the server layout, the
* padding would survive the bar and leave a 64px gap under a chat composer.
*/
import { cn } from "@/lib/utils";
import { useHidesAppChrome } from "@/lib/use-chrome-visibility";
export function AppShell({ children }: { children: React.ReactNode }) {
const noTabBar = useHidesAppChrome();
return (
<div
className={cn(
"flex flex-1 flex-col md:pl-16",
// Matches SiteBottomNav's own visibility exactly.
noTabBar ? "pb-0" : "pb-16 md:pb-0",
)}
>
{children}
</div>
);
}
+5
View File
@@ -19,6 +19,7 @@ import {
useChatUnread,
} from "@/components/layout/nav-unread-badge";
import { useAuth } from "@/components/auth/auth-provider";
import { useHidesAppChrome } from "@/lib/use-chrome-visibility";
import { cn } from "@/lib/utils";
type Item = {
@@ -31,6 +32,7 @@ type Item = {
export function SiteBottomNav() {
const pathname = usePathname();
const hidden = useHidesAppChrome();
const { user } = useAuth();
// Read here rather than inside the badge so the count can also reach the
// link's accessible name — the badge itself is decorative.
@@ -97,6 +99,9 @@ export function SiteBottomNav() {
return pathname === item.href || pathname.startsWith(`${item.href}/`);
}
// A chat thread owns the bottom edge — its composer lives there.
if (hidden) return null;
return (
<nav
aria-label="Primary"
+5
View File
@@ -26,6 +26,7 @@ import Link from "next/link";
import { ReadingSettings } from "@/components/reading-settings";
import { NotificationBell } from "@/components/layout/notification-bell";
import { useAuth } from "@/components/auth/auth-provider";
import { useHidesAppChrome } from "@/lib/use-chrome-visibility";
export function SiteHeader() {
const { user, loading } = useAuth();
@@ -33,6 +34,10 @@ export function SiteHeader() {
// auth check we treat `loading` as anonymous so the gear doesn't flash
// in and out — once auth resolves, it hides for signed-in users.
const showMobileGear = !loading && !user;
// A chat thread has its own header — back button, name, avatar — so the
// wordmark bar would stack a second title above it.
const hidden = useHidesAppChrome();
if (hidden) return null;
return (
<header className="sticky top-0 z-30 bg-background/60 backdrop-blur-md">
+5
View File
@@ -17,6 +17,7 @@ import { useState } from "react";
import Link from "next/link";
import { Menu } from "lucide-react";
import { cn } from "@/lib/utils";
import { useHidesAppChrome } from "@/lib/use-chrome-visibility";
import {
Drawer,
DrawerContent,
@@ -43,6 +44,10 @@ export function MobileNavToggle({
items: ReadonlyArray<NavItem>;
}) {
const [open, setOpen] = useState(false);
// A thread already has a back button in its own header; a hamburger beside
// it offers a second, conflicting way out.
const hidden = useHidesAppChrome();
if (hidden) return null;
return (
<Drawer open={open} onOpenChange={setOpen} direction="left">