docs: create docs workspace (#5174)
* docs: migrate ui docs to docs universe * created yarn workspace * added eslint and tsconfig configurations * fix eslint configurations * fixed eslint configurations * shared tailwind configurations * added shared ui package * added more shared components * migrating more components * made details components shared * move InlineCode component * moved InputText * moved Loading component * Moved Modal component * moved Select components * Moved Tooltip component * moved Search components * moved ColorMode provider * Moved Notification components and providers * used icons package * use UI colors in api-reference * moved Navbar component * used Navbar and Search in UI docs * added Feedback to UI docs * general enhancements * fix color mode * added copy colors file from ui-preset * added features and enhancements to UI docs * move Sidebar component and provider * general fixes and preparations for deployment * update docusaurus version * adjusted versions * fix output directory * remove rootDirectory property * fix yarn.lock * moved code component * added vale for all docs MD and MDX * fix tests * fix vale error * fix deployment errors * change ignore commands * add output directory * fix docs test * general fixes * content fixes * fix announcement script * added changeset * fix vale checks * added nofilter option * fix vale error
This commit is contained in:
33
www/apps/docs/src/components/BorderedIcon/index.tsx
Normal file
33
www/apps/docs/src/components/BorderedIcon/index.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import React from "react"
|
||||
import type { BorderedIconProps as UiBorderedIconProps } from "docs-ui"
|
||||
import { BorderedIcon as UiBorderedIcon } from "docs-ui"
|
||||
import { useColorMode } from "@docusaurus/theme-common"
|
||||
|
||||
type BorderedIconProps = {
|
||||
icon?: {
|
||||
light: string
|
||||
dark?: string
|
||||
}
|
||||
} & Omit<UiBorderedIconProps, "icon">
|
||||
|
||||
const BorderedIcon: React.FC<BorderedIconProps> = ({
|
||||
icon = null,
|
||||
...props
|
||||
}) => {
|
||||
const { colorMode } = useColorMode()
|
||||
|
||||
return (
|
||||
<UiBorderedIcon
|
||||
{...props}
|
||||
icon={
|
||||
icon
|
||||
? colorMode === "light"
|
||||
? icon.light
|
||||
: icon.dark || icon.light
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default BorderedIcon
|
||||
106
www/apps/docs/src/components/CloudinaryImage/index.tsx
Normal file
106
www/apps/docs/src/components/CloudinaryImage/index.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import React from "react"
|
||||
import { useThemeConfig } from "@docusaurus/theme-common"
|
||||
// @ts-expect-error: wait until docusaurus uses type: module
|
||||
import { Cloudinary } from "@cloudinary/url-gen"
|
||||
import MDXImg, { Props as MDXImgProps } from "@theme/MDXComponents/Img"
|
||||
import {
|
||||
pad,
|
||||
imaggaScale,
|
||||
imaggaCrop,
|
||||
crop,
|
||||
fit,
|
||||
minimumPad,
|
||||
fill,
|
||||
scale,
|
||||
limitFit,
|
||||
thumbnail,
|
||||
limitFill,
|
||||
minimumFit,
|
||||
limitPad,
|
||||
fillPad,
|
||||
} from "@cloudinary/url-gen/actions/resize"
|
||||
import { byRadius } from "@cloudinary/url-gen/actions/roundCorners"
|
||||
import { ThemeConfig } from "@medusajs/docs"
|
||||
|
||||
const resizeActions = {
|
||||
pad: pad,
|
||||
imaggaScale: imaggaScale,
|
||||
imaggaCrop: imaggaCrop,
|
||||
crop: crop,
|
||||
fit: fit,
|
||||
minimumPad: minimumPad,
|
||||
fill: fill,
|
||||
scale: scale,
|
||||
limitFit: limitFit,
|
||||
thumbnail: thumbnail,
|
||||
limitFill: limitFill,
|
||||
minimumFit: minimumFit,
|
||||
limitPad: limitPad,
|
||||
fillPad: fillPad,
|
||||
}
|
||||
|
||||
const imageRegex =
|
||||
/^https:\/\/res.cloudinary.com\/.*\/upload\/v[0-9]+\/(?<imageId>.*)$/
|
||||
|
||||
type CloudinaryImageProps = MDXImgProps
|
||||
|
||||
const CloudinaryImage: React.FC<CloudinaryImageProps> = ({ src, ...props }) => {
|
||||
const { cloudinaryConfig } = useThemeConfig() as ThemeConfig
|
||||
const matchingRegex = src.match(imageRegex)
|
||||
if (
|
||||
!cloudinaryConfig ||
|
||||
!matchingRegex?.groups ||
|
||||
!matchingRegex.groups.imageId
|
||||
) {
|
||||
// either cloudinary isn't configured or
|
||||
// could not match url to a cloudinary url
|
||||
// default to docusaurus's image component
|
||||
return <MDXImg src={src} {...props} />
|
||||
}
|
||||
|
||||
const cloudinary = new Cloudinary({
|
||||
cloud: {
|
||||
cloudName: cloudinaryConfig.cloudName,
|
||||
},
|
||||
})
|
||||
const image = cloudinary.image(
|
||||
matchingRegex.groups.imageId.replaceAll("%20", " ")
|
||||
)
|
||||
|
||||
cloudinaryConfig.flags?.forEach((flag) => image.addTransformation(flag))
|
||||
|
||||
if (cloudinaryConfig.roundCorners) {
|
||||
image.roundCorners(byRadius(cloudinaryConfig.roundCorners))
|
||||
}
|
||||
if (cloudinaryConfig.resize) {
|
||||
const action = resizeActions[cloudinaryConfig.resize.action]
|
||||
let resizeAction = action()
|
||||
if (props.width || props.height) {
|
||||
if (props.width) {
|
||||
resizeAction = resizeAction.width(props.width)
|
||||
}
|
||||
|
||||
if (props.height) {
|
||||
resizeAction = resizeAction.height(props.height)
|
||||
}
|
||||
} else if (cloudinaryConfig.resize.aspectRatio) {
|
||||
resizeAction = resizeAction.aspectRatio(
|
||||
cloudinaryConfig.resize.aspectRatio
|
||||
)
|
||||
} else {
|
||||
if (cloudinaryConfig.resize.width) {
|
||||
resizeAction = resizeAction.width(cloudinaryConfig.resize.width)
|
||||
}
|
||||
|
||||
if (cloudinaryConfig.resize.height) {
|
||||
resizeAction = resizeAction.height(cloudinaryConfig.resize.height)
|
||||
}
|
||||
}
|
||||
|
||||
image.resize(resizeAction)
|
||||
}
|
||||
|
||||
return <MDXImg {...props} src={image.toURL()} />
|
||||
}
|
||||
|
||||
export default CloudinaryImage
|
||||
47
www/apps/docs/src/components/DocSidebarItemIcon/index.tsx
Normal file
47
www/apps/docs/src/components/DocSidebarItemIcon/index.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import React from "react"
|
||||
import icons from "../../theme/Icon"
|
||||
import BorderedIcon from "../BorderedIcon"
|
||||
import clsx from "clsx"
|
||||
|
||||
type DocSidebarItemIconProps = {
|
||||
icon?: string
|
||||
is_title?: boolean
|
||||
is_disabled?: boolean
|
||||
} & React.HTMLAttributes<HTMLSpanElement>
|
||||
|
||||
const DocSidebarItemIcon: React.FC<DocSidebarItemIconProps> = ({
|
||||
icon,
|
||||
is_title,
|
||||
is_disabled,
|
||||
}) => {
|
||||
const IconComponent = icons[icon]
|
||||
|
||||
return (
|
||||
<>
|
||||
{is_title && (
|
||||
<BorderedIcon
|
||||
icon={null}
|
||||
IconComponent={IconComponent}
|
||||
iconClassName={clsx("sidebar-item-icon")}
|
||||
iconColorClassName={clsx(
|
||||
"text-medusa-fg-subtle dark:text-medusa-fg-subtle-dark",
|
||||
is_disabled &&
|
||||
"text-medusa-fg-disabled dark:text-medusa-fg-disabled-dark"
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{!is_title && (
|
||||
<IconComponent
|
||||
className={clsx(
|
||||
"sidebar-item-icon",
|
||||
"text-medusa-fg-subtle dark:text-medusa-fg-subtle-dark",
|
||||
is_disabled &&
|
||||
"text-medusa-fg-disabled dark:text-medusa-fg-disabled-dark"
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default DocSidebarItemIcon
|
||||
28
www/apps/docs/src/components/Feedback/index.tsx
Normal file
28
www/apps/docs/src/components/Feedback/index.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import React from "react"
|
||||
import {
|
||||
Feedback as UiFeedback,
|
||||
type FeedbackProps as UiFeedbackProps,
|
||||
GITHUB_ISSUES_PREFIX,
|
||||
} from "docs-ui"
|
||||
import useIsBrowser from "@docusaurus/useIsBrowser"
|
||||
import { useLocation } from "@docusaurus/router"
|
||||
import clsx from "clsx"
|
||||
|
||||
type FeedbackProps = Omit<UiFeedbackProps, "pathName" | "reportLink">
|
||||
|
||||
const Feedback = (props: FeedbackProps) => {
|
||||
const isBrowser = useIsBrowser()
|
||||
const location = useLocation()
|
||||
|
||||
return (
|
||||
<UiFeedback
|
||||
{...props}
|
||||
className={clsx("py-2", props.className)}
|
||||
pathName={isBrowser && location ? location.pathname : ""}
|
||||
reportLink={GITHUB_ISSUES_PREFIX}
|
||||
showLongForm={true}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default Feedback
|
||||
39
www/apps/docs/src/components/Footer/SocialLinks/index.tsx
Normal file
39
www/apps/docs/src/components/Footer/SocialLinks/index.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import React from "react"
|
||||
import IconTwitter from "@site/src/theme/Icon/Twitter"
|
||||
import IconGitHub from "@site/src/theme/Icon/GitHub"
|
||||
import IconDiscord from "@site/src/theme/Icon/Discord"
|
||||
import IconLinkedIn from "@site/src/theme/Icon/LinkedIn"
|
||||
import { SocialLink } from "@medusajs/docs"
|
||||
|
||||
type SocialLinksProps = {
|
||||
links?: SocialLink[]
|
||||
} & React.HTMLAttributes<HTMLDivElement>
|
||||
|
||||
const SocialLinks: React.FC<SocialLinksProps> = ({ links = [] }) => {
|
||||
const socialIcons = {
|
||||
twitter: (
|
||||
<IconTwitter className="text-ui-fg-muted group-hover:text-ui-fg-subtle" />
|
||||
),
|
||||
github: (
|
||||
<IconGitHub className="text-ui-fg-muted group-hover:text-ui-fg-subtle" />
|
||||
),
|
||||
discord: (
|
||||
<IconDiscord className="text-ui-fg-muted group-hover:text-ui-fg-subtle" />
|
||||
),
|
||||
linkedin: (
|
||||
<IconLinkedIn className="text-ui-fg-muted group-hover:text-ui-fg-subtle" />
|
||||
),
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
{links.map((link) => (
|
||||
<a className="group ml-1 first:ml-0" href={link.href} key={link.type}>
|
||||
{socialIcons[link.type]}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SocialLinks
|
||||
50
www/apps/docs/src/components/Glossary/index.tsx
Normal file
50
www/apps/docs/src/components/Glossary/index.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import React, { useMemo } from "react"
|
||||
import Heading from "@theme/Heading"
|
||||
import { GlossaryType, getGlossary } from "../../utils/glossary"
|
||||
import Link from "@docusaurus/Link"
|
||||
|
||||
type GlossaryProps = React.HTMLAttributes<HTMLDivElement>
|
||||
|
||||
type GroupedGlossary = {
|
||||
[letter: string]: GlossaryType[]
|
||||
}
|
||||
|
||||
const Glossary: React.FC<GlossaryProps> = (props) => {
|
||||
const groupedGlossary: GroupedGlossary = useMemo(() => {
|
||||
const glossary = getGlossary()
|
||||
glossary.sort((a, b) => {
|
||||
return a.title.localeCompare(b.title)
|
||||
})
|
||||
const grouped: GroupedGlossary = {}
|
||||
glossary.forEach((glossaryItem) => {
|
||||
const firstChar = glossaryItem.title.charAt(0).toLowerCase()
|
||||
grouped[firstChar] = [...(grouped[firstChar] || []), glossaryItem]
|
||||
})
|
||||
|
||||
return grouped
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div {...props}>
|
||||
{Object.entries(groupedGlossary).map(([letter, glossary], index) => (
|
||||
<React.Fragment key={index}>
|
||||
<Heading id={letter.toLowerCase()} as="h2">
|
||||
{letter.toUpperCase()}
|
||||
</Heading>
|
||||
<ul>
|
||||
{glossary.map((glossaryItem, index) => (
|
||||
<li key={index}>
|
||||
<Link to={glossaryItem.referenceLink}>
|
||||
{glossaryItem.title}
|
||||
</Link>
|
||||
: {glossaryItem.content}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Glossary
|
||||
101
www/apps/docs/src/components/LargeCard/index.tsx
Normal file
101
www/apps/docs/src/components/LargeCard/index.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import React from "react"
|
||||
import clsx from "clsx"
|
||||
import Link from "@docusaurus/Link"
|
||||
import { Badge } from "docs-ui"
|
||||
import BorderedIcon from "../BorderedIcon"
|
||||
|
||||
type LargeCardProps = {
|
||||
// TODO change to React.FC<IconProps>
|
||||
// once react versions are resolved
|
||||
Icon: any
|
||||
image: {
|
||||
light: string
|
||||
dark?: string
|
||||
}
|
||||
title: string
|
||||
action?: {
|
||||
href?: string
|
||||
}
|
||||
isSoon?: boolean
|
||||
className?: string
|
||||
} & React.HTMLAttributes<HTMLDivElement>
|
||||
|
||||
const LargeCard: React.FC<LargeCardProps> = ({
|
||||
Icon,
|
||||
image,
|
||||
title,
|
||||
action: { href } = {
|
||||
href: "",
|
||||
},
|
||||
isSoon = false,
|
||||
className = "",
|
||||
children,
|
||||
}) => {
|
||||
return (
|
||||
<article
|
||||
className={clsx(
|
||||
"group bg-docs-bg-surface dark:bg-docs-bg-surface-dark",
|
||||
"rounded",
|
||||
"p-1 !pb-1.5 shadow-card-rest dark:shadow-card-rest-dark",
|
||||
"flex flex-col justify-between relative",
|
||||
"[&:nth-child(3n+1):before]:bg-[2%_52%] [&:nth-child(3n+2):before]:bg-[19%_16%] [&:nth-child(3n+3):before]:bg-[17%_50%]",
|
||||
!isSoon &&
|
||||
"hover:bg-medusa-bg-subtle-hover dark:hover:bg-medusa-bg-base-hover-dark hover:shadow-card-hover dark:hover:shadow-card-hover-dark",
|
||||
!isSoon &&
|
||||
"group-hover:bg-medusa-bg-subtle-hover dark:group-hover:bg-medusa-bg-base-hover-dark group-hover:shadow-card-hover dark:group-hover:shadow-card-hover-dark",
|
||||
"transition-all duration-200 ease-ease",
|
||||
"large-card",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className={clsx("z-[3]")}>
|
||||
{isSoon && (
|
||||
<Badge variant={"purple"} className="absolute top-1 right-1">
|
||||
Guide coming soon
|
||||
</Badge>
|
||||
)}
|
||||
{(Icon || image) && (
|
||||
<BorderedIcon
|
||||
IconComponent={Icon}
|
||||
icon={image}
|
||||
iconClassName="w-[20px] h-[20px]"
|
||||
wrapperClassName="mb-1"
|
||||
iconWrapperClassName="p-[6px]"
|
||||
/>
|
||||
)}
|
||||
<div className="mb-0.25">
|
||||
<span
|
||||
className={clsx(
|
||||
isSoon &&
|
||||
"group-hover:text-medusa-fg-disabled dark:group-hover:text-medusa-fg-disabled-dark",
|
||||
"text-medusa-fg-base dark:text-medusa-fg-base-dark text-compact-medium-plus",
|
||||
"transition-all duration-200 ease-ease"
|
||||
)}
|
||||
>
|
||||
{title}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
isSoon &&
|
||||
"group-hover:text-medusa-fg-disabled dark:group-hover:text-medusa-fg-disabled-dark",
|
||||
"transition-all duration-200 ease-ease text-medium"
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
{href && (
|
||||
<Link
|
||||
href={href}
|
||||
className={clsx(
|
||||
"absolute top-0 left-0 w-full h-full z-[4] rounded",
|
||||
isSoon && "group-hover:pointer-events-none"
|
||||
)}
|
||||
></Link>
|
||||
)}
|
||||
</article>
|
||||
)
|
||||
}
|
||||
|
||||
export default LargeCard
|
||||
30
www/apps/docs/src/components/LargeCardList/index.tsx
Normal file
30
www/apps/docs/src/components/LargeCardList/index.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React from "react"
|
||||
import clsx from "clsx"
|
||||
|
||||
type LargeCardListProps = {
|
||||
colSize?: string
|
||||
className?: string
|
||||
} & React.HTMLAttributes<HTMLDivElement>
|
||||
|
||||
const LargeCardList: React.FC<LargeCardListProps> = ({
|
||||
colSize = "4",
|
||||
className,
|
||||
children,
|
||||
}) => {
|
||||
return (
|
||||
<section
|
||||
className={clsx(
|
||||
"cards-grid",
|
||||
`grid-${colSize}`,
|
||||
"gap-1",
|
||||
"[&+*:not(.large-card)]:mt-2",
|
||||
"[&+.large-card]:mt-1",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default LargeCardList
|
||||
42
www/apps/docs/src/components/LearningPath/Finish/index.tsx
Normal file
42
www/apps/docs/src/components/LearningPath/Finish/index.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { LearningPathStepType } from "@site/src/providers/LearningPath"
|
||||
import { Rating } from "docs-ui"
|
||||
import React from "react"
|
||||
|
||||
export type LearningPathFinishType =
|
||||
| {
|
||||
type: "rating"
|
||||
step: Omit<LearningPathStepType, "descriptionJSX"> & {
|
||||
eventName?: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
type: "custom"
|
||||
step: LearningPathStepType & {
|
||||
descriptionJSX: JSX.Element
|
||||
}
|
||||
}
|
||||
|
||||
type LearningPathFinishProps = LearningPathFinishType & {
|
||||
onRating?: () => void
|
||||
}
|
||||
|
||||
const LearningPathFinish: React.FC<LearningPathFinishProps> = ({
|
||||
type,
|
||||
step,
|
||||
onRating,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
{type === "rating" && (
|
||||
<Rating event={step.eventName} onRating={onRating} />
|
||||
)}
|
||||
{type === "custom" && (
|
||||
<span className="text-compact-small text-medusa-fg-subtle dark:text-medusa-fg-subtle-dark">
|
||||
{step.descriptionJSX}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default LearningPathFinish
|
||||
30
www/apps/docs/src/components/LearningPath/Icon/index.tsx
Normal file
30
www/apps/docs/src/components/LearningPath/Icon/index.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import useBaseUrl from "@docusaurus/useBaseUrl"
|
||||
import clsx from "clsx"
|
||||
import React from "react"
|
||||
|
||||
type LearningPathIconProps = {
|
||||
className?: string
|
||||
imgClassName?: string
|
||||
} & React.AllHTMLAttributes<HTMLDivElement>
|
||||
|
||||
const LearningPathIcon: React.FC<LearningPathIconProps> = ({
|
||||
className = "",
|
||||
imgClassName = "",
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"rounded-full shadow-card-rest dark:shadow-card-rest-dark w-3 h-3 bg-medusa-bg-base dark:bg-medusa-bg-base-dark",
|
||||
"flex justify-center items-center flex-none",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<img
|
||||
src={useBaseUrl("/img/learning-path-img.png")}
|
||||
className={clsx("rounded-full w-2.5 h-2.5 no-zoom-img", imgClassName)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LearningPathIcon
|
||||
35
www/apps/docs/src/components/LearningPath/List/index.tsx
Normal file
35
www/apps/docs/src/components/LearningPath/List/index.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import React, { useMemo } from "react"
|
||||
import { getLearningPaths } from "../../../utils/learning-paths"
|
||||
import LearningPath from ".."
|
||||
|
||||
type LearningPathListProps = {
|
||||
ignore?: string[]
|
||||
} & React.AllHTMLAttributes<HTMLDivElement>
|
||||
|
||||
const LearningPathList: React.FC<LearningPathListProps> = ({ ignore = [] }) => {
|
||||
const paths = useMemo(() => {
|
||||
const paths = getLearningPaths()
|
||||
ignore.forEach((pathName) => {
|
||||
const pathIndex = paths.findIndex((path) => path.name === pathName)
|
||||
if (pathIndex !== -1) {
|
||||
paths.splice(pathIndex, 1)
|
||||
}
|
||||
})
|
||||
|
||||
return paths
|
||||
}, [ignore])
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-wrap gap-2 mt-1.5">
|
||||
{paths.map((path, index) => (
|
||||
<LearningPath
|
||||
pathName={path.name}
|
||||
key={index}
|
||||
className="!mt-0 !mb-0"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LearningPathList
|
||||
@@ -0,0 +1,41 @@
|
||||
import React from "react"
|
||||
import { useLearningPath } from "../../../../providers/LearningPath"
|
||||
import { Button } from "docs-ui"
|
||||
|
||||
type LearningPathStepActionsType = {
|
||||
onFinish?: () => void
|
||||
onClose?: () => void
|
||||
} & React.AllHTMLAttributes<HTMLDivElement>
|
||||
|
||||
const LearningPathStepActions: React.FC<LearningPathStepActionsType> = ({
|
||||
onFinish,
|
||||
onClose,
|
||||
}) => {
|
||||
const { hasNextStep, nextStep, endPath } = useLearningPath()
|
||||
|
||||
const handleFinish = () => {
|
||||
if (onFinish) {
|
||||
onFinish()
|
||||
} else {
|
||||
endPath()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex gap-0.5 p-1 justify-end items-center">
|
||||
<Button onClick={onClose}>Close</Button>
|
||||
{hasNextStep() && (
|
||||
<Button onClick={nextStep} variant="primary">
|
||||
Next
|
||||
</Button>
|
||||
)}
|
||||
{!hasNextStep() && (
|
||||
<Button onClick={handleFinish} variant="primary">
|
||||
Finish
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LearningPathStepActions
|
||||
83
www/apps/docs/src/components/LearningPath/Steps/index.tsx
Normal file
83
www/apps/docs/src/components/LearningPath/Steps/index.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { useLearningPath } from "@site/src/providers/LearningPath"
|
||||
import React from "react"
|
||||
import LearningPathStepActions from "./Actions"
|
||||
import clsx from "clsx"
|
||||
import IconCircleDottedLine from "@site/src/theme/Icon/CircleDottedLine"
|
||||
import Link from "@docusaurus/Link"
|
||||
import { CheckCircleSolid, CircleMiniSolid } from "@medusajs/icons"
|
||||
|
||||
type LearningPathStepsProps = {
|
||||
onFinish?: () => void
|
||||
onClose?: () => void
|
||||
}
|
||||
|
||||
const LearningPathSteps: React.FC<LearningPathStepsProps> = ({ ...rest }) => {
|
||||
const { path, currentStep, goToStep } = useLearningPath()
|
||||
|
||||
if (!path) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="overflow-auto basis-3/4">
|
||||
{path.steps.map((step, index) => (
|
||||
<div
|
||||
className={clsx(
|
||||
"border-0 border-b border-solid border-medusa-border-base dark:border-medusa-border-base-dark",
|
||||
"relative p-1"
|
||||
)}
|
||||
key={index}
|
||||
>
|
||||
<div className={clsx("flex items-center gap-1")}>
|
||||
<div className="w-2 flex-none flex items-center justify-center">
|
||||
{index === currentStep && (
|
||||
<IconCircleDottedLine
|
||||
className={clsx(
|
||||
"shadow-active dark:shadow-active-dark rounded-full",
|
||||
"text-ui-fg-interactive"
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{index < currentStep && (
|
||||
<CheckCircleSolid className="text-ui-fg-interactive" />
|
||||
)}
|
||||
{index > currentStep && (
|
||||
<CircleMiniSolid className="text-ui-fg-subtle" />
|
||||
)}
|
||||
</div>
|
||||
<span
|
||||
className={clsx(
|
||||
"text-compact-medium-plus text-medusa-fg-base dark:text-medusa-fg-base-dark"
|
||||
)}
|
||||
>
|
||||
{step.title}
|
||||
</span>
|
||||
</div>
|
||||
{index === currentStep && (
|
||||
<div className={clsx("flex items-center gap-1")}>
|
||||
<div className="w-2 flex-none"></div>
|
||||
<div className={clsx("text-medium text-ui-fg-subtle mt-1")}>
|
||||
{step.descriptionJSX ?? step.description}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{index < currentStep && (
|
||||
<Link
|
||||
href={step.path}
|
||||
className={clsx("absolute top-0 left-0 w-full h-full")}
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
goToStep(index)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<LearningPathStepActions {...rest} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default LearningPathSteps
|
||||
92
www/apps/docs/src/components/LearningPath/index.tsx
Normal file
92
www/apps/docs/src/components/LearningPath/index.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import clsx from "clsx"
|
||||
import React from "react"
|
||||
import LearningPathIcon from "./Icon"
|
||||
import { getLearningPath } from "../../utils/learning-paths"
|
||||
import { useLearningPath } from "../../providers/LearningPath"
|
||||
import { Button, useNotifications } from "docs-ui"
|
||||
import { CircleMiniSolid } from "@medusajs/icons"
|
||||
|
||||
type LearningPathProps = {
|
||||
pathName: string
|
||||
className?: string
|
||||
} & React.AllHTMLAttributes<HTMLDivElement>
|
||||
|
||||
const LearningPath: React.FC<LearningPathProps> = ({
|
||||
pathName,
|
||||
className = "",
|
||||
}) => {
|
||||
const path = getLearningPath(pathName)
|
||||
if (!path) {
|
||||
throw new Error(`Learning path ${pathName} does not exist.`)
|
||||
}
|
||||
const { startPath, path: currentPath } = useLearningPath()
|
||||
const notificationContext = useNotifications()
|
||||
|
||||
const handleClick = () => {
|
||||
if (notificationContext && currentPath?.notificationId) {
|
||||
notificationContext.removeNotification(currentPath.notificationId)
|
||||
}
|
||||
startPath(path)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"rounded shadow-card-rest dark:shadow-card-rest-dark bg-docs-bg-surface dark:bg-docs-bg-surface-dark mt-1.5 mb-4",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
"flex items-center gap-1 p-1 border-0 border-b border-solid border-medusa-border-base dark:border-medusa-border-base-dark"
|
||||
)}
|
||||
>
|
||||
<LearningPathIcon />
|
||||
<div className={clsx("basis-3/4")}>
|
||||
<span
|
||||
className={clsx(
|
||||
"text-medusa-fg-base dark:text-medusa-fg-base-dark text-compact-large-plus block"
|
||||
)}
|
||||
>
|
||||
{path.label}
|
||||
</span>
|
||||
{path.description && (
|
||||
<span
|
||||
className={clsx(
|
||||
"text-medusa-fg-subtle dark:text-medusa-fg-subtle-dark text-compact-medium mt-0.25 inline-block"
|
||||
)}
|
||||
>
|
||||
{path.description}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<Button onClick={handleClick} className={clsx("basis-1/4 max-w-fit")}>
|
||||
Start Path
|
||||
</Button>
|
||||
</div>
|
||||
{path.steps.map((step, index) => (
|
||||
<div
|
||||
className={clsx(
|
||||
"flex items-center p-1 gap-1",
|
||||
index !== path.steps.length - 1 &&
|
||||
"border-0 border-b border-solid border-medusa-border-base dark:border-medusa-border-base-dark"
|
||||
)}
|
||||
key={index}
|
||||
>
|
||||
<div className={clsx("w-3 flex items-center justify-center")}>
|
||||
<CircleMiniSolid className="text-medusa-fg-muted dark:text-medusa-fg-muted-dark" />
|
||||
</div>
|
||||
<span
|
||||
className={clsx(
|
||||
"text-medusa-fg-base dark:text-medusa-fg-base-dark text-compact-medium-plus"
|
||||
)}
|
||||
>
|
||||
{step.title}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LearningPath
|
||||
79
www/apps/docs/src/components/Navbar/Actions/index.tsx
Normal file
79
www/apps/docs/src/components/Navbar/Actions/index.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import React from "react"
|
||||
import { NavbarAction } from "@medusajs/docs"
|
||||
import Icon from "../../../theme/Icon"
|
||||
import clsx from "clsx"
|
||||
import { Button, Tooltip } from "docs-ui"
|
||||
|
||||
type NavbarActionsProps = {
|
||||
items: NavbarAction[]
|
||||
className?: string
|
||||
} & React.HTMLAttributes<HTMLDivElement>
|
||||
|
||||
const NavbarActions: React.FC<NavbarActionsProps> = ({
|
||||
items = [],
|
||||
className = "",
|
||||
}) => {
|
||||
return (
|
||||
<div className={clsx("lg:block hidden", className)}>
|
||||
{items.map((item, index) => {
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const ItemIconElm = item.Icon
|
||||
const ItemIcon = item.icon ? Icon[item.icon] : null
|
||||
switch (item.type) {
|
||||
case "link":
|
||||
return (
|
||||
<Tooltip
|
||||
text={item.title}
|
||||
html={item.html}
|
||||
key={index}
|
||||
tooltipClassName="!text-compact-x-small-plus"
|
||||
>
|
||||
<a
|
||||
href={item.href}
|
||||
title={item.title}
|
||||
className={clsx(
|
||||
(ItemIcon || ItemIconElm) && "navbar-action-icon-item",
|
||||
item.className
|
||||
)}
|
||||
>
|
||||
{item.label}
|
||||
{ItemIconElm}
|
||||
{ItemIcon && <ItemIcon />}
|
||||
</a>
|
||||
</Tooltip>
|
||||
)
|
||||
case "button":
|
||||
return (
|
||||
<Tooltip
|
||||
text={item.title}
|
||||
html={item.html}
|
||||
key={index}
|
||||
tooltipClassName="!text-compact-x-small-plus"
|
||||
>
|
||||
<Button
|
||||
className={clsx(item.href && "relative", item.className)}
|
||||
variant={item.variant || "secondary"}
|
||||
buttonType={item.buttonType || "default"}
|
||||
{...item.events}
|
||||
>
|
||||
{item.label}
|
||||
{ItemIconElm}
|
||||
{ItemIcon && <ItemIcon />}
|
||||
{item.href && (
|
||||
<a
|
||||
href={item.href}
|
||||
className="absolute top-0 left-0 w-full h-full"
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)
|
||||
default:
|
||||
return <></>
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default NavbarActions
|
||||
29
www/apps/docs/src/components/QueryNote/index.tsx
Normal file
29
www/apps/docs/src/components/QueryNote/index.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import Admonition, { Props as AdmonitionProps } from "@theme/Admonition"
|
||||
import { useQueryStringValue } from "@docusaurus/theme-common/internal"
|
||||
import React from "react"
|
||||
|
||||
type QueryNoteProps = {
|
||||
query: {
|
||||
key: string
|
||||
value?: string
|
||||
}
|
||||
admonition: AdmonitionProps
|
||||
} & React.HTMLAttributes<HTMLDivElement>
|
||||
|
||||
const QueryNote: React.FC<QueryNoteProps> = ({
|
||||
query: { key, value = "" },
|
||||
admonition,
|
||||
children,
|
||||
}) => {
|
||||
const queryValue = useQueryStringValue(key)
|
||||
|
||||
return (
|
||||
<>
|
||||
{queryValue === value && (
|
||||
<Admonition {...admonition}>{children}</Admonition>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default QueryNote
|
||||
44
www/apps/docs/src/components/StructuredData/HowTo/index.tsx
Normal file
44
www/apps/docs/src/components/StructuredData/HowTo/index.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React from "react"
|
||||
import Head from "@docusaurus/Head"
|
||||
import { useLocation } from "@docusaurus/router"
|
||||
import useDocusaurusContext from "@docusaurus/useDocusaurusContext"
|
||||
import type { TOCItem } from "@docusaurus/mdx-loader"
|
||||
|
||||
type StructuredDataHowToProps = {
|
||||
toc: readonly TOCItem[]
|
||||
title: string
|
||||
}
|
||||
|
||||
const StructuredDataHowTo: React.FC<StructuredDataHowToProps> = ({
|
||||
toc,
|
||||
title,
|
||||
}) => {
|
||||
const location = useLocation()
|
||||
const {
|
||||
siteConfig: { url },
|
||||
} = useDocusaurusContext()
|
||||
const mainUrl = `${url}/${location.pathname}`
|
||||
|
||||
return (
|
||||
<Head>
|
||||
<script type="application/ld+json">
|
||||
{JSON.stringify({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "HowTo",
|
||||
name: title,
|
||||
step: [
|
||||
toc
|
||||
.filter((item) => item.level === 2)
|
||||
.map((item) => ({
|
||||
"@type": "HowToStep",
|
||||
text: item.value,
|
||||
url: `${mainUrl}#${item.id}`,
|
||||
})),
|
||||
],
|
||||
})}
|
||||
</script>
|
||||
</Head>
|
||||
)
|
||||
}
|
||||
|
||||
export default StructuredDataHowTo
|
||||
30
www/apps/docs/src/components/Troubleshooting/index.tsx
Normal file
30
www/apps/docs/src/components/Troubleshooting/index.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React from "react"
|
||||
import Details from "../../theme/Details"
|
||||
import clsx from "clsx"
|
||||
|
||||
type TroubleshootingSection = {
|
||||
title: string
|
||||
content: React.ReactNode
|
||||
}
|
||||
|
||||
type TroubleshootingProps = {
|
||||
sections: TroubleshootingSection[]
|
||||
} & React.AllHTMLAttributes<HTMLDivElement>
|
||||
|
||||
const Troubleshooting: React.FC<TroubleshootingProps> = ({ sections }) => {
|
||||
return (
|
||||
<>
|
||||
{sections.map(({ title, content }, index) => (
|
||||
<Details
|
||||
summary={title}
|
||||
key={index}
|
||||
className={clsx(index !== 0 && "border-t-0")}
|
||||
>
|
||||
{content}
|
||||
</Details>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Troubleshooting
|
||||
27
www/apps/docs/src/components/UiIcon/index.tsx
Normal file
27
www/apps/docs/src/components/UiIcon/index.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import React from "react"
|
||||
import ThemedImage from "@theme/ThemedImage"
|
||||
|
||||
type UiIconProps = {
|
||||
lightIcon: string
|
||||
darkIcon?: string
|
||||
alt?: string
|
||||
}
|
||||
|
||||
const UiIcon: React.FC<UiIconProps> = ({
|
||||
lightIcon,
|
||||
darkIcon = "",
|
||||
alt = "",
|
||||
}) => {
|
||||
return (
|
||||
<ThemedImage
|
||||
alt={alt}
|
||||
sources={{
|
||||
light: lightIcon,
|
||||
dark: darkIcon || lightIcon,
|
||||
}}
|
||||
className="align-sub w-[20px] h-[20px]"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default UiIcon
|
||||
287
www/apps/docs/src/css/_docusaurus.css
Normal file
287
www/apps/docs/src/css/_docusaurus.css
Normal file
@@ -0,0 +1,287 @@
|
||||
/** This CSS file includes tailwind styling definitions for classes defined by docusaurus **/
|
||||
html,
|
||||
body {
|
||||
@apply h-full;
|
||||
}
|
||||
|
||||
.cards-grid {
|
||||
@apply grid gap-x-1 auto-rows-fr;
|
||||
}
|
||||
|
||||
.cards-grid.grid-6 {
|
||||
@apply md:grid-cols-2 grid-cols-1;
|
||||
}
|
||||
|
||||
.cards-grid.grid-4 {
|
||||
@apply lg:grid-cols-3 md:grid-cols-2 grid-cols-1;
|
||||
}
|
||||
|
||||
h1 + .cards-grid,
|
||||
h1 + .card-wrapper,
|
||||
h2 + .cards-grid,
|
||||
h2 + .card-wrapper,
|
||||
h3 + .cards-grid,
|
||||
h3 + .card-wrapper {
|
||||
@apply mt-1.5;
|
||||
}
|
||||
|
||||
.markdown p + img,
|
||||
.markdown .alert,
|
||||
.markdown .code-wrapper {
|
||||
@apply mb-2;
|
||||
}
|
||||
|
||||
.markdown h2,
|
||||
.markdown h3,
|
||||
.markdown p + .code-wrapper,
|
||||
.markdown p + .tabs-wrapper,
|
||||
.markdown p + .alert,
|
||||
.markdown p + section,
|
||||
video {
|
||||
@apply mt-2;
|
||||
}
|
||||
|
||||
.theme-code-block {
|
||||
@apply border border-solid border-medusa-code-border dark:border-medusa-code-border-dark;
|
||||
}
|
||||
|
||||
.markdown p + .card-wrapper {
|
||||
@apply mt-1.5;
|
||||
}
|
||||
|
||||
.card + p {
|
||||
@apply mt-2;
|
||||
}
|
||||
|
||||
.card-highlighted {
|
||||
|
||||
@apply relative mb-4 bg-medusa-bg-subtle dark:bg-medusa-bg-base-dark hover:bg-medusa-bg-subtle-hover dark:hover:bg-medusa-bg-subtle-hover-dark;
|
||||
}
|
||||
|
||||
.col.toc-wrapper {
|
||||
--ifm-col-width: var(--ifm-toc-width);
|
||||
}
|
||||
|
||||
.markdown-doc-wrapper--fluid {
|
||||
@apply max-w-[inherit];
|
||||
}
|
||||
|
||||
:global(.docusaurus-mt-lg) {
|
||||
@apply mt-3;
|
||||
}
|
||||
|
||||
:global(#__docusaurus) {
|
||||
@apply min-h-full flex flex-col;
|
||||
}
|
||||
|
||||
.code-tabs .theme-code-block {
|
||||
@apply !rounded-t-none !rounded-b
|
||||
}
|
||||
|
||||
[class*=codeLineNumber] {
|
||||
@apply text-medusa-code-text-subtle dark:text-medusa-code-text-subtle-dark !pl-0;
|
||||
}
|
||||
|
||||
.prism-code {
|
||||
@apply xs:max-w-[90%] text-code-body [&_*]:text-code-body xs:after:content-[''] xs:after:rounded xs:after:absolute;
|
||||
@apply xs:after:right-0 xs:after:top-0 xs:after:w-[calc(10%+24px)] xs:after:h-full xs:after:bg-code-fade dark:xs:after:bg-code-fade-dark;
|
||||
}
|
||||
|
||||
.prism-code:not(:hover)::-webkit-scrollbar-thumb,
|
||||
.prism-code:not(:hover)::-webkit-scrollbar-track {
|
||||
@apply xs:invisible;
|
||||
}
|
||||
|
||||
.prism-code:hover::-webkit-scrollbar-thumb,
|
||||
.prism-code:hover::-webkit-scrollbar-track {
|
||||
@apply xs:opacity-100
|
||||
}
|
||||
|
||||
.prism-code {
|
||||
@apply bg-transparent break-words !outline-none;
|
||||
}
|
||||
|
||||
.prism-code div {
|
||||
@apply !outline-none focus:!outline-none active:!outline-none;
|
||||
}
|
||||
|
||||
.code-tabs {
|
||||
@apply relative;
|
||||
}
|
||||
|
||||
.sidebar-desktop nav {
|
||||
--ifm-scrollbar-track-background-color: transparent !important;
|
||||
--ifm-scrollbar-thumb-background-color: transparent !important;
|
||||
--ifm-scrollbar-thumb-hover-background-color: transparent !important;
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
@apply max-w-xl mx-auto xl:min-w-xl w-full;
|
||||
}
|
||||
|
||||
.theme-doc-breadcrumbs {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
article {
|
||||
@apply max-w-[960px];
|
||||
}
|
||||
|
||||
.padding-top--md {
|
||||
@apply !pt-3;
|
||||
}
|
||||
|
||||
.margin-bottom--lg {
|
||||
@apply !mb-1;
|
||||
}
|
||||
|
||||
details summary {
|
||||
@apply cursor-pointer;
|
||||
}
|
||||
|
||||
.reference-table {
|
||||
@apply !table table-fixed w-full;
|
||||
}
|
||||
|
||||
.reference-table p {
|
||||
--ifm-paragraph-margin-bottom: 0;
|
||||
}
|
||||
|
||||
.reference-table.table-col-4 th,
|
||||
.reference-table.table-col-4 td {
|
||||
@apply [&:nth-child(1)]:!w-1/5 [&:nth-child(2)]:!w-1/5 [&:nth-child(3)]:!w-2/5 [&:nth-child(4)]:!w-1/5;
|
||||
}
|
||||
|
||||
.reference-table th:nth-child(1),
|
||||
.reference-table td:nth-child(1) {
|
||||
@apply [&:nth-child(1)]:!w-[30%] [&:nth-child(2)]:!w-[30%] [&:nth-child(3)]:!w-2/5;
|
||||
}
|
||||
|
||||
.reference-table .tooltip-container code {
|
||||
@apply whitespace-nowrap;
|
||||
}
|
||||
|
||||
.reference-table .theme-code-block span {
|
||||
@apply !max-w-full !break-words !whitespace-break-spaces;
|
||||
}
|
||||
|
||||
.theme-code-block {
|
||||
@apply !bg-medusa-code-bg-base dark:!bg-medusa-code-bg-base-dark
|
||||
}
|
||||
|
||||
.reference-table .code-block-numbering {
|
||||
@apply !block;
|
||||
}
|
||||
|
||||
.container {
|
||||
@apply !pt-3 !max-w-full;
|
||||
}
|
||||
|
||||
.pagination-nav {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
.theme-doc-footer {
|
||||
@apply !mt-0 border-0 !border-t border-solid !border-medusa-border-base dark:!border-medusa-border-base-dark;
|
||||
@apply pt-2;
|
||||
}
|
||||
|
||||
.theme-last-updated {
|
||||
@apply font-normal not-italic text-compact-small-plus;
|
||||
}
|
||||
|
||||
.theme-last-updated,
|
||||
.theme-last-updated b {
|
||||
@apply !font-normal;
|
||||
}
|
||||
|
||||
.medium-zoom-overlay {
|
||||
@apply z-[400];
|
||||
}
|
||||
|
||||
.medium-zoom-image--opened {
|
||||
@apply z-[400];
|
||||
}
|
||||
|
||||
details > div {
|
||||
--docusaurus-details-decoration-color: transparent !important;
|
||||
}
|
||||
|
||||
.row--justify-end {
|
||||
@apply justify-end;
|
||||
}
|
||||
|
||||
.docs-page-container {
|
||||
@apply !px-0;
|
||||
}
|
||||
|
||||
.search-result-match {
|
||||
@apply text-medusa-fg-base dark:text-medusa-fg-base-dark;
|
||||
@apply bg-medusa-bg-highlight dark:bg-medusa-bg-highlight-dark py-0.25 px-0;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
@apply z-[399] p-0 border-0 border-b border-solid border-medusa-border-base dark:border-medusa-border-base-dark;
|
||||
}
|
||||
|
||||
html:not(.plugin-redoc) .navbar:not(.navbar-sidebar--show) {
|
||||
@apply supports-[backdrop-filter]:bg-transparent supports-[backdrop-filter]:backdrop-blur-md;
|
||||
}
|
||||
|
||||
.navbar__link {
|
||||
@apply text-compact-small-plus;
|
||||
}
|
||||
|
||||
.navbar__brand {
|
||||
@apply mr-0;
|
||||
}
|
||||
|
||||
.navbar__logo {
|
||||
@apply w-[82px] h-[20px] lg:w-[20px] lg:h-[20px] flex justify-center items-center !mr-0;
|
||||
}
|
||||
|
||||
.navbar__item {
|
||||
@apply p-0 lg:!block !hidden;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@apply !text-h1;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@apply !text-h2;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@apply !text-h3;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
@apply text-medusa-fg-base dark:text-medusa-fg-base-dark;
|
||||
}
|
||||
|
||||
.markdown > h1:first-child,
|
||||
.markdown > h2,
|
||||
.markdown > h3 {
|
||||
@apply mb-0.5;
|
||||
}
|
||||
|
||||
.markdown > p {
|
||||
@apply mb-0.5;
|
||||
}
|
||||
|
||||
.markdown > p img {
|
||||
@apply mt-0.5;
|
||||
}
|
||||
|
||||
.markdown {
|
||||
@apply mb-0;
|
||||
}
|
||||
|
||||
*::selection {
|
||||
@apply bg-medusa-bg-highlight dark:bg-medusa-bg-highlight-dark;
|
||||
}
|
||||
|
||||
.prism-code *::selection, .code-header::selection {
|
||||
@apply bg-medusa-code-text-highlight;
|
||||
}
|
||||
208
www/apps/docs/src/css/_variables.css
Normal file
208
www/apps/docs/src/css/_variables.css
Normal file
@@ -0,0 +1,208 @@
|
||||
/* You can override the default Infima variables here. */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono&display=swap');
|
||||
|
||||
:root {
|
||||
/* Base Styles */
|
||||
--ifm-hr-margin-vertical: theme(margin.2);
|
||||
--ifm-leading: theme(lineHeight.DEFAULT);
|
||||
--ifm-list-left-padding: theme(spacing.1);
|
||||
--font-inter: "Inter";
|
||||
--font-roboto-mono: "Roboto Mono";
|
||||
|
||||
/* Colors */
|
||||
--ifm-color-primary: theme(colors.medusa.fg.base.DEFAULT);
|
||||
--ifm-background-color: theme(colors.medusa.bg.base.DEFAULT);
|
||||
--ifm-background-surface-color: theme(colors.medusa.bg.subtle.DEFAULT);
|
||||
--ifm-color-content: theme(colors.medusa.fg.subtle.DEFAULT) !important;
|
||||
--ifm-color-content-secondary: var(--ifm-color-content);
|
||||
--ifm-base-border-color: theme(colors.medusa.border.base.DEFAULT);
|
||||
--ifm-strong-border-color: theme(colors.medusa.border.strong.DEFAULT);
|
||||
--ifm-hr-background-color: theme(colors.medusa.border.base.DEFAULT);
|
||||
|
||||
/* Fonts */
|
||||
--ifm-code-font-size: theme(fontSize.code-label);
|
||||
--ifm-font-family-base: theme(fontFamily.base);
|
||||
--ifm-font-family-monospace: theme(fontFamily.monospace);
|
||||
--ifm-font-size-base: theme(fontSize.medium[0]);
|
||||
--ifm-line-height-base: theme(fontSize.medium[1].lineHeight);
|
||||
--ifm-font-weight-base: theme(fontSize.medium[1].fontWeight);
|
||||
|
||||
/* Headings */
|
||||
/** Due to how docusaurus styles headings, the styles are applied on h1, h2, and h3 again in _docusaurus **/
|
||||
--ifm-color-headers: var(--ifm-color-primary);
|
||||
--ifm-h1-font-size: theme(fontSize.h1[0]);
|
||||
--ifm-h1-line-height: theme(fontSize.h1[1].lineHeight);
|
||||
--ifm-h1-weight: theme(fontSize.h1[1].fontWeight);
|
||||
--ifm-h2-font-size: theme(fontSize.h2[0]);
|
||||
--ifm-h2-line-height: theme(fontSize.h2[1].lineHeight);
|
||||
--ifm-h2-weight: theme(fontSize.h2[1].fontWeight);
|
||||
--ifm-h3-font-size: theme(fontSize.h3[0]);
|
||||
--ifm-h3-line-height: theme(fontSize.h3[1].lineHeight);
|
||||
--ifm-h3-weight: theme(fontSize.h3[1].fontWeight);
|
||||
--ifm-h4-font-size: var(--ifm-font-size-base);
|
||||
|
||||
/* Links */
|
||||
--ifm-link-color: theme(colors.medusa.fg.interactive.DEFAULT);
|
||||
--ifm-link-hover-color: theme(colors.medusa.fg.interactive.hover.DEFAULT);
|
||||
--ifm-link-decoration: none;
|
||||
--ifm-link-hover-decoration: none;
|
||||
|
||||
/* Sidebar */
|
||||
--ifm-menu-link-padding-vertical: 6px;
|
||||
--ifm-menu-link-padding-horizontal: theme(margin.1);
|
||||
--ifm-menu-color: theme(colors.medusa.fg.subtle.DEFAULT);
|
||||
--ifm-menu-color-active: theme(colors.medusa.fg.base.DEFAULT);
|
||||
--ifm-menu-color-background-hover: theme(colors.medusa.bg.base.hover.DEFAULT);
|
||||
--ifm-menu-color-background-active: theme(colors.medusa.bg.base.pressed.DEFAULT);
|
||||
|
||||
/* Toc */
|
||||
--ifm-toc-border-color: theme(colors.medusa.border.base.DEFAULT);
|
||||
--ifm-toc-link-color: theme(colors.medusa.fg.subtle.DEFAULT);
|
||||
--ifm-toc-padding-horizontal: theme(padding.1);
|
||||
|
||||
/* Navbar */
|
||||
--ifm-navbar-background-color: var(--ifm-background-color);
|
||||
--ifm-navbar-shadow: none;
|
||||
--ifm-navbar-padding-vertical: 12px;
|
||||
--ifm-navbar-padding-horizontal: theme(padding[1.5]);
|
||||
--ifm-navbar-item-padding-vertical: 6px;
|
||||
--ifm-navbar-item-padding-horizontal: theme(padding[1]);
|
||||
--ifm-navbar-height: theme(height.navbar);
|
||||
--ifm-navbar-link-hover-color: theme(colors.medusa.fg.base.DEFAULT);
|
||||
--ifm-navbar-link-color: theme(colors.medusa.fg.subtle.DEFAULT);
|
||||
|
||||
/* Inline Code */
|
||||
--ifm-code-border-radius: theme(borderRadius.DEFAULT);
|
||||
--ifm-code-padding-horizontal: theme(padding[0.5]);
|
||||
--ifm-code-background: theme(colors.medusa.tag.neutral.bg.DEFAULT) !important;
|
||||
|
||||
/* Code Blocks */
|
||||
--ifm-pre-background: theme(colors.medusa.code.bg.base.DEFAULT);
|
||||
--ifm-pre-padding: theme(padding.1);
|
||||
|
||||
/* Tabs */
|
||||
--ifm-tabs-color-active: var(--ifm-color-primary);
|
||||
|
||||
/* Tooltip */
|
||||
--rt-opacity: theme(opacity.100) !important;
|
||||
--rt-color-dark: theme(colors.medusa.bg.base.DEFAULT) !important;
|
||||
--rt-color-white: var(--ifm-color-content) !important;
|
||||
|
||||
/* Footer */
|
||||
--ifm-footer-color: theme(colors.medusa.fg.muted.DEFAULT);
|
||||
--ifm-footer-background-color: transparent;
|
||||
--ifm-footer-padding-horizontal: 0;
|
||||
--ifm-footer-link-color: var(--ifm-color-content);
|
||||
|
||||
/* Search */
|
||||
--docsearch-searchbox-background: theme(colors.medusa.bg.subtle.DEFAULT) !important;
|
||||
--docsearch-searchbox-focus-background: theme(colors.medusa.bg.subtle.hover.DEFAULT) !important;
|
||||
--docsearch-searchbox-shadow: none !important;
|
||||
--docsearch-modal-height: 472px !important;
|
||||
--docsearch-modal-background: theme(colors.medusa.bg.base.DEFAULT) !important;
|
||||
--docsearch-modal-shadow: theme(boxShadow.modal) !important;
|
||||
--docsearch-container-background: theme(colors.medusa.bg.overlay.DEFAULT) !important;
|
||||
--docsearch-key-gradient: theme(colors.medusa.tag.neutral.bg.DEFAULT) !important;
|
||||
--docsearch-muted-color: theme(colors.medusa.fg.subtle.DEFAULT) !important;
|
||||
--docsearch-spacing: 12px theme(spacing[1.5]) !important;
|
||||
--docsearch-highlight-color: theme(colors.medusa.fg.muted.DEFAULT) !important;
|
||||
--docsearch-text-color: var(--ifm-color-primary) !important;
|
||||
--docsearch-hit-background: var(--docsearch-modal-background) !important;
|
||||
--docsearch-hit-height: auto !important;
|
||||
--docsearch-hit-active-color: var(--docsearch-text-color) !important;
|
||||
--docsearch-footer-height: 40px !important;
|
||||
|
||||
/* Announcement Bar */
|
||||
--docusaurus-announcement-bar-height: auto !important;
|
||||
|
||||
/* Tables */
|
||||
--ifm-table-border-color: theme(colors.medusa.border.base.DEFAULT);
|
||||
--ifm-table-head-background: var(--ifm-background-surface-color);
|
||||
--ifm-table-head-color: var(--ifm-color-primary);
|
||||
--ifm-table-head-font-weight: theme(fontSize.medium-plus[1].fontWeight);
|
||||
--ifm-table-stripe-background: var(--ifm-background-surface-color) !important;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] {
|
||||
/* Colors */
|
||||
--ifm-background-color: theme(colors.medusa.bg.subtle.dark);
|
||||
--ifm-background-surface-color: theme(colors.medusa.bg.base.dark);
|
||||
--ifm-hr-background-color: theme(colors.medusa.border.base.dark);
|
||||
--ifm-color-primary: theme(colors.medusa.fg.base.dark);
|
||||
--ifm-color-content: theme(colors.medusa.fg.subtle.dark) !important;
|
||||
--ifm-base-border-color: theme(colors.medusa.border.base.dark);
|
||||
--ifm-strong-border-color: theme(colors.medusa.border.strong.dark);
|
||||
|
||||
/* Links */
|
||||
--ifm-link-color: theme(colors.medusa.fg.interactive.DEFAULT);
|
||||
--ifm-link-hover-color: theme(colors.medusa.fg.interactive.hover.DEFAULT);
|
||||
|
||||
/* Sidebar */
|
||||
--ifm-menu-color: theme(colors.medusa.fg.subtle.dark);
|
||||
--ifm-menu-color-active: theme(colors.medusa.fg.base.dark);
|
||||
--ifm-menu-color-background-hover: theme(colors.medusa.bg.subtle.hover.dark);
|
||||
--ifm-menu-color-background-active: theme(colors.medusa.bg.subtle.pressed.dark);
|
||||
|
||||
/* Toc */
|
||||
--ifm-toc-border-color: theme(colors.medusa.border.base.dark);
|
||||
--ifm-toc-link-color: theme(colors.medusa.fg.subtle.dark);
|
||||
|
||||
/* Navbar */
|
||||
--ifm-navbar-shadow: none;
|
||||
--ifm-navbar-link-hover-color: theme(colors.medusa.fg.base.dark);
|
||||
--ifm-navbar-link-color: theme(colors.medusa.fg.subtle.dark);
|
||||
|
||||
/* Inline Code */
|
||||
--ifm-code-background: theme(colors.medusa.tag.neutral.bg.dark) !important;
|
||||
|
||||
/* Code Blocks */
|
||||
--ifm-pre-background: theme(colors.medusa.code.bg.base.dark);
|
||||
|
||||
/* Tooltip */
|
||||
--rt-color-dark: theme(colors.medusa.bg.base.dark) !important;
|
||||
|
||||
/* Footer */
|
||||
--ifm-footer-color: theme(colors.medusa.fg.muted.dark);
|
||||
|
||||
/* Search */
|
||||
--docsearch-searchbox-background: theme(colors.medusa.bg.subtle.dark) !important;
|
||||
--docsearch-searchbox-focus-background: theme(colors.medusa.bg.subtle.hover.dark) !important;
|
||||
--docsearch-modal-background: theme(colors.medusa.bg.base.dark) !important;
|
||||
--docsearch-modal-shadow: theme(boxShadow.modal-dark) !important;
|
||||
--docsearch-container-background: theme(colors.medusa.bg.overlay.dark) !important;
|
||||
--docsearch-key-gradient: theme(colors.medusa.tag.neutral.bg.dark) !important;
|
||||
--docsearch-muted-color: theme(colors.medusa.fg.subtle.dark) !important;
|
||||
--docsearch-highlight-color: theme(colors.medusa.fg.muted.dark) !important;
|
||||
|
||||
/* Tables */
|
||||
--ifm-table-border-color: theme(colors.medusa.border.base.dark);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1441px) {
|
||||
:root {
|
||||
/** Table of Content **/
|
||||
--ifm-toc-width: calc(2 / 12 * 100%);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1440px) {
|
||||
:root {
|
||||
/** Table of Content **/
|
||||
--ifm-toc-width: calc(3 / 12 * 100%);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 997px) {
|
||||
:root {
|
||||
--docusaurus-announcement-bar-height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.alert {
|
||||
--ifm-code-background: theme(colors.medusa.tag.neutral.bg.DEFAULT) !important;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .alert {
|
||||
--ifm-code-background: theme(colors.medusa.tag.neutral.bg.dark) !important;
|
||||
}
|
||||
167
www/apps/docs/src/css/components/sidebar.css
Normal file
167
www/apps/docs/src/css/components/sidebar.css
Normal file
@@ -0,0 +1,167 @@
|
||||
.theme-doc-sidebar-container {
|
||||
--animate-duration: 0.2s;
|
||||
}
|
||||
|
||||
.sidebar-desktop nav {
|
||||
--ifm-scrollbar-track-background-color: transparent !important;
|
||||
--ifm-scrollbar-thumb-background-color: transparent !important;
|
||||
--ifm-scrollbar-thumb-hover-background-color: transparent !important;
|
||||
}
|
||||
|
||||
.menu__list-item:not(:first-child):not(.sidebar-title) {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.menu__list-item .menu__list:not(.theme-doc-sidebar-menu) [class*=theme-doc-sidebar-item-],
|
||||
.menu__list-item .menu__list:not(.theme-doc-sidebar-menu) [class*=theme-doc-sidebar-item-] .menu__link--active {
|
||||
@apply relative;
|
||||
}
|
||||
|
||||
.menu__list-item .menu__list:not(.theme-doc-sidebar-menu) [class*=theme-doc-sidebar-item-]:not(.theme-doc-sidebar-item-link-category)::before,
|
||||
.menu__list-item .menu__list:not(.theme-doc-sidebar-menu) .theme-doc-sidebar-item-category .menu__list-item-collapsible .menu__link--active::before,
|
||||
.menu__list-item .menu__list:not(.theme-doc-sidebar-menu) > .theme-doc-sidebar-item-link > .menu__link--active::before,
|
||||
.menu__list-item .menu__list:not(.theme-doc-sidebar-menu) .theme-doc-sidebar-item-category > .menu__list-item-collapsible::before {
|
||||
@apply content-[''] h-full w-[1px] absolute left-0.5 top-0 z-[1];
|
||||
}
|
||||
|
||||
.menu__list-item .menu__list:not(.theme-doc-sidebar-menu) [class*=theme-doc-sidebar-item-] .menu__link--active::before {
|
||||
@apply !h-[20px];
|
||||
}
|
||||
|
||||
.theme-doc-sidebar-item-category-level-1 > .menu__list > .menu__list-item:first-child::before,
|
||||
.menu__list-item .menu__list:not(.theme-doc-sidebar-menu) .theme-doc-sidebar-item-category:first-child .menu__list-item-collapsible::before,
|
||||
.theme-doc-sidebar-item-category-level-1 > .menu__list > .menu__list-item:last-child::before {
|
||||
@apply !h-[28px];
|
||||
}
|
||||
|
||||
.menu__list-item .menu__list:not(.theme-doc-sidebar-menu) .theme-doc-sidebar-item-link:first-child::before,
|
||||
.menu__list-item .menu__list:not(.theme-doc-sidebar-menu) .theme-doc-sidebar-item-category:first-child .menu__list-item-collapsible::before {
|
||||
@apply !bottom-0 !top-[unset];
|
||||
}
|
||||
|
||||
.menu__list-item .menu__list:not(.theme-doc-sidebar-menu) .theme-doc-sidebar-item-category:first-child .menu__list-item-collapsible {
|
||||
@apply !pb-[6px];
|
||||
}
|
||||
|
||||
.menu__list-item .menu__list:not(.theme-doc-sidebar-menu) .theme-doc-sidebar-item-category:first-child .menu__list-item-collapsible .menu__link {
|
||||
@apply !pb-0;
|
||||
}
|
||||
|
||||
.menu__list-item .menu__list:not(.theme-doc-sidebar-menu) [class*=theme-doc-sidebar-item-]::before,
|
||||
.menu__list-item .menu__list:not(.theme-doc-sidebar-menu) .theme-doc-sidebar-item-category:first-child .menu__list-item-collapsible::before {
|
||||
@apply bg-medusa-border-base dark:bg-medusa-border-base-dark;
|
||||
}
|
||||
|
||||
.theme-doc-sidebar-item-category-level-2 > .menu__list-item-collapsible .menu__link--active::before,
|
||||
.menu__list:not(.theme-doc-sidebar-menu) > .theme-doc-sidebar-item-link.theme-doc-sidebar-item-link-level-2 > .menu__link--active::before {
|
||||
@apply bg-medusa-fg-interactive dark:bg-medusa-fg-interactive-dark !z-[2] !top-[6px];
|
||||
}
|
||||
|
||||
.theme-doc-sidebar-item-category-level-2:only-child > .menu__list-item-collapsible .menu__link--active::before,
|
||||
.menu__list:not(.theme-doc-sidebar-menu) > .theme-doc-sidebar-item-link.theme-doc-sidebar-item-link-level-2:only-child > .menu__link--active::before,
|
||||
.menu__list:not(.theme-doc-sidebar-menu) > .theme-doc-sidebar-item-link.theme-doc-sidebar-item-link-level-2:first-child > .menu__link--active::before {
|
||||
@apply !top-0.5;
|
||||
}
|
||||
|
||||
.theme-doc-sidebar-item-link-level-1:not(.sidebar-title):not(.homepage-sidebar-item):not(.sidebar-back-link) {
|
||||
@apply mb-1;
|
||||
}
|
||||
|
||||
.theme-doc-sidebar-item-link-level-1:not(.sidebar-title):not(.homepage-sidebar-item):not(.sidebar-back-link) .menu__link {
|
||||
@apply !pl-0;
|
||||
}
|
||||
|
||||
.theme-doc-sidebar-item-category-level-1.sidebar-group-headline {
|
||||
@apply mb-1;
|
||||
}
|
||||
|
||||
[class*="level-2"]:is([class*="theme-doc-sidebar-item"]) .menu__link,
|
||||
[class*="level-2"]:is([class*="theme-doc-sidebar-item"]).sidebar-group-divider,
|
||||
[class*="level-3"]:is([class*="theme-doc-sidebar-item"]) .menu__link,
|
||||
[class*="level-3"]:is([class*="theme-doc-sidebar-item"]).sidebar-group-divider,
|
||||
[class*="level-4"]:is([class*="theme-doc-sidebar-item"]) .menu__link,
|
||||
[class*="level-4"]:is([class*="theme-doc-sidebar-item"]).sidebar-group-divider {
|
||||
@apply py-[6px];
|
||||
}
|
||||
|
||||
[class*="level-2"]:is([class*="theme-doc-sidebar-item"]) .menu__link,
|
||||
[class*="level-2"]:is([class*="theme-doc-sidebar-item"]).sidebar-group-divider {
|
||||
@apply pl-2;
|
||||
}
|
||||
|
||||
[class*="level-3"]:is([class*="theme-doc-sidebar-item"]) .menu__link,
|
||||
[class*="level-3"]:is([class*="theme-doc-sidebar-item"]).sidebar-group-divider {
|
||||
@apply pl-3;
|
||||
}
|
||||
|
||||
[class*="level-4"]:is([class*="theme-doc-sidebar-item"]) .menu__link,
|
||||
[class*="level-4"]:is([class*="theme-doc-sidebar-item"]).sidebar-group-divider {
|
||||
@apply pl-4;
|
||||
}
|
||||
|
||||
.homepage-sidebar-item:is([class*="level-1"]):is([class*="theme-doc-sidebar-item"]) > .menu__link,
|
||||
.homepage-sidebar-item:is([class*="level-1"]):is([class*="theme-doc-sidebar-item"]) > .menu__list-item-collapsible > .menu__link {
|
||||
@apply !pl-0.5;
|
||||
}
|
||||
|
||||
.menu__list-item-collapsible {
|
||||
@apply !rounded;
|
||||
}
|
||||
|
||||
.menu__list-item-collapsible--active {
|
||||
@apply !bg-transparent;
|
||||
}
|
||||
|
||||
.menu__list-item-collapsible .menu__link--sublist:hover {
|
||||
@apply !bg-medusa-bg-base-hover dark:!bg-medusa-bg-subtle-hover-dark;
|
||||
}
|
||||
|
||||
.menu__list:not(.theme-doc-sidebar-menu) > .theme-doc-sidebar-item-link:last-child > .menu__link--active::before {
|
||||
@apply !top-0.25;
|
||||
}
|
||||
|
||||
.menu__link--sublist-caret:after {
|
||||
@apply content-none;
|
||||
}
|
||||
|
||||
.menu__link {
|
||||
@apply cursor-pointer !rounded text-compact-small-plus;
|
||||
}
|
||||
|
||||
.theme-doc-sidebar-item-category-level-1 > .menu__list {
|
||||
@apply mb-0.25;
|
||||
}
|
||||
|
||||
.menu__list:not(.theme-doc-sidebar-menu),
|
||||
.theme-doc-sidebar-item-category-level-1 > .menu__list {
|
||||
@apply !pl-0;
|
||||
}
|
||||
|
||||
.menu__list .menu__list {
|
||||
@apply !mt-0;
|
||||
}
|
||||
|
||||
/** General sidebar styles **/
|
||||
.theme-doc-sidebar-container {
|
||||
@apply z-[398] border-0 border-r border-solid border-medusa-border-base dark:border-medusa-border-base-dark;
|
||||
}
|
||||
|
||||
/** Mobile Sidebar **/
|
||||
.navbar-sidebar__back {
|
||||
@apply bg-transparent w-fit top-[unset] pl-0 ml-0 mb-1 text-compact-small-plus;
|
||||
}
|
||||
|
||||
.theme-doc-sidebar-item-link {
|
||||
@apply flex items-center;
|
||||
}
|
||||
|
||||
/** Redocly sidebar **/
|
||||
|
||||
.redocusaurus .menu-content > div > ul > li > ul label:not([type=tag]):not(.active) {
|
||||
@apply text-medusa-fg-subtle dark:text-medusa-fg-subtle-dark;
|
||||
@apply hover:text-medusa-fg-base dark:hover:text-medusa-fg-base-dark;
|
||||
}
|
||||
|
||||
.redocusaurus .menu-content > div > ul > li > ul label:not([type=tag]).active {
|
||||
@apply text-medusa-fg-base dark:text-medusa-fg-base-dark;
|
||||
}
|
||||
60
www/apps/docs/src/css/components/toc.css
Normal file
60
www/apps/docs/src/css/components/toc.css
Normal file
@@ -0,0 +1,60 @@
|
||||
.theme-doc-toc-desktop {
|
||||
@apply pt-3;
|
||||
}
|
||||
|
||||
.theme-doc-toc-desktop .table-of-contents {
|
||||
@apply !border-l-0 !p-0 text-[12px] relative overflow-y-hidden;
|
||||
@apply before:bg-toc dark:before:bg-toc-dark;
|
||||
@apply before:bg-no-repeat before:bg-[length:14px_10px] before:bg-[center_left];
|
||||
@apply before:pl-[22px] before:content-['On_this_page'] before:pb-0 text-compact-small-plus;
|
||||
@apply after:content-[''] after:absolute after:left-0 after:top-2.5;
|
||||
@apply after:h-full after:w-[1px] after:bg-medusa-border-base dark:after:bg-medusa-border-base-dark;
|
||||
}
|
||||
|
||||
.theme-doc-toc-desktop .table-of-contents .table-of-contents__link {
|
||||
@apply pl-[11px];
|
||||
}
|
||||
|
||||
.theme-doc-toc-desktop .table-of-contents > li {
|
||||
@apply first:mt-1 [&:not(:first-child)]:mt-0 last:mb-0 [&:not(:last-child)]:mb-0.25;
|
||||
}
|
||||
|
||||
.theme-doc-toc-desktop .table-of-contents li a + ul {
|
||||
@apply mt-0.25;
|
||||
}
|
||||
|
||||
.theme-doc-toc-desktop .table-of-contents__link {
|
||||
@apply relative text-compact-x-small-plus;
|
||||
@apply hover:text-medusa-fg-base dark:hover:text-medusa-fg-base-dark;
|
||||
}
|
||||
|
||||
.theme-doc-toc-desktop .table-of-contents__link:hover code {
|
||||
@apply text-medusa-fg-base dark:text-medusa-fg-base-dark;
|
||||
}
|
||||
|
||||
.theme-doc-toc-desktop .table-of-contents__link--active {
|
||||
@apply text-medusa-fg-base dark:text-medusa-fg-base-dark;
|
||||
@apply after:content-[''] after:absolute after:left-0 after:top-0;
|
||||
@apply after:h-full after:w-[1px] after:bg-medusa-fg-base dark:after:bg-medusa-fg-base-dark;
|
||||
@apply z-[1];
|
||||
}
|
||||
|
||||
.theme-doc-toc-desktop .table-of-contents__link--active code {
|
||||
@apply text-medusa-fg-base dark:text-medusa-fg-base-dark;
|
||||
}
|
||||
|
||||
.theme-doc-toc-desktop .table-of-contents li {
|
||||
@apply mx-0;
|
||||
}
|
||||
|
||||
.theme-doc-toc-desktop .table-of-contents ul li {
|
||||
@apply mt-0;
|
||||
}
|
||||
|
||||
.table-of-contents ul {
|
||||
@apply pl-0;
|
||||
}
|
||||
|
||||
.table-of-contents ul .table-of-contents__link {
|
||||
@apply !pl-[27px];
|
||||
}
|
||||
10
www/apps/docs/src/css/components/tooltip.css
Normal file
10
www/apps/docs/src/css/components/tooltip.css
Normal file
@@ -0,0 +1,10 @@
|
||||
.react-tooltip {
|
||||
@apply !border !border-solid !border-medusa-border-base dark:!border-medusa-border-base-dark;
|
||||
@apply !rounded !text-compact-x-small-plus !shadow-tooltip dark:!shadow-tooltip-dark;
|
||||
@apply !py-0.4 !px-1 lg:block hidden;
|
||||
}
|
||||
|
||||
.react-tooltip-arrow {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
185
www/apps/docs/src/css/custom.css
Normal file
185
www/apps/docs/src/css/custom.css
Normal file
@@ -0,0 +1,185 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
body[data-modal="opened"] {
|
||||
@apply !overflow-hidden;
|
||||
}
|
||||
|
||||
mark {
|
||||
@apply bg-medusa-bg-highlight dark:bg-medusa-bg-highlight-dark;
|
||||
@apply text-medusa-fg-interactive dark:text-medusa-fg-interactive-dark;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.sidebar-title {
|
||||
@apply !m-0 !py-1 !px-0;
|
||||
@apply lg:first:top-0 lg:[&:not(:first-child)]:top-[20px];
|
||||
}
|
||||
|
||||
.sidebar-title .menu__link {
|
||||
@apply !px-0;
|
||||
}
|
||||
|
||||
.sidebar-title .menu__link,
|
||||
.sidebar-title span {
|
||||
@apply !text-compact-medium-plus text-medusa-fg-base dark:text-medusa-fg-base-dark;
|
||||
}
|
||||
|
||||
.sidebar-title .menu__link--active,
|
||||
.sidebar-title .menu__link:hover {
|
||||
@apply !bg-transparent;
|
||||
}
|
||||
|
||||
.sidebar-group-headline {
|
||||
@apply mt-1 py-[6px] px-0;
|
||||
}
|
||||
|
||||
.sidebar-group-headline:not(.theme-doc-sidebar-item-category-level-1) {
|
||||
@apply mb-[6px];
|
||||
}
|
||||
|
||||
.sidebar-group-headline > .menu__list-item-collapsible > .menu__link {
|
||||
@apply text-medusa-fg-base dark:text-medusa-fg-base-dark !p-0;
|
||||
}
|
||||
|
||||
.sidebar-group-headline > .menu__list-item-collapsible > .menu__link:not([href]) {
|
||||
@apply cursor-default
|
||||
}
|
||||
|
||||
.sidebar-group-headline > .menu__list-item-collapsible > .menu__link:hover,
|
||||
.sidebar-group-headline .menu__list-item-collapsible:hover {
|
||||
@apply !bg-transparent;
|
||||
}
|
||||
|
||||
.sidebar-group-headline > .menu__link,
|
||||
.sidebar-group-headline > .menu__list-item-collapsible > .menu__link {
|
||||
@apply font-semibold;
|
||||
}
|
||||
|
||||
.sidebar-group-divider {
|
||||
@apply pb-[6px] uppercase text-medusa-fg-muted dark:text-medusa-fg-muted-dark text-compact-x-small-plus;
|
||||
@apply first:pt-[6px] [&:not(:first-child)]:!pt-1;
|
||||
}
|
||||
|
||||
.sidebar-divider-line {
|
||||
@apply h-[1px] w-full bg-medusa-border-base dark:bg-medusa-border-base-dark my-1 mx-0;
|
||||
}
|
||||
|
||||
.sidebar-back-link {
|
||||
@apply cursor-pointer lg:top-0;
|
||||
}
|
||||
|
||||
.sidebar-back-link,
|
||||
.sidebar-title {
|
||||
@apply lg:sticky lg:bg-medusa-bg-base dark:lg:bg-medusa-bg-subtle-dark lg:z-[100];
|
||||
}
|
||||
|
||||
.sidebar-back-link .menu__link {
|
||||
@apply !p-0 hover:!bg-transparent hover:text-medusa-fg-base dark:hover:text-medusa-fg-base-dark;
|
||||
}
|
||||
|
||||
.sidebar-back-link .menu__link,
|
||||
.sidebar-back-link span {
|
||||
@apply text-compact-small-plus;
|
||||
}
|
||||
|
||||
.sidebar-back-link .sidebar-item-icon {
|
||||
@apply mr-0.5;
|
||||
}
|
||||
|
||||
.sidebar-soon-link {
|
||||
@apply pointer-events-none [&_*]:text-medusa-fg-disabled dark:[&_*]:text-medusa-fg-disabled-dark;
|
||||
}
|
||||
|
||||
|
||||
.sidebar-badge-wrapper {
|
||||
@apply flex justify-between items-center;
|
||||
}
|
||||
|
||||
.search-page-input {
|
||||
@apply rounded border border-solid border-medusa-border-base dark:border-medusa-border-base-dark;
|
||||
@apply font-base text-medium p-0.75 w-full bg-medusa-bg-subtle dark:bg-medusa-bg-subtle-dark;
|
||||
@apply text-medusa-fg-base dark:text-medusa-fg-base-dark mb-1;
|
||||
@apply transition-[border] duration-200 ease-ease;
|
||||
@apply focus:border-medusa-border-base dark:focus:border-medusa-border-base-dark;
|
||||
@apply focus:outline-none !shadow-none;
|
||||
}
|
||||
|
||||
.code-header {
|
||||
@apply py-0.75 px-1 bg-medusa-code-bg-header dark:bg-medusa-code-bg-header-dark text-medusa-code-text-subtle dark:text-medusa-code-text-subtle;
|
||||
@apply flex justify-between items-center;
|
||||
@apply rounded-tl rounded-tr rounded-br-none rounded-bl-none border-b-0;
|
||||
@apply border border-solid border-medusa-code-border dark:border-medusa-code-border-dark border-b-0;
|
||||
}
|
||||
|
||||
.no-scrollbar::-webkit-scrollbar {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
.animate__fastest {
|
||||
--animate-duration: 0.2s;
|
||||
}
|
||||
|
||||
/* .btn-secondary {
|
||||
@apply inline-flex flex-row justify-center items-center;
|
||||
@apply py-[5px] px-0.75 rounded-sm cursor-pointer;
|
||||
@apply bg-button-neutral bg-medusa-button-neutral dark:bg-button-neutral-dark dark:bg-medusa-button-neutral-dark;
|
||||
@apply hover:bg-medusa-button-neutral-hover hover:bg-no-image dark:hover:bg-medusa-button-neutral-hover-dark hover:no-underline;
|
||||
@apply active:bg-medusa-button-neutral-pressed active:bg-no-image dark:active:bg-medusa-button-neutral-pressed-dark;
|
||||
@apply focus:bg-medusa-button-neutral-pressed focus:bg-no-image dark:focus:bg-medusa-button-neutral-pressed-dark;
|
||||
@apply disabled:!bg-no-image disabled:bg-medusa-bg-disabled dark:disabled:bg-medusa-bg-disabled-dark;
|
||||
@apply disabled:cursor-not-allowed;
|
||||
@apply border border-solid border-medusa-border-base dark:border-medusa-border-base-dark;
|
||||
@apply text-compact-small-plus text-medusa-fg-base dark:text-medusa-fg-base-dark;
|
||||
@apply hover:text-medusa-fg-base hover:dark:text-medusa-fg-base-dark;
|
||||
@apply shadow-button-neutral focus:shadow-button-neutral-focused active:shadow-button-neutral-focused transition-shadow;
|
||||
@apply dark:shadow-button-neutral dark:focus:shadow-button-neutral-focused dark:active:shadow-button-neutral-focused;
|
||||
@apply select-none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
@apply inline-flex flex-row justify-center items-center;
|
||||
@apply py-[5px] px-0.75 rounded-sm cursor-pointer;
|
||||
@apply bg-button-inverted bg-medusa-button-inverted dark:bg-button-inverted-dark dark:bg-medusa-button-inverted-dark;
|
||||
@apply hover:bg-medusa-button-inverted-hover hover:bg-no-image dark:hover:bg-medusa-button-inverted-hover-dark hover:no-underline;
|
||||
@apply active:bg-medusa-button-inverted-pressed active:bg-no-image dark:active:bg-medusa-button-inverted-pressed-dark;
|
||||
@apply focus:bg-medusa-button-inverted-pressed focus:bg-no-image dark:focus:bg-medusa-button-inverted-pressed-dark;
|
||||
@apply shadow-button-colored active:shadow-button-colored-focused focus:shadow-button-colored-focused transition-shadow;
|
||||
@apply dark:shadow-button-colored-dark dark:active:shadow-button-colored-focused-dark dark:focus:shadow-button-colored-focused-dark;
|
||||
@apply disabled:!bg-no-image disabled:bg-medusa-button-disabled dark:disabled:bg-medusa-button-disabled-dark;
|
||||
@apply disabled:cursor-not-allowed disabled:border-medusa-border-base dark:disabled:border-medusa-border-base-dark;
|
||||
@apply text-compact-small-plus text-medusa-fg-on-inverted dark:text-medusa-fg-on-inverted-dark;
|
||||
@apply disabled:text-medusa-fg-disabled dark:disabled:text-medusa-fg-disabled-dark;
|
||||
@apply border border-medusa-border-loud dark:border-medusa-border-loud-dark;
|
||||
@apply select-none;
|
||||
} */
|
||||
|
||||
.navbar-action-icon-item {
|
||||
@apply lg:bg-button-neutral lg:bg-medusa-button-neutral lg:dark:bg-button-neutral-dark lg:dark:bg-medusa-button-neutral-dark;
|
||||
@apply lg:hover:bg-medusa-button-neutral-hover lg:hover:bg-no-image lg:dark:hover:bg-medusa-button-neutral-hover-dark lg:hover:no-underline;
|
||||
@apply lg:active:bg-medusa-button-neutral-pressed lg:active:bg-no-image lg:dark:active:bg-medusa-button-neutral-pressed-dark;
|
||||
@apply lg:focus:bg-medusa-button-neutral-pressed lg:focus:bg-no-image lg:dark:focus:bg-medusa-button-neutral-pressed-dark;
|
||||
@apply lg:lg:border lg:border-solid lg:border-medusa-border-base lg:dark:border-medusa-border-base-dark rounded;
|
||||
@apply w-2 h-2 flex justify-center items-center cursor-pointer;
|
||||
}
|
||||
|
||||
.btn-clear {
|
||||
@apply bg-transparent shadow-none border-0 outline-none cursor-pointer;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.clip {
|
||||
clip-path: inset(0);
|
||||
}
|
||||
}
|
||||
|
||||
@import url('./_variables.css');
|
||||
|
||||
@import url('./_docusaurus.css');
|
||||
@import url('./components/sidebar.css');
|
||||
@import url('./components/toc.css');
|
||||
@import url('./components/tooltip.css');
|
||||
95
www/apps/docs/src/hooks/use-current-learning-path.tsx
Normal file
95
www/apps/docs/src/hooks/use-current-learning-path.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import React, { useEffect } from "react"
|
||||
import { useLearningPath } from "../providers/LearningPath"
|
||||
import LearningPathSteps from "../components/LearningPath/Steps"
|
||||
import LearningPathFinish from "../components/LearningPath/Finish"
|
||||
import LearningPathIcon from "../components/LearningPath/Icon"
|
||||
import { useNotifications } from "docs-ui"
|
||||
|
||||
const useCurrentLearningPath = () => {
|
||||
const { path, currentStep, updatePath, endPath } = useLearningPath()
|
||||
const step = path?.steps[currentStep]
|
||||
const {
|
||||
addNotification,
|
||||
generateId,
|
||||
removeNotification,
|
||||
updateNotification,
|
||||
} = useNotifications()
|
||||
|
||||
// used when a notification closed (finished or not)
|
||||
const handleClose = (notificationId: string, shouldEndPath = true) => {
|
||||
if (shouldEndPath) {
|
||||
setTimeout(() => {
|
||||
endPath()
|
||||
}, 500)
|
||||
}
|
||||
removeNotification(notificationId)
|
||||
}
|
||||
|
||||
// used when the learning path is completely finished
|
||||
// shows the finish step, if the path has any
|
||||
const handleFinish = (notificationId: string) => {
|
||||
if (path.finish) {
|
||||
updateNotification(notificationId, {
|
||||
title: path.finish.step.title,
|
||||
text: path.finish.step.description,
|
||||
type: "custom",
|
||||
layout: "default",
|
||||
CustomIcon: (
|
||||
<LearningPathIcon
|
||||
className="!w-2 !h-2"
|
||||
imgClassName="!w-1.5 !h-1.5"
|
||||
/>
|
||||
),
|
||||
children: (
|
||||
<LearningPathFinish
|
||||
{...path.finish}
|
||||
onRating={() =>
|
||||
setTimeout(() => {
|
||||
handleClose(notificationId, false)
|
||||
}, 1500)
|
||||
}
|
||||
/>
|
||||
),
|
||||
})
|
||||
endPath()
|
||||
} else {
|
||||
handleClose(notificationId)
|
||||
}
|
||||
}
|
||||
|
||||
const LearningStep = (notificationId: string) => {
|
||||
return <LearningPathSteps onFinish={() => handleFinish(notificationId)} />
|
||||
}
|
||||
|
||||
// create a notification when a path is initialized
|
||||
useEffect(() => {
|
||||
if (path && !path.notificationId) {
|
||||
const id = generateId()
|
||||
|
||||
addNotification({
|
||||
title: path.label,
|
||||
text: step?.description,
|
||||
onClose: () => handleClose(id),
|
||||
layout: "empty",
|
||||
id,
|
||||
children: LearningStep(id),
|
||||
className: "flex flex-col",
|
||||
})
|
||||
updatePath({
|
||||
notificationId: id,
|
||||
})
|
||||
}
|
||||
}, [path])
|
||||
|
||||
// update an existing notification when the step changes
|
||||
useEffect(() => {
|
||||
if (path && path.notificationId && step) {
|
||||
updateNotification(path.notificationId, {
|
||||
text: step?.description,
|
||||
children: LearningStep(path.notificationId),
|
||||
})
|
||||
}
|
||||
}, [step])
|
||||
}
|
||||
|
||||
export default useCurrentLearningPath
|
||||
35
www/apps/docs/src/hooks/use-onboarding.tsx
Normal file
35
www/apps/docs/src/hooks/use-onboarding.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useQueryStringValue } from "@docusaurus/theme-common/internal"
|
||||
import { Rating, useAnalytics, useNotifications } from "docs-ui"
|
||||
|
||||
const useOnboarding = () => {
|
||||
const isOnboarding = useQueryStringValue("ref") === "onboarding"
|
||||
const [showNotification, setShowNotification] = useState(isOnboarding)
|
||||
const { addNotification, removeNotification, generateId } = useNotifications()
|
||||
const { track } = useAnalytics()
|
||||
|
||||
useEffect(() => {
|
||||
if (isOnboarding) {
|
||||
track("finished_onboarding")
|
||||
const id = generateId()
|
||||
addNotification({
|
||||
title: "Thank you for installing Medusa!",
|
||||
text: "Please rate your onboarding experience",
|
||||
type: "success",
|
||||
show: showNotification,
|
||||
setShow: setShowNotification,
|
||||
id,
|
||||
children: (
|
||||
<Rating
|
||||
event="rating_onboarding"
|
||||
onRating={() => {
|
||||
setTimeout(() => removeNotification(id), 1500)
|
||||
}}
|
||||
/>
|
||||
),
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
}
|
||||
|
||||
export default useOnboarding
|
||||
93
www/apps/docs/src/hooks/use-select.tsx
Normal file
93
www/apps/docs/src/hooks/use-select.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import { useCallback, useMemo } from "react"
|
||||
|
||||
export type OptionType = {
|
||||
value: string
|
||||
label: string
|
||||
index?: string
|
||||
isAllOption?: boolean
|
||||
}
|
||||
|
||||
export type SelectOptions = {
|
||||
value: string | string[]
|
||||
multiple?: boolean
|
||||
options: OptionType[]
|
||||
setSelected?: (value: string | string[]) => void
|
||||
addSelected?: (value: string) => void
|
||||
removeSelected?: (value: string) => void
|
||||
handleAddAll?: (isAllSelected: boolean) => void
|
||||
}
|
||||
|
||||
const useSelect = ({
|
||||
value,
|
||||
options,
|
||||
multiple = false,
|
||||
setSelected,
|
||||
addSelected,
|
||||
removeSelected,
|
||||
handleAddAll,
|
||||
}: SelectOptions) => {
|
||||
const isValueSelected = useCallback(
|
||||
(val: string) => {
|
||||
return (
|
||||
(typeof value === "string" && val === value) ||
|
||||
(Array.isArray(value) && value.includes(val))
|
||||
)
|
||||
},
|
||||
[value]
|
||||
)
|
||||
|
||||
// checks if there are multiple selected values
|
||||
const hasSelectedValues = useMemo(() => {
|
||||
return multiple && Array.isArray(value) && value.length > 0
|
||||
}, [value, multiple])
|
||||
|
||||
// checks if there are any selected values,
|
||||
// whether multiple or one
|
||||
const hasSelectedValue = useMemo(() => {
|
||||
return hasSelectedValues || (typeof value === "string" && value.length)
|
||||
}, [hasSelectedValues, value])
|
||||
|
||||
const selectedValues: OptionType[] = useMemo(() => {
|
||||
if (typeof value === "string") {
|
||||
const selectedValue = options.find((option) => option.value === value)
|
||||
return selectedValue ? [selectedValue] : []
|
||||
} else if (Array.isArray(value)) {
|
||||
return options.filter((option) => value.includes(option.value))
|
||||
}
|
||||
return []
|
||||
}, [options, value])
|
||||
|
||||
const isAllSelected = useMemo(() => {
|
||||
return Array.isArray(value) && value.length === options.length
|
||||
}, [options, value])
|
||||
|
||||
const handleChange = (selectedValue: string, wasSelected: boolean) => {
|
||||
if (multiple) {
|
||||
wasSelected
|
||||
? removeSelected?.(selectedValue)
|
||||
: addSelected?.(selectedValue)
|
||||
} else {
|
||||
setSelected?.(selectedValue)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSelectAll = () => {
|
||||
if (handleAddAll) {
|
||||
handleAddAll(isAllSelected)
|
||||
} else {
|
||||
setSelected?.(options.map((option) => option.value))
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isValueSelected,
|
||||
hasSelectedValue,
|
||||
hasSelectedValues,
|
||||
selectedValues,
|
||||
isAllSelected,
|
||||
handleChange,
|
||||
handleSelectAll,
|
||||
}
|
||||
}
|
||||
|
||||
export default useSelect
|
||||
30
www/apps/docs/src/providers/DocsProviders/index.tsx
Normal file
30
www/apps/docs/src/providers/DocsProviders/index.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { AnalyticsProvider, ModalProvider, NotificationProvider } from "docs-ui"
|
||||
import React from "react"
|
||||
import { useThemeConfig } from "@docusaurus/theme-common"
|
||||
import { ThemeConfig } from "@medusajs/docs"
|
||||
import SearchProvider from "../Search"
|
||||
import LearningPathProvider from "../LearningPath"
|
||||
|
||||
type DocsProvidersProps = {
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
const DocsProviders = ({ children }: DocsProvidersProps) => {
|
||||
const {
|
||||
analytics: { apiKey },
|
||||
} = useThemeConfig() as ThemeConfig
|
||||
|
||||
return (
|
||||
<AnalyticsProvider writeKey={apiKey}>
|
||||
<ModalProvider>
|
||||
<SearchProvider>
|
||||
<LearningPathProvider>
|
||||
<NotificationProvider>{children}</NotificationProvider>
|
||||
</LearningPathProvider>
|
||||
</SearchProvider>
|
||||
</ModalProvider>
|
||||
</AnalyticsProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default DocsProviders
|
||||
256
www/apps/docs/src/providers/LearningPath/index.tsx
Normal file
256
www/apps/docs/src/providers/LearningPath/index.tsx
Normal file
@@ -0,0 +1,256 @@
|
||||
import useIsBrowser from "@docusaurus/useIsBrowser"
|
||||
import { getLearningPath } from "@site/src/utils/learning-paths"
|
||||
import React, { createContext, useContext, useEffect, useState } from "react"
|
||||
import { useHistory } from "@docusaurus/router"
|
||||
import { LearningPathFinishType } from "@site/src/components/LearningPath/Finish"
|
||||
import { useAnalytics } from "docs-ui"
|
||||
|
||||
export type LearningPathType = {
|
||||
name: string
|
||||
label: string
|
||||
description?: string
|
||||
steps: LearningPathStepType[]
|
||||
finish?: LearningPathFinishType
|
||||
notificationId?: string
|
||||
}
|
||||
|
||||
export type LearningPathStepType = {
|
||||
title?: string
|
||||
description?: string
|
||||
descriptionJSX?: JSX.Element
|
||||
path?: string
|
||||
}
|
||||
|
||||
export type LearningPathContextType = {
|
||||
path?: LearningPathType
|
||||
setPath: (value: LearningPathType) => void
|
||||
currentStep: number
|
||||
setCurrentStep: (value: number) => void
|
||||
startPath: (path: LearningPathType) => void
|
||||
updatePath: (data: Pick<LearningPathType, "notificationId">) => void
|
||||
endPath: () => void
|
||||
nextStep: () => void
|
||||
hasNextStep: () => boolean
|
||||
previousStep: () => void
|
||||
hasPreviousStep: () => boolean
|
||||
goToStep: (stepIndex: number) => void
|
||||
isCurrentPath: () => boolean
|
||||
goToCurrentPath: () => void
|
||||
}
|
||||
|
||||
type LearningPathProviderProps = {
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
const LearningPathContext = createContext<LearningPathContextType | null>(null)
|
||||
|
||||
const LearningPathProvider: React.FC<LearningPathProviderProps> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [path, setPath] = useState<LearningPathType | null>(null)
|
||||
const [currentStep, setCurrentStep] = useState(-1)
|
||||
const isBrowser = useIsBrowser()
|
||||
const history = useHistory()
|
||||
const { track } = useAnalytics()
|
||||
|
||||
const startPath = (path: LearningPathType) => {
|
||||
setPath(path)
|
||||
setCurrentStep(-1)
|
||||
if (isBrowser) {
|
||||
localStorage.setItem(
|
||||
"learning-path",
|
||||
JSON.stringify({
|
||||
pathName: path.name,
|
||||
currentStep: -1,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
track(`learning_path_${path.name}`, {
|
||||
url: history.location.pathname,
|
||||
state: `start`,
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (path && currentStep === -1) {
|
||||
nextStep()
|
||||
}
|
||||
}, [path])
|
||||
|
||||
const endPath = () => {
|
||||
const didFinish = currentStep === path.steps.length - 1
|
||||
const reachedIndex = currentStep === -1 ? 0 : currentStep
|
||||
track(`learning_path_${path.name}`, {
|
||||
url: history.location.pathname,
|
||||
state: !didFinish ? `closed` : `end`,
|
||||
reachedStep:
|
||||
path.steps[reachedIndex]?.title ||
|
||||
path.steps[reachedIndex]?.description ||
|
||||
path.steps[reachedIndex]?.descriptionJSX ||
|
||||
reachedIndex,
|
||||
})
|
||||
setPath(null)
|
||||
setCurrentStep(-1)
|
||||
if (isBrowser) {
|
||||
localStorage.removeItem("learning-path")
|
||||
}
|
||||
}
|
||||
|
||||
const hasNextStep = () => currentStep !== path?.steps.length - 1
|
||||
|
||||
const nextStep = () => {
|
||||
if (!path || !hasNextStep()) {
|
||||
return
|
||||
}
|
||||
const nextStepIndex = currentStep + 1
|
||||
setCurrentStep(nextStepIndex)
|
||||
const newPath = path.steps[nextStepIndex].path
|
||||
if (isBrowser) {
|
||||
localStorage.setItem(
|
||||
"learning-path",
|
||||
JSON.stringify({
|
||||
pathName: path.name,
|
||||
currentStep: nextStepIndex,
|
||||
})
|
||||
)
|
||||
}
|
||||
if (history.location.pathname !== newPath) {
|
||||
history.push(newPath)
|
||||
}
|
||||
}
|
||||
|
||||
const hasPreviousStep = () => currentStep > 0
|
||||
|
||||
const previousStep = () => {
|
||||
if (!path || !hasPreviousStep()) {
|
||||
return
|
||||
}
|
||||
|
||||
const previousStepIndex = currentStep - 1
|
||||
setCurrentStep(previousStepIndex)
|
||||
const newPath = path.steps[previousStepIndex].path
|
||||
if (isBrowser) {
|
||||
localStorage.setItem(
|
||||
"learning-path",
|
||||
JSON.stringify({
|
||||
pathName: path.name,
|
||||
currentStep: previousStepIndex,
|
||||
})
|
||||
)
|
||||
}
|
||||
if (history.location.pathname !== newPath) {
|
||||
history.push(newPath)
|
||||
}
|
||||
}
|
||||
|
||||
const goToStep = (stepIndex: number) => {
|
||||
if (!path || stepIndex >= path.steps.length) {
|
||||
return
|
||||
}
|
||||
|
||||
setCurrentStep(stepIndex)
|
||||
const newPath = path.steps[stepIndex].path
|
||||
if (isBrowser) {
|
||||
localStorage.setItem(
|
||||
"learning-path",
|
||||
JSON.stringify({
|
||||
pathName: path.name,
|
||||
currentStep: stepIndex,
|
||||
})
|
||||
)
|
||||
}
|
||||
if (history.location.pathname !== newPath) {
|
||||
history.push(newPath)
|
||||
}
|
||||
}
|
||||
|
||||
const isCurrentPath = () => {
|
||||
if (!path || currentStep === -1) {
|
||||
return false
|
||||
}
|
||||
|
||||
return history.location.pathname === path.steps[currentStep].path
|
||||
}
|
||||
|
||||
const goToCurrentPath = () => {
|
||||
if (!path || currentStep === -1) {
|
||||
return
|
||||
}
|
||||
|
||||
history.push(path.steps[currentStep].path)
|
||||
}
|
||||
|
||||
const updatePath = (data: Pick<LearningPathType, "notificationId">) => {
|
||||
setPath({
|
||||
...path,
|
||||
...data,
|
||||
})
|
||||
}
|
||||
|
||||
const initPath = () => {
|
||||
if (isBrowser) {
|
||||
// give query parameters higher precedence over local storage
|
||||
const queryPathName = new URLSearchParams(history.location.search).get(
|
||||
"path"
|
||||
)
|
||||
const queryPath = getLearningPath(queryPathName)
|
||||
if (queryPath) {
|
||||
startPath(queryPath)
|
||||
} else {
|
||||
const storedPath = localStorage.getItem("learning-path")
|
||||
if (storedPath) {
|
||||
const storedPathParsed = JSON.parse(storedPath)
|
||||
const currentPath = getLearningPath(storedPathParsed?.pathName)
|
||||
if (currentPath) {
|
||||
setPath(currentPath)
|
||||
setCurrentStep(storedPathParsed?.currentStep || 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (isBrowser && !path) {
|
||||
initPath()
|
||||
}
|
||||
}, [isBrowser])
|
||||
|
||||
return (
|
||||
<LearningPathContext.Provider
|
||||
value={{
|
||||
path,
|
||||
setPath,
|
||||
currentStep,
|
||||
setCurrentStep,
|
||||
startPath,
|
||||
updatePath,
|
||||
endPath,
|
||||
nextStep,
|
||||
hasNextStep,
|
||||
previousStep,
|
||||
hasPreviousStep,
|
||||
goToStep,
|
||||
isCurrentPath,
|
||||
goToCurrentPath,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</LearningPathContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export default LearningPathProvider
|
||||
|
||||
export const useLearningPath = () => {
|
||||
const context = useContext(LearningPathContext)
|
||||
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
"useLearningPath must be used within a LearningPathProvider"
|
||||
)
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
71
www/apps/docs/src/providers/Search/index.tsx
Normal file
71
www/apps/docs/src/providers/Search/index.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { SearchProvider as UiSearchProvider, checkArraySameElms } from "docs-ui"
|
||||
import { ThemeConfig } from "@medusajs/docs"
|
||||
import { useThemeConfig } from "@docusaurus/theme-common"
|
||||
import { useLocalPathname } from "@docusaurus/theme-common/internal"
|
||||
|
||||
type SearchProviderProps = {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const SearchProvider = ({ children }: SearchProviderProps) => {
|
||||
const [defaultFilters, setDefaultFilters] = useState<string[]>([])
|
||||
const { algoliaConfig: algolia } = useThemeConfig() as ThemeConfig
|
||||
const currentPath = useLocalPathname()
|
||||
|
||||
useEffect(() => {
|
||||
let resultFilters = []
|
||||
algolia.defaultFiltersByPath.some((filtersByPath) => {
|
||||
if (currentPath.startsWith(filtersByPath.path)) {
|
||||
resultFilters = filtersByPath.filters
|
||||
}
|
||||
})
|
||||
if (!resultFilters.length && algolia.defaultFilters) {
|
||||
resultFilters = algolia.defaultFilters
|
||||
}
|
||||
if (!checkArraySameElms(defaultFilters, resultFilters)) {
|
||||
setDefaultFilters(resultFilters)
|
||||
}
|
||||
}, [currentPath])
|
||||
|
||||
return (
|
||||
<UiSearchProvider
|
||||
algolia={{
|
||||
appId: algolia.appId,
|
||||
apiKey: algolia.apiKey,
|
||||
mainIndexName: algolia.indexNames.docs,
|
||||
indices: Object.values(algolia.indexNames),
|
||||
}}
|
||||
searchProps={{
|
||||
filterOptions: algolia.filters,
|
||||
suggestions: [
|
||||
{
|
||||
title: "Getting started? Try one of the following terms.",
|
||||
items: [
|
||||
"Install Medusa with create-medusa-app",
|
||||
"Next.js quickstart",
|
||||
"Admin dashboard quickstart",
|
||||
"Commerce modules",
|
||||
"Medusa architecture",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Developing with Medusa",
|
||||
items: [
|
||||
"Recipes",
|
||||
"How to create endpoints",
|
||||
"How to create an entity",
|
||||
"How to create a plugin",
|
||||
"How to create an admin widget",
|
||||
],
|
||||
},
|
||||
],
|
||||
}}
|
||||
initialDefaultFilters={defaultFilters}
|
||||
>
|
||||
{children}
|
||||
</UiSearchProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default SearchProvider
|
||||
102
www/apps/docs/src/providers/Sidebar/index.tsx
Normal file
102
www/apps/docs/src/providers/Sidebar/index.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import React, {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react"
|
||||
import { prefersReducedMotion } from "@docusaurus/theme-common"
|
||||
|
||||
type SidebarContextType = {
|
||||
hasSidebar: boolean
|
||||
hiddenSidebar: boolean
|
||||
setHiddenSidebar: (value: boolean) => void
|
||||
hiddenSidebarContainer: boolean
|
||||
setHiddenSidebarContainer: (value: boolean) => void
|
||||
floatingSidebar: boolean
|
||||
setFloatingSidebar: (value: boolean) => void
|
||||
onCollapse: () => void
|
||||
}
|
||||
|
||||
const SidebarContext = createContext<SidebarContextType | null>(null)
|
||||
|
||||
type SidebarProviderProps = {
|
||||
sidebarName?: string
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
const SidebarProvider: React.FC<SidebarProviderProps> = ({
|
||||
sidebarName,
|
||||
children,
|
||||
}) => {
|
||||
const [hiddenSidebar, setHiddenSidebar] = useState(false)
|
||||
const [hiddenSidebarContainer, setHiddenSidebarContainer] = useState(false)
|
||||
const [floatingSidebar, setFloatingSidebar] = useState(false)
|
||||
|
||||
const toggleSidebar = useCallback(() => {
|
||||
if (hiddenSidebar) {
|
||||
setHiddenSidebar(false)
|
||||
}
|
||||
// onTransitionEnd won't fire when sidebar animation is disabled
|
||||
// fixes https://github.com/facebook/docusaurus/issues/8918
|
||||
if (!hiddenSidebar && prefersReducedMotion()) {
|
||||
setHiddenSidebar(true)
|
||||
}
|
||||
setHiddenSidebarContainer((value) => !value)
|
||||
}, [setHiddenSidebarContainer, hiddenSidebar])
|
||||
|
||||
useEffect(() => {
|
||||
function isEditingContent(event: KeyboardEvent) {
|
||||
const element = event.target as HTMLElement
|
||||
const tagName = element.tagName
|
||||
return (
|
||||
element.isContentEditable ||
|
||||
tagName === "INPUT" ||
|
||||
tagName === "SELECT" ||
|
||||
tagName === "TEXTAREA"
|
||||
)
|
||||
}
|
||||
|
||||
function sidebarShortcut(e: KeyboardEvent) {
|
||||
if (
|
||||
(e.metaKey || e.ctrlKey) &&
|
||||
e.key.toLowerCase() === "i" &&
|
||||
!isEditingContent(e)
|
||||
) {
|
||||
e.preventDefault()
|
||||
toggleSidebar()
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("keydown", sidebarShortcut)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("keydown", sidebarShortcut)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<SidebarContext.Provider
|
||||
value={{
|
||||
hasSidebar: sidebarName !== undefined,
|
||||
hiddenSidebar,
|
||||
setHiddenSidebar,
|
||||
hiddenSidebarContainer,
|
||||
setHiddenSidebarContainer,
|
||||
floatingSidebar,
|
||||
setFloatingSidebar,
|
||||
onCollapse: toggleSidebar,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</SidebarContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export default SidebarProvider
|
||||
|
||||
export const useSidebar = () => {
|
||||
const context = useContext(SidebarContext)
|
||||
|
||||
return context
|
||||
}
|
||||
188
www/apps/docs/src/theme/Admonition/index.tsx
Normal file
188
www/apps/docs/src/theme/Admonition/index.tsx
Normal file
@@ -0,0 +1,188 @@
|
||||
import React, { type ReactNode } from "react"
|
||||
import clsx from "clsx"
|
||||
import Translate from "@docusaurus/Translate"
|
||||
import type { Props } from "@theme/Admonition"
|
||||
import {
|
||||
ExclamationCircleSolid,
|
||||
InformationCircleSolid,
|
||||
LightBulbSolid,
|
||||
} from "@medusajs/icons"
|
||||
|
||||
function NoteIcon() {
|
||||
return (
|
||||
<InformationCircleSolid className="inline-block mr-0.125 text-medusa-fg-interactive-dark" />
|
||||
)
|
||||
}
|
||||
|
||||
function TipIcon() {
|
||||
return (
|
||||
<LightBulbSolid className="inline-block mr-0.125 text-medusa-tag-orange-icon-dark" />
|
||||
)
|
||||
}
|
||||
|
||||
function DangerIcon() {
|
||||
return (
|
||||
<ExclamationCircleSolid className="inline-block mr-0.125 text-medusa-fg-error dark:text-medusa-fg-error-dark" />
|
||||
)
|
||||
}
|
||||
|
||||
function InfoIcon() {
|
||||
return NoteIcon()
|
||||
}
|
||||
|
||||
function CautionIcon() {
|
||||
return DangerIcon()
|
||||
}
|
||||
|
||||
type AdmonitionConfig = {
|
||||
iconComponent: React.ComponentType
|
||||
infimaClassName: string
|
||||
label: ReactNode
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style
|
||||
const AdmonitionConfigs: Record<Props["type"], AdmonitionConfig> = {
|
||||
note: {
|
||||
infimaClassName: "secondary",
|
||||
iconComponent: NoteIcon,
|
||||
label: (
|
||||
<Translate
|
||||
id="theme.admonition.note"
|
||||
description="The default label used for the Note admonition (:::note)"
|
||||
>
|
||||
note
|
||||
</Translate>
|
||||
),
|
||||
},
|
||||
tip: {
|
||||
infimaClassName: "success",
|
||||
iconComponent: TipIcon,
|
||||
label: (
|
||||
<Translate
|
||||
id="theme.admonition.tip"
|
||||
description="The default label used for the Tip admonition (:::tip)"
|
||||
>
|
||||
tip
|
||||
</Translate>
|
||||
),
|
||||
},
|
||||
danger: {
|
||||
infimaClassName: "danger",
|
||||
iconComponent: DangerIcon,
|
||||
label: (
|
||||
<Translate
|
||||
id="theme.admonition.danger"
|
||||
description="The default label used for the Danger admonition (:::danger)"
|
||||
>
|
||||
danger
|
||||
</Translate>
|
||||
),
|
||||
},
|
||||
info: {
|
||||
infimaClassName: "info",
|
||||
iconComponent: InfoIcon,
|
||||
label: (
|
||||
<Translate
|
||||
id="theme.admonition.info"
|
||||
description="The default label used for the Info admonition (:::info)"
|
||||
>
|
||||
info
|
||||
</Translate>
|
||||
),
|
||||
},
|
||||
caution: {
|
||||
infimaClassName: "warning",
|
||||
iconComponent: CautionIcon,
|
||||
label: (
|
||||
<Translate
|
||||
id="theme.admonition.caution"
|
||||
description="The default label used for the Caution admonition (:::caution)"
|
||||
>
|
||||
caution
|
||||
</Translate>
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
// Legacy aliases, undocumented but kept for retro-compatibility
|
||||
const aliases = {
|
||||
secondary: "note",
|
||||
important: "info",
|
||||
success: "tip",
|
||||
warning: "danger",
|
||||
} as const
|
||||
|
||||
function getAdmonitionConfig(unsafeType: string): AdmonitionConfig {
|
||||
const type =
|
||||
(aliases as { [key: string]: Props["type"] })[unsafeType] ?? unsafeType
|
||||
const config = (AdmonitionConfigs as { [key: string]: AdmonitionConfig })[
|
||||
type
|
||||
]
|
||||
if (config) {
|
||||
return config
|
||||
}
|
||||
console.warn(
|
||||
`No admonition config found for admonition type "${type}". Using Info as fallback.`
|
||||
)
|
||||
return AdmonitionConfigs.info
|
||||
}
|
||||
|
||||
// Workaround because it's difficult in MDX v1 to provide a MDX title as props
|
||||
// See https://github.com/facebook/docusaurus/pull/7152#issuecomment-1145779682
|
||||
function extractMDXAdmonitionTitle(children: ReactNode): {
|
||||
mdxAdmonitionTitle: ReactNode | undefined
|
||||
rest: ReactNode
|
||||
} {
|
||||
const items = React.Children.toArray(children)
|
||||
const mdxAdmonitionTitle = items.find(
|
||||
(item) =>
|
||||
React.isValidElement(item) &&
|
||||
(item.props as { mdxType: string } | null)?.mdxType ===
|
||||
"mdxAdmonitionTitle"
|
||||
)
|
||||
const rest = <>{items.filter((item) => item !== mdxAdmonitionTitle)}</>
|
||||
return {
|
||||
mdxAdmonitionTitle,
|
||||
rest,
|
||||
}
|
||||
}
|
||||
|
||||
function processAdmonitionProps(props: Props): Props {
|
||||
const { mdxAdmonitionTitle, rest } = extractMDXAdmonitionTitle(props.children)
|
||||
return {
|
||||
...props,
|
||||
title: props.title ?? mdxAdmonitionTitle,
|
||||
children: rest,
|
||||
}
|
||||
}
|
||||
|
||||
export default function Admonition(props: Props): JSX.Element {
|
||||
const { children, type, icon: iconProp } = processAdmonitionProps(props)
|
||||
|
||||
const typeConfig = getAdmonitionConfig(type)
|
||||
const { iconComponent: IconComponent } = typeConfig
|
||||
const icon = iconProp ?? <IconComponent />
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"p-1 border border-solid border-medusa-border-base dark:border-medusa-border-base-dark rounded",
|
||||
"bg-medusa-bg-subtle dark:bg-medusa-bg-base-dark shadow-none",
|
||||
"[&_a]:no-underline [&_a]:text-medusa-fg-interactive dark:[&_a]:text-medusa-fg-interactive-dark hover:[&_a]:text-medusa-fg-interactive-hover dark:hover:[&_a]:text-medusa-fg-interactive-hover-dark",
|
||||
"mb-2 alert"
|
||||
)}
|
||||
>
|
||||
<div className={clsx("flex")}>
|
||||
<span className={clsx("inline-block h-1.5 w-1.5 mr-1")}>{icon}</span>
|
||||
<div
|
||||
className={clsx(
|
||||
"text-medusa-fg-subtle dark:text-medusa-fg-subtle-dark",
|
||||
"text-medium flex-1 [&>*:last-child]:mb-0",
|
||||
"[&>p>code]:px-0.5 [&>p>code]:text-code-label"
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import React from "react"
|
||||
import clsx from "clsx"
|
||||
import { translate } from "@docusaurus/Translate"
|
||||
import IconClose from "../../Icon/Close"
|
||||
import type { Props } from "@theme/AnnouncementBar/CloseButton"
|
||||
|
||||
export default function AnnouncementBarCloseButton(
|
||||
props: Props
|
||||
): JSX.Element | null {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
aria-label={translate({
|
||||
id: "theme.AnnouncementBar.closeButtonAriaLabel",
|
||||
message: "Close",
|
||||
description: "The ARIA label for close button of announcement bar",
|
||||
})}
|
||||
{...props}
|
||||
className={clsx(
|
||||
"p-0 leading-[0] self-start opacity-100 hover:opacity-100",
|
||||
"bg-transparent border-0 cursor-pointer",
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
<IconClose
|
||||
width={20}
|
||||
height={20}
|
||||
strokeWidth={1.5}
|
||||
className="text-medusa-fg-muted dark:text-medusa-fg-muted-dark"
|
||||
/>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
29
www/apps/docs/src/theme/AnnouncementBar/Content/index.tsx
Normal file
29
www/apps/docs/src/theme/AnnouncementBar/Content/index.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from "react"
|
||||
import clsx from "clsx"
|
||||
import { useThemeConfig } from "@docusaurus/theme-common"
|
||||
import type { Props } from "@theme/AnnouncementBar/Content"
|
||||
|
||||
export default function AnnouncementBarContent(
|
||||
props: Props
|
||||
): JSX.Element | null {
|
||||
const { announcementBar } = useThemeConfig()
|
||||
const { content } = announcementBar!
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"text-medusa-fg-subtle dark:text-medusa-fg-subtle-dark",
|
||||
"text-compact-x-small-plus",
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
<div
|
||||
{...props}
|
||||
className={clsx("text-medusa-fg-base dark:text-medusa-fg-base-dark")}
|
||||
// Developer provided the HTML, so assume it's safe.
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{ __html: content }}
|
||||
/>
|
||||
<span>Read more</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
50
www/apps/docs/src/theme/AnnouncementBar/index.tsx
Normal file
50
www/apps/docs/src/theme/AnnouncementBar/index.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import React from "react"
|
||||
import { useThemeConfig } from "@docusaurus/theme-common"
|
||||
import { useAnnouncementBar } from "@docusaurus/theme-common/internal"
|
||||
import AnnouncementBarCloseButton from "@theme/AnnouncementBar/CloseButton"
|
||||
import AnnouncementBarContent from "@theme/AnnouncementBar/Content"
|
||||
import clsx from "clsx"
|
||||
import { Bordered } from "docs-ui"
|
||||
import { BellAlertSolid } from "@medusajs/icons"
|
||||
|
||||
export default function AnnouncementBar(): JSX.Element | null {
|
||||
const { announcementBar } = useThemeConfig()
|
||||
const { isActive, close } = useAnnouncementBar()
|
||||
if (!isActive) {
|
||||
return null
|
||||
}
|
||||
const { isCloseable, id } = announcementBar!
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"relative flex items-center h-auto bg-medusa-bg-subtle dark:bg-medusa-bg-base-dark p-0.75",
|
||||
"rounded mx-1.5 mb-1 shadow-card-rest dark:shadow-card-rest-dark",
|
||||
"transition-all duration-200 ease-ease",
|
||||
"hover:bg-medusa-bg-subtle-hover dark:hover:bg-medusa-bg-base-hover-dark",
|
||||
"print:hidden"
|
||||
)}
|
||||
>
|
||||
<Bordered wrapperClassName="mr-0.75">
|
||||
<div
|
||||
className={clsx(
|
||||
"p-[6px] flex justify-center items-center",
|
||||
"rounded-xs bg-medusa-bg-component dark:bg-medusa-bg-component-dark"
|
||||
)}
|
||||
>
|
||||
<BellAlertSolid className="text-medusa-fg-subtle dark:text-medusa-fg-subtle-dark" />
|
||||
</div>
|
||||
</Bordered>
|
||||
<AnnouncementBarContent className={clsx("flex-1")} />
|
||||
{isCloseable && (
|
||||
<AnnouncementBarCloseButton
|
||||
onClick={close}
|
||||
className={clsx("z-[101] text-right lg:basis-[50px]")}
|
||||
/>
|
||||
)}
|
||||
<a
|
||||
href={id}
|
||||
className={clsx("absolute top-0 left-0 w-full h-full z-[100]")}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
144
www/apps/docs/src/theme/CodeBlock/Content/String.tsx
Normal file
144
www/apps/docs/src/theme/CodeBlock/Content/String.tsx
Normal file
@@ -0,0 +1,144 @@
|
||||
import React from "react"
|
||||
import clsx from "clsx"
|
||||
import { useThemeConfig, usePrismTheme } from "@docusaurus/theme-common"
|
||||
import {
|
||||
parseCodeBlockTitle,
|
||||
parseLanguage,
|
||||
parseLines,
|
||||
containsLineNumbers,
|
||||
useCodeWordWrap,
|
||||
} from "@docusaurus/theme-common/internal"
|
||||
import Highlight, { defaultProps, type Language } from "prism-react-renderer"
|
||||
import Line from "@theme/CodeBlock/Line"
|
||||
import Container from "@theme/CodeBlock/Container"
|
||||
import type { Props } from "@theme/CodeBlock/Content/String"
|
||||
import useIsBrowser from "@docusaurus/useIsBrowser"
|
||||
import { ThemeConfig } from "@medusajs/docs"
|
||||
import { CopyButton, Tooltip } from "docs-ui"
|
||||
import { ExclamationCircleSolid, SquareTwoStackSolid } from "@medusajs/icons"
|
||||
|
||||
export default function CodeBlockString({
|
||||
children,
|
||||
className: blockClassName = "",
|
||||
metastring,
|
||||
title: titleProp,
|
||||
showLineNumbers: showLineNumbersProp,
|
||||
language: languageProp,
|
||||
noReport = false,
|
||||
noCopy = false,
|
||||
}: Props): JSX.Element {
|
||||
const {
|
||||
prism: { defaultLanguage, magicComments },
|
||||
reportCodeLinkPrefix = "",
|
||||
} = useThemeConfig() as ThemeConfig
|
||||
const language =
|
||||
languageProp ?? parseLanguage(blockClassName) ?? defaultLanguage
|
||||
const prismTheme = usePrismTheme()
|
||||
const wordWrap = useCodeWordWrap()
|
||||
const isBrowser = useIsBrowser()
|
||||
|
||||
// We still parse the metastring in case we want to support more syntax in the
|
||||
// future. Note that MDX doesn't strip quotes when parsing metastring:
|
||||
// "title=\"xyz\"" => title: "\"xyz\""
|
||||
const title = parseCodeBlockTitle(metastring) || titleProp
|
||||
|
||||
const { lineClassNames, code } = parseLines(children, {
|
||||
metastring,
|
||||
language,
|
||||
magicComments,
|
||||
})
|
||||
const showLineNumbers = showLineNumbersProp ?? containsLineNumbers(metastring)
|
||||
|
||||
return (
|
||||
<Container
|
||||
as="div"
|
||||
className={clsx(
|
||||
blockClassName,
|
||||
language &&
|
||||
!blockClassName.includes(`language-${language}`) &&
|
||||
`language-${language}`
|
||||
)}
|
||||
>
|
||||
{title && <div>{title}</div>}
|
||||
<div className={clsx("relative rounded-[inherit]")}>
|
||||
<Highlight
|
||||
{...defaultProps}
|
||||
theme={prismTheme}
|
||||
code={code}
|
||||
language={(language ?? "text") as Language}
|
||||
>
|
||||
{({ className, tokens, getLineProps, getTokenProps }) => (
|
||||
<>
|
||||
<pre
|
||||
tabIndex={0}
|
||||
ref={wordWrap.codeBlockRef}
|
||||
className={clsx("m-0 p-0", "thin-scrollbar", className)}
|
||||
>
|
||||
<code
|
||||
className={clsx(
|
||||
"font-[inherit] float-left min-w-full print:whitespace-pre-wrap",
|
||||
showLineNumbers &&
|
||||
tokens.length > 1 &&
|
||||
"table p-1 code-block-numbering",
|
||||
title && "p-1",
|
||||
!title && tokens.length > 1 && "p-1",
|
||||
!title && tokens.length === 1 && "py-0.5 pr-0.5 pl-1"
|
||||
)}
|
||||
>
|
||||
{tokens.map((line, i) => (
|
||||
<Line
|
||||
key={i}
|
||||
line={line}
|
||||
getLineProps={getLineProps}
|
||||
getTokenProps={getTokenProps}
|
||||
classNames={lineClassNames[i]}
|
||||
showLineNumbers={showLineNumbers && tokens.length > 1}
|
||||
/>
|
||||
))}
|
||||
</code>
|
||||
</pre>
|
||||
<div
|
||||
className={clsx(
|
||||
"flex gap-x-0.125 absolute right-1",
|
||||
tokens.length === 1 && "top-0.25",
|
||||
tokens.length > 1 && "top-1"
|
||||
)}
|
||||
>
|
||||
{!noReport && (
|
||||
<Tooltip text="Report Incorrect Code">
|
||||
<a
|
||||
href={`${reportCodeLinkPrefix}&title=${encodeURIComponent(
|
||||
`Docs(Code Issue): Code Issue in ${
|
||||
isBrowser ? location.pathname : ""
|
||||
}`
|
||||
)}`}
|
||||
target="_blank"
|
||||
className={clsx(
|
||||
"bg-transparent border-none p-0.25 cursor-pointer rounded",
|
||||
"hover:bg-medusa-code-bg-base dark:hover:bg-medusa-code-bg-base-dark [&:not(:first-child)]:ml-0.5",
|
||||
"inline-flex justify-center items-center invisible xs:visible"
|
||||
)}
|
||||
rel="noreferrer"
|
||||
>
|
||||
<ExclamationCircleSolid className="text-medusa-code-icon dark:text-medusa-code-icon-dark" />
|
||||
</a>
|
||||
</Tooltip>
|
||||
)}
|
||||
{!noCopy && (
|
||||
<CopyButton
|
||||
buttonClassName={clsx(
|
||||
"flex bg-transparent border-none p-0.25 cursor-pointer rounded"
|
||||
)}
|
||||
text={code}
|
||||
>
|
||||
<SquareTwoStackSolid className="text-medusa-code-icon dark:text-medusa-code-icon-dark" />
|
||||
</CopyButton>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Highlight>
|
||||
</div>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
59
www/apps/docs/src/theme/CodeBlock/index.tsx
Normal file
59
www/apps/docs/src/theme/CodeBlock/index.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import React, { isValidElement, type ReactNode } from "react"
|
||||
import useIsBrowser from "@docusaurus/useIsBrowser"
|
||||
import ElementContent from "@theme/CodeBlock/Content/Element"
|
||||
import StringContent from "@theme/CodeBlock/Content/String"
|
||||
import type { Props } from "@theme/CodeBlock"
|
||||
import clsx from "clsx"
|
||||
|
||||
/**
|
||||
* Best attempt to make the children a plain string so it is copyable. If there
|
||||
* are react elements, we will not be able to copy the content, and it will
|
||||
* return `children` as-is; otherwise, it concatenates the string children
|
||||
* together.
|
||||
*/
|
||||
function maybeStringifyChildren(children: ReactNode): ReactNode {
|
||||
if (React.Children.toArray(children).some((el) => isValidElement(el))) {
|
||||
return children
|
||||
}
|
||||
// The children is now guaranteed to be one/more plain strings
|
||||
return Array.isArray(children) ? children.join("") : (children as string)
|
||||
}
|
||||
|
||||
export default function CodeBlock({
|
||||
children: rawChildren,
|
||||
noReport = false,
|
||||
noCopy = false,
|
||||
...props
|
||||
}: Props): JSX.Element {
|
||||
// The Prism theme on SSR is always the default theme but the site theme can
|
||||
// be in a different mode. React hydration doesn't update DOM styles that come
|
||||
// from SSR. Hence force a re-render after mounting to apply the current
|
||||
// relevant styles.
|
||||
const isBrowser = useIsBrowser()
|
||||
const children = maybeStringifyChildren(rawChildren)
|
||||
const CodeBlockComp =
|
||||
typeof children === "string" ? StringContent : ElementContent
|
||||
|
||||
const title = props.title
|
||||
delete props.title
|
||||
|
||||
return (
|
||||
<div className="code-wrapper">
|
||||
{title && <div className="code-header">{title}</div>}
|
||||
<CodeBlockComp
|
||||
key={String(isBrowser)}
|
||||
noReport={noReport}
|
||||
noCopy={noCopy}
|
||||
{...props}
|
||||
className={clsx(
|
||||
!title && "rounded",
|
||||
title && "!rounded-t-none !rounded-b",
|
||||
props.className
|
||||
)}
|
||||
showLineNumbers={true}
|
||||
>
|
||||
{children as string}
|
||||
</CodeBlockComp>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
26
www/apps/docs/src/theme/Details/index.tsx
Normal file
26
www/apps/docs/src/theme/Details/index.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from "react"
|
||||
import {
|
||||
Details as UiDetails,
|
||||
type DetailsProps as UiDetailsProps,
|
||||
} from "docs-ui"
|
||||
|
||||
export type DetailsProps = {
|
||||
summary: React.ReactNode
|
||||
children?: React.ReactNode
|
||||
} & UiDetailsProps
|
||||
|
||||
export default function Details({
|
||||
summary,
|
||||
children,
|
||||
...props
|
||||
}: DetailsProps): JSX.Element {
|
||||
return (
|
||||
<UiDetails
|
||||
{...props}
|
||||
summaryContent={!React.isValidElement(summary) ? summary : undefined}
|
||||
summaryElm={React.isValidElement(summary) ? summary : undefined}
|
||||
>
|
||||
{children}
|
||||
</UiDetails>
|
||||
)
|
||||
}
|
||||
247
www/apps/docs/src/theme/DocCard/index.tsx
Normal file
247
www/apps/docs/src/theme/DocCard/index.tsx
Normal file
@@ -0,0 +1,247 @@
|
||||
import React, { type ReactNode } from "react"
|
||||
import clsx from "clsx"
|
||||
import Link from "@docusaurus/Link"
|
||||
import {
|
||||
findFirstCategoryLink,
|
||||
useDocById,
|
||||
} from "@docusaurus/theme-common/internal"
|
||||
import isInternalUrl from "@docusaurus/isInternalUrl"
|
||||
import { translate } from "@docusaurus/Translate"
|
||||
import {
|
||||
ModifiedDocCard,
|
||||
ModifiedDocCardItemCategory,
|
||||
ModifiedDocCardItemLink,
|
||||
ModifiedSidebarItem,
|
||||
} from "@medusajs/docs"
|
||||
import { Badge } from "docs-ui"
|
||||
import BorderedIcon from "../../components/BorderedIcon"
|
||||
import Icons from "../Icon"
|
||||
|
||||
type ModifiedProps = {
|
||||
item: ModifiedDocCard
|
||||
}
|
||||
|
||||
function CardContainer({
|
||||
href,
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
href: string
|
||||
children: ReactNode
|
||||
className?: string
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<article className={`card-wrapper margin-bottom--lg`}>
|
||||
<Link
|
||||
href={href}
|
||||
className={clsx(
|
||||
"card",
|
||||
"bg-medusa-bg-subtle dark:bg-medusa-bg-base-dark",
|
||||
"rounded shadow-card-rest dark:shadow-card-rest-dark",
|
||||
"transition-all duration-200 ease-ease",
|
||||
"flex p-1 !pb-1.5 h-full",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
|
||||
function CardLayout({
|
||||
href,
|
||||
icon,
|
||||
title,
|
||||
description,
|
||||
html,
|
||||
containerClassName,
|
||||
isSoon = false,
|
||||
badge,
|
||||
}: ModifiedDocCard): JSX.Element {
|
||||
const isHighlighted = containerClassName?.includes("card-highlighted")
|
||||
return (
|
||||
<CardContainer
|
||||
href={href}
|
||||
className={clsx(
|
||||
containerClassName,
|
||||
!isSoon &&
|
||||
"hover:bg-medusa-bg-subtle-hover dark:hover:bg-medusa-bg-base-hover-dark",
|
||||
isSoon && "pointer-events-none",
|
||||
isHighlighted &&
|
||||
"md:before:content-[''] md:before:absolute md:before:top-0 before:right-0 md:before:w-1/2 md:before:h-full md:before:bg-no-repeat md:before:bg-cover md:before:bg-card-highlighted dark:md:before:bg-card-highlighted-dark",
|
||||
!isSoon && "hover:shadow-card-hover dark:hover:shadow-card-hover-dark"
|
||||
)}
|
||||
>
|
||||
<div className={clsx("mb-1 flex justify-between items-center")}>
|
||||
{icon}
|
||||
{isSoon && <Badge variant={"purple"}>Guide coming soon</Badge>}
|
||||
{badge && <Badge {...badge} />}
|
||||
</div>
|
||||
<div className={clsx("w-[calc(100%-20px)] [&>*:last-child]:mb-0")}>
|
||||
<span
|
||||
className={clsx(
|
||||
"text-compact-medium-plus text-medusa-fg-base dark:text-medusa-fg-base-dark",
|
||||
"mb-0.25 block",
|
||||
"transition-all duration-200 ease-ease",
|
||||
isSoon &&
|
||||
"group-hover:text-medusa-fg-disabled dark:group-hover:text-medusa-fg-disabled-dark"
|
||||
)}
|
||||
title={title}
|
||||
>
|
||||
{title}
|
||||
</span>
|
||||
{description && (
|
||||
<p
|
||||
className={clsx(
|
||||
"text-medium text-medusa-fg-subtle dark:text-medusa-fg-subtle-dark",
|
||||
"transition-all duration-200 ease-ease",
|
||||
isSoon &&
|
||||
"group-hover:text-medusa-fg-disabled dark:group-hover:text-medusa-fg-disabled-dark",
|
||||
isHighlighted && "md:w-1/2"
|
||||
)}
|
||||
title={description}
|
||||
>
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
{html && (
|
||||
<p
|
||||
className={clsx(
|
||||
"text-compact-medium text-medusa-fg-subtle dark:text-medusa-fg-subtle-dark",
|
||||
"transition-all duration-200 ease-ease",
|
||||
isSoon &&
|
||||
"group-hover:text-medusa-fg-disabled dark:group-hover:text-medusa-fg-disabled-dark",
|
||||
isHighlighted && "md:w-1/2"
|
||||
)}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: html,
|
||||
}}
|
||||
></p>
|
||||
)}
|
||||
</div>
|
||||
</CardContainer>
|
||||
)
|
||||
}
|
||||
|
||||
function getCardIcon(item: ModifiedSidebarItem): JSX.Element {
|
||||
if (item.customProps?.themedImage) {
|
||||
return (
|
||||
<BorderedIcon
|
||||
icon={{
|
||||
light: item.customProps.themedImage.light,
|
||||
dark: item.customProps.themedImage.dark,
|
||||
}}
|
||||
iconWrapperClassName={clsx("p-[6px]")}
|
||||
iconClassName={clsx("h-[20px] w-[20px]")}
|
||||
/>
|
||||
)
|
||||
} else if (item.customProps?.image) {
|
||||
return (
|
||||
<BorderedIcon
|
||||
icon={{
|
||||
light: item.customProps.image,
|
||||
}}
|
||||
iconWrapperClassName={clsx("p-[6px]")}
|
||||
iconClassName={clsx("h-[20px] w-[20px]")}
|
||||
/>
|
||||
)
|
||||
} else if (item.customProps?.icon) {
|
||||
return (
|
||||
<BorderedIcon
|
||||
IconComponent={item.customProps.icon}
|
||||
iconWrapperClassName={clsx("p-[6px]")}
|
||||
iconClassName={clsx("h-[20px] w-[20px]")}
|
||||
/>
|
||||
)
|
||||
} else if (
|
||||
item.customProps?.iconName &&
|
||||
Object.hasOwn(Icons, item.customProps?.iconName)
|
||||
) {
|
||||
return (
|
||||
<BorderedIcon
|
||||
IconComponent={Icons[item.customProps?.iconName]}
|
||||
iconWrapperClassName={clsx("p-[6px]")}
|
||||
iconClassName={clsx("h-[20px] w-[20px]")}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
// "card-icon-wrapper",
|
||||
"no-zoom-img"
|
||||
)}
|
||||
>
|
||||
{isInternalUrl(
|
||||
"href" in item ? item.href : "value" in item ? item.value : "#"
|
||||
)
|
||||
? "📄️"
|
||||
: "🔗"}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function CardCategory({
|
||||
item,
|
||||
}: {
|
||||
item: ModifiedDocCardItemCategory
|
||||
}): JSX.Element | null {
|
||||
const href = findFirstCategoryLink(item)
|
||||
const icon = getCardIcon(item)
|
||||
// Unexpected: categories that don't have a link have been filtered upfront
|
||||
if (!href) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<CardLayout
|
||||
{...item}
|
||||
href={href}
|
||||
icon={icon}
|
||||
title={item.label}
|
||||
// eslint-disable-next-line @docusaurus/string-literal-i18n-messages
|
||||
description={translate(
|
||||
{
|
||||
message: item.customProps?.description || "{count} items",
|
||||
id: "theme.docs.DocCard.categoryDescription",
|
||||
description:
|
||||
"The default description for a category card in the generated index about how many items this category includes",
|
||||
},
|
||||
{ count: item.items.length }
|
||||
)}
|
||||
containerClassName={item.customProps?.className}
|
||||
isSoon={item.customProps?.isSoon}
|
||||
badge={item.customProps?.badge}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardLink({ item }: { item: ModifiedDocCardItemLink }): JSX.Element {
|
||||
const icon = getCardIcon(item)
|
||||
const doc = useDocById(item.docId ?? undefined)
|
||||
|
||||
return (
|
||||
<CardLayout
|
||||
{...item}
|
||||
icon={icon}
|
||||
title={item.label}
|
||||
description={item.customProps?.description || doc?.description}
|
||||
html={item.customProps?.html}
|
||||
containerClassName={item.customProps?.className}
|
||||
isSoon={item.customProps?.isSoon}
|
||||
badge={item.customProps?.badge}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default function DocCard({ item }: ModifiedProps): JSX.Element {
|
||||
switch (item.type) {
|
||||
case "link":
|
||||
return <CardLink item={item} />
|
||||
case "category":
|
||||
return <CardCategory item={item} />
|
||||
default:
|
||||
throw new Error(`unknown item type ${JSON.stringify(item)}`)
|
||||
}
|
||||
}
|
||||
37
www/apps/docs/src/theme/DocCardList/index.tsx
Normal file
37
www/apps/docs/src/theme/DocCardList/index.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import React from "react"
|
||||
import clsx from "clsx"
|
||||
import {
|
||||
useCurrentSidebarCategory,
|
||||
filterDocCardListItems,
|
||||
} from "@docusaurus/theme-common"
|
||||
import DocCard from "@theme/DocCard"
|
||||
import type { Props } from "@theme/DocCardList"
|
||||
|
||||
function DocCardListForCurrentSidebarCategory({ className }: Props) {
|
||||
const category = useCurrentSidebarCategory()
|
||||
return <DocCardList items={category.items} className={className} />
|
||||
}
|
||||
|
||||
type ModifiedProps = {
|
||||
colSize?: string
|
||||
} & Props
|
||||
|
||||
export default function DocCardList(props: ModifiedProps): JSX.Element {
|
||||
const { items, className } = props
|
||||
if (!items) {
|
||||
return <DocCardListForCurrentSidebarCategory {...props} />
|
||||
}
|
||||
const filteredItems = filterDocCardListItems(items).filter(
|
||||
(item) => !item.customProps.excludeFromDocList
|
||||
)
|
||||
|
||||
return (
|
||||
<section
|
||||
className={clsx("cards-grid", `grid-${props.colSize || "4"}`, className)}
|
||||
>
|
||||
{filteredItems.map((item, index) => (
|
||||
<DocCard item={item} key={index} />
|
||||
))}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
67
www/apps/docs/src/theme/DocItem/Content/index.tsx
Normal file
67
www/apps/docs/src/theme/DocItem/Content/index.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import React from "react"
|
||||
import clsx from "clsx"
|
||||
import { ThemeClassNames } from "@docusaurus/theme-common"
|
||||
import { useDoc } from "@docusaurus/theme-common/internal"
|
||||
import Heading from "@theme/Heading"
|
||||
import MDXContent from "@theme/MDXContent"
|
||||
import type { Props } from "@theme/DocItem/Content"
|
||||
import { DocContextValue } from "@medusajs/docs"
|
||||
import { Badge, type BadgeVariant } from "docs-ui"
|
||||
|
||||
/**
|
||||
Title can be declared inside md content or declared through
|
||||
front matter and added manually. To make both cases consistent,
|
||||
the added title is added under the same div.markdown block
|
||||
See https://github.com/facebook/docusaurus/pull/4882#issuecomment-853021120
|
||||
|
||||
We render a "synthetic title" if:
|
||||
- user doesn't ask to hide it with front matter
|
||||
- the markdown content does not already contain a top-level h1 heading
|
||||
*/
|
||||
function useSyntheticTitle(): string | null {
|
||||
const { metadata, frontMatter, contentTitle } = useDoc()
|
||||
const shouldRender =
|
||||
!frontMatter.hide_title && typeof contentTitle === "undefined"
|
||||
if (!shouldRender) {
|
||||
return null
|
||||
}
|
||||
return metadata.title
|
||||
}
|
||||
|
||||
export default function DocItemContent({ children }: Props): JSX.Element {
|
||||
const {
|
||||
frontMatter: { badge },
|
||||
} = useDoc() as DocContextValue
|
||||
const syntheticTitle = useSyntheticTitle()
|
||||
|
||||
return (
|
||||
<div className={clsx(ThemeClassNames.docs.docMarkdown, "markdown")}>
|
||||
{syntheticTitle && (
|
||||
<header
|
||||
className={clsx(badge && "md:flex md:items-center md:gap-0.5 mb-2")}
|
||||
>
|
||||
<Heading as="h1" className={clsx(badge && "!mb-0")}>
|
||||
{syntheticTitle}
|
||||
{badge && (
|
||||
<Badge
|
||||
variant={badge.variant as BadgeVariant}
|
||||
className="md:hidden ml-1 align-middle"
|
||||
>
|
||||
{badge.text}
|
||||
</Badge>
|
||||
)}
|
||||
</Heading>
|
||||
{badge && (
|
||||
<Badge
|
||||
variant={badge.variant as BadgeVariant}
|
||||
className={clsx("md:block hidden")}
|
||||
>
|
||||
{badge.text}
|
||||
</Badge>
|
||||
)}
|
||||
</header>
|
||||
)}
|
||||
<MDXContent>{children}</MDXContent>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
29
www/apps/docs/src/theme/DocItem/Footer/index.tsx
Normal file
29
www/apps/docs/src/theme/DocItem/Footer/index.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from "react"
|
||||
import Footer from "@theme-original/DocItem/Footer"
|
||||
import type FooterType from "@theme/DocItem/Footer"
|
||||
import type { WrapperProps } from "@docusaurus/types"
|
||||
import { useDoc } from "@docusaurus/theme-common/internal"
|
||||
import { useThemeConfig } from "@docusaurus/theme-common"
|
||||
import { ThemeConfig } from "@medusajs/docs"
|
||||
import Feedback from "@site/src/components/Feedback"
|
||||
|
||||
type Props = WrapperProps<typeof FooterType>
|
||||
|
||||
export default function FooterWrapper(props: Props): JSX.Element {
|
||||
const { metadata } = useDoc()
|
||||
const { footerFeedback = { event: "" } } = useThemeConfig() as ThemeConfig
|
||||
|
||||
return (
|
||||
<>
|
||||
{!metadata.frontMatter?.hide_footer && (
|
||||
<div className="mt-[42px]">
|
||||
<Feedback
|
||||
{...footerFeedback}
|
||||
className="border-0 border-t border-solid border-medusa-border-base dark:border-medusa-border-base-dark"
|
||||
/>
|
||||
<Footer {...props} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
72
www/apps/docs/src/theme/DocItem/Layout/index.tsx
Normal file
72
www/apps/docs/src/theme/DocItem/Layout/index.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import React from "react"
|
||||
import clsx from "clsx"
|
||||
import { useWindowSize } from "@docusaurus/theme-common"
|
||||
import { useDoc } from "@docusaurus/theme-common/internal"
|
||||
import DocItemPaginator from "@theme/DocItem/Paginator"
|
||||
import DocVersionBanner from "@theme/DocVersionBanner"
|
||||
import DocVersionBadge from "@theme/DocVersionBadge"
|
||||
import DocItemFooter from "@theme/DocItem/Footer"
|
||||
import DocItemTOCMobile from "@theme/DocItem/TOC/Mobile"
|
||||
import DocItemTOCDesktop from "@theme/DocItem/TOC/Desktop"
|
||||
import DocItemContent from "@theme/DocItem/Content"
|
||||
import DocBreadcrumbs from "@theme/DocBreadcrumbs"
|
||||
import type { Props } from "@theme/DocItem/Layout"
|
||||
import Footer from "@theme/Footer"
|
||||
import { useSidebar } from "../../../providers/Sidebar"
|
||||
|
||||
/**
|
||||
* Decide if the toc should be rendered, on mobile or desktop viewports
|
||||
*/
|
||||
function useDocTOC() {
|
||||
const { frontMatter, toc } = useDoc()
|
||||
const windowSize = useWindowSize()
|
||||
|
||||
const hidden = frontMatter.hide_table_of_contents
|
||||
const canRender = !hidden && toc.length > 0
|
||||
|
||||
const mobile = canRender ? <DocItemTOCMobile /> : undefined
|
||||
|
||||
const desktop =
|
||||
canRender && (windowSize === "desktop" || windowSize === "ssr") ? (
|
||||
<DocItemTOCDesktop />
|
||||
) : undefined
|
||||
|
||||
return {
|
||||
hidden,
|
||||
mobile,
|
||||
desktop,
|
||||
}
|
||||
}
|
||||
|
||||
export default function DocItemLayout({ children }: Props): JSX.Element {
|
||||
const docTOC = useDocTOC()
|
||||
const sidebarContext = useSidebar()
|
||||
return (
|
||||
<div className="row m-0">
|
||||
<div
|
||||
className={clsx(
|
||||
"col",
|
||||
"my-0 mx-auto max-w-main-content w-full ml-auto lg:py-0 py-0 px-1",
|
||||
!docTOC.hidden && "w-9/12",
|
||||
!sidebarContext?.hiddenSidebarContainer && "!max-w-[720px]"
|
||||
)}
|
||||
>
|
||||
<DocVersionBanner />
|
||||
<div>
|
||||
<article className={clsx("[&>*:first-child]:mt-0")}>
|
||||
<DocBreadcrumbs />
|
||||
<DocVersionBadge />
|
||||
{docTOC.mobile}
|
||||
<DocItemContent>{children}</DocItemContent>
|
||||
<DocItemFooter />
|
||||
</article>
|
||||
<DocItemPaginator />
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
{docTOC.desktop && (
|
||||
<div className="col toc-wrapper">{docTOC.desktop}</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
31
www/apps/docs/src/theme/DocPage/Layout/Main/index.tsx
Normal file
31
www/apps/docs/src/theme/DocPage/Layout/Main/index.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from "react"
|
||||
import clsx from "clsx"
|
||||
import { useDocsSidebar } from "@docusaurus/theme-common/internal"
|
||||
import type { Props } from "@theme/DocPage/Layout/Main"
|
||||
import { useSidebar } from "@site/src/providers/Sidebar"
|
||||
|
||||
export default function DocPageLayoutMain({ children }: Props): JSX.Element {
|
||||
const sidebar = useDocsSidebar()
|
||||
const sidebarContext = useSidebar()
|
||||
return (
|
||||
<main
|
||||
className={clsx(
|
||||
"flex flex-col w-full lg:flex-grow",
|
||||
(sidebarContext?.hiddenSidebarContainer || !sidebar) &&
|
||||
"lg:max-w-[calc(100%-30px)]",
|
||||
!sidebarContext?.hiddenSidebarContainer &&
|
||||
"xxl:max-w-[1119px] lg:max-w-[calc(100%-321px)]"
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
"container padding-top--md px-0",
|
||||
sidebarContext?.hiddenSidebarContainer &&
|
||||
"lg:max-w-main-content-hidden-sidebar"
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
105
www/apps/docs/src/theme/DocPage/Layout/Sidebar/index.tsx
Normal file
105
www/apps/docs/src/theme/DocPage/Layout/Sidebar/index.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import React, { type ReactNode, useRef } from "react"
|
||||
import clsx from "clsx"
|
||||
import { ThemeClassNames } from "@docusaurus/theme-common"
|
||||
import { useDocsSidebar } from "@docusaurus/theme-common/internal"
|
||||
import { useLocation } from "@docusaurus/router"
|
||||
import DocSidebar from "@theme/DocSidebar"
|
||||
import type { Props } from "@theme/DocPage/Layout/Sidebar"
|
||||
import { SwitchTransition, CSSTransition } from "react-transition-group"
|
||||
import { useSidebar } from "@site/src/providers/Sidebar"
|
||||
|
||||
// Reset sidebar state when sidebar changes
|
||||
// Use React key to unmount/remount the children
|
||||
// See https://github.com/facebook/docusaurus/issues/3414
|
||||
function ResetOnSidebarChange({ children }: { children: ReactNode }) {
|
||||
const sidebar = useDocsSidebar()
|
||||
return (
|
||||
<React.Fragment key={sidebar?.name ?? "noSidebar"}>
|
||||
{children}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export default function DocPageLayoutSidebar({
|
||||
sidebar,
|
||||
hiddenSidebarContainer,
|
||||
}: Props): JSX.Element {
|
||||
const { pathname } = useLocation()
|
||||
const sidebarContext = useSidebar()
|
||||
const { name } = useDocsSidebar()
|
||||
const sidebarRef = useRef(null)
|
||||
|
||||
return (
|
||||
<aside
|
||||
className={clsx(
|
||||
ThemeClassNames.docs.docSidebarContainer,
|
||||
"min-[997px]:block min-[997px]:w-sidebar min-[997px]:transition-[left] min-[997px]:ease-ease min-[997px]:duration-200 min-[997px]:left-0 hidden",
|
||||
!hiddenSidebarContainer && "clip",
|
||||
hiddenSidebarContainer &&
|
||||
"min-[997px]:fixed min-[997px]:left-[-100%] min-[997px]:rounded min-[997px]:border-0 min-[997px]:border-medusa-border-strong min-[997px]:dark:border-medusa-border-strong-dark",
|
||||
hiddenSidebarContainer &&
|
||||
sidebarContext?.floatingSidebar &&
|
||||
"min-[997px]:!left-0.5 min-[997px]:top-[65px] min-[997px]:z-20 min-[997px]:bg-docs-bg min-[997px]:dark:bg-docs-bg-dark min-[997px]:shadow-flyout min-[997px]:dark:shadow-flyout-dark"
|
||||
)}
|
||||
onTransitionEnd={(e) => {
|
||||
if (
|
||||
!e.currentTarget.classList.contains(
|
||||
ThemeClassNames.docs.docSidebarContainer
|
||||
)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
if (hiddenSidebarContainer) {
|
||||
sidebarContext?.setHiddenSidebar(true)
|
||||
}
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setTimeout(() => {
|
||||
if (!document.querySelector(".sidebar-toggler:hover")) {
|
||||
sidebarContext?.setFloatingSidebar(false)
|
||||
}
|
||||
}, 100)
|
||||
}}
|
||||
onMouseUp={(e) => {
|
||||
const target = e.target as Element
|
||||
if (
|
||||
target.classList.contains("menu__list-item") ||
|
||||
target.parentElement.classList.contains("menu__list-item")
|
||||
) {
|
||||
sidebarContext?.setFloatingSidebar(false)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SwitchTransition>
|
||||
<CSSTransition
|
||||
key={name}
|
||||
nodeRef={sidebarRef}
|
||||
classNames={{
|
||||
enter: "animate__animated animate__fadeInLeft animate__fastest",
|
||||
exit: "animate__animated animate__fadeOutLeft animate__fastest",
|
||||
}}
|
||||
timeout={200}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
"min-[997px]:top-[57px] min-[997px]:sticky min-[997px]:max-h-screen min-[997px]:[&>div]:max-h-screen"
|
||||
)}
|
||||
ref={sidebarRef}
|
||||
>
|
||||
<ResetOnSidebarChange>
|
||||
<div>
|
||||
<DocSidebar
|
||||
sidebar={sidebar}
|
||||
path={pathname}
|
||||
onCollapse={sidebarContext?.onCollapse}
|
||||
isHidden={sidebarContext?.hiddenSidebar}
|
||||
/>
|
||||
</div>
|
||||
</ResetOnSidebarChange>
|
||||
</div>
|
||||
</CSSTransition>
|
||||
</SwitchTransition>
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
40
www/apps/docs/src/theme/DocPage/Layout/index.tsx
Normal file
40
www/apps/docs/src/theme/DocPage/Layout/index.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import React from "react"
|
||||
import { useDocsSidebar } from "@docusaurus/theme-common/internal"
|
||||
import Layout from "@theme/Layout"
|
||||
import BackToTopButton from "@theme/BackToTopButton"
|
||||
import DocPageLayoutSidebar from "@theme/DocPage/Layout/Sidebar"
|
||||
import DocPageLayoutMain from "@theme/DocPage/Layout/Main"
|
||||
import type { Props } from "@theme/DocPage/Layout"
|
||||
import clsx from "clsx"
|
||||
import { useSidebar } from "../../../providers/Sidebar"
|
||||
import useOnboarding from "../../../hooks/use-onboarding"
|
||||
import useCurrentLearningPath from "../../../hooks/use-current-learning-path"
|
||||
|
||||
export default function DocPageLayout({ children }: Props): JSX.Element {
|
||||
const sidebar = useDocsSidebar()
|
||||
const sidebarContext = useSidebar()
|
||||
useOnboarding()
|
||||
useCurrentLearningPath()
|
||||
|
||||
return (
|
||||
<Layout wrapperClassName={clsx("flex flex-[1_0_auto]")}>
|
||||
<BackToTopButton />
|
||||
<div className={clsx("flex w-full flex-[1_0]")}>
|
||||
{sidebar && (
|
||||
<DocPageLayoutSidebar
|
||||
sidebar={sidebar.items}
|
||||
hiddenSidebarContainer={sidebarContext?.hiddenSidebarContainer}
|
||||
setHiddenSidebarContainer={
|
||||
sidebarContext?.setHiddenSidebarContainer
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<DocPageLayoutMain
|
||||
hiddenSidebarContainer={sidebarContext?.hiddenSidebarContainer}
|
||||
>
|
||||
{children}
|
||||
</DocPageLayoutMain>
|
||||
</div>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
73
www/apps/docs/src/theme/DocPage/index.tsx
Normal file
73
www/apps/docs/src/theme/DocPage/index.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import React from "react"
|
||||
import clsx from "clsx"
|
||||
import {
|
||||
HtmlClassNameProvider,
|
||||
ThemeClassNames,
|
||||
PageMetadata,
|
||||
} from "@docusaurus/theme-common"
|
||||
import {
|
||||
docVersionSearchTag,
|
||||
DocsSidebarProvider,
|
||||
DocsVersionProvider,
|
||||
useDocRouteMetadata,
|
||||
} from "@docusaurus/theme-common/internal"
|
||||
import DocPageLayout from "@theme/DocPage/Layout"
|
||||
import NotFound from "@theme/NotFound"
|
||||
import SearchMetadata from "@theme/SearchMetadata"
|
||||
import type { Props } from "@theme/DocPage"
|
||||
import SidebarProvider from "@site/src/providers/Sidebar"
|
||||
import DocsProviders from "../../providers/DocsProviders"
|
||||
|
||||
function DocPageMetadata(props: Props): JSX.Element {
|
||||
const { versionMetadata } = props
|
||||
return (
|
||||
<>
|
||||
<SearchMetadata
|
||||
version={versionMetadata.version}
|
||||
tag={docVersionSearchTag(
|
||||
versionMetadata.pluginId,
|
||||
versionMetadata.version
|
||||
)}
|
||||
/>
|
||||
<PageMetadata>
|
||||
{versionMetadata.noIndex && (
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
)}
|
||||
</PageMetadata>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default function DocPage(props: Props): JSX.Element {
|
||||
const { versionMetadata } = props
|
||||
const currentDocRouteMetadata = useDocRouteMetadata(props)
|
||||
if (!currentDocRouteMetadata) {
|
||||
return <NotFound />
|
||||
}
|
||||
const { docElement, sidebarName, sidebarItems } = currentDocRouteMetadata
|
||||
|
||||
return (
|
||||
<>
|
||||
<DocPageMetadata {...props} />
|
||||
<HtmlClassNameProvider
|
||||
className={clsx(
|
||||
// TODO: it should be removed from here
|
||||
ThemeClassNames.wrapper.docsPages,
|
||||
ThemeClassNames.page.docsDocPage,
|
||||
props.versionMetadata.className
|
||||
// sidebarName && "doc-has-sidebar"
|
||||
)}
|
||||
>
|
||||
<DocsVersionProvider version={versionMetadata}>
|
||||
<DocsProviders>
|
||||
<DocsSidebarProvider name={sidebarName} items={sidebarItems}>
|
||||
<SidebarProvider sidebarName={sidebarName}>
|
||||
<DocPageLayout>{docElement}</DocPageLayout>
|
||||
</SidebarProvider>
|
||||
</DocsSidebarProvider>
|
||||
</DocsProviders>
|
||||
</DocsVersionProvider>
|
||||
</HtmlClassNameProvider>
|
||||
</>
|
||||
)
|
||||
}
|
||||
95
www/apps/docs/src/theme/DocSidebar/Desktop/index.tsx
Normal file
95
www/apps/docs/src/theme/DocSidebar/Desktop/index.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import React, { useEffect, useRef } from "react"
|
||||
import clsx from "clsx"
|
||||
import { useThemeConfig } from "@docusaurus/theme-common"
|
||||
import Content from "@theme/DocSidebar/Desktop/Content"
|
||||
import type { Props } from "@theme/DocSidebar/Desktop"
|
||||
import useIsBrowser from "@docusaurus/useIsBrowser"
|
||||
import { useLocation } from "@docusaurus/router"
|
||||
import AnnouncementBar from "../../AnnouncementBar/index"
|
||||
|
||||
function DocSidebarDesktop({ path, sidebar }: Props) {
|
||||
const {
|
||||
navbar: { hideOnScroll },
|
||||
} = useThemeConfig()
|
||||
const isBrowser = useIsBrowser()
|
||||
const sidebarRef = useRef(null)
|
||||
const location = useLocation()
|
||||
|
||||
useEffect(() => {
|
||||
function handleScroll() {
|
||||
if (!sidebarRef.current?.classList.contains("scrolling")) {
|
||||
sidebarRef.current?.classList.add("scrolling")
|
||||
const intervalId = setInterval(() => {
|
||||
if (!sidebarRef.current?.matches(":hover")) {
|
||||
sidebarRef.current?.classList.remove("scrolling")
|
||||
clearInterval(intervalId)
|
||||
}
|
||||
}, 300)
|
||||
}
|
||||
}
|
||||
|
||||
if (isBrowser && sidebarRef.current) {
|
||||
const navElement = sidebarRef.current.querySelector(".main-sidebar")
|
||||
navElement.addEventListener("scroll", handleScroll)
|
||||
|
||||
return () => {
|
||||
navElement?.removeEventListener("scroll", handleScroll)
|
||||
}
|
||||
}
|
||||
}, [isBrowser, sidebarRef.current])
|
||||
|
||||
useEffect(() => {
|
||||
const navElement = sidebarRef.current.querySelector(".main-sidebar")
|
||||
const navElementBoundingRect = navElement.getBoundingClientRect()
|
||||
|
||||
// logic to scroll to current active item
|
||||
const activeItem = document.querySelector(
|
||||
".sidebar-desktop [aria-current=page]"
|
||||
)
|
||||
|
||||
if (!activeItem) {
|
||||
return
|
||||
}
|
||||
|
||||
const activeItemBoundingReact = activeItem.getBoundingClientRect()
|
||||
// the extra 160 is due to the sticky elements in the sidebar
|
||||
const isActiveItemVisible =
|
||||
activeItemBoundingReact.top >= 0 &&
|
||||
activeItemBoundingReact.bottom + 160 <= navElementBoundingRect.height
|
||||
|
||||
if (!isActiveItemVisible) {
|
||||
const elementToScrollTo = activeItem
|
||||
const elementBounding = elementToScrollTo.getBoundingClientRect()
|
||||
// scroll to element
|
||||
navElement.scroll({
|
||||
// the extra 160 is due to the sticky elements in the sidebar
|
||||
top:
|
||||
elementBounding.top -
|
||||
navElementBoundingRect.top +
|
||||
navElement.scrollTop -
|
||||
160,
|
||||
behaviour: "smooth",
|
||||
})
|
||||
}
|
||||
}, [location])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"lg:flex lg:flex-col lg:max-h-screen lg:h-full lg:sticky lg:top-0 lg:transition-opacity lg:duration-[50ms] lg:ease-ease lg:pt-1.5",
|
||||
"sidebar-desktop",
|
||||
hideOnScroll && "lg:pt-0"
|
||||
)}
|
||||
ref={sidebarRef}
|
||||
>
|
||||
<AnnouncementBar />
|
||||
<Content
|
||||
path={path}
|
||||
sidebar={sidebar}
|
||||
className={clsx("main-sidebar", "!mt-0 !pt-0 !px-1.5 !pb-4")}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(DocSidebarDesktop)
|
||||
238
www/apps/docs/src/theme/DocSidebarItem/Category/index.tsx
Normal file
238
www/apps/docs/src/theme/DocSidebarItem/Category/index.tsx
Normal file
@@ -0,0 +1,238 @@
|
||||
import React, { type ComponentProps, useEffect, useMemo } from "react"
|
||||
import clsx from "clsx"
|
||||
import {
|
||||
ThemeClassNames,
|
||||
useThemeConfig,
|
||||
usePrevious,
|
||||
Collapsible,
|
||||
useCollapsible,
|
||||
} from "@docusaurus/theme-common"
|
||||
import {
|
||||
isActiveSidebarItem,
|
||||
findFirstCategoryLink,
|
||||
useDocSidebarItemsExpandedState,
|
||||
isSamePath,
|
||||
} from "@docusaurus/theme-common/internal"
|
||||
import Link from "@docusaurus/Link"
|
||||
import { translate } from "@docusaurus/Translate"
|
||||
import useIsBrowser from "@docusaurus/useIsBrowser"
|
||||
import DocSidebarItems from "@theme/DocSidebarItems"
|
||||
import type { Props } from "@theme/DocSidebarItem/Category"
|
||||
import { ModifiedPropSidebarItemCategory } from "@medusajs/docs"
|
||||
import DocSidebarItemIcon from "../../../components/DocSidebarItemIcon"
|
||||
import { Badge } from "docs-ui"
|
||||
|
||||
type ModifiedProps = Props & {
|
||||
item: ModifiedPropSidebarItemCategory
|
||||
}
|
||||
|
||||
// If we navigate to a category and it becomes active, it should automatically
|
||||
// expand itself
|
||||
function useAutoExpandActiveCategory({
|
||||
isActive,
|
||||
collapsed,
|
||||
updateCollapsed,
|
||||
}: {
|
||||
isActive: boolean
|
||||
collapsed: boolean
|
||||
updateCollapsed: (b: boolean) => void
|
||||
}) {
|
||||
const wasActive = usePrevious(isActive)
|
||||
useEffect(() => {
|
||||
const justBecameActive = isActive && !wasActive
|
||||
if (justBecameActive && collapsed) {
|
||||
updateCollapsed(false)
|
||||
}
|
||||
}, [isActive, wasActive, collapsed, updateCollapsed])
|
||||
}
|
||||
|
||||
/**
|
||||
* When a collapsible category has no link, we still link it to its first child
|
||||
* during SSR as a temporary fallback. This allows to be able to navigate inside
|
||||
* the category even when JS fails to load, is delayed or simply disabled
|
||||
* React hydration becomes an optional progressive enhancement
|
||||
* see https://github.com/facebookincubator/infima/issues/36#issuecomment-772543188
|
||||
* see https://github.com/facebook/docusaurus/issues/3030
|
||||
*/
|
||||
function useCategoryHrefWithSSRFallback(
|
||||
item: Props["item"]
|
||||
): string | undefined {
|
||||
const isBrowser = useIsBrowser()
|
||||
return useMemo(() => {
|
||||
if (item.href) {
|
||||
return item.href
|
||||
}
|
||||
// In these cases, it's not necessary to render a fallback
|
||||
// We skip the "findFirstCategoryLink" computation
|
||||
if (isBrowser || !item.collapsible) {
|
||||
return undefined
|
||||
}
|
||||
return findFirstCategoryLink(item)
|
||||
}, [item, isBrowser])
|
||||
}
|
||||
|
||||
function CollapseButton({
|
||||
categoryLabel,
|
||||
onClick,
|
||||
}: {
|
||||
categoryLabel: string
|
||||
onClick: ComponentProps<"button">["onClick"]
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
aria-label={translate(
|
||||
{
|
||||
id: "theme.DocSidebarItem.toggleCollapsedCategoryAriaLabel",
|
||||
message: "Toggle the collapsible sidebar category '{label}'",
|
||||
description:
|
||||
"The ARIA label to toggle the collapsible sidebar category",
|
||||
},
|
||||
{ label: categoryLabel }
|
||||
)}
|
||||
type="button"
|
||||
className="hidden"
|
||||
onClick={onClick}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default function DocSidebarItemCategory({
|
||||
item,
|
||||
onItemClick,
|
||||
activePath,
|
||||
level,
|
||||
index,
|
||||
...props
|
||||
}: ModifiedProps): JSX.Element {
|
||||
const { items, label, collapsible, className, href, customProps } = item
|
||||
const {
|
||||
docs: {
|
||||
sidebar: { autoCollapseCategories },
|
||||
},
|
||||
} = useThemeConfig()
|
||||
const hrefWithSSRFallback = useCategoryHrefWithSSRFallback(item)
|
||||
|
||||
const isActive = isActiveSidebarItem(item, activePath)
|
||||
const isCurrentPage = isSamePath(href, activePath)
|
||||
|
||||
const { collapsed, setCollapsed } = useCollapsible({
|
||||
// Active categories are always initialized as expanded. The default
|
||||
// (`item.collapsed`) is only used for non-active categories.
|
||||
initialState: () => {
|
||||
if (!collapsible) {
|
||||
return false
|
||||
}
|
||||
return isActive ? false : item.collapsed
|
||||
},
|
||||
})
|
||||
|
||||
const { expandedItem, setExpandedItem } = useDocSidebarItemsExpandedState()
|
||||
// Use this instead of `setCollapsed`, because it is also reactive
|
||||
const updateCollapsed = (toCollapsed = !collapsed) => {
|
||||
setExpandedItem(toCollapsed ? null : index)
|
||||
setCollapsed(toCollapsed)
|
||||
}
|
||||
useAutoExpandActiveCategory({ isActive, collapsed, updateCollapsed })
|
||||
useEffect(() => {
|
||||
if (
|
||||
collapsible &&
|
||||
expandedItem != null &&
|
||||
expandedItem !== index &&
|
||||
autoCollapseCategories
|
||||
) {
|
||||
setCollapsed(true)
|
||||
}
|
||||
}, [collapsible, expandedItem, index, setCollapsed, autoCollapseCategories])
|
||||
|
||||
return (
|
||||
<li
|
||||
className={clsx(
|
||||
ThemeClassNames.docs.docSidebarItemCategory,
|
||||
ThemeClassNames.docs.docSidebarItemCategoryLevel(level),
|
||||
"menu__list-item",
|
||||
// {
|
||||
// "menu__list-item--collapsed": collapsed,
|
||||
// },
|
||||
className,
|
||||
customProps?.sidebar_is_title && "sidebar-title",
|
||||
customProps?.sidebar_is_group_headline && "sidebar-group-headline",
|
||||
customProps?.sidebar_is_group_divider && "sidebar-group-divider",
|
||||
customProps?.sidebar_is_divider_line && "sidebar-divider-line",
|
||||
customProps?.sidebar_is_back_link && "sidebar-back-link",
|
||||
customProps?.sidebar_is_soon &&
|
||||
"sidebar-soon-link sidebar-badge-wrapper",
|
||||
!customProps?.sidebar_is_title &&
|
||||
"[&_.sidebar-item-icon]:w-[20px] [&_.sidebar-item-icon]:h-[20px]",
|
||||
!customProps?.sidebar_is_title &&
|
||||
!customProps?.sidebar_is_back_link &&
|
||||
"[&_.sidebar-item-icon]:mr-0.75"
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx("menu__list-item-collapsible", {
|
||||
"menu__list-item-collapsible--active": isCurrentPage,
|
||||
})}
|
||||
>
|
||||
<Link
|
||||
className={clsx("menu__link", {
|
||||
"menu__link--sublist": collapsible,
|
||||
"menu__link--sublist-caret": !href && collapsible,
|
||||
"menu__link--active": isActive,
|
||||
})}
|
||||
onClick={
|
||||
collapsible
|
||||
? (e) => {
|
||||
onItemClick?.(item)
|
||||
if (href) {
|
||||
updateCollapsed(false)
|
||||
} else {
|
||||
e.preventDefault()
|
||||
updateCollapsed()
|
||||
}
|
||||
}
|
||||
: () => {
|
||||
onItemClick?.(item)
|
||||
}
|
||||
}
|
||||
aria-current={isCurrentPage ? "page" : undefined}
|
||||
aria-expanded={collapsible ? !collapsed : undefined}
|
||||
href={collapsible ? hrefWithSSRFallback ?? "#" : hrefWithSSRFallback}
|
||||
{...props}
|
||||
>
|
||||
{customProps?.sidebar_icon && (
|
||||
<DocSidebarItemIcon
|
||||
icon={customProps.sidebar_icon}
|
||||
is_title={customProps.sidebar_is_title}
|
||||
is_disabled={customProps?.sidebar_is_soon}
|
||||
/>
|
||||
)}
|
||||
{label}
|
||||
</Link>
|
||||
{href && collapsible && (
|
||||
<CollapseButton
|
||||
categoryLabel={label}
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
updateCollapsed()
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{customProps?.sidebar_is_soon && (
|
||||
<Badge variant="purple" className={`sidebar-soon-badge`}>
|
||||
Soon
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Collapsible lazy as="ul" className="menu__list" collapsed={collapsed}>
|
||||
<DocSidebarItems
|
||||
items={items}
|
||||
tabIndex={collapsed ? -1 : 0}
|
||||
onItemClick={onItemClick}
|
||||
activePath={activePath}
|
||||
level={level + 1}
|
||||
/>
|
||||
</Collapsible>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
60
www/apps/docs/src/theme/DocSidebarItem/Html/index.tsx
Normal file
60
www/apps/docs/src/theme/DocSidebarItem/Html/index.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import React from "react"
|
||||
import clsx from "clsx"
|
||||
import { ThemeClassNames } from "@docusaurus/theme-common"
|
||||
import type { Props } from "@theme/DocSidebarItem/Html"
|
||||
import { ModifiedPropSidebarItemHtml } from "@medusajs/docs"
|
||||
import DocSidebarItemIcon from "../../../components/DocSidebarItemIcon"
|
||||
import { Badge } from "docs-ui"
|
||||
|
||||
type ModifiedProps = Props & {
|
||||
item: ModifiedPropSidebarItemHtml
|
||||
}
|
||||
|
||||
export default function DocSidebarItemHtml({
|
||||
item,
|
||||
level,
|
||||
index,
|
||||
}: ModifiedProps): JSX.Element {
|
||||
const { value, defaultStyle, className, customProps } = item
|
||||
|
||||
return (
|
||||
<li
|
||||
className={clsx(
|
||||
ThemeClassNames.docs.docSidebarItemLink,
|
||||
ThemeClassNames.docs.docSidebarItemLinkLevel(level),
|
||||
defaultStyle && ["lg:py-[6px] lg:px-1", "menu__list-item"],
|
||||
className,
|
||||
customProps?.sidebar_is_title && "sidebar-title",
|
||||
customProps?.sidebar_is_group_headline && "sidebar-group-headline",
|
||||
customProps?.sidebar_is_group_divider && "sidebar-group-divider",
|
||||
customProps?.sidebar_is_divider_line && "sidebar-divider-line",
|
||||
customProps?.sidebar_is_back_link && "sidebar-back-link",
|
||||
customProps?.sidebar_is_soon &&
|
||||
"sidebar-soon-link sidebar-badge-wrapper",
|
||||
!customProps?.sidebar_is_title &&
|
||||
"[&_.sidebar-item-icon]:w-[20px] [&_.sidebar-item-icon]:h-[20px]",
|
||||
!customProps?.sidebar_is_title &&
|
||||
!customProps?.sidebar_is_back_link &&
|
||||
"[&_.sidebar-item-icon]:mr-0.75"
|
||||
)}
|
||||
key={index}
|
||||
>
|
||||
{customProps?.sidebar_icon && (
|
||||
<DocSidebarItemIcon
|
||||
icon={customProps.sidebar_icon}
|
||||
is_title={customProps.sidebar_is_title}
|
||||
is_disabled={customProps?.sidebar_is_soon}
|
||||
/>
|
||||
)}
|
||||
<span
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{ __html: value }}
|
||||
></span>
|
||||
{customProps?.sidebar_is_soon && (
|
||||
<Badge variant="purple" className={`sidebar-soon-badge`}>
|
||||
Soon
|
||||
</Badge>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
78
www/apps/docs/src/theme/DocSidebarItem/Link/index.tsx
Normal file
78
www/apps/docs/src/theme/DocSidebarItem/Link/index.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import React from "react"
|
||||
import clsx from "clsx"
|
||||
import { ThemeClassNames } from "@docusaurus/theme-common"
|
||||
import { isActiveSidebarItem } from "@docusaurus/theme-common/internal"
|
||||
import Link from "@docusaurus/Link"
|
||||
import isInternalUrl from "@docusaurus/isInternalUrl"
|
||||
import IconExternalLink from "@theme/Icon/ExternalLink"
|
||||
import type { Props } from "@theme/DocSidebarItem/Link"
|
||||
import { ModifiedPropSidebarItemLink } from "@medusajs/docs"
|
||||
import DocSidebarItemIcon from "../../../components/DocSidebarItemIcon"
|
||||
import { Badge } from "docs-ui"
|
||||
|
||||
type ModifiedProps = Props & {
|
||||
item: ModifiedPropSidebarItemLink
|
||||
}
|
||||
|
||||
export default function DocSidebarItemLink({
|
||||
item,
|
||||
onItemClick,
|
||||
activePath,
|
||||
level,
|
||||
...props
|
||||
}: ModifiedProps): JSX.Element {
|
||||
const { href, label, className, autoAddBaseUrl, customProps } = item
|
||||
const isActive = isActiveSidebarItem(item, activePath)
|
||||
const isInternalLink = isInternalUrl(href)
|
||||
return (
|
||||
<li
|
||||
className={clsx(
|
||||
ThemeClassNames.docs.docSidebarItemLink,
|
||||
ThemeClassNames.docs.docSidebarItemLinkLevel(level),
|
||||
"menu__list-item",
|
||||
className,
|
||||
customProps?.sidebar_is_title && "sidebar-title",
|
||||
customProps?.sidebar_is_group_headline && "sidebar-group-headline",
|
||||
customProps?.sidebar_is_group_divider && "sidebar-group-divider",
|
||||
customProps?.sidebar_is_divider_line && "sidebar-divider-line",
|
||||
customProps?.sidebar_is_back_link && "sidebar-back-link",
|
||||
customProps?.sidebar_is_soon &&
|
||||
"sidebar-soon-link sidebar-badge-wrapper",
|
||||
!customProps?.sidebar_is_title &&
|
||||
"[&_.sidebar-item-icon]:w-[20px] [&_.sidebar-item-icon]:h-[20px]",
|
||||
!customProps?.sidebar_is_title &&
|
||||
!customProps?.sidebar_is_back_link &&
|
||||
"[&_.sidebar-item-icon]:mr-0.75"
|
||||
)}
|
||||
key={label}
|
||||
>
|
||||
<Link
|
||||
className={clsx("menu__link", !isInternalLink && "items-center", {
|
||||
"menu__link--active": isActive,
|
||||
})}
|
||||
autoAddBaseUrl={autoAddBaseUrl}
|
||||
aria-current={isActive ? "page" : undefined}
|
||||
to={href}
|
||||
{...(isInternalLink && {
|
||||
onClick: onItemClick ? () => onItemClick(item) : undefined,
|
||||
})}
|
||||
{...props}
|
||||
>
|
||||
{customProps?.sidebar_icon && (
|
||||
<DocSidebarItemIcon
|
||||
icon={customProps.sidebar_icon}
|
||||
is_title={customProps.sidebar_is_title}
|
||||
is_disabled={customProps?.sidebar_is_soon}
|
||||
/>
|
||||
)}
|
||||
{label}
|
||||
{!isInternalLink && <IconExternalLink />}
|
||||
</Link>
|
||||
{customProps?.sidebar_is_soon && (
|
||||
<Badge variant="purple" className={`sidebar-soon-badge`}>
|
||||
Soon
|
||||
</Badge>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
26
www/apps/docs/src/theme/EditThisPage/index.tsx
Normal file
26
www/apps/docs/src/theme/EditThisPage/index.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from "react"
|
||||
import Translate from "@docusaurus/Translate"
|
||||
import { ThemeClassNames } from "@docusaurus/theme-common"
|
||||
import type { Props } from "@theme/EditThisPage"
|
||||
import clsx from "clsx"
|
||||
import { Button } from "docs-ui"
|
||||
|
||||
export default function EditThisPage({ editUrl }: Props): JSX.Element {
|
||||
return (
|
||||
<Button variant="secondary">
|
||||
<a
|
||||
href={editUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className={clsx(ThemeClassNames.common.editThisPage)}
|
||||
>
|
||||
<Translate
|
||||
id="theme.common.editThisPage"
|
||||
description="The link label to edit the current page"
|
||||
>
|
||||
Edit this page
|
||||
</Translate>
|
||||
</a>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
51
www/apps/docs/src/theme/Footer/Layout/index.tsx
Normal file
51
www/apps/docs/src/theme/Footer/Layout/index.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import React from "react"
|
||||
import clsx from "clsx"
|
||||
import type { Props } from "@theme/Footer/Layout"
|
||||
import { useThemeConfig } from "@docusaurus/theme-common"
|
||||
import { ThemeConfig } from "@medusajs/docs"
|
||||
import SocialLinks from "@site/src/components/Footer/SocialLinks"
|
||||
|
||||
export default function FooterLayout({
|
||||
style,
|
||||
links,
|
||||
logo,
|
||||
copyright,
|
||||
}: Props): JSX.Element {
|
||||
const { socialLinks } = useThemeConfig() as ThemeConfig
|
||||
|
||||
return (
|
||||
<footer
|
||||
className={clsx(
|
||||
"footer",
|
||||
"border-t border-x-0 border-b-0 border-solid border-medusa-border-base dark:border-medusa-border-base-dark",
|
||||
"pt-[108px] pb-4 mt-2",
|
||||
{
|
||||
"footer--dark": style === "dark",
|
||||
}
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
"container container-fluid",
|
||||
"flex !px-0",
|
||||
"[&_.col]:!px-0",
|
||||
"lg:flex-row flex-col",
|
||||
"!pt-0"
|
||||
)}
|
||||
>
|
||||
{(logo || copyright || socialLinks) && (
|
||||
<div className="col col--6">
|
||||
<div className={clsx("lg:mb-0 mb-2")}>
|
||||
{logo && <div>{logo}</div>}
|
||||
{copyright}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className={clsx("col col--6 row lg:justify-end justify-start")}>
|
||||
{socialLinks && <SocialLinks links={socialLinks} />}
|
||||
{links}
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
26
www/apps/docs/src/theme/Footer/index.tsx
Normal file
26
www/apps/docs/src/theme/Footer/index.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from "react"
|
||||
|
||||
import { useThemeConfig } from "@docusaurus/theme-common"
|
||||
import FooterLinks from "@theme/Footer/Links"
|
||||
import FooterLogo from "@theme/Footer/Logo"
|
||||
import FooterCopyright from "@theme/Footer/Copyright"
|
||||
import FooterLayout from "@theme/Footer/Layout"
|
||||
|
||||
function Footer(): JSX.Element | null {
|
||||
const { footer } = useThemeConfig()
|
||||
if (!footer) {
|
||||
return null
|
||||
}
|
||||
const { copyright, links, logo, style } = footer
|
||||
|
||||
return (
|
||||
<FooterLayout
|
||||
style={style}
|
||||
links={links && links.length > 0 && <FooterLinks links={links} />}
|
||||
logo={logo && <FooterLogo logo={logo} />}
|
||||
copyright={copyright && <FooterCopyright copyright={copyright} />}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Footer)
|
||||
62
www/apps/docs/src/theme/Icon/CircleDottedLine/index.tsx
Normal file
62
www/apps/docs/src/theme/Icon/CircleDottedLine/index.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import { IconProps } from "@medusajs/icons/dist/types"
|
||||
import clsx from "clsx"
|
||||
import React from "react"
|
||||
|
||||
const IconCircleDottedLine = (props: IconProps) => {
|
||||
return (
|
||||
<svg
|
||||
width={props.width || 20}
|
||||
height={props.height || 20}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
className={clsx("text-ui-fg-subtle", props.className)}
|
||||
>
|
||||
<path
|
||||
d="M12.5 2.93589C10.884 2.3547 9.116 2.3547 7.5 2.93589"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M2.63049 8.63198C2.94295 6.94471 3.82573 5.41606 5.13094 4.30209"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M5.13094 15.6979C3.82575 14.5839 2.94298 13.0552 2.63049 11.368"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M7.5 17.0641C9.116 17.6453 10.884 17.6453 12.5 17.0641"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M17.3695 8.63198C17.057 6.94471 16.1742 5.41606 14.869 4.30209"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M14.869 15.6979C16.1742 14.5839 17.057 13.0552 17.3695 11.368"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconCircleDottedLine
|
||||
9
www/apps/docs/src/theme/Icon/Close/index.tsx
Normal file
9
www/apps/docs/src/theme/Icon/Close/index.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from "react"
|
||||
import { XMark } from "@medusajs/icons"
|
||||
import { IconProps } from "@medusajs/icons/dist/types"
|
||||
|
||||
const IconClose = (props: IconProps) => {
|
||||
return <XMark {...props} />
|
||||
}
|
||||
|
||||
export default IconClose
|
||||
9
www/apps/docs/src/theme/Icon/Copy/index.tsx
Normal file
9
www/apps/docs/src/theme/Icon/Copy/index.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from "react"
|
||||
import { SquareTwoStackSolid } from "@medusajs/icons"
|
||||
import { IconProps } from "@medusajs/icons/dist/types"
|
||||
|
||||
const IconCopy = (props: IconProps) => {
|
||||
return <SquareTwoStackSolid {...props} />
|
||||
}
|
||||
|
||||
export default IconCopy
|
||||
9
www/apps/docs/src/theme/Icon/DarkMode/index.tsx
Normal file
9
www/apps/docs/src/theme/Icon/DarkMode/index.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Moon } from "@medusajs/icons"
|
||||
import { IconProps } from "@medusajs/icons/dist/types"
|
||||
import React from "react"
|
||||
|
||||
const IconDarkMode = (props: IconProps) => {
|
||||
return <Moon {...props} />
|
||||
}
|
||||
|
||||
export default IconDarkMode
|
||||
24
www/apps/docs/src/theme/Icon/Discord/index.tsx
Normal file
24
www/apps/docs/src/theme/Icon/Discord/index.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { IconProps } from "@medusajs/icons/dist/types"
|
||||
import clsx from "clsx"
|
||||
import React from "react"
|
||||
|
||||
const IconDiscord = (props: IconProps) => {
|
||||
return (
|
||||
<svg
|
||||
width={props.width || 20}
|
||||
height={props.height || 20}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
className={clsx("text-ui-fg-subtle", props.className)}
|
||||
>
|
||||
<path
|
||||
d="M12.9953 12.4964C12.1086 12.4964 11.3775 11.6826 11.3775 10.6825C11.3775 9.68252 12.0938 8.86871 12.9953 8.86871C13.8968 8.86871 14.6268 9.6904 14.613 10.6825C14.5992 11.6747 13.9037 12.4964 12.9953 12.4964ZM7.01487 12.4964C6.12815 12.4964 5.3971 11.6826 5.3971 10.6825C5.3971 9.68252 6.11337 8.86871 7.01487 8.86871C7.91636 8.86871 8.64642 9.6904 8.63263 10.6825C8.61884 11.6747 7.91538 12.4964 7.01487 12.4964ZM16.2357 4.2765C15.069 3.741 13.8376 3.35915 12.5726 3.14052C12.5611 3.13824 12.5492 3.13968 12.5385 3.14464C12.5279 3.1496 12.5191 3.15782 12.5135 3.16811C12.3458 3.47256 12.1935 3.78528 12.0573 4.10507C10.6937 3.89797 9.3066 3.89797 7.94296 4.10507C7.80595 3.78447 7.65136 3.47168 7.4799 3.16811C7.47394 3.15812 7.46512 3.15016 7.45458 3.14524C7.44404 3.14032 7.43227 3.13868 7.42078 3.14052C6.15564 3.35869 4.92426 3.74056 3.75767 4.2765C3.74785 4.28081 3.73959 4.28803 3.73402 4.29719C1.40097 7.78297 0.761549 11.183 1.07387 14.5437C1.07476 14.552 1.07732 14.56 1.08138 14.5673C1.08545 14.5746 1.09093 14.5809 1.09752 14.5861C2.45592 15.5924 3.97543 16.3607 5.59119 16.858C5.60256 16.8614 5.61469 16.8613 5.62596 16.8576C5.63724 16.8539 5.64711 16.8468 5.65425 16.8373C6.00123 16.3649 6.30868 15.8647 6.57348 15.3417C6.57701 15.3346 6.57899 15.3267 6.57929 15.3187C6.5796 15.3107 6.57822 15.3028 6.57525 15.2953C6.57228 15.2879 6.56778 15.2812 6.56204 15.2756C6.55631 15.27 6.54946 15.2657 6.54195 15.2629C6.05702 15.0774 5.58757 14.8537 5.13798 14.5939C5.1293 14.5892 5.12196 14.5824 5.11666 14.5741C5.11135 14.5657 5.10826 14.5562 5.10766 14.5463C5.10707 14.5365 5.109 14.5266 5.11328 14.5177C5.11755 14.5088 5.12401 14.5011 5.13207 14.4954C5.2306 14.4245 5.32124 14.3516 5.4109 14.2767C5.41886 14.27 5.42854 14.2658 5.43883 14.2644C5.44911 14.263 5.45958 14.2645 5.46903 14.2688C8.41391 15.6137 11.6031 15.6137 14.5135 14.2688C14.523 14.2642 14.5336 14.2624 14.5441 14.2636C14.5546 14.2648 14.5645 14.269 14.5726 14.2757C14.6623 14.3496 14.7569 14.4245 14.8524 14.4944C14.8605 14.5001 14.8671 14.5077 14.8714 14.5165C14.8758 14.5254 14.8778 14.5352 14.8773 14.5451C14.8768 14.5549 14.8738 14.5645 14.8686 14.5729C14.8634 14.5812 14.8561 14.5882 14.8475 14.593C14.4006 14.8535 13.9327 15.0759 13.4485 15.258C13.4409 15.2608 13.4341 15.2653 13.4283 15.271C13.4226 15.2767 13.4182 15.2835 13.4153 15.291C13.4124 15.2986 13.4111 15.3066 13.4116 15.3147C13.4121 15.3228 13.4142 15.3306 13.4179 15.3378C13.6867 15.8581 13.9933 16.358 14.3352 16.8334C14.3421 16.8432 14.3519 16.8505 14.3632 16.8544C14.3745 16.8583 14.3868 16.8585 14.3983 16.8551C16.0168 16.3593 17.5388 15.591 18.8988 14.5831C18.9055 14.5783 18.911 14.5721 18.9151 14.565C18.9192 14.5578 18.9217 14.5499 18.9225 14.5417C19.2978 10.6599 18.2939 7.2874 16.2623 4.29522C16.2563 4.28616 16.2462 4.27953 16.2357 4.2765Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconDiscord
|
||||
9
www/apps/docs/src/theme/Icon/ExternalLink/index.tsx
Normal file
9
www/apps/docs/src/theme/Icon/ExternalLink/index.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { ArrowUpRightOnBox } from "@medusajs/icons"
|
||||
import { IconProps } from "@medusajs/icons/dist/types"
|
||||
import React from "react"
|
||||
|
||||
const IconExternalLink = (props: IconProps) => {
|
||||
return <ArrowUpRightOnBox {...props} />
|
||||
}
|
||||
|
||||
export default IconExternalLink
|
||||
26
www/apps/docs/src/theme/Icon/GitHub/index.tsx
Normal file
26
www/apps/docs/src/theme/Icon/GitHub/index.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { IconProps } from "@medusajs/icons/dist/types"
|
||||
import clsx from "clsx"
|
||||
import React from "react"
|
||||
|
||||
const IconGitHub = (props: IconProps) => {
|
||||
return (
|
||||
<svg
|
||||
width={props.width || 20}
|
||||
height={props.height || 20}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
className={clsx("text-ui-fg-subtle", props.className)}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10 1.22205C5.0275 1.22205 1 5.24955 1 10.222C1 14.2045 3.57625 17.5683 7.15375 18.7608C7.60375 18.8395 7.7725 18.5695 7.7725 18.3333C7.7725 18.1195 7.76125 17.4108 7.76125 16.657C5.5 17.0733 4.915 16.1058 4.735 15.5995C4.63375 15.3408 4.195 14.542 3.8125 14.3283C3.4975 14.1595 3.0475 13.7433 3.80125 13.732C4.51 13.7208 5.01625 14.3845 5.185 14.6545C5.995 16.0158 7.28875 15.6333 7.80625 15.397C7.885 14.812 8.12125 14.4183 8.38 14.1933C6.3775 13.9683 4.285 13.192 4.285 9.74955C4.285 8.7708 4.63375 7.9608 5.2075 7.3308C5.1175 7.1058 4.8025 6.1833 5.2975 4.9458C5.2975 4.9458 6.05125 4.70955 7.7725 5.8683C8.4925 5.6658 9.2575 5.56455 10.0225 5.56455C10.7875 5.56455 11.5525 5.6658 12.2725 5.8683C13.9938 4.6983 14.7475 4.9458 14.7475 4.9458C15.2425 6.1833 14.9275 7.1058 14.8375 7.3308C15.4113 7.9608 15.76 8.75955 15.76 9.74955C15.76 13.2033 13.6562 13.9683 11.6538 14.1933C11.98 14.4745 12.2613 15.0145 12.2613 15.8583C12.2613 17.062 12.25 18.0295 12.25 18.3333C12.25 18.5695 12.4188 18.8508 12.8688 18.7608C16.4238 17.5683 19 14.1933 19 10.222C19 5.24955 14.9725 1.22205 10 1.22205Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconGitHub
|
||||
9
www/apps/docs/src/theme/Icon/LightMode/index.tsx
Normal file
9
www/apps/docs/src/theme/Icon/LightMode/index.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from "react"
|
||||
import { Sun } from "@medusajs/icons"
|
||||
import { IconProps } from "@medusajs/icons/dist/types"
|
||||
|
||||
const IconLightMode = (props: IconProps) => {
|
||||
return <Sun {...props} />
|
||||
}
|
||||
|
||||
export default IconLightMode
|
||||
24
www/apps/docs/src/theme/Icon/LinkedIn/index.tsx
Normal file
24
www/apps/docs/src/theme/Icon/LinkedIn/index.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { IconProps } from "@medusajs/icons/dist/types"
|
||||
import clsx from "clsx"
|
||||
import React from "react"
|
||||
|
||||
const IconLinkedIn = (props: IconProps) => {
|
||||
return (
|
||||
<svg
|
||||
width={props.width || 20}
|
||||
height={props.height || 20}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
className={clsx("text-ui-fg-subtle", props.className)}
|
||||
>
|
||||
<path
|
||||
d="M15.25 1H4.75C2.67925 1 1 2.67925 1 4.75V15.25C1 17.3208 2.67925 19 4.75 19H15.25C17.3215 19 19 17.3208 19 15.25V4.75C19 2.67925 17.3215 1 15.25 1ZM7 15.25H4.75V7H7V15.25ZM5.875 6.049C5.1505 6.049 4.5625 5.4565 4.5625 4.726C4.5625 3.9955 5.1505 3.403 5.875 3.403C6.5995 3.403 7.1875 3.9955 7.1875 4.726C7.1875 5.4565 6.60025 6.049 5.875 6.049ZM16 15.25H13.75V11.047C13.75 8.521 10.75 8.71225 10.75 11.047V15.25H8.5V7H10.75V8.32375C11.797 6.38425 16 6.241 16 10.1808V15.25Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconLinkedIn
|
||||
28
www/apps/docs/src/theme/Icon/Nextjs/index.tsx
Normal file
28
www/apps/docs/src/theme/Icon/Nextjs/index.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { IconProps } from "@medusajs/icons/dist/types"
|
||||
import clsx from "clsx"
|
||||
import React from "react"
|
||||
|
||||
const IconNextjs = (props: IconProps) => {
|
||||
return (
|
||||
<svg
|
||||
width={props.width || 20}
|
||||
height={props.height || 20}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
className={clsx("text-ui-fg-subtle", props.className)}
|
||||
>
|
||||
<path
|
||||
d="M9.41129 1.01314C9.37262 1.01666 9.24959 1.02896 9.13885 1.03775C6.585 1.268 4.19285 2.64599 2.67777 4.76395C1.8341 5.94157 1.2945 7.27737 1.09062 8.69227C1.01855 9.18617 1.00977 9.33206 1.00977 10.0017C1.00977 10.6714 1.01855 10.8173 1.09062 11.3112C1.57924 14.6876 3.98194 17.5244 7.2406 18.5755C7.82414 18.7636 8.43931 18.8919 9.13885 18.9692C9.41129 18.9991 10.5889 18.9991 10.8613 18.9692C12.0688 18.8356 13.0918 18.5368 14.1007 18.0218C14.2553 17.9427 14.2852 17.9216 14.2641 17.9041C14.2501 17.8935 13.591 17.0094 12.8 15.9408L11.3623 13.9986L9.56069 11.3323C8.56938 9.86638 7.75383 8.66767 7.7468 8.66767C7.73977 8.66591 7.73274 9.85056 7.72923 11.2971C7.72395 13.8299 7.7222 13.9318 7.69056 13.9916C7.64486 14.0777 7.60971 14.1128 7.53589 14.1515C7.47964 14.1796 7.43043 14.1849 7.16502 14.1849H6.86095L6.7801 14.1339C6.72737 14.1005 6.6887 14.0566 6.66234 14.0056L6.62543 13.9265L6.62894 10.4025L6.63422 6.87663L6.6887 6.80808C6.71683 6.77117 6.77659 6.72372 6.81877 6.70087C6.89083 6.66571 6.91895 6.6622 7.22303 6.6622C7.58158 6.6622 7.64134 6.67626 7.7345 6.7782C7.76086 6.80632 8.73635 8.27571 9.90343 10.0457C11.0705 11.8156 12.6664 14.2324 13.4503 15.4188L14.874 17.5754L14.9461 17.5279C15.5841 17.1131 16.2591 16.5226 16.7934 15.9074C17.9306 14.6015 18.6635 13.009 18.9096 11.3112C18.9816 10.8173 18.9904 10.6714 18.9904 10.0017C18.9904 9.33206 18.9816 9.18617 18.9096 8.69227C18.421 5.31585 16.0183 2.47901 12.7596 1.42794C12.1848 1.24163 11.5732 1.11333 10.8877 1.03599C10.719 1.01841 9.55717 0.999079 9.41129 1.01314ZM13.0918 6.45128C13.1762 6.49346 13.2447 6.57432 13.2693 6.65868C13.2834 6.70438 13.2869 7.68163 13.2834 9.88395L13.2781 13.0442L12.7209 12.19L12.162 11.3358V9.03853C12.162 7.55332 12.169 6.71844 12.1796 6.67802C12.2077 6.57959 12.2692 6.50225 12.3536 6.45655C12.4256 6.41964 12.452 6.41613 12.728 6.41613C12.9881 6.41613 13.0338 6.41964 13.0918 6.45128Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M14.7861 17.6141C14.7246 17.6527 14.7053 17.6791 14.7598 17.6492C14.7984 17.6264 14.8617 17.5789 14.8512 17.5771C14.8459 17.5771 14.816 17.5947 14.7861 17.6141ZM14.6648 17.6932C14.6332 17.7178 14.6332 17.7195 14.6719 17.7002C14.693 17.6896 14.7105 17.6773 14.7105 17.6738C14.7105 17.6598 14.7018 17.6633 14.6648 17.6932ZM14.577 17.7459C14.5453 17.7705 14.5453 17.7722 14.584 17.7529C14.6051 17.7424 14.6227 17.7301 14.6227 17.7265C14.6227 17.7125 14.6139 17.716 14.577 17.7459ZM14.4891 17.7986C14.4574 17.8232 14.4574 17.825 14.4961 17.8056C14.5172 17.7951 14.5348 17.7828 14.5348 17.7793C14.5348 17.7652 14.526 17.7687 14.4891 17.7986ZM14.3555 17.8689C14.2887 17.9041 14.2922 17.9181 14.359 17.8847C14.3889 17.8689 14.4117 17.8531 14.4117 17.8496C14.4117 17.8373 14.41 17.839 14.3555 17.8689Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconNextjs
|
||||
24
www/apps/docs/src/theme/Icon/PuzzleSolid/index.tsx
Normal file
24
www/apps/docs/src/theme/Icon/PuzzleSolid/index.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { IconProps } from "@medusajs/icons/dist/types"
|
||||
import clsx from "clsx"
|
||||
import React from "react"
|
||||
|
||||
const IconPuzzleSolid = (props: IconProps) => {
|
||||
return (
|
||||
<svg
|
||||
width={props.width || 20}
|
||||
height={props.height || 20}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
className={clsx("text-ui-fg-subtle", props.className)}
|
||||
>
|
||||
<path
|
||||
d="M9.375 4.4475C9.375 4.15167 9.22 3.88417 9.04083 3.64833C8.85426 3.40948 8.752 3.11558 8.75 2.8125C8.75 1.94917 9.58917 1.25 10.625 1.25C11.6608 1.25 12.5 1.95 12.5 2.8125C12.5 3.12 12.3933 3.40667 12.2092 3.64833C12.03 3.88417 11.875 4.15167 11.875 4.4475C11.875 4.72417 12.1067 4.94583 12.3833 4.92917C13.975 4.83417 15.5417 4.64417 17.0767 4.36583C17.1603 4.35068 17.2461 4.35273 17.3289 4.37186C17.4117 4.39099 17.4897 4.42681 17.5582 4.47712C17.6267 4.52742 17.6842 4.59117 17.7272 4.66445C17.7702 4.73773 17.7978 4.81902 17.8083 4.90333C17.9969 6.4108 18.1015 7.92758 18.1217 9.44667C18.1224 9.51927 18.1088 9.59129 18.0814 9.65856C18.0541 9.72582 18.0137 9.78698 17.9625 9.83848C17.9113 9.88997 17.8504 9.93077 17.7833 9.9585C17.7162 9.98623 17.6443 10.0003 17.5717 10C17.2767 10 17.0092 9.845 16.7733 9.66583C16.5345 9.47926 16.2406 9.377 15.9375 9.375C15.075 9.375 14.375 10.2142 14.375 11.25C14.375 12.2858 15.075 13.125 15.9375 13.125C16.245 13.125 16.5317 13.0183 16.7733 12.8342C17.0092 12.655 17.2767 12.5 17.5725 12.5C17.8308 12.5 18.0367 12.7183 18.0175 12.9758C17.9197 14.3291 17.7542 15.6766 17.5217 17.0133C17.4996 17.1401 17.4389 17.2569 17.3479 17.3479C17.2569 17.4389 17.1401 17.4996 17.0133 17.5217C15.4967 17.7858 13.9525 17.9658 12.3842 18.0567C12.3185 18.0602 12.2528 18.0503 12.1911 18.0275C12.1294 18.0047 12.0729 17.9696 12.0253 17.9243C11.9776 17.879 11.9397 17.8244 11.9139 17.7639C11.888 17.7034 11.8748 17.6383 11.875 17.5725C11.875 17.2767 12.03 17.0092 12.2092 16.7733C12.3933 16.5317 12.5 16.245 12.5 15.9375C12.5 15.075 11.6608 14.375 10.625 14.375C9.58917 14.375 8.75 15.075 8.75 15.9375C8.75 16.245 8.85667 16.5317 9.04083 16.7733C9.22 17.0092 9.375 17.2767 9.375 17.5725C9.37525 17.644 9.36114 17.7148 9.33351 17.7808C9.30589 17.8467 9.2653 17.9064 9.21417 17.9564C9.16303 18.0064 9.10239 18.0456 9.03583 18.0717C8.96926 18.0978 8.89814 18.1102 8.82667 18.1083C7.51484 18.0713 6.20555 17.9712 4.90333 17.8083C4.81902 17.7978 4.73773 17.7702 4.66445 17.7272C4.59117 17.6842 4.52742 17.6267 4.47712 17.5582C4.42681 17.4897 4.39099 17.4117 4.37186 17.3289C4.35273 17.2461 4.35068 17.1603 4.36583 17.0767C4.61 15.7317 4.78583 14.3625 4.89 12.9733C4.89436 12.9127 4.88614 12.8517 4.86586 12.7944C4.84557 12.737 4.81365 12.6845 4.77211 12.64C4.73056 12.5956 4.68028 12.5602 4.62443 12.5361C4.56857 12.512 4.50833 12.4997 4.4475 12.5C4.15167 12.5 3.88417 12.655 3.64833 12.8342C3.40667 13.0183 3.12 13.125 2.8125 13.125C1.94917 13.125 1.25 12.2858 1.25 11.25C1.25 10.2142 1.95 9.375 2.8125 9.375C3.12 9.375 3.40667 9.48167 3.64833 9.66583C3.88417 9.845 4.15167 10 4.4475 10C4.51999 10.0003 4.59183 9.98625 4.65883 9.95856C4.72582 9.93087 4.78665 9.89013 4.83775 9.83872C4.88886 9.7873 4.92922 9.72624 4.95651 9.65907C4.98379 9.59191 4.99744 9.51999 4.99667 9.4475C4.97844 8.10511 4.89221 6.7645 4.73833 5.43083C4.72743 5.33729 4.7378 5.2425 4.76868 5.15353C4.79956 5.06457 4.85015 4.98373 4.91666 4.91706C4.98317 4.85039 5.06388 4.79961 5.15277 4.76852C5.24167 4.73742 5.33643 4.72682 5.43 4.7375C6.54917 4.86667 7.6825 4.94917 8.8275 4.9825C8.89886 4.9844 8.96988 4.97195 9.03634 4.94589C9.1028 4.91983 9.16336 4.8807 9.21441 4.83081C9.26547 4.78092 9.30599 4.72128 9.33358 4.65544C9.36116 4.5896 9.37525 4.51889 9.375 4.4475Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconPuzzleSolid
|
||||
24
www/apps/docs/src/theme/Icon/Twitter/index.tsx
Normal file
24
www/apps/docs/src/theme/Icon/Twitter/index.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { IconProps } from "@medusajs/icons/dist/types"
|
||||
import clsx from "clsx"
|
||||
import React from "react"
|
||||
|
||||
const IconTwitter = (props: IconProps) => {
|
||||
return (
|
||||
<svg
|
||||
width={props.width || 20}
|
||||
height={props.height || 20}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
className={clsx("text-ui-fg-subtle", props.className)}
|
||||
>
|
||||
<path
|
||||
d="M18.5921 4.33226C18.3093 4.45768 18.0186 4.56353 17.7215 4.64947C18.0732 4.25173 18.3413 3.78373 18.505 3.2716C18.5417 3.15681 18.5037 3.03116 18.4093 2.95608C18.3151 2.88094 18.1841 2.87194 18.0804 2.93344C17.4495 3.3076 16.7689 3.5765 16.0552 3.73374C15.3363 3.03127 14.3599 2.6315 13.3505 2.6315C11.2199 2.6315 9.4864 4.3649 9.4864 6.49551C9.4864 6.66332 9.49703 6.83019 9.51805 6.99475C6.87409 6.76261 4.41605 5.46307 2.72812 3.39255C2.66797 3.31875 2.5753 3.27898 2.48042 3.28658C2.38549 3.29402 2.30019 3.34755 2.25223 3.42983C1.90988 4.01726 1.72889 4.68913 1.72889 5.37272C1.72889 6.30378 2.06131 7.18717 2.64852 7.87744C2.46997 7.81561 2.2967 7.73832 2.13134 7.64652C2.04256 7.59711 1.93421 7.59786 1.84601 7.64844C1.75775 7.69901 1.70242 7.79203 1.70009 7.8937C1.69969 7.91083 1.69969 7.92796 1.69969 7.94532C1.69969 9.33509 2.44767 10.5863 3.59125 11.2683C3.493 11.2585 3.39482 11.2442 3.29727 11.2256C3.1967 11.2064 3.09329 11.2416 3.02547 11.3183C2.95754 11.395 2.93506 11.5018 2.96636 11.5994C3.38965 12.9209 4.47946 13.893 5.79694 14.1893C4.70423 14.8737 3.45462 15.2322 2.14283 15.2322C1.86912 15.2322 1.59384 15.2161 1.32442 15.1843C1.19058 15.1684 1.06261 15.2474 1.01703 15.3747C0.971445 15.502 1.01975 15.644 1.13362 15.7169C2.81882 16.7975 4.7674 17.3686 6.76859 17.3686C10.7027 17.3686 13.1637 15.5134 14.5355 13.9571C16.2461 12.0166 17.2271 9.44797 17.2271 6.91003C17.2271 6.80401 17.2255 6.69694 17.2223 6.59021C17.8971 6.08174 18.4782 5.46638 18.951 4.7591C19.0228 4.65168 19.0151 4.50971 18.9318 4.41083C18.8488 4.31188 18.7103 4.27989 18.5921 4.33226Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconTwitter
|
||||
210
www/apps/docs/src/theme/Icon/index.tsx
Normal file
210
www/apps/docs/src/theme/Icon/index.tsx
Normal file
@@ -0,0 +1,210 @@
|
||||
import {
|
||||
AcademicCapSolid,
|
||||
Adjustments,
|
||||
ArrowDownLeftMini,
|
||||
ArrowDownTray,
|
||||
ArrowUpRightOnBox,
|
||||
ArrowUturnLeft,
|
||||
BarsThree,
|
||||
BellAlert,
|
||||
BellAlertSolid,
|
||||
Bolt,
|
||||
BoltSolid,
|
||||
BookOpen,
|
||||
Bug,
|
||||
BugAntSolid,
|
||||
BuildingStorefront,
|
||||
BuildingTax,
|
||||
BuildingsSolid,
|
||||
Calendar,
|
||||
CashSolid,
|
||||
Channels,
|
||||
ChannelsSolid,
|
||||
CheckCircleSolid,
|
||||
CheckMini,
|
||||
ChevronDoubleLeftMiniSolid,
|
||||
ChevronUpDown,
|
||||
CircleDottedLine,
|
||||
CircleMiniSolid,
|
||||
CircleStack,
|
||||
CircleStackSolid,
|
||||
ClockSolidMini,
|
||||
CloudArrowUp,
|
||||
CogSixTooth,
|
||||
CogSixToothSolid,
|
||||
CommandLine,
|
||||
CommandLineSolid,
|
||||
ComponentSolid,
|
||||
ComputerDesktop,
|
||||
ComputerDesktopSolid,
|
||||
CreditCardSolid,
|
||||
CubeSolid,
|
||||
CurrencyDollar,
|
||||
CurrencyDollarSolid,
|
||||
Discord,
|
||||
DocumentText,
|
||||
DocumentTextSolid,
|
||||
EllipseMiniSolid,
|
||||
ExclamationCircle,
|
||||
ExclamationCircleSolid,
|
||||
FlyingBox,
|
||||
Folder,
|
||||
FolderOpen,
|
||||
Gatsby,
|
||||
GiftSolid,
|
||||
Github,
|
||||
GlobeEurope,
|
||||
GlobeEuropeSolid,
|
||||
InformationCircleSolid,
|
||||
JavascriptEx,
|
||||
Key,
|
||||
KeySolid,
|
||||
LightBulb,
|
||||
LightBulbSolid,
|
||||
Linkedin,
|
||||
MagnifyingGlass,
|
||||
Map,
|
||||
Moon,
|
||||
Newspaper,
|
||||
PencilSquareSolid,
|
||||
Puzzle,
|
||||
ReactJsEx,
|
||||
ReceiptPercent,
|
||||
RocketLaunch,
|
||||
Server,
|
||||
ServerSolid,
|
||||
ServerStack,
|
||||
ServerStackSolid,
|
||||
ShoppingCart,
|
||||
ShoppingCartSolid,
|
||||
Sidebar,
|
||||
Sparkles,
|
||||
SparklesSolid,
|
||||
SquareTwoStackSolid,
|
||||
SquaresPlus,
|
||||
SquaresPlusSolid,
|
||||
Star,
|
||||
StarSolid,
|
||||
Stripe,
|
||||
Sun,
|
||||
SwatchSolid,
|
||||
TagSolid,
|
||||
Tools,
|
||||
ToolsSolid,
|
||||
Twitter,
|
||||
User,
|
||||
UsersSolid,
|
||||
XCircleSolid,
|
||||
XMark,
|
||||
XMarkMini,
|
||||
PhotoSolid,
|
||||
} from "@medusajs/icons"
|
||||
import IconPuzzleSolid from "./PuzzleSolid"
|
||||
import IconNextjs from "./Nextjs"
|
||||
|
||||
export default {
|
||||
"academic-cap-solid": AcademicCapSolid,
|
||||
adjustments: Adjustments,
|
||||
alert: ExclamationCircleSolid,
|
||||
"arrow-down-left-mini": ArrowDownLeftMini,
|
||||
"arrow-down-tray": ArrowDownTray,
|
||||
"back-arrow": ArrowUturnLeft,
|
||||
"bars-three": BarsThree,
|
||||
bell: BellAlert,
|
||||
"bell-alert-solid": BellAlertSolid,
|
||||
bolt: Bolt,
|
||||
"bolt-solid": BoltSolid,
|
||||
"book-open": BookOpen,
|
||||
bug: Bug,
|
||||
"bug-ant-solid": BugAntSolid,
|
||||
"building-solid": BuildingsSolid,
|
||||
"building-storefront": BuildingStorefront,
|
||||
"building-tax": BuildingTax,
|
||||
calendar: Calendar,
|
||||
"cash-solid": CashSolid,
|
||||
"channels-solid": ChannelsSolid,
|
||||
channels: Channels,
|
||||
"check-circle-solid": CheckCircleSolid,
|
||||
"check-mini": CheckMini,
|
||||
"chevron-double-left-mini-solid": ChevronDoubleLeftMiniSolid,
|
||||
"chevron-up-down": ChevronUpDown,
|
||||
"circle-dotted-line": CircleDottedLine,
|
||||
"circle-mini-solid": CircleMiniSolid,
|
||||
"circle-stack": CircleStack,
|
||||
"circle-stack-solid": CircleStackSolid,
|
||||
"clock-solid-mini": ClockSolidMini,
|
||||
close: XMark,
|
||||
"cloud-arrow-up": CloudArrowUp,
|
||||
"cog-six-tooth": CogSixTooth,
|
||||
"cog-six-tooth-solid": CogSixToothSolid,
|
||||
"command-line": CommandLine,
|
||||
"command-line-solid": CommandLineSolid,
|
||||
"component-solid": ComponentSolid,
|
||||
"computer-desktop": ComputerDesktop,
|
||||
"computer-desktop-solid": ComputerDesktopSolid,
|
||||
copy: SquareTwoStackSolid,
|
||||
"credit-card-solid": CreditCardSolid,
|
||||
"cube-solid": CubeSolid,
|
||||
"currency-dollar": CurrencyDollar,
|
||||
"currency-dollar-solid": CurrencyDollarSolid,
|
||||
"dark-mode": Moon,
|
||||
discord: Discord,
|
||||
"document-text": DocumentText,
|
||||
"document-text-solid": DocumentTextSolid,
|
||||
"ellipse-mini-solid": EllipseMiniSolid,
|
||||
"exclamation-circle-solid": ExclamationCircleSolid,
|
||||
"external-link": ArrowUpRightOnBox,
|
||||
"flying-box": FlyingBox,
|
||||
folder: Folder,
|
||||
"folder-open": FolderOpen,
|
||||
gatsby: Gatsby,
|
||||
"gift-solid": GiftSolid,
|
||||
github: Github,
|
||||
"globe-europe": GlobeEurope,
|
||||
"globe-europe-solid": GlobeEuropeSolid,
|
||||
"information-circle-solid": InformationCircleSolid,
|
||||
javascript: JavascriptEx,
|
||||
key: Key,
|
||||
"key-solid": KeySolid,
|
||||
"light-bulb": LightBulb,
|
||||
"light-bulb-solid": LightBulbSolid,
|
||||
"light-mode": Sun,
|
||||
linkedin: Linkedin,
|
||||
"magnifying-glass": MagnifyingGlass,
|
||||
map: Map,
|
||||
newspaper: Newspaper,
|
||||
nextjs: IconNextjs,
|
||||
"pencil-square-solid": PencilSquareSolid,
|
||||
"photo-solid": PhotoSolid,
|
||||
puzzle: Puzzle,
|
||||
// TODO change once available in Icons package
|
||||
"puzzle-solid": IconPuzzleSolid,
|
||||
react: ReactJsEx,
|
||||
"receipt-percent": ReceiptPercent,
|
||||
report: ExclamationCircle,
|
||||
"rocket-launch": RocketLaunch,
|
||||
server: Server,
|
||||
"server-solid": ServerSolid,
|
||||
"server-stack": ServerStack,
|
||||
"server-stack-solid": ServerStackSolid,
|
||||
"shopping-cart": ShoppingCart,
|
||||
"shopping-cart-solid": ShoppingCartSolid,
|
||||
sidebar: Sidebar,
|
||||
sparkles: Sparkles,
|
||||
"sparkles-solid": SparklesSolid,
|
||||
"squares-plus": SquaresPlus,
|
||||
"squares-plus-solid": SquaresPlusSolid,
|
||||
star: Star,
|
||||
"star-solid": StarSolid,
|
||||
stripe: Stripe,
|
||||
"swatch-solid": SwatchSolid,
|
||||
"tag-solid": TagSolid,
|
||||
tools: Tools,
|
||||
"tools-solid": ToolsSolid,
|
||||
twitter: Twitter,
|
||||
user: User,
|
||||
"users-solid": UsersSolid,
|
||||
"x-circle-solid": XCircleSolid,
|
||||
"x-mark": XMark,
|
||||
"x-mark-mini": XMarkMini,
|
||||
}
|
||||
74
www/apps/docs/src/theme/Layout/index.tsx
Normal file
74
www/apps/docs/src/theme/Layout/index.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import React, { useEffect } from "react"
|
||||
import clsx from "clsx"
|
||||
import ErrorBoundary from "@docusaurus/ErrorBoundary"
|
||||
import {
|
||||
PageMetadata,
|
||||
SkipToContentFallbackId,
|
||||
ThemeClassNames,
|
||||
} from "@docusaurus/theme-common"
|
||||
import { useKeyboardNavigation } from "@docusaurus/theme-common/internal"
|
||||
import SkipToContent from "@theme/SkipToContent"
|
||||
import Navbar from "@theme/Navbar"
|
||||
import LayoutProvider from "@theme/Layout/Provider"
|
||||
import ErrorPageContent from "@theme/ErrorPageContent"
|
||||
import type { Props } from "@theme/Layout"
|
||||
import useIsBrowser from "@docusaurus/useIsBrowser"
|
||||
import { useLocation } from "@docusaurus/router"
|
||||
import { useAnalytics } from "docs-ui"
|
||||
|
||||
export default function Layout(props: Props): JSX.Element {
|
||||
const {
|
||||
children,
|
||||
wrapperClassName,
|
||||
// Not really layout-related, but kept for convenience/retro-compatibility
|
||||
title,
|
||||
description,
|
||||
} = props
|
||||
|
||||
useKeyboardNavigation()
|
||||
const isBrowser = useIsBrowser()
|
||||
const location = useLocation()
|
||||
const { track } = useAnalytics()
|
||||
|
||||
useEffect(() => {
|
||||
if (isBrowser) {
|
||||
const handlePlay = () => {
|
||||
track("video_played")
|
||||
}
|
||||
|
||||
const videos = document.querySelectorAll("video")
|
||||
videos.forEach((video) =>
|
||||
video.addEventListener("play", handlePlay, {
|
||||
once: true,
|
||||
capture: true,
|
||||
})
|
||||
)
|
||||
|
||||
return () => {
|
||||
videos.forEach((video) => video.removeEventListener("play", handlePlay))
|
||||
}
|
||||
}
|
||||
}, [isBrowser, location.pathname])
|
||||
|
||||
return (
|
||||
<LayoutProvider>
|
||||
<PageMetadata title={title} description={description} />
|
||||
<SkipToContent />
|
||||
|
||||
<Navbar />
|
||||
|
||||
<div
|
||||
id={SkipToContentFallbackId}
|
||||
className={clsx(
|
||||
ThemeClassNames.wrapper.main,
|
||||
"flex-auto flex-grow flex-shrink-0",
|
||||
wrapperClassName
|
||||
)}
|
||||
>
|
||||
<ErrorBoundary fallback={(params) => <ErrorPageContent {...params} />}>
|
||||
{children}
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</LayoutProvider>
|
||||
)
|
||||
}
|
||||
48
www/apps/docs/src/theme/MDXComponents/A.tsx
Normal file
48
www/apps/docs/src/theme/MDXComponents/A.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import React, { useMemo } from "react"
|
||||
import type { Props } from "@theme/MDXComponents/A"
|
||||
import { getGlossaryByPath } from "../../utils/glossary"
|
||||
import useDocusaurusContext from "@docusaurus/useDocusaurusContext"
|
||||
import { MedusaDocusaurusContext } from "@medusajs/docs"
|
||||
import Link from "@docusaurus/Link"
|
||||
import clsx from "clsx"
|
||||
import { Tooltip } from "docs-ui"
|
||||
|
||||
const MDXA = (props: Props) => {
|
||||
const { href, children } = props
|
||||
const {
|
||||
siteConfig: { url },
|
||||
} = useDocusaurusContext() as MedusaDocusaurusContext
|
||||
|
||||
// check if a glossary exists for href
|
||||
const glossary = useMemo(() => {
|
||||
return (typeof children === "string" && href.startsWith("/")) ||
|
||||
href.includes(url)
|
||||
? getGlossaryByPath(children as string)
|
||||
: null
|
||||
}, [href, children])
|
||||
|
||||
if (!glossary) {
|
||||
return <Link {...props} />
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
tooltipChildren={
|
||||
<span className="flex flex-col gap-0.25 max-w-[200px]">
|
||||
<span className="text-compact-small-plus text-medusa-fg-base dark:text-medusa-fg-base-dark">
|
||||
{glossary.title}
|
||||
</span>
|
||||
<span className="text-compact-small">{glossary.content}</span>
|
||||
</span>
|
||||
}
|
||||
clickable={true}
|
||||
>
|
||||
<Link
|
||||
{...props}
|
||||
className={clsx(props.className, "border-0 border-b border-dashed")}
|
||||
/>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export default MDXA
|
||||
24
www/apps/docs/src/theme/MDXComponents/Details.tsx
Normal file
24
www/apps/docs/src/theme/MDXComponents/Details.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import React, { type ComponentProps, type ReactElement } from "react"
|
||||
import type { Props } from "@theme/MDXComponents/Details"
|
||||
import Details, { DetailsProps } from "../Details"
|
||||
import { type DetailsSummaryProps } from "docs-ui"
|
||||
|
||||
type MDXDetailsProps = Props & DetailsProps
|
||||
|
||||
export default function MDXDetails(props: MDXDetailsProps): JSX.Element {
|
||||
const items = React.Children.toArray(props.children)
|
||||
// Split summary item from the rest to pass it as a separate prop to the
|
||||
// Details theme component
|
||||
const summary = items.find(
|
||||
(item): item is ReactElement<ComponentProps<"summary">> =>
|
||||
React.isValidElement(item) &&
|
||||
(item.props as { mdxType: string } | null)?.mdxType === "summary"
|
||||
) as React.ReactElement<DetailsSummaryProps>
|
||||
const children = <>{items.filter((item) => item !== summary)}</>
|
||||
|
||||
return (
|
||||
<Details summary={summary} {...props}>
|
||||
{children}
|
||||
</Details>
|
||||
)
|
||||
}
|
||||
17
www/apps/docs/src/theme/MDXComponents/index.tsx
Normal file
17
www/apps/docs/src/theme/MDXComponents/index.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
// Import the original mapper
|
||||
import MDXComponents from "@theme-original/MDXComponents"
|
||||
import CloudinaryImage from "@site/src/components/CloudinaryImage"
|
||||
import MDXDetails from "./Details"
|
||||
import MDXA from "./A"
|
||||
import { Kbd, DetailsSummary, InlineCode } from "docs-ui"
|
||||
|
||||
export default {
|
||||
// Re-use the default mapping
|
||||
...MDXComponents,
|
||||
inlineCode: InlineCode,
|
||||
img: CloudinaryImage,
|
||||
details: MDXDetails,
|
||||
summary: DetailsSummary,
|
||||
a: MDXA,
|
||||
kbd: Kbd,
|
||||
}
|
||||
79
www/apps/docs/src/theme/MobileLogo/index.tsx
Normal file
79
www/apps/docs/src/theme/MobileLogo/index.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import React from "react"
|
||||
import Link from "@docusaurus/Link"
|
||||
import useBaseUrl from "@docusaurus/useBaseUrl"
|
||||
import useDocusaurusContext from "@docusaurus/useDocusaurusContext"
|
||||
import { useThemeConfig, type NavbarLogo } from "@docusaurus/theme-common"
|
||||
import ThemedImage from "@theme/ThemedImage"
|
||||
import type { Props } from "@theme/Logo"
|
||||
import { ThemeConfig } from "@medusajs/docs"
|
||||
|
||||
function LogoThemedImage({
|
||||
logo,
|
||||
alt,
|
||||
imageClassName,
|
||||
}: {
|
||||
logo: NavbarLogo
|
||||
alt: string
|
||||
imageClassName?: string
|
||||
}) {
|
||||
const sources = {
|
||||
light: useBaseUrl(logo.src),
|
||||
dark: useBaseUrl(logo.srcDark || logo.src),
|
||||
}
|
||||
const themedImage = (
|
||||
<ThemedImage
|
||||
className={logo.className}
|
||||
sources={sources}
|
||||
height={logo.height}
|
||||
width={logo.width}
|
||||
alt={alt}
|
||||
style={logo.style}
|
||||
/>
|
||||
)
|
||||
|
||||
// Is this extra div really necessary?
|
||||
// introduced in https://github.com/facebook/docusaurus/pull/5666
|
||||
return imageClassName ? (
|
||||
<div className={imageClassName}>{themedImage}</div>
|
||||
) : (
|
||||
themedImage
|
||||
)
|
||||
}
|
||||
|
||||
export default function MobileLogo(props: Props): JSX.Element {
|
||||
const {
|
||||
siteConfig: { title },
|
||||
} = useDocusaurusContext()
|
||||
const {
|
||||
navbar: { title: navbarTitle },
|
||||
mobileLogo: logo,
|
||||
} = useThemeConfig() as ThemeConfig
|
||||
|
||||
const { imageClassName, titleClassName, ...propsRest } = props
|
||||
const logoLink = useBaseUrl(logo?.href || "/")
|
||||
|
||||
// If visible title is shown, fallback alt text should be
|
||||
// an empty string to mark the logo as decorative.
|
||||
const fallbackAlt = navbarTitle ? "" : title
|
||||
|
||||
// Use logo alt text if provided (including empty string),
|
||||
// and provide a sensible fallback otherwise.
|
||||
const alt = logo?.alt ?? fallbackAlt
|
||||
|
||||
return (
|
||||
<Link
|
||||
to={logoLink}
|
||||
{...propsRest}
|
||||
{...(logo?.target && { target: logo.target })}
|
||||
>
|
||||
{logo && (
|
||||
<LogoThemedImage
|
||||
logo={logo}
|
||||
alt={alt}
|
||||
imageClassName={imageClassName}
|
||||
/>
|
||||
)}
|
||||
{navbarTitle != null && <b className={titleClassName}>{navbarTitle}</b>}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
27
www/apps/docs/src/theme/Navbar/ColorModeToggle/index.tsx
Normal file
27
www/apps/docs/src/theme/Navbar/ColorModeToggle/index.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import React from "react"
|
||||
import { useColorMode, useThemeConfig } from "@docusaurus/theme-common"
|
||||
import ColorModeToggle from "@theme/ColorModeToggle"
|
||||
import type { Props } from "@theme/Navbar/ColorModeToggle"
|
||||
import clsx from "clsx"
|
||||
|
||||
export default function NavbarColorModeToggle({
|
||||
className,
|
||||
}: Props): JSX.Element | null {
|
||||
const disabled = useThemeConfig().colorMode.disableSwitch
|
||||
const { colorMode, setColorMode } = useColorMode()
|
||||
|
||||
if (disabled) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<ColorModeToggle
|
||||
className={clsx("text-ui-fg-muted", className)}
|
||||
buttonClassName={clsx(
|
||||
"hover:!bg-medusa-button-neutral-hover dark:hover:!bg-medusa-button-neutral-hover-dark"
|
||||
)}
|
||||
value={colorMode}
|
||||
onChange={setColorMode}
|
||||
/>
|
||||
)
|
||||
}
|
||||
167
www/apps/docs/src/theme/Navbar/Content/index.tsx
Normal file
167
www/apps/docs/src/theme/Navbar/Content/index.tsx
Normal file
@@ -0,0 +1,167 @@
|
||||
import React, { type ReactNode } from "react"
|
||||
import { useThemeConfig } from "@docusaurus/theme-common"
|
||||
import {
|
||||
splitNavbarItems,
|
||||
useNavbarMobileSidebar,
|
||||
} from "@docusaurus/theme-common/internal"
|
||||
import NavbarItem, { type Props as NavbarItemConfig } from "@theme/NavbarItem"
|
||||
import NavbarColorModeToggle from "@theme/Navbar/ColorModeToggle"
|
||||
import NavbarMobileSidebarToggle from "@theme/Navbar/MobileSidebar/Toggle"
|
||||
import NavbarLogo from "@theme/Navbar/Logo"
|
||||
import NavbarActions from "../../../components/Navbar/Actions"
|
||||
import { ThemeConfig } from "@medusajs/docs"
|
||||
import useIsBrowser from "@docusaurus/useIsBrowser"
|
||||
import clsx from "clsx"
|
||||
import { useSidebar } from "../../../providers/Sidebar"
|
||||
import { Tooltip } from "docs-ui"
|
||||
import { ChevronDoubleLeftMiniSolid, Sidebar } from "@medusajs/icons"
|
||||
|
||||
function useNavbarItems() {
|
||||
// TODO temporary casting until ThemeConfig type is improved
|
||||
return useThemeConfig().navbar.items as NavbarItemConfig[]
|
||||
}
|
||||
|
||||
function NavbarItems({ items }: { items: NavbarItemConfig[] }): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
{items.map((item, i) => (
|
||||
<NavbarItem {...item} key={i} />
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function NavbarContentLayout({
|
||||
left,
|
||||
right,
|
||||
}: {
|
||||
left: ReactNode
|
||||
right: ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"flex flex-wrap justify-between items-center w-full",
|
||||
"lg:max-w-xl mx-auto py-0.5 px-1"
|
||||
)}
|
||||
>
|
||||
<div className={clsx("items-center flex flex-1 min-w-0 gap-1.5")}>
|
||||
{left}
|
||||
</div>
|
||||
<div
|
||||
className={clsx("items-center flex lg:flex-1 min-w-0", "justify-end")}
|
||||
>
|
||||
{right}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function NavbarContent(): JSX.Element {
|
||||
const mobileSidebar = useNavbarMobileSidebar()
|
||||
|
||||
const items = useNavbarItems()
|
||||
const [leftItems, rightItems] = splitNavbarItems(items)
|
||||
const {
|
||||
navbarActions,
|
||||
docs: {
|
||||
sidebar: { hideable },
|
||||
},
|
||||
} = useThemeConfig() as ThemeConfig
|
||||
const sidebarContext = useSidebar()
|
||||
const isBrowser = useIsBrowser()
|
||||
|
||||
const isApple = isBrowser
|
||||
? navigator.userAgent.toLowerCase().indexOf("mac") !== 0
|
||||
: true
|
||||
|
||||
return (
|
||||
<NavbarContentLayout
|
||||
left={
|
||||
// TODO stop hardcoding items?
|
||||
<>
|
||||
{!mobileSidebar.disabled && <NavbarMobileSidebarToggle />}
|
||||
<NavbarLogo />
|
||||
<NavbarItems items={leftItems} />
|
||||
</>
|
||||
}
|
||||
right={
|
||||
// TODO stop hardcoding items?
|
||||
// Ask the user to add the respective navbar items => more flexible
|
||||
<div className="flex gap-0.5">
|
||||
<NavbarItems items={rightItems} />
|
||||
{hideable && sidebarContext?.hasSidebar && (
|
||||
<NavbarActions
|
||||
items={[
|
||||
{
|
||||
type: "button",
|
||||
html: !sidebarContext?.hiddenSidebarContainer
|
||||
? `<span class="text-compact-x-small-plus">Close sidebar <kbd class="${clsx(
|
||||
"bg-medusa-tag-neutral-bg dark:bg-medusa-tag-neutral-bg-dark",
|
||||
"border border-solid rounded border-medusa-tag-neutral-border dark:border-medusa-tag-neutral-border-dark",
|
||||
"text-medusa-fg-subtle dark:text-medusa-fg-subtle-dark font-base text-compact-x-small-plus",
|
||||
"inline-flex w-[22px] h-[22px] !p-0 justify-center items-center shadow-none ml-0.5"
|
||||
)}">${isApple ? "⌘" : "Ctrl"}</kbd>
|
||||
<kbd class="${clsx(
|
||||
"bg-medusa-tag-neutral-bg dark:bg-medusa-tag-neutral-bg-dark",
|
||||
"border border-solid rounded border-medusa-tag-neutral-border dark:border-medusa-tag-neutral-border-dark",
|
||||
"text-medusa-fg-subtle dark:text-medusa-fg-subtle-dark font-base text-compact-x-small-plus",
|
||||
"inline-flex w-[22px] h-[22px] !p-0 justify-center items-center shadow-none"
|
||||
)}">I</kbd></span>`
|
||||
: `<span class="text-compact-x-small-plus">Lock sidebar open <kbd class="${clsx(
|
||||
"bg-medusa-tag-neutral-bg dark:bg-medusa-tag-neutral-bg-dark",
|
||||
"border border-solid rounded border-medusa-tag-neutral-border dark:border-medusa-tag-neutral-border-dark",
|
||||
"text-medusa-fg-subtle dark:text-medusa-fg-subtle-dark font-base text-compact-x-small-plus",
|
||||
"inline-flex w-[22px] h-[22px] !p-0 justify-center items-center shadow-none ml-0.5"
|
||||
)}">${isApple ? "⌘" : "Ctrl"}</kbd>
|
||||
<kbd class="${clsx(
|
||||
"bg-medusa-tag-neutral-bg dark:bg-medusa-tag-neutral-bg-dark",
|
||||
"border border-solid rounded border-medusa-tag-neutral-border dark:border-medusa-tag-neutral-border-dark",
|
||||
"text-medusa-fg-subtle dark:text-medusa-fg-subtle-dark font-base text-compact-x-small-plus",
|
||||
"inline-flex w-[22px] h-[22px] !p-0 justify-center items-center shadow-none"
|
||||
)}">I</kbd></span>`,
|
||||
events: {
|
||||
onClick: sidebarContext?.onCollapse,
|
||||
onMouseEnter: () => {
|
||||
if (!sidebarContext?.hiddenSidebarContainer) {
|
||||
sidebarContext?.setFloatingSidebar(false)
|
||||
} else {
|
||||
sidebarContext?.setFloatingSidebar(true)
|
||||
}
|
||||
},
|
||||
onMouseLeave: () => {
|
||||
setTimeout(() => {
|
||||
if (
|
||||
!document.querySelector(
|
||||
".theme-doc-sidebar-container:hover"
|
||||
)
|
||||
) {
|
||||
sidebarContext?.setFloatingSidebar(false)
|
||||
}
|
||||
}, 100)
|
||||
},
|
||||
},
|
||||
Icon: !sidebarContext?.hiddenSidebarContainer ? (
|
||||
<Sidebar className="text-medusa-fg-muted dark:text-medusa-fg-muted-dark" />
|
||||
) : (
|
||||
<ChevronDoubleLeftMiniSolid className="text-medusa-fg-muted dark:text-medusa-fg-muted-dark" />
|
||||
),
|
||||
buttonType: "icon",
|
||||
},
|
||||
]}
|
||||
className="sidebar-toggler"
|
||||
/>
|
||||
)}
|
||||
<Tooltip text="Switch theme">
|
||||
<NavbarColorModeToggle
|
||||
className={clsx(
|
||||
"navbar-action-icon-item !w-2 !h-2 [&>button]:!rounded"
|
||||
)}
|
||||
/>
|
||||
</Tooltip>
|
||||
<NavbarActions items={navbarActions} />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
44
www/apps/docs/src/theme/Navbar/Layout/index.tsx
Normal file
44
www/apps/docs/src/theme/Navbar/Layout/index.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React from "react"
|
||||
import clsx from "clsx"
|
||||
import { useThemeConfig } from "@docusaurus/theme-common"
|
||||
import {
|
||||
useHideableNavbar,
|
||||
useNavbarMobileSidebar,
|
||||
} from "@docusaurus/theme-common/internal"
|
||||
import { translate } from "@docusaurus/Translate"
|
||||
import NavbarMobileSidebar from "@theme/Navbar/MobileSidebar"
|
||||
import type { Props } from "@theme/Navbar/Layout"
|
||||
|
||||
export default function NavbarLayout({ children }: Props): JSX.Element {
|
||||
const {
|
||||
navbar: { hideOnScroll, style },
|
||||
} = useThemeConfig()
|
||||
const mobileSidebar = useNavbarMobileSidebar()
|
||||
const { navbarRef, isNavbarVisible } = useHideableNavbar(hideOnScroll)
|
||||
return (
|
||||
<nav
|
||||
ref={navbarRef}
|
||||
aria-label={translate({
|
||||
id: "theme.NavBar.navAriaLabel",
|
||||
message: "Main",
|
||||
description: "The ARIA label for the main navigation",
|
||||
})}
|
||||
className={clsx(
|
||||
"navbar",
|
||||
"navbar--fixed-top",
|
||||
hideOnScroll && [
|
||||
"transition-transform",
|
||||
!isNavbarVisible && "translate-x-0 translate-y-[calc(-100%-2px)]",
|
||||
],
|
||||
{
|
||||
"navbar--dark": style === "dark",
|
||||
"navbar--primary": style === "primary",
|
||||
"navbar-sidebar--show": mobileSidebar.shown,
|
||||
}
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
<NavbarMobileSidebar />
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
20
www/apps/docs/src/theme/Navbar/Logo/index.tsx
Normal file
20
www/apps/docs/src/theme/Navbar/Logo/index.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from "react"
|
||||
import Logo from "@theme/Logo"
|
||||
import MobileLogo from "../../MobileLogo"
|
||||
|
||||
export default function NavbarLogo(): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<Logo
|
||||
className="navbar__brand hidden lg:block"
|
||||
imageClassName="navbar__logo"
|
||||
titleClassName="navbar__title text--truncate"
|
||||
/>
|
||||
<MobileLogo
|
||||
className="navbar__brand lg:hidden mx-auto"
|
||||
imageClassName="navbar__logo"
|
||||
titleClassName="navbar__title text--truncate"
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import React from "react"
|
||||
import { useNavbarMobileSidebar } from "@docusaurus/theme-common/internal"
|
||||
import NavbarColorModeToggle from "@theme/Navbar/ColorModeToggle"
|
||||
import IconClose from "@theme/Icon/Close"
|
||||
import NavbarLogo from "@theme/Navbar/Logo"
|
||||
import clsx from "clsx"
|
||||
|
||||
function CloseButton() {
|
||||
const mobileSidebar = useNavbarMobileSidebar()
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={clsx(
|
||||
"bg-transparent border-0 text-inherit cursor-pointer p-0",
|
||||
"flex ml-auto"
|
||||
)}
|
||||
onClick={() => mobileSidebar.toggle()}
|
||||
>
|
||||
<IconClose color="var(--ifm-color-emphasis-600)" />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default function NavbarMobileSidebarHeader(): JSX.Element {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"items-center shadow-navbar dark:shadow-navbar-dark",
|
||||
"flex flex-1 h-navbar py-0.75 px-1.5"
|
||||
)}
|
||||
>
|
||||
<NavbarLogo />
|
||||
<NavbarColorModeToggle
|
||||
className={clsx(
|
||||
"[&>button]:hover:bg-medusa-button-neutral-hover dark:[&>button]:hover:bg-medusa-button-neutral-hover-dark",
|
||||
"[&>button]:!rounded"
|
||||
)}
|
||||
/>
|
||||
<CloseButton />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from "react"
|
||||
import clsx from "clsx"
|
||||
import { useNavbarSecondaryMenu } from "@docusaurus/theme-common/internal"
|
||||
import type { Props } from "@theme/Navbar/MobileSidebar/Layout"
|
||||
|
||||
export default function NavbarMobileSidebarLayout({
|
||||
primaryMenu,
|
||||
secondaryMenu,
|
||||
}: Props): JSX.Element {
|
||||
const { shown: secondaryMenuShown } = useNavbarSecondaryMenu()
|
||||
return (
|
||||
<div className="navbar-sidebar top-[57px] shadow-none">
|
||||
<div
|
||||
className={clsx("navbar-sidebar__items", {
|
||||
"navbar-sidebar__items--show-secondary": secondaryMenuShown,
|
||||
})}
|
||||
>
|
||||
<div className="navbar-sidebar__item menu">{primaryMenu}</div>
|
||||
<div className="navbar-sidebar__item menu">{secondaryMenu}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from "react"
|
||||
import { useNavbarMobileSidebar } from "@docusaurus/theme-common/internal"
|
||||
import { translate } from "@docusaurus/Translate"
|
||||
import { Sidebar, XMark } from "@medusajs/icons"
|
||||
|
||||
export default function MobileSidebarToggle(): JSX.Element {
|
||||
const { toggle, shown } = useNavbarMobileSidebar()
|
||||
return (
|
||||
<button
|
||||
onClick={toggle}
|
||||
aria-label={translate({
|
||||
id: "theme.docs.sidebar.toggleSidebarButtonAriaLabel",
|
||||
message: "Toggle navigation bar",
|
||||
description:
|
||||
"The ARIA label for hamburger menu button of mobile navigation",
|
||||
})}
|
||||
aria-expanded={shown}
|
||||
className="navbar__toggle !block lg:!hidden clean-btn"
|
||||
type="button"
|
||||
>
|
||||
{!shown && (
|
||||
<Sidebar className="text-medusa-fg-muted dark:text-medusa-fg-muted-dark" />
|
||||
)}
|
||||
{shown && (
|
||||
<XMark className="text-medusa-fg-muted dark:text-medusa-fg-muted-dark" />
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
10
www/apps/docs/src/theme/Navbar/Search/index.tsx
Normal file
10
www/apps/docs/src/theme/Navbar/Search/index.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from "react"
|
||||
import clsx from "clsx"
|
||||
import type { Props } from "@theme/Navbar/Search"
|
||||
|
||||
export default function NavbarSearch({
|
||||
children,
|
||||
className,
|
||||
}: Props): JSX.Element {
|
||||
return <div className={clsx("flex", className)}>{children}</div>
|
||||
}
|
||||
55
www/apps/docs/src/theme/NavbarItem/NavbarNavLink.tsx
Normal file
55
www/apps/docs/src/theme/NavbarItem/NavbarNavLink.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import React from "react"
|
||||
import Link from "@docusaurus/Link"
|
||||
import useBaseUrl from "@docusaurus/useBaseUrl"
|
||||
import { isRegexpStringMatch } from "@docusaurus/theme-common"
|
||||
import type { Props } from "@theme/NavbarItem/NavbarNavLink"
|
||||
|
||||
export default function NavbarNavLink({
|
||||
activeBasePath,
|
||||
activeBaseRegex,
|
||||
to,
|
||||
href,
|
||||
label,
|
||||
html,
|
||||
prependBaseUrlToHref,
|
||||
...props
|
||||
}: Props): JSX.Element {
|
||||
// TODO all this seems hacky
|
||||
// {to: 'version'} should probably be forbidden, in favor of {to: '/version'}
|
||||
const toUrl = useBaseUrl(to)
|
||||
const activeBaseUrl = useBaseUrl(activeBasePath)
|
||||
const normalizedHref = useBaseUrl(href, { forcePrependBaseUrl: true })
|
||||
|
||||
// Link content is set through html XOR label
|
||||
const linkContentProps = html
|
||||
? { dangerouslySetInnerHTML: { __html: html } }
|
||||
: {
|
||||
children: <>{label}</>,
|
||||
}
|
||||
|
||||
if (href) {
|
||||
return (
|
||||
<Link
|
||||
href={prependBaseUrlToHref ? normalizedHref : href}
|
||||
{...props}
|
||||
{...linkContentProps}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
to={toUrl}
|
||||
isNavLink
|
||||
{...((activeBasePath || activeBaseRegex) && {
|
||||
isActive: (_match, location) => {
|
||||
return activeBaseRegex
|
||||
? isRegexpStringMatch(activeBaseRegex, location.pathname)
|
||||
: location.pathname.startsWith(activeBaseUrl)
|
||||
},
|
||||
})}
|
||||
{...props}
|
||||
{...linkContentProps}
|
||||
/>
|
||||
)
|
||||
}
|
||||
17
www/apps/docs/src/theme/NavbarItem/SearchNavbarItem.tsx
Normal file
17
www/apps/docs/src/theme/NavbarItem/SearchNavbarItem.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from "react"
|
||||
import NavbarSearch from "@theme/Navbar/Search"
|
||||
import type { Props } from "@theme/NavbarItem/SearchNavbarItem"
|
||||
import { SearchModalOpener } from "docs-ui"
|
||||
import { useWindowSize } from "@docusaurus/theme-common"
|
||||
|
||||
export default function SearchNavbarItem({
|
||||
mobile,
|
||||
}: Props): JSX.Element | null {
|
||||
const windowSize = useWindowSize()
|
||||
|
||||
return (
|
||||
<NavbarSearch>
|
||||
<SearchModalOpener isMobile={mobile || windowSize !== "desktop"} />
|
||||
</NavbarSearch>
|
||||
)
|
||||
}
|
||||
86
www/apps/docs/src/theme/NotFound.tsx
Normal file
86
www/apps/docs/src/theme/NotFound.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import React from "react"
|
||||
import Translate, { translate } from "@docusaurus/Translate"
|
||||
import { PageMetadata } from "@docusaurus/theme-common"
|
||||
import Layout from "@theme/Layout"
|
||||
import useBaseUrl from "@docusaurus/useBaseUrl"
|
||||
import DocsProviders from "../providers/DocsProviders"
|
||||
|
||||
export default function NotFound(): JSX.Element {
|
||||
return (
|
||||
<DocsProviders>
|
||||
<PageMetadata
|
||||
title={translate({
|
||||
id: "theme.NotFound.title",
|
||||
message: "Page Not Found",
|
||||
})}
|
||||
/>
|
||||
<Layout>
|
||||
<main className="container markdown theme-doc-markdown my-4">
|
||||
<div className="row">
|
||||
<div className="col col--6 col--offset-3">
|
||||
<h1>
|
||||
<Translate
|
||||
id="theme.NotFound.title"
|
||||
description="The title of the 404 page"
|
||||
>
|
||||
Page Not Found
|
||||
</Translate>
|
||||
</h1>
|
||||
<p>
|
||||
<Translate
|
||||
id="theme.NotFound.p1"
|
||||
description="The first paragraph of the 404 page"
|
||||
>
|
||||
Looks like the page you're looking for has either changed
|
||||
into a different location or isn't in our documentation
|
||||
anymore.
|
||||
</Translate>
|
||||
</p>
|
||||
<p>
|
||||
If you think this is a mistake, please{" "}
|
||||
<a
|
||||
href="https://github.com/medusajs/medusa/issues/new?assignees=&labels=type%3A+docs&template=docs.yml"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
report this issue on GitHub
|
||||
</a>
|
||||
</p>
|
||||
<h2>Some popular links</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a href={useBaseUrl("/usage/create-medusa-app")}>
|
||||
Install Medusa with create-medusa-app
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://docs.medusajs.com/api/store">
|
||||
Storefront REST API Reference
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://docs.medusajs.com/api/admin">
|
||||
Admin REST API Reference
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href={useBaseUrl("/starters/nextjs-medusa-starter")}>
|
||||
Install Next.js Storefront
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href={useBaseUrl("/admin/quickstart")}>
|
||||
Install Medusa Admin
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href={useBaseUrl("/user-guide")}>User Guide</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</Layout>
|
||||
</DocsProviders>
|
||||
)
|
||||
}
|
||||
51
www/apps/docs/src/theme/SearchBar/index.tsx
Normal file
51
www/apps/docs/src/theme/SearchBar/index.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import React, { useEffect } from "react"
|
||||
import SearchBar from "@theme-original/SearchBar"
|
||||
import type SearchBarType from "@theme/SearchBar"
|
||||
import type { WrapperProps } from "@docusaurus/types"
|
||||
import useIsBrowser from "@docusaurus/useIsBrowser"
|
||||
import { useLocation } from "@docusaurus/router"
|
||||
import { useAnalytics } from "docs-ui"
|
||||
|
||||
type Props = WrapperProps<typeof SearchBarType>
|
||||
|
||||
export default function SearchBarWrapper(props: Props): JSX.Element {
|
||||
const isBrowser = useIsBrowser()
|
||||
const location = useLocation()
|
||||
const { track } = useAnalytics()
|
||||
|
||||
useEffect(() => {
|
||||
if (isBrowser) {
|
||||
const trackSearch = (e) => {
|
||||
if (
|
||||
!e.target.classList?.contains("DocSearch-Input") &&
|
||||
!(
|
||||
e.target.tagName.toLowerCase() === "input" &&
|
||||
e.target.getAttribute("type") === "search"
|
||||
)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const query = e.target.value
|
||||
if (query.length >= 3) {
|
||||
// send event to segment
|
||||
track("search", {
|
||||
query,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
document.body.addEventListener("keyup", trackSearch)
|
||||
|
||||
return () => {
|
||||
document.body.removeEventListener("keyup", trackSearch)
|
||||
}
|
||||
}
|
||||
}, [isBrowser, location.pathname])
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchBar {...props} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
12
www/apps/docs/src/theme/SearchTranslations/index.ts
Normal file
12
www/apps/docs/src/theme/SearchTranslations/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { translate } from "@docusaurus/Translate"
|
||||
import translations from "@theme-original/SearchTranslations"
|
||||
|
||||
const changedTranslations = {
|
||||
...translations,
|
||||
placeholder: translate({
|
||||
id: "theme.SearchModal.placeholder",
|
||||
message: "Find something",
|
||||
description: "The placeholder of the input of the DocSearch pop-up modal",
|
||||
}),
|
||||
}
|
||||
export default changedTranslations
|
||||
22
www/apps/docs/src/theme/TOCItems/index.tsx
Normal file
22
www/apps/docs/src/theme/TOCItems/index.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import React from "react"
|
||||
import TOCItems from "@theme-original/TOCItems"
|
||||
import type TOCItemsType from "@theme/TOCItems"
|
||||
import type { WrapperProps } from "@docusaurus/types"
|
||||
import StructuredDataHowTo from "@site/src/components/StructuredData/HowTo"
|
||||
import { useDoc } from "@docusaurus/theme-common/internal"
|
||||
import { DocContextValue } from "@medusajs/docs"
|
||||
|
||||
type Props = WrapperProps<typeof TOCItemsType>
|
||||
|
||||
export default function TOCItemsWrapper(props: Props): JSX.Element {
|
||||
const { frontMatter, contentTitle } = useDoc() as DocContextValue
|
||||
|
||||
return (
|
||||
<>
|
||||
<TOCItems {...props} />
|
||||
{frontMatter?.addHowToData && (
|
||||
<StructuredDataHowTo toc={props.toc} title={contentTitle} />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
261
www/apps/docs/src/theme/Tabs/index.tsx
Normal file
261
www/apps/docs/src/theme/Tabs/index.tsx
Normal file
@@ -0,0 +1,261 @@
|
||||
import React, { ReactElement, cloneElement, useEffect, useRef } from "react"
|
||||
import clsx from "clsx"
|
||||
import {
|
||||
useScrollPositionBlocker,
|
||||
useTabs,
|
||||
type TabItemProps,
|
||||
} from "@docusaurus/theme-common/internal"
|
||||
import useIsBrowser from "@docusaurus/useIsBrowser"
|
||||
import type { Props as OldProps } from "@theme/Tabs"
|
||||
// import styles from "./styles.module.css"
|
||||
|
||||
type TabsCustomProps = {
|
||||
isCodeTabs?: boolean
|
||||
codeTitle?: string
|
||||
}
|
||||
|
||||
type TabListProps = OldProps & ReturnType<typeof useTabs> & TabsCustomProps
|
||||
|
||||
function TabList({
|
||||
className,
|
||||
selectedValue,
|
||||
selectValue,
|
||||
tabValues,
|
||||
isCodeTabs = false,
|
||||
codeTitle,
|
||||
}: TabListProps) {
|
||||
const tabRefs: (HTMLLIElement | null)[] = []
|
||||
const { blockElementScrollPositionUntilNextRender } =
|
||||
useScrollPositionBlocker()
|
||||
const codeTabSelectorRef = useRef(null)
|
||||
const codeTabsWrapperRef = useRef(null)
|
||||
const handleTabChange = (
|
||||
event:
|
||||
| React.FocusEvent<HTMLLIElement>
|
||||
| React.MouseEvent<HTMLLIElement>
|
||||
| React.KeyboardEvent<HTMLLIElement>
|
||||
) => {
|
||||
const newTab = event.currentTarget
|
||||
const newTabIndex = tabRefs.indexOf(newTab)
|
||||
const newTabValue = tabValues[newTabIndex]!.value
|
||||
|
||||
if (newTabValue !== selectedValue) {
|
||||
blockElementScrollPositionUntilNextRender(newTab)
|
||||
selectValue(newTabValue)
|
||||
}
|
||||
}
|
||||
|
||||
const handleKeydown = (event: React.KeyboardEvent<HTMLLIElement>) => {
|
||||
let focusElement: HTMLLIElement | null = null
|
||||
|
||||
switch (event.key) {
|
||||
case "Enter": {
|
||||
handleTabChange(event)
|
||||
break
|
||||
}
|
||||
case "ArrowRight": {
|
||||
const nextTab = tabRefs.indexOf(event.currentTarget) + 1
|
||||
focusElement = tabRefs[nextTab] ?? tabRefs[0]!
|
||||
break
|
||||
}
|
||||
case "ArrowLeft": {
|
||||
const prevTab = tabRefs.indexOf(event.currentTarget) - 1
|
||||
focusElement = tabRefs[prevTab] ?? tabRefs[tabRefs.length - 1]!
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
focusElement?.focus()
|
||||
}
|
||||
|
||||
const changeTabSelectorCoordinates = (selectedTab) => {
|
||||
if (!codeTabSelectorRef?.current || !codeTabsWrapperRef?.current) {
|
||||
return
|
||||
}
|
||||
const selectedTabsCoordinates = selectedTab.getBoundingClientRect()
|
||||
const tabsWrapperCoordinates =
|
||||
codeTabsWrapperRef.current.getBoundingClientRect()
|
||||
codeTabSelectorRef.current.style.left = `${
|
||||
selectedTabsCoordinates.left - tabsWrapperCoordinates.left
|
||||
}px`
|
||||
codeTabSelectorRef.current.style.width = `${selectedTabsCoordinates.width}px`
|
||||
codeTabSelectorRef.current.style.height = `${selectedTabsCoordinates.height}px`
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (codeTabSelectorRef?.current && tabRefs.length) {
|
||||
const selectedTab = tabRefs.find(
|
||||
(tab) => tab.getAttribute("aria-selected") === "true"
|
||||
)
|
||||
if (selectedTab) {
|
||||
changeTabSelectorCoordinates(selectedTab)
|
||||
}
|
||||
}
|
||||
}, [codeTabSelectorRef, tabRefs])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(isCodeTabs && "code-header", !isCodeTabs && "[&+*]:pt-2")}
|
||||
>
|
||||
<div
|
||||
className={clsx(isCodeTabs && "relative overflow-auto")}
|
||||
ref={codeTabsWrapperRef}
|
||||
>
|
||||
{isCodeTabs && (
|
||||
<span
|
||||
className={clsx(
|
||||
"xs:absolute xs:border xs:border-solid xs:border-medusa-code-border dark:xs:border-medusa-code-border-dark xs:bg-medusa-code-bg-base dark:xs:bg-medusa-code-bg-base-dark xs:transition-all xs:duration-200 xs:ease-ease xs:top-0 xs:z-[1] xs:rounded-full"
|
||||
)}
|
||||
ref={codeTabSelectorRef}
|
||||
></span>
|
||||
)}
|
||||
<ul
|
||||
role="tablist"
|
||||
aria-orientation="horizontal"
|
||||
className={clsx(
|
||||
"tabs",
|
||||
isCodeTabs && "no-scrollbar",
|
||||
"list-none",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{tabValues.map(({ value, label, attributes }) => (
|
||||
<li
|
||||
// TODO extract TabListItem
|
||||
role="tab"
|
||||
tabIndex={selectedValue === value ? 0 : -1}
|
||||
aria-selected={selectedValue === value}
|
||||
key={value}
|
||||
ref={(tabControl) => tabRefs.push(tabControl)}
|
||||
onKeyDown={handleKeydown}
|
||||
onClick={handleTabChange}
|
||||
{...attributes}
|
||||
className={clsx(
|
||||
isCodeTabs &&
|
||||
"text-compact-small-plus py-0.25 border border-solid border-transparent whitespace-nowrap rounded-full [&:not(:first-child)]:ml-0.25",
|
||||
"!mt-0 cursor-pointer",
|
||||
attributes?.className,
|
||||
isCodeTabs && "z-[2] flex justify-center items-center",
|
||||
isCodeTabs &&
|
||||
selectedValue !== value &&
|
||||
"text-medusa-code-text-subtle dark:text-medusa-code-text-subtle-dark hover:!bg-medusa-code-bg-base dark:hover:!bg-medusa-code-bg-base-dark",
|
||||
isCodeTabs &&
|
||||
selectedValue === value &&
|
||||
"text-medusa-code-text-base dark:text-medusa-code-text-base-dark border border-solid border-medusa-code-border dark:border-medusa-code-border-dark bg-medusa-code-bg-base dark:bg-medusa-code-bg-base-dark xs:!border-none xs:!bg-transparent",
|
||||
!isCodeTabs &&
|
||||
"border-0 border-b-[3px] rounded inline-flex p-1 transition-[background-color] duration-200 ease-ease",
|
||||
!isCodeTabs &&
|
||||
selectedValue === value &&
|
||||
"border-solid border-medusa-fg-base dark:border-medusa-fg-base-dark rounded-b-none",
|
||||
!isCodeTabs &&
|
||||
selectedValue !== value &&
|
||||
"text-medusa-fg-subtle dark:text-medusa-fg-subtle-dark",
|
||||
(!isCodeTabs || !attributes?.badge) && "px-0.75",
|
||||
isCodeTabs &&
|
||||
attributes?.badge &&
|
||||
"[&_.badge]:ml-0.5 [&_.badge]:py-0.125 [&_.badge]:px-[6px] [&_.badge]:rounded-full pl-0.75 pr-0.25"
|
||||
)}
|
||||
>
|
||||
{label ?? value}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
{isCodeTabs && (
|
||||
<span
|
||||
className={clsx(
|
||||
"text-compact-small-plus text-medusa-code-text-subtle dark:text-medusa-code-text-subtle-dark hidden xs:block"
|
||||
)}
|
||||
>
|
||||
{codeTitle}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function TabContent({
|
||||
lazy,
|
||||
children,
|
||||
selectedValue,
|
||||
}: OldProps & ReturnType<typeof useTabs>) {
|
||||
const childTabs = (Array.isArray(children) ? children : [children]).filter(
|
||||
Boolean
|
||||
) as ReactElement<TabItemProps>[]
|
||||
if (lazy) {
|
||||
const selectedTabItem = childTabs.find(
|
||||
(tabItem) => tabItem.props.value === selectedValue
|
||||
)
|
||||
if (!selectedTabItem) {
|
||||
// fail-safe or fail-fast? not sure what's best here
|
||||
return null
|
||||
}
|
||||
return cloneElement(selectedTabItem)
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
{childTabs.map((tabItem, i) =>
|
||||
cloneElement(tabItem, {
|
||||
key: i,
|
||||
hidden: tabItem.props.value !== selectedValue,
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type TabsComponentProp = TabsCustomProps & OldProps
|
||||
|
||||
function TabsComponent(props: TabsComponentProp): JSX.Element {
|
||||
const tabs = useTabs(props)
|
||||
return (
|
||||
<div className={clsx("mb-1.5")}>
|
||||
<TabList {...props} {...tabs} />
|
||||
<TabContent {...props} {...tabs} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type TabsProps = {
|
||||
wrapperClassName?: string
|
||||
isCodeTabs?: boolean
|
||||
} & OldProps
|
||||
|
||||
function checkCodeTabs(props: TabsProps): boolean {
|
||||
return props.groupId === "npm2yarn" || props.isCodeTabs
|
||||
}
|
||||
|
||||
export default function Tabs(props: TabsProps): JSX.Element {
|
||||
const isBrowser = useIsBrowser()
|
||||
|
||||
useEffect(() => {
|
||||
if (!window.localStorage.getItem("docusaurus.tab.npm2yarn")) {
|
||||
// set the default
|
||||
window.localStorage.setItem("docusaurus.tab.npm2yarn", "yarn")
|
||||
}
|
||||
}, [])
|
||||
|
||||
const isCodeTabs = checkCodeTabs(props)
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"tabs-wrapper",
|
||||
props.wrapperClassName,
|
||||
isCodeTabs && "code-tabs",
|
||||
!isCodeTabs &&
|
||||
"bg-docs-bg-surface dark:bg-docs-bg-surface-dark p-1 border border-solid border-medusa-border-base dark:border-medusa-border-base-dark rounded"
|
||||
)}
|
||||
>
|
||||
<TabsComponent
|
||||
// Remount tabs after hydration
|
||||
// Temporary fix for https://github.com/facebook/docusaurus/issues/5653
|
||||
key={String(isBrowser)}
|
||||
isCodeTabs={isCodeTabs}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
12
www/apps/docs/src/themes/medusaDocs.js
Normal file
12
www/apps/docs/src/themes/medusaDocs.js
Normal file
@@ -0,0 +1,12 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const vsCodeTheme = require("prism-react-renderer/themes/vsDark")
|
||||
|
||||
const theme = {
|
||||
...vsCodeTheme,
|
||||
plain: {
|
||||
...vsCodeTheme.plain,
|
||||
backgroundColor: "#111827",
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = theme
|
||||
6
www/apps/docs/src/types/global.d.ts
vendored
Normal file
6
www/apps/docs/src/types/global.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
export declare global {
|
||||
interface Window {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
analytics?: any
|
||||
}
|
||||
}
|
||||
202
www/apps/docs/src/types/index.d.ts
vendored
Normal file
202
www/apps/docs/src/types/index.d.ts
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
declare module "@theme/CodeBlock" {
|
||||
import type { Props as DocusaurusProps } from "@theme/CodeBlock"
|
||||
|
||||
export interface Props extends DocusaurusProps {
|
||||
readonly noReport?: boolean
|
||||
readonly noCopy?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
declare module "@theme/CodeBlock/Content/String" {
|
||||
import type { Props as CodeBlockProps } from "@theme/CodeBlock"
|
||||
|
||||
export interface Props extends Omit<CodeBlockProps, "children"> {
|
||||
readonly children: string
|
||||
readonly noReport?: boolean
|
||||
readonly noCopy?: boolean
|
||||
}
|
||||
|
||||
export default function CodeBlockStringContent(props: Props): JSX.Element
|
||||
}
|
||||
|
||||
declare module "@medusajs/docs" {
|
||||
import type { ThemeConfig as DocusaurusThemeConfig } from "@docusaurus/preset-classic"
|
||||
import type { DocusaurusConfig } from "@docusaurus/types"
|
||||
import type {
|
||||
PropSidebarItemCategory,
|
||||
PropSidebarItemLink,
|
||||
PropSidebarItemHtml,
|
||||
} from "@docusaurus/plugin-content-docs"
|
||||
import { BadgeProps, ButtonType, ButtonVariants } from "docs-ui"
|
||||
import { IconProps } from "@medusajs/icons/dist/types"
|
||||
import { DocContextValue as DocusaurusDocContextValue } from "@docusaurus/theme-common/internal"
|
||||
import { ReactNode } from "react"
|
||||
import { NavbarLogo } from "@docusaurus/theme-common"
|
||||
import type { DocusaurusContext } from "@docusaurus/types"
|
||||
|
||||
type ItemCustomProps = {
|
||||
customProps?: {
|
||||
themedImage: {
|
||||
light: string
|
||||
dark?: string
|
||||
}
|
||||
image?: string
|
||||
icon?: React.FC<IconProps>
|
||||
iconName?: string
|
||||
description?: string
|
||||
className?: string
|
||||
isSoon?: boolean
|
||||
badge: BadgeProps
|
||||
html?: string
|
||||
sidebar_icon?: string
|
||||
sidebar_is_title?: boolean
|
||||
sidebar_is_soon?: boolean
|
||||
sidebar_is_group_headline?: boolean
|
||||
sidebar_is_group_divider?: boolean
|
||||
sidebar_is_divider_line?: boolean
|
||||
sidebar_is_back_link?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export declare type ModifiedPropSidebarItemCategory =
|
||||
PropSidebarItemCategory & ItemCustomProps
|
||||
|
||||
export declare type ModifiedPropSidebarItemLink = PropSidebarItemLink &
|
||||
ItemCustomProps
|
||||
|
||||
export declare type ModifiedPropSidebarItemHtml = PropSidebarItemHtml &
|
||||
ItemCustomProps
|
||||
|
||||
export declare type ModifiedSidebarItem =
|
||||
| ModifiedPropSidebarItemCategory
|
||||
| ModifiedPropSidebarItemLink
|
||||
| ModifiedPropSidebarItemHtml
|
||||
|
||||
export declare type ModifiedDocCardBase = {
|
||||
type: string
|
||||
href: string
|
||||
icon: ReactNode | string
|
||||
title: string
|
||||
description?: string
|
||||
html?: string
|
||||
containerClassName?: string
|
||||
isSoon?: boolean
|
||||
badge?: BadgeProps
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
export declare type ModifiedDocCardItemLink = {
|
||||
type: "link"
|
||||
} & ModifiedDocCardBase &
|
||||
ModifiedPropSidebarItemLink
|
||||
|
||||
export declare type ModifiedDocCardItemCategory = {
|
||||
type: "category"
|
||||
} & ModifiedDocCardBase &
|
||||
ModifiedPropSidebarItemCategory
|
||||
|
||||
export declare type ModifiedDocCard =
|
||||
| ModifiedDocCardItemLink
|
||||
| ModifiedDocCardItemCategory
|
||||
|
||||
export declare type SocialLink = {
|
||||
href: string
|
||||
type: string
|
||||
}
|
||||
|
||||
export declare type NavbarActionBase = {
|
||||
type: string
|
||||
title?: string
|
||||
icon?: string
|
||||
Icon?: React.ReactElement
|
||||
className?: string
|
||||
label?: string
|
||||
html?: string
|
||||
}
|
||||
|
||||
export declare type NavbarActionLink = NavbarActionBase & {
|
||||
type: "link"
|
||||
href: string
|
||||
}
|
||||
|
||||
export declare type NavbarActionButton = NavbarActionBase & {
|
||||
type: "button"
|
||||
variant?: ButtonVariants
|
||||
buttonType?: ButtonType
|
||||
href?: string
|
||||
events?: {
|
||||
onClick?: MouseEventHandler<HTMLButtonElement>
|
||||
onMouseEnter?: MouseEventHandler<HTMLButtonElement>
|
||||
onMouseLeave?: MouseEventHandler<HTMLButtonElement>
|
||||
onMouseOver?: MouseEventHandler<HTMLButtonElement>
|
||||
}
|
||||
}
|
||||
|
||||
export declare type NavbarAction = NavbarActionLink | NavbarActionButton
|
||||
|
||||
export declare type OptionType = {
|
||||
value: string
|
||||
label: string
|
||||
isAllOption?: boolean
|
||||
}
|
||||
|
||||
export declare type ThemeConfig = {
|
||||
reportCodeLinkPrefix?: string
|
||||
footerFeedback: {
|
||||
event: string
|
||||
}
|
||||
socialLinks?: SocialLink[]
|
||||
cloudinaryConfig?: {
|
||||
cloudName?: string
|
||||
flags?: string[]
|
||||
resize?: {
|
||||
action: string
|
||||
width?: number
|
||||
height?: number
|
||||
aspectRatio?: string
|
||||
}
|
||||
roundCorners?: number
|
||||
}
|
||||
navbarActions: NavbarAction[]
|
||||
// resolve type errors
|
||||
prism: {
|
||||
magicComments: MagicCommentConfig[]
|
||||
}
|
||||
mobileLogo: NavbarLogo
|
||||
algoliaConfig?: {
|
||||
apiKey: string
|
||||
indexNames: {
|
||||
docs: string
|
||||
api: string
|
||||
}
|
||||
appId: string
|
||||
filters: OptionType[]
|
||||
defaultFilters: string[]
|
||||
defaultFiltersByPath: {
|
||||
path: string
|
||||
filters: string[]
|
||||
}[]
|
||||
}
|
||||
analytics?: {
|
||||
apiKey: string
|
||||
}
|
||||
} & DocusaurusThemeConfig
|
||||
|
||||
export declare type MedusaDocusaurusConfig = {
|
||||
themeConfig: ThemeConfig
|
||||
} & DocusaurusConfig
|
||||
|
||||
export declare type DocContextValue = {
|
||||
frontMatter: {
|
||||
addHowToData?: boolean
|
||||
badge?: {
|
||||
variant: string
|
||||
text: string
|
||||
}
|
||||
}
|
||||
} & DocusaurusDocContextValue
|
||||
|
||||
export declare type MedusaDocusaurusContext = DocusaurusContext & {
|
||||
siteConfig: MedusaDocusaurusConfig
|
||||
}
|
||||
}
|
||||
33
www/apps/docs/src/utils/filter-list-items.ts
Normal file
33
www/apps/docs/src/utils/filter-list-items.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import {
|
||||
PropSidebarItem,
|
||||
PropSidebarItemLink,
|
||||
} from "@docusaurus/plugin-content-docs"
|
||||
|
||||
export default function filterListItems(
|
||||
items: PropSidebarItemLink[],
|
||||
pathPattern: string | RegExp
|
||||
): PropSidebarItemLink[] {
|
||||
if (!items.length) {
|
||||
return items
|
||||
}
|
||||
|
||||
const pattern = new RegExp(pathPattern)
|
||||
|
||||
return items.filter((item: PropSidebarItemLink) => pattern.test(item.href))
|
||||
}
|
||||
|
||||
/**
|
||||
* Flatting a sidebar list moving items from category
|
||||
* to links
|
||||
*/
|
||||
export function flattenList(items: PropSidebarItem[]): PropSidebarItem[] {
|
||||
const newItems = items.map((item: PropSidebarItem) => {
|
||||
if (item.type !== "category") {
|
||||
return item
|
||||
}
|
||||
|
||||
return item.items
|
||||
})
|
||||
|
||||
return newItems.flat()
|
||||
}
|
||||
14
www/apps/docs/src/utils/get-first-category-item.ts
Normal file
14
www/apps/docs/src/utils/get-first-category-item.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import {
|
||||
findSidebarCategory,
|
||||
useDocsSidebar,
|
||||
} from "@docusaurus/theme-common/internal"
|
||||
import { PropSidebarItem } from "@docusaurus/plugin-content-docs"
|
||||
|
||||
export default function getFirstCategoryItem(
|
||||
categoryLabel: string
|
||||
): PropSidebarItem | undefined {
|
||||
return findSidebarCategory(
|
||||
useDocsSidebar().items,
|
||||
(item) => item.label === categoryLabel
|
||||
)?.items[0]
|
||||
}
|
||||
8
www/apps/docs/src/utils/get-os-shortcut.ts
Normal file
8
www/apps/docs/src/utils/get-os-shortcut.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export default function getOsShortcut() {
|
||||
const isMacOs =
|
||||
typeof navigator !== "undefined"
|
||||
? navigator.userAgent.toLowerCase().indexOf("mac") !== 0
|
||||
: true
|
||||
|
||||
return isMacOs ? "⌘" : "Ctrl"
|
||||
}
|
||||
192
www/apps/docs/src/utils/glossary.ts
Normal file
192
www/apps/docs/src/utils/glossary.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
export type GlossaryType = {
|
||||
matchTextRegex: RegExp
|
||||
ignoreTextRegex?: RegExp
|
||||
title: string
|
||||
content: string
|
||||
referenceLink: string
|
||||
}
|
||||
|
||||
const glossary: GlossaryType[] = [
|
||||
{
|
||||
title: "Migration",
|
||||
content: "A script that is used to reflect changes to the database schema.",
|
||||
matchTextRegex: /migration/i,
|
||||
referenceLink: "/development/entities/migrations/overview",
|
||||
},
|
||||
{
|
||||
title: "Repository",
|
||||
content:
|
||||
"A class that provides generic methods to retrieve and manipulate entities.",
|
||||
matchTextRegex: /(repository|repositories)/i,
|
||||
referenceLink: "/development/entities/repositories",
|
||||
},
|
||||
{
|
||||
title: "Entity",
|
||||
content: "A class that represents a table in the database.",
|
||||
matchTextRegex: /(entity|entities)/i,
|
||||
referenceLink: "/development/entities/overview",
|
||||
},
|
||||
{
|
||||
title: "Dependency Injection",
|
||||
content:
|
||||
"Classes receive other resources, such as services or repositories, in their constructor using dependency injection.",
|
||||
matchTextRegex: /(dependency injection|dependency-injection)/i,
|
||||
referenceLink: "/development/fundamentals/dependency-injection",
|
||||
},
|
||||
{
|
||||
title: "Middleware",
|
||||
content:
|
||||
"A function that can be executed before or after endpoint requests are handled.",
|
||||
matchTextRegex: /middleware/i,
|
||||
referenceLink: "/development/endpoints/add-middleware",
|
||||
},
|
||||
{
|
||||
title: "Endpoint",
|
||||
content: "REST API routes exposed to frontends or external systems.",
|
||||
matchTextRegex: /endpoint/i,
|
||||
referenceLink: "/development/endpoints/overview",
|
||||
},
|
||||
{
|
||||
title: "Subscriber",
|
||||
content: "A class that registers handler methods to an event.",
|
||||
matchTextRegex: /subscriber/i,
|
||||
referenceLink: "/development/events/subscribers",
|
||||
},
|
||||
{
|
||||
title: "Module",
|
||||
content:
|
||||
"Reusable pieces of code, typically as NPM packages, that provide specific functionality or feature.",
|
||||
matchTextRegex: /module/,
|
||||
ignoreTextRegex: /commerce module/i,
|
||||
referenceLink: "/development/modules/overview",
|
||||
},
|
||||
{
|
||||
title: "Loader",
|
||||
content: "A script that runs when the Medusa backend starts.",
|
||||
matchTextRegex: /loader/i,
|
||||
referenceLink: "/development/loaders/overview",
|
||||
},
|
||||
{
|
||||
title: "Scheduled Job",
|
||||
content:
|
||||
"A task that is performed at specific times during the Medusa backend's runtime.",
|
||||
matchTextRegex: /scheduled job/i,
|
||||
referenceLink: "/development/scheduled-jobs/overview",
|
||||
},
|
||||
{
|
||||
title: "Batch Job",
|
||||
content: "A task that is performed asynchronously and iteratively.",
|
||||
matchTextRegex: /batch job/i,
|
||||
referenceLink: "/development/batch-jobs",
|
||||
},
|
||||
{
|
||||
title: "Strategy",
|
||||
content:
|
||||
"A class that contains an isolated piece of logic that can be overridden and customized.",
|
||||
matchTextRegex: /(strategy|strategies)/i,
|
||||
referenceLink: "/development/strategies/overview",
|
||||
},
|
||||
{
|
||||
title: "Feature Flag",
|
||||
content:
|
||||
"A flag that guards beta features in the Medusa backend and ensures they can only be used when enabled.",
|
||||
matchTextRegex: /(feature-flag|feature flag)/i,
|
||||
referenceLink: "/development/feature-flags/overview",
|
||||
},
|
||||
{
|
||||
title: "Idempotency Key",
|
||||
content:
|
||||
"A unique, randomly-generated key associated with a process, such as cart completion.",
|
||||
matchTextRegex: /(idempotency-key|idempotency key)/i,
|
||||
referenceLink: "/development/idempotency-key/overview",
|
||||
},
|
||||
{
|
||||
title: "Search Service",
|
||||
content:
|
||||
"A class that implements an interface to provide search functionalities.",
|
||||
matchTextRegex: /(search service|search-service)/i,
|
||||
referenceLink: "/development/search/overview",
|
||||
},
|
||||
{
|
||||
title: "File Service",
|
||||
content:
|
||||
"A class that implements an interface to provide storage functionalities.",
|
||||
matchTextRegex: /(file service|file-service)/i,
|
||||
referenceLink: "/development/file-service/overview",
|
||||
},
|
||||
{
|
||||
title: "Notification Service",
|
||||
content:
|
||||
"A class that implements an interface to provide notification functionalities.",
|
||||
matchTextRegex: /(notification service|notification-service)/i,
|
||||
referenceLink: "/development/notification/overview",
|
||||
},
|
||||
{
|
||||
title: "Plugin",
|
||||
content:
|
||||
"A reusable NPM package that can be reused in Medusa backends and provide custom functionalities.",
|
||||
matchTextRegex: /plugin/i,
|
||||
ignoreTextRegex:
|
||||
/(file-service plugin|file service plugin|notification service plugin|notification-service plugin|search service plugin|search-service plugin)/i,
|
||||
referenceLink: "/development/plugins/overview",
|
||||
},
|
||||
{
|
||||
title: "Service",
|
||||
content:
|
||||
"A class that typically includes helper methods associated with an entity.",
|
||||
matchTextRegex: /service/i,
|
||||
ignoreTextRegex:
|
||||
/(file-service|file service|notification service|notification-service|search service|search-service)/i,
|
||||
referenceLink: "/development/services/overview",
|
||||
},
|
||||
{
|
||||
title: "Publishable API Key",
|
||||
content:
|
||||
"An API key that is associated with a set of resources and used on the client (storefront) side.",
|
||||
matchTextRegex: /(publishable-api-key|publishable api key)/i,
|
||||
referenceLink: "/development/publishable-api-keys",
|
||||
},
|
||||
{
|
||||
title: "JavaScript Client",
|
||||
content:
|
||||
"An NPM package that provides methods to interact with the Medusa backend's REST APIs.",
|
||||
matchTextRegex: /(js-client|js client|medusa javascript client)/i,
|
||||
referenceLink: "/js-client/overview",
|
||||
},
|
||||
{
|
||||
title: "Medusa React",
|
||||
content:
|
||||
"An NPM package that provides React hooks and utility methods to interact with the Medusa backend's REST APIs.",
|
||||
matchTextRegex: /(medusa-react|medusa react)/i,
|
||||
referenceLink: "/medusa-react/overview",
|
||||
},
|
||||
{
|
||||
title: "Admin Widget",
|
||||
content:
|
||||
"Custom React components that can be injected into different locations in the Medusa admin dashboard.",
|
||||
matchTextRegex: /admin widget/i,
|
||||
referenceLink: "/admin/widgets",
|
||||
},
|
||||
{
|
||||
title: "Admin UI Route",
|
||||
content:
|
||||
"A React component that is used to create a new page in the Medusa admin dashboard.",
|
||||
matchTextRegex: /(admin route|admin UI route)/i,
|
||||
referenceLink: "/admin/routes",
|
||||
},
|
||||
{
|
||||
title: "Admin Setting Page",
|
||||
content:
|
||||
"A React component that is used to create a new setting page in the Medusa admin dashboard.",
|
||||
matchTextRegex: /admin setting page/i,
|
||||
referenceLink: "/admin/setting-pages",
|
||||
},
|
||||
]
|
||||
|
||||
export const getGlossary = () => [...glossary]
|
||||
|
||||
export const getGlossaryByPath = (path: string): GlossaryType | undefined => {
|
||||
return glossary.find(
|
||||
(g) => g.matchTextRegex.test(path) && !g.ignoreTextRegex?.test(path)
|
||||
)
|
||||
}
|
||||
797
www/apps/docs/src/utils/learning-paths.tsx
Normal file
797
www/apps/docs/src/utils/learning-paths.tsx
Normal file
@@ -0,0 +1,797 @@
|
||||
import React from "react"
|
||||
import { LearningPathType } from "../providers/LearningPath"
|
||||
import Link from "@docusaurus/Link"
|
||||
|
||||
const paths: LearningPathType[] = [
|
||||
{
|
||||
name: "simple-quickstart",
|
||||
label: "Start Selling in 3 Steps",
|
||||
description: "Create and deploy a full-fledged ecommerce store.",
|
||||
steps: [
|
||||
{
|
||||
title: "Create a Next.js Starter Template",
|
||||
description:
|
||||
"Create a Next.js Starter Template and connect it to your Medusa backend.",
|
||||
path: "/starters/nextjs-medusa-starter",
|
||||
},
|
||||
{
|
||||
title: "Deploy the backend",
|
||||
path: "/deployments/server/deploying-on-railway",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
Deploy your backend to Railway. You can alternatively check out{" "}
|
||||
<Link href="/deployments/server">other deployment guides</Link>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Deploy the storefront",
|
||||
description: "Deploy your storefront to your preferred hosting.",
|
||||
path: "/deployments/storefront",
|
||||
},
|
||||
],
|
||||
finish: {
|
||||
type: "rating",
|
||||
step: {
|
||||
title: "Congratulations on building your store!",
|
||||
description: "Please rate your experience using this recipe.",
|
||||
eventName: "rating_path_simple-quickstart",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "marketplace",
|
||||
label: "Build a marketplace",
|
||||
description:
|
||||
"Customize the backend and handle events to build a marketplace.",
|
||||
steps: [
|
||||
{
|
||||
title: "Extend entities",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
Extend entities, such as <code>User</code> or <code>Product</code>{" "}
|
||||
entites, to associate them with the <code>Store</code> entity.
|
||||
</>
|
||||
),
|
||||
path: "/development/entities/extend-entity",
|
||||
},
|
||||
{
|
||||
title: "Access logged-in user",
|
||||
description:
|
||||
"Create a middleware that registers the logged-in user in the dependency container.",
|
||||
path: "/development/endpoints/example-logged-in-user",
|
||||
},
|
||||
{
|
||||
title: "Extend services",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
Extend services, such as <code>ProductService</code> to customize
|
||||
data management functionalities
|
||||
</>
|
||||
),
|
||||
path: "/development/services/extend-service",
|
||||
},
|
||||
{
|
||||
title: "Handle events",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
Listen to events like <code>order.placed</code> and handle them with
|
||||
subscribers
|
||||
</>
|
||||
),
|
||||
path: "/development/events/create-subscriber",
|
||||
},
|
||||
{
|
||||
title: "Add Payment Provider",
|
||||
path: "/plugins/payment",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
Add a payment provider to your Medusa backend. You can choose to
|
||||
install a plugin or{" "}
|
||||
<Link href="/modules/carts-and-checkout/backend/add-payment-provider">
|
||||
create your own provider
|
||||
</Link>
|
||||
.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Customize Admin",
|
||||
path: "/admin/widgets",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
As you add marketplace features to your store, you'll most
|
||||
likely need to customize the admin to provide an interface to manage
|
||||
these features.
|
||||
<br />
|
||||
You can extend the admin plugin to add widgets,{" "}
|
||||
<Link to="/admin/routes">UI routes</Link>, or{" "}
|
||||
<Link to="/admin/setting-pages">setting pages</Link>.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Implement Role-Based Access Control",
|
||||
path: "/modules/users/backend/rbac",
|
||||
description:
|
||||
"In your marketplace, you may need to implement role-based access control (RBAC) within stores. This will restrict some users' permissions to specified functionalities or endpoints.",
|
||||
},
|
||||
{
|
||||
title: "Create a storefront",
|
||||
path: "/starters/nextjs-medusa-starter",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
Build a storefront either using the Next.js Starter Template or{" "}
|
||||
<Link href="/storefront/roadmap">from scratch</Link>.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Deploy the backend",
|
||||
path: "/deployments/server/deploying-on-railway",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
Deploy your backend to Railway. You can alternatively check out{" "}
|
||||
<Link href="/deployments/server">other deployment guides</Link>
|
||||
</>
|
||||
),
|
||||
},
|
||||
],
|
||||
finish: {
|
||||
type: "rating",
|
||||
step: {
|
||||
title: "Congratulations on building your marketplace!",
|
||||
description: "Please rate your experience using this recipe.",
|
||||
eventName: "rating_path_marketplace",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "subscriptions",
|
||||
label: "Build Subscription-based Purchases",
|
||||
description:
|
||||
"Customize the backend and handle events to implement subscriptions",
|
||||
steps: [
|
||||
{
|
||||
title: "Extend entities",
|
||||
path: "/development/entities/extend-entity",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
Extend entities, such as the <code>Order</code> entity, to associate
|
||||
them with the <code>Store</code> entity. You can also{" "}
|
||||
<Link href="/development/entities/create">
|
||||
Create a custom entity
|
||||
</Link>
|
||||
.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Handle events",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
Create a subscriber that listens to the <code>order.placed</code>{" "}
|
||||
event, or other{" "}
|
||||
<Link href="/development/events/events-list">events</Link>, and
|
||||
handles creating the subscription in Medusa.
|
||||
</>
|
||||
),
|
||||
path: "/development/events/create-subscriber",
|
||||
},
|
||||
{
|
||||
title: "Create a Scheduled Job",
|
||||
description:
|
||||
"Create a scheduled job that checks daily for subscriptions that needs renewal.",
|
||||
path: "/development/scheduled-jobs/create",
|
||||
},
|
||||
{
|
||||
title: "Customize Admin",
|
||||
path: "/admin/widgets",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
As you add subscription features to your store, you may need to
|
||||
customize the admin to provide an interface to manage these
|
||||
features.
|
||||
<br />
|
||||
You can extend the admin plugin to add widgets,{" "}
|
||||
<Link to="/admin/routes">UI routes</Link>, or{" "}
|
||||
<Link to="/admin/setting-pages">setting pages</Link>.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Create a storefront",
|
||||
path: "/starters/nextjs-medusa-starter",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
Build a storefront either using the Next.js Starter Template or{" "}
|
||||
<Link href="/storefront/roadmap">from scratch</Link>.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Deploy the backend",
|
||||
path: "/deployments/server/deploying-on-railway",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
Deploy your backend to Railway. You can alternatively check out{" "}
|
||||
<Link href="/deployments/server">other deployment guides</Link>
|
||||
</>
|
||||
),
|
||||
},
|
||||
],
|
||||
finish: {
|
||||
type: "rating",
|
||||
step: {
|
||||
title: "Congratulations on implementing subscription-based purchases!",
|
||||
description: "Please rate your experience using this recipe.",
|
||||
eventName: "rating_path_subscriptions",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "b2b",
|
||||
label: "Build a B2B store",
|
||||
description:
|
||||
"Utilize Medusa's features and customization capabilities to build a B2B store.",
|
||||
steps: [
|
||||
{
|
||||
title: "Create a B2B Sales Channel",
|
||||
path: "/user-guide/sales-channels/manage",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
You can create a B2B sales channel that will include only your
|
||||
wholesale products.
|
||||
<br />
|
||||
You can either use the Medusa admin, or the{" "}
|
||||
<Link to="/modules/sales-channels/admin/manage">
|
||||
Admin REST APIs
|
||||
</Link>
|
||||
.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Create a Publishable API Key",
|
||||
path: "/user-guide/settings/publishable-api-keys",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
Publishable API keys can be associated with one or more sales
|
||||
channels. You can then use the publishable API key in your
|
||||
storefront or client.
|
||||
<br />
|
||||
You can either use the Medusa admin, or the{" "}
|
||||
<Link to="/development/publishable-api-keys/admin/manage-publishable-api-keys">
|
||||
Admin REST APIs
|
||||
</Link>
|
||||
.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Add Wholesale Products",
|
||||
path: "/user-guide/products/manage",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
You can add your wholesale products and make them only available in
|
||||
the B2B sales channel.
|
||||
<br />
|
||||
You can use the Medusa admin to add the products. Other alternatives
|
||||
are:
|
||||
<ul>
|
||||
<li>
|
||||
<Link to="/modules/products/admin/manage-products">
|
||||
Add Products Using REST APIs
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/user-guide/products/import">
|
||||
Import Products Using Medusa Admin
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/modules/products/admin/import-products">
|
||||
Import Products Using REST APIs
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Create a B2B Customer Group",
|
||||
path: "/user-guide/customers/groups",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
Customer groups can be used to apply different prices for different
|
||||
subsets of customers, in this case B2B customers.
|
||||
<br />
|
||||
You can either use the Medusa admin, or the{" "}
|
||||
<Link to="/modules/customers/admin/manage-customer-groups">
|
||||
Admin REST APIs
|
||||
</Link>
|
||||
.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Add B2B Customers",
|
||||
path: "/user-guide/customers/manage",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
You can now add B2B customers and assign them to the B2B customer
|
||||
group. Alternatively, if you want to allow B2B customers to register
|
||||
themselves, you can implement that logic within your storefront.
|
||||
<br />
|
||||
You can either use the Medusa admin, or the{" "}
|
||||
<Link to="/modules/customers/admin/manage-customers">
|
||||
Admin REST APIs
|
||||
</Link>
|
||||
.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Create B2B Price Lists",
|
||||
path: "/user-guide/price-lists/manage",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
A price list allows you to set different prices on a set of products
|
||||
for different conditions. You can use this when building a B2B store
|
||||
to assign different prices for B2B customer groups.
|
||||
<br />
|
||||
You can use the Medusa admin to add the price list. Other
|
||||
alternatives are:
|
||||
<ul>
|
||||
<li>
|
||||
<Link to="/modules/price-lists/admin/manage-price-lists">
|
||||
Add Price List Using REST APIs
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/user-guide/price-lists/import">
|
||||
Import Prices Using Medusa Admin
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/modules/price-lists/admin/import-prices">
|
||||
Import Prices Using REST APIs
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Create Custom Entities",
|
||||
path: "/development/entities/create",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
Your use case may be more elaborate than what is shown in this
|
||||
recipe.
|
||||
<br />
|
||||
Medusa can be customized to add custom entities, endpoints,
|
||||
services, and more.
|
||||
<br />
|
||||
You can find additional development resources in the{" "}
|
||||
<Link to="/development/overview">Medusa development section</Link>.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Create an Endpoint to Check Customers",
|
||||
path: "/development/entities/create",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
On the clients communicating with your store, such as the
|
||||
storefront, you’ll need to check if the currently logged-in customer
|
||||
is a normal customer or a B2B customer.
|
||||
<br />
|
||||
To do that, you need to create a custom endpoint that handles the
|
||||
checking based on the custom logic you've chosen to indicate a
|
||||
customer is a B2B customer.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Customize Admin",
|
||||
path: "/admin/widgets",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
As you add B2B features to your store, you may need to customize the
|
||||
admin to provide an interface to manage these features.
|
||||
<br />
|
||||
You can extend the admin plugin to add widgets,{" "}
|
||||
<Link to="/admin/routes">UI routes</Link>, or{" "}
|
||||
<Link to="/admin/setting-pages">setting pages</Link>.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Customize Storefront",
|
||||
path: "/starters/nextjs-medusa-starter",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
You may need to customize your storefront to add different
|
||||
interfaces for B2B and regular customers, or show products
|
||||
differently.
|
||||
<br />
|
||||
You can customize the Next.js storefront, or you can{" "}
|
||||
<Link to="/storefront/roadmap">build a custom storefront</Link>.
|
||||
<br />
|
||||
In your storefront, make sure to{" "}
|
||||
<Link to="/development/publishable-api-keys/storefront/use-in-requests">
|
||||
use publishable API keys
|
||||
</Link>{" "}
|
||||
in your requests.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Deploy the B2B store",
|
||||
path: "/deployments/server",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
Once you finish your development, you can deploy your B2B backend to
|
||||
your preferred hosting provider. You can also{" "}
|
||||
<Link to="/deployments/storefront">deploy your storefront</Link> to
|
||||
your preferred hosting provider.
|
||||
</>
|
||||
),
|
||||
},
|
||||
],
|
||||
finish: {
|
||||
type: "rating",
|
||||
step: {
|
||||
title: "Congratulations on building a B2B store!",
|
||||
description: "Please rate your experience using this recipe.",
|
||||
eventName: "rating_path_b2b",
|
||||
},
|
||||
},
|
||||
},
|
||||
// TODO: Eventually remove these learning paths
|
||||
{
|
||||
name: "rbac",
|
||||
label: "Role-based access control (RBAC)",
|
||||
description: "Implement roles and permissions for admin users in Medusa",
|
||||
steps: [
|
||||
{
|
||||
title: "Create Role and Permission Entities",
|
||||
path: "/development/entities/create",
|
||||
description:
|
||||
"When implementing RBAC, you typically require the availability of roles and permissions, both of which would require new entities. A role would include different permissions, such as the ability to access the products’ route, and it can be assigned to one or more users.",
|
||||
},
|
||||
{
|
||||
title: "Extend Entities",
|
||||
path: "/development/entities/extend-entity",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
To associate roles with users, you need to extend the{" "}
|
||||
<code>User</code> entity to add the relation between it and the new{" "}
|
||||
<code>Role</code> entity. You can also extend other entities that
|
||||
are associated with your custom one, such as the <code>Store</code>{" "}
|
||||
entity.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Create Guard Middleware",
|
||||
path: "/development/endpoints/add-middleware",
|
||||
description:
|
||||
"To ensure that users who have the privilege can access an endpoint, you must create a middleware that guards admin routes. This middleware will run on all authenticated admin requests to ensure that only allowed users can access an endpoint.",
|
||||
},
|
||||
{
|
||||
title: "Create Services",
|
||||
path: "/development/services/create-service",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
For every entity you create, such as the <code>Role</code> and{" "}
|
||||
<code>Permission</code> entities, you must create a service that
|
||||
provides create, read, update, and delete (CRUD) functionalities at
|
||||
the very least.
|
||||
<br />
|
||||
If you also extended entities, such as the <code>User</code> entity,
|
||||
you may need to{" "}
|
||||
<Link to="/development/services/extend-service">
|
||||
extend its core service
|
||||
</Link>{" "}
|
||||
<code>UserService</code> as well to perform custom functionalities
|
||||
related to your implementation.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Create Endpoints",
|
||||
path: "/development/endpoints/create",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
To manage the roles and permissions, you’ll need to create custom
|
||||
endpoints, typically for Create, Read, Update, and Delete (CRUD)
|
||||
operations.
|
||||
<br />
|
||||
After creating the endpoints, you may test adding roles and
|
||||
permissions, and how they provide different access for different
|
||||
roles and users.
|
||||
</>
|
||||
),
|
||||
},
|
||||
],
|
||||
finish: {
|
||||
type: "rating",
|
||||
step: {
|
||||
title: "Congratulations on implementing RBAC!",
|
||||
description: "Please rate your experience using this recipe.",
|
||||
eventName: "rating_path_rbac",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "entity-and-api",
|
||||
label: "Create Entity and Expose it with Endpoints",
|
||||
description:
|
||||
"Learn how to create a new table in your database, then create endpoints to expose and manipulate its data.",
|
||||
steps: [
|
||||
{
|
||||
title: "Create entity",
|
||||
path: "/development/entities/create",
|
||||
description: "Create your entity, its migration, and repository.",
|
||||
},
|
||||
{
|
||||
title: "Create service",
|
||||
path: "/development/services/create-service",
|
||||
description:
|
||||
"A service is a class that defines helper methods for your entity. The service will be used by the endpoints to access or modify the entity's data.",
|
||||
},
|
||||
{
|
||||
title: "Create endpoints",
|
||||
path: "/development/endpoints/create",
|
||||
},
|
||||
],
|
||||
finish: {
|
||||
type: "rating",
|
||||
step: {
|
||||
title: "Congratulations on creating your entity and endpoints!",
|
||||
description: "Please rate your experience using this recipe.",
|
||||
eventName: "rating_path_entity-and-api",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "storefront",
|
||||
label: "Create a Custom Storefront",
|
||||
description:
|
||||
"Learn how to create a custom storefront with your preferred language or framework.",
|
||||
steps: [
|
||||
{
|
||||
title: "Choose your client",
|
||||
path: "/medusa-react/overview",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
As your storefront connect to the Medusa backend, you need a way to
|
||||
interact with the backend's REST APIs. There are three ways to
|
||||
do so, based on your type of project:
|
||||
<ul>
|
||||
<li>
|
||||
<Link to="/medusa-react/overview">Medusa React</Link>: Can be
|
||||
used in any React-based project. For example, in a Next.js
|
||||
storefront.
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/js-client/overview">Medusa JS Client</Link>: Can be
|
||||
used in any JavaScript and NPM based project. For example, in a
|
||||
Nuxt storefront.
|
||||
</li>
|
||||
<li>
|
||||
<Link to={`https://docs.medusajs.com/api/store`}>
|
||||
Store REST APIs
|
||||
</Link>
|
||||
: You can send requests directly to the API endpoints without
|
||||
using Medusa's clients.
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Set CORS configurations in Backend",
|
||||
path: "/development/backend/configurations#admin_cors-and-store_cors",
|
||||
description:
|
||||
"To ensure your storefront can connect to the backend, make sure to configure the backend's CORS configuration based on your storefront's local or remote URL.",
|
||||
},
|
||||
{
|
||||
title: "Create a Publishable API Key",
|
||||
path: "/user-guide/settings/publishable-api-keys",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
A publishable API key allows you to associate a key with a sales
|
||||
channel. Then, you can include that key in the headers of all your
|
||||
requests.
|
||||
<br />
|
||||
You can create the publishable API key from the dashboard.
|
||||
Alternatively, you can create it using the{" "}
|
||||
<Link to="/development/publishable-api-keys/admin/manage-publishable-api-keys">
|
||||
Admin APIs
|
||||
</Link>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Use Publishable API Key",
|
||||
path: "/development/publishable-api-keys/storefront/use-in-requests",
|
||||
description:
|
||||
"After creating the publishable API key and associating it with sales channels, you can pass it in the header of your requests to Store API endpoints.",
|
||||
},
|
||||
{
|
||||
title: "Add Region Selection",
|
||||
path: "/modules/regions-and-currencies/storefront/use-regions",
|
||||
description:
|
||||
"In your storefront, you can allow your customers to view available regions and select their current region. This can affect the prices, discounts, and shipping and payment providers available to the customer.",
|
||||
},
|
||||
{
|
||||
title: "Display Products",
|
||||
path: "/modules/products/storefront/show-products",
|
||||
description: "Display products to your customers in the storefront.",
|
||||
},
|
||||
{
|
||||
title: "Implement Cart Functionalities",
|
||||
path: "/modules/carts-and-checkout/storefront/implement-cart",
|
||||
description:
|
||||
"Allow your customers to add items to their cart, update them, and more in preparation for checkout.",
|
||||
},
|
||||
{
|
||||
title: "Implement Checkout Flow",
|
||||
path: "/modules/carts-and-checkout/storefront/implement-checkout-flow",
|
||||
description:
|
||||
"Implement the checkout flow that allows customers to handle shipping and payment, then place their orders.",
|
||||
},
|
||||
{
|
||||
title: "Implement Customer Profiles",
|
||||
path: "/modules/customers/storefront/implement-customer-profiles",
|
||||
description:
|
||||
"Allow customers to register, login, edit their profile information, and more.",
|
||||
},
|
||||
{
|
||||
title: "More Commerce Functionalities",
|
||||
path: "/modules/overview",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
This recipe guided you to create a storefront with basic
|
||||
functionalities. You can add more functionalities to your storefront
|
||||
based on your use case.
|
||||
<ul>
|
||||
<li>
|
||||
The <Link to="/modules/overview">Commerce Modules</Link>{" "}
|
||||
documentation holds various storefront-related how-to guides to
|
||||
help you implement different features.
|
||||
</li>
|
||||
<li>
|
||||
You can also checkout the{" "}
|
||||
<Link to={`https://docs.medusajs.com/api/store`}>
|
||||
Store REST APIs
|
||||
</Link>{" "}
|
||||
for a full list of available REST APIs.
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
),
|
||||
},
|
||||
],
|
||||
finish: {
|
||||
type: "rating",
|
||||
step: {
|
||||
title: "Congratulations on creating your storefront!",
|
||||
description: "Please rate your experience using this recipe.",
|
||||
eventName: "rating_path_storefront",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "plugin",
|
||||
label: "Create a Plugin",
|
||||
description:
|
||||
"Learn how to create a plugin that can be re-used across Medusa backends.",
|
||||
steps: [
|
||||
{
|
||||
title: "Setup plugin project",
|
||||
path: "/development/backend/install",
|
||||
description:
|
||||
"A plugin is initially a Medusa backend with customizations. If you don't have a project ready, you can create one using Medusa's CLI tool.",
|
||||
},
|
||||
{
|
||||
title: "Implement Customizations",
|
||||
path: "/development/entities/create",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
Your plugin can hold backend and admin customizations. Those
|
||||
include:
|
||||
<ul>
|
||||
<li>
|
||||
<Link to="/development/entities/create">Create Entity</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/development/services/create-service">
|
||||
Create Service
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/development/endpoints/create">
|
||||
Create an Endpoint
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/development/events/create-subscriber">
|
||||
Create Subscriber
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/admin/widgets">Create Admin Widgets</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/admin/routes">Create Admin Routes</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/admin/setting-pages">
|
||||
Create Admin Setting Pages
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/development/search/create">
|
||||
Create Search Service
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/development/file-service/create-file-service">
|
||||
Create File Service
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/development/notification-service/create-notification-service">
|
||||
Create Notification Service
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
If you've already made your custom development, you can skip to
|
||||
the next step.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Change your package.json",
|
||||
path: "/development/plugins/create#changes-to-packagejson",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
Once you're done making your customizations and you're
|
||||
ready to publish your plugin, make changes to your{" "}
|
||||
<code>package.json</code> in preparation for publishing.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Optionally test locally",
|
||||
path: "/development/plugins/create#test-your-plugin",
|
||||
description:
|
||||
"If necessary, you can test your plugin in a separate local Medusa backend. It's recommended, however, to do your plugin testing within the plugin project.",
|
||||
},
|
||||
{
|
||||
title: "Publish plugin",
|
||||
path: "/development/plugins/publish",
|
||||
description: "Publish your plugin on NPM.",
|
||||
},
|
||||
],
|
||||
finish: {
|
||||
type: "rating",
|
||||
step: {
|
||||
title: "Congratulations on creating your plugin!",
|
||||
description: "Please rate your experience using this recipe.",
|
||||
eventName: "rating_path_plugin",
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
// get a shallow copy
|
||||
export const getLearningPaths = () => [...paths]
|
||||
|
||||
export const getLearningPath = (
|
||||
pathName: string
|
||||
): LearningPathType | undefined => paths.find((path) => path.name === pathName)
|
||||
27
www/apps/docs/src/utils/reverseSidebar.js
Normal file
27
www/apps/docs/src/utils/reverseSidebar.js
Normal file
@@ -0,0 +1,27 @@
|
||||
function reverseSidebarItems(sidebarItems, categoryItem) {
|
||||
let result = sidebarItems
|
||||
if (categoryItem.customProps?.reverse) {
|
||||
// Reverse items in categories
|
||||
result = result.map((item) => {
|
||||
if (item.type === "category") {
|
||||
return { ...item, items: reverseSidebarItems(item.items, categoryItem) }
|
||||
}
|
||||
return item
|
||||
})
|
||||
// Reverse items at current level
|
||||
// use localeCompare since the reverse array method doesn't account for
|
||||
// numeric strings
|
||||
result.sort((a, b) => {
|
||||
const aToCompare = a.id || a.href || a.value || ""
|
||||
const bToCompare = b.id || b.href || b.value || ""
|
||||
const comparison = aToCompare.localeCompare(bToCompare, undefined, {
|
||||
numeric: true,
|
||||
})
|
||||
|
||||
return comparison < 0 ? 1 : comparison > 0 ? -1 : 0
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
module.exports = reverseSidebarItems
|
||||
Reference in New Issue
Block a user