feat: add smooth scroll
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Kasper Juul Hermansen 2025-01-12 16:15:13 +01:00
parent 13e6fa474d
commit 955946d623
Signed by: kjuulh
SSH Key Fingerprint: SHA256:RjXh0p7U6opxnfd3ga/Y9TCo18FYlHFdSpRIV72S/QM
3 changed files with 62 additions and 10 deletions

1
Cargo.lock generated
View File

@ -75,6 +75,7 @@ dependencies = [
"serde_json", "serde_json",
"server_fn", "server_fn",
"thiserror 2.0.11", "thiserror 2.0.11",
"web-sys",
] ]
[[package]] [[package]]

View File

@ -20,6 +20,10 @@ cfg-if.workspace = true
thiserror.workspace = true thiserror.workspace = true
reqwest = { version = "0.12.11", optional = true, features = ["json"] } reqwest = { version = "0.12.11", optional = true, features = ["json"] }
serde_json = { version = "1.0.134", optional = true } serde_json = { version = "1.0.134", optional = true }
web-sys = { version = "0.3.76", features = [
"ScrollBehavior",
"ScrollToOptions",
] }
[features] [features]
default = [] default = []

View File

@ -1,8 +1,9 @@
use crate::error_template::{AppError, ErrorTemplate}; use crate::error_template::{AppError, ErrorTemplate};
use leptos::prelude::*; use leptos::{ev::SubmitEvent, html, prelude::*};
use leptos_meta::*; use leptos_meta::*;
use leptos_router::{components::*, StaticSegment}; use leptos_router::{components::*, StaticSegment};
use message::Message;
pub mod error_template; pub mod error_template;
@ -55,9 +56,49 @@ pub fn App() -> impl IntoView {
} }
} }
fn smooth_scroll_to_bottom() {
if let Some(window) = web_sys::window() {
if let Some(document) = window.document() {
if let Some(body) = document.get_element_by_id("messages") {
body.set_scroll_top(body.scroll_height());
leptos::logging::log!("moving to top");
}
}
}
}
#[component] #[component]
pub fn HomePage() -> impl IntoView { pub fn HomePage() -> impl IntoView {
let messages = message::get_messages(); let (messages, set_messages) = signal(
message::get_messages()
.into_iter()
.enumerate()
.map(|(index, value)| (index, ArcRwSignal::new(value)))
.collect::<Vec<_>>(),
);
let (input, set_input) = signal("".to_string());
let on_submit = move |ev: SubmitEvent| {
// stop the page from reloading!
ev.prevent_default();
let messages_len = messages.get().len();
let mut messages = set_messages.write();
messages.push((
messages_len,
ArcRwSignal::new(Message {
role: "user".into(),
content: input.get().into(),
}),
));
set_input.set("".into());
request_animation_frame(move || {
smooth_scroll_to_bottom();
});
};
view! { view! {
<div class="flex flex-col h-screen bg-gray-50"> <div class="flex flex-col h-screen bg-gray-50">
@ -70,11 +111,14 @@ pub fn HomePage() -> impl IntoView {
</div> </div>
</header> </header>
<div class="overflow-y-auto flex-1 px-4"> <div class="overflow-y-auto flex-1 px-4" id="messages">
<div class="py-6 mx-auto space-y-6 max-w-5xl"> <div class="py-6 mx-auto space-y-6 max-w-5xl">
{messages <For
.iter() each=move || messages.get()
.map(|message| { key=|message| message.0
children=move |(_id, message)| {
let message = message.read();
view! { view! {
<div class=format!( <div class=format!(
"flex {}", "flex {}",
@ -94,24 +138,27 @@ pub fn HomePage() -> impl IntoView {
)>{message.content.clone()}</div> )>{message.content.clone()}</div>
</div> </div>
} }
}) }
.collect_view()} />
<div class="flex justify-start"> <div class="flex justify-start">
<div class="py-3 px-4 bg-white rounded-sm border border-gray-200 max-w-[80%]"> <div class="py-3 px-4 bg-white rounded-sm border border-gray-200 max-w-[80%]">
"Loading..." "Loading..."
</div> </div>
</div> <div /> </div>
<div />
</div> </div>
</div> </div>
<div class="py-4 px-4 bg-white border-t border-gray-200"> <div class="py-4 px-4 bg-white border-t border-gray-200">
<form class="mx-auto max-w-5xl"> <form class="mx-auto max-w-5xl" on:submit=on_submit>
<div class="flex gap-4"> <div class="flex gap-4">
<input <input
type="text" type="text"
placeholder="Type your medical question here..." placeholder="Type your medical question here..."
class="flex-1 py-2 px-4 rounded-sm border border-gray-200 focus:border-transparent focus:ring-2 focus:ring-blue-500 focus:outline-none" class="flex-1 py-2 px-4 rounded-sm border border-gray-200 focus:border-transparent focus:ring-2 focus:ring-blue-500 focus:outline-none"
bind:value=(input, set_input)
/> />
<button <button
type="submit" type="submit"
class="flex gap-2 items-center py-2 px-4 text-white bg-blue-500 rounded-sm hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed" class="flex gap-2 items-center py-2 px-4 text-white bg-blue-500 rounded-sm hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed"