Skip to main content

dioxus_mdx/components/
steps.rs

1//! Steps component for sequential documentation guides.
2
3use std::sync::LazyLock;
4
5use dioxus::prelude::*;
6use regex::Regex;
7
8use crate::components::DocNodeRenderer;
9use crate::parser::{DocNode, StepsNode};
10
11static STEP_PREFIX_RE: LazyLock<Regex> =
12    LazyLock::new(|| Regex::new(r"^Step\s+\d+[:.]\s*").unwrap());
13
14/// Props for DocSteps component.
15#[derive(Props, Clone, PartialEq)]
16pub struct DocStepsProps {
17    /// Steps data.
18    pub steps: StepsNode,
19}
20
21/// Sequential steps component using DaisyUI steps or custom styling.
22#[component]
23pub fn DocSteps(props: DocStepsProps) -> Element {
24    rsx! {
25        div { class: "my-8",
26            // Use div instead of ol to avoid default list numbering
27            div { class: "relative border-l-2 border-primary/20 ml-5 space-y-8",
28                for (i, step) in props.steps.steps.iter().enumerate() {
29                    div { key: "{i}", class: "relative pl-10",
30                        // Step number circle - positioned to overlap the border line
31                        span {
32                            class: "absolute left-0 top-0 -translate-x-1/2 flex items-center justify-center w-7 h-7 bg-primary text-primary-content rounded-full font-semibold text-sm shadow-sm",
33                            "{i + 1}"
34                        }
35                        // Step content
36                        div {
37                            // Step title
38                            h4 { class: "font-semibold text-base text-base-content mb-2",
39                                // Clean up step title (remove "Step X:" prefix if present)
40                                {clean_step_title(&step.title)}
41                            }
42                            // Step body (render as markdown with nested components)
43                            StepContent { content: step.content.clone() }
44                        }
45                    }
46                }
47            }
48        }
49    }
50}
51
52/// Clean up step title by removing redundant prefixes.
53fn clean_step_title(title: &str) -> String {
54    // Remove "Step N:" or "Step N." prefix
55    STEP_PREFIX_RE.replace(title, "").trim().to_string()
56}
57
58/// Props for StepContent.
59#[derive(Props, Clone, PartialEq)]
60struct StepContentProps {
61    content: Vec<DocNode>,
62}
63
64/// Render step content which may contain nested MDX components.
65#[component]
66fn StepContent(props: StepContentProps) -> Element {
67    rsx! {
68        div { class: "prose prose-sm max-w-none",
69            for (i, node) in props.content.iter().enumerate() {
70                DocNodeRenderer { key: "{i}", node: node.clone() }
71            }
72        }
73    }
74}