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