SPB Git forge

spb/uqo-chat

Public
14commits 1branches 0releases
1.4 MBsize
maindefault branch
17 days agolast push
Python 64.6% TypeScript 33.7% CSS 0.8%
4.6 KB · 76 lines tsx
Raw Blame History
1import { useEffect } from 'react';2import { Outlet, useNavigate, useParams } from 'react-router-dom';3import * as RD from '@radix-ui/react-dialog';4import { Menu, PanelRight, Plus } from 'lucide-react';5import { Sidebar } from './sidebar';6import { ArtifactsPanel } from '@/features/conversations/artifacts-panel';7import { SettingsDialog } from '@/features/auth/settings-dialog';8import { Logo } from '@/components/ui/logo';9import { useUI } from '@/stores/ui';10import { useChat } from '@/stores/chat';11import { useAuth } from '@/stores/auth';12import { cn } from '@/lib/cn';1314export function AppShell() {15  const { id } = useParams();16  const nav = useNavigate();17  const { sidebarOpen, setSidebar, panelOpen, setPanel } = useUI();18  const conv = useChat((s) => s.conversations.find((c) => c.id === id));19  const user = useAuth((s) => s.user);2021  useEffect(() => {22    const fs = user?.preferences.font_scale ?? 1;23    document.documentElement.style.setProperty('--font-scale', String(fs));24  }, [user]);2526  return (27    <div className="flex h-[100dvh] w-full overflow-hidden bg-neutral-bg">28      <div className="hidden lg:block shrink-0"><Sidebar /></div>29      <RD.Root open={sidebarOpen} onOpenChange={setSidebar}>30        <RD.Portal>31          <RD.Overlay className="fixed inset-0 z-40 bg-uqo-blue-dark/50 backdrop-blur-[2px] lg:hidden animate-fadein" />32          <RD.Content className="fixed inset-y-0 left-0 z-50 lg:hidden outline-none data-[state=open]:animate-[slidein_.22s_ease-out]" aria-describedby={undefined}>33            <RD.Title className="sr-only">Menu</RD.Title>34            <Sidebar onClose={() => setSidebar(false)} />35          </RD.Content>36        </RD.Portal>37      </RD.Root>3839      <main className="relative flex min-w-0 flex-1 flex-col">40        <header className="relative z-10 flex items-center gap-1 bg-white/90 backdrop-blur border-b border-neutral-line px-2 sm:px-4 pt-[var(--safe-top)] h-[calc(58px+var(--safe-top))] shrink-0">41          <span className="absolute inset-x-0 top-0 h-[3px] bg-gradient-to-r from-uqo-blue via-uqo-blue-dark to-uqo-green lg:hidden" aria-hidden="true" />42          <button onClick={() => setSidebar(true)} className="lg:hidden h-11 w-11 inline-flex items-center justify-center rounded-xl hover:bg-neutral-surface active:scale-95 transition" aria-label="Ouvrir le menu"><Menu size={22} /></button>43          <div className="lg:hidden pl-1"><Logo withText={false} size="sm" /></div>44          <div className="min-w-0 flex-1 px-2">45            <div className="truncate text-[15px] sm:text-base font-semibold text-uqo-blue-dark tracking-tight">{conv?.title || 'Nouvelle conversation'}</div>46            <div className="truncate text-[11px] text-neutral-muted">{conv ? <><span className="inline-block h-1.5 w-1.5 rounded-full bg-uqo-green mr-1 align-middle" />{conv.course}</> : 'Tuteur IA · IMM1003 · IMM1033'}</div>47          </div>48          <button onClick={() => nav('/')} className="lg:hidden h-11 w-11 inline-flex items-center justify-center rounded-xl hover:bg-neutral-surface active:scale-95 transition text-uqo-blue" aria-label="Nouvelle conversation"><Plus size={22} /></button>49          {id && (50            <button onClick={() => setPanel(!panelOpen)} className={cn('h-11 w-11 inline-flex items-center justify-center rounded-xl hover:bg-neutral-surface active:scale-95 transition', panelOpen && 'bg-uqo-blue-light text-uqo-blue')} aria-label="Fichiers et sources de la conversation" aria-pressed={panelOpen}><PanelRight size={20} /></button>51          )}52        </header>53        <div className="flex min-h-0 flex-1 bg-uqo-soft">54          <div className="relative min-w-0 flex-1"><Outlet /></div>55          {id && panelOpen && (56            <div className="hidden md:block w-[330px] shrink-0 border-l border-neutral-line bg-white"><ArtifactsPanel conversationId={id} /></div>57          )}58        </div>59        {id && panelOpen && (60          <RD.Root open onOpenChange={setPanel}>61            <RD.Portal>62              <RD.Overlay className="fixed inset-0 z-40 bg-uqo-blue-dark/50 backdrop-blur-[2px] md:hidden" />63              <RD.Content className="fixed inset-x-0 bottom-0 z-50 md:hidden max-h-[82dvh] rounded-t-3xl bg-white outline-none shadow-float" aria-describedby={undefined}>64                <div className="mx-auto mt-2 h-1.5 w-10 rounded-full bg-neutral-line" aria-hidden="true" />65                <RD.Title className="sr-only">Fichiers et sources</RD.Title>66                <ArtifactsPanel conversationId={id} onClose={() => setPanel(false)} />67              </RD.Content>68            </RD.Portal>69          </RD.Root>70        )}71      </main>72      <SettingsDialog />73    </div>74  );75}76