Skip to content

Commit

Permalink
Refactor schema view (#8)
Browse files Browse the repository at this point in the history
* Refactor schema view

* Allow html in OpenAPI schema descriptions

* Refactor and synchronize sidecar response tabs

* Remove rehype-raw from MDX
  • Loading branch information
dan-lee authored Aug 20, 2024
1 parent dd2fbd5 commit 25ea4ad
Show file tree
Hide file tree
Showing 14 changed files with 461 additions and 374 deletions.
1 change: 1 addition & 0 deletions packages/zudoku/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@
"react-is": "18.3.1",
"react-markdown": "9.0.1",
"react-router-dom": "6.25.1",
"rehype-raw": "7.0.0",
"rehype-slug": "6.0.0",
"remark-comment": "1.0.0",
"remark-directive": "3.0.0",
Expand Down
5 changes: 3 additions & 2 deletions packages/zudoku/src/lib/components/Markdown.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ReactMarkdown from "react-markdown";
import rehypeRaw from "rehype-raw";
import remarkGfm from "remark-gfm";
import { visit } from "unist-util-visit";
import { MdxComponents } from "../util/MdxComponents.js";
Expand All @@ -8,13 +9,13 @@ import { MdxComponents } from "../util/MdxComponents.js";
const rehypeCodeBlockPlugin = () => (tree: any) => {
visit(tree, "element", (node, _index, parent) => {
if (node.tagName === "code") {
node.properties.inline = parent?.tagName !== "pre";
node.properties.inline = String(parent?.tagName !== "pre");
}
});
};

const remarkPlugins = [remarkGfm];
const rehypePlugins = [rehypeCodeBlockPlugin];
const rehypePlugins = [rehypeCodeBlockPlugin, rehypeRaw];

// other styles are defined in main.css .prose
export const ProseClasses = "prose dark:prose-invert prose-neutral";
Expand Down
26 changes: 18 additions & 8 deletions packages/zudoku/src/lib/plugins/openapi/OperationListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useState } from "react";
import { Heading } from "../../components/Heading.js";
import { Markdown } from "../../components/Markdown.js";
import { Card } from "../../ui/Card.js";
Expand All @@ -6,9 +7,9 @@ import { groupBy } from "../../util/groupBy.js";
import { renderIf } from "../../util/renderIf.js";
import { OperationsFragment } from "./OperationList.js";
import { ParameterList } from "./ParameterList.js";
import { SchemaListView } from "./SchemaListView.js";
import { Sidecar } from "./Sidecar.js";
import { FragmentType, useFragment } from "./graphql/index.js";
import { SchemaView } from "./schema/SchemaView.js";
import { SchemaProseClasses } from "./util/prose.js";

