dioxus_docs_kit/components/blog/
blog_card.rs1use dioxus::prelude::*;
2use dioxus_free_icons::Icon;
3use dioxus_free_icons::icons::ld_icons::LdClock;
4
5use crate::BlogContext;
6use crate::blog::registry::BlogRegistry;
7use crate::blog::types::BlogPost;
8
9#[component]
11pub fn BlogCard(post: BlogPost) -> Element {
12 let ctx = use_context::<BlogContext>();
13 let registry = use_context::<&'static BlogRegistry>();
14 let slug = post.slug.clone();
15 let href = format!("{}/{}", ctx.base_path, slug);
16 let author = registry.get_author(&post.frontmatter.author);
17 let date_display = registry.format_date(&post.frontmatter.date);
18
19 rsx! {
20 Link {
21 to: NavigationTarget::Internal(href),
22 class: "group flex flex-col rounded-xl border border-base-300 bg-base-200/30 hover:border-primary/30 hover:shadow-lg transition-all duration-200 overflow-hidden",
23
24 if let Some(ref cover) = post.frontmatter.cover_image {
25 div { class: "aspect-video overflow-hidden bg-base-300",
26 img {
27 src: "{cover}",
28 alt: "{post.frontmatter.title}",
29 class: "w-full h-full object-cover group-hover:scale-105 transition-transform duration-300",
30 }
31 }
32 }
33
34 div { class: "flex flex-col flex-1 p-5 gap-3",
35 if !post.frontmatter.tags.is_empty() {
36 div { class: "flex flex-wrap gap-1.5",
37 for tag in post.frontmatter.tags.iter().take(3) {
38 span { class: "badge badge-sm badge-outline", "{tag}" }
39 }
40 if post.frontmatter.tags.len() > 3 {
41 span { class: "badge badge-sm badge-ghost",
42 "+{post.frontmatter.tags.len() - 3}"
43 }
44 }
45 }
46 }
47
48 h2 { class: "text-lg font-semibold leading-snug group-hover:text-primary transition-colors line-clamp-2",
49 "{post.frontmatter.title}"
50 }
51
52 if let Some(ref desc) = post.frontmatter.description {
53 p { class: "text-sm text-base-content/60 leading-relaxed line-clamp-3",
54 "{desc}"
55 }
56 }
57
58 div { class: "mt-auto pt-3 border-t border-base-300/60 flex items-center gap-3 text-xs text-base-content/50",
59 if let Some(author) = author {
60 div { class: "flex items-center gap-1.5",
61 if let Some(ref avatar) = author.avatar {
62 img {
63 src: "{avatar}",
64 alt: "{author.name}",
65 class: "size-5 rounded-full",
66 }
67 }
68 span { "{author.name}" }
69 }
70 }
71 span { "{date_display}" }
72 div { class: "flex items-center gap-1 ml-auto",
73 Icon { class: "size-3", icon: LdClock }
74 span { "{post.reading_time_minutes} min read" }
75 }
76 }
77 }
78 }
79 }
80}