dioxus_mdx/components/openapi/
endpoint_page.rs1use dioxus::prelude::*;
4#[cfg(feature = "highlight")]
5use dioxus_code::{Code, CodeTheme, Language, SourceCode, Theme};
6
7use crate::parser::{ApiOperation, OpenApiSpec};
8
9use super::method_badge::MethodBadge;
10use super::parameters_list::ParametersList;
11use super::request_body::RequestBodySection;
12use super::responses_list::ResponsesList;
13
14enum SampleLang {
17 Bash,
18 Json,
19}
20
21#[cfg(feature = "highlight")]
24fn code_sample(code: String, lang: SampleLang) -> Element {
25 let language = match lang {
26 SampleLang::Bash => Language::Bash,
27 SampleLang::Json => Language::Json,
28 };
29 let theme = CodeTheme::system(Theme::GITHUB_LIGHT, Theme::TOKYO_NIGHT);
30 rsx! {
31 Code {
32 src: SourceCode::new(language, code),
33 theme,
34 }
35 }
36}
37
38#[cfg(not(feature = "highlight"))]
41fn code_sample(code: String, _lang: SampleLang) -> Element {
42 crate::components::code::plain_code_block(&code)
43}
44
45#[derive(Props, Clone, PartialEq)]
47pub struct EndpointPageProps {
48 pub operation: ApiOperation,
50 pub spec: OpenApiSpec,
52}
53
54#[component]
59pub fn EndpointPage(props: EndpointPageProps) -> Element {
60 let op = &props.operation;
61 let spec = &props.spec;
62
63 let base_url = spec
64 .servers
65 .first()
66 .map(|s| s.url.as_str())
67 .unwrap_or("https://api.example.com");
68
69 let curl = op.generate_curl(base_url);
70 let response_example = op.generate_response_example();
71
72 let method_bg = op.method.bg_class();
73
74 rsx! {
75 div { class: "flex flex-col lg:flex-row gap-0",
76 div { class: "flex-1 min-w-0 px-8 py-12 lg:px-12",
78 div { class: "max-w-2xl",
79 div { class: "flex items-center gap-3 mb-6",
81 span {
82 class: "px-3 py-1.5 rounded-lg font-mono text-sm font-bold border {method_bg}",
83 "{op.method.as_str()}"
84 }
85 code { class: "font-mono text-lg text-base-content",
86 "{op.path}"
87 }
88 if op.deprecated {
89 span { class: "badge badge-warning badge-sm", "deprecated" }
90 }
91 }
92
93 if let Some(summary) = &op.summary {
95 h1 { class: "text-3xl font-bold tracking-tight mb-3",
96 "{summary}"
97 }
98 }
99
100 if let Some(desc) = &op.description {
102 p { class: "text-base text-base-content/70 mb-6 leading-relaxed",
103 "{desc}"
104 }
105 }
106
107 div { class: "mb-8 flex items-center gap-2",
109 span { class: "text-xs text-base-content/50 font-semibold uppercase tracking-wider",
110 "Base URL"
111 }
112 code { class: "text-sm font-mono text-base-content/70 bg-base-200 px-2 py-1 rounded",
113 "{base_url}"
114 }
115 }
116
117 if !op.parameters.is_empty() {
119 div { class: "mb-8",
120 h2 { class: "text-lg font-semibold mb-4 pb-2 border-b border-base-300",
121 "Parameters"
122 }
123 ParametersList { parameters: op.parameters.clone() }
124 }
125 }
126
127 if let Some(body) = &op.request_body {
129 div { class: "mb-8",
130 h2 { class: "text-lg font-semibold mb-4 pb-2 border-b border-base-300",
131 "Request Body"
132 }
133 RequestBodySection { body: body.clone() }
134 }
135 }
136
137 if !op.responses.is_empty() {
139 div { class: "mb-8",
140 h2 { class: "text-lg font-semibold mb-4 pb-2 border-b border-base-300",
141 "Responses"
142 }
143 ResponsesList { responses: op.responses.clone() }
144 }
145 }
146 }
147 }
148
149 aside { class: "lg:w-[45%] lg:shrink-0 lg:border-l border-base-300 bg-base-200/20",
151 div { class: "lg:sticky lg:top-16 lg:h-[calc(100vh-4rem)] lg:overflow-y-auto p-6 space-y-6",
152 div {
154 h3 { class: "text-sm font-semibold text-base-content/70 uppercase tracking-wider mb-3",
155 "Request"
156 }
157 div { class: "rounded-lg border border-base-300 overflow-hidden",
158 div { class: "px-3 py-2 bg-base-300/50 border-b border-base-300 flex items-center gap-2",
159 MethodBadge { method: op.method }
160 code { class: "text-xs font-mono text-base-content/70 truncate",
161 "{op.path}"
162 }
163 }
164 div { class: "dk-code-block-body bg-base-200",
165 {code_sample(curl.clone(), SampleLang::Bash)}
166 }
167 }
168 }
169
170 if let Some((status_code, response_json)) = &response_example {
172 {
173 let status_color = if status_code.starts_with('2') {
174 "badge-success"
175 } else if status_code.starts_with('3') {
176 "badge-info"
177 } else {
178 "badge-ghost"
179 };
180 rsx! {
181 div {
182 h3 { class: "text-sm font-semibold text-base-content/70 uppercase tracking-wider mb-3",
183 "Response"
184 }
185 div { class: "rounded-lg border border-base-300 overflow-hidden",
186 div { class: "px-3 py-2 bg-base-300/50 border-b border-base-300 flex items-center gap-2",
187 span { class: "badge {status_color} badge-sm font-mono font-bold",
188 "{status_code}"
189 }
190 span { class: "text-xs text-base-content/50",
191 "application/json"
192 }
193 }
194 div { class: "dk-code-block-body bg-base-200 max-h-[60vh] overflow-y-auto",
195 {code_sample(response_json.clone(), SampleLang::Json)}
196 }
197 }
198 }
199 }
200 }
201 }
202 }
203 }
204 }
205 }
206}