Fix UI — CSS variables, sidebar toggle with chevron, settings panel pushes content

This commit is contained in:
2026-04-26 06:37:13 +00:00
parent 996c4e19a7
commit e9531a0321
6 changed files with 56 additions and 51 deletions
+5 -4
View File
@@ -38,22 +38,23 @@
[data-font="medium"] { --font-size: 15px; } [data-font="medium"] { --font-size: 15px; }
[data-font="large"] { --font-size: 17px; } [data-font="large"] { --font-size: 17px; }
* { box-sizing: border-box; margin: 0; padding: 0; } html {
background: var(--bg);
color: var(--text);
}
body { body {
background: var(--bg); background: var(--bg);
color: var(--text); color: var(--text);
font-size: var(--font-size); font-size: var(--font-size);
font-family: var(--font-sans); line-height: 1.6;
} }
/* Markdown styles */
.prose p { margin-bottom: 0.75em; } .prose p { margin-bottom: 0.75em; }
.prose p:last-child { margin-bottom: 0; } .prose p:last-child { margin-bottom: 0; }
.prose strong { font-weight: 500; color: var(--text); } .prose strong { font-weight: 500; color: var(--text); }
.prose em { font-style: italic; } .prose em { font-style: italic; }
.prose code { .prose code {
font-family: var(--font-mono);
font-size: 0.88em; font-size: 0.88em;
background: var(--bg3); background: var(--bg3);
padding: 1px 5px; padding: 1px 5px;
+2 -2
View File
@@ -39,8 +39,8 @@ export default function RootLayout({
children: React.ReactNode; children: React.ReactNode;
}) { }) {
return ( return (
<html lang="en" suppressHydrationWarning> <html lang="en" data-theme="light" data-font="medium" suppressHydrationWarning>
<body className={`${sans.variable} ${mono.variable}`}> <body style={{ fontFamily: 'var(--font-sans, sans-serif)' }} className={`${sans.variable} ${mono.variable}`}>
{children} {children}
</body> </body>
</html> </html>
+43 -32
View File
@@ -17,19 +17,26 @@ export default function Home() {
setMessages, setMessages,
currentId, currentId,
settingsOpen, settingsOpen,
setSettingsOpen,
sidebarOpen, sidebarOpen,
setSidebarOpen, setSidebarOpen,
} = useStore(); } = useStore();
useEffect(() => { useEffect(() => {
// Load settings from server api.getSettings().then(s => {
api.getSettings().then(setSettings).catch(console.error); setSettings(s);
// Load conversations document.documentElement.setAttribute('data-theme', s.theme || 'light');
document.documentElement.setAttribute('data-font', s.font_size || 'medium');
}).catch(console.error);
api.getConversations().then(setConversations).catch(console.error); api.getConversations().then(setConversations).catch(console.error);
}, []); }, []);
useEffect(() => { useEffect(() => {
// Load messages when conversation changes document.documentElement.setAttribute('data-theme', settings.theme || 'light');
document.documentElement.setAttribute('data-font', settings.font_size || 'medium');
}, [settings.theme, settings.font_size]);
useEffect(() => {
if (currentId) { if (currentId) {
api.getMessages(currentId).then(setMessages).catch(console.error); api.getMessages(currentId).then(setMessages).catch(console.error);
} else { } else {
@@ -38,51 +45,58 @@ export default function Home() {
}, [currentId]); }, [currentId]);
return ( return (
<div <div className="flex h-dvh overflow-hidden" style={{ background: 'var(--bg)' }}>
className="flex h-dvh overflow-hidden"
style={{ background: 'var(--bg)' }}
data-theme={settings.theme}
data-font={settings.font_size}
>
{/* Mobile sidebar overlay */} {/* Mobile sidebar overlay */}
{sidebarOpen && ( {sidebarOpen && (
<div <div
className="fixed inset-0 z-20 bg-black/40 md:hidden" className="fixed inset-0 z-20 md:hidden"
style={{ background: 'rgba(0,0,0,0.4)' }}
onClick={() => setSidebarOpen(false)} onClick={() => setSidebarOpen(false)}
/> />
)} )}
{/* Sidebar */} {/* Sidebar — desktop: toggleable, mobile: slide-over */}
<div <div
className={` className={`
fixed inset-y-0 left-0 z-30 w-72 transform transition-transform duration-200 fixed inset-y-0 left-0 z-30 flex-shrink-0 transition-all duration-200
md:relative md:translate-x-0 md:w-64 md:flex-shrink-0 md:relative md:translate-x-0
${sidebarOpen ? 'translate-x-0' : '-translate-x-full'} ${sidebarOpen ? 'translate-x-0 w-64' : '-translate-x-full md:translate-x-0'}
${sidebarOpen ? 'md:w-64' : 'md:w-0 md:overflow-hidden'}
`} `}
style={{ background: 'var(--sidebar-bg)', borderRight: '1px solid var(--border)' }} style={{
background: 'var(--sidebar-bg)',
borderRight: sidebarOpen ? '1px solid var(--border)' : 'none',
width: undefined,
}}
> >
<Sidebar /> <div className="w-64 h-full">
<Sidebar onClose={() => setSidebarOpen(false)} />
</div>
</div> </div>
{/* Main */} {/* Main — shrinks when settings opens */}
<div className="flex flex-col flex-1 min-w-0"> <div
className="flex flex-col flex-1 min-w-0 transition-all duration-200"
style={{ marginRight: settingsOpen ? '340px' : '0' }}
>
{/* Topbar */} {/* Topbar */}
<div <div
className="flex items-center justify-between px-4 py-3 flex-shrink-0" className="flex items-center justify-between px-4 py-3 flex-shrink-0"
style={{ borderBottom: '1px solid var(--border)' }} style={{ borderBottom: '1px solid var(--border)' }}
> >
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
{/* Hamburger — mobile only */} {/* Sidebar toggle */}
<button <button
className="md:hidden p-1 rounded"
style={{ color: 'var(--text3)' }}
onClick={() => setSidebarOpen(!sidebarOpen)} onClick={() => setSidebarOpen(!sidebarOpen)}
style={{ color: 'var(--text3)', background: 'none', border: 'none', cursor: 'pointer', padding: '4px', borderRadius: '6px', lineHeight: 1 }}
aria-label="Toggle sidebar" aria-label="Toggle sidebar"
> >
<svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor"> <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<rect y="3" width="20" height="2" rx="1"/> {sidebarOpen
<rect y="9" width="20" height="2" rx="1"/> ? <polyline points="15 18 9 12 15 6" />
<rect y="15" width="20" height="2" rx="1"/> : <polyline points="9 18 15 12 9 6" />
}
</svg> </svg>
</button> </button>
<span style={{ fontSize: '13px', color: 'var(--text3)' }}> <span style={{ fontSize: '13px', color: 'var(--text3)' }}>
@@ -91,22 +105,19 @@ export default function Home() {
</span> </span>
</div> </div>
<button <button
onClick={() => useStore.getState().setSettingsOpen(!settingsOpen)} onClick={() => setSettingsOpen(!settingsOpen)}
style={{ color: 'var(--text3)', background: 'none', border: 'none', cursor: 'pointer', padding: '4px', borderRadius: '6px', fontSize: '18px' }} style={{ color: settingsOpen ? 'var(--accent)' : 'var(--text3)', background: 'none', border: 'none', cursor: 'pointer', padding: '4px', borderRadius: '6px', fontSize: '18px' }}
aria-label="Settings" aria-label="Settings"
> >
</button> </button>
</div> </div>
{/* Messages */}
<MessageList /> <MessageList />
{/* Input */}
<MessageInput /> <MessageInput />
</div> </div>
{/* Settings panel */} {/* Settings panel — pushes content, doesn't cover */}
<SettingsPanel /> <SettingsPanel />
</div> </div>
); );
+1 -9
View File
@@ -60,20 +60,12 @@ export default function SettingsPanel() {
return ( return (
<> <>
{/* Backdrop on mobile */}
{settingsOpen && (
<div
className="fixed inset-0 z-40 bg-black/40 md:hidden"
onClick={() => setSettingsOpen(false)}
/>
)}
<div <div
className={`fixed top-0 right-0 bottom-0 z-50 flex flex-col transition-transform duration-200 ${ className={`fixed top-0 right-0 bottom-0 z-50 flex flex-col transition-transform duration-200 ${
settingsOpen ? 'translate-x-0' : 'translate-x-full' settingsOpen ? 'translate-x-0' : 'translate-x-full'
}`} }`}
style={{ style={{
width: 'min(340px, 100vw)', width: '340px',
background: 'var(--bg)', background: 'var(--bg)',
borderLeft: '1px solid var(--border)', borderLeft: '1px solid var(--border)',
}} }}
+2 -2
View File
@@ -27,7 +27,7 @@ function groupConversations(convs: { id: string; title: string; updated_at: stri
return groups; return groups;
} }
export default function Sidebar() { export default function Sidebar({ onClose }: { onClose?: () => void }) {
const { conversations, currentId, setCurrentId, setMessages, setConversations, setSidebarOpen } = useStore(); const { conversations, currentId, setCurrentId, setMessages, setConversations, setSidebarOpen } = useStore();
async function newConversation() { async function newConversation() {
@@ -36,7 +36,7 @@ export default function Sidebar() {
setConversations(updated); setConversations(updated);
setCurrentId(conv.id); setCurrentId(conv.id);
setMessages([]); setMessages([]);
setSidebarOpen(false); if (onClose) onClose();
} }
async function loadConversation(id: string) { async function loadConversation(id: string) {
+3 -2
View File
@@ -4,8 +4,9 @@ import type { NextRequest } from 'next/server';
export function proxy(request: NextRequest) { export function proxy(request: NextRequest) {
const { pathname } = request.nextUrl; const { pathname } = request.nextUrl;
// Allow login page and public assets // Always allow these through
if ( if (
pathname.startsWith('/api/') ||
pathname.startsWith('/login') || pathname.startsWith('/login') ||
pathname.startsWith('/_next') || pathname.startsWith('/_next') ||
pathname.startsWith('/manifest.json') || pathname.startsWith('/manifest.json') ||
@@ -15,7 +16,7 @@ export function proxy(request: NextRequest) {
return NextResponse.next(); return NextResponse.next();
} }
// Check for session cookie // Check for session cookie on all other routes
const session = request.cookies.get('aaronai_session'); const session = request.cookies.get('aaronai_session');
if (!session?.value) { if (!session?.value) {
return NextResponse.redirect(new URL('/login', request.url)); return NextResponse.redirect(new URL('/login', request.url));