Improve mobile conversation drawer layering and empty state (#59)
Unit Tests / test (pull_request) Successful in 13s

Make the dashboard sidebar a clearer fixed sheet on small screens with an explicit close control and empty-list guidance so it is less likely to look like a blank page.
This commit is contained in:
2026-07-29 07:03:42 -05:00
parent e6ae9d6b1e
commit ecaccf88f4
@@ -2,7 +2,7 @@ import React, { useContext, useEffect, useRef, useState } from "react";
import styled, { ThemeContext } from "styled-components"; import styled, { ThemeContext } from "styled-components";
import { Formik, Form, Field } from "formik"; import { Formik, Form, Field } from "formik";
import * as Yup from "yup"; import * as Yup from "yup";
import { AttachFile, Delete, Send, Menu } from "@mui/icons-material"; // Keeping icons for now, can replace later if needed import { AttachFile, Delete, Send, Menu, Close } from "@mui/icons-material"; // Keeping icons for now, can replace later if needed
import { Tooltip } from "@mui/material"; import { Tooltip } from "@mui/material";
import Markdown from "markdown-to-jsx"; import Markdown from "markdown-to-jsx";
@@ -44,10 +44,15 @@ const Sidebar = styled.div<{ $isOpen: boolean }>`
transition: transform 0.3s ease; transition: transform 0.3s ease;
@media (max-width: 768px) { @media (max-width: 768px) {
position: absolute; position: fixed;
top: 0;
left: 0;
width: min(85vw, 320px);
height: 100vh;
transform: ${({ $isOpen }) => $isOpen ? 'translateX(0)' : 'translateX(-100%)'}; transform: ${({ $isOpen }) => $isOpen ? 'translateX(0)' : 'translateX(-100%)'};
background: ${({ theme }) => theme.darkMode ? 'rgba(0, 0, 0, 0.95)' : 'rgba(255, 255, 255, 0.95)'}; background: ${({ theme }) => theme.darkMode ? 'rgba(0, 0, 0, 0.95)' : 'rgba(255, 255, 255, 0.95)'};
box-shadow: ${({ $isOpen }) => $isOpen ? '0 0 20px rgba(0,0,0,0.5)' : 'none'}; box-shadow: ${({ $isOpen }) => $isOpen ? '0 0 20px rgba(0,0,0,0.5)' : 'none'};
z-index: 120;
} }
`; `;
@@ -88,7 +93,7 @@ const Overlay = styled.div<{ $isOpen: boolean }>`
height: 100vh; height: 100vh;
background: rgba(0, 0, 0, 0.5); background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(2px); backdrop-filter: blur(2px);
z-index: 15; z-index: 110;
opacity: ${({ $isOpen }) => $isOpen ? 1 : 0}; opacity: ${({ $isOpen }) => $isOpen ? 1 : 0};
pointer-events: ${({ $isOpen }) => $isOpen ? 'auto' : 'none'}; pointer-events: ${({ $isOpen }) => $isOpen ? 'auto' : 'none'};
transition: opacity 0.3s ease; transition: opacity 0.3s ease;
@@ -98,6 +103,36 @@ const Overlay = styled.div<{ $isOpen: boolean }>`
} }
`; `;
const MobileSidebarHeader = styled.div`
display: none;
@media (max-width: 768px) {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1rem;
color: ${({ theme }) => theme.colors.text};
font-weight: 700;
font-size: 1rem;
}
`;
const MobileSidebarCloseButton = styled.button`
display: none;
@media (max-width: 768px) {
display: flex;
align-items: center;
justify-content: center;
border: none;
background: transparent;
color: ${({ theme }) => theme.colors.text};
cursor: pointer;
border-radius: 999px;
padding: 0.3rem;
}
`;
const MainContent = styled.div` const MainContent = styled.div`
flex: 1; flex: 1;
height: 100%; height: 100%;
@@ -384,6 +419,12 @@ const AsyncDashboardInner = (): JSX.Element => {
</MobileSidebarToggle> </MobileSidebarToggle>
<Sidebar $isOpen={isSidebarOpen}> <Sidebar $isOpen={isSidebarOpen}>
<MobileSidebarHeader>
<span>Conversations</span>
<MobileSidebarCloseButton onClick={() => setIsSidebarOpen(false)} aria-label="Close conversations drawer">
<Close fontSize="small" />
</MobileSidebarCloseButton>
</MobileSidebarHeader>
<NewChatButton onClick={() => { <NewChatButton onClick={() => {
setSelectedConversation(undefined); setSelectedConversation(undefined);
setIsSidebarOpen(false); setIsSidebarOpen(false);
@@ -391,30 +432,41 @@ const AsyncDashboardInner = (): JSX.Element => {
+ New Chat + New Chat
</NewChatButton> </NewChatButton>
<div style={{ overflowY: 'auto', flex: 1 }}> <div style={{ overflowY: 'auto', flex: 1 }}>
{conversations.map((convo) => ( {conversations.length === 0 ? (
<ConversationItem <div style={{
key={convo.id} color: theme?.darkMode ? 'rgba(255,255,255,0.55)' : 'rgba(0,0,0,0.55)',
$active={convo.id === selectedConversation} fontSize: '0.9rem',
onClick={() => { lineHeight: 1.5,
setSelectedConversation(convo.id); padding: '0.25rem 0.5rem'
setIsSidebarOpen(false); }}>
}} No conversations yet. Start with <strong>+ New Chat</strong>.
> </div>
<ConversationTitle>{convo.title || "New Conversation"}</ConversationTitle> ) : (
{convo.id === selectedConversation && ( conversations.map((convo) => (
<IconButton <ConversationItem
as="div" key={convo.id}
onClick={(e) => { $active={convo.id === selectedConversation}
e.stopPropagation(); onClick={() => {
deleteConversation(convo.id); setSelectedConversation(convo.id);
}} setIsSidebarOpen(false);
style={{ padding: 4, width: 'auto', height: 'auto' }} }}
> >
<Delete fontSize="small" /> <ConversationTitle>{convo.title || "New Conversation"}</ConversationTitle>
</IconButton> {convo.id === selectedConversation && (
)} <IconButton
</ConversationItem> as="div"
))} onClick={(e) => {
e.stopPropagation();
deleteConversation(convo.id);
}}
style={{ padding: 4, width: 'auto', height: 'auto' }}
>
<Delete fontSize="small" />
</IconButton>
)}
</ConversationItem>
))
)}
</div> </div>
</Sidebar> </Sidebar>