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