docs: improvements and fixes to components in API reference (#9410)

- Change layout of parameters for clearer display
- Fix schema code blocks being longer than container
- switch between showing required to showing optional indicator
- Fixed code tabs not having copy / report buttons

Closes DOCS-936

Preview: https://api-reference-v2-git-docs-api-ref-comp-improv-medusajs.vercel.app/v2/api/store
This commit is contained in:
Shahed Nasser
2024-10-09 20:51:27 +03:00
committed by GitHub
parent 8d3e7b3336
commit 7f1f075222
14 changed files with 239 additions and 187 deletions

View File

@@ -67,6 +67,14 @@ const TagsOperationDescriptionSection = ({
workflow={operation["x-workflow"]}
/>
)}
{operation.externalDocs && (
<>
Related guide:{" "}
<Link href={operation.externalDocs.url} target="_blank">
{operation.externalDocs.description || "Read More"}
</Link>
</>
)}
<Feedback
event="survey_api-ref"
extraData={{
@@ -79,14 +87,6 @@ const TagsOperationDescriptionSection = ({
vertical={true}
question="Did this API Route run successfully?"
/>
{operation.externalDocs && (
<>
Related guide:{" "}
<Link href={operation.externalDocs.url} target="_blank">
{operation.externalDocs.description || "Read More"}
</Link>
</>
)}
{operation.security && (
<TagsOperationDescriptionSectionSecurity
security={operation.security}

View File

@@ -16,100 +16,37 @@ type TagOperationParametersDescriptionProps = {
const TagOperationParametersDescription = ({
schema,
}: TagOperationParametersDescriptionProps) => {
let typeDescription: React.ReactNode = <></>
switch (true) {
case schema.type === "object":
typeDescription = (
<>
{schema.type} {schema.title ? `(${schema.title})` : ""}
{schema.nullable ? ` or null` : ""}
</>
)
break
case schema.type === "array":
typeDescription = (
<>
{schema.type === "array" && formatArrayDescription(schema.items)}
{schema.nullable ? ` or null` : ""}
</>
)
break
case schema.anyOf !== undefined:
case schema.allOf !== undefined:
typeDescription = (
<>
{formatUnionDescription(schema.allOf)}
{schema.nullable ? ` or null` : ""}
</>
)
break
case schema.oneOf !== undefined:
typeDescription = (
<>
{schema.oneOf?.map((item, index) => (
<Fragment key={index}>
{index !== 0 && <> or </>}
{item.type !== "array" && <>{item.title || item.type}</>}
{item.type === "array" && (
<>array{item.items.type ? ` of ${item.items.type}s` : ""}</>
)}
</Fragment>
))}
{schema.nullable ? ` or null` : ""}
</>
)
break
default:
typeDescription = (
<>
{!schema.type ? "any" : schema.type}
{schema.nullable ? ` or null` : ""}
{schema.format ? ` <${schema.format}>` : ""}
</>
)
}
return (
<div className={clsx("w-2/3 break-words pb-0.5")}>
{typeDescription}
<div className={clsx("pb-0.5 flex flex-col gap-0.25")}>
{schema.default !== undefined && (
<>
<br />
<span>
Default:{" "}
<InlineCode className="break-words">
{JSON.stringify(schema.default)}
</InlineCode>
</span>
</>
<span>
Default:{" "}
<InlineCode className="break-words">
{JSON.stringify(schema.default)}
</InlineCode>
</span>
)}
{schema.enum && (
<>
<br />
<span>
Enum:{" "}
{schema.enum.map((value, index) => (
<Fragment key={index}>
{index !== 0 && <>, </>}
<InlineCode key={index}>{JSON.stringify(value)}</InlineCode>
</Fragment>
))}
</span>
</>
<span>
Enum:{" "}
{schema.enum.map((value, index) => (
<Fragment key={index}>
{index !== 0 && <>, </>}
<InlineCode key={index}>{JSON.stringify(value)}</InlineCode>
</Fragment>
))}
</span>
)}
{schema.example !== undefined && (
<>
<br />
<span>
Example:{" "}
<InlineCode className="break-words">
{JSON.stringify(schema.example)}
</InlineCode>
</span>
</>
<span>
Example:{" "}
<InlineCode className="break-words">
{JSON.stringify(schema.example)}
</InlineCode>
</span>
)}
{schema.description && (
<>
<br />
<MDXContentClient
content={capitalize(schema.description)}
className={clsx("!mb-0 [&>*]:!mb-0")}
@@ -120,33 +57,15 @@ const TagOperationParametersDescription = ({
</>
)}
{schema.externalDocs && (
<>
<span>
Related guide:{" "}
<Link href={schema.externalDocs.url} target="_blank">
{schema.externalDocs.description || "Read More"}
</Link>
</>
</span>
)}
</div>
)
}
export default TagOperationParametersDescription
function formatArrayDescription(schema?: SchemaObject) {
if (!schema) {
return "Array"
}
const type =
schema.type === "object"
? `objects ${schema.title ? `(${schema.title})` : ""}`
: `${schema.type || "object"}s`
return `Array of ${type}`
}
function formatUnionDescription(arr?: SchemaObject[]) {
const types = [...new Set(arr?.map((type) => type.type || "object"))]
return <>{types.join(" or ")}</>
}

View File

@@ -1,5 +1,6 @@
import type { SchemaObject } from "@/types/openapi"
import { Badge, ExpandableNotice, FeatureFlagNotice } from "docs-ui"
import { Fragment } from "react"
export type TagOperationParametersNameProps = {
name: string
@@ -12,39 +13,101 @@ const TagOperationParametersName = ({
isRequired,
schema,
}: TagOperationParametersNameProps) => {
let typeDescription: React.ReactNode = <></>
switch (true) {
case schema.type === "object":
typeDescription = (
<>
{schema.type} {schema.title ? `(${schema.title})` : ""}
{schema.nullable ? ` or null` : ""}
</>
)
break
case schema.type === "array":
typeDescription = (
<>
{schema.type === "array" && formatArrayDescription(schema.items)}
{schema.nullable ? ` or null` : ""}
</>
)
break
case schema.anyOf !== undefined:
case schema.allOf !== undefined:
typeDescription = (
<>
{formatUnionDescription(schema.allOf)}
{schema.nullable ? ` or null` : ""}
</>
)
break
case schema.oneOf !== undefined:
typeDescription = (
<>
{schema.oneOf?.map((item, index) => (
<Fragment key={index}>
{index !== 0 && <> or </>}
{item.type !== "array" && <>{item.title || item.type}</>}
{item.type === "array" && (
<>array{item.items.type ? ` of ${item.items.type}s` : ""}</>
)}
</Fragment>
))}
{schema.nullable ? ` or null` : ""}
</>
)
break
default:
typeDescription = (
<>
{!schema.type ? "any" : schema.type}
{schema.nullable ? ` or null` : ""}
{schema.format ? ` <${schema.format}>` : ""}
</>
)
}
return (
<span className="w-1/3 break-words pr-0.5">
<span className="inline-flex gap-0.5 items-center">
<span className="font-monospace">{name}</span>
<span className="text-medusa-fg-muted text-compact-small">
{typeDescription}
</span>
{schema.deprecated && (
<Badge variant="orange" className="ml-1">
deprecated
</Badge>
)}
{schema["x-expandable"] && (
<>
<br />
<ExpandableNotice type="request" link="#expanding-relations" />
</>
<ExpandableNotice type="request" link="#expanding-relations" />
)}
{schema["x-featureFlag"] && (
<>
<br />
<FeatureFlagNotice
featureFlag={schema["x-featureFlag"]}
type="type"
/>
</>
<FeatureFlagNotice featureFlag={schema["x-featureFlag"]} type="type" />
)}
{isRequired && (
<>
<br />
<span className="text-medusa-tag-red-text text-compact-x-small">
required
</span>
</>
{!isRequired && (
<span className="text-medusa-tag-blue-text text-compact-x-small">
optional
</span>
)}
</span>
)
}
export default TagOperationParametersName
function formatArrayDescription(schema?: SchemaObject) {
if (!schema) {
return "Array"
}
const type =
schema.type === "object"
? `objects ${schema.title ? `(${schema.title})` : ""}`
: `${schema.type || "object"}s`
return `Array of ${type}`
}
function formatUnionDescription(arr?: SchemaObject[]) {
const types = [...new Set(arr?.map((type) => type.type || "object"))]
return <>{types.join(" or ")}</>
}

View File

@@ -21,7 +21,7 @@ const TagOperationParametersDefault = ({
return (
<div
className={clsx(
"my-0.5 inline-flex justify-between",
"my-0.5 inline-flex flex-col justify-between gap-0.25",
expandable && "w-[calc(100%-16px)]",
!expandable && "w-full pl-1",
className

View File

@@ -101,16 +101,20 @@ const TagOperationParametersObject = ({
const content = (
<>
{sortedProperties.map((property, index) => (
<TagOperationParameters
schemaObject={{
...properties[property],
parameterName: property,
}}
key={index}
isRequired={
properties[property].isRequired || checkRequired(schema, property)
}
/>
<>
{index !== 0 && <hr className="bg-medusa-border-base my-0" />}
<TagOperationParameters
schemaObject={{
...properties[property],
parameterName: property,
}}
key={index}
isRequired={
properties[property].isRequired ||
checkRequired(schema, property)
}
/>
</>
))}
</>
)

View File

@@ -1,4 +1,4 @@
import { useEffect, useMemo } from "react"
import { useEffect, useMemo, useRef, useState } from "react"
import { SchemaObject } from "../../../../types/openapi"
import TagOperationParameters from "../../Operation/Parameters"
import {
@@ -16,6 +16,8 @@ import useSchemaExample from "../../../../hooks/use-schema-example"
import { InView } from "react-intersection-observer"
import checkElementInViewport from "../../../../utils/check-element-in-viewport"
import { singular } from "pluralize"
import useResizeObserver from "@react-hook/resize-observer"
import clsx from "clsx"
export type TagSectionSchemaProps = {
schema: SchemaObject
@@ -23,6 +25,8 @@ export type TagSectionSchemaProps = {
}
const TagSectionSchema = ({ schema, tagName }: TagSectionSchemaProps) => {
const paramsRef = useRef<HTMLDivElement>(null)
const [maxCodeHeight, setMaxCodeHeight] = useState(0)
const { addItems, setActivePath, activePath } = useSidebar()
const tagSlugName = useMemo(() => getSectionId([tagName]), [tagName])
const formattedName = useMemo(
@@ -39,6 +43,9 @@ const TagSectionSchema = ({ schema, tagName }: TagSectionSchemaProps) => {
skipNonRequired: false,
},
})
useResizeObserver(paramsRef, () => {
setMaxCodeHeight(paramsRef.current?.clientHeight || 0)
})
const { scrollableElement, scrollToElement } = useScrollController()
const root = useMemo(() => {
@@ -106,7 +113,7 @@ const TagSectionSchema = ({ schema, tagName }: TagSectionSchemaProps) => {
>
<DividedLayout
mainContent={
<SectionContainer>
<SectionContainer ref={paramsRef}>
<h2>{formattedName} Object</h2>
<h4 className="border-medusa-border-base border-b py-1.5 mt-2">
Fields
@@ -121,6 +128,11 @@ const TagSectionSchema = ({ schema, tagName }: TagSectionSchemaProps) => {
source={examples[0].content}
lang="json"
title={`The ${formattedName} Object`}
className={clsx(maxCodeHeight && "overflow-auto")}
style={{
// remove padding + extra space
maxHeight: maxCodeHeight ? maxCodeHeight - 212 : "unset",
}}
/>
)}
</SectionContainer>

View File

@@ -74,7 +74,7 @@ const TagSection = ({ tag }: TagSectionProps) => {
return isElmWindow(scrollableElement) ? document.body : scrollableElement
}, [scrollableElement])
const { ref } = useInView({
threshold: 0.5,
threshold: 0.8,
rootMargin: `112px 0px 112px 0px`,
root,
onChange: (inView) => {