export const PARAM_GROUPS = ["path", "query", "header", "cookie"] as const;
Expand All @@ -26,6 +27,8 @@ export const OperationListItem = ({
);

const first = operation.responses.at(0);
const [selectedResponse, setSelectedResponse] = useState(first?.statusCode);

return (
<div
key={operation.operationId}
Expand Down Expand Up @@ -62,20 +65,23 @@ export const OperationListItem = ({
<Heading level={3} className="capitalize">
Request Body
</Heading>
<SchemaListView schema={schema} />
<SchemaView schema={schema} />
</div>
))}
{operation.responses.length > 0 && (
<>
<Heading level={3} className="capitalize mt-8 pt-8 border-t">
Responses
</Heading>
<Tabs defaultValue={`${first?.statusCode}${first?.description}`}>
<Tabs
onValueChange={(value) => setSelectedResponse(value)}
value={selectedResponse}
>
{operation.responses.length > 1 && (
<TabsList>
{operation.responses.map((response) => (
<TabsTrigger
value={response.statusCode + response.description}
value={response.statusCode}
key={response.statusCode}
title={response.description}
>
Expand All @@ -87,16 +93,16 @@ export const OperationListItem = ({
<ul className="list-none m-0 px-0 overflow-hidden">
{operation.responses.map((response) => (
<TabsContent
value={response.statusCode + response.description}
value={response.statusCode}
key={response.statusCode}
>
{renderIf(
response.content?.find((content) => content.schema),
(content) => {
return <SchemaListView schema={content.schema} />;
return <SchemaView schema={content.schema} />;
},
) ?? (
<Card className="font-mono text-sm p-4">
<Card className="font-mono text-sm p-4 italic bg-border/20">
No response body
</Card>
)}
Expand All @@ -108,7 +114,11 @@ export const OperationListItem = ({
)}
</div>

<Sidecar operation={operation} />
<Sidecar
selectedResponse={selectedResponse}
onSelectResponse={setSelectedResponse}
operation={operation}
/>
</div>
);
};
18 changes: 10 additions & 8 deletions packages/zudoku/src/lib/plugins/openapi/ParameterList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,16 @@ export const ParameterList = ({
</Heading>
<Card>
<ul className="list-none m-0 px-0 divide-y ">
{parameters.map((parameter) => (
<ParameterListItem
key={`${parameter.name}-${parameter.in}`}
parameter={parameter}
id={id}
group={group}
/>
))}
{parameters
.sort((a, b) => (a.required === b.required ? 0 : a.required ? -1 : 1))
.map((parameter) => (
<ParameterListItem
key={`${parameter.name}-${parameter.in}`}
parameter={parameter}
id={id}
group={group}
/>
))}
</ul>
</Card>
</>
Expand Down
90 changes: 51 additions & 39 deletions packages/zudoku/src/lib/plugins/openapi/ResponsesSidecarBox.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from "react";
import * as Tabs from "@radix-ui/react-tabs";
import { SyntaxHighlight } from "../../components/SyntaxHighlight.js";
import { type SchemaObject } from "../../oas/graphql/index.js";
import { cn } from "../../util/cn.js";
Expand All @@ -9,52 +9,64 @@ import { generateSchemaExample } from "./util/generateSchemaExample.js";
type Responses = OperationListItemResult["responses"];
export const ResponsesSidecarBox = ({
responses,
selectedResponse,
onSelectResponse,
}: {
responses: Responses;
}) => {
const [tabIndex, setTabIndex] = useState(0);

const activeTab = responses[tabIndex];
const schema = activeTab.content?.[0]?.schema as SchemaObject | undefined;

return (
<SidecarBox.Root>
<SidecarBox.Head className="text-xs grid grid-rows-2 pb-0">
selectedResponse?: string;
onSelectResponse: (response: string) => void;
}) => (
<SidecarBox.Root>
<Tabs.Root
defaultValue={responses[0]?.statusCode}
value={selectedResponse}
onValueChange={(value) => onSelectResponse(value)}
>
<SidecarBox.Head className="text-xs flex flex-col gap-2 pb-0">
<span className="font-mono">Example Responses</span>
<div className="flex gap-2">
{responses.map((response, index) => (
<div
<Tabs.List className="flex gap-2">
{responses.map((response) => (
<Tabs.Trigger
key={response.statusCode}
onClick={() => setTabIndex(index)}
value={response.statusCode}
className={cn(
"text-xs font-mono px-1.5 py-1 pb-px translate-y-px border-b-2 border-transparent rounded-t cursor-pointer",
tabIndex === index
? "text-primary dark:text-inherit border-primary"
: "hover:border-accent-foreground/25",
"data-[state=active]:text-primary data-[state=active]:dark:text-inherit data-[state=active]:border-primary",
"hover:border-accent-foreground/25",
)}
>
{response.statusCode}
</div>
</Tabs.Trigger>
))}
</div>
</Tabs.List>
</SidecarBox.Head>
<SidecarBox.Body>
{schema ? (
<SyntaxHighlight
language="json"
noBackground
className="text-xs"
code={JSON.stringify(generateSchemaExample(schema), null, 2)}
/>
) : (
<span className="text-muted-foreground font-mono italic text-xs">
Empty Response
</span>
)}
</SidecarBox.Body>
<SidecarBox.Footer className="flex justify-end text-xs">
{responses[tabIndex].description}
</SidecarBox.Footer>
</SidecarBox.Root>
);
};
{responses.map((response) => {
const schema = response.content?.[0]?.schema as
| SchemaObject
| undefined;

return (
<Tabs.Content key={response.statusCode} value={response.statusCode}>
<SidecarBox.Body>
{schema ? (
<SyntaxHighlight
language="json"
noBackground
className="text-xs"
code={JSON.stringify(generateSchemaExample(schema), null, 2)}
/>
) : (
<span className="text-muted-foreground font-mono italic text-xs">
Empty Response
</span>
)}
</SidecarBox.Body>
<SidecarBox.Footer className="flex justify-end text-xs">
{response.description}
</SidecarBox.Footer>
</Tabs.Content>
);
})}
</Tabs.Root>
</SidecarBox.Root>
);
75 changes: 0 additions & 75 deletions packages/zudoku/src/lib/plugins/openapi/SchemaListView.tsx

This file was deleted.

Loading

0 comments on commit 25ea4ad

Please sign in to comment.