feat: add responsive layout and mobile support (#19)

Add collapsible sidebars with slide-in drawers on mobile, hamburger menu
for session sidebar, stacked layouts for screens under 768px, 44px touch
targets, and overflow prevention across all pages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
shahondin1624
2026-03-12 13:17:02 +01:00
parent d837ed9050
commit dfef26bad5
11 changed files with 268 additions and 68 deletions

View File

@@ -32,6 +32,7 @@
create(SessionConfigSchema, { overrideLevel: OverrideLevel.NONE })
);
let showConfig = $state(false);
let showSessionSidebar = $state(false);
const lineageHref = resolveRoute('/lineage');
const memoryHref = resolveRoute('/memory');
const auditHref = resolveRoute('/audit');
@@ -163,29 +164,54 @@
}
</script>
<div class="flex h-screen bg-white dark:bg-gray-900">
<SessionSidebar onSelectSession={handleSelectSession} onNewChat={handleNewChat} />
<div class="flex h-screen overflow-hidden bg-white dark:bg-gray-900">
<!-- Desktop sidebar: always visible on md+. Mobile: controlled by showSessionSidebar -->
<div class="hidden md:flex">
<SessionSidebar onSelectSession={handleSelectSession} onNewChat={handleNewChat} />
</div>
<!-- Mobile sidebar drawer -->
<div class="md:hidden">
<SessionSidebar
onSelectSession={handleSelectSession}
onNewChat={handleNewChat}
open={showSessionSidebar}
onClose={() => (showSessionSidebar = false)}
/>
</div>
<div class="flex flex-1 flex-col">
<header class="flex items-center justify-between border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 px-4 py-3">
<h1 class="text-lg font-semibold text-gray-900 dark:text-gray-100">Chat</h1>
<div class="flex min-w-0 flex-1 flex-col">
<header class="flex items-center justify-between border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 px-3 py-3 md:px-4">
<div class="flex items-center gap-2">
<!-- Hamburger menu button (mobile only) -->
<button
type="button"
onclick={() => (showSessionSidebar = true)}
class="flex h-10 w-10 items-center justify-center rounded-lg text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 md:hidden"
aria-label="Open sessions sidebar"
>
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
</svg>
</button>
<h1 class="text-lg font-semibold text-gray-900 dark:text-gray-100">Chat</h1>
</div>
<div class="flex items-center gap-1 md:gap-2">
<!-- eslint-disable svelte/no-navigation-without-resolve -- resolveRoute is resolve; plugin does not recognize the alias -->
<a
href={lineageHref}
class="rounded-lg px-2.5 py-1.5 text-sm text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-gray-100"
class="hidden rounded-lg px-2.5 py-1.5 text-sm text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-gray-100 sm:inline-flex"
>
Lineage
</a>
<a
href={memoryHref}
class="rounded-lg px-2.5 py-1.5 text-sm text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-gray-100"
class="hidden rounded-lg px-2.5 py-1.5 text-sm text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-gray-100 sm:inline-flex"
>
Memory
</a>
<a
href={auditHref}
class="rounded-lg px-2.5 py-1.5 text-sm text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-gray-100"
class="hidden rounded-lg px-2.5 py-1.5 text-sm text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-gray-100 sm:inline-flex"
>
Audit
</a>
@@ -194,13 +220,18 @@
<button
type="button"
onclick={() => (showConfig = !showConfig)}
class="flex items-center gap-1 rounded-lg px-2.5 py-1.5 text-sm
class="flex min-h-[44px] min-w-[44px] items-center justify-center gap-1 rounded-lg px-2.5 py-1.5 text-sm md:min-h-0 md:min-w-0
{showConfig ? 'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300' : 'bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600'}"
>
{#if isNonDefaultConfig}
<span class="h-2 w-2 rounded-full bg-amber-500"></span>
{/if}
Config
<span class="hidden sm:inline">Config</span>
<!-- Config icon for mobile when text is hidden -->
<svg class="h-5 w-5 sm:hidden" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
</button>
</div>
</header>
@@ -244,6 +275,7 @@
<ConfigSidebar
config={sessionConfig}
onConfigChange={(c) => (sessionConfig = c)}
onClose={() => (showConfig = false)}
/>
{/if}
</div>