Skip to content

Instantly share code, notes, and snippets.

@WorldSEnder
Created May 1, 2022 12:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save WorldSEnder/7fbb846fac5ba8a063ec1c3826c53115 to your computer and use it in GitHub Desktop.
Save WorldSEnder/7fbb846fac5ba8a063ec1c3826c53115 to your computer and use it in GitHub Desktop.
use gloo_events::EventListener;
use wasm_bindgen::{JsCast, UnwrapThrowExt};
use yew::events::{Event, KeyboardEvent};
use yew::html::IntoPropValue;
use yew::prelude::*;
use yew::virtual_dom::VNode;
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum TooltipPosition {
Top,
Right,
Left,
Bottom,
}
impl Default for TooltipPosition {
fn default() -> Self {
Self::Top
}
}
#[derive(Clone, Debug, PartialEq, Properties)]
pub struct TooltipProps {
#[prop_or_default]
pub position: TooltipPosition,
#[prop_or_default]
pub with_arrow: bool,
pub tooltip_text: String,
#[prop_or_default]
pub children: Children,
}
#[function_component(Tooltip)]
pub fn tooltip(props: &TooltipProps) -> Html {
let tooltip_active = use_state_eq(|| false);
let [node]: [VNode; 1] = props
.children
.iter()
.collect::<Vec<_>>()
.try_into()
.expect("Expected a single vtag child");
let mut tag = match node {
VNode::VTag(tag) => *tag,
_ => panic!("The immediate children must be of type `VTag`"),
};
tag.add_attribute("data-tooltip", props.tooltip_text.clone());
let attributes = &mut tag.attributes;
let class_entry = attributes.get_mut_index_map().entry("class".into());
let class_str = class_entry.or_insert("".into());
let mut class_vec: Vec<String> = Vec::new();
for s in class_str.split_whitespace() {
class_vec.push(s.to_string());
}
let mut classes = Classes::from(class_vec.as_slice());
classes.push("tooltip");
if props.with_arrow {
classes.push("has-tooltip-arrow");
}
classes.push(match props.position {
TooltipPosition::Left => "has-tooltip-left",
TooltipPosition::Bottom => "has-tooltip-bottom",
TooltipPosition::Right => "has-tooltip-right",
TooltipPosition::Top => "has-tooltip-top",
});
if *tooltip_active {
classes.push("has-tooltip-active");
}
*class_str = classes.into_prop_value();
let node_ref = tag.node_ref.clone();
let tooltip_setter = tooltip_active.setter();
use_effect_with_deps(
move |_| {
gloo_console::debug!("Attaching listeners...");
let el = node_ref.cast::<web_sys::HtmlElement>().unwrap_throw();
let focus_listener = EventListener::new(&el, "focus", {
let tooltip_setter = tooltip_setter.clone();
move |_: &Event| {
gloo_console::debug!("Focus gained!");
tooltip_setter.set(true);
}
});
let blur_listener = EventListener::new(&el, "blur", {
let tooltip_setter = tooltip_setter.clone();
move |_: &Event| {
gloo_console::debug!("Focus lost!");
tooltip_setter.set(false);
}
});
let mouseover_listener = EventListener::new(&el, "mouseover", {
let tooltip_setter = tooltip_setter.clone();
move |_: &Event| {
gloo_console::debug!("Mouse over!");
tooltip_setter.set(true);
}
});
let mouseleave_listener = EventListener::new(&el, "mouseleave", {
move |_: &Event| {
gloo_console::debug!("Mouse leave!");
tooltip_setter.set(false);
}
});
let keydown_listener = EventListener::new(&el.clone(), "keydown", {
move |e: &Event| {
let e = e.clone().unchecked_into::<KeyboardEvent>();
if e.key() == "Escape" {
gloo_console::debug!(format!("Key down! {}", e.key()), &e);
el.blur().unwrap_throw();
}
}
});
|| {
drop(focus_listener);
drop(blur_listener);
drop(mouseover_listener);
drop(mouseleave_listener);
drop(keydown_listener);
}
},
(),
);
VNode::from(tag)
}
#[function_component]
fn App() -> Html {
html! {
<Tooltip tooltip_text="Tooltip">
<button type="text">{"Some input..."}</button>
</Tooltip>
}
}
fn main() {
yew::Renderer::<App>::new().render();
}
@TimBoettcher
Copy link

Kinda late, but many thanks for coming up with this :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment