yew_bs/components/
carousel.rs1use yew::prelude::*;
2use web_sys::Element;
3use crate::interop::BsCarousel;
4#[derive(Properties, PartialEq)]
5pub struct CarouselItemProps {
6 #[prop_or_default]
7 pub children: Children,
8 #[prop_or_default]
9 pub class: Option<AttrValue>,
10 #[prop_or_default]
11 pub interval: Option<u32>,
12 #[prop_or_default]
13 pub active: bool,
14}
15#[function_component(CarouselItem)]
16pub fn carousel_item(props: &CarouselItemProps) -> Html {
17 let mut classes = Classes::new();
18 classes.push("carousel-item");
19 if props.active {
20 classes.push("active");
21 }
22 if let Some(class) = &props.class {
23 classes.push(class.to_string());
24 }
25 let data_interval = props.interval.map(|i| i.to_string());
26 html! {
27 < div class = { classes } data - bs - interval = { data_interval } > { for props
28 .children.iter() } </ div >
29 }
30}
31#[derive(Properties, PartialEq)]
32pub struct CarouselProps {
33 #[prop_or_default]
34 pub children: Children,
35 #[prop_or(true)]
36 pub indicators: bool,
37 #[prop_or(true)]
38 pub controls: bool,
39 #[prop_or_default]
40 pub captions: bool,
41 #[prop_or(true)]
42 pub ride: bool,
43 #[prop_or(true)]
44 pub pause: bool,
45 #[prop_or(true)]
46 pub wrap: bool,
47 #[prop_or(true)]
48 pub touch: bool,
49 #[prop_or_default]
50 pub interval: Option<u32>,
51 #[prop_or(true)]
52 pub keyboard: bool,
53 #[prop_or_default]
54 pub active_index: usize,
55 #[prop_or_default]
56 pub on_slide_change: Option<Callback<usize>>,
57 #[prop_or_default]
58 pub class: Option<AttrValue>,
59 #[prop_or_default]
60 pub node_ref: NodeRef,
61}
62#[function_component(Carousel)]
63pub fn carousel(props: &CarouselProps) -> Html {
64 let carousel_ref = use_node_ref();
65 let bs_carousel = use_state(|| None::<BsCarousel>);
66 let active_index = use_state(|| props.active_index);
67 {
68 let active_index_state = active_index.clone();
69 use_effect_with(
70 props.active_index,
71 move |active_index_prop| {
72 active_index_state.set(*active_index_prop);
73 },
74 );
75 }
76 {
77 let carousel_ref = carousel_ref.clone();
78 let bs_carousel = bs_carousel.clone();
79 let ride = props.ride;
80 let wrap = props.wrap;
81 let pause = props.pause;
82 let touch = props.touch;
83 let keyboard = props.keyboard;
84 let interval = props.interval;
85 use_effect(move || {
86 if let Some(element) = carousel_ref.cast::<Element>() {
87 let options = js_sys::Object::new();
88 let _ = js_sys::Reflect::set(
89 &options,
90 &"ride".into(),
91 &if ride { "carousel".into() } else { false.into() },
92 );
93 let _ = js_sys::Reflect::set(&options, &"wrap".into(), &wrap.into());
94 let _ = js_sys::Reflect::set(
95 &options,
96 &"pause".into(),
97 &if pause { "hover".into() } else { false.into() },
98 );
99 let _ = js_sys::Reflect::set(&options, &"touch".into(), &touch.into());
100 let _ = js_sys::Reflect::set(
101 &options,
102 &"keyboard".into(),
103 &keyboard.into(),
104 );
105 if let Some(interval) = interval {
106 let _ = js_sys::Reflect::set(
107 &options,
108 &"interval".into(),
109 &interval.into(),
110 );
111 }
112 bs_carousel.set(Some(BsCarousel::new(&element, Some(&options.into()))));
113 }
114 || {}
115 });
116 }
117 let mut classes = Classes::new();
118 classes.push("carousel");
119 classes.push("slide");
120 if let Some(class) = &props.class {
121 classes.push(class.to_string());
122 }
123 let indicators = if props.indicators {
124 let children_count = props.children.len();
125 let current_active_index = *active_index;
126 Some(
127 html! {
128 < div class = "carousel-indicators" > { (0..children_count).map(| index |
129 { let is_active = index == current_active_index; let onclick = { let
130 bs_carousel = bs_carousel.clone(); let active_index_state = active_index
131 .clone(); let on_slide_change = props.on_slide_change.clone();
132 Callback::from(move | _ | { if let Some(bs_carousel) = &* bs_carousel {
133 bs_carousel.to(index as u32); } active_index_state.set(index); if let
134 Some(callback) = & on_slide_change { callback.emit(index); } }) }; html!
135 { < button type = "button" data - bs - target = "" data - bs - slide - to
136 = { index.to_string() } class = { if is_active { "active" } else { "" } }
137 aria - current = { if is_active { "true" } else { "false" } } aria -
138 label = { format!("Slide {}", index + 1) } onclick = { onclick } /> } })
139 .collect::< Html > () } </ div >
140 },
141 )
142 } else {
143 None
144 };
145 let controls = if props.controls {
146 let prev_onclick = {
147 let bs_carousel = bs_carousel.clone();
148 let active_index_state = active_index.clone();
149 let children_count = props.children.len();
150 let on_slide_change = props.on_slide_change.clone();
151 Callback::from(move |_| {
152 if let Some(bs_carousel) = &*bs_carousel {
153 bs_carousel.prev();
154 }
155 let new_index = if *active_index_state == 0 {
156 children_count - 1
157 } else {
158 *active_index_state - 1
159 };
160 active_index_state.set(new_index);
161 if let Some(callback) = &on_slide_change {
162 callback.emit(new_index);
163 }
164 })
165 };
166 let next_onclick = {
167 let bs_carousel = bs_carousel.clone();
168 let active_index_state = active_index.clone();
169 let children_count = props.children.len();
170 let on_slide_change = props.on_slide_change.clone();
171 Callback::from(move |_| {
172 if let Some(bs_carousel) = &*bs_carousel {
173 bs_carousel.next();
174 }
175 let new_index = (*active_index_state + 1) % children_count;
176 active_index_state.set(new_index);
177 if let Some(callback) = &on_slide_change {
178 callback.emit(new_index);
179 }
180 })
181 };
182 Some(
183 html! {
184 <> < button class = "carousel-control-prev" type = "button" data - bs -
185 target = "" data - bs - slide = "prev" onclick = { prev_onclick } > <
186 span class = "carousel-control-prev-icon" aria - hidden = "true" ></ span
187 > < span class = "visually-hidden" > { "Previous" } </ span > </ button >
188 < button class = "carousel-control-next" type = "button" data - bs -
189 target = "" data - bs - slide = "next" onclick = { next_onclick } > <
190 span class = "carousel-control-next-icon" aria - hidden = "true" ></ span
191 > < span class = "visually-hidden" > { "Next" } </ span > </ button > </>
192 },
193 )
194 } else {
195 None
196 };
197 html! {
198 < div class = { classes } data - bs - ride = { if props.ride { "carousel" } else
199 { "false" } } ref = { carousel_ref } > { indicators } < div class =
200 "carousel-inner" > { props.children.iter().enumerate().map(| (index, child) | {
201 let is_active = index == * active_index; html! { < CarouselItem active = {
202 is_active } > { child } </ CarouselItem > } }).collect::< Html > () } </ div > {
203 controls } </ div >
204 }
205}