dioxus_mdx/components/openapi/
schema_viewer.rs1use dioxus::prelude::*;
4use dioxus_free_icons::{Icon, icons::ld_icons::*};
5
6use crate::parser::SchemaDefinition;
7
8#[derive(Props, Clone, PartialEq)]
10pub struct SchemaViewerProps {
11 pub schema: SchemaDefinition,
13 #[props(default = 0)]
15 pub depth: usize,
16 #[props(default = false)]
18 pub expanded: bool,
19 #[props(default)]
21 pub name: Option<String>,
22 #[props(default = false)]
24 pub required: bool,
25}
26
27#[component]
29pub fn SchemaViewer(props: SchemaViewerProps) -> Element {
30 let mut is_expanded = use_signal(|| props.expanded || props.depth == 0);
31 let schema = &props.schema;
32
33 let is_complex = schema.is_complex();
34 let type_display = schema.display_type();
35
36 let indent_class = if props.depth > 0 {
37 "ml-4 border-l-2 border-base-300 pl-3"
38 } else {
39 ""
40 };
41
42 rsx! {
43 div { class: "py-1.5 {indent_class}",
44 div { class: "flex items-center gap-2 flex-wrap",
46 if is_complex && !schema.properties.is_empty() {
48 button {
49 class: "p-0.5 hover:bg-base-300 rounded transition-colors",
50 onclick: move |_| is_expanded.set(!is_expanded()),
51 Icon {
52 class: if is_expanded() { "size-4 text-base-content/50 transform rotate-90 transition-transform" } else { "size-4 text-base-content/50 transition-transform" },
53 icon: LdChevronRight
54 }
55 }
56 }
57
58 if let Some(name) = &props.name {
60 code { class: "font-mono font-semibold text-primary text-sm",
61 "{name}"
62 }
63 }
64
65 span { class: "text-xs px-2 py-0.5 rounded-full bg-base-300 text-base-content/70",
67 "{type_display}"
68 }
69
70 if props.required {
72 span { class: "text-xs px-2 py-0.5 rounded-full bg-error/20 text-error",
73 "required"
74 }
75 }
76
77 if schema.nullable {
79 span { class: "text-xs px-2 py-0.5 rounded-full bg-base-300 text-base-content/50",
80 "nullable"
81 }
82 }
83
84 if let Some(format) = &schema.format {
86 span { class: "text-xs text-base-content/50",
87 "({format})"
88 }
89 }
90 }
91
92 if let Some(desc) = &schema.description {
94 p { class: "text-sm text-base-content/70 mt-1",
95 "{desc}"
96 }
97 }
98
99 if !schema.enum_values.is_empty() {
101 div { class: "mt-1 flex items-center gap-2 flex-wrap",
102 span { class: "text-xs text-base-content/50", "Enum:" }
103 for value in &schema.enum_values {
104 code { class: "text-xs px-1.5 py-0.5 rounded bg-base-300 font-mono",
105 "{value}"
106 }
107 }
108 }
109 }
110
111 if let Some(default) = &schema.default {
113 div { class: "mt-1",
114 span { class: "text-xs text-base-content/50", "Default: " }
115 code { class: "text-xs font-mono text-primary",
116 "{default}"
117 }
118 }
119 }
120
121 if let Some(example) = &schema.example {
123 div { class: "mt-1",
124 span { class: "text-xs text-base-content/50", "Example: " }
125 code { class: "text-xs font-mono text-secondary",
126 "{example}"
127 }
128 }
129 }
130
131 if is_expanded() && !schema.properties.is_empty() {
133 div { class: "mt-2",
134 for (name, prop_schema) in &schema.properties {
135 SchemaViewer {
136 key: "{name}",
137 schema: prop_schema.clone(),
138 depth: props.depth + 1,
139 name: Some(name.clone()),
140 required: schema.required.contains(name),
141 }
142 }
143 }
144 }
145
146 if is_expanded() {
148 if let Some(items) = &schema.items {
149 if items.is_complex() {
150 div { class: "mt-2",
151 span { class: "text-xs text-base-content/50 ml-4", "Array items:" }
152 SchemaViewer {
153 schema: (**items).clone(),
154 depth: props.depth + 1,
155 }
156 }
157 }
158 }
159 }
160
161 if is_expanded() {
163 if !schema.one_of.is_empty() {
164 div { class: "mt-2 ml-4",
165 span { class: "text-xs text-base-content/50 font-semibold", "One of:" }
166 for (i, variant) in schema.one_of.iter().enumerate() {
167 SchemaViewer {
168 key: "{i}",
169 schema: variant.clone(),
170 depth: props.depth + 1,
171 }
172 }
173 }
174 }
175 if !schema.any_of.is_empty() {
176 div { class: "mt-2 ml-4",
177 span { class: "text-xs text-base-content/50 font-semibold", "Any of:" }
178 for (i, variant) in schema.any_of.iter().enumerate() {
179 SchemaViewer {
180 key: "{i}",
181 schema: variant.clone(),
182 depth: props.depth + 1,
183 }
184 }
185 }
186 }
187 if !schema.all_of.is_empty() {
188 div { class: "mt-2 ml-4",
189 span { class: "text-xs text-base-content/50 font-semibold", "All of:" }
190 for (i, variant) in schema.all_of.iter().enumerate() {
191 SchemaViewer {
192 key: "{i}",
193 schema: variant.clone(),
194 depth: props.depth + 1,
195 }
196 }
197 }
198 }
199 }
200 }
201 }
202}
203
204#[derive(Props, Clone, PartialEq)]
206pub struct SchemaTypeLabelProps {
207 pub schema: SchemaDefinition,
209}
210
211#[component]
213pub fn SchemaTypeLabel(props: SchemaTypeLabelProps) -> Element {
214 let type_display = props.schema.display_type();
215
216 rsx! {
217 span { class: "text-xs px-2 py-0.5 rounded-full bg-base-300 text-base-content/70 font-mono",
218 "{type_display}"
219 }
220 }
221}