Skip to content

Commit 4451998

Browse files
committed
OpenAPI: improve select UI
1 parent 25393cd commit 4451998

File tree

2 files changed

+71
-46
lines changed

2 files changed

+71
-46
lines changed

packages/openapi/src/ui/operation/client.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,22 +89,28 @@ export function SelectTab({
8989

9090
export function SelectTabTrigger({
9191
items,
92+
className,
9293
...props
93-
}: ComponentProps<typeof SelectTrigger> & { items: string[] }) {
94+
}: ComponentProps<typeof SelectTrigger> & {
95+
items: {
96+
label: ReactNode;
97+
value: string;
98+
}[];
99+
}) {
94100
const { type, setType } = use(Context)!;
95101

96102
return (
97103
<Select value={type ?? ''} onValueChange={setType}>
98104
<SelectTrigger
105+
className={cn('not-prose w-fit min-w-0 *:min-w-0', className)}
99106
{...props}
100-
className={cn('not-prose w-fit', props.className)}
101107
>
102108
<SelectValue />
103109
</SelectTrigger>
104110
<SelectContent>
105-
{items.map((type) => (
106-
<SelectItem key={type} value={type}>
107-
{type}
111+
{items.map(({ label, value }) => (
112+
<SelectItem key={value} value={value}>
113+
{label}
108114
</SelectItem>
109115
))}
110116
</SelectContent>

packages/openapi/src/ui/operation/index.tsx

Lines changed: 60 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -86,19 +86,22 @@ export async function Operation({
8686
const contentTypes = body ? Object.entries(body.content) : null;
8787

8888
if (body && contentTypes && contentTypes.length > 0) {
89-
const [defaultValue] = contentTypes[0];
89+
const items = contentTypes.map(([key]) => ({
90+
label: <code className="text-xs">{key}</code>,
91+
value: key,
92+
}));
9093

9194
bodyNode = (
92-
<SelectTabs defaultValue={defaultValue}>
93-
<div className="flex gap-2 items-end justify-between">
95+
<SelectTabs defaultValue={items[0].value}>
96+
<div className="flex gap-2 items-center justify-between">
9497
{ctx.renderHeading(headingLevel, 'Request Body', {
9598
className: 'my-0!',
9699
})}
97100
{contentTypes.length > 1 ? (
98-
<SelectTabTrigger items={contentTypes.map(([key]) => key)} />
101+
<SelectTabTrigger items={items} className="font-medium" />
99102
) : (
100-
<p className="text-sm text-fd-muted-foreground font-medium not-prose">
101-
{defaultValue}
103+
<p className="text-fd-muted-foreground not-prose">
104+
{items[0].label}
102105
</p>
103106
)}
104107
</div>
@@ -189,29 +192,40 @@ export async function Operation({
189192

190193
if (type === 'operation' && securities.length > 0) {
191194
const securitySchemes = dereferenced.components?.securitySchemes;
192-
const names = securities.map((security) => {
193-
const intersection = new Set<string>();
194-
for (const [key, value] of Object.entries(security)) {
195-
intersection.add(`${key} ${value}`);
196-
}
197-
198-
return Array.from(intersection).join(' & ');
195+
const items = securities.map((security, i) => {
196+
return {
197+
value: String(i),
198+
label: (
199+
<div className="flex flex-col text-xs min-w-0">
200+
{Object.entries(security).map(([key, scopes]) => (
201+
<code key={key} className="truncate">
202+
<span className="font-medium">{key}</span>{' '}
203+
{scopes.length > 0 && (
204+
<span className="text-fd-muted-foreground">
205+
{scopes.join(', ')}
206+
</span>
207+
)}
208+
</code>
209+
))}
210+
</div>
211+
),
212+
};
199213
});
200214

201215
authNode = (
202-
<SelectTabs defaultValue={names[0]}>
203-
<div className="flex items-end justify-between gap-2">
204-
{ctx.renderHeading(headingLevel, 'Authorization')}
205-
{names.length > 1 ? (
206-
<SelectTabTrigger items={names} className="mb-4" />
216+
<SelectTabs defaultValue={items[0].value}>
217+
<div className="flex items-start justify-between gap-2 mt-10 mb-5">
218+
{ctx.renderHeading(headingLevel, 'Authorization', {
219+
className: 'my-0!',
220+
})}
221+
{items.length > 1 ? (
222+
<SelectTabTrigger items={items} />
207223
) : (
208-
<p className="text-sm text-fd-muted-foreground font-medium not-prose mb-7">
209-
{names[0]}
210-
</p>
224+
<p className="not-prose">{items[0].label}</p>
211225
)}
212226
</div>
213227
{securities.map((security, i) => (
214-
<SelectTab key={i} value={names[i]}>
228+
<SelectTab key={i} value={items[i].value}>
215229
{Object.entries(security).map(([key, scopes]) => {
216230
const scheme = securitySchemes?.[key];
217231
if (!scheme) return;
@@ -233,20 +247,22 @@ export async function Operation({
233247

234248
const callbacks = method.callbacks ? Object.entries(method.callbacks) : null;
235249
if (callbacks && callbacks.length > 0) {
236-
const [defaultValue] = callbacks[0];
250+
const items = callbacks.map(([key]) => ({
251+
label: <code className="text-xs">{key}</code>,
252+
value: key,
253+
}));
237254

238255
callbacksNode = (
239-
<SelectTabs defaultValue={defaultValue}>
240-
<div className="flex justify-between gap-2 items-end">
241-
{ctx.renderHeading(headingLevel, 'Callbacks')}
256+
<SelectTabs defaultValue={items[0].value}>
257+
<div className="flex justify-between gap-2 items-end mt-10 mb-5">
258+
{ctx.renderHeading(headingLevel, 'Callbacks', {
259+
className: 'my-0!',
260+
})}
242261
{callbacks.length > 1 ? (
243-
<SelectTabTrigger
244-
items={callbacks.map(([key]) => key)}
245-
className="mb-4"
246-
/>
262+
<SelectTabTrigger items={items} className="font-medium" />
247263
) : (
248-
<p className="text-sm text-fd-muted-foreground font-medium not-prose mb-7">
249-
{defaultValue}
264+
<p className="text-fd-muted-foreground not-prose">
265+
{items[0].label}
250266
</p>
251267
)}
252268
</div>
@@ -368,15 +384,19 @@ async function ResponseAccordion({
368384
let selectorNode: ReactNode = null;
369385

370386
if (contentTypes.length > 0) {
371-
const [defaultValue] = contentTypes[0];
387+
const items = contentTypes.map(([key]) => ({
388+
label: <code className="text-xs">{key}</code>,
389+
value: key,
390+
}));
391+
372392
selectorNode =
373-
contentTypes.length === 1 ? (
374-
<p className="text-sm text-fd-muted-foreground">{defaultValue}</p>
393+
items.length === 1 ? (
394+
<p className="text-fd-muted-foreground not-prose">{items[0].label}</p>
375395
) : (
376-
<SelectTabTrigger items={contentTypes.map(([key]) => key)} />
396+
<SelectTabTrigger items={items} />
377397
);
378398
wrapper = (children) => (
379-
<SelectTabs defaultValue={defaultValue}>{children}</SelectTabs>
399+
<SelectTabs defaultValue={items[0].value}>{children}</SelectTabs>
380400
);
381401
}
382402

@@ -447,7 +467,7 @@ function WebhookCallback({
447467
return (
448468
<div
449469
key={method}
450-
className="border p-3 my-2 prose-no-margin rounded-lg"
470+
className="border p-3 my-2 @container prose-no-margin rounded-lg"
451471
>
452472
<Operation
453473
type="webhook"
@@ -525,16 +545,15 @@ function AuthProperty({
525545
name,
526546
type,
527547
scopes = [],
548+
className,
528549
...props
529550
}: ComponentProps<'div'> & {
530551
name: string;
531552
type: string;
532553
scopes?: string[];
533554
}) {
534555
return (
535-
<div
536-
className={cn('text-sm border-t py-4 first:border-t-0', props.className)}
537-
>
556+
<div className={cn('text-sm border-t py-4 first:border-t-0', className)}>
538557
<div className="flex flex-wrap items-center gap-3 not-prose">
539558
<span className="font-medium font-mono text-fd-primary">{name}</span>
540559
<span className="text-sm font-mono text-fd-muted-foreground">

0 commit comments

Comments
 (0)