use dioxus::prelude::*;
use crate::{
CURSOR_CSS,
animation::{
AnimationConfig, default_splitter, final_class_name, first_string, normalize_speed,
run_animation,
},
repeat::Repeat,
sequence::{SequenceElement, StringSplitter},
speed::Speed,
wrapper::Wrapper,
};
pub fn TypeAnimation(props: TypeAnimationProps) -> Element {
let initial_text = if props.pre_render_first_string {
first_string(&props.sequence).unwrap_or_default()
} else {
String::new()
};
let mut displayed = use_signal(|| initial_text.clone());
{
let sequence = props.sequence.clone();
let repeat = props.repeat;
let speed = normalize_speed(props.speed);
let deletion_speed = normalize_speed(props.deletion_speed.unwrap_or(props.speed));
let omit_deletion_animation = props.omit_deletion_animation;
let splitter = props.splitter.clone().unwrap_or_else(default_splitter);
let starting_text = initial_text;
use_future(move || {
let sequence = sequence.clone();
let splitter = splitter.clone();
let starting_text = starting_text.clone();
async move {
let config = AnimationConfig {
repeat,
speed,
deletion_speed,
omit_deletion_animation,
};
run_animation(&sequence, &splitter, config, starting_text, &mut displayed).await;
}
});
}
let text = displayed.read().clone();
let class = final_class_name(props.cursor, props.class.as_deref());
let style = props.style.unwrap_or_default();
let aria_label = props.aria_label.unwrap_or_default();
let aria_hidden = props.aria_hidden.unwrap_or_default();
let role = props.role.unwrap_or_default();
let use_inner_accessibility_span = !aria_label.is_empty();
let render_data = RenderData {
text,
class,
style,
aria_label,
aria_hidden,
role,
use_inner_accessibility_span,
};
match props.wrapper {
Wrapper::P => render_p(render_data),
Wrapper::Div => render_div(render_data),
Wrapper::Span => render_span(render_data),
Wrapper::Strong => render_strong(render_data),
Wrapper::A => render_a(render_data),
Wrapper::H1 => render_h1(render_data),
Wrapper::H2 => render_h2(render_data),
Wrapper::H3 => render_h3(render_data),
Wrapper::H4 => render_h4(render_data),
Wrapper::H5 => render_h5(render_data),
Wrapper::H6 => render_h6(render_data),
Wrapper::B => render_b(render_data),
}
}
#[derive(Clone, Props)]
pub struct TypeAnimationProps {
pub sequence: Vec<SequenceElement>,
#[props(default)]
pub repeat: Repeat,
#[props(default)]
pub wrapper: Wrapper,
#[props(default = true)]
pub cursor: bool,
#[props(default)]
pub speed: Speed,
#[props(default)]
pub deletion_speed: Option<Speed>,
#[props(default)]
pub omit_deletion_animation: bool,
#[props(default)]
pub pre_render_first_string: bool,
#[props(default)]
pub splitter: Option<StringSplitter>,
#[props(default)]
pub class: Option<String>,
#[props(default)]
pub style: Option<String>,
#[props(default)]
pub aria_label: Option<String>,
#[props(default)]
pub aria_hidden: Option<String>,
#[props(default)]
pub role: Option<String>,
}
impl PartialEq for TypeAnimationProps {
fn eq(&self, _other: &Self) -> bool {
true
}
}
struct RenderData {
text: String,
class: String,
style: String,
aria_label: String,
aria_hidden: String,
role: String,
use_inner_accessibility_span: bool,
}
fn render_p(data: RenderData) -> Element {
let RenderData {
text,
class,
style,
aria_label,
aria_hidden,
role,
use_inner_accessibility_span,
} = data;
rsx! {
style { {CURSOR_CSS} }
p { class, style, role, "aria-label": aria_label, "aria-hidden": aria_hidden,
if use_inner_accessibility_span { span { "aria-hidden": "true", "{text}" } } else { "{text}" }
}
}
}
fn render_div(data: RenderData) -> Element {
let RenderData {
text,
class,
style,
aria_label,
aria_hidden,
role,
use_inner_accessibility_span,
} = data;
rsx! {
style { {CURSOR_CSS} }
div { class, style, role, "aria-label": aria_label, "aria-hidden": aria_hidden,
if use_inner_accessibility_span { span { "aria-hidden": "true", "{text}" } } else { "{text}" }
}
}
}
fn render_span(data: RenderData) -> Element {
let RenderData {
text,
class,
style,
aria_label,
aria_hidden,
role,
use_inner_accessibility_span,
} = data;
rsx! {
style { {CURSOR_CSS} }
span { class, style, role, "aria-label": aria_label, "aria-hidden": aria_hidden,
if use_inner_accessibility_span { span { "aria-hidden": "true", "{text}" } } else { "{text}" }
}
}
}
fn render_strong(data: RenderData) -> Element {
let RenderData {
text,
class,
style,
aria_label,
aria_hidden,
role,
use_inner_accessibility_span,
} = data;
rsx! {
style { {CURSOR_CSS} }
strong { class, style, role, "aria-label": aria_label, "aria-hidden": aria_hidden,
if use_inner_accessibility_span { span { "aria-hidden": "true", "{text}" } } else { "{text}" }
}
}
}
fn render_a(data: RenderData) -> Element {
let RenderData {
text,
class,
style,
aria_label,
aria_hidden,
role,
use_inner_accessibility_span,
} = data;
rsx! {
style { {CURSOR_CSS} }
a { class, style, role, "aria-label": aria_label, "aria-hidden": aria_hidden,
if use_inner_accessibility_span { span { "aria-hidden": "true", "{text}" } } else { "{text}" }
}
}
}
fn render_h1(data: RenderData) -> Element {
let RenderData {
text,
class,
style,
aria_label,
aria_hidden,
role,
use_inner_accessibility_span,
} = data;
rsx! {
style { {CURSOR_CSS} }
h1 { class, style, role, "aria-label": aria_label, "aria-hidden": aria_hidden,
if use_inner_accessibility_span { span { "aria-hidden": "true", "{text}" } } else { "{text}" }
}
}
}
fn render_h2(data: RenderData) -> Element {
let RenderData {
text,
class,
style,
aria_label,
aria_hidden,
role,
use_inner_accessibility_span,
} = data;
rsx! {
style { {CURSOR_CSS} }
h2 { class, style, role, "aria-label": aria_label, "aria-hidden": aria_hidden,
if use_inner_accessibility_span { span { "aria-hidden": "true", "{text}" } } else { "{text}" }
}
}
}
fn render_h3(data: RenderData) -> Element {
let RenderData {
text,
class,
style,
aria_label,
aria_hidden,
role,
use_inner_accessibility_span,
} = data;
rsx! {
style { {CURSOR_CSS} }
h3 { class, style, role, "aria-label": aria_label, "aria-hidden": aria_hidden,
if use_inner_accessibility_span { span { "aria-hidden": "true", "{text}" } } else { "{text}" }
}
}
}
fn render_h4(data: RenderData) -> Element {
let RenderData {
text,
class,
style,
aria_label,
aria_hidden,
role,
use_inner_accessibility_span,
} = data;
rsx! {
style { {CURSOR_CSS} }
h4 { class, style, role, "aria-label": aria_label, "aria-hidden": aria_hidden,
if use_inner_accessibility_span { span { "aria-hidden": "true", "{text}" } } else { "{text}" }
}
}
}
fn render_h5(data: RenderData) -> Element {
let RenderData {
text,
class,
style,
aria_label,
aria_hidden,
role,
use_inner_accessibility_span,
} = data;
rsx! {
style { {CURSOR_CSS} }
h5 { class, style, role, "aria-label": aria_label, "aria-hidden": aria_hidden,
if use_inner_accessibility_span { span { "aria-hidden": "true", "{text}" } } else { "{text}" }
}
}
}
fn render_h6(data: RenderData) -> Element {
let RenderData {
text,
class,
style,
aria_label,
aria_hidden,
role,
use_inner_accessibility_span,
} = data;
rsx! {
style { {CURSOR_CSS} }
h6 { class, style, role, "aria-label": aria_label, "aria-hidden": aria_hidden,
if use_inner_accessibility_span { span { "aria-hidden": "true", "{text}" } } else { "{text}" }
}
}
}
fn render_b(data: RenderData) -> Element {
let RenderData {
text,
class,
style,
aria_label,
aria_hidden,
role,
use_inner_accessibility_span,
} = data;
rsx! {
style { {CURSOR_CSS} }
b { class, style, role, "aria-label": aria_label, "aria-hidden": aria_hidden,
if use_inner_accessibility_span { span { "aria-hidden": "true", "{text}" } } else { "{text}" }
}
}
}