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
co-authored by Vadim Smirnov zakariasaad Vilfred Sikker olivermrbl
parent 40400b483c
commit 143f06aa39
85 changed files with 19022 additions and 7116 deletions
+13
View File
@@ -0,0 +1,13 @@
{
"presets": [
[
"babel-preset-gatsby",
{
"reactRuntime": "automatic",
"targets": {
"browsers": [">0.25%", "not dead"]
}
}
]
]
}
+69
View File
@@ -0,0 +1,69 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# dotenv environment variable files
.env*
# gatsby files
.cache/
public
# Mac files
.DS_Store
# Yarn
yarn-error.log
.pnp/
.pnp.js
# Yarn Integrity file
.yarn-integrity
+4
View File
@@ -0,0 +1,4 @@
.cache
package.json
package-lock.json
public
+4
View File
@@ -0,0 +1,4 @@
{
"arrowParens": "avoid",
"semi": false
}
+14
View File
@@ -0,0 +1,14 @@
The BSD Zero Clause License (0BSD)
Copyright (c) 2020 Medusa Commerce
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
+3
View File
@@ -0,0 +1,3 @@
# Medusa Docs
Visit: docs.medusa-commerce.com to browse docs.
+16
View File
@@ -0,0 +1,16 @@
import React from "react"
import { NavigationProvider } from "./src/context/navigation-context"
export const onServiceWorkerUpdateReady = () => {
const answer = window.confirm(
"We have updated the docs site. Reload to display the latest version?"
)
if (answer) {
window.location.reload()
}
}
export const wrapPageElement = ({ element }) => {
return <NavigationProvider>{element}</NavigationProvider>
}
+86
View File
@@ -0,0 +1,86 @@
module.exports = {
siteMetadata: {
title: "Medusa",
description: "Open-source headless commerce engine",
author: "Medusa core team",
},
plugins: [
`gatsby-plugin-react-helmet`,
{
resolve: `gatsby-plugin-anchor-links`,
options: {
offset: -100,
duration: 1000,
},
},
`gatsby-transformer-json`,
`gatsby-plugin-emotion`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: "store-api",
path: `${__dirname}/../../docs/api/store-spec3.json`,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: "admin-api",
path: `${__dirname}/../../docs/api/admin-spec3.json`,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `docs`,
path: `${__dirname}/../../docs/content/`,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
path: `${__dirname}/../../docs/pages`,
name: `pages`,
},
},
{
resolve: `gatsby-transformer-remark`,
options: {
plugins: [
{
resolve: `gatsby-remark-autolink-headers`,
options: {
elements: [`h2`, `h3`, `h4`],
},
},
],
},
},
`gatsby-plugin-theme-ui`,
{
resolve: `gatsby-plugin-algolia-docsearch`,
options: {
apiKey: process.env.ALGOLIA_DOCSEARCH_API_KEY, // required
indexName: process.env.ALGOLIA_DOCSEARCH_INDEX_NAME, // required
inputSelector: "#algolia-doc-search", // required
debug: false, // (bool) Optional. Default `false`
},
},
// `gatsby-plugin-preact`,
// {
// resolve: `gatsby-source-openapi-aggregate`,
// options: {
// specs: [
// {
// name: "admin-spec",
// resolve: () =>
// fromJson(
// path.resolve(__dirname, "../../docs/api/admin-spec3.json")
// ),
// },
// ],
// },
// },
],
}
+551
View File
@@ -0,0 +1,551 @@
const path = require(`path`)
const { createFilePath } = require(`gatsby-source-filesystem`)
const fixtures = require("../../docs/api/fixtures.json")
const convertToKebabCase = string => {
return string
.replace(/\s+/g, "-")
.replace("'", "")
.replace(".", "")
.replace('"', "")
.toLowerCase()
}
const createCustomNode = ({ name, node, createNode }) => {
const tags = node.tags
const nodePaths = Object.entries(node.paths).map(([path, values]) => {
return {
name: path,
methods: Object.entries(values).map(([method, values]) => {
const requestBodyValues =
values?.requestBody?.content?.["application/json"]?.schema
return {
method: method,
...values,
requestBody: {
required: requestBodyValues?.required,
type: requestBodyValues?.type,
properties: Object.entries(requestBodyValues?.properties || {}).map(
([property, values]) => {
let ref = null
let component_id = null
let nestedModelNode = null
const { items, anyOf, oneOf, ...rest } = values
if (items || anyOf || oneOf) {
if (items?.anyOf) {
component_id = items.anyOf[0]["$ref"].substring(
items.anyOf[0]["$ref"].lastIndexOf("/") + 1
)
ref = node.components.schemas[component_id]
} else if (items) {
const refPath = items["$ref"]
if (refPath) {
component_id = refPath.substring(
items["$ref"].lastIndexOf("/") + 1
)
ref = node.components.schemas[component_id]
}
} else if (anyOf) {
component_id = anyOf[0]["$ref"].substring(
anyOf[0]["$ref"].lastIndexOf("/") + 1
)
ref = node.components.schemas[component_id]
} else if (oneOf) {
component_id = oneOf[0]["$ref"].substring(
oneOf[0]["$ref"].lastIndexOf("/") + 1
)
ref = node.components.schemas[component_id]
}
}
if (ref) {
const { properties, ...rest } = ref
let nestedModelProperties = []
if (properties) {
Object.entries(properties).map(([key, values]) => {
nestedModelProperties.push({
property: key,
...values,
})
})
nestedModelNode = {
properties: nestedModelProperties,
...rest,
}
}
}
return {
property: property,
nestedModel: nestedModelNode,
...rest,
}
}
),
},
responses: Object.entries(values.responses).map(
([response, values]) => {
const properties =
values.content?.["application/json"]?.schema?.properties
return {
status: response,
content: properties
? Object.entries(properties).map(([property, values]) => {
const toSet = {}
let concept
if (values.items && values.items["$ref"]) {
const [, ...path] = values.items["$ref"].split("/")
concept = path[path.length - 1]
if (values.type === "array") {
if (concept in fixtures.resources) {
//make the array key plural
let prop
if (property.slice(-1) !== "s") {
prop = property + "s"
} else {
prop = property
}
toSet[prop] = [fixtures.resources[concept]]
}
} else if (concept in fixtures.resources) {
toSet[property] = fixtures.resources[concept]
}
} else {
if (fixtures.resources[property]) {
toSet[property] = fixtures.resources[property]
} else if (values["$ref"]) {
const [, ...path] = values["$ref"].split("/")
toSet[property] =
fixtures.resources[path[path.length - 1]]
}
}
const json =
Object.keys(toSet).length > 0
? JSON.stringify(toSet, undefined, 2)
: null
return {
property: property,
json: json,
...values,
}
})
: null,
description: values.description,
}
}
),
}
}),
}
})
const nodeSections = nodePaths.reduce((acc, current) => {
// Not bulleproof and kind of naive
const section = current.methods.find(method => method.tags).tags[0]
if (!section) {
return acc
}
const existingSection = acc.find(s => s.section.section_name === section)
//Because section_name does not always equal the resourceId
const tag = tags.find(
tag => tag.name.toLowerCase() === section.toLowerCase()
)
let resourceId
if (tag) resourceId = tag["x-resourceId"]
const schema =
node.components.schemas[section.toLowerCase()] ||
node.components.schemas[resourceId] ||
null
//Create a schemaNode so we can access descriptions and attributes of objects
let schemaNode
if (schema) {
let props = []
Object.entries(schema.properties).map(([key, values]) => {
let ref = null
let component_id = null
let nestedModelNode = null
let { items, anyOf, oneOf, ...rest } = values
if (items || anyOf || oneOf) {
if (items?.anyOf) {
component_id = items.anyOf[0]["$ref"].substring(
items.anyOf[0]["$ref"].lastIndexOf("/") + 1
)
ref = node.components.schemas[component_id]
} else if (items) {
const refPath = items["$ref"]
if (refPath) {
component_id = refPath.substring(
items["$ref"].lastIndexOf("/") + 1
)
ref = node.components.schemas[component_id]
}
} else if (anyOf) {
component_id = anyOf[0]["$ref"].substring(
anyOf[0]["$ref"].lastIndexOf("/") + 1
)
ref = node.components.schemas[component_id]
}
if (ref) {
const { properties, ...rest } = ref
let nestedModelProperties = []
if (properties) {
Object.entries(properties).map(([key, values]) => {
nestedModelProperties.push({
property: key,
...values,
})
})
nestedModelNode = {
properties: nestedModelProperties,
...rest,
}
}
}
}
props.push({
property: key,
nestedModel: nestedModelNode,
...rest,
})
})
let object = fixtures.resources[resourceId] || null
if (object) object = JSON.stringify(object, undefined, 2)
schemaNode = {
object: object,
description: schema.description,
properties: props,
}
}
/** We want the query to return the following:
*
* sections [
* section: {
* section_name: Orders
* paths: [<array-of-paths>]
* }
* ]
*
* The reason that we wrap it in a section is so the query returns Section type, the alternative is
* sections [
* Order: {...},
* Customer: {...}
* ...
* ]
*
* Which isn't structured as a reuseable type
*/
if (!existingSection) {
acc.push({
section: {
section_name: section,
paths: [{ ...current }],
schema: schemaNode,
},
})
} else {
existingSection.section.paths.push({ ...current })
}
return acc
}, [])
const result = {
name: name,
paths: nodePaths,
sections: nodeSections,
rawNode: node,
// required fields
id: name,
parent: null, // or null if it's a source node without a parent
children: [],
internal: {
type: name,
contentDigest: `store-api-${node.internal.contentDigest}`,
mediaType: node.internal.mediaType, // optional
content: node.internal.content, // optional
description: `A cleaned version of file-system-api json files`, // optional
},
}
createNode(result)
}
exports.onCreateNode = ({ node, getNode, actions }) => {
const { createNodeField, createNode } = actions
if (node.internal.type === `MarkdownRemark`) {
const slug = createFilePath({ node, getNode })
createNodeField({
node,
name: `slug`,
value: slug,
})
}
if (node.internal.type === "ApiJson" && node.components) {
if (node.info.title === "Medusa Storefront API") {
createCustomNode({ name: "Store", node: node, createNode: createNode })
}
if (node.info.title === "Medusa Admin API") {
createCustomNode({ name: "Admin", node: node, createNode: createNode })
}
}
}
const createAllPages = (sections, api, siteData, createPage, template) => {
sections.forEach(edge => {
const baseURL = convertToKebabCase(edge.section.section_name)
createPage({
path: `api/${api}/${baseURL}`,
component: template,
context: {
data: siteData.data[api],
api: api,
title: edge.section.section_name,
description: edge.section.schema?.description || "",
to: { section: baseURL, method: null },
},
})
edge.section.paths.forEach(p => {
p.methods.forEach(method => {
const methodURL = convertToKebabCase(method.summary)
createPage({
path: `api/${api}/${baseURL}/${methodURL}`,
component: template,
context: {
data: siteData.data[api],
api: api,
title: method.summary,
description: method.description || "",
to: { section: baseURL, method: methodURL },
},
})
})
})
})
}
exports.createPages = ({ graphql, actions }) => {
const { createPage } = actions
const template = path.resolve(`src/templates/reference-page.js`)
// Query for markdown nodes to use in creating pages.
// You can query for whatever data you want to create pages for e.g.
// products, portfolio items, landing pages, etc.
// Variables can be added as the second function parameter
return graphql(
`
query Pages {
admin {
sections {
section {
section_name
paths {
name
methods {
tags
summary
description
method
operationId
responses {
status
description
content {
_ref
type
property
description
json
items {
type
_ref
}
}
}
requestBody {
type
required
properties {
description
enum
format
property
type
nestedModel {
title
properties {
property
type
description
}
}
}
}
parameters {
description
in
name
required
schema {
type
}
}
}
}
schema {
object
description
properties {
property
type
description
format
nestedModel {
title
properties {
property
type
description
}
}
}
}
}
}
}
store {
sections {
section {
section_name
paths {
name
methods {
tags
summary
description
method
operationId
responses {
status
description
content {
_ref
property
description
json
items {
_ref
}
}
}
requestBody {
type
required
properties {
description
property
type
nestedModel {
title
properties {
property
type
}
}
}
}
parameters {
description
in
name
required
schema {
type
}
}
}
}
schema {
object
description
properties {
property
type
description
format
nestedModel {
title
properties {
property
type
}
}
}
}
}
}
}
}
`,
{ limit: 1000 }
).then(result => {
if (result.errors) {
throw result.errors
}
//create entrypoint
createPage({
path: `api`,
component: template,
context: {
data: result.data.store,
api: "store",
title: "Store",
description: "Storefront API",
},
})
const apis = [
{ title: "Store", description: "Storefront API", slug: "store" },
{ title: "Admin", description: "Admin API", slug: "admin" },
]
apis.forEach(api => {
//create main page for API
createPage({
path: `api/${api.slug}`,
component: template,
context: {
data: result.data[api.slug],
api: api.slug,
title: api.title,
description: api.description,
},
})
//create pages for all sections and methods
createAllPages(
result.data[api.slug].sections,
api.slug,
result,
createPage,
template
)
})
})
}
+6
View File
@@ -0,0 +1,6 @@
import React from "react"
import { NavigationProvider } from "./src/context/navigation-context"
export const wrapPageElement = ({ element }) => {
return <NavigationProvider>{element}</NavigationProvider>
}
+55
View File
@@ -0,0 +1,55 @@
{
"name": "medusa-commerce-docs",
"private": true,
"description": "Docs engine for Medusa docs",
"version": "0.1.0",
"license": "0BSD",
"scripts": {
"build": "gatsby build",
"develop": "gatsby develop",
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md}\"",
"start": "npm run develop",
"serve": "gatsby serve",
"clean": "gatsby clean",
"test": "echo \"Write tests! -> https://gatsby.dev/unit-testing\" && exit 1"
},
"dependencies": {
"@emotion/react": "^11.1.2",
"@emotion/styled": "^11.0.0",
"@rebass/forms": "^4.0.6",
"emotion-theming": "^10.0.27",
"gatsby": "^3.9.1",
"gatsby-plugin-algolia-docsearch": "^1.0.5",
"gatsby-plugin-anchor-links": "^1.1.1",
"gatsby-plugin-emotion": "^5.0.0",
"gatsby-plugin-preact": "^5.9.0",
"gatsby-plugin-react-helmet": "^3.3.12",
"gatsby-plugin-theme-ui": "^0.10.1",
"gatsby-remark-autolink-headers": "^4.6.0",
"gatsby-source-filesystem": "^3.9.0",
"gatsby-source-openapi-aggregate": "^0.3.0",
"gatsby-transformer-json": "^3.0.0",
"gatsby-transformer-remark": "^4.6.0",
"gatsby-transformer-yaml": "^2.4.13",
"lodash.throttle": "^4.1.1",
"preact": "^10.5.14",
"preact-render-to-string": "^5.1.19",
"prismjs": "^1.24.1",
"react": "^16.12.0",
"react-collapsible": "^2.8.1",
"react-dom": "^16.12.0",
"react-helmet": "^6.1.0",
"react-highlight.js": "^1.0.7",
"react-intersection-observer": "^8.29.0",
"react-markdown": "^5.0.3",
"react-virtualized": "^9.22.3",
"theme-ui": "^0.10.0"
},
"devDependencies": {
"prettier": "2.1.2"
},
"repository": {
"type": "git",
"url": "https://github.com/medusajs/medusa"
}
}
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="33px" height="33px" viewBox="0 0 33 33" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>GitHub</title>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="GitHub-Mark" transform="translate(-136.000000, -331.000000)" fill="#000">
<path d="M152.608,331.455 C143.614,331.455 136.32,338.748 136.32,347.745 C136.32,354.942 140.987,361.047 147.46,363.201 C148.275,363.351 148.572,362.848 148.572,362.416 C148.572,362.029 148.558,361.005 148.55,359.646 C144.019,360.63 143.063,357.462 143.063,357.462 C142.322,355.58 141.254,355.079 141.254,355.079 C139.775,354.069 141.366,354.089 141.366,354.089 C143.001,354.204 143.861,355.768 143.861,355.768 C145.314,358.257 147.674,357.538 148.602,357.121 C148.75,356.069 149.171,355.351 149.636,354.944 C146.019,354.533 142.216,353.135 142.216,346.893 C142.216,345.115 142.851,343.66 143.893,342.522 C143.725,342.11 143.166,340.453 144.053,338.211 C144.053,338.211 145.42,337.773 148.532,339.881 C149.831,339.519 151.225,339.339 152.61,339.332 C153.994,339.339 155.387,339.519 156.688,339.881 C159.798,337.773 161.163,338.211 161.163,338.211 C162.052,340.453 161.493,342.11 161.326,342.522 C162.37,343.66 163,345.115 163,346.893 C163,353.151 159.191,354.528 155.563,354.931 C156.147,355.434 156.668,356.428 156.668,357.947 C156.668,360.125 156.648,361.882 156.648,362.416 C156.648,362.852 156.942,363.359 157.768,363.2 C164.236,361.041 168.899,354.94 168.899,347.745 C168.899,338.748 161.605,331.455 152.608,331.455" id="Fill-51"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

