dioxus_docs_kit/components/blog/
blog_list.rs1use dioxus::prelude::*;
2use dioxus_free_icons::Icon;
3use dioxus_free_icons::icons::ld_icons::{LdChevronLeft, LdChevronRight};
4
5use crate::blog::hooks::{ActiveTag, CurrentPage};
6use crate::blog::registry::BlogRegistry;
7
8use super::blog_card::BlogCard;
9use super::blog_meta::BlogIndexMeta;
10use super::tag_filter::TagFilter;
11
12#[component]
14pub fn BlogList(hero: Option<Element>) -> Element {
15 let registry = use_context::<&'static BlogRegistry>();
16 let ActiveTag(active_tag) = use_context::<ActiveTag>();
17 let CurrentPage(mut current_page) = use_context::<CurrentPage>();
18
19 let posts = use_memo(move || {
20 let tag = active_tag();
21 let page = current_page();
22 match tag.as_deref() {
23 Some(tag) => registry
24 .posts_page_by_tag(tag, page)
25 .into_iter()
26 .cloned()
27 .collect::<Vec<_>>(),
28 None => registry
29 .non_featured_posts_page(page)
30 .into_iter()
31 .cloned()
32 .collect::<Vec<_>>(),
33 }
34 });
35
36 let total_pages = use_memo(move || {
37 let tag = active_tag();
38 match tag.as_deref() {
39 Some(tag) => registry.total_pages_for_tag(tag),
40 None => registry.non_featured_total_pages(),
41 }
42 });
43
44 rsx! {
45 div { class: "max-w-6xl mx-auto px-4 py-12",
46 {
47 let active_tag = active_tag();
48 let (title, description) = match active_tag.as_deref() {
49 Some(tag) => (
50 format!("Blog: {tag}"),
51 format!("Browse blog posts tagged {tag}."),
52 ),
53 None => (
54 "Blog".to_string(),
55 "Latest blog posts and updates.".to_string(),
56 ),
57 };
58 rsx! {
59 BlogIndexMeta { title, description }
60 }
61 }
62 if let Some(hero) = hero {
63 {hero}
64 }
65
66 if !registry.all_tags().is_empty() {
67 div { class: "mb-8",
68 TagFilter {}
69 }
70 }
71
72 if active_tag().is_none() && registry.has_featured() {
74 div { class: "mb-10",
75 div { class: "flex items-center gap-3 mb-4",
76 span { class: "badge badge-primary badge-sm", "Featured" }
77 div { class: "h-px flex-1 bg-base-300" }
78 }
79 div { class: "grid grid-cols-1 md:grid-cols-2 gap-6",
80 for post in registry.featured_posts() {
81 BlogCard { key: "{post.slug}", post: post.clone() }
82 }
83 }
84 }
85 }
86
87 if posts.read().is_empty() {
88 div { class: "text-center py-16 text-base-content/50",
89 p { class: "text-lg", "No posts found." }
90 }
91 } else {
92 div { class: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6",
93 for post in posts.read().iter() {
94 BlogCard { key: "{post.slug}", post: post.clone() }
95 }
96 }
97 }
98
99 if total_pages() > 1 {
100 nav { class: "flex items-center justify-center gap-2 mt-12",
101 button {
102 class: "btn btn-ghost btn-sm",
103 disabled: current_page() == 0,
104 onclick: move |_| {
105 if current_page() > 0 {
106 current_page -= 1;
107 }
108 },
109 Icon { class: "size-4", icon: LdChevronLeft }
110 "Prev"
111 }
112 for page in 0..total_pages() {
113 {
114 let is_active = page == current_page();
115 let class = if is_active {
116 "btn btn-sm btn-primary"
117 } else {
118 "btn btn-sm btn-ghost"
119 };
120 rsx! {
121 button {
122 class: "{class}",
123 onclick: move |_| current_page.set(page),
124 "{page + 1}"
125 }
126 }
127 }
128 }
129 button {
130 class: "btn btn-ghost btn-sm",
131 disabled: current_page() + 1 >= total_pages(),
132 onclick: move |_| {
133 if current_page() + 1 < total_pages() {
134 current_page += 1;
135 }
136 },
137 "Next"
138 Icon { class: "size-4", icon: LdChevronRight }
139 }
140 }
141 }
142 }
143 }
144}