feat: Update to API references look and feel (#343)

Co-authored-by: Vadim Smirnov <smirnou.vadzim@gmail.com>
Co-authored-by: zakariasaad <zakaria.elas@gmail.com>
Co-authored-by: Vilfred Sikker <vilfredsikker@gmail.com>
Co-authored-by: olivermrbl <oliver@mrbltech.com>
This commit is contained in:
Kasper Fabricius Kristensen
2021-08-20 10:26:29 +02:00
committed by GitHub
parent 40400b483c
commit 143f06aa39
85 changed files with 19022 additions and 7116 deletions
@@ -0,0 +1,54 @@
import React from "react"
import { Flex, Box, Text } from "theme-ui"
const CodeBox = ({ header, children }) => {
return (
<Box
sx={{
background: "fadedContrast",
borderRadius: "small",
boxShadow: "0 0 0 1px rgb(0 0 0 / 7%)",
alignSelf: "flex-start",
marginLeft: "auto",
marginRight: "auto",
width: "100%",
mb: "4",
}}
>
<Box
sx={{
bg: "faded",
p: "8px 10px",
letterSpacing: "0.01em",
borderRadius: "8px 8px 0 0",
}}
>
<Text variant="small" sx={{ fontWeight: "400" }}>
{header}
</Text>
</Box>
<Box
sx={{
position: "relative",
boxSizing: "content-box",
maxHeight: "calc(90vh - 20px)",
minHeight: "10px",
}}
>
<Flex
sx={{
flexDirection: "column",
position: "relative",
minHeight: "inherit",
maxHeight: "inherit",
overflowY: "auto",
}}
>
{children}
</Flex>
</Box>
</Box>
)
}
export default CodeBox
@@ -0,0 +1,130 @@
import React, { useState } from "react"
import Collapsible from "react-collapsible"
import { Flex, Box, Text, Heading } from "theme-ui"
import Markdown from "react-markdown"
import Description from "./description"
const NestedCollapsible = ({ properties, title }) => {
const [isOpen, setIsOpen] = useState(false)
return (
<Box
mt={2}
sx={{
"& .child-attrs": {
cursor: "pointer",
fontSize: "12px",
p: "6px 10px",
boxSizing: "border-box",
width: "max-content",
borderRadius: "small",
border: "1px solid",
borderColor: "faded",
},
"& .child-attrs.is-open": {
width: "100%",
borderBottom: "none",
borderBottomLeftRadius: "0",
borderBottomRightRadius: "0",
},
}}
>
<Collapsible
transitionTime={50}
triggerClassName={"child-attrs"}
triggerOpenedClassName={"child-attrs"}
triggerTagName="div"
trigger={
<Flex
sx={{
alignItems: "baseline",
fontSize: "13px",
fontWeight: "400",
}}
>
<Box
mr={1}
className={isOpen ? "rotated" : null}
sx={{
userSelect: "none",
transition: "all 0.2s ease",
transform: "rotate(45deg)",
"&.rotated": {
transform: "rotate(0deg)",
},
}}
>
&times;
</Box>{" "}
<Text
sx={{ fontSize: "0", fontFamily: "body", userSelect: "none" }}
>{`${isOpen ? "Hide" : "Show"} child attributes`}</Text>
</Flex>
}
onTriggerOpening={() => setIsOpen(true)}
onTriggerClosing={() => setIsOpen(false)}
>
<Box
sx={{
padding: "2",
borderRadius: "small",
borderTopLeftRadius: 0,
borderTopRightRadius: 0,
border: "hairline",
borderColor: "faded",
}}
mb="2"
>
<Heading as="h3" p={2} sx={{ fontFamily: "body", fontWeight: "500" }}>
{title}
</Heading>
{properties.map((param, i) => {
return (
<Box
p={2}
sx={{
borderTop: "hairline",
}}
key={i}
>
<Flex
sx={{
fontSize: "0",
alignItems: "baseline",
pb: "1",
fontFamily: "monospace",
}}
>
<Box mr={2} fontSize={"12px"}>
{param.property}
</Box>
<Text color={"gray"} fontSize={"10px"}>
{param.type}
</Text>
{param.required && (
<Text ml={1} fontSize={"10px"} variant="labels.required">
required
</Text>
)}
</Flex>
<Description>
<Text
sx={{
fontSize: "0",
lineHeight: "26px",
fontFamily: "body",
}}
>
<Markdown>{param.description}</Markdown>
</Text>
</Description>
</Box>
)
})}
</Box>
</Collapsible>
</Box>
)
}
export default NestedCollapsible
@@ -0,0 +1,20 @@
import React from "react"
import { Box } from "theme-ui"
const Description = ({ children }) => {
return (
<Box
sx={{
code: {
backgroundColor: "faded",
borderRadius: "4px",
p: "3px",
},
}}
>
{children}
</Box>
)
}
export default Description
@@ -0,0 +1,45 @@
import React from "react"
import { Flex, Text } from "theme-ui"
import CodeBox from "./code-box"
const EndpointContainer = ({ endpoints }) => {
if (!endpoints) return null
return (
<CodeBox header="ENDPOINTS">
<Flex
py={2}
sx={{
flexDirection: "column",
}}
>
{endpoints.map((e, i) => {
const method = e.method.toUpperCase()
const endpoint = e.endpoint
return (
<Flex
key={i}
sx={{ fontSize: "0", fontFamily: "monospace", px: "3", py: "2" }}
>
<Text
variant={`labels.${method}`}
sx={{
width: "55px",
textAlign: "right",
}}
mr={2}
>
{method}
</Text>
<Text sx={{ color: "dark" }}>
{endpoint.replace(/{(.*?)}/g, ":$1")}
</Text>
</Flex>
)
})}
</Flex>
</CodeBox>
)
}
export default EndpointContainer
@@ -0,0 +1,31 @@
import React from "react"
import { Box, Flex } from "theme-ui"
import Topbar from "../topbar"
import Section from "./section"
const Content = ({ data, api }) => {
return (
<Flex
sx={{
flexDirection: "column",
}}
>
<Topbar data={data} api={api} />
<Box
sx={{
maxHeight: "calc(100vh - 50px)",
overflowY: "scroll",
"@media screen and (max-width: 848px)": {
mt: "50px",
},
}}
>
{data.sections.map((s, i) => {
return <Section key={i} data={s} />
})}
</Box>
</Flex>
)
}
export default Content
@@ -0,0 +1,31 @@
import React, { useRef, useEffect } from "react"
import { Box } from "theme-ui"
import "../../prism-medusa-theme/prism.css"
import Prism from "prismjs"
import "prismjs/components/prism-json"
import CodeBox from "./code-box"
const JsonContainer = ({ json, header }) => {
const jsonRef = useRef()
//INVESTIGATE: @theme-ui/prism might be a better solution
useEffect(() => {
if (jsonRef.current) {
Prism.highlightAllUnder(jsonRef.current)
}
}, [])
if (typeof json !== "string" || json === "{}") return null
return (
<Box ref={jsonRef} sx={{ position: "sticky", top: "20px" }}>
<CodeBox header={header}>
<pre>
<code className={"language-json"}>{json}</code>
</pre>
</CodeBox>
</Box>
)
}
export default JsonContainer
@@ -0,0 +1,110 @@
import React, { useContext, useEffect, useRef } from "react"
import Markdown from "react-markdown"
import { Flex, Text, Box, Heading } from "theme-ui"
import { convertToKebabCase } from "../../utils/convert-to-kebab-case"
import Parameters from "./parameters"
import Route from "./route"
import JsonContainer from "./json-container"
import Description from "./description"
import ResponsiveContainer from "./responsive-container"
import { formatMethodParams } from "../../utils/format-parameters"
import useInView from "../../hooks/use-in-view"
import NavigationContext from "../../context/navigation-context"
const Method = ({ data, section, pathname }) => {
const { parameters, requestBody, description, method, summary } = data
const jsonResponse = data.responses[0].content?.[0].json
const { updateHash, updateMetadata } = useContext(NavigationContext)
const methodRef = useRef(null)
const [containerRef, isInView] = useInView({
root: null,
rootMargin: "0px 0px -80% 0px",
threshold: 0,
})
useEffect(() => {
if (isInView) {
updateHash(section, convertToKebabCase(summary))
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isInView])
const handleMetaChange = () => {
updateMetadata({
title: summary,
description: description,
})
if (methodRef.current) {
methodRef.current.scrollIntoView({
behavior: "smooth",
})
}
}
return (
<Flex
py={"5vw"}
sx={{
borderTop: "hairline",
flexDirection: "column",
}}
id={convertToKebabCase(summary)}
ref={methodRef}
>
<Flex>
<Heading
as="h2"
mb={4}
sx={{
fontSize: "4",
fontWeight: "500",
cursor: "pointer",
}}
ref={containerRef}
onClick={() => handleMetaChange()}
>
{summary}
</Heading>
</Flex>
<ResponsiveContainer>
<Flex
className="info"
sx={{
flexDirection: "column",
pr: "5",
"@media screen and (max-width: 848px)": {
pr: "0",
},
}}
>
<Route path={pathname} method={method} />
<Description>
<Text
sx={{
lineHeight: "26px",
}}
mt={3}
>
<Markdown>{description}</Markdown>
</Text>
</Description>
<Box mt={4}>
<Parameters
params={formatMethodParams({ parameters, requestBody })}
/>
</Box>
</Flex>
<Box className="code">
<JsonContainer
json={jsonResponse}
header={"RESPONSE"}
method={convertToKebabCase(summary)}
/>
</Box>
</ResponsiveContainer>
</Flex>
)
}
export default Method
@@ -0,0 +1,68 @@
import React from "react"
import { Flex, Text, Box } from "theme-ui"
import Markdown from "react-markdown"
import NestedCollapsible from "./collapsible"
import Description from "./description"
const Parameters = ({ params, type }) => {
return (
<Flex
sx={{
flexDirection: "column",
}}
>
<Text pb="2">{type === "attr" ? "Attributes" : "Parameters"}</Text>
{params.properties.length > 0 ? (
params.properties.map((prop, i) => {
const nested = prop.nestedModel || prop.items?.properties || null
return (
<Box
py={2}
sx={{
borderTop: "hairline",
fontFamily: "monospace",
fontSize: "0",
}}
key={i}
>
<Flex sx={{ alignItems: "baseline", fontSize: "0" }}>
<Text mr={2}>{prop.property || prop.name}</Text>
<Text color={"gray"}>
{prop.type || prop.schema?.type || nested?.title}
</Text>
{prop.required ? (
<Text ml={1} variant="labels.required">
required
</Text>
) : null}
</Flex>
<Description>
<Text
sx={{
fontSize: "0",
lineHeight: "26px",
fontFamily: "body",
}}
>
<Markdown>{prop.description}</Markdown>
</Text>
</Description>
{nested?.properties && (
<NestedCollapsible
properties={nested.properties}
title={nested.title}
/>
)}
</Box>
)
})
) : (
<Text sx={{ fontSize: "0", py: "3", fontFamily: "monospace" }}>
No parameters
</Text>
)}
</Flex>
)
}
export default Parameters
@@ -0,0 +1,29 @@
import React from "react"
import styled from "@emotion/styled"
import { Flex } from "theme-ui"
const ResponsiveFlex = styled(Flex)`
.info {
width: 55%;
}
.code {
width: 45%;
}
@media screen and (max-width: 848px) {
flex-direction: column;
align-items: center;
.info,
.code {
width: 100%;
}
}
`
const ResponsiveContainer = ({ children }) => {
return <ResponsiveFlex>{children}</ResponsiveFlex>
}
export default ResponsiveContainer
@@ -0,0 +1,17 @@
import React from "react"
import { Flex, Text } from "theme-ui"
import { formatRoute } from "../../utils/format-route"
const Route = ({ method, path }) => {
const fixedMethod = method.toUpperCase()
return (
<Flex sx={{ fontFamily: "monospace" }}>
<Text variant={`labels.${fixedMethod}`} mr={1}>
{fixedMethod}
</Text>
<Text>{formatRoute(path)}</Text>
</Flex>
)
}
export default Route
@@ -0,0 +1,205 @@
import React, { useState, useRef, useEffect, useContext } from "react"
import { Flex, Box, Heading, Text, Button } from "theme-ui"
import Method from "./method"
import Parameters from "./parameters"
import { convertToKebabCase } from "../../utils/convert-to-kebab-case"
import EndpointContainer from "./endpoint-container"
import Markdown from "react-markdown"
import JsonContainer from "./json-container"
import ResponsiveContainer from "./responsive-container"
import Description from "./description"
import NavigationContext from "../../context/navigation-context"
import ChevronDown from "../icons/chevron-down"
import useInView from "../../hooks/use-in-view"
const Section = ({ data }) => {
const { section } = data
const [isExpanded, setIsExpanded] = useState(false)
const { openSections, updateSection, updateMetadata } = useContext(
NavigationContext
)
const endpoints = section.paths
.map(p => {
let path = p.name
let ep = []
p.methods.forEach(m => {
ep.push({ method: m.method, endpoint: path })
})
return ep
})
.flat()
const sectionRef = useRef(null)
const scrollIntoView = () => {
if (sectionRef.current) {
sectionRef.current.scrollIntoView({
behavior: "smooth",
})
}
}
const handleExpand = () => {
updateMetadata({
title: section.section_name,
description: section.schema?.description,
})
setIsExpanded(true)
scrollIntoView()
}
useEffect(() => {
if (isExpanded) {
scrollIntoView()
}
}, [isExpanded])
useEffect(() => {
const shouldOpen = openSections.includes(
convertToKebabCase(section.section_name)
)
if (shouldOpen) {
setIsExpanded(true)
}
}, [section.section_name, openSections, openSections.length])
const [containerRef, isInView] = useInView({
root: null,
rootMargin: "0px 0px -80% 0px",
threshold: 1.0,
})
useEffect(() => {
const handleInView = () => {
if (isInView) {
updateSection(convertToKebabCase(section.section_name))
}
}
handleInView()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isInView])
return (
<section ref={sectionRef} id={convertToKebabCase(section.section_name)}>
<Box
sx={{
borderBottom: "hairline",
padding: "5vw",
backgroundColor: isExpanded ? "transparent" : "fadedContrast",
}}
>
<Flex>
<Heading
as="h1"
sx={{
fontWeight: "500",
fontSize: "22",
mb: "3",
cursor: "pointer",
}}
ref={containerRef}
className={`header-${convertToKebabCase(section.section_name)}`}
onClick={handleExpand}
>
{section.section_name}
</Heading>
</Flex>
<Flex
sx={{
flexDirection: "column",
}}
>
<ResponsiveContainer>
<Flex
sx={{
flexDirection: "column",
lineHeight: "26px",
pr: "5",
"@media screen and (max-width: 848px)": {
pr: "0",
},
}}
className="info"
>
<Description>
<Text mb={4}>
<Markdown>{section.schema?.description}</Markdown>
</Text>
</Description>
{isExpanded && section.schema ? (
<Parameters params={section.schema} type={"attr"} />
) : null}
</Flex>
<Flex
className="code"
sx={{
flexDirection: "column",
}}
>
<EndpointContainer endpoints={endpoints} />
{isExpanded ? (
<JsonContainer
json={section.schema?.object}
header={`${section.section_name.toUpperCase()} OBJECT`}
/>
) : null}
</Flex>
</ResponsiveContainer>
{isExpanded ? (
<Box mt={4}>
{section.paths.map((p, i) => {
return (
<Flex
key={i}
sx={{
flexDirection: "column",
}}
>
{p.methods.map((m, i) => {
return (
<Method
key={i}
data={m}
section={convertToKebabCase(section.section_name)}
pathname={p.name}
/>
)
})}
</Flex>
)
})}
</Box>
) : (
<Flex
sx={{
justifyContent: "center",
alignItems: "center",
width: "100%",
}}
mt={4}
>
<Button
onClick={handleExpand}
sx={{
display: "flex",
alignItems: "center",
borderRadius: "24px",
bg: "light",
fontWeight: "500",
}}
>
SHOW <ChevronDown fill={"dark"} styles={{ mr: "-10px" }} />
</Button>
</Flex>
)}
</Flex>
</Box>
</section>
)
}
export default Section