feat: scroll to current contact in sidebar by default

This commit is contained in:
Robert Perce 2026-04-07 10:49:04 -05:00
parent 0baf51646e
commit 62b0efac04
4 changed files with 36 additions and 7 deletions

View file

@ -96,9 +96,5 @@ test('bullet points in free text display well', async ({ page }) => {
});
home: contact list scrolls in screen, not off screen
home: clicking off contact list closes it
home: contact list is sorted ignoring case
home: contact list should scroll to current contact in center of view
journal: bullet points don't display
*/

View file

@ -81,3 +81,21 @@ test('upcoming and recent show at least one birthday a week away', async ({ page
await expect(page.locator('#upcoming').getByRole('link')).toHaveCount(4);
await expect(page.locator('#recent').getByRole('link')).toHaveCount(4);
});
test('contact list scrolls (independently) to current contact in center of view', async ({ page }) => {
for (let count = 0; count < 30; count++) {
await verifyCreateUser(page, { names: [`Contact${count < 10 ? '0' + count : count}`] });
}
await page.goto('/contact/28');
await expect(page.getByRole('navigation').getByRole('link', { name: /Contact28/ })).toBeVisible();
expect(await page.locator('main').evaluate(e => e.scrollTop)).toEqual(0);
await page.goto('/contact/16');
await expect(page.locator('#nav-link-16')).toBeVisible();
const linkPos: number = await page.locator('#nav-link-16').evaluate(e => e.getBoundingClientRect().y);
// roughly centered is fine, not that fussy about headers and whatnot
expect(Math.abs(linkPos - (await page.evaluate('window.innerHeight/2') as number))).toBeLessThan(200);
});

View file

@ -61,6 +61,21 @@ fn human_delta(span: &jiff::Span) -> String {
mod get {
use super::*;
fn scroll_to(id: DbId) -> String {
format!(
"\
const top = document\
.getElementById('nav-link-{}')\
?.getBoundingClientRect()\
?.top;\
console.log({{ top }});\
top && document\
.getElementById('contacts-sidebar')\
.scrollTo({{top: top+window.innerHeight/2,left:0,behavior:'instant'}});",
id
)
}
pub async fn contact(
auth_session: AuthSession,
State(state): State<AppState>,
@ -125,7 +140,7 @@ mod get {
html! {
a href=(format!("/contact/{}/edit", contact_id)) { "Edit" }
div id="fields" {
div #fields x-init=(scroll_to(contact_id)) {
label { @if contact.names.len() > 1 { "names" } @else { "name" }}
div {
@for name in &contact.names {
@ -249,7 +264,7 @@ mod get {
div #error;
}
#fields x-data=(json!({ "status": contact.status() })){
#fields x-data=(json!({ "status": contact.status() })) x-init=(scroll_to(contact_id)) {
label { @if contact.names.len() > 1 { "names" } @else { "name" }}
div #names x-data=(format!("{{ names: {:?}, new_name: '' }}", &contact.names)) {
template x-for="(name, idx) in names" {

View file

@ -117,7 +117,7 @@ impl Layout {
li { button hx-post="/contact/new" { "+ Add Contact" } }
@for link in &self.contact_links {
li {
a href=(format!("/contact/{}", link.contact_id)) {
a id=(format!("nav-link-{}", link.contact_id)) href=(format!("/contact/{}", link.contact_id)) {
(link.name)
}
}