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:
committed by
GitHub
parent
40400b483c
commit
143f06aa39
@@ -0,0 +1,109 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { Flex, Image, Box } from "theme-ui"
|
||||
import styled from "@emotion/styled"
|
||||
import Logo from "../../assets/logo.svg"
|
||||
import LogoMuted from "../../assets/logo-muted.svg"
|
||||
import SideBarItem from "./sidebar-item"
|
||||
import SideBarSelector from "./sidebar-selector"
|
||||
|
||||
const SideBarContainer = styled(Flex)`
|
||||
--side-bar-width: 220px;
|
||||
|
||||
@media screen and (min-width: 1680px) {
|
||||
--side-bar-width: 280px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 848px) {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
|
||||
const SideBarFade = styled(Box)`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: calc(var(--side-bar-width) - 1px);
|
||||
height: 50px;
|
||||
pointer-events: none;
|
||||
box-shadow: inset 0 50px 25px calc(-1 * 25px) white;
|
||||
`
|
||||
|
||||
const Sidebar = ({ data, api }) => {
|
||||
const [scrollPos, setScrollPos] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
const nav = document.querySelector("#nav")
|
||||
|
||||
const handleScroll = e => {
|
||||
const pos = e.srcElement.scrollTop / 50
|
||||
if (pos < 1) {
|
||||
setScrollPos(pos)
|
||||
}
|
||||
}
|
||||
|
||||
nav.addEventListener("scroll", handleScroll)
|
||||
return () => nav.removeEventListener("scroll", handleScroll)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<SideBarContainer
|
||||
sx={{
|
||||
position: "sticky",
|
||||
top: "0",
|
||||
bottom: "0",
|
||||
height: "100vh",
|
||||
backgroundColor: "light",
|
||||
boxShadow: "sidebarShadow",
|
||||
minWidth: "var(--side-bar-width)",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
sx={{
|
||||
px: "4",
|
||||
pt: "3",
|
||||
background: "light",
|
||||
width: "calc(var(--side-bar-width) - 1px)",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<Flex>
|
||||
<Image
|
||||
src={Logo}
|
||||
alt="Medusa logo"
|
||||
sx={{
|
||||
height: "28px",
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex py={4}>
|
||||
<SideBarSelector api={api} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex
|
||||
id="nav"
|
||||
sx={{
|
||||
flex: 1,
|
||||
position: "relative",
|
||||
px: "3",
|
||||
pb: "3",
|
||||
mr: "1px",
|
||||
flexDirection: "column",
|
||||
overflowY: "scroll",
|
||||
pr: "6px",
|
||||
scrollbarColor: "faded light",
|
||||
}}
|
||||
>
|
||||
<SideBarFade opacity={scrollPos} />
|
||||
{data.sections.map((s, i) => {
|
||||
return <SideBarItem item={s} key={i} />
|
||||
})}
|
||||
</Flex>
|
||||
<Flex sx={{ py: 4, px: 4, borderTop: "1px solid #efefef" }}>
|
||||
<Image src={LogoMuted} alt="Medusa Type" sx={{ height: "10px" }} />
|
||||
</Flex>
|
||||
</SideBarContainer>
|
||||
)
|
||||
}
|
||||
|
||||
export default Sidebar
|
||||
@@ -0,0 +1,138 @@
|
||||
import React, { useContext } from "react"
|
||||
import Collapsible from "react-collapsible"
|
||||
import { Flex, Box, Text } from "theme-ui"
|
||||
import styled from "@emotion/styled"
|
||||
import { convertToKebabCase } from "../../utils/convert-to-kebab-case"
|
||||
import ChevronDown from "../icons/chevron-down"
|
||||
import NavigationContext from "../../context/navigation-context"
|
||||
|
||||
const StyledCollapsible = styled(Collapsible)`
|
||||
margin-bottom: 10px;
|
||||
`
|
||||
|
||||
const Container = styled(Box)`
|
||||
div.Collapsible span.Collapsible__trigger.is-open {
|
||||
svg {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const SideBarItem = ({ item }) => {
|
||||
const {
|
||||
openSection,
|
||||
openSections,
|
||||
currentHash,
|
||||
currentSection,
|
||||
goTo,
|
||||
} = useContext(NavigationContext)
|
||||
const { section } = item
|
||||
const subItems = section.paths
|
||||
.map(p => {
|
||||
return p.methods
|
||||
})
|
||||
.reduce((pre, cur) => {
|
||||
return pre.concat(cur)
|
||||
})
|
||||
.map(m => {
|
||||
return {
|
||||
title: m.summary,
|
||||
path: convertToKebabCase(m.summary),
|
||||
}
|
||||
})
|
||||
|
||||
const handleClick = () => {
|
||||
const id = convertToKebabCase(section.section_name)
|
||||
const element = document.querySelector(`#${id}`)
|
||||
if (element) {
|
||||
element.scrollIntoView()
|
||||
if (!openSections.includes(id)) {
|
||||
openSection(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubClick = path => {
|
||||
const id = convertToKebabCase(section.section_name)
|
||||
goTo({ section: id, method: path })
|
||||
}
|
||||
|
||||
return (
|
||||
<Container id={`nav-${convertToKebabCase(section.section_name)}`}>
|
||||
<StyledCollapsible
|
||||
trigger={
|
||||
<Flex
|
||||
sx={{
|
||||
fontSize: "1",
|
||||
pl: "16px",
|
||||
pr: "10px",
|
||||
alignItems: "center",
|
||||
borderRadius: "small",
|
||||
cursor: "pointer",
|
||||
mr: "4px",
|
||||
mb: "5px",
|
||||
height: "25px",
|
||||
justifyContent: "space-between",
|
||||
"&:hover, &.active": {
|
||||
backgroundColor: "faded",
|
||||
},
|
||||
}}
|
||||
className={
|
||||
currentSection === convertToKebabCase(section.section_name)
|
||||
? "active"
|
||||
: null
|
||||
}
|
||||
>
|
||||
{section.section_name} <ChevronDown />
|
||||
</Flex>
|
||||
}
|
||||
open={
|
||||
currentSection === convertToKebabCase(section.section_name) ||
|
||||
openSections.includes(convertToKebabCase(section.section_name))
|
||||
}
|
||||
onTriggerOpening={handleClick}
|
||||
transitionTime={1}
|
||||
>
|
||||
{subItems.map((si, i) => {
|
||||
const path = convertToKebabCase(si.path)
|
||||
return (
|
||||
<Flex
|
||||
key={i}
|
||||
className={currentHash === path ? "active" : null}
|
||||
onClick={() => handleSubClick(path)}
|
||||
id={`nav-${path}`}
|
||||
sx={{
|
||||
ml: "10px",
|
||||
pl: "10px",
|
||||
pr: "10px",
|
||||
alignItems: "center",
|
||||
borderRadius: "small",
|
||||
cursor: "pointer",
|
||||
mb: "8px",
|
||||
textDecoration: "none",
|
||||
color: "black",
|
||||
height: "25px",
|
||||
"&:hover": {
|
||||
backgroundColor: "faded",
|
||||
},
|
||||
"&.active": {
|
||||
backgroundColor: "faded",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
sx={{
|
||||
fontSize: "0",
|
||||
}}
|
||||
>
|
||||
{si.title}
|
||||
</Text>
|
||||
</Flex>
|
||||
)
|
||||
})}
|
||||
</StyledCollapsible>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default SideBarItem
|
||||
@@ -0,0 +1,56 @@
|
||||
import React, { useContext } from "react"
|
||||
import { Flex, Select } from "theme-ui"
|
||||
import { navigate } from "gatsby-link"
|
||||
import NavigationContext from "../../context/navigation-context"
|
||||
import ChevronDown from "../icons/chevron-down"
|
||||
|
||||
const SideBarSelector = ({ api }) => {
|
||||
const { reset } = useContext(NavigationContext)
|
||||
|
||||
const handleSelect = e => {
|
||||
reset()
|
||||
navigate(`/api/${e.target.value}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
marginLeft: "-16px",
|
||||
marginRight: "-10px",
|
||||
width: "calc(100% + 26px)",
|
||||
"& div": {
|
||||
width: "100%",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Select
|
||||
arrow={<ChevronDown fill={"dark"} styles={{ ml: "-28px" }} />}
|
||||
sx={{
|
||||
paddingLeft: "16px",
|
||||
marginRight: "-5px",
|
||||
borderRadius: "small",
|
||||
borderColor: "faded",
|
||||
width: "100%",
|
||||
fontSize: "1",
|
||||
fontFamily: "body",
|
||||
transition: "all .1s ease-in-out",
|
||||
"&:focus": {
|
||||
outline: "none !important",
|
||||
},
|
||||
boxShadow: "ctaBoxShadow",
|
||||
|
||||
"&:hover": {
|
||||
boxShadow: "buttonBoxShadowHover",
|
||||
},
|
||||
}}
|
||||
value={api}
|
||||
onChange={handleSelect}
|
||||
>
|
||||
<option value="admin">Admin</option>
|
||||
<option value="store">Storefront</option>
|
||||
</Select>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
export default SideBarSelector
|
||||
@@ -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)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
×
|
||||
</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
|
||||
@@ -0,0 +1,24 @@
|
||||
import React from "react"
|
||||
import { Box } from "theme-ui"
|
||||
|
||||
const ChevronDown = ({ fill = "darkContrast", styles }) => {
|
||||
return (
|
||||
<Box
|
||||
as="svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
sx={{
|
||||
...styles,
|
||||
alignSelf: "center",
|
||||
pointerEvents: "none",
|
||||
fill: `${fill}`,
|
||||
}}
|
||||
>
|
||||
<path d="M7.41 7.84l4.59 4.58 4.59-4.58 1.41 1.41-6 6-6-6z" />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default ChevronDown
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Image } from "@theme-ui/components"
|
||||
import React from "react"
|
||||
|
||||
import Logo from "../../assets/github.svg"
|
||||
|
||||
const GitHub = () => {
|
||||
return (
|
||||
<Image
|
||||
src={Logo}
|
||||
sx={{
|
||||
height: "20px",
|
||||
fill: "#000",
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default GitHub
|
||||
@@ -0,0 +1,26 @@
|
||||
import React from "react"
|
||||
import { Flex, Box } from "theme-ui"
|
||||
import Sidebar from "./sidebar"
|
||||
|
||||
const Layout = ({ data, api, children }) => {
|
||||
return (
|
||||
<Flex sx={{ p: "0", m: "0", overflow: "hidden" }}>
|
||||
<Flex
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
fontFamily: "body",
|
||||
flexGrow: "1",
|
||||
}}
|
||||
>
|
||||
<Sidebar data={data} api={api} />
|
||||
<Box>{children}</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
export default Layout
|
||||
@@ -0,0 +1,115 @@
|
||||
import { Flex, Box, Link, Select } from "@theme-ui/components"
|
||||
import React, { useContext } from "react"
|
||||
import { navigate } from "gatsby-link"
|
||||
|
||||
import GitHub from "../components/icons/github"
|
||||
import NavigationContext from "../context/navigation-context"
|
||||
import { convertToKebabCase } from "../utils/convert-to-kebab-case"
|
||||
import ChevronDown from "./icons/chevron-down"
|
||||
|
||||
const Topbar = ({ data, api }) => {
|
||||
const { goTo, reset, currentSection } = useContext(NavigationContext)
|
||||
|
||||
const handleChange = e => {
|
||||
const parts = e.target.value.split(" ")
|
||||
|
||||
if (parts[0] === api) {
|
||||
goTo({ section: parts[1] })
|
||||
} else {
|
||||
reset()
|
||||
navigate(`/api/${api === "admin" ? "store" : "admin"}`)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
px: "5vw",
|
||||
height: "headerHeight",
|
||||
boxShadow: "topbarShadow",
|
||||
"@media screen and (max-width: 848px)": {
|
||||
position: "fixed",
|
||||
top: "0",
|
||||
left: "0",
|
||||
right: "0",
|
||||
zIndex: "9999",
|
||||
backgroundColor: "light",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "none",
|
||||
alignItems: "center",
|
||||
"@media screen and (max-width: 848px)": {
|
||||
display: "block",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Select
|
||||
arrow={<ChevronDown fill={"dark"} />}
|
||||
sx={{
|
||||
border: "none",
|
||||
width: "100%",
|
||||
fontSize: "2",
|
||||
fontFamily: "body",
|
||||
fontWeight: "500",
|
||||
flexGrow: "1",
|
||||
px: "0",
|
||||
transition: "all .1s ease-in-out",
|
||||
"&:focus": {
|
||||
outline: "none !important",
|
||||
},
|
||||
}}
|
||||
onChange={handleChange}
|
||||
value={`${api} ${currentSection}`}
|
||||
>
|
||||
<optgroup label={`${api.slice(0, 1).toUpperCase()}${api.slice(1)}`}>
|
||||
{data.sections.map((s, i) => {
|
||||
return (
|
||||
<option
|
||||
key={i}
|
||||
value={`${api} ${convertToKebabCase(
|
||||
s.section.section_name
|
||||
)}`}
|
||||
>
|
||||
{s.section.section_name}
|
||||
</option>
|
||||
)
|
||||
})}
|
||||
</optgroup>
|
||||
<optgroup label={api === "admin" ? "Store" : "Admin"}>
|
||||
<option>{`Go to ${
|
||||
api === "admin" ? "Storefront API" : "Admin API"
|
||||
}`}</option>
|
||||
</optgroup>
|
||||
</Select>
|
||||
</Box>
|
||||
</Box>
|
||||
<Flex
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Link variant="topbar" mr={3} href="https://docs.medusa-commerce.com">
|
||||
Docs
|
||||
</Link>
|
||||
<Link
|
||||
sx={{
|
||||
pt: "4px",
|
||||
}}
|
||||
variant="topbar"
|
||||
href="https://github.com/medusajs/medusa"
|
||||
>
|
||||
<GitHub />
|
||||
</Link>
|
||||
</Flex>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
export default Topbar
|
||||
Reference in New Issue
Block a user