#![doc = include_str!("../DIOXUS.md")]
use crate::common::{
Color, Language, Size, Theme, Variant, base_code_style, base_content_style,
base_copy_trigger_style, base_lang_badge_style, base_title_style, themed_block_style,
themed_header_style, themed_pre_style,
};
use dioxus::prelude::*;
#[derive(Props, Clone, PartialEq)]
pub struct CodeProps {
#[props(default)]
pub children: Element,
#[props(default = Color::Default)]
pub color: Color,
#[props(default = Variant::Subtle)]
pub variant: Variant,
#[props(default = Size::Sm)]
pub size: Size,
#[props(default)]
pub class: &'static str,
#[props(default)]
pub style: &'static str,
#[props(default)]
pub id: &'static str,
#[props(default)]
pub aria_label: &'static str,
#[props(default)]
pub data_testid: &'static str,
}
#[component]
pub fn Code(props: CodeProps) -> Element {
let color_style = match props.variant {
Variant::Solid => props.color.to_solid_style(),
Variant::Subtle => props.color.to_subtle_style(),
Variant::Outline => props.color.to_outline_style(),
Variant::Surface => props.color.to_surface_style(),
Variant::Plain => "",
Variant::Custom(_) => "",
};
rsx! {
code {
id: props.id,
class: "code {props.variant.to_class()} {props.color.to_class()} {props.size.to_class()} {props.class}",
style: "{base_code_style()} {props.size.to_style()} {color_style} {props.style}",
aria_label: props.aria_label,
"data-testid": props.data_testid,
{props.children}
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct BlockContext {
pub code: &'static str,
pub language: Language,
pub theme: Theme,
}
#[derive(Props, Clone, PartialEq)]
pub struct HeaderProps {
#[props(default)]
pub children: Element,
#[props(default)]
pub class: &'static str,
#[props(default)]
pub style: &'static str,
#[props(default)]
pub id: &'static str,
}
#[component]
pub fn Header(props: HeaderProps) -> Element {
let theme = try_consume_context::<BlockContext>()
.map(|c| c.theme)
.unwrap_or_default();
rsx! {
div {
id: props.id,
class: "code-block__header {props.class}",
style: "{themed_header_style(theme)} {props.style}",
{props.children}
}
}
}
#[derive(Props, Clone, PartialEq)]
pub struct TitleProps {
#[props(default)]
pub children: Element,
#[props(default)]
pub class: &'static str,
#[props(default)]
pub style: &'static str,
#[props(default)]
pub id: &'static str,
}
#[component]
pub fn Title(props: TitleProps) -> Element {
rsx! {
span {
id: props.id,
class: "code-block__title {props.class}",
style: "{base_title_style()} {props.style}",
{props.children}
}
}
}
#[derive(Props, Clone, PartialEq)]
pub struct CopyTriggerProps {
#[props(default = "Copy")]
pub copy_label: &'static str,
#[props(default = "Copied!")]
pub copied_label: &'static str,
#[props(default = 2000)]
pub reset_ms: u32,
#[props(default)]
pub class: &'static str,
#[props(default)]
pub style: &'static str,
#[props(default)]
pub id: &'static str,
#[props(default = "Copy code to clipboard")]
pub aria_label: &'static str,
}
#[component]
pub fn CopyTrigger(props: CopyTriggerProps) -> Element {
let ctx = consume_context::<BlockContext>();
let mut copied = use_signal(|| false);
let reset_ms = props.reset_ms;
let on_click = move |_| {
let code = ctx.code;
spawn(async move {
if let Some(window) = web_sys::window() {
let clipboard = window.navigator().clipboard();
let _ = wasm_bindgen_futures::JsFuture::from(clipboard.write_text(code)).await;
}
copied.set(true);
gloo_timers::future::TimeoutFuture::new(reset_ms).await;
copied.set(false);
});
};
let label = if copied() {
props.copied_label
} else {
props.copy_label
};
rsx! {
button {
id: props.id,
r#type: "button",
class: "code-block__copy-trigger {props.class}",
style: "{base_copy_trigger_style()} {props.style}",
onclick: on_click,
aria_label: props.aria_label,
"aria-live": "polite",
{label}
}
}
}
#[derive(Props, Clone, PartialEq)]
pub struct ContentProps {
#[props(default)]
pub children: Option<Element>,
#[props(default)]
pub class: &'static str,
#[props(default)]
pub style: &'static str,
#[props(default)]
pub id: &'static str,
#[props(default)]
pub pre_class: &'static str,
#[props(default)]
pub pre_style: &'static str,
}
#[component]
pub fn Content(props: ContentProps) -> Element {
let ctx = consume_context::<BlockContext>();
let hl = crate::common::highlight_code(ctx.code, ctx.language, ctx.theme);
rsx! {
div {
id: props.id,
class: "code-block__content {props.class}",
style: "{base_content_style()} {props.style}",
match props.children {
Some(children) => rsx! { {children} },
None => rsx! {
pre {
class: "code-block__pre {props.pre_class}",
style: "{themed_pre_style(ctx.theme)} {props.pre_style}",
tabindex: "0",
code {
class: ctx.language.to_class(),
dangerous_inner_html: hl,
}
}
},
}
}
}
}
#[derive(Props, Clone, PartialEq)]
pub struct LanguageBadgeProps {
#[props(default)]
pub class: &'static str,
#[props(default)]
pub style: &'static str,
}
#[component]
pub fn LanguageBadge(props: LanguageBadgeProps) -> Element {
let ctx = consume_context::<BlockContext>();
rsx! {
span {
class: "code-block__lang {props.class}",
style: "{base_lang_badge_style()} {props.style}",
aria_label: "Language: {ctx.language.to_label()}",
{ctx.language.to_label()}
}
}
}
#[derive(Props, Clone, PartialEq)]
pub struct BlockProps {
pub code: &'static str,
#[props(default)]
pub language: Language,
#[props(default)]
pub theme: Theme,
#[props(default)]
pub children: Option<Element>,
#[props(default)]
pub class: &'static str,
#[props(default)]
pub style: &'static str,
#[props(default)]
pub id: &'static str,
#[props(default = "Code block")]
pub aria_label: &'static str,
#[props(default)]
pub data_testid: &'static str,
}
#[component]
pub fn Block(props: BlockProps) -> Element {
let ctx = BlockContext {
code: props.code,
language: props.language,
theme: props.theme,
};
use_context_provider(|| ctx);
rsx! {
figure {
id: props.id,
class: "code-block {props.language.to_class()} {props.class}",
style: "{themed_block_style(props.theme)} {props.style}",
role: "region",
aria_label: props.aria_label,
"data-testid": props.data_testid,
match props.children {
Some(children) => rsx! { {children} },
None => rsx! { Content {} },
}
}
}
}