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 {
58 class: if props.block.language.is_some() || props.block.filename.is_some() {
59 "bg-base-300/50 px-4 py-4 overflow-x-auto syntax-highlight mt-0"
60 } else {
61 "bg-base-300/50 p-4 overflow-x-auto relative syntax-highlight"
62 },
63 code {
64 class: "text-sm font-mono leading-relaxed",
65 dangerous_inner_html: "{highlighted}",
66 }
67 if props.block.language.is_none() && props.block.filename.is_none() {
69 div { class: "absolute top-3 right-3",
70 CopyButton {
71 code: code_for_copy,
72 copied: copied,
73 }
74 }
75 }
76 }
77 }
78 }
79}
80
81#[derive(Props, Clone, PartialEq)]
83pub struct DocCodeGroupProps {
84 pub group: CodeGroupNode,
86}
87
88#[component]
90pub fn DocCodeGroup(props: DocCodeGroupProps) -> Element {
91 let mut active_tab = use_signal(|| 0usize);
92
93 rsx! {
94 div { class: "my-6 rounded-lg border border-base-content/10 overflow-hidden",
95 div { class: "flex items-center bg-base-200/80 border-b border-base-content/10",
97 for (i, block) in props.group.blocks.iter().enumerate() {
98 button {
99 key: "{i}",
100 class: if active_tab() == i {
101 "px-4 py-2.5 text-sm font-medium text-primary border-b-2 border-primary -mb-px bg-base-300/30 transition-colors"
102 } else {
103 "px-4 py-2.5 text-sm font-medium text-base-content/60 hover:text-base-content hover:bg-base-300/20 transition-colors"
104 },
105 onclick: move |_| active_tab.set(i),
106 if let Some(filename) = &block.filename {
107 "{filename}"
108 } else if let Some(lang) = &block.language {
109 "{lang}"
110 } else {
111 "Code"
112 }
113 }
114 }
115 }
116
117 if let Some(block) = props.group.blocks.get(active_tab()) {
119 CodeGroupBlock { block: block.clone() }
120 }
121 }
122 }
123}
124
125#[derive(Props, Clone, PartialEq)]
127struct CodeGroupBlockProps {
128 block: CodeBlockNode,
129}
130
131#[component]
133fn CodeGroupBlock(props: CodeGroupBlockProps) -> Element {
134 let copied = use_signal(|| false);
135 let code = props.block.code.clone();
136
137 let highlighted = highlight_code(&code, props.block.language.as_deref());
139
140 rsx! {
141 div { class: "relative group",
142 pre { class: "bg-base-300/50 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}