docs: create docs workspace (#5174)
* docs: migrate ui docs to docs universe * created yarn workspace * added eslint and tsconfig configurations * fix eslint configurations * fixed eslint configurations * shared tailwind configurations * added shared ui package * added more shared components * migrating more components * made details components shared * move InlineCode component * moved InputText * moved Loading component * Moved Modal component * moved Select components * Moved Tooltip component * moved Search components * moved ColorMode provider * Moved Notification components and providers * used icons package * use UI colors in api-reference * moved Navbar component * used Navbar and Search in UI docs * added Feedback to UI docs * general enhancements * fix color mode * added copy colors file from ui-preset * added features and enhancements to UI docs * move Sidebar component and provider * general fixes and preparations for deployment * update docusaurus version * adjusted versions * fix output directory * remove rootDirectory property * fix yarn.lock * moved code component * added vale for all docs MD and MDX * fix tests * fix vale error * fix deployment errors * change ignore commands * add output directory * fix docs test * general fixes * content fixes * fix announcement script * added changeset * fix vale checks * added nofilter option * fix vale error
This commit is contained in:
24
www/apps/api-reference/components/Description/index.tsx
Normal file
24
www/apps/api-reference/components/Description/index.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
"use server"
|
||||
|
||||
import type { OpenAPIV3 } from "openapi-types"
|
||||
import Section from "../Section"
|
||||
import MDXContentServer from "../MDXContent/Server"
|
||||
|
||||
export type DescriptionProps = {
|
||||
specs: OpenAPIV3.Document
|
||||
}
|
||||
|
||||
const Description = ({ specs }: DescriptionProps) => {
|
||||
return (
|
||||
<Section>
|
||||
<MDXContentServer
|
||||
content={specs.info.description}
|
||||
scope={{
|
||||
specs,
|
||||
}}
|
||||
/>
|
||||
</Section>
|
||||
)
|
||||
}
|
||||
|
||||
export default Description
|
||||
83
www/apps/api-reference/components/DetailedFeedback/index.tsx
Normal file
83
www/apps/api-reference/components/DetailedFeedback/index.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { Label, TextArea, useAnalytics, useModal, ModalFooter } from "docs-ui"
|
||||
|
||||
const DetailedFeedback = () => {
|
||||
const [improvementFeedback, setImprovementFeedback] = useState("")
|
||||
const [positiveFeedback, setPositiveFeedback] = useState("")
|
||||
const [additionalFeedback, setAdditionalFeedback] = useState("")
|
||||
const { loaded, track } = useAnalytics()
|
||||
const { closeModal } = useModal()
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-1 overflow-auto py-1.5 px-2 lg:min-h-[400px]">
|
||||
<div className="flex flex-col gap-1">
|
||||
<Label>What should be improved in this API reference?</Label>
|
||||
<TextArea
|
||||
rows={4}
|
||||
value={improvementFeedback}
|
||||
onChange={(e) => setImprovementFeedback(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Label>Is there a feature you like in this API reference?</Label>
|
||||
<TextArea
|
||||
rows={4}
|
||||
value={positiveFeedback}
|
||||
onChange={(e) => setPositiveFeedback(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Label>Do you have any additional notes or feedback?</Label>
|
||||
<TextArea
|
||||
rows={4}
|
||||
value={additionalFeedback}
|
||||
onChange={(e) => setAdditionalFeedback(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ModalFooter
|
||||
actions={[
|
||||
{
|
||||
children: "Save",
|
||||
onClick: (e) => {
|
||||
if (
|
||||
!loaded ||
|
||||
(!improvementFeedback &&
|
||||
!positiveFeedback &&
|
||||
!additionalFeedback)
|
||||
) {
|
||||
return
|
||||
}
|
||||
const buttonElm = e.target as HTMLButtonElement
|
||||
buttonElm.classList.add("cursor-not-allowed")
|
||||
buttonElm.textContent = "Please wait"
|
||||
track(
|
||||
"api-ref-general-feedback",
|
||||
{
|
||||
feedbackData: {
|
||||
improvementFeedback,
|
||||
positiveFeedback,
|
||||
additionalFeedback,
|
||||
},
|
||||
},
|
||||
function () {
|
||||
buttonElm.textContent = "Thank you!"
|
||||
setTimeout(() => {
|
||||
closeModal()
|
||||
}, 1000)
|
||||
}
|
||||
)
|
||||
},
|
||||
variant: "primary",
|
||||
},
|
||||
]}
|
||||
className="mt-1"
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default DetailedFeedback
|
||||
55
www/apps/api-reference/components/DividedLoading/index.tsx
Normal file
55
www/apps/api-reference/components/DividedLoading/index.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import DividedLayout from "@/layouts/Divided"
|
||||
import { Loading } from "docs-ui"
|
||||
|
||||
type DividedLoadingProps = {
|
||||
className?: string
|
||||
}
|
||||
|
||||
const DividedLoading = ({ className }: DividedLoadingProps) => {
|
||||
return (
|
||||
<DividedLayout
|
||||
mainContent={
|
||||
<>
|
||||
<Loading count={1} className="mb-2 !w-1/3" />
|
||||
<Loading count={1} />
|
||||
<div className="flex gap-1">
|
||||
<Loading count={1} className="!w-1/3" />
|
||||
<Loading count={1} className="!w-2/3" />
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
<Loading count={1} className="!w-1/3" />
|
||||
<Loading count={1} className="!w-2/3" />
|
||||
</div>
|
||||
<Loading count={1} className="mt-2 !w-1/3" />
|
||||
<Loading count={1} />
|
||||
<div className="mt-2 flex gap-1">
|
||||
<Loading count={1} className="!w-1/3" />
|
||||
<Loading count={1} className="!w-2/3" />
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
<Loading count={1} className="!w-1/3" />
|
||||
<Loading count={1} className="!w-2/3" />
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
<Loading count={1} className="!w-1/3" />
|
||||
<Loading count={1} className="!w-2/3" />
|
||||
</div>
|
||||
<Loading count={5} barClassName="mt-1" />
|
||||
</>
|
||||
}
|
||||
codeContent={
|
||||
<>
|
||||
<Loading count={1} />
|
||||
<Loading count={1} className="my-2" />
|
||||
<Loading count={1} barClassName="h-[200px] !rounded-sm" />
|
||||
<Loading count={1} className="my-2" />
|
||||
<Loading count={1} barClassName="h-3 !rounded-sm" />
|
||||
<Loading count={1} barClassName="h-[230px] !rounded-sm" />
|
||||
</>
|
||||
}
|
||||
className={className}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default DividedLoading
|
||||
68
www/apps/api-reference/components/MDXComponents/H2/index.tsx
Normal file
68
www/apps/api-reference/components/MDXComponents/H2/index.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
"use client"
|
||||
|
||||
import { InView } from "react-intersection-observer"
|
||||
import { useSidebar } from "docs-ui"
|
||||
import checkElementInViewport from "../../../utils/check-element-in-viewport"
|
||||
import { useEffect } from "react"
|
||||
import getSectionId from "../../../utils/get-section-id"
|
||||
|
||||
type H2Props = {
|
||||
addToSidebar?: boolean
|
||||
} & React.HTMLAttributes<HTMLHeadingElement>
|
||||
|
||||
const H2 = ({ addToSidebar = true, children, ...props }: H2Props) => {
|
||||
const { activePath, setActivePath, addItems } = useSidebar()
|
||||
|
||||
const handleViewChange = (
|
||||
inView: boolean,
|
||||
entry: IntersectionObserverEntry
|
||||
) => {
|
||||
if (!addToSidebar) {
|
||||
return
|
||||
}
|
||||
const heading = entry.target
|
||||
if (
|
||||
(inView ||
|
||||
checkElementInViewport(heading.parentElement || heading, 40)) &&
|
||||
window.scrollY !== 0 &&
|
||||
activePath !== heading.id
|
||||
) {
|
||||
// can't use next router as it doesn't support
|
||||
// changing url without scrolling
|
||||
history.pushState({}, "", `#${heading.id}`)
|
||||
setActivePath(heading.id)
|
||||
}
|
||||
}
|
||||
const id = getSectionId([children as string])
|
||||
|
||||
useEffect(() => {
|
||||
if (id === (activePath || location.hash.replace("#", ""))) {
|
||||
const elm = document.getElementById(id)
|
||||
elm?.scrollIntoView()
|
||||
}
|
||||
|
||||
addItems([
|
||||
{
|
||||
path: `${id}`,
|
||||
title: children as string,
|
||||
loaded: true,
|
||||
},
|
||||
])
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<InView
|
||||
as="h2"
|
||||
threshold={0.4}
|
||||
skip={!addToSidebar}
|
||||
initialInView={false}
|
||||
{...props}
|
||||
onChange={handleViewChange}
|
||||
id={id}
|
||||
>
|
||||
{children}
|
||||
</InView>
|
||||
)
|
||||
}
|
||||
|
||||
export default H2
|
||||
@@ -0,0 +1,66 @@
|
||||
import type { MDXContentClientProps } from "@/components/MDXContent/Client"
|
||||
import type { MDXContentServerProps } from "@/components/MDXContent/Server"
|
||||
import type { SecuritySchemeObject } from "@/types/openapi"
|
||||
import getSecuritySchemaTypeName from "@/utils/get-security-schema-type-name"
|
||||
import clsx from "clsx"
|
||||
import { Loading } from "docs-ui"
|
||||
import dynamic from "next/dynamic"
|
||||
|
||||
const MDXContentClient = dynamic<MDXContentClientProps>(
|
||||
async () => import("../../../MDXContent/Client"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<MDXContentClientProps>
|
||||
|
||||
const MDXContentServer = dynamic<MDXContentServerProps>(
|
||||
async () => import("../../../MDXContent/Server"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<MDXContentServerProps>
|
||||
|
||||
export type SecurityDescriptionProps = {
|
||||
securitySchema: SecuritySchemeObject
|
||||
isServer?: boolean
|
||||
}
|
||||
|
||||
const SecurityDescription = ({
|
||||
securitySchema,
|
||||
isServer = true,
|
||||
}: SecurityDescriptionProps) => {
|
||||
return (
|
||||
<>
|
||||
<h2>{securitySchema["x-displayName"] as string}</h2>
|
||||
{isServer && <MDXContentServer content={securitySchema.description} />}
|
||||
{!isServer && <MDXContentClient content={securitySchema.description} />}
|
||||
<p>
|
||||
<strong>Security Scheme Type:</strong>{" "}
|
||||
{getSecuritySchemaTypeName(securitySchema)}
|
||||
</p>
|
||||
{(securitySchema.type === "http" || securitySchema.type === "apiKey") && (
|
||||
<p
|
||||
className={clsx(
|
||||
"bg-docs-bg-surface dark:bg-docs-bg-surface-dark",
|
||||
"p-1"
|
||||
)}
|
||||
>
|
||||
<strong>
|
||||
{securitySchema.type === "http"
|
||||
? "HTTP Authorization Scheme"
|
||||
: "Cookie parameter name"}
|
||||
:
|
||||
</strong>{" "}
|
||||
<code>
|
||||
{securitySchema.type === "http"
|
||||
? securitySchema.scheme
|
||||
: securitySchema.name}
|
||||
</code>
|
||||
</p>
|
||||
)}
|
||||
<hr />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default SecurityDescription
|
||||
@@ -0,0 +1,34 @@
|
||||
import dynamic from "next/dynamic"
|
||||
import type { OpenAPIV3 } from "openapi-types"
|
||||
import type { SecurityDescriptionProps } from "./Description"
|
||||
import { Fragment } from "react"
|
||||
|
||||
const SecurityDescription = dynamic<SecurityDescriptionProps>(
|
||||
async () => import("./Description")
|
||||
) as React.FC<SecurityDescriptionProps>
|
||||
|
||||
type SecurityProps = {
|
||||
specs?: OpenAPIV3.Document
|
||||
}
|
||||
|
||||
const Security = ({ specs }: SecurityProps) => {
|
||||
return (
|
||||
<div>
|
||||
{specs && (
|
||||
<>
|
||||
{Object.values(specs.components?.securitySchemes || {}).map(
|
||||
(securitySchema, index) => (
|
||||
<Fragment key={index}>
|
||||
{!("$ref" in securitySchema) && (
|
||||
<SecurityDescription securitySchema={securitySchema} />
|
||||
)}
|
||||
</Fragment>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Security
|
||||
22
www/apps/api-reference/components/MDXComponents/index.tsx
Normal file
22
www/apps/api-reference/components/MDXComponents/index.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { MDXComponents } from "mdx/types"
|
||||
import Security from "./Security"
|
||||
import type { OpenAPIV3 } from "openapi-types"
|
||||
import H2 from "./H2"
|
||||
import { CodeMdx, Kbd, NextLink } from "docs-ui"
|
||||
|
||||
export type ScopeType = {
|
||||
specs?: OpenAPIV3.Document
|
||||
addToSidebar?: boolean
|
||||
}
|
||||
|
||||
const getCustomComponents = (scope?: ScopeType): MDXComponents => {
|
||||
return {
|
||||
Security: () => <Security specs={scope?.specs} />,
|
||||
code: CodeMdx,
|
||||
a: NextLink,
|
||||
h2: (props) => <H2 addToSidebar={scope?.addToSidebar} {...props} />,
|
||||
kbd: Kbd,
|
||||
}
|
||||
}
|
||||
|
||||
export default getCustomComponents
|
||||
@@ -0,0 +1,48 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
import getCustomComponents from "../../MDXComponents"
|
||||
import type { ScopeType } from "../../MDXComponents"
|
||||
import { MDXRemote } from "next-mdx-remote"
|
||||
import type { MDXRemoteProps, MDXRemoteSerializeResult } from "next-mdx-remote"
|
||||
import { serialize } from "next-mdx-remote/serialize"
|
||||
|
||||
export type MDXContentClientProps = {
|
||||
content: any
|
||||
className?: string
|
||||
} & Partial<MDXRemoteProps>
|
||||
|
||||
const MDXContentClient = ({
|
||||
content,
|
||||
className,
|
||||
...props
|
||||
}: MDXContentClientProps) => {
|
||||
const [parsedContent, setParsedContent] = useState<MDXRemoteSerializeResult>()
|
||||
|
||||
useEffect(() => {
|
||||
void serialize(content, {
|
||||
mdxOptions: {
|
||||
// A workaround for an error in next-mdx-remote
|
||||
// more details in this issue:
|
||||
// https://github.com/hashicorp/next-mdx-remote/issues/350
|
||||
development: process.env.NEXT_PUBLIC_ENV === "development",
|
||||
},
|
||||
scope: props.scope,
|
||||
}).then((output) => {
|
||||
setParsedContent(output)
|
||||
})
|
||||
}, [content, props.scope])
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{parsedContent !== undefined && (
|
||||
<MDXRemote
|
||||
{...parsedContent}
|
||||
components={getCustomComponents((props.scope as ScopeType) || {})}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MDXContentClient
|
||||
@@ -0,0 +1,30 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
"use server"
|
||||
|
||||
import { MDXRemote } from "next-mdx-remote/rsc"
|
||||
import getCustomComponents from "../../MDXComponents"
|
||||
import type { ScopeType } from "../../MDXComponents"
|
||||
import type { MDXRemoteProps } from "next-mdx-remote"
|
||||
|
||||
export type MDXContentServerProps = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
content: any
|
||||
} & Partial<MDXRemoteProps>
|
||||
|
||||
const MDXContentServer = ({ content, ...props }: MDXContentServerProps) => {
|
||||
return (
|
||||
<>
|
||||
{/* @ts-ignore promise error */}
|
||||
<MDXRemote
|
||||
source={content}
|
||||
components={getCustomComponents((props.scope as ScopeType) || {})}
|
||||
options={{
|
||||
scope: props.scope,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default MDXContentServer
|
||||
19
www/apps/api-reference/components/MethodLabel/index.tsx
Normal file
19
www/apps/api-reference/components/MethodLabel/index.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Badge, capitalize } from "docs-ui"
|
||||
|
||||
export type MethodLabelProps = {
|
||||
method: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
const MethodLabel = ({ method, className }: MethodLabelProps) => {
|
||||
return (
|
||||
<Badge
|
||||
variant={method === "get" ? "green" : method === "post" ? "blue" : "red"}
|
||||
className={className}
|
||||
>
|
||||
{method === "delete" ? "Del" : capitalize(method)}
|
||||
</Badge>
|
||||
)
|
||||
}
|
||||
|
||||
export default MethodLabel
|
||||
@@ -0,0 +1,28 @@
|
||||
"use client"
|
||||
|
||||
import { Button, useModal, usePageLoading } from "docs-ui"
|
||||
import DetailedFeedback from "../../DetailedFeedback"
|
||||
|
||||
const FeedbackModal = () => {
|
||||
const { setModalProps } = useModal()
|
||||
const { isLoading } = usePageLoading()
|
||||
|
||||
const openModal = () => {
|
||||
if (isLoading) {
|
||||
return
|
||||
}
|
||||
setModalProps({
|
||||
title: "Send your Feedback",
|
||||
children: <DetailedFeedback />,
|
||||
contentClassName: "lg:!min-h-auto !p-0",
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Button onClick={openModal} variant="secondary">
|
||||
Feedback
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export default FeedbackModal
|
||||
50
www/apps/api-reference/components/Navbar/index.tsx
Normal file
50
www/apps/api-reference/components/Navbar/index.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
"use client"
|
||||
|
||||
import { Navbar as UiNavbar, usePageLoading } from "docs-ui"
|
||||
import getLinkWithBasePath from "../../utils/get-link-with-base-path"
|
||||
import { useSidebar } from "docs-ui"
|
||||
import FeedbackModal from "./FeedbackModal"
|
||||
|
||||
const Navbar = () => {
|
||||
const { setMobileSidebarOpen, mobileSidebarOpen } = useSidebar()
|
||||
const { isLoading } = usePageLoading()
|
||||
|
||||
return (
|
||||
<UiNavbar
|
||||
logo={{
|
||||
light: "/images/logo-icon.png",
|
||||
dark: "/images/logo-icon-dark.png",
|
||||
}}
|
||||
items={[
|
||||
{
|
||||
href: `/`,
|
||||
label: "Docs",
|
||||
},
|
||||
{
|
||||
href: `/user-guide`,
|
||||
label: "User Guide",
|
||||
},
|
||||
{
|
||||
href: `${getLinkWithBasePath("/store")}`,
|
||||
label: "Store API",
|
||||
},
|
||||
{
|
||||
href: `${getLinkWithBasePath("/admin")}`,
|
||||
label: "Admin API",
|
||||
},
|
||||
{
|
||||
href: `/ui`,
|
||||
label: "UI",
|
||||
},
|
||||
]}
|
||||
mobileMenuButton={{
|
||||
setMobileSidebarOpen,
|
||||
mobileSidebarOpen,
|
||||
}}
|
||||
additionalActions={<FeedbackModal />}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default Navbar
|
||||
@@ -0,0 +1,21 @@
|
||||
import clsx from "clsx"
|
||||
import SectionDivider from "../Divider"
|
||||
import { forwardRef } from "react"
|
||||
|
||||
type SectionContainerProps = {
|
||||
children: React.ReactNode
|
||||
noTopPadding?: boolean
|
||||
}
|
||||
|
||||
const SectionContainer = forwardRef<HTMLDivElement, SectionContainerProps>(
|
||||
function SectionContainer({ children, noTopPadding = false }, ref) {
|
||||
return (
|
||||
<div className={clsx("relative pb-7", !noTopPadding && "pt-7")} ref={ref}>
|
||||
{children}
|
||||
<SectionDivider className="-left-1.5 lg:!-left-4" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
export default SectionContainer
|
||||
18
www/apps/api-reference/components/Section/Divider/index.tsx
Normal file
18
www/apps/api-reference/components/Section/Divider/index.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import clsx from "clsx"
|
||||
|
||||
type SectionDividerProps = {
|
||||
className?: string
|
||||
}
|
||||
|
||||
const SectionDivider = ({ className }: SectionDividerProps) => {
|
||||
return (
|
||||
<hr
|
||||
className={clsx(
|
||||
"absolute bottom-0 -left-1.5 z-0 m-0 w-screen lg:left-0",
|
||||
className
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default SectionDivider
|
||||
30
www/apps/api-reference/components/Section/index.tsx
Normal file
30
www/apps/api-reference/components/Section/index.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
"use client"
|
||||
|
||||
import clsx from "clsx"
|
||||
import { useEffect, useRef } from "react"
|
||||
|
||||
export type SectionProps = {
|
||||
addToSidebar?: boolean
|
||||
} & React.AllHTMLAttributes<HTMLDivElement>
|
||||
|
||||
const Section = ({ children, className }: SectionProps) => {
|
||||
const sectionRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if ("scrollRestoration" in history) {
|
||||
// disable scroll on refresh
|
||||
history.scrollRestoration = "manual"
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={sectionRef}
|
||||
className={clsx("[&_ul]:list-disc [&_ul]:px-1", "[&_h2]:pt-7", className)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Section
|
||||
23
www/apps/api-reference/components/Space/index.tsx
Normal file
23
www/apps/api-reference/components/Space/index.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
type SpaceProps = {
|
||||
top?: number
|
||||
bottom?: number
|
||||
left?: number
|
||||
right?: number
|
||||
}
|
||||
|
||||
const Space = ({ top = 0, bottom = 0, left = 0, right = 0 }: SpaceProps) => {
|
||||
return (
|
||||
<div
|
||||
className="w-full"
|
||||
style={{
|
||||
height: `1px`,
|
||||
marginTop: `${top ? top - 1 : top}px`,
|
||||
marginBottom: `${bottom ? bottom - 1 : bottom}px`,
|
||||
marginLeft: `${left}px`,
|
||||
marginRight: `${right}px`,
|
||||
}}
|
||||
></div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Space
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { Code } from "@/types/openapi"
|
||||
import { CodeTabs } from "docs-ui"
|
||||
import slugify from "slugify"
|
||||
|
||||
export type TagOperationCodeSectionRequestSamplesProps = {
|
||||
codeSamples: Code[]
|
||||
}
|
||||
|
||||
const TagOperationCodeSectionRequestSamples = ({
|
||||
codeSamples,
|
||||
}: TagOperationCodeSectionRequestSamplesProps) => {
|
||||
return (
|
||||
<div>
|
||||
<h3>Request samples</h3>
|
||||
<CodeTabs
|
||||
tabs={codeSamples.map((codeSample) => ({
|
||||
label: codeSample.label,
|
||||
value: slugify(codeSample.label),
|
||||
code: {
|
||||
...codeSample,
|
||||
collapsed: true,
|
||||
className: "!mb-0",
|
||||
},
|
||||
}))}
|
||||
className="mt-2 !mb-0"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagOperationCodeSectionRequestSamples
|
||||
@@ -0,0 +1,122 @@
|
||||
import { CodeBlock } from "docs-ui"
|
||||
import type { ExampleObject, ResponseObject } from "@/types/openapi"
|
||||
import type { JSONSchema7 } from "json-schema"
|
||||
import stringify from "json-stringify-pretty-compact"
|
||||
import { sample } from "openapi-sampler"
|
||||
import { useCallback, useEffect, useState } from "react"
|
||||
|
||||
export type TagsOperationCodeSectionResponsesSampleProps = {
|
||||
response: ResponseObject
|
||||
} & React.AllHTMLAttributes<HTMLDivElement>
|
||||
|
||||
const TagsOperationCodeSectionResponsesSample = ({
|
||||
response,
|
||||
className,
|
||||
}: TagsOperationCodeSectionResponsesSampleProps) => {
|
||||
const [examples, setExamples] = useState<ExampleObject[]>([])
|
||||
const [selectedExample, setSelectedExample] = useState<
|
||||
ExampleObject | undefined
|
||||
>()
|
||||
|
||||
const initExamples = useCallback(() => {
|
||||
if (!response.content) {
|
||||
return []
|
||||
}
|
||||
const contentSchema = Object.values(response.content)[0]
|
||||
const tempExamples = []
|
||||
if (contentSchema.examples) {
|
||||
Object.entries(contentSchema.examples).forEach(([value, example]) => {
|
||||
if ("$ref" in example) {
|
||||
return []
|
||||
}
|
||||
|
||||
tempExamples.push({
|
||||
title: example.summary || "",
|
||||
value,
|
||||
content: stringify(example.value, {
|
||||
maxLength: 50,
|
||||
}),
|
||||
})
|
||||
})
|
||||
} else if (contentSchema.example) {
|
||||
tempExamples.push({
|
||||
title: "",
|
||||
value: "",
|
||||
content: stringify(contentSchema.example, {
|
||||
maxLength: 50,
|
||||
}),
|
||||
})
|
||||
} else {
|
||||
const contentSample = stringify(
|
||||
sample(
|
||||
{
|
||||
...contentSchema.schema,
|
||||
} as JSONSchema7,
|
||||
{
|
||||
skipNonRequired: true,
|
||||
}
|
||||
),
|
||||
{
|
||||
maxLength: 50,
|
||||
}
|
||||
)
|
||||
|
||||
tempExamples.push({
|
||||
title: "",
|
||||
value: "",
|
||||
content: contentSample,
|
||||
})
|
||||
}
|
||||
|
||||
return tempExamples
|
||||
}, [response.content])
|
||||
|
||||
useEffect(() => {
|
||||
const tempExamples = initExamples()
|
||||
setExamples(tempExamples)
|
||||
setSelectedExample(tempExamples[0])
|
||||
}, [initExamples])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={className}>
|
||||
{response.content && (
|
||||
<span>Content type: {Object.keys(response.content)[0]}</span>
|
||||
)}
|
||||
<>
|
||||
{examples.length > 1 && (
|
||||
<select
|
||||
onChange={(event) =>
|
||||
setSelectedExample(
|
||||
examples.find((ex) => ex.value === event.target.value)
|
||||
)
|
||||
}
|
||||
className="border-medusa-border-base dark:border-medusa-border-base-dark my-1 w-full rounded-sm border p-0.5"
|
||||
>
|
||||
{examples.map((example, index) => (
|
||||
<option value={example.value} key={index}>
|
||||
{example.title}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
{selectedExample && (
|
||||
<CodeBlock
|
||||
source={selectedExample.content}
|
||||
lang={getLanguageFromMedia(Object.keys(response.content)[0])}
|
||||
collapsed={true}
|
||||
className="mt-2 mb-0"
|
||||
/>
|
||||
)}
|
||||
{!selectedExample && <>Empty Response</>}
|
||||
</>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagsOperationCodeSectionResponsesSample
|
||||
|
||||
const getLanguageFromMedia = (media: string) => {
|
||||
return media.substring(media.indexOf("/"))
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import type { Operation } from "@/types/openapi"
|
||||
import dynamic from "next/dynamic"
|
||||
import type { TagsOperationCodeSectionResponsesSampleProps } from "./Sample"
|
||||
import { Badge } from "docs-ui"
|
||||
|
||||
const TagsOperationCodeSectionResponsesSample =
|
||||
dynamic<TagsOperationCodeSectionResponsesSampleProps>(
|
||||
async () => import("./Sample")
|
||||
) as React.FC<TagsOperationCodeSectionResponsesSampleProps>
|
||||
|
||||
type TagsOperationCodeSectionResponsesProps = {
|
||||
operation: Operation
|
||||
}
|
||||
|
||||
const TagsOperationCodeSectionResponses = ({
|
||||
operation,
|
||||
}: TagsOperationCodeSectionResponsesProps) => {
|
||||
const responseCodes = Object.keys(operation.responses)
|
||||
const responseCode = responseCodes.find((rc) => rc === "200" || rc === "201")
|
||||
const response = responseCode ? operation.responses[responseCode] : null
|
||||
|
||||
if (!response) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-0.5 flex items-center gap-0.5">
|
||||
<h3 className="mb-0">Response </h3>
|
||||
<Badge variant="green">{responseCode}</Badge>
|
||||
</div>
|
||||
|
||||
<TagsOperationCodeSectionResponsesSample response={response} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagsOperationCodeSectionResponses
|
||||
@@ -0,0 +1,58 @@
|
||||
"use client"
|
||||
|
||||
import MethodLabel from "@/components/MethodLabel"
|
||||
import type { Operation } from "@/types/openapi"
|
||||
import TagsOperationCodeSectionResponses from "./Responses"
|
||||
import type { TagOperationCodeSectionRequestSamplesProps } from "./RequestSamples"
|
||||
import dynamic from "next/dynamic"
|
||||
import clsx from "clsx"
|
||||
import { CopyButton } from "docs-ui"
|
||||
import { SquareTwoStack } from "@medusajs/icons"
|
||||
|
||||
const TagOperationCodeSectionRequestSamples =
|
||||
dynamic<TagOperationCodeSectionRequestSamplesProps>(
|
||||
async () => import("./RequestSamples")
|
||||
) as React.FC<TagOperationCodeSectionRequestSamplesProps>
|
||||
|
||||
export type TagOperationCodeSectionProps = {
|
||||
operation: Operation
|
||||
method: string
|
||||
endpointPath: string
|
||||
} & React.HTMLAttributes<HTMLDivElement>
|
||||
|
||||
const TagOperationCodeSection = ({
|
||||
operation,
|
||||
method,
|
||||
endpointPath,
|
||||
className,
|
||||
}: TagOperationCodeSectionProps) => {
|
||||
return (
|
||||
<div className={clsx("mt-2 flex flex-col gap-2", className)}>
|
||||
<div
|
||||
className={clsx(
|
||||
"bg-medusa-bg-subtle border-medusa-border-base px-0.75 rounded border py-0.5",
|
||||
"text-code-body flex w-full justify-between gap-1",
|
||||
"dark:bg-medusa-bg-subtle-dark dark:border-medusa-border-base-dark"
|
||||
)}
|
||||
>
|
||||
<div className={clsx("flex w-[calc(100%-36px)] gap-1")}>
|
||||
<MethodLabel method={method} className="h-fit" />
|
||||
<code className="text-medusa-fg-subtle dark:text-medusa-fg-subtle-dark break-words break-all">
|
||||
{endpointPath}
|
||||
</code>
|
||||
</div>
|
||||
<CopyButton text={endpointPath} tooltipClassName="font-base">
|
||||
<SquareTwoStack className="text-medusa-fg-muted dark:text-medusa-fg-muted-dark" />
|
||||
</CopyButton>
|
||||
</div>
|
||||
{operation["x-codeSamples"] && (
|
||||
<TagOperationCodeSectionRequestSamples
|
||||
codeSamples={operation["x-codeSamples"]}
|
||||
/>
|
||||
)}
|
||||
<TagsOperationCodeSectionResponses operation={operation} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagOperationCodeSection
|
||||
@@ -0,0 +1,71 @@
|
||||
import type { Parameter, SchemaObject } from "@/types/openapi"
|
||||
import TagOperationParameters from "../../Parameters"
|
||||
|
||||
export type TagsOperationDescriptionSectionParametersProps = {
|
||||
parameters: Parameter[]
|
||||
}
|
||||
|
||||
const TagsOperationDescriptionSectionParameters = ({
|
||||
parameters,
|
||||
}: TagsOperationDescriptionSectionParametersProps) => {
|
||||
const pathParameters: SchemaObject = {
|
||||
type: "object",
|
||||
required: [],
|
||||
properties: {},
|
||||
}
|
||||
const queryParameters: SchemaObject = {
|
||||
type: "object",
|
||||
required: [],
|
||||
properties: {},
|
||||
}
|
||||
|
||||
parameters.forEach((parameter) => {
|
||||
const parameterObject = {
|
||||
...parameter.schema,
|
||||
parameterName: parameter.name,
|
||||
description: parameter.description,
|
||||
example: parameter.example,
|
||||
examples: parameter.examples,
|
||||
}
|
||||
if (parameter.in === "path") {
|
||||
if (parameter.required) {
|
||||
pathParameters.required?.push(parameter.name)
|
||||
}
|
||||
pathParameters.properties[parameter.name] = parameterObject
|
||||
} else if (parameter.in === "query") {
|
||||
if (parameter.required) {
|
||||
queryParameters.required?.push(parameter.name)
|
||||
}
|
||||
queryParameters.properties[parameter.name] = parameterObject
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
{Object.values(pathParameters.properties).length > 0 && (
|
||||
<>
|
||||
<h3 className="border-medusa-border-base dark:border-medusa-border-base-dark border-b py-1.5">
|
||||
Path Parameters
|
||||
</h3>
|
||||
<TagOperationParameters
|
||||
schemaObject={pathParameters}
|
||||
topLevel={true}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{Object.values(queryParameters.properties).length > 0 && (
|
||||
<>
|
||||
<h3 className="border-medusa-border-base dark:border-medusa-border-base-dark border-b py-1.5">
|
||||
Query Parameters
|
||||
</h3>
|
||||
<TagOperationParameters
|
||||
schemaObject={queryParameters}
|
||||
topLevel={true}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagsOperationDescriptionSectionParameters
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { RequestObject } from "@/types/openapi"
|
||||
import TagOperationParameters from "../../Parameters"
|
||||
import { DetailsSummary } from "docs-ui"
|
||||
|
||||
export type TagsOperationDescriptionSectionRequestProps = {
|
||||
requestBody: RequestObject
|
||||
}
|
||||
|
||||
const TagsOperationDescriptionSectionRequest = ({
|
||||
requestBody,
|
||||
}: TagsOperationDescriptionSectionRequestProps) => {
|
||||
return (
|
||||
<>
|
||||
<DetailsSummary
|
||||
title="Request Body"
|
||||
subtitle={Object.keys(requestBody.content)[0]}
|
||||
expandable={false}
|
||||
className="border-t-0"
|
||||
titleClassName="text-h3"
|
||||
/>
|
||||
<TagOperationParameters
|
||||
schemaObject={
|
||||
requestBody.content[Object.keys(requestBody.content)[0]].schema
|
||||
}
|
||||
topLevel={true}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagsOperationDescriptionSectionRequest
|
||||
@@ -0,0 +1,100 @@
|
||||
import type { ResponsesObject } from "@/types/openapi"
|
||||
import clsx from "clsx"
|
||||
import TagOperationParameters from "../../Parameters"
|
||||
import { Fragment } from "react"
|
||||
import { Badge, Details, DetailsSummary } from "docs-ui"
|
||||
|
||||
export type TagsOperationDescriptionSectionResponsesProps = {
|
||||
responses: ResponsesObject
|
||||
}
|
||||
|
||||
const TagsOperationDescriptionSectionResponses = ({
|
||||
responses,
|
||||
}: TagsOperationDescriptionSectionResponsesProps) => {
|
||||
return (
|
||||
<>
|
||||
<h3 className="my-1.5">Responses</h3>
|
||||
<div
|
||||
className={clsx("[&>details:not(:first-of-type)>summary]:border-t-0")}
|
||||
>
|
||||
{Object.entries(responses).map(([code, response], index) => {
|
||||
return (
|
||||
<Fragment key={index}>
|
||||
{response.content && (
|
||||
<>
|
||||
{(code === "200" || code === "201") && (
|
||||
<>
|
||||
<DetailsSummary
|
||||
title={`${code} ${response.description}`}
|
||||
subtitle={Object.keys(response.content)[0]}
|
||||
badge={<Badge variant="green">Success</Badge>}
|
||||
expandable={false}
|
||||
className={clsx(
|
||||
index !== 0 && "border-t-0",
|
||||
index === 0 && "border-b-0"
|
||||
)}
|
||||
/>
|
||||
<TagOperationParameters
|
||||
schemaObject={
|
||||
response.content[Object.keys(response.content)[0]]
|
||||
.schema
|
||||
}
|
||||
topLevel={true}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{code !== "200" && code !== "201" && (
|
||||
<Details
|
||||
summaryElm={
|
||||
<DetailsSummary
|
||||
title={`${code} ${response.description}`}
|
||||
subtitle={Object.keys(response.content)[0]}
|
||||
badge={<Badge variant="red">Error</Badge>}
|
||||
open={index === 0}
|
||||
/>
|
||||
}
|
||||
openInitial={index === 0}
|
||||
className={clsx(index > 1 && "border-t-0")}
|
||||
>
|
||||
<TagOperationParameters
|
||||
schemaObject={
|
||||
response.content[Object.keys(response.content)[0]]
|
||||
.schema
|
||||
}
|
||||
topLevel={true}
|
||||
/>
|
||||
</Details>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{!response.content && (
|
||||
<DetailsSummary
|
||||
title={`${code} ${response.description}`}
|
||||
subtitle={"Empty response"}
|
||||
badge={
|
||||
<Badge
|
||||
variant={
|
||||
code === "200" || code === "201" ? "green" : "red"
|
||||
}
|
||||
>
|
||||
{code === "200" || code === "201" ? "Success" : "Error"}
|
||||
</Badge>
|
||||
}
|
||||
expandable={false}
|
||||
className={clsx(
|
||||
index !== 0 && "border-t-0",
|
||||
index === 0 &&
|
||||
Object.entries(responses).length > 1 &&
|
||||
"border-b-0"
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagsOperationDescriptionSectionResponses
|
||||
@@ -0,0 +1,36 @@
|
||||
import { useBaseSpecs } from "@/providers/base-specs"
|
||||
import type { OpenAPIV3 } from "openapi-types"
|
||||
import { Card } from "docs-ui"
|
||||
|
||||
export type TagsOperationDescriptionSectionSecurityProps = {
|
||||
security: OpenAPIV3.SecurityRequirementObject[]
|
||||
}
|
||||
|
||||
const TagsOperationDescriptionSectionSecurity = ({
|
||||
security,
|
||||
}: TagsOperationDescriptionSectionSecurityProps) => {
|
||||
const { getSecuritySchema } = useBaseSpecs()
|
||||
|
||||
const getDescription = () => {
|
||||
let str = ""
|
||||
security.forEach((item) => {
|
||||
if (str.length) {
|
||||
str += " or "
|
||||
}
|
||||
str += getSecuritySchema(Object.keys(item)[0])?.["x-displayName"]
|
||||
})
|
||||
return str
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="my-2">
|
||||
<Card
|
||||
title="Authorization"
|
||||
text={getDescription()}
|
||||
href="#authentication"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagsOperationDescriptionSectionSecurity
|
||||
@@ -0,0 +1,107 @@
|
||||
"use client"
|
||||
|
||||
import type { Operation } from "@/types/openapi"
|
||||
import type { TagsOperationDescriptionSectionSecurityProps } from "./Security"
|
||||
import type { TagsOperationDescriptionSectionRequestProps } from "./RequestBody"
|
||||
import type { TagsOperationDescriptionSectionResponsesProps } from "./Responses"
|
||||
import dynamic from "next/dynamic"
|
||||
import TagsOperationDescriptionSectionParameters from "./Parameters"
|
||||
import MDXContentClient from "@/components/MDXContent/Client"
|
||||
import type { TagsOperationFeatureFlagNoticeProps } from "../FeatureFlagNotice"
|
||||
import { useArea } from "../../../../providers/area"
|
||||
import { Feedback, Badge, NextLink } from "docs-ui"
|
||||
import { usePathname } from "next/navigation"
|
||||
import formatReportLink from "../../../../utils/format-report-link"
|
||||
|
||||
const TagsOperationDescriptionSectionSecurity =
|
||||
dynamic<TagsOperationDescriptionSectionSecurityProps>(
|
||||
async () => import("./Security")
|
||||
) as React.FC<TagsOperationDescriptionSectionSecurityProps>
|
||||
|
||||
const TagsOperationDescriptionSectionRequest =
|
||||
dynamic<TagsOperationDescriptionSectionRequestProps>(
|
||||
async () => import("./RequestBody")
|
||||
) as React.FC<TagsOperationDescriptionSectionRequestProps>
|
||||
|
||||
const TagsOperationDescriptionSectionResponses =
|
||||
dynamic<TagsOperationDescriptionSectionResponsesProps>(
|
||||
async () => import("./Responses")
|
||||
) as React.FC<TagsOperationDescriptionSectionResponsesProps>
|
||||
|
||||
const TagsOperationFeatureFlagNotice =
|
||||
dynamic<TagsOperationFeatureFlagNoticeProps>(
|
||||
async () => import("../FeatureFlagNotice")
|
||||
) as React.FC<TagsOperationFeatureFlagNoticeProps>
|
||||
|
||||
type TagsOperationDescriptionSectionProps = {
|
||||
operation: Operation
|
||||
}
|
||||
const TagsOperationDescriptionSection = ({
|
||||
operation,
|
||||
}: TagsOperationDescriptionSectionProps) => {
|
||||
const { area } = useArea()
|
||||
const pathname = usePathname()
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>
|
||||
{operation.summary}
|
||||
{operation.deprecated && (
|
||||
<Badge variant="orange" className="ml-0.5">
|
||||
deprecated
|
||||
</Badge>
|
||||
)}
|
||||
{operation["x-featureFlag"] && (
|
||||
<TagsOperationFeatureFlagNotice
|
||||
featureFlag={operation["x-featureFlag"]}
|
||||
tooltipTextClassName="font-normal text-medusa-fg-subtle dark:text-medusa-fg-subtle-dark"
|
||||
badgeClassName="ml-0.5"
|
||||
/>
|
||||
)}
|
||||
</h2>
|
||||
<div className="my-1">
|
||||
<MDXContentClient content={operation.description} />
|
||||
</div>
|
||||
<Feedback
|
||||
event="survey_api-ref"
|
||||
extraData={{
|
||||
area,
|
||||
section: operation.summary,
|
||||
}}
|
||||
pathName={pathname}
|
||||
reportLink={formatReportLink(area, operation.summary)}
|
||||
className="!my-2"
|
||||
vertical={true}
|
||||
question="Did this endpoint run successfully?"
|
||||
/>
|
||||
{operation.externalDocs && (
|
||||
<>
|
||||
Related guide:{" "}
|
||||
<NextLink href={operation.externalDocs.url} target="_blank">
|
||||
{operation.externalDocs.description || "Read More"}
|
||||
</NextLink>
|
||||
</>
|
||||
)}
|
||||
{operation.security && (
|
||||
<TagsOperationDescriptionSectionSecurity
|
||||
security={operation.security}
|
||||
/>
|
||||
)}
|
||||
{operation.parameters && (
|
||||
<TagsOperationDescriptionSectionParameters
|
||||
parameters={operation.parameters}
|
||||
/>
|
||||
)}
|
||||
{operation.requestBody && (
|
||||
<TagsOperationDescriptionSectionRequest
|
||||
requestBody={operation.requestBody}
|
||||
/>
|
||||
)}
|
||||
<TagsOperationDescriptionSectionResponses
|
||||
responses={operation.responses}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagsOperationDescriptionSection
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Badge, NextLink, Tooltip } from "docs-ui"
|
||||
|
||||
export type TagsOperationFeatureFlagNoticeProps = {
|
||||
featureFlag: string
|
||||
type?: "endpoint" | "parameter"
|
||||
tooltipTextClassName?: string
|
||||
badgeClassName?: string
|
||||
}
|
||||
|
||||
const TagsOperationFeatureFlagNotice = ({
|
||||
featureFlag,
|
||||
type = "endpoint",
|
||||
tooltipTextClassName,
|
||||
badgeClassName,
|
||||
}: TagsOperationFeatureFlagNoticeProps) => {
|
||||
return (
|
||||
<Tooltip
|
||||
tooltipChildren={
|
||||
<span className={tooltipTextClassName}>
|
||||
To use this {type}, make sure to
|
||||
<br />
|
||||
<NextLink
|
||||
href="https://docs.medusajs.com/development/feature-flags/toggle"
|
||||
target="__blank"
|
||||
>
|
||||
enable its feature flag: <code>{featureFlag}</code>
|
||||
</NextLink>
|
||||
</span>
|
||||
}
|
||||
clickable
|
||||
>
|
||||
<Badge variant="green" className={badgeClassName}>
|
||||
feature flag
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagsOperationFeatureFlagNotice
|
||||
@@ -0,0 +1,152 @@
|
||||
import MDXContentClient from "@/components/MDXContent/Client"
|
||||
import type { SchemaObject } from "@/types/openapi"
|
||||
import clsx from "clsx"
|
||||
import dynamic from "next/dynamic"
|
||||
import { Fragment } from "react"
|
||||
import { NextLink, type InlineCodeProps, capitalize } from "docs-ui"
|
||||
|
||||
const InlineCode = dynamic<InlineCodeProps>(
|
||||
async () => (await import("docs-ui")).InlineCode
|
||||
) as React.FC<InlineCodeProps>
|
||||
|
||||
type TagOperationParametersDescriptionProps = {
|
||||
schema: SchemaObject
|
||||
}
|
||||
|
||||
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}
|
||||
{schema.nullable ? ` or null` : ""}
|
||||
{schema.format ? ` <${schema.format}>` : ""}
|
||||
</>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className={clsx("w-2/3 break-words pb-0.5")}>
|
||||
{typeDescription}
|
||||
{schema.default !== undefined && (
|
||||
<>
|
||||
<br />
|
||||
<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>
|
||||
</>
|
||||
)}
|
||||
{schema.example !== undefined && (
|
||||
<>
|
||||
<br />
|
||||
<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")}
|
||||
scope={{
|
||||
addToSidebar: false,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{schema.externalDocs && (
|
||||
<>
|
||||
Related guide:{" "}
|
||||
<NextLink href={schema.externalDocs.url} target="_blank">
|
||||
{schema.externalDocs.description || "Read More"}
|
||||
</NextLink>
|
||||
</>
|
||||
)}
|
||||
</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 ")}</>
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import type { SchemaObject } from "@/types/openapi"
|
||||
import dynamic from "next/dynamic"
|
||||
import type { TooltipProps } from "docs-ui"
|
||||
import type { TagsOperationFeatureFlagNoticeProps } from "../../FeatureFlagNotice"
|
||||
import { Badge, NextLink } from "docs-ui"
|
||||
|
||||
const Tooltip = dynamic<TooltipProps>(
|
||||
async () => (await import("docs-ui")).Tooltip
|
||||
) as React.FC<TooltipProps>
|
||||
|
||||
const TagsOperationFeatureFlagNotice =
|
||||
dynamic<TagsOperationFeatureFlagNoticeProps>(
|
||||
async () => import("../../FeatureFlagNotice")
|
||||
) as React.FC<TagsOperationFeatureFlagNoticeProps>
|
||||
|
||||
export type TagOperationParametersNameProps = {
|
||||
name: string
|
||||
isRequired?: boolean
|
||||
schema: SchemaObject
|
||||
}
|
||||
|
||||
const TagOperationParametersName = ({
|
||||
name,
|
||||
isRequired,
|
||||
schema,
|
||||
}: TagOperationParametersNameProps) => {
|
||||
return (
|
||||
<span className="w-1/3 break-words pr-0.5">
|
||||
<span className="font-monospace">{name}</span>
|
||||
{schema.deprecated && (
|
||||
<Badge variant="orange" className="ml-1">
|
||||
deprecated
|
||||
</Badge>
|
||||
)}
|
||||
{schema["x-expandable"] && (
|
||||
<>
|
||||
<br />
|
||||
<Tooltip
|
||||
tooltipChildren={
|
||||
<>
|
||||
If this request accepts an <code>expand</code> parameter,
|
||||
<br /> this field can be{" "}
|
||||
<NextLink href="#expanding-fields">expanded</NextLink> into an
|
||||
object.
|
||||
</>
|
||||
}
|
||||
clickable
|
||||
>
|
||||
<Badge variant="blue">expandable</Badge>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
{schema["x-featureFlag"] && (
|
||||
<>
|
||||
<br />
|
||||
<TagsOperationFeatureFlagNotice
|
||||
featureFlag={schema["x-featureFlag"]}
|
||||
type="parameter"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{isRequired && (
|
||||
<>
|
||||
<br />
|
||||
<span className="text-medusa-tag-red-text dark:text-medusa-tag-red-text-dark text-compact-x-small">
|
||||
required
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagOperationParametersName
|
||||
@@ -0,0 +1,24 @@
|
||||
import clsx from "clsx"
|
||||
|
||||
export type TagsOperationParametersNestedProps =
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
|
||||
const TagsOperationParametersNested = ({
|
||||
children,
|
||||
...props
|
||||
}: TagsOperationParametersNestedProps) => {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={clsx(
|
||||
props.className,
|
||||
"bg-docs-bg-surface dark:bg-docs-bg-surface-dark px-1 pt-1",
|
||||
"border-medusa-border-base dark:border-medusa-border-base-dark my-1 rounded-sm border"
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagsOperationParametersNested
|
||||
@@ -0,0 +1,44 @@
|
||||
import type { SchemaObject } from "@/types/openapi"
|
||||
import clsx from "clsx"
|
||||
import type { TagOperationParametersProps } from ".."
|
||||
import dynamic from "next/dynamic"
|
||||
import { Loading } from "docs-ui"
|
||||
|
||||
const TagOperationParameters = dynamic<TagOperationParametersProps>(
|
||||
async () => import(".."),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagOperationParametersProps>
|
||||
|
||||
type TagsOperationParametersSectionProps = {
|
||||
header?: string
|
||||
contentType?: string
|
||||
schema: SchemaObject
|
||||
}
|
||||
|
||||
const TagsOperationParametersSection = ({
|
||||
header,
|
||||
contentType,
|
||||
schema,
|
||||
}: TagsOperationParametersSectionProps) => {
|
||||
return (
|
||||
<>
|
||||
{header && (
|
||||
<h3
|
||||
className={clsx(!contentType && "my-2", contentType && "mt-2 mb-0")}
|
||||
>
|
||||
{header}
|
||||
</h3>
|
||||
)}
|
||||
{contentType && (
|
||||
<span className={clsx("mb-2 inline-block")}>
|
||||
Content type: {contentType}
|
||||
</span>
|
||||
)}
|
||||
<TagOperationParameters schemaObject={schema} topLevel={true} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagsOperationParametersSection
|
||||
@@ -0,0 +1,79 @@
|
||||
import type { SchemaObject } from "@/types/openapi"
|
||||
import dynamic from "next/dynamic"
|
||||
import type { TagOperationParametersDefaultProps } from "../Default"
|
||||
import type { TagOperationParametersProps } from "../.."
|
||||
import TagsOperationParametersNested from "../../Nested"
|
||||
import { Details, Loading } from "docs-ui"
|
||||
|
||||
const TagOperationParametersDefault =
|
||||
dynamic<TagOperationParametersDefaultProps>(
|
||||
async () => import("../Default"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagOperationParametersDefaultProps>
|
||||
|
||||
const TagOperationParameters = dynamic<TagOperationParametersProps>(
|
||||
async () => import("../.."),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagOperationParametersProps>
|
||||
|
||||
export type TagOperationParametersArrayProps = {
|
||||
name: string
|
||||
schema: SchemaObject
|
||||
isRequired?: boolean
|
||||
}
|
||||
|
||||
const TagOperationParametersArray = ({
|
||||
name,
|
||||
schema,
|
||||
isRequired,
|
||||
}: TagOperationParametersArrayProps) => {
|
||||
if (schema.type !== "array") {
|
||||
return <></>
|
||||
}
|
||||
|
||||
if (
|
||||
!schema.items ||
|
||||
(schema.items.type !== "object" &&
|
||||
schema.items.type !== "array" &&
|
||||
schema.items.type !== undefined) ||
|
||||
(schema.items.type === "object" &&
|
||||
!schema.items.properties &&
|
||||
!schema.items.allOf &&
|
||||
!schema.items.anyOf &&
|
||||
!schema.items.oneOf)
|
||||
) {
|
||||
return (
|
||||
<TagOperationParametersDefault
|
||||
name={name}
|
||||
schema={schema}
|
||||
isRequired={isRequired}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Details
|
||||
summaryElm={
|
||||
<summary className="cursor-pointer">
|
||||
<TagOperationParametersDefault
|
||||
name={name}
|
||||
schema={schema}
|
||||
isRequired={isRequired}
|
||||
expandable={true}
|
||||
/>
|
||||
</summary>
|
||||
}
|
||||
className="!border-y-0"
|
||||
>
|
||||
<TagsOperationParametersNested>
|
||||
<TagOperationParameters schemaObject={schema.items} topLevel={true} />
|
||||
</TagsOperationParametersNested>
|
||||
</Details>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagOperationParametersArray
|
||||
@@ -0,0 +1,42 @@
|
||||
import type { SchemaObject } from "@/types/openapi"
|
||||
import TagOperationParametersDescription from "../../Description"
|
||||
import clsx from "clsx"
|
||||
import TagOperationParametersName from "../../Name"
|
||||
|
||||
export type TagOperationParametersDefaultProps = {
|
||||
name?: string
|
||||
schema: SchemaObject
|
||||
isRequired?: boolean
|
||||
className?: string
|
||||
expandable?: boolean
|
||||
}
|
||||
|
||||
const TagOperationParametersDefault = ({
|
||||
name,
|
||||
schema,
|
||||
isRequired,
|
||||
className,
|
||||
expandable = false,
|
||||
}: TagOperationParametersDefaultProps) => {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"my-0.5 inline-flex justify-between",
|
||||
expandable && "w-[calc(100%-16px)]",
|
||||
!expandable && "w-full pl-1",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{name && (
|
||||
<TagOperationParametersName
|
||||
name={name}
|
||||
isRequired={isRequired}
|
||||
schema={schema}
|
||||
/>
|
||||
)}
|
||||
<TagOperationParametersDescription schema={schema} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagOperationParametersDefault
|
||||
@@ -0,0 +1,135 @@
|
||||
import type { SchemaObject } from "@/types/openapi"
|
||||
import TagOperationParametersDefault from "../Default"
|
||||
import dynamic from "next/dynamic"
|
||||
import type { TagOperationParametersProps } from "../.."
|
||||
import type { TagsOperationParametersNestedProps } from "../../Nested"
|
||||
import checkRequired from "@/utils/check-required"
|
||||
import { Loading, type DetailsProps } from "docs-ui"
|
||||
|
||||
const TagOperationParameters = dynamic<TagOperationParametersProps>(
|
||||
async () => import("../.."),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagOperationParametersProps>
|
||||
|
||||
const TagsOperationParametersNested =
|
||||
dynamic<TagsOperationParametersNestedProps>(
|
||||
async () => import("../../Nested"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagsOperationParametersNestedProps>
|
||||
|
||||
const Details = dynamic<DetailsProps>(
|
||||
async () => (await import("docs-ui")).Details,
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<DetailsProps>
|
||||
|
||||
export type TagOperationParametersObjectProps = {
|
||||
name?: string
|
||||
schema: SchemaObject
|
||||
isRequired?: boolean
|
||||
topLevel?: boolean
|
||||
}
|
||||
|
||||
const TagOperationParametersObject = ({
|
||||
name,
|
||||
schema,
|
||||
isRequired,
|
||||
topLevel = false,
|
||||
}: TagOperationParametersObjectProps) => {
|
||||
if (
|
||||
(schema.type !== "object" && schema.type !== undefined) ||
|
||||
(!schema.properties && !name)
|
||||
) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
const getPropertyDescriptionElm = (expandable = false) => {
|
||||
const content = (
|
||||
<TagOperationParametersDefault
|
||||
name={name}
|
||||
schema={schema}
|
||||
isRequired={isRequired}
|
||||
expandable={expandable}
|
||||
/>
|
||||
)
|
||||
return expandable ? (
|
||||
<summary className="cursor-pointer">{content}</summary>
|
||||
) : (
|
||||
<>{content}</>
|
||||
)
|
||||
}
|
||||
|
||||
const getPropertyParameterElms = (isNested = false) => {
|
||||
// sort properties to show required fields first
|
||||
const sortedProperties = Object.keys(schema.properties).sort(
|
||||
(property1, property2) => {
|
||||
schema.properties[property1].isRequired = checkRequired(
|
||||
schema,
|
||||
property1
|
||||
)
|
||||
schema.properties[property2].isRequired = checkRequired(
|
||||
schema,
|
||||
property2
|
||||
)
|
||||
|
||||
return schema.properties[property1].isRequired &&
|
||||
schema.properties[property2].isRequired
|
||||
? 0
|
||||
: schema.properties[property1].isRequired
|
||||
? -1
|
||||
: 1
|
||||
}
|
||||
)
|
||||
const content = (
|
||||
<>
|
||||
{sortedProperties.map((property, index) => (
|
||||
<TagOperationParameters
|
||||
schemaObject={{
|
||||
...schema.properties[property],
|
||||
parameterName: property,
|
||||
}}
|
||||
key={index}
|
||||
isRequired={
|
||||
schema.properties[property].isRequired ||
|
||||
checkRequired(schema, property)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
return (
|
||||
<>
|
||||
{isNested && (
|
||||
<TagsOperationParametersNested>
|
||||
{content}
|
||||
</TagsOperationParametersNested>
|
||||
)}
|
||||
{!isNested && <div>{content}</div>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
if (!schema.properties) {
|
||||
return getPropertyDescriptionElm()
|
||||
}
|
||||
|
||||
if (topLevel) {
|
||||
return getPropertyParameterElms()
|
||||
}
|
||||
|
||||
return (
|
||||
<Details
|
||||
summaryElm={getPropertyDescriptionElm(true)}
|
||||
className="!border-y-0"
|
||||
>
|
||||
{getPropertyParameterElms(true)}
|
||||
</Details>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagOperationParametersObject
|
||||
@@ -0,0 +1,132 @@
|
||||
import type { SchemaObject } from "@/types/openapi"
|
||||
import clsx from "clsx"
|
||||
import dynamic from "next/dynamic"
|
||||
import { useState } from "react"
|
||||
import type { TagOperationParametersDefaultProps } from "../Default"
|
||||
import type { TagsOperationParametersNestedProps } from "../../Nested"
|
||||
import type { TagOperationParametersProps } from "../.."
|
||||
import { Details, Loading } from "docs-ui"
|
||||
|
||||
const TagOperationParameters = dynamic<TagOperationParametersProps>(
|
||||
async () => import("../.."),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagOperationParametersProps>
|
||||
|
||||
const TagOperationParametersDefault =
|
||||
dynamic<TagOperationParametersDefaultProps>(
|
||||
async () => import("../Default"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagOperationParametersDefaultProps>
|
||||
|
||||
const TagsOperationParametersNested =
|
||||
dynamic<TagsOperationParametersNestedProps>(
|
||||
async () => import("../../Nested"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagsOperationParametersNestedProps>
|
||||
|
||||
export type TagOperationParamatersOneOfProps = {
|
||||
schema: SchemaObject
|
||||
isRequired?: boolean
|
||||
isNested?: boolean
|
||||
}
|
||||
|
||||
const TagOperationParamatersOneOf = ({
|
||||
schema,
|
||||
isRequired = false,
|
||||
isNested = false,
|
||||
}: TagOperationParamatersOneOfProps) => {
|
||||
const [activeTab, setActiveTab] = useState<number>(0)
|
||||
|
||||
const getName = (item: SchemaObject): string => {
|
||||
if (item.title) {
|
||||
return item.title
|
||||
}
|
||||
|
||||
if (item.anyOf || item.allOf) {
|
||||
// return the name of any of the items
|
||||
const name = item.anyOf
|
||||
? item.anyOf.find((i) => i.title !== undefined)?.title
|
||||
: item.allOf?.find((i) => i.title !== undefined)?.title
|
||||
|
||||
if (name) {
|
||||
return name
|
||||
}
|
||||
}
|
||||
|
||||
return item.type || ""
|
||||
}
|
||||
|
||||
const getContent = () => {
|
||||
return (
|
||||
<>
|
||||
<div className={clsx("flex items-center gap-1 pl-1")}>
|
||||
<span className="inline-block">One of</span>
|
||||
<ul className="mb-0 flex list-none gap-1">
|
||||
{schema.oneOf?.map((item, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className={clsx(
|
||||
"rounded-xs cursor-pointer p-0.5",
|
||||
"border border-solid",
|
||||
activeTab === index && [
|
||||
"bg-medusa-bg-subtle border-medusa-border-strong",
|
||||
"dark:bg-medusa-bg-subtle-dark dark:border-medusa-border-strong-dark",
|
||||
],
|
||||
activeTab !== index && [
|
||||
"bg-medusa-bg-base border-medusa-border-base",
|
||||
"dark:bg-medusa-bg-base-dark dark:border-medusa-border-base-dark",
|
||||
]
|
||||
)}
|
||||
onClick={() => setActiveTab(index)}
|
||||
>
|
||||
{getName(item)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{schema.oneOf && (
|
||||
<>
|
||||
<TagOperationParameters
|
||||
schemaObject={schema.oneOf[activeTab]}
|
||||
topLevel={true}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{isNested && (
|
||||
<Details
|
||||
summaryElm={
|
||||
<summary className="cursor-pointer">
|
||||
<TagOperationParametersDefault
|
||||
schema={schema}
|
||||
name={schema.parameterName || schema.title || ""}
|
||||
isRequired={isRequired}
|
||||
expandable={true}
|
||||
/>
|
||||
</summary>
|
||||
}
|
||||
className="!border-y-0"
|
||||
>
|
||||
<TagsOperationParametersNested>
|
||||
{getContent()}
|
||||
</TagsOperationParametersNested>
|
||||
</Details>
|
||||
)}
|
||||
{!isNested && getContent()}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagOperationParamatersOneOf
|
||||
@@ -0,0 +1,65 @@
|
||||
import type { SchemaObject } from "@/types/openapi"
|
||||
import dynamic from "next/dynamic"
|
||||
import type { TagOperationParametersDefaultProps } from "../Default"
|
||||
import { TagOperationParametersObjectProps } from "../Object"
|
||||
import { Loading } from "docs-ui"
|
||||
|
||||
const TagOperationParametersObject = dynamic<TagOperationParametersObjectProps>(
|
||||
async () => import("../Object"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagOperationParametersObjectProps>
|
||||
|
||||
const TagOperationParametersDefault =
|
||||
dynamic<TagOperationParametersDefaultProps>(
|
||||
async () => import("../Default"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagOperationParametersDefaultProps>
|
||||
|
||||
export type TagOperationParametersUnionProps = {
|
||||
name: string
|
||||
schema: SchemaObject
|
||||
isRequired?: boolean
|
||||
topLevel?: boolean
|
||||
}
|
||||
|
||||
const TagOperationParametersUnion = ({
|
||||
name,
|
||||
schema,
|
||||
isRequired,
|
||||
topLevel,
|
||||
}: TagOperationParametersUnionProps) => {
|
||||
const objectSchema = schema.anyOf
|
||||
? schema.anyOf.find((item) => item.type === "object" && item.properties)
|
||||
: schema.allOf?.find((item) => item.type === "object" && item.properties)
|
||||
|
||||
if (!objectSchema) {
|
||||
return (
|
||||
<TagOperationParametersDefault
|
||||
schema={schema}
|
||||
name={name}
|
||||
isRequired={isRequired}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (!objectSchema.description) {
|
||||
objectSchema.description = schema.anyOf
|
||||
? schema.anyOf.find((item) => item.description !== undefined)?.description
|
||||
: schema.allOf?.find((item) => item.description !== undefined)
|
||||
?.description
|
||||
}
|
||||
|
||||
return (
|
||||
<TagOperationParametersObject
|
||||
schema={objectSchema}
|
||||
name={name}
|
||||
topLevel={topLevel}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagOperationParametersUnion
|
||||
@@ -0,0 +1,116 @@
|
||||
import type { SchemaObject } from "@/types/openapi"
|
||||
import dynamic from "next/dynamic"
|
||||
import type { TagOperationParametersObjectProps } from "./Types/Object"
|
||||
import type { TagOperationParametersDefaultProps } from "./Types/Default"
|
||||
import type { TagOperationParametersArrayProps } from "./Types/Array"
|
||||
import type { TagOperationParametersUnionProps } from "./Types/Union"
|
||||
import type { TagOperationParamatersOneOfProps } from "./Types/OneOf"
|
||||
import checkRequired from "@/utils/check-required"
|
||||
import { Loading } from "docs-ui"
|
||||
|
||||
const TagOperationParametersObject = dynamic<TagOperationParametersObjectProps>(
|
||||
async () => import("./Types/Object"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagOperationParametersObjectProps>
|
||||
const TagOperationParametersDefault =
|
||||
dynamic<TagOperationParametersDefaultProps>(
|
||||
async () => import("./Types/Default"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagOperationParametersDefaultProps>
|
||||
const TagOperationParametersArray = dynamic<TagOperationParametersArrayProps>(
|
||||
async () => import("./Types/Array"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagOperationParametersArrayProps>
|
||||
const TagOperationParametersUnion = dynamic<TagOperationParametersUnionProps>(
|
||||
async () => import("./Types/Union"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagOperationParametersUnionProps>
|
||||
const TagOperationParamatersOneOf = dynamic<TagOperationParamatersOneOfProps>(
|
||||
async () => import("./Types/OneOf"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagOperationParamatersOneOfProps>
|
||||
|
||||
export type TagOperationParametersProps = {
|
||||
schemaObject: SchemaObject
|
||||
topLevel?: boolean
|
||||
className?: string
|
||||
isRequired?: boolean
|
||||
}
|
||||
|
||||
const TagOperationParameters = ({
|
||||
schemaObject,
|
||||
className,
|
||||
topLevel = false,
|
||||
isRequired: originalIsRequired = false,
|
||||
}: TagOperationParametersProps) => {
|
||||
const isRequired =
|
||||
originalIsRequired || checkRequired(schemaObject, schemaObject.title)
|
||||
const propertyName = schemaObject.parameterName || schemaObject.title || ""
|
||||
|
||||
const getElement = () => {
|
||||
if (schemaObject.anyOf || schemaObject.allOf) {
|
||||
return (
|
||||
<TagOperationParametersUnion
|
||||
schema={schemaObject}
|
||||
name={propertyName}
|
||||
isRequired={isRequired}
|
||||
topLevel={topLevel}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (schemaObject.oneOf) {
|
||||
return (
|
||||
<TagOperationParamatersOneOf
|
||||
schema={schemaObject}
|
||||
isNested={!topLevel}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (schemaObject.type === "array") {
|
||||
return (
|
||||
<TagOperationParametersArray
|
||||
name={propertyName}
|
||||
schema={schemaObject}
|
||||
isRequired={isRequired}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (schemaObject.type === "object" || !schemaObject.type) {
|
||||
return (
|
||||
<TagOperationParametersObject
|
||||
name={propertyName}
|
||||
schema={schemaObject}
|
||||
topLevel={topLevel}
|
||||
isRequired={isRequired}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<TagOperationParametersDefault
|
||||
schema={schemaObject}
|
||||
name={propertyName}
|
||||
isRequired={isRequired}
|
||||
/>
|
||||
)
|
||||
|
||||
return <></>
|
||||
}
|
||||
|
||||
return <div className={className}>{getElement()}</div>
|
||||
}
|
||||
|
||||
export default TagOperationParameters
|
||||
126
www/apps/api-reference/components/Tags/Operation/index.tsx
Normal file
126
www/apps/api-reference/components/Tags/Operation/index.tsx
Normal file
@@ -0,0 +1,126 @@
|
||||
"use client"
|
||||
|
||||
import type { Operation } from "@/types/openapi"
|
||||
import clsx from "clsx"
|
||||
import type { OpenAPIV3 } from "openapi-types"
|
||||
import getSectionId from "@/utils/get-section-id"
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||
import dynamic from "next/dynamic"
|
||||
import { useInView } from "react-intersection-observer"
|
||||
import { useSidebar } from "docs-ui"
|
||||
import type { TagOperationCodeSectionProps } from "./CodeSection"
|
||||
import TagsOperationDescriptionSection from "./DescriptionSection"
|
||||
import DividedLayout from "@/layouts/Divided"
|
||||
import { useLoading } from "@/providers/loading"
|
||||
import SectionDivider from "../../Section/Divider"
|
||||
|
||||
const TagOperationCodeSection = dynamic<TagOperationCodeSectionProps>(
|
||||
async () => import("./CodeSection")
|
||||
) as React.FC<TagOperationCodeSectionProps>
|
||||
|
||||
export type TagOperationProps = {
|
||||
operation: Operation
|
||||
method?: string
|
||||
tag: OpenAPIV3.TagObject
|
||||
endpointPath: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
const TagOperation = ({
|
||||
operation,
|
||||
method,
|
||||
endpointPath,
|
||||
className,
|
||||
}: TagOperationProps) => {
|
||||
const { setActivePath } = useSidebar()
|
||||
const [show, setShow] = useState(false)
|
||||
const path = useMemo(
|
||||
() => getSectionId([...(operation.tags || []), operation.operationId]),
|
||||
[operation]
|
||||
)
|
||||
const nodeRef = useRef<Element | null>(null)
|
||||
const { loading, removeLoading } = useLoading()
|
||||
const { ref } = useInView({
|
||||
threshold: 0.3,
|
||||
rootMargin: `112px 0px 112px 0px`,
|
||||
onChange: (changedInView) => {
|
||||
if (changedInView) {
|
||||
if (!show) {
|
||||
if (loading) {
|
||||
removeLoading()
|
||||
}
|
||||
setShow(true)
|
||||
}
|
||||
// can't use next router as it doesn't support
|
||||
// changing url without scrolling
|
||||
history.replaceState({}, "", `#${path}`)
|
||||
setActivePath(path)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
// Use `useCallback` so we don't recreate the function on each render
|
||||
const setRefs = useCallback(
|
||||
(node: Element | null) => {
|
||||
// Ref's from useRef needs to have the node assigned to `current`
|
||||
nodeRef.current = node
|
||||
// Callback refs, like the one from `useInView`, is a function that takes the node as an argument
|
||||
ref(node)
|
||||
},
|
||||
[ref]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const enableShow = () => {
|
||||
setShow(true)
|
||||
}
|
||||
|
||||
if (nodeRef && nodeRef.current) {
|
||||
removeLoading()
|
||||
const currentHash = location.hash.replace("#", "")
|
||||
if (currentHash === path) {
|
||||
setTimeout(() => {
|
||||
nodeRef.current?.scrollIntoView()
|
||||
enableShow()
|
||||
}, 100)
|
||||
} else if (currentHash.split("_")[0] === path.split("_")[0]) {
|
||||
enableShow()
|
||||
}
|
||||
}
|
||||
}, [nodeRef, path])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx("relative min-h-screen w-full pb-7", className)}
|
||||
id={path}
|
||||
ref={setRefs}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
"flex w-full justify-between gap-1 opacity-0",
|
||||
!show && "invisible",
|
||||
show && "animate-fadeIn"
|
||||
)}
|
||||
style={{
|
||||
animationFillMode: "forwards",
|
||||
}}
|
||||
>
|
||||
<DividedLayout
|
||||
mainContent={
|
||||
<TagsOperationDescriptionSection operation={operation} />
|
||||
}
|
||||
codeContent={
|
||||
<TagOperationCodeSection
|
||||
method={method || ""}
|
||||
operation={operation}
|
||||
endpointPath={endpointPath}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<SectionDivider />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagOperation
|
||||
101
www/apps/api-reference/components/Tags/Paths/index.tsx
Normal file
101
www/apps/api-reference/components/Tags/Paths/index.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
"use client"
|
||||
|
||||
import getSectionId from "@/utils/get-section-id"
|
||||
import fetcher from "@/utils/swr-fetcher"
|
||||
import type { OpenAPIV3 } from "openapi-types"
|
||||
import useSWR from "swr"
|
||||
import type { Operation, PathsObject } from "@/types/openapi"
|
||||
import { SidebarItemSections, useSidebar, type SidebarItemType } from "docs-ui"
|
||||
import { Fragment, useEffect, useMemo } from "react"
|
||||
import dynamic from "next/dynamic"
|
||||
import type { TagOperationProps } from "../Operation"
|
||||
import { useArea } from "@/providers/area"
|
||||
import getLinkWithBasePath from "@/utils/get-link-with-base-path"
|
||||
import clsx from "clsx"
|
||||
import { useBaseSpecs } from "@/providers/base-specs"
|
||||
import getTagChildSidebarItems from "@/utils/get-tag-child-sidebar-items"
|
||||
import { useLoading } from "@/providers/loading"
|
||||
import DividedLoading from "@/components/DividedLoading"
|
||||
|
||||
const TagOperation = dynamic<TagOperationProps>(
|
||||
async () => import("../Operation")
|
||||
) as React.FC<TagOperationProps>
|
||||
|
||||
export type TagPathsProps = {
|
||||
tag: OpenAPIV3.TagObject
|
||||
} & React.HTMLAttributes<HTMLDivElement>
|
||||
|
||||
const TagPaths = ({ tag, className }: TagPathsProps) => {
|
||||
const tagSlugName = useMemo(() => getSectionId([tag.name]), [tag])
|
||||
const { area } = useArea()
|
||||
const { items, addItems, findItemInSection } = useSidebar()
|
||||
const { baseSpecs } = useBaseSpecs()
|
||||
const { loading } = useLoading()
|
||||
// if paths are already loaded since through
|
||||
// the expanded field, they're loaded directly
|
||||
// otherwise, they're loaded using the API endpoint
|
||||
let paths: PathsObject =
|
||||
baseSpecs?.expandedTags &&
|
||||
Object.hasOwn(baseSpecs.expandedTags, tagSlugName)
|
||||
? baseSpecs.expandedTags[tagSlugName]
|
||||
: {}
|
||||
const { data } = useSWR<{
|
||||
paths: PathsObject
|
||||
}>(
|
||||
!Object.keys(paths).length
|
||||
? getLinkWithBasePath(`/tag?tagName=${tagSlugName}&area=${area}`)
|
||||
: null,
|
||||
fetcher,
|
||||
{
|
||||
errorRetryInterval: 2000,
|
||||
}
|
||||
)
|
||||
|
||||
paths = data?.paths || paths
|
||||
|
||||
useEffect(() => {
|
||||
if (paths) {
|
||||
const parentItem = findItemInSection(
|
||||
items[SidebarItemSections.BOTTOM],
|
||||
{ path: tagSlugName },
|
||||
false
|
||||
)
|
||||
if (!parentItem?.children?.length) {
|
||||
const items: SidebarItemType[] = getTagChildSidebarItems(paths)
|
||||
|
||||
addItems(items, {
|
||||
section: SidebarItemSections.BOTTOM,
|
||||
parent: {
|
||||
path: tagSlugName,
|
||||
changeLoaded: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [paths])
|
||||
|
||||
return (
|
||||
<div className={clsx("relative", className)}>
|
||||
{loading && <DividedLoading className="mt-7" />}
|
||||
{Object.entries(paths).map(([endpointPath, operations], pathIndex) => (
|
||||
<Fragment key={pathIndex}>
|
||||
{Object.entries(operations).map(
|
||||
([method, operation], operationIndex) => (
|
||||
<TagOperation
|
||||
method={method}
|
||||
operation={operation as Operation}
|
||||
tag={tag}
|
||||
key={`${pathIndex}-${operationIndex}`}
|
||||
endpointPath={endpointPath}
|
||||
className={clsx("pt-7")}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagPaths
|
||||
126
www/apps/api-reference/components/Tags/Section/index.tsx
Normal file
126
www/apps/api-reference/components/Tags/Section/index.tsx
Normal file
@@ -0,0 +1,126 @@
|
||||
"use client"
|
||||
|
||||
import getSectionId from "@/utils/get-section-id"
|
||||
import type { OpenAPIV3 } from "openapi-types"
|
||||
import { useInView } from "react-intersection-observer"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { useSidebar } from "docs-ui"
|
||||
import dynamic from "next/dynamic"
|
||||
import type { SectionProps } from "../../Section"
|
||||
import type { MDXContentClientProps } from "../../MDXContent/Client"
|
||||
import TagPaths from "../Paths"
|
||||
import DividedLayout from "@/layouts/Divided"
|
||||
import LoadingProvider from "@/providers/loading"
|
||||
import SectionContainer from "../../Section/Container"
|
||||
import { useArea } from "../../../providers/area"
|
||||
import SectionDivider from "../../Section/Divider"
|
||||
import clsx from "clsx"
|
||||
import { Feedback, Loading, NextLink } from "docs-ui"
|
||||
import { usePathname } from "next/navigation"
|
||||
import formatReportLink from "../../../utils/format-report-link"
|
||||
|
||||
export type TagSectionProps = {
|
||||
tag: OpenAPIV3.TagObject
|
||||
} & React.HTMLAttributes<HTMLDivElement>
|
||||
|
||||
const Section = dynamic<SectionProps>(
|
||||
async () => import("../../Section")
|
||||
) as React.FC<SectionProps>
|
||||
|
||||
const MDXContentClient = dynamic<MDXContentClientProps>(
|
||||
async () => import("../../MDXContent/Client"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<MDXContentClientProps>
|
||||
|
||||
const TagSection = ({ tag }: TagSectionProps) => {
|
||||
const { activePath, setActivePath } = useSidebar()
|
||||
const [loadPaths, setLoadPaths] = useState(false)
|
||||
const slugTagName = useMemo(() => getSectionId([tag.name]), [tag])
|
||||
const { area } = useArea()
|
||||
const pathname = usePathname()
|
||||
const { ref } = useInView({
|
||||
threshold: 0.5,
|
||||
rootMargin: `112px 0px 112px 0px`,
|
||||
onChange: (inView) => {
|
||||
if (inView && !loadPaths) {
|
||||
setLoadPaths(true)
|
||||
}
|
||||
if (inView) {
|
||||
// ensure that the hash link doesn't change if it links to an inner path
|
||||
const currentHashArr = location.hash.replace("#", "").split("_")
|
||||
if (currentHashArr.length < 2 || currentHashArr[0] !== slugTagName) {
|
||||
// can't use next router as it doesn't support
|
||||
// changing url without scrolling
|
||||
history.replaceState({}, "", `#${slugTagName}`)
|
||||
setActivePath(slugTagName)
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (activePath && activePath.includes(slugTagName)) {
|
||||
const tagName = activePath.split("_")
|
||||
if (tagName.length === 1 && tagName[0] === slugTagName) {
|
||||
const elm = document.getElementById(tagName[0]) as Element
|
||||
elm?.scrollIntoView()
|
||||
} else if (tagName.length > 1 && tagName[0] === slugTagName) {
|
||||
setLoadPaths(true)
|
||||
}
|
||||
}
|
||||
}, [slugTagName, activePath])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx("min-h-screen", !loadPaths && "relative")}
|
||||
id={slugTagName}
|
||||
>
|
||||
<DividedLayout
|
||||
ref={ref}
|
||||
mainContent={
|
||||
<SectionContainer>
|
||||
<h2>{tag.name}</h2>
|
||||
{tag.description && (
|
||||
<Section>
|
||||
<MDXContentClient
|
||||
content={tag.description}
|
||||
scope={{
|
||||
addToSidebar: false,
|
||||
}}
|
||||
/>
|
||||
</Section>
|
||||
)}
|
||||
{tag.externalDocs && (
|
||||
<>
|
||||
Related guide:{" "}
|
||||
<NextLink href={tag.externalDocs.url} target="_blank">
|
||||
{tag.externalDocs.description || "Read More"}
|
||||
</NextLink>
|
||||
</>
|
||||
)}
|
||||
<Feedback
|
||||
event="survey_api-ref"
|
||||
extraData={{
|
||||
area,
|
||||
section: tag.name,
|
||||
}}
|
||||
pathName={pathname}
|
||||
reportLink={formatReportLink(area, tag.name)}
|
||||
/>
|
||||
</SectionContainer>
|
||||
}
|
||||
codeContent={<></>}
|
||||
/>
|
||||
{loadPaths && (
|
||||
<LoadingProvider initialLoading={true}>
|
||||
<TagPaths tag={tag} />
|
||||
</LoadingProvider>
|
||||
)}
|
||||
{!loadPaths && <SectionDivider />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagSection
|
||||
97
www/apps/api-reference/components/Tags/index.tsx
Normal file
97
www/apps/api-reference/components/Tags/index.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
"use client"
|
||||
|
||||
import type { OpenAPIV3 } from "openapi-types"
|
||||
import { useEffect, useState } from "react"
|
||||
import useSWR from "swr"
|
||||
import fetcher from "@/utils/swr-fetcher"
|
||||
import { useBaseSpecs } from "@/providers/base-specs"
|
||||
import dynamic from "next/dynamic"
|
||||
import type { TagSectionProps } from "./Section"
|
||||
import { useArea } from "@/providers/area"
|
||||
import getLinkWithBasePath from "@/utils/get-link-with-base-path"
|
||||
import { SidebarItemSections, useSidebar } from "docs-ui"
|
||||
import getSectionId from "@/utils/get-section-id"
|
||||
import { ExpandedDocument } from "@/types/openapi"
|
||||
import getTagChildSidebarItems from "@/utils/get-tag-child-sidebar-items"
|
||||
|
||||
const TagSection = dynamic<TagSectionProps>(
|
||||
async () => import("./Section")
|
||||
) as React.FC<TagSectionProps>
|
||||
|
||||
export type TagsProps = React.HTMLAttributes<HTMLDivElement>
|
||||
|
||||
function getCurrentTag() {
|
||||
return typeof location !== "undefined"
|
||||
? location.hash.replace("#", "").split("_")[0]
|
||||
: ""
|
||||
}
|
||||
|
||||
const Tags = () => {
|
||||
const [tags, setTags] = useState<OpenAPIV3.TagObject[]>([])
|
||||
const [loadData, setLoadData] = useState<boolean>(false)
|
||||
const [expand, setExpand] = useState<string>("")
|
||||
const { baseSpecs, setBaseSpecs } = useBaseSpecs()
|
||||
const { addItems } = useSidebar()
|
||||
const { area } = useArea()
|
||||
|
||||
const { data } = useSWR<ExpandedDocument>(
|
||||
loadData && !baseSpecs
|
||||
? getLinkWithBasePath(`/base-specs?area=${area}&expand=${expand}`)
|
||||
: null,
|
||||
fetcher,
|
||||
{
|
||||
errorRetryInterval: 2000,
|
||||
}
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
setExpand(getCurrentTag())
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
setLoadData(true)
|
||||
}, [expand])
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setBaseSpecs(data)
|
||||
}
|
||||
if (data?.tags) {
|
||||
setTags(data.tags)
|
||||
}
|
||||
}, [data, setBaseSpecs])
|
||||
|
||||
useEffect(() => {
|
||||
if (baseSpecs) {
|
||||
addItems(
|
||||
baseSpecs.tags?.map((tag) => {
|
||||
const tagPathName = getSectionId([tag.name.toLowerCase()])
|
||||
const childItems =
|
||||
baseSpecs.expandedTags &&
|
||||
Object.hasOwn(baseSpecs.expandedTags, tagPathName)
|
||||
? getTagChildSidebarItems(baseSpecs.expandedTags[tagPathName])
|
||||
: []
|
||||
return {
|
||||
path: tagPathName,
|
||||
title: tag.name,
|
||||
children: childItems,
|
||||
loaded: childItems.length > 0,
|
||||
}
|
||||
}) || [],
|
||||
{
|
||||
section: SidebarItemSections.BOTTOM,
|
||||
}
|
||||
)
|
||||
}
|
||||
}, [baseSpecs, addItems])
|
||||
|
||||
return (
|
||||
<>
|
||||
{tags.map((tag, index) => (
|
||||
<TagSection tag={tag} key={index} />
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Tags
|
||||
Reference in New Issue
Block a user