yew_bs/components/
carousel.rs

1use 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}