+9
View File
@@ -0,0 +1,9 @@
<svg width="602" height="122" viewBox="0 0 602 122" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="602" height="122" fill="white"/>
<path d="M95.0625 0H129.431V119.437H108.713V18.0375L75.5625 119.437H53.8688L20.7188 19.2562V119.437H0V0H34.6125L64.8375 95.7937L95.0625 0Z" fill="#89959C"/>
<path d="M233.756 92.3826C228.881 110.42 213.525 121.633 192.319 121.633C165.75 121.633 148.688 103.595 148.688 76.5388C148.688 49.4826 165.75 31.4451 192.806 31.4451C222.788 31.4451 238.387 53.8701 230.1 81.9014H169.406C171.112 96.0389 178.913 104.326 192.562 104.326C202.069 104.326 209.625 99.9389 212.55 92.1389H233.756V92.3826ZM169.894 68.2514H211.819C213.037 56.0639 205.969 48.0201 192.562 48.0201C179.888 48.0201 171.844 55.0889 169.894 68.2514Z" fill="#89959C"/>
<path d="M312.244 0.000396729H331.744V119.438H312.244V106.763C305.663 116.269 295.669 121.875 283.238 121.875C260.813 121.875 244.725 103.838 244.725 76.7816C244.725 49.7254 260.325 31.6879 283.238 31.6879C295.669 31.6879 305.663 37.0504 312.244 46.8004V0.000396729V0.000396729ZM312.975 76.7816C312.975 59.9629 303.469 48.7504 289.088 48.7504C274.463 48.7504 264.956 59.9629 264.956 76.7816C264.956 93.6004 274.463 104.813 289.088 104.813C303.469 104.569 312.975 93.6004 312.975 76.7816Z" fill="#89959C"/>
<path d="M403.894 34.1264H423.394V119.439H403.894V104.083C397.8 115.539 388.294 121.389 376.35 121.389C352.706 121.389 348.806 97.7451 348.806 82.6326V33.8826H368.306V82.3889C368.306 93.6014 370.987 104.326 383.175 104.326C395.85 104.326 403.894 93.1139 403.894 77.2701V34.1264Z" fill="#89959C"/>
<path d="M437.775 88.7263H456.544C455.325 99.2076 463.125 106.033 476.531 106.033C487.013 106.033 493.838 101.889 493.838 95.7951C493.838 78.7326 438.994 90.9201 438.994 58.9889C438.994 41.9264 454.838 31.4451 475.556 31.4451C498.469 31.4451 515.044 44.1201 511.387 63.6201H492.375C494.813 53.8701 487.013 47.2888 475.313 47.2888C465.319 47.2888 458.981 51.6764 458.981 57.7701C458.981 75.0764 514.069 61.9139 514.069 94.3326C514.069 111.639 498.469 121.633 476.287 121.633C452.156 121.633 436.313 109.445 437.775 88.7263Z" fill="#89959C"/>
<path d="M601.331 70.9316V119.438H583.293V104.569C576.956 115.538 566.475 121.875 552.581 121.875C534.544 121.875 523.331 111.394 523.331 96.5254C523.331 79.2191 538.444 68.9816 562.331 68.9816C568.912 68.9816 575.737 69.7129 581.831 71.1754C581.831 58.2566 577.2 48.7504 563.55 48.7504C553.312 48.7504 546.975 53.8691 548.925 63.3754H529.181C524.55 44.3629 541.369 31.6879 563.55 31.6879C588.656 31.6879 601.331 47.0441 601.331 70.9316ZM582.562 85.8004C576.956 83.8504 570.131 82.8754 564.037 82.8754C550.144 82.8754 543.319 87.9941 543.319 95.7941C543.319 102.619 548.437 106.275 556.725 106.275C569.156 106.275 579.394 98.2316 582.562 85.8004Z" fill="#89959C"/>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

+16
View File
@@ -0,0 +1,16 @@
<svg width="905" height="234" viewBox="0 0 905 234" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path d="M189.638 38.0267L136.744 7.55792C119.438 -2.43583 98.2313 -2.43583 80.925 7.55792L27.7875 38.0267C10.725 48.0204 0 66.5454 0 86.2892V147.47C0 167.458 10.725 185.739 27.7875 195.733L80.6812 226.445C97.9875 236.439 119.194 236.439 136.5 226.445L189.394 195.733C206.7 185.739 217.181 167.458 217.181 147.47V86.2892C217.669 66.5454 206.944 48.0204 189.638 38.0267ZM108.712 171.358C78.7312 171.358 54.3562 146.983 54.3562 117.002C54.3562 87.0204 78.7312 62.6454 108.712 62.6454C138.694 62.6454 163.312 87.0204 163.312 117.002C163.312 146.983 138.938 171.358 108.712 171.358Z" fill="#0A3149"/>
<path d="M398.808 55.3289H433.176V174.766H412.458V73.3664L379.308 174.766H357.614L324.464 74.5852V174.766H303.745V55.3289H338.358L368.583 151.123L398.808 55.3289Z" fill="#0A3149"/>
<path d="M537.501 147.712C532.626 165.749 517.27 176.962 496.064 176.962C469.495 176.962 452.433 158.924 452.433 131.868C452.433 104.812 469.495 86.774 496.552 86.774C526.533 86.774 542.133 109.199 533.845 137.23H473.151C474.858 151.368 482.658 159.655 496.308 159.655C505.814 159.655 513.37 155.268 516.295 147.468H537.501V147.712ZM473.639 123.58H515.564C516.783 111.393 509.714 103.349 496.308 103.349C483.633 103.349 475.589 110.418 473.639 123.58Z" fill="#0A3149"/>
<path d="M615.989 55.3293H635.489V174.767H615.989V162.092C609.408 171.598 599.414 177.204 586.983 177.204C564.558 177.204 548.47 159.167 548.47 132.111C548.47 105.054 564.07 87.0168 586.983 87.0168C599.414 87.0168 609.408 92.3793 615.989 102.129V55.3293ZM616.72 132.111C616.72 115.292 607.214 104.079 592.833 104.079C578.208 104.079 568.701 115.292 568.701 132.111C568.701 148.929 578.208 160.142 592.833 160.142C607.214 159.898 616.72 148.929 616.72 132.111Z" fill="#0A3149"/>
<path d="M707.639 89.4553H727.139V174.768H707.639V159.412C701.545 170.868 692.039 176.718 680.095 176.718C656.451 176.718 652.552 153.074 652.552 137.962V89.2115H672.052V137.718C672.052 148.93 674.733 159.655 686.92 159.655C699.595 159.655 707.639 148.443 707.639 132.599V89.4553Z" fill="#0A3149"/>
<path d="M741.52 144.055H760.289C759.07 154.537 766.87 161.362 780.277 161.362C790.758 161.362 797.583 157.218 797.583 151.124C797.583 134.062 742.739 146.249 742.739 114.318C742.739 97.2553 758.583 86.774 779.302 86.774C802.214 86.774 818.789 99.449 815.133 118.949H796.12C798.558 109.199 790.758 102.618 779.058 102.618C769.064 102.618 762.726 107.005 762.726 113.099C762.726 130.405 817.814 117.243 817.814 149.662C817.814 166.968 802.214 176.962 780.033 176.962C755.901 176.962 740.058 164.774 741.52 144.055Z" fill="#0A3149"/>
<path d="M905.077 126.261V174.767H887.039V159.898C880.702 170.867 870.22 177.204 856.327 177.204C838.289 177.204 827.077 166.723 827.077 151.854C827.077 134.548 842.189 124.311 866.077 124.311C872.658 124.311 879.483 125.042 885.577 126.504C885.577 113.586 880.945 104.079 867.295 104.079C857.058 104.079 850.72 109.198 852.67 118.704H832.927C828.295 99.6918 845.114 87.0168 867.295 87.0168C892.402 87.0168 905.077 102.373 905.077 126.261ZM886.308 141.129C880.702 139.179 873.877 138.204 867.783 138.204C853.889 138.204 847.064 143.323 847.064 151.123C847.064 157.948 852.183 161.604 860.47 161.604C872.902 161.604 883.139 153.561 886.308 141.129Z" fill="#0A3149"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="904.8" height="234" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

@@ -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)",
},
}}
>
&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
@@ -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
+26
View File
@@ -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
+115
View File
@@ -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
@@ -0,0 +1,157 @@
import React, { useReducer } from "react"
import scrollParent from "../utils/scroll-parent"
import types from "./types"
export const defaultNavigationContext = {
api: "null",
setApi: () => {},
currentSection: null,
updateSection: () => {},
currentHash: null,
updateHash: () => {},
openSections: [],
openSection: () => {},
metadata: null,
updateMetadata: () => {},
reset: () => {},
}
const NavigationContext = React.createContext(defaultNavigationContext)
export default NavigationContext
const reducer = (state, action) => {
switch (action.type) {
case types.SET_API: {
return {
...state,
api: action.payload,
}
}
case types.UPDATE_HASH:
return {
...state,
currentSection: action.payload.section,
currentHash: action.payload.method,
}
case types.UPDATE_SECTION:
return {
...state,
currentSection: action.payload,
currentHash: null,
}
case types.OPEN_SECTION:
const obj = state.openSections
obj.push(action.payload)
return {
...state,
openSections: obj,
}
case types.RESET:
return {
...state,
openSections: [],
currentSection: null,
currentHash: null,
}
case types.UPDATE_METADATA:
return {
...state,
metadata: {
title: action.payload.title,
description: action.payload.description,
},
}
default:
return state
}
}
const scrollNav = id => {
const nav = document.querySelector("#nav")
if (nav) {
const element = nav.querySelector(`#nav-${id}`)
if (element) {
scrollParent(nav, element)
}
}
}
const scrollToElement = async id => {
const element = document.querySelector(`#${id}`)
if (element) {
element.scrollIntoView({
block: "start",
inline: "nearest",
})
} else {
setTimeout(() => {
scrollToElement(id)
}, 100)
}
}
export const NavigationProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, defaultNavigationContext)
const setApi = api => {
dispatch({ type: types.SET_API, payload: api })
}
const updateMetadata = metadata => {
dispatch({ type: types.UPDATE_METADATA, payload: metadata })
}
const updateHash = (section, method) => {
dispatch({
type: types.UPDATE_HASH,
payload: { method: method, section: section },
})
window.history.replaceState(
null,
"",
`/api/${state.api}/${section}/${method}`
)
scrollNav(method)
}
const updateSection = section => {
dispatch({ type: types.UPDATE_SECTION, payload: section })
window.history.replaceState(null, "", `/api/${state.api}/${section}`)
scrollNav(section)
}
const openSection = sectionName => {
dispatch({ type: types.OPEN_SECTION, payload: sectionName })
}
const goTo = to => {
const { section, method } = to
if (!state.openSections.includes(section)) {
openSection(section)
}
scrollToElement(method || section)
}
const reset = () => {
dispatch({ type: types.RESET })
}
return (
<NavigationContext.Provider
value={{
...state,
openSection,
updateSection,
updateHash,
setApi,
goTo,
reset,
updateMetadata,
dispatch,
}}
>
{children}
</NavigationContext.Provider>
)
}
+10
View File
@@ -0,0 +1,10 @@
const types = {
SET_API: "SET_API",
UPDATE_HASH: "UPDATE_HASH",
UPDATE_SECTION: "UPDATE_SECTION",
OPEN_SECTION: "OPEN_SECTION",
RESET: "RESET",
UPDATE_METADATA: "UPDATE_METADATA",
}
export default types
Binary file not shown.
Binary file not shown.
+4
View File
@@ -0,0 +1,4 @@
@font-face {
font-family: "Medusa";
src: url("./Montserrat-Medium.ttf");
}
@@ -0,0 +1,2 @@
// eslint-disable-next-line import/no-anonymous-default-export
export default ["40em", "52em", "64em"]
@@ -0,0 +1,174 @@
export const buttons = {
pillActive: {
cursor: "pointer",
backgroundColor: "transparent",
color: "dark",
height: "40px",
outline: 0,
borderRadius: "3px",
paddingTop: "3px",
paddingBottom: "3px",
boxShadow: "pillActive",
"&:focus": {
boxShadow: "pillActiveFocus",
},
},
pill: {
cursor: "pointer",
backgroundColor: "transparent",
color: "dark",
height: "40px",
outline: 0,
borderRadius: "3px",
paddingTop: "3px",
paddingBottom: "3px",
boxShadow: "pill",
"&:focus": {
boxShadow: "buttonBoxShadowActive",
},
},
danger: {
minHeight: "28px",
fontWeight: "500",
color: "light",
fontSize: "14px",
lineHeight: "14px",
backgroundColor: "danger",
border: 0,
outline: 0,
paddingTop: "3px",
paddingBottom: "3px",
cursor: "pointer",
borderRadius: "3px",
boxShadow: "buttonBoxShadow",
"&:hover": {
color: "darkest",
boxShadow: "buttonBoxShadowHover",
},
"&:active": {
boxShadow: "buttonBoxShadowActive",
},
},
primary: {
minHeight: "24px",
height: "28px",
fontWeight: "500",
color: "dark",
fontSize: "14px",
lineHeight: "14px",
backgroundColor: "lightest",
border: 0,
outline: 0,
paddingTop: "3px",
paddingBottom: "3px",
cursor: "pointer",
borderRadius: "3px",
boxShadow: "buttonBoxShadow",
"&:hover": {
color: "darkest",
boxShadow: "buttonBoxShadowHover",
},
"&:active": {
boxShadow: "buttonBoxShadowActive",
},
"&:disabled": {
cursor: "not-allowed",
pointerEvents: "none",
opacity: 0.5,
},
},
green: {
height: "30px",
color: "lightest",
backgroundColor: "#53725D",
border: 0,
outline: 0,
paddingTop: "3px",
paddingBottom: "3px",
cursor: "pointer",
borderRadius: "3px",
"&:focus": {
boxShadow: "buttonBoxShadowFocus",
},
"&:active": {
boxShadow: "buttonBoxShadowActive",
},
},
secondary: {
height: "30px",
color: "light",
backgroundColor: "dark",
border: 0,
outline: 0,
paddingTop: "3px",
paddingBottom: "3px",
cursor: "pointer",
borderRadius: "3px",
"&:focus": {
boxShadow: "buttonBoxShadowFocus",
},
"&:active": {
boxShadow: "buttonBoxShadowActive",
},
},
cta: {
height: "30px",
color: "white",
backgroundColor: "medusaGreen",
fontWeight: 600,
fontSize: "14px",
lineHeight: "14px",
border: 0,
outline: 0,
paddingTop: "3px",
paddingBottom: "3px",
cursor: "pointer",
borderRadius: "3px",
boxShadow: "ctaBoxShadow",
"&:hover": {
boxShadow: "ctaBoxShadowHover",
},
"&:disabled": {
cursor: "not-allowed",
pointerEvents: "none",
opacity: 0.5,
},
},
delete: {
height: "30px",
color: "white",
backgroundColor: "#b02b13",
fontWeight: 600,
fontSize: "14px",
lineHeight: "14px",
border: 0,
outline: 0,
paddingTop: "3px",
paddingBottom: "3px",
cursor: "pointer",
borderRadius: "3px",
boxShadow: "ctaBoxShadow",
"&:hover": {
boxShadow: "ctaBoxShadowHover",
},
},
trigger: {
bg: "faded",
alignItems: "baseline",
fontSize: "13px",
fontWeight: "400",
},
}
export default buttons
@@ -0,0 +1,11 @@
export default {
primary: "#B27979",
secondary: "#79B28A",
medusaGreen: "#3ecf8e",
lightest: "#fefefe",
light: "#f0f0f0",
gray: "#a3acb9",
dark: "#454545",
darkest: "#212121",
placeholder: "#a3acb9",
}
@@ -0,0 +1,93 @@
// eslint-disable-next-line import/no-anonymous-default-export
export default {
invalidInput: {
color: "dark",
backgroundColor: "lightest",
border: 0,
outline: 0,
cursor: "text",
transition: "all 0.2s ease",
borderRadius: "3px",
boxShadow: "invalidBoxShadow",
"&.tag__focus": {
boxShadow: "inputBoxShadowHover",
},
"&:focus": {
boxShadow: "inputBoxShadowHover",
},
"&::placeholder": {
color: "danger",
},
},
input: {
color: "dark",
backgroundColor: "lightest",
border: 0,
outline: 0,
cursor: "text",
transition: "all 0.2s ease",
borderRadius: "3px",
boxShadow: "inputBoxShadow",
"&.tag__focus": {
boxShadow: "inputBoxShadowHover",
},
"&:focus": {
boxShadow: "inputBoxShadowHover",
},
"&::placeholder": {
color: "placeholder",
},
},
textarea: {
color: "dark",
backgroundColor: "lightest",
border: 0,
outline: 0,
cursor: "text",
transition: "all 0.2s ease",
borderRadius: "3px",
boxShadow: "inputBoxShadow",
"&.tag__focus": {
boxShadow: "inputBoxShadowHover",
},
"&:focus": {
boxShadow: "inputBoxShadowHover",
},
"&::placeholder": {
color: "placeholder",
},
},
dropdown: {
color: "dark",
backgroundColor: "lightest",
border: 0,
outline: 0,
paddingTop: "3px",
paddingBottom: "3px",
cursor: "pointer",
borderRadius: "3px",
boxShadow: "buttonPrimaryBoxShadow",
"&:hover": {
color: "darkest",
boxShadow: "buttonPrimaryBoxShadowHover",
},
"&:active": {
boxShadow: "buttonPrimaryBoxShadowActive",
},
},
}
@@ -0,0 +1,131 @@
import "../fonts/index.css"
import breakpoints from "./breakpoints"
import buttons from "./buttons"
import spacing from "./spacing"
import shadows from "./shadows"
import forms from "./forms"
import labels from "./labels"
// eslint-disable-next-line import/no-anonymous-default-export
export default {
sizes: {
headerHeight: "50px",
},
links: {
topbar: {
fontFamily: "body",
fontSize: "2",
textDecoration: "none",
color: "dark",
},
},
text: {
labels,
largest: {
fontFamily: "body",
fontSize: "22px",
fontweight: "300",
lineHeight: "1.5",
letterSpacing: "normal",
},
large: {
fontFamily: "body",
fontSize: "18px",
fontWeight: "300",
lineHeight: "1.22",
letterSpacing: "-0.5px",
},
medium: {
fontFamily: "body",
fontSize: "16px",
fontWeight: "300",
lineHeight: "1.22",
letterSpacing: "normal",
},
base: {
fontFamily: "body",
fontsize: "14px",
fontWeight: "300",
lineHeight: "1.22",
letterSpacing: "-0.25px",
},
small: {
fontFamily: "body",
fontSize: "12px",
fontWeight: "300",
lineHeight: "12px",
letterSpacing: "0px",
},
},
colors: {
primary: "#53725D",
secondary: "#79B28A",
danger: "#FF7675",
placeholder: "#a3acb9",
dark: "#0a3149",
darkContrast: "#0a314940",
light: "#ffffff",
faded: "#eef0f5",
fadedContrast: "#eef0f540",
},
borders: {
hairline: "1px solid #E3E8EE",
},
fontSizes: [12, 14, 16, 18, 22],
fonts: {
body:
"-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Ubuntu, sans-serif",
heading: "system-ui, sans-serif",
monospace: "Menlo, monospace",
},
breakpoints,
spacing,
mediaQueries: {
small: `@media screen and (min-width: ${breakpoints[0]})`,
medium: `@media screen and (min-width: ${breakpoints[1]})`,
large: `@media screen and (min-width: ${breakpoints[2]})`,
},
grid: {
selectedShadow: `
inset rgba(0, 0, 0, 0) 0px 0px 0px 0px,
inset rgba(206, 208, 190, 0.56) 0px 0px 0px 2px,
inset rgba(0, 0, 0, 0) 0px 0px 0px 0px,
inset rgba(60, 66, 87, 0.16) 0px 0px 0px 1px,
inset rgba(0, 0, 0, 0) 0px 0px 0px 0px,
inset rgba(0, 0, 0, 0) 0px 0px 0px 0px,
inset rgba(0, 0, 0, 0) 0px 0px 0px 0px;
`,
header: {
padding: 2,
fontSize: 1,
fontFamily: "body",
},
data: {
padding: 2,
fontSize: 1,
fontFamily: "body",
},
},
shadows,
variants: {
loginCard: {
boxShadow: "buttonBoxShadow",
borderRadius: "3px",
},
badge: {
fontSize: "0",
color: "dark",
backgroundColor: "lightest",
boxShadow: "buttonBoxShadow",
borderRadius: "3px",
minWidth: "unset",
px: "1",
},
},
forms,
buttons,
radii: {
small: "8px",
},
}
@@ -0,0 +1,17 @@
export const labels = {
required: {
color: "danger",
fontSize: 0,
},
GET: {
color: "#4159de",
},
POST: {
color: "#09825d",
},
DELETE: {
color: "#b50a0a",
},
}
export default labels
@@ -0,0 +1,119 @@
// eslint-disable-next-line import/no-anonymous-default-export
export default {
pill: `
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0.12) 0px 1px 1px 0px,
rgba(60, 66, 87, 0.16) 0px 0px 0px 1px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(60, 66, 87, 0.08) 0px 0px 5px 0px;
`,
pillActive: `
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(83, 114, 93, 0.8) 0px 0px 0px 2px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(60, 66, 87, 0.16) 0px 0px 0px 1px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px
`,
pillActiveFocus: `
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(206, 208, 190, 0.36) 0px 0px 0px 4px,
rgba(83, 114, 93, 0.8) 0px 0px 0px 2px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(60, 66, 87, 0.16) 0px 0px 0px 1px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px
`,
invalidBoxShadow: `
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(255, 66, 87, 0.8) 0px 0px 0px 1px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px;
`,
inputBoxShadow: `
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(60, 66, 87, 0.16) 0px 0px 0px 1px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px;
`,
inputBoxShadowHover: `
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(206, 208, 190, 0.36) 0px 0px 0px 4px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(60, 66, 87, 0.16) 0px 0px 0px 1px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px;
`,
buttonBoxShadow: `
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0.12) 0px 1px 1px 0px,
rgba(60, 66, 87, 0.16) 0px 0px 0px 1px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(60, 66, 87, 0.08) 0px 2px 5px 0px;
`,
buttonBoxShadowHover: `
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0.12) 0px 1px 1px 0px,
rgba(60, 66, 87, 0.16) 0px 0px 0px 1px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(60, 66, 87, 0.08) 0px 3px 9px 0px,
rgba(60, 66, 87, 0.08) 0px 2px 5px 0px;
`,
buttonBoxShadowActive: `
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(206, 208, 190, 0.36) 0px 0px 0px 4px,
rgba(0, 0, 0, 0.12) 0px 1px 1px 0px,
rgba(60, 66, 87, 0.16) 0px 0px 0px 1px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(60, 66, 87, 0.08) 0px 3px 9px 0px;
rgba(60, 66, 87, 0.08) 0px 2px 5px 0px;
`,
tagBoxShadow: `
rgba(0,0,0,0) 0px 0px 0px 0px,
rgb(190, 191, 208) 0px 0px 0px 2px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(60, 62, 87, 0.22) 0px 0px 0px 2px,
rgba(0,0,0,0) 0px 0px 0px 0px,
rgba(0,0,0,0) 0px 0px 0px 0px,
rgba(0,0,0,0) 0px 0px 0px 0px;
`,
ctaBoxShadow: `
rgba(0,0,0,0) 0px 0px 0px 0px,
rgba(0,0,0,0) 0px 0px 0px 0px,
rgba(0,0,0,0.12) 0px 1px 1px 0px,
rgba(62, 207, 142, 0) 0px 0px 0px 1px,
rgba(0,0,0,0) 0px 0px 0px 0px,
rgba(0,0,0,0) 0px 0px 0px 0px,
rgba(60,66,87,0.08) 0px 2px 5px 0px;
`,
ctaBoxShadowHover: `
rgba(0,0,0,0) 0px 0px 0px 0px,
rgba(0,0,0,0) 0px 0px 0px 0px,
rgba(0,0,0,0.12) 0px 1px 1px 0px,
rgba(62, 207, 142, 0) 0px 0px 0px 1px,
rgba(0,0,0,0) 0px 0px 0px 0px,
rgba(62, 207, 142, 0.25) 0px 3px 9px 0px;
rgba(62, 207, 142, 0.25) 0px 2px 5px 0px;
`,
sidebarShadow: `
inset -1px 0 0 0 #eef0f5
`,
topbarShadow: `
0 2px 5px 0 rgb(59 65 94 / 10%), 0 1px 1px 0 rgb(0 0 0 / 7%)
`,
}
@@ -0,0 +1,2 @@
// eslint-disable-next-line import/no-anonymous-default-export
export default [0, 4, 8, 16, 32, 64]
+29
View File
@@ -0,0 +1,29 @@
import { useEffect, useRef, useState } from "react"
const useInView = options => {
const containerRef = useRef(null)
const [isInView, setIsInView] = useState(false)
const callback = entries => {
const [entry] = entries
setIsInView(entry.isIntersecting)
}
useEffect(() => {
const ref = containerRef.current
const observer = new IntersectionObserver(callback, options)
if (ref) {
observer.observe(ref)
}
return () => {
if (ref) {
observer.unobserve(ref)
}
}
}, [containerRef, options])
return [containerRef, isInView]
}
export default useInView
@@ -0,0 +1,123 @@
code[class*="language-"],
pre[class*="language-"] {
color: #0a3149;
background: none;
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
font-size: 12px;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: 0;
overflow: auto;
border-radius: 0.3em;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: transparent;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: 0.1em;
border-radius: 0.3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: blue;
}
.token.punctuation {
color: #0a3149;
}
.token.namespace {
opacity: 0.7;
}
.token.property,
.token.tag,
.token.constant,
.token.symbol,
.token.deleted {
color: #0a3149;
}
.token.number {
color: #e56f4a;
}
.token.boolean {
color: #3a97d4;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #09825d;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string,
.token.variable {
color: #0a3149;
}
.token.atrule,
.token.attr-value,
.token.function,
.token.class-name {
color: #e6db74;
}
.token.keyword {
color: #3a97d4;
}
.token.keyword.null {
color: grey;
}
.token.regex,
.token.important {
color: #fd971f;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
@@ -0,0 +1,43 @@
import React, { useContext, useEffect, useState } from "react"
import { Helmet } from "react-helmet"
import Layout from "../components/layout"
import Content from "../components/content"
import NavigationContext from "../context/navigation-context"
export default function ReferencePage({
pageContext: { data, api, title, description, to },
}) {
const { setApi, goTo, metadata } = useContext(NavigationContext)
const [siteData, setSiteData] = useState({
title: title,
description: description,
})
useEffect(() => {
setApi(api)
if (to) {
goTo(to)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
useEffect(() => {
if (metadata) {
setSiteData({
title: metadata.title,
description: metadata.description,
})
}
}, [metadata])
return (
<Layout data={data} api={api}>
<Helmet>
<title>{`${siteData.title} | Medusa Commerce API Reference`}</title>
<meta name="description" content={siteData.description} />
</Helmet>
<Content data={data} api={api} />
</Layout>
)
}
@@ -0,0 +1,8 @@
export const convertToKebabCase = string => {
return string
.replace(/\s+/g, "-")
.replace("'", "")
.replace(".", "")
.replace('"', "")
.toLowerCase()
}
@@ -0,0 +1,29 @@
export const formatMethodParams = method => {
const { parameters, requestBody } = method
const params = []
if (parameters && parameters.length > 0) {
parameters.map(p => {
return params.push({
property: p.name,
description: p.description,
required: p.required,
type: p.schema.type,
})
})
}
if (requestBody) {
const { required, properties } = requestBody
properties.map(p => {
return params.push({
property: p.property,
description: p.description,
required: required ? required.some(req => req === p.property) : false,
type: p.type,
nestedModel: p.nestedModel,
})
})
}
return { properties: params }
}
+3
View File
@@ -0,0 +1,3 @@
export const formatRoute = string => {
return string.replace(/{(.*?)}/g, ":$1")
}
+215
View File
@@ -0,0 +1,215 @@
import { graphql } from "gatsby"
export const StoreSections = graphql`
fragment StoreSections on StoreSections {
sections {
...StoreSection
}
}
`
export const StoreSection = graphql`
fragment StoreSection on StoreSectionsSection {
section_name
paths {
...StorePath
}
schema {
object
description
properties {
property
type
description
format
nestedModel {
title
properties {
property
type
}
}
}
}
}
`
export const StorePath = graphql`
fragment StorePath on StoreSectionsSectionPaths {
name
methods {
...StoreMethod
}
}
`
export const StoreMethod = graphql`
fragment StoreMethod on StoreSectionsSectionPathsMethods {
tags
summary
description
method
operationId
responses {
...StoreResponse
}
requestBody {
...StoreRequestBody
}
parameters {
description
in
name
required
schema {
type
}
}
}
`
export const StoreResponse = graphql`
fragment StoreResponse on StoreSectionsSectionPathsMethodsResponses {
status
description
content {
_ref
property
description
json
items {
_ref
}
}
}
`
export const StoreRequestBody = graphql`
fragment StoreRequestBody on StoreSectionsSectionPathsMethodsRequestBody {
type
required
properties {
description
property
type
nestedModel {
title
properties {
property
type
}
}
}
}
`
export const AdminSections = graphql`
fragment AdminSections on AdminSections {
sections {
...AdminSection
}
}
`
export const AdminSection = graphql`
fragment AdminSection on AdminSectionsSection {
section_name
paths {
...AdminPath
}
schema {
object
description
properties {
property
type
description
format
nestedModel {
title
properties {
property
type
description
}
}
}
}
}
`
export const AdminPath = graphql`
fragment AdminPath on AdminSectionsSectionPaths {
name
methods {
...AdminMethod
}
}
`
export const AdminMethod = graphql`
fragment AdminMethod on AdminSectionsSectionPathsMethods {
tags
summary
description
method
operationId
responses {
...AdminResponse
}
requestBody {
...AdminRequestBody
}
parameters {
description
in
name
required
schema {
type
}
}
}
`
export const AdminResponse = graphql`
fragment AdminResponse on AdminSectionsSectionPathsMethodsResponses {
status
description
content {
_ref
type
property
description
json
items {
type
_ref
}
}
}
`
export const AdminRequestBody = graphql`
fragment AdminRequestBody on AdminSectionsSectionPathsMethodsRequestBody {
type
required
properties {
description
enum
format
property
type
nestedModel {
title
properties {
property
type
description
}
}
}
}
`
+20
View File
@@ -0,0 +1,20 @@
const scrollParent = (parent, child) => {
const parentRect = parent.getBoundingClientRect()
const parentViewableArea = {
height: parent.clientHeight,
width: parent.clientWidth,
}
const childRect = child.getBoundingClientRect()
const isViewable =
childRect.top >= parentRect.top &&
childRect.top <= parentRect.top + parentViewableArea.height
if (!isViewable) {
const pos = childRect.top + parent.scrollTop - parentRect.top
parent.scrollTop = pos > 0 ? pos : 0
}
}
export default scrollParent
Binary file not shown.

After

Width:  |  Height:  |  Size: 225 B

File diff suppressed because it is too large Load Diff