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