Skip to main content

dioxus_mdx/components/
response_field.rs

1//! ResponseField and Expandable components for API response documentation.
2
3use dioxus::prelude::*;
4use dioxus_free_icons::{Icon, icons::ld_icons::*};
5
6use crate::parser::{ExpandableNode, ResponseFieldNode};
7
8/// Props for DocResponseField component.
9#[derive(Props, Clone, PartialEq)]
10pub struct DocResponseFieldProps {
11    /// The response field to render.
12    pub field: ResponseFieldNode,
13    /// Nesting depth for indentation.
14    #[props(default = 0)]
15    pub depth: usize,
16}
17
18/// API response field documentation.
19#[component]
20pub fn DocResponseField(props: DocResponseFieldProps) -> Element {
21    let field = &props.field;
22
23    let description_html = if !field.content.is_empty() {
24        markdown::to_html_with_options(&field.content, &markdown::Options::gfm())
25            .unwrap_or_else(|_| field.content.clone())
26    } else {
27        String::new()
28    };
29
30    let indent_class = if props.depth > 0 {
31        "ml-4 border-l-2 border-base-300 pl-4"
32    } else {
33        ""
34    };
35
36    rsx! {
37        div { class: "py-3 {indent_class}",
38            div { class: "flex items-start gap-2 flex-wrap",
39                // Field name
40                code { class: "font-mono font-semibold text-base-content bg-base-300 px-2 py-0.5 rounded",
41                    "{field.name}"
42                }
43                // Type badge
44                span { class: "badge badge-outline badge-sm",
45                    "{field.field_type}"
46                }
47                // Required indicator
48                if field.required {
49                    span { class: "badge badge-success badge-sm",
50                        "required"
51                    }
52                }
53            }
54            // Description
55            if !description_html.is_empty() {
56                div {
57                    class: "prose prose-sm max-w-none mt-2 text-base-content/80",
58                    dangerous_inner_html: description_html,
59                }
60            }
61            // Nested expandable
62            if let Some(expandable) = &field.expandable {
63                DocExpandable {
64                    expandable: expandable.clone(),
65                    depth: props.depth + 1,
66                }
67            }
68        }
69    }
70}
71
72/// Props for DocExpandable component.
73#[derive(Props, Clone, PartialEq)]
74pub struct DocExpandableProps {
75    /// The expandable section to render.
76    pub expandable: ExpandableNode,
77    /// Nesting depth for indentation.
78    #[props(default = 0)]
79    pub depth: usize,
80}
81
82/// Expandable section for nested fields.
83#[component]
84pub fn DocExpandable(props: DocExpandableProps) -> Element {
85    let mut expanded = use_signal(|| false);
86    let expandable = &props.expandable;
87
88    let chevron_class = if expanded() {
89        "size-4 text-base-content/50 transform rotate-90 transition-transform"
90    } else {
91        "size-4 text-base-content/50 transition-transform"
92    };
93
94    rsx! {
95        div { class: "mt-3 border border-base-300 rounded-lg overflow-hidden",
96            // Header
97            button {
98                class: "w-full flex items-center gap-2 px-3 py-2 text-left hover:bg-base-200 transition-colors text-sm",
99                onclick: move |_| expanded.set(!expanded()),
100                Icon { class: chevron_class, icon: LdChevronRight }
101                span { class: "font-medium text-base-content/70",
102                    "{expandable.title}"
103                }
104            }
105            // Content
106            if expanded() {
107                div { class: "px-3 pb-3 border-t border-base-300 bg-base-200/30",
108                    for (i, field) in expandable.fields.iter().enumerate() {
109                        DocResponseField {
110                            key: "{i}",
111                            field: field.clone(),
112                            depth: props.depth,
113                        }
114                    }
115                }
116            }
117        }
118    }
119}