dioxus_mdx/components/
code.rs1use dioxus::prelude::*;
6use dioxus_free_icons::{Icon, icons::ld_icons::*};
7
8#[cfg(feature = "mermaid")]
9use super::mermaid::MermaidDiagram;
10use crate::parser::{CodeBlockNode, CodeGroupNode, highlight_code};
11
12#[derive(Props, Clone, PartialEq)]
14pub struct DocCodeBlockProps {
15 pub block: CodeBlockNode,
17}
18
19#[component]
21pub fn DocCodeBlock(props: DocCodeBlockProps) -> Element {
22 #[cfg(feature = "mermaid")]
24 if props.block.language.as_deref() == Some("mermaid") {
25 return rsx! { MermaidDiagram { code: props.block.code.clone() } };
26 }
27
28 let copied = use_signal(|| false);
29 let code = props.block.code.clone();
30 let code_for_copy = code.clone();
31
32 let highlighted = highlight_code(&code, props.block.language.as_deref());
34
35 rsx! {
36 div { class: "my-6 relative group rounded-lg border border-base-content/10 overflow-hidden",
37 if props.block.language.is_some() || props.block.filename.is_some() {
39 div { class: "flex items-center justify-between bg-base-200/80 px-4 py-2.5 border-b border-base-content/10 text-sm",
40 span { class: "text-base-content/60 font-mono text-xs tracking-wide",
41 if let Some(filename) = &props.block.filename {
42 "{filename}"
43 } else if let Some(lang) = &props.block.language {
44 "{lang}"
45 }
46 }
47 CopyButton {
49 code: code_for_copy.clone(),
50 copied: copied,
51 }
52 }
53 }
54
55 pre {
57 class: if props.block.language.is_some() || props.block.filename.is_some() {
58 "bg-base-200 px-4 py-4 overflow-x-auto syntax-highlight mt-0"
59 } else {
60 "bg-base-200 p-4 overflow-x-auto relative syntax-highlight"
61 },
62 code {
63 class: "text-sm font-mono leading-relaxed",
64 dangerous_inner_html: "{highlighted}",
65 }
66 if props.block.language.is_none() && props.block.filename.is_none() {
68 div { class: "absolute top-3 right-3",
69 CopyButton {
70 code: code_for_copy,
71 copied: copied,
72 }
73 }
74 }
75 }
76 }
77 }
78}
79
80#[derive(Props, Clone, PartialEq)]
82pub struct DocCodeGroupProps {
83 pub group: CodeGroupNode,
85}
86
87#[component]
89pub fn DocCodeGroup(props: DocCodeGroupProps) -> Element {
90 let mut active_tab = use_signal(|| 0usize);
91
92 rsx! {
93 div { class: "my-6 rounded-lg border border-base-content/10 overflow-hidden",
94 div { class: "flex items-center bg-base-200/80 border-b border-base-content/10",
96 for (i, block) in props.group.blocks.iter().enumerate() {
97 button {
98 key: "{i}",
99 class: if active_tab() == i {
100 "px-4 py-2.5 text-sm font-medium text-primary border-b-2 border-primary -mb-px bg-base-200/60 transition-colors"
101 } else {
102 "px-4 py-2.5 text-sm font-medium text-base-content/60 hover:text-base-content hover:bg-base-300/20 transition-colors"
103 },
104 onclick: move |_| active_tab.set(i),
105 if let Some(filename) = &block.filename {
106 "{filename}"
107 } else if let Some(lang) = &block.language {
108 "{lang}"
109 } else {
110 "Code"
111 }
112 }
113 }
114 }
115
116 if let Some(block) = props.group.blocks.get(active_tab()) {
118 CodeGroupBlock { block: block.clone() }
119 }
120 }
121 }
122}
123
124#[derive(Props, Clone, PartialEq)]
126struct CodeGroupBlockProps {
127 block: CodeBlockNode,
128}
129
130#[component]
132fn CodeGroupBlock(props: CodeGroupBlockProps) -> Element {
133 let copied = use_signal(|| false);
134 let code = props.block.code.clone();
135
136 let highlighted = highlight_code(&code, props.block.language.as_deref());
138
139 rsx! {
140 div { class: "relative group",
141 pre {
143 class: "bg-base-200 px-4 py-4 overflow-x-auto syntax-highlight mt-0",
144 code {
145 class: "text-sm font-mono leading-relaxed",
146 dangerous_inner_html: "{highlighted}",
147 }
148 }
149 div { class: "absolute top-3 right-3",
150 CopyButton {
151 code: code.clone(),
152 copied: copied,
153 }
154 }
155 }
156 }
157}
158
159#[derive(Props, Clone, PartialEq)]
161struct CopyButtonProps {
162 code: String,
163 copied: Signal<bool>,
164}
165
166#[component]
168fn CopyButton(props: CopyButtonProps) -> Element {
169 #[allow(unused_mut)]
170 let mut copied = props.copied;
171 let code = props.code.clone();
172
173 rsx! {
174 button {
175 class: "btn btn-ghost btn-xs opacity-60 hover:opacity-100 group-hover:opacity-100 transition-all duration-150 hover:bg-base-content/10",
176 "data-code": "{code}",
177 onclick: move |_| {
178 #[cfg(target_arch = "wasm32")]
180 {
181 use dioxus::prelude::*;
182 let code = code.clone();
183 spawn(async move {
184 let js = format!(
186 "navigator.clipboard.writeText({}).catch(console.error)",
187 serde_json::to_string(&code).unwrap_or_default()
188 );
189 let _ = document::eval(&js);
190 copied.set(true);
191 gloo_timers::future::TimeoutFuture::new(2000).await;
192 copied.set(false);
193 });
194 }
195 },
196 if copied() {
197 Icon { class: "size-4 text-success", icon: LdCheck }
198 } else {
199 Icon { class: "size-4", icon: LdCopy }
200 }
201 }
202 }
203}