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