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
|
||||
Reference in New Issue
Block a user