feedsignal/crates/web/src/app/article_row.rs
Austin Schaefer 21103b1f25 Fix article row layout: button overlapping title, uneven summary gaps
article-row had no layout structure at all — title link, score badge,
and the dismiss button were plain inline siblings in a block <li>, so a
long title pushed the button flush against (or past) the row's edge.
Group title+score into a header row and make the row itself a flex
column so summary/topics/button always stack on their own line below,
regardless of title length.

Also stopped rendering the summary <p> and topics div at all when
there's no content (many RSS entries have no description), instead of
emitting empty elements — spacing was uneven because those still
reserved a line's worth of height. Left this as an explicit Rust-side
branch rather than CSS :empty: an empty <p> still contains a
zero-length text node, and whether :empty matches that is
inconsistent across engines, whereas the topics div (an empty `for`
loop) has zero children and would be a safe :empty candidate — but
kept both on the same explicit mechanism for consistency.

Swapped the dismiss button from a raw <button> to the dx Button
component, matching the rest of the row and the project's preference
for the component library over raw elements.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqTJmazHBQK878vjQ4JnnF
2026-09-03 18:08:14 +02:00

41 lines
1.4 KiB
Rust

use crate::api;
use crate::api::ArticleView;
use crate::components::badge::{Badge, BadgeVariant};
use crate::components::button::{Button, ButtonSize, ButtonVariant};
use dioxus::prelude::*;
#[component]
pub fn ArticleRow(article: ArticleView) -> Element {
let score_pct = article.final_score.map(|s| (s * 100.0).round() as i32);
rsx! {
li { class: "article-row",
div { class: "article-row-header",
a { href: "{article.url}", target: "_blank", "{article.title}" }
if let Some(pct) = score_pct {
Badge { variant: BadgeVariant::Secondary, "{pct}%" }
}
}
if !article.summary.trim().is_empty() {
p { class: "summary", "{article.summary}" }
}
if !article.topics.is_empty() {
div { class: "topics",
for topic in article.topics.iter() {
Badge { variant: BadgeVariant::Outline, "{topic}" }
}
}
}
Button {
variant: ButtonVariant::Outline,
size: ButtonSize::Sm,
onclick: move |_| {
let id = article.id.clone();
async move {
let _ = api::articles::mark_dismissed(id).await;
}
},
"Not relevant"
}
}
}
}