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:
Shahed Nasser
2023-09-21 20:57:15 +03:00
committed by GitHub
parent 19c5d5ba36
commit fa7c94b4cc
3209 changed files with 32188 additions and 31018 deletions

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@@ -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

View 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

View 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

View 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

View 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

View 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

View 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

View 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