use dioxus::prelude::*;
use dioxus_free_icons::{Icon, icons::ld_icons::*};
#[cfg(feature = "mermaid")]
use super::mermaid::MermaidDiagram;
use crate::parser::{CodeBlockNode, CodeGroupNode, highlight_code};
#[derive(Props, Clone, PartialEq)]
pub struct DocCodeBlockProps {
pub block: CodeBlockNode,
}
#[component]
pub fn DocCodeBlock(props: DocCodeBlockProps) -> Element {
#[cfg(feature = "mermaid")]
if props.block.language.as_deref() == Some("mermaid") {
return rsx! { MermaidDiagram { code: props.block.code.clone() } };
}
let copied = use_signal(|| false);
let code = props.block.code.clone();
let code_for_copy = code.clone();
let highlighted = highlight_code(&code, props.block.language.as_deref());
rsx! {
div { class: "my-6 relative group rounded-lg border border-base-content/10 overflow-hidden",
if props.block.language.is_some() || props.block.filename.is_some() {
div { class: "flex items-center justify-between bg-base-200/80 px-4 py-2.5 border-b border-base-content/10 text-sm",
span { class: "text-base-content/60 font-mono text-xs tracking-wide",
if let Some(filename) = &props.block.filename {
"{filename}"
} else if let Some(lang) = &props.block.language {
"{lang}"
}
}
CopyButton {
code: code_for_copy.clone(),
copied: copied,
}
}
}
pre {
class: if props.block.language.is_some() || props.block.filename.is_some() {
"bg-base-200 px-4 py-4 overflow-x-auto syntax-highlight mt-0"
} else {
"bg-base-200 p-4 overflow-x-auto relative syntax-highlight"
},
code {
class: "text-sm font-mono leading-relaxed",
dangerous_inner_html: "{highlighted}",
}
if props.block.language.is_none() && props.block.filename.is_none() {
div { class: "absolute top-3 right-3",
CopyButton {
code: code_for_copy,
copied: copied,
}
}
}
}
}
}
}
#[derive(Props, Clone, PartialEq)]
pub struct DocCodeGroupProps {
pub group: CodeGroupNode,
}
#[component]
pub fn DocCodeGroup(props: DocCodeGroupProps) -> Element {
let mut active_tab = use_signal(|| 0usize);
rsx! {
div { class: "my-6 rounded-lg border border-base-content/10 overflow-hidden",
div { class: "flex items-center bg-base-200/80 border-b border-base-content/10",
for (i, block) in props.group.blocks.iter().enumerate() {
button {
key: "{i}",
class: if active_tab() == i {
"px-4 py-2.5 text-sm font-medium text-primary border-b-2 border-primary -mb-px bg-base-200/60 transition-colors"
} else {
"px-4 py-2.5 text-sm font-medium text-base-content/60 hover:text-base-content hover:bg-base-300/20 transition-colors"
},
onclick: move |_| active_tab.set(i),
if let Some(filename) = &block.filename {
"{filename}"
} else if let Some(lang) = &block.language {
"{lang}"
} else {
"Code"
}
}
}
}
if let Some(block) = props.group.blocks.get(active_tab()) {
CodeGroupBlock { block: block.clone() }
}
}
}
}
#[derive(Props, Clone, PartialEq)]
struct CodeGroupBlockProps {
block: CodeBlockNode,
}
#[component]
fn CodeGroupBlock(props: CodeGroupBlockProps) -> Element {
let copied = use_signal(|| false);
let code = props.block.code.clone();
let highlighted = highlight_code(&code, props.block.language.as_deref());
rsx! {
div { class: "relative group",
pre {
class: "bg-base-200 px-4 py-4 overflow-x-auto syntax-highlight mt-0",
code {
class: "text-sm font-mono leading-relaxed",
dangerous_inner_html: "{highlighted}",
}
}
div { class: "absolute top-3 right-3",
CopyButton {
code: code.clone(),
copied: copied,
}
}
}
}
}
#[derive(Props, Clone, PartialEq)]
struct CopyButtonProps {
code: String,
copied: Signal<bool>,
}
#[component]
fn CopyButton(props: CopyButtonProps) -> Element {
#[allow(unused_mut)]
let mut copied = props.copied;
let code = props.code.clone();
rsx! {
button {
class: "btn btn-ghost btn-xs opacity-60 hover:opacity-100 group-hover:opacity-100 transition-all duration-150 hover:bg-base-content/10",
"data-code": "{code}",
onclick: move |_| {
#[cfg(target_arch = "wasm32")]
{
use dioxus::prelude::*;
let code = code.clone();
spawn(async move {
let js = format!(
"navigator.clipboard.writeText({}).catch(console.error)",
serde_json::to_string(&code).unwrap_or_default()
);
let _ = document::eval(&js);
copied.set(true);
gloo_timers::future::TimeoutFuture::new(2000).await;
copied.set(false);
});
}
},
if copied() {
Icon { class: "size-4 text-success", icon: LdCheck }
} else {
Icon { class: "size-4", icon: LdCopy }
}
}
}
}