Surface render crashes and make chat layout viewport-safe (#61) #62
@@ -2,7 +2,7 @@ import React, { useContext, useEffect, useRef, useState } from "react";
|
||||
import styled, { ThemeContext } from "styled-components";
|
||||
import { Formik, Form, Field } from "formik";
|
||||
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 Markdown from "markdown-to-jsx";
|
||||
|
||||
@@ -44,10 +44,15 @@ const Sidebar = styled.div<{ $isOpen: boolean }>`
|
||||
transition: transform 0.3s ease;
|
||||
|
||||
@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%)'};
|
||||
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'};
|
||||
z-index: 120;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -88,7 +93,7 @@ const Overlay = styled.div<{ $isOpen: boolean }>`
|
||||
height: 100vh;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(2px);
|
||||
z-index: 15;
|
||||
z-index: 110;
|
||||
opacity: ${({ $isOpen }) => $isOpen ? 1 : 0};
|
||||
pointer-events: ${({ $isOpen }) => $isOpen ? 'auto' : 'none'};
|
||||
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`
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
@@ -384,6 +419,12 @@ const AsyncDashboardInner = (): JSX.Element => {
|
||||
</MobileSidebarToggle>
|
||||
|
||||
<Sidebar $isOpen={isSidebarOpen}>
|
||||
<MobileSidebarHeader>
|
||||
<span>Conversations</span>
|
||||
<MobileSidebarCloseButton onClick={() => setIsSidebarOpen(false)} aria-label="Close conversations drawer">
|
||||
<Close fontSize="small" />
|
||||
</MobileSidebarCloseButton>
|
||||
</MobileSidebarHeader>
|
||||
<NewChatButton onClick={() => {
|
||||
setSelectedConversation(undefined);
|
||||
setIsSidebarOpen(false);
|
||||
@@ -391,30 +432,41 @@ const AsyncDashboardInner = (): JSX.Element => {
|
||||
+ New Chat
|
||||
</NewChatButton>
|
||||
<div style={{ overflowY: 'auto', flex: 1 }}>
|
||||
{conversations.map((convo) => (
|
||||
<ConversationItem
|
||||
key={convo.id}
|
||||
$active={convo.id === selectedConversation}
|
||||
onClick={() => {
|
||||
setSelectedConversation(convo.id);
|
||||
setIsSidebarOpen(false);
|
||||
}}
|
||||
>
|
||||
<ConversationTitle>{convo.title || "New Conversation"}</ConversationTitle>
|
||||
{convo.id === selectedConversation && (
|
||||
<IconButton
|
||||
as="div"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
deleteConversation(convo.id);
|
||||
}}
|
||||
style={{ padding: 4, width: 'auto', height: 'auto' }}
|
||||
>
|
||||
<Delete fontSize="small" />
|
||||
</IconButton>
|
||||
)}
|
||||
</ConversationItem>
|
||||
))}
|
||||
{conversations.length === 0 ? (
|
||||
<div style={{
|
||||
color: theme?.darkMode ? 'rgba(255,255,255,0.55)' : 'rgba(0,0,0,0.55)',
|
||||
fontSize: '0.9rem',
|
||||
lineHeight: 1.5,
|
||||
padding: '0.25rem 0.5rem'
|
||||
}}>
|
||||
No conversations yet. Start with <strong>+ New Chat</strong>.
|
||||
</div>
|
||||
) : (
|
||||
conversations.map((convo) => (
|
||||
<ConversationItem
|
||||
key={convo.id}
|
||||
$active={convo.id === selectedConversation}
|
||||
onClick={() => {
|
||||
setSelectedConversation(convo.id);
|
||||
setIsSidebarOpen(false);
|
||||
}}
|
||||
>
|
||||
<ConversationTitle>{convo.title || "New Conversation"}</ConversationTitle>
|
||||
{convo.id === selectedConversation && (
|
||||
<IconButton
|
||||
as="div"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
deleteConversation(convo.id);
|
||||
}}
|
||||
style={{ padding: 4, width: 'auto', height: 'auto' }}
|
||||
>
|
||||
<Delete fontSize="small" />
|
||||
</IconButton>
|
||||
)}
|
||||
</ConversationItem>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</Sidebar>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user