yew_bs/components/
scrollspy.rs

1use yew::prelude::*;
2use web_sys::Element;
3use wasm_bindgen::JsValue;
4use crate::interop::BsScrollSpy;
5#[derive(Properties, PartialEq)]
6pub struct ScrollSpyProps {
7    #[prop_or_default]
8    pub children: Children,
9    #[prop_or_default]
10    pub target: Option<AttrValue>,
11    #[prop_or(10)]
12    pub offset: i32,
13    #[prop_or(ScrollMethod::Auto)]
14    pub method: ScrollMethod,
15    #[prop_or_default]
16    pub nav_selector: Option<AttrValue>,
17    #[prop_or_default]
18    pub class: Option<AttrValue>,
19    #[prop_or_default]
20    pub node_ref: NodeRef,
21}
22#[derive(Clone, Copy, PartialEq, Debug)]
23pub enum ScrollMethod {
24    Auto,
25    Offset,
26    Position,
27}
28impl ScrollMethod {
29    pub fn as_str(&self) -> &'static str {
30        match self {
31            ScrollMethod::Auto => "auto",
32            ScrollMethod::Offset => "offset",
33            ScrollMethod::Position => "position",
34        }
35    }
36}
37/// Bootstrap ScrollSpy component
38#[function_component(ScrollSpy)]
39pub fn scrollspy(props: &ScrollSpyProps) -> Html {
40    let node_ref = props.node_ref.clone();
41    let options = {
42        let opts = js_sys::Object::new();
43        if let Some(target) = &props.target {
44            js_sys::Reflect::set(
45                    &opts,
46                    &"target".into(),
47                    &JsValue::from(target.as_str()),
48                )
49                .unwrap();
50        }
51        js_sys::Reflect::set(&opts, &"offset".into(), &JsValue::from(props.offset))
52            .unwrap();
53        js_sys::Reflect::set(
54                &opts,
55                &"method".into(),
56                &JsValue::from(props.method.as_str()),
57            )
58            .unwrap();
59        JsValue::from(opts)
60    };
61    {
62        let node_ref = node_ref.clone();
63        use_effect_with(
64            (options.clone(), props.target.clone()),
65            move |_| {
66                if let Some(element) = node_ref.cast::<Element>() {
67                    let _bs_scrollspy = BsScrollSpy::new(&element, Some(&options));
68                }
69                || ()
70            },
71        );
72    }
73    let children = props.children.clone();
74    html! {
75        < div ref = { props.node_ref.clone() } class = { props.class.clone() } data - bs
76        - spy = "scroll" data - bs - target = { props.target.clone() } data - bs - offset
77        = { props.offset.to_string() } data - bs - method = { props.method.as_str() } > {
78        for children.iter() } </ div >
79    }
80}
81#[derive(Properties, PartialEq)]
82pub struct ScrollSpyNavProps {
83    #[prop_or_default]
84    pub children: Children,
85    #[prop_or_default]
86    pub class: Option<AttrValue>,
87    #[prop_or_default]
88    pub variant: Option<ScrollSpyNavVariant>,
89    #[prop_or_default]
90    pub node_ref: NodeRef,
91}
92#[derive(Clone, Copy, PartialEq, Debug)]
93pub enum ScrollSpyNavVariant {
94    Pills,
95    Tabs,
96    Underline,
97}
98impl ScrollSpyNavVariant {
99    pub fn as_str(&self) -> &'static str {
100        match self {
101            ScrollSpyNavVariant::Pills => "nav-pills",
102            ScrollSpyNavVariant::Tabs => "nav-tabs",
103            ScrollSpyNavVariant::Underline => "nav-underline",
104        }
105    }
106}
107/// Bootstrap ScrollSpy Navigation component
108#[function_component(ScrollSpyNav)]
109pub fn scrollspy_nav(props: &ScrollSpyNavProps) -> Html {
110    let mut classes = Classes::new();
111    classes.push("nav");
112    classes.push("nav-scrollspy");
113    if let Some(variant) = &props.variant {
114        classes.push(variant.as_str());
115    }
116    if let Some(class) = &props.class {
117        classes.push(class.to_string());
118    }
119    html! {
120        < nav ref = { props.node_ref.clone() } class = { classes } > { for props.children
121        .iter() } </ nav >
122    }
123}
124/// Props for the ScrollSpyNavItem component
125#[derive(Properties, PartialEq)]
126pub struct ScrollSpyNavItemProps {
127    /// Navigation item content
128    #[prop_or_default]
129    pub children: Children,
130    /// Target element ID (without #)
131    pub href: AttrValue,
132    /// Whether this item is currently active
133    #[prop_or_default]
134    pub active: bool,
135    /// Whether this item is disabled
136    #[prop_or_default]
137    pub disabled: bool,
138    /// Additional CSS classes
139    #[prop_or_default]
140    pub class: Option<AttrValue>,
141    /// Additional HTML attributes
142    #[prop_or_default]
143    pub node_ref: NodeRef,
144}
145/// Bootstrap ScrollSpy Navigation Item component
146#[function_component(ScrollSpyNavItem)]
147pub fn scrollspy_nav_item(props: &ScrollSpyNavItemProps) -> Html {
148    let mut classes = Classes::new();
149    classes.push("nav-item");
150    if let Some(class) = &props.class {
151        classes.push(class.to_string());
152    }
153    let link_classes = {
154        let mut link_classes = Classes::new();
155        link_classes.push("nav-link");
156        if props.active {
157            link_classes.push("active");
158        }
159        if props.disabled {
160            link_classes.push("disabled");
161        }
162        link_classes
163    };
164    let href = format!("#{}", props.href.as_str());
165    html! {
166        < li class = { classes } > < a ref = { props.node_ref.clone() } class = {
167        link_classes } href = { href } > { for props.children.iter() } </ a > </ li >
168    }
169}
170/// Props for the ScrollSpyContent component
171#[derive(Properties, PartialEq)]
172pub struct ScrollSpyContentProps {
173    /// Content sections
174    #[prop_or_default]
175    pub children: Children,
176    /// Additional CSS classes
177    #[prop_or_default]
178    pub class: Option<AttrValue>,
179    /// Additional HTML attributes
180    #[prop_or_default]
181    pub node_ref: NodeRef,
182}
183/// Bootstrap ScrollSpy Content component
184#[function_component(ScrollSpyContent)]
185pub fn scrollspy_content(props: &ScrollSpyContentProps) -> Html {
186    let mut classes = Classes::new();
187    if let Some(class) = &props.class {
188        classes.push(class.to_string());
189    }
190    html! {
191        < div ref = { props.node_ref.clone() } class = { classes } > { for props.children
192        .iter() } </ div >
193    }
194}
195/// Props for the ScrollSpySection component
196#[derive(Properties, PartialEq)]
197pub struct ScrollSpySectionProps {
198    /// Section content
199    #[prop_or_default]
200    pub children: Children,
201    /// Section ID (for navigation targeting)
202    pub id: AttrValue,
203    /// Additional CSS classes
204    #[prop_or_default]
205    pub class: Option<AttrValue>,
206    /// Additional HTML attributes
207    #[prop_or_default]
208    pub node_ref: NodeRef,
209}
210/// Bootstrap ScrollSpy Section component
211#[function_component(ScrollSpySection)]
212pub fn scrollspy_section(props: &ScrollSpySectionProps) -> Html {
213    let mut classes = Classes::new();
214    if let Some(class) = &props.class {
215        classes.push(class.to_string());
216    }
217    html! {
218        < section ref = { props.node_ref.clone() } id = { props.id.clone() } class = {
219        classes } > { for props.children.iter() } </ section >
220    }
221}