1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
use crate::prelude::*;
use web_sys::window;
use web_sys::{ScrollBehavior, ScrollToOptions};

/// Properties for the Link component.
#[derive(Properties, Clone, PartialEq)]
pub struct LinkProps {
    /// The target URL for the link.
    #[prop_or_default]
    pub to: &'static str,

    /// The CSS class for styling the link.
    #[prop_or_default]
    pub class: &'static str,

    /// The target attribute for the link.
    #[prop_or("_blank")]
    pub target: &'static str,

    /// The "rel" attribute for the link.
    #[prop_or("noreferrer")]
    pub rel: &'static str,

    /// The content to be displayed within the link.
    #[prop_or_default]
    pub children: Html,

    /// Enable scrolling behavior when clicking the link.
    #[prop_or_default]
    pub scroll: bool,

    /// Offset for the scrolling behavior, specifying how far from the top the scroll should stop.
    #[prop_or_default]
    pub scroll_offset: f64,

    /// Scroll behavior when clicking the link. Valid values: "auto", "instant", "smooth".
    #[prop_or("auto")]
    pub scroll_behavior: &'static str,

    /// Indicates the current state of the link in a navigation menu. Valid values: "page", "step", "location", "date", "time", "true", "false".
    #[prop_or_default]
    pub aria_current: &'static str,

    /// Describes the link using the ID of the element that provides a description.
    #[prop_or_default]
    pub aria_describedby: &'static str,

    /// Indicates whether the content associated with the link is currently expanded or collapsed. Valid values: "true", "false".
    #[prop_or_default]
    pub aria_expanded: &'static str,

    /// Indicates whether the link is currently hidden from the user. Valid values: "true", "false".
    #[prop_or_default]
    pub aria_hidden: &'static str,

    /// Indicates whether the content associated with the link is live and dynamic. Valid values: "off", "assertive", "polite".
    #[prop_or_default]
    pub aria_live: &'static str,

    /// Indicates whether the link is currently pressed or selected. Valid values: "true", "false", "mixed", "undefined".
    #[prop_or_default]
    pub aria_pressed: &'static str,

    /// ID of the element that the link controls or owns.
    #[prop_or_default]
    pub aria_controls: &'static str,

    /// ID of the element that labels the link.
    #[prop_or_default]
    pub aria_labelledby: &'static str,
}

/// The Link component is used for creating accessible links with additional features.
///
/// # Arguments
/// * `props` - The properties of the component.
///
/// # Returns
/// (Html): An HTML representation of the link component.
///
/// # Examples
/// ```
/// // Example of using the Link component
/// use next_rs::{Link, LinkProps};
/// use next_rs::prelude::*;
///
/// #[func]
/// pub fn my_component() -> Html {
///
///     rsx! {
///         <Link
///             scroll_offset=300.0
///             scroll_behavior="smooth"
///             to={"#about"}
///             scroll=true
///         >{ "Go Home" }</Link>
///     }
/// }
/// ```
#[function_component(Link)]
pub fn link(props: &LinkProps) -> Html {
    let props = props.clone();

    let (target, href) = if props.to.starts_with("/#") {
        // local anchor
        ("_self", &props.to[1..])
    } else if props.to.starts_with('#') {
        // also local anchor
        ("_self", props.to)
    } else {
        // external
        (props.target, props.to)
    };
    let onclick = Callback::from(move |event: MouseEvent| {
        if props.scroll {
            let scroll_behavior = match props.scroll_behavior {
                "auto" => ScrollBehavior::Auto,
                "instant" => ScrollBehavior::Instant,
                "smooth" => ScrollBehavior::Smooth,
                _ => ScrollBehavior::Auto,
            };

            if props.to.starts_with('#') || props.to.starts_with("/#") {
                // Prevent default navigation behavior("instant")
                event.prevent_default();
                // Local anchor link
                if let Some(element) = window()
                    .and_then(|win| win.document())
                    .and_then(|doc| doc.get_element_by_id(&href[1..]))
                {
                    let offset_top = element.get_bounding_client_rect().y();
                    window()
                        .and_then(|win| {
                            Some(
                                win.scroll_to_with_scroll_to_options(
                                    &ScrollToOptions::new()
                                        .top(offset_top)
                                        .behavior(scroll_behavior),
                                ),
                            )
                        })
                        .expect("Failed to scroll to local anchor link");
                } else {
                    // Fallback to prop offset if element is not found
                    window()
                        .and_then(|win| {
                            Some(
                                win.scroll_to_with_scroll_to_options(
                                    &ScrollToOptions::new()
                                        .top(props.scroll_offset)
                                        .behavior(scroll_behavior),
                                ),
                            )
                        })
                        .expect("Failed to scroll to fallback offset");
                }
            } else {
                // External link
                window()
                    .and_then(|win| {
                        Some(
                            win.scroll_to_with_scroll_to_options(
                                &ScrollToOptions::new()
                                    .top(props.scroll_offset)
                                    .behavior(scroll_behavior),
                            ),
                        )
                    })
                    .expect("Failed to scroll to external link");
            }
        }
    });
    let aria_label = "Link to ".to_string() + &href;

    let tabindex = if props.scroll { "0" } else { "-1" };

    rsx! {
        <a
            href={href}
            target={target}
            rel={props.rel}
            class={props.class}
            onclick={onclick}
            role="link"
            tabindex={tabindex}
            aria-label={aria_label.clone()}
            title={aria_label.clone()}
            aria-haspopup="true"
            aria-current={props.aria_current}
            aria-describedby={props.aria_describedby}
            aria-expanded={props.aria_expanded}
            aria-hidden={props.aria_hidden}
            aria-live={props.aria_live}
            aria-pressed={props.aria_pressed}
            aria-controls={props.aria_controls}
            aria-labelledby={props.aria_labelledby}
        >{ props.children.clone() }</a>
    }
}