Skip to content

Commit 702158c

Browse files
committed
fix: component
1 parent c8acbbc commit 702158c

File tree

4 files changed

+327
-96
lines changed

4 files changed

+327
-96
lines changed

docs/api/api/earthquake.mdx

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
---
2+
sidebar_position: 1
3+
---
4+
5+
import ApiEndpoint from "@site/src/components/ApiEndpoint";
6+
7+
# 地震報告
8+
9+
## v1
10+
11+
<ApiEndpoint
12+
method="GET"
13+
baseUrls={[
14+
{ name: "Auto", url: "https://api.exptech.dev/api" },
15+
{ name: "API-1", url: "https://api-1.exptech.dev/api" },
16+
{ name: "API-2", url: "https://api-2.exptech.dev/api" },
17+
]}
18+
endpoint="/v1/report"
19+
params={[
20+
{
21+
name: "limit",
22+
type: "number",
23+
optional: true,
24+
description: "限制回傳數量,最大為 100",
25+
default: "10",
26+
},
27+
{
28+
name: "offset",
29+
type: "number",
30+
optional: true,
31+
description: "偏移量",
32+
default: "0",
33+
},
34+
]}
35+
responses={{
36+
"200": {
37+
description: "成功",
38+
data: {
39+
code: 200,
40+
message: "success",
41+
data: [],
42+
},
43+
},
44+
"400": {
45+
description: "錯誤請求",
46+
data: {
47+
code: 400,
48+
message: "Invalid parameters",
49+
error: "limit must be a positive number",
50+
},
51+
},
52+
"403": {
53+
description: "無權限",
54+
data: {
55+
code: 403,
56+
message: "Forbidden",
57+
error: "API key required",
58+
},
59+
},
60+
"500": {
61+
description: "伺服器錯誤",
62+
data: {
63+
code: 500,
64+
message: "Internal server error",
65+
},
66+
},
67+
}}
68+
/>

docs/api/api/index.mdx

Lines changed: 13 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -6,65 +6,18 @@ import ApiEndpoint from "@site/src/components/ApiEndpoint";
66

77
# API
88

9-
## 地震報告
9+
## Base URL
1010

11-
### v1
11+
### Balance
1212

13-
<ApiEndpoint
14-
method="GET"
15-
baseUrls={[
16-
{ name: "Auto", url: "https://api.exptech.dev/api" },
17-
{ name: "API-1", url: "https://api-1.exptech.dev/api" },
18-
{ name: "API-2", url: "https://api-2.exptech.dev/api" },
19-
]}
20-
endpoint="/v1/report"
21-
params={[
22-
{
23-
name: "limit",
24-
type: "number",
25-
optional: true,
26-
description: "限制回傳數量,最大為 100",
27-
default: "10",
28-
},
29-
{
30-
name: "offset",
31-
type: "number",
32-
optional: true,
33-
description: "偏移量",
34-
default: "0",
35-
},
36-
]}
37-
responses={{
38-
"200": {
39-
description: "成功",
40-
data: {
41-
code: 200,
42-
message: "success",
43-
data: [],
44-
},
45-
},
46-
"400": {
47-
description: "錯誤請求",
48-
data: {
49-
code: 400,
50-
message: "Invalid parameters",
51-
error: "limit must be a positive number",
52-
},
53-
},
54-
"403": {
55-
description: "無權限",
56-
data: {
57-
code: 403,
58-
message: "Forbidden",
59-
error: "API key required",
60-
},
61-
},
62-
"500": {
63-
description: "伺服器錯誤",
64-
data: {
65-
code: 500,
66-
message: "Internal server error",
67-
},
68-
},
69-
}}
70-
/>
13+
- `https://lb.exptech.dev/api`
14+
- `https://lb-1.exptech.dev/api`
15+
- `https://lb-2.exptech.dev/api`
16+
- `https://lb-3.exptech.dev/api`
17+
- `https://lb-4.exptech.dev/api`
18+
19+
### Core
20+
21+
- `https://api.exptech.dev/api`
22+
- `https://api-1.exptech.dev/api`
23+
- `https://api-2.exptech.dev/api`

src/components/ApiEndpoint/index.tsx

Lines changed: 104 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ export default function ApiEndpoint({
4747
Object.keys(responses)[0] || "200"
4848
);
4949
const [copied, setCopied] = useState(false);
50+
const [paramsCollapsed, setParamsCollapsed] = useState(true);
51+
const [responsesCollapsed, setResponsesCollapsed] = useState(true);
5052

5153
const currentBaseUrl = normalizedBaseUrls[selectedBaseUrl]?.url || '';
5254
const fullUrl = `${currentBaseUrl}${endpoint}`;
@@ -110,48 +112,116 @@ export default function ApiEndpoint({
110112

111113
{params.length > 0 && (
112114
<div className={styles.section}>
113-
<h4 className={styles.sectionTitle}>參數</h4>
114-
<div className={styles.paramsList}>
115-
{params.map((param) => (
116-
<div key={param.name} className={styles.param}>
117-
<div className={styles.paramHeader}>
118-
<code className={styles.paramName}>{param.name}</code>
119-
{param.type && <span className={styles.paramType}>{param.type}</span>}
120-
{param.optional && <span className={styles.optional}>optional</span>}
115+
<div className={styles.collapsibleHeader} onClick={() => setParamsCollapsed(!paramsCollapsed)}>
116+
<div className={styles.titleContainer}>
117+
<h4 className={styles.sectionTitle}>參數</h4>
118+
{paramsCollapsed && (
119+
<div className={styles.previewTags}>
120+
{params.slice(0, 3).map((param, index) => (
121+
<span key={index} className={`${styles.previewTag} ${param.optional ? styles.optional : styles.required}`}>
122+
{param.name}
123+
</span>
124+
))}
125+
{params.length > 3 && (
126+
<span className={styles.moreCount}>+{params.length - 3}</span>
127+
)}
121128
</div>
122-
<div className={styles.paramDescription}>
123-
{param.description}
124-
{param.default && <span className={styles.default}> (預設: {param.default})</span>}
125-
</div>
126-
</div>
127-
))}
129+
)}
130+
</div>
131+
<button className={styles.collapseButton}>
132+
<svg
133+
width="16"
134+
height="16"
135+
viewBox="0 0 16 16"
136+
fill="currentColor"
137+
style={{ transform: paramsCollapsed ? 'rotate(0deg)' : 'rotate(90deg)' }}
138+
>
139+
<path d="M6 4l4 4-4 4V4z"/>
140+
</svg>
141+
</button>
128142
</div>
143+
{!paramsCollapsed && (
144+
<div className={styles.paramsList}>
145+
{params.map((param) => (
146+
<div key={param.name} className={styles.param}>
147+
<div className={styles.paramHeader}>
148+
<code className={styles.paramName}>{param.name}</code>
149+
{param.type && <span className={styles.paramType}>{param.type}</span>}
150+
{param.optional && <span className={styles.optional}>optional</span>}
151+
</div>
152+
<div className={styles.paramDescription}>
153+
{param.description}
154+
{param.default && <span className={styles.default}> (預設: {param.default})</span>}
155+
</div>
156+
</div>
157+
))}
158+
</div>
159+
)}
129160
</div>
130161
)}
131162

132163
{Object.keys(responses).length > 0 && (
133164
<div className={styles.section}>
134-
<div className={styles.responseHeader}>
135-
<h4 className={styles.sectionTitle}>回傳</h4>
136-
<select
137-
className={`${styles.statusSelect} ${getStatusColor(selectedStatus)}`}
138-
value={selectedStatus}
139-
onChange={(e) => setSelectedStatus(e.target.value)}
140-
>
141-
{Object.keys(responses).map((status) => (
142-
<option key={status} value={status}>
143-
{status} {responses[status].description && `- ${responses[status].description}`}
144-
</option>
145-
))}
146-
</select>
165+
<div className={styles.collapsibleHeader} onClick={() => setResponsesCollapsed(!responsesCollapsed)}>
166+
<div className={styles.titleContainer}>
167+
<h4 className={styles.sectionTitle}>回傳</h4>
168+
{responsesCollapsed && (
169+
<div className={styles.previewTags}>
170+
{Object.keys(responses).slice(0, 4).map((status) => {
171+
const code = parseInt(status);
172+
let statusType = 'default';
173+
if (code >= 200 && code < 300) statusType = 'success';
174+
else if (code >= 400 && code < 500) statusType = 'error';
175+
else if (code >= 500) statusType = 'serverError';
176+
177+
return (
178+
<span key={status} className={`${styles.previewTag} ${styles[statusType]}`}>
179+
{status}
180+
</span>
181+
);
182+
})}
183+
{Object.keys(responses).length > 4 && (
184+
<span className={styles.moreCount}>+{Object.keys(responses).length - 4}</span>
185+
)}
186+
</div>
187+
)}
188+
</div>
189+
<button className={styles.collapseButton}>
190+
<svg
191+
width="16"
192+
height="16"
193+
viewBox="0 0 16 16"
194+
fill="currentColor"
195+
style={{ transform: responsesCollapsed ? 'rotate(0deg)' : 'rotate(90deg)' }}
196+
>
197+
<path d="M6 4l4 4-4 4V4z"/>
198+
</svg>
199+
</button>
147200
</div>
148-
<pre className={styles.codeBlock}>
149-
<code
150-
dangerouslySetInnerHTML={{
151-
__html: highlightJSON(JSON.stringify(responses[selectedStatus]?.data, null, 2))
152-
}}
153-
/>
154-
</pre>
201+
{!responsesCollapsed && (
202+
<>
203+
<div className={styles.responseHeader}>
204+
<select
205+
className={`${styles.statusSelect} ${getStatusColor(selectedStatus)}`}
206+
value={selectedStatus}
207+
onChange={(e) => setSelectedStatus(e.target.value)}
208+
>
209+
{Object.keys(responses).map((status) => (
210+
<option key={status} value={status}>
211+
{status} {responses[status].description && `- ${responses[status].description}`}
212+
</option>
213+
))}
214+
</select>
215+
</div>
216+
<pre className={styles.codeBlock}>
217+
<code
218+
dangerouslySetInnerHTML={{
219+
__html: highlightJSON(JSON.stringify(responses[selectedStatus]?.data, null, 2))
220+
}}
221+
/>
222+
</pre>
223+
</>
224+
)}
155225
</div>
156226
)}
157227
</div>

0 commit comments

Comments
 (0)