feat: add scroll to bottom button and auto-scroll on session load

This commit is contained in:
Alexej Wolff
2026-02-11 16:29:50 +01:00
parent e5c7bd1b0d
commit dae3c88020
2 changed files with 63 additions and 1 deletions
+35
View File
@@ -225,6 +225,33 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
position: relative;
}
.scroll-to-bottom-btn {
position: absolute;
bottom: 5rem;
right: 1rem;
width: 40px;
height: 40px;
background: rgba(30, 30, 30, 0.9);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 50%;
color: #fff;
font-size: 1.25rem;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
z-index: 5;
transition: all 0.2s;
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
.scroll-to-bottom-btn:hover {
background: rgba(50, 50, 50, 0.95);
transform: scale(1.05);
} }
.messages-container { .messages-container {
@@ -629,4 +656,12 @@
right: 0; right: 0;
left: auto; left: auto;
} }
.scroll-to-bottom-btn {
width: 36px;
height: 36px;
bottom: 4.5rem;
right: 0.75rem;
font-size: 1rem;
}
} }
+28 -1
View File
@@ -60,8 +60,10 @@ export default function GamePage() {
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [streamingContent, setStreamingContent] = useState(""); const [streamingContent, setStreamingContent] = useState("");
const [showSessionMenu, setShowSessionMenu] = useState(false); const [showSessionMenu, setShowSessionMenu] = useState(false);
const [showScrollButton, setShowScrollButton] = useState(false);
const abortControllerRef = useRef<AbortController | null>(null); const abortControllerRef = useRef<AbortController | null>(null);
const messagesEndRef = useRef<HTMLDivElement>(null); const messagesEndRef = useRef<HTMLDivElement>(null);
const messagesContainerRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLTextAreaElement>(null); const inputRef = useRef<HTMLTextAreaElement>(null);
useEffect(() => { useEffect(() => {
@@ -159,10 +161,25 @@ export default function GamePage() {
scrollToBottom(); scrollToBottom();
}, [session?.messages]); }, [session?.messages]);
// Scroll to bottom on session load
useEffect(() => {
if (session && !isInitialLoading) {
setTimeout(() => scrollToBottom(), 100);
}
}, [currentSessionId, isInitialLoading]);
const scrollToBottom = () => { const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
}; };
const handleScroll = () => {
const container = messagesContainerRef.current;
if (!container) return;
const { scrollTop, scrollHeight, clientHeight } = container;
const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
setShowScrollButton(distanceFromBottom > 200);
};
const startStory = async ( const startStory = async (
storyData: Story, storyData: Story,
sessionData: GameSession, sessionData: GameSession,
@@ -528,7 +545,11 @@ export default function GamePage() {
</header> </header>
<div className="game-content"> <div className="game-content">
<div className="messages-container"> <div
className="messages-container"
ref={messagesContainerRef}
onScroll={handleScroll}
>
{session?.messages.map((message) => ( {session?.messages.map((message) => (
<div key={message.id} className={`message ${message.role}`}> <div key={message.id} className={`message ${message.role}`}>
<div className="message-content"> <div className="message-content">
@@ -571,6 +592,12 @@ export default function GamePage() {
<div ref={messagesEndRef} /> <div ref={messagesEndRef} />
</div> </div>
{showScrollButton && (
<button className="scroll-to-bottom-btn" onClick={scrollToBottom}>
</button>
)}
{/* RPG кнопки скрыты — раскомментировать при необходимости {/* RPG кнопки скрыты — раскомментировать при необходимости
<div className="quick-actions"> <div className="quick-actions">
<button onClick={() => handleQuickAction("Осмотреться вокруг")}> <button onClick={() => handleQuickAction("Осмотреться вокруг")}>