Python 64.6%
TypeScript 33.7%
CSS 0.8%
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