docs: generate documentation for UI components (#5849)

* added tool to generate spec files for React components

* use typedoc for missing descriptions and types

* improvements and fixes

* improvements

* added doc comments for half of the components

* add custom resolver + more doc comments

* added all tsdocs

* general improvements

* add specs to UI docs

* added github action

* remove unnecessary api route

* Added readme for react-docs-generator

* remove comment

* Update packages/design-system/ui/src/components/currency-input/currency-input.tsx

Co-authored-by: Kasper Fabricius Kristensen <45367945+kasperkristensen@users.noreply.github.com>

* remove description of aria fields + add generate script

---------

Co-authored-by: Kasper Fabricius Kristensen <45367945+kasperkristensen@users.noreply.github.com>
This commit is contained in:
Shahed Nasser
2023-12-13 16:02:41 +02:00
committed by GitHub
co-authored by Kasper Fabricius Kristensen
parent edc49bfe1d
commit 245e5c9a69
288 changed files with 6029 additions and 1447 deletions
@@ -1,48 +0,0 @@
import { Spinner } from "@medusajs/icons"
import { Container } from "@medusajs/ui"
import * as React from "react"
import { PropRegistry } from "@/registries/prop-registry"
import { Feedback } from "./feedback"
type ComponentPropsProps = {
component: string
}
const ComponentProps = ({ component }: ComponentPropsProps) => {
const Props = React.useMemo(() => {
const Table = PropRegistry[component]?.table
if (!Table) {
return (
<div className="flex min-h-[200px] w-full items-center justify-center">
<p className="txt-compact-small">
No API reference found for{" "}
<span className="txt-compact-small-plus">{component}</span>
</p>
</div>
)
}
return <Table />
}, [component])
return (
<>
<Container className="mb-6 mt-8 overflow-hidden p-0">
<React.Suspense
fallback={
<div className="text-medusa-fg-muted flex flex-1 items-center justify-center">
<Spinner className="animate-spin" />
</div>
}
>
{Props}
</React.Suspense>
</Container>
<Feedback title={`props of ${component}`} />
</>
)
}
export { ComponentProps }
@@ -0,0 +1,81 @@
import { Documentation } from "react-docgen"
import { Suspense } from "react"
import { Spinner } from "@medusajs/icons"
import { PropTable } from "./props-table"
import { Container, clx } from "@medusajs/ui"
import { Feedback } from "./feedback"
import { MarkdownContent } from "docs-ui"
import { components } from "./mdx-components"
type ComponentReferenceProps = {
mainComponent: string
componentsToShow?: string[]
specsSrc?: string
}
const ComponentReference = ({
mainComponent,
componentsToShow = [mainComponent],
specsSrc,
}: ComponentReferenceProps) => {
if (!specsSrc) {
return <></>
}
const specs = JSON.parse(specsSrc) as Documentation[]
return (
<>
{componentsToShow.map((component, index) => {
const componentSpec = specs?.find(
(spec) => spec.displayName === component
)
const hasProps =
componentSpec?.props && Object.keys(componentSpec.props).length > 0
return (
<Suspense
fallback={
<div className="text-medusa-fg-muted flex flex-1 items-center justify-center">
<Spinner className="animate-spin" />
</div>
}
key={index}
>
{componentSpec && (
<>
{componentsToShow.length > 1 && (
<h3 className={clx("h3-docs mb-2 mt-10 text-medusa-fg-base")}>
{componentSpec.displayName || component}
</h3>
)}
{componentSpec.description && (
<MarkdownContent components={components}>
{componentSpec.description}
</MarkdownContent>
)}
{hasProps && (
<>
<Container className="mb-6 mt-8 overflow-hidden p-0">
<Suspense
fallback={
<div className="text-medusa-fg-muted flex flex-1 items-center justify-center">
<Spinner className="animate-spin" />
</div>
}
>
<PropTable props={componentSpec.props!} />
</Suspense>
</Container>
<Feedback title={`props of ${component}`} />
</>
)}
</>
)}
</Suspense>
)
})}
</>
)
}
export { ComponentReference }
+4 -1
View File
@@ -3,6 +3,7 @@ import { Table, Tooltip } from "@medusajs/ui"
import { HookData, HookDataMap } from "@/types/hooks"
import { EnumType, FunctionType, ObjectType } from "@/types/props"
import { InlineCode } from "docs-ui"
const HookTable = ({ props }: { props: HookDataMap }) => {
return (
@@ -47,7 +48,9 @@ const Row = ({ value, type, description }: HookData) => {
return (
<Table.Row className="code-body">
<Table.Cell>{value}</Table.Cell>
<Table.Cell>
<InlineCode>{value}</InlineCode>
</Table.Cell>
<Table.Cell>
{!isComplexType && type.toString()}
{isEnum(type) && (
@@ -6,12 +6,12 @@ import * as React from "react"
import { Colors } from "@/components/colors"
import { ComponentExample } from "@/components/component-example"
import { ComponentProps } from "@/components/component-props"
import { HookValues } from "@/components/hook-values"
import { IconSearch } from "@/components/icon-search"
import { PackageInstall } from "@/components/package-install"
import { Feedback } from "@/components/feedback"
import { FigmaIcon } from "@/components/figma-icon"
import { ComponentReference } from "@/components/component-reference"
import clsx from "clsx"
import { NextLink, Card, BorderedIcon, CodeMdx, CodeBlock } from "docs-ui"
@@ -120,7 +120,6 @@ const components = {
return <hr className={clx("mb-4", className)} {...props} />
},
HookValues,
ComponentProps,
CodeBlock,
ComponentExample,
PackageInstall,
@@ -130,6 +129,7 @@ const components = {
Card,
BorderedIcon,
FigmaIcon,
ComponentReference,
}
const Mdx = ({ code }: MdxProps) => {
@@ -142,4 +142,4 @@ const Mdx = ({ code }: MdxProps) => {
)
}
export { Mdx }
export { Mdx, components }
+136 -79
View File
@@ -3,13 +3,9 @@
import { InformationCircleSolid } from "@medusajs/icons"
import { Table, Tooltip } from "@medusajs/ui"
import {
EnumType,
FunctionType,
ObjectType,
PropData,
PropDataMap,
} from "@/types/props"
import { PropData, PropDataMap, PropSpecType } from "@/types/props"
import { useCallback, useMemo } from "react"
import { InlineCode, MarkdownContent } from "docs-ui"
type PropTableProps = {
props: PropDataMap
@@ -26,101 +22,162 @@ const PropTable = ({ props }: PropTableProps) => {
</Table.Row>
</Table.Header>
<Table.Body className="border-b-0 [&_tr:last-child]:border-b-0">
{/* eslint-disable-next-line react/prop-types */}
{props.map((propData, index) => (
<Row key={index} {...propData} />
{Object.entries(props).map(([propName, propData]) => (
<Row key={propName} propName={propName} propData={propData} />
))}
</Table.Body>
</Table>
)
}
const Row = ({ prop, type, defaultValue }: PropData) => {
const isEnum = (t: unknown): t is EnumType => {
return (t as EnumType).type !== undefined && (t as EnumType).type === "enum"
}
type RowProps = {
propName: string
propData: PropData
}
const isObject = (t: unknown): t is ObjectType => {
return (
(t as ObjectType).type !== undefined &&
(t as ObjectType).type === "object"
)
}
type TypeNode = {
text: string
tooltipContent?: string
canBeCopied?: boolean
}
const isFunction = (t: unknown): t is FunctionType => {
return (
(t as FunctionType).type !== undefined &&
(t as FunctionType).type === "function"
)
const Row = ({
propName,
propData: { tsType: tsType, defaultValue, description },
}: RowProps) => {
const normalizeRaw = (str: string): string => {
return str.replace("\\|", "|")
}
const defaultValueRenderer = (
v: string | number | boolean | null | undefined
) => {
if (v === undefined) {
return "-"
const getTypeRaw = useCallback((type: PropSpecType): string => {
let raw = "raw" in type ? type.raw || type.name : type.name
if ("type" in type) {
if (type.type === "object") {
raw = `{\n ${type.signature.properties
.map((property) => `${property.key}: ${property.value.name}`)
.join("\n ")}\n}`
} else {
raw = type.raw
}
} else if (type.name === "Array" && "elements" in type) {
raw = type.elements.map((element) => getTypeRaw(element)).join(" | ")
}
if (typeof v === "boolean") {
return v ? "true" : "false"
return normalizeRaw(raw)
}, [])
const getTypeText = useCallback((type: PropSpecType): string => {
if (type?.name === "signature" && "type" in type) {
return type.type
} else if (type?.name === "Array" && type.raw) {
return normalizeRaw(type.raw) || "array"
}
if (v === null) {
return "null"
return type.name || ""
}, [])
const getTypeTooltipContent = useCallback(
(type: PropSpecType): string | undefined => {
if (type?.name === "signature" && "type" in type) {
return getTypeRaw(type)
} else if (type?.name === "Array" && type.raw) {
return getTypeRaw(type)
}
return undefined
},
[getTypeRaw]
)
const typeNodes = useMemo((): TypeNode[] => {
const typeNodes: TypeNode[] = []
if (tsType?.name === "union" && "elements" in tsType) {
tsType.elements.forEach((element) => {
if (
("elements" in element && element.elements.length) ||
"signature" in element
) {
const elementTypeText = getTypeText(element)
const elementTooltipContent = getTypeTooltipContent(element)
typeNodes.push({
text: elementTypeText,
tooltipContent:
elementTypeText !== elementTooltipContent
? elementTooltipContent
: undefined,
})
} else if ("value" in element) {
typeNodes.push({
text: element.value,
canBeCopied: true,
})
} else {
typeNodes.push({
text: element.name,
})
}
})
} else if (tsType) {
typeNodes.push({
text: getTypeText(tsType),
tooltipContent: getTypeTooltipContent(tsType),
})
}
if (typeof v === "string") {
return `"${v}"`
}
return typeNodes
}, [tsType, getTypeText, getTypeTooltipContent])
return v
}
const isComplexType = isEnum(type) || isObject(type) || isFunction(type)
const defaultVal: string | undefined = defaultValue?.value as string
return (
<Table.Row className="code-body">
<Table.Cell>{prop}</Table.Cell>
<Table.Row>
<Table.Cell>
{!isComplexType && type.toString()}
{isEnum(type) && (
<Tooltip
content={type.values.map((v) => `"${v}"`).join(" | ")}
className="font-mono"
>
<div className="flex items-center gap-x-1">
<span>enum</span>
<div className="flex items-center gap-x-1">
<InlineCode>{propName}</InlineCode>
{description && (
<Tooltip
content={
<MarkdownContent
allowedElements={["a", "code"]}
unwrapDisallowed={true}
>
{description}
</MarkdownContent>
}
>
<InformationCircleSolid className="text-medusa-fg-subtle" />
</Tooltip>
)}
</div>
</Table.Cell>
<Table.Cell>
<div className="flex items-center flex-wrap gap-1 py-1">
{typeNodes.map((typeNode, index) => (
<div key={index} className="flex items-center gap-x-1">
{index > 0 && <span>|</span>}
{typeNode.tooltipContent && (
<Tooltip
content={<pre>{typeNode.tooltipContent}</pre>}
className="font-mono !max-w-none"
>
<div className="flex items-center gap-x-1">
<code>{typeNode.text}</code>
<InformationCircleSolid className="text-medusa-fg-subtle" />
</div>
</Tooltip>
)}
{!typeNode.tooltipContent && (
<>
{typeNode.canBeCopied && (
<InlineCode>{typeNode.text}</InlineCode>
)}
{!typeNode.canBeCopied && <code>{typeNode.text}</code>}
</>
)}
</div>
</Tooltip>
)}
{isObject(type) && (
<Tooltip
content={<pre>{type.shape}</pre>}
className="font-mono"
maxWidth={500}
>
<div className="flex items-center gap-x-1">
<span>{type.name}</span>
<InformationCircleSolid className="text-medusa-fg-subtle" />
</div>
</Tooltip>
)}
{isFunction(type) && (
<Tooltip
content={<pre>{type.signature}</pre>}
className="font-mono"
maxWidth={500}
>
<div className="flex items-center gap-x-1">
<span>function</span>
<InformationCircleSolid className="text-medusa-fg-subtle" />
</div>
</Tooltip>
)}
))}
</div>
</Table.Cell>
<Table.Cell className="text-right">
{defaultValueRenderer(defaultValue)}
{defaultVal && <InlineCode>{defaultVal}</InlineCode>}
{!defaultVal && " - "}
</Table.Cell>
</Table.Row>
)
@@ -1,60 +0,0 @@
"use client"
import { clx } from "@medusajs/ui"
import * as Primitives from "@radix-ui/react-scroll-area"
import clsx from "clsx"
import { Key } from "@/types/props"
type ScrollbarProps = React.ComponentProps<typeof Primitives.Scrollbar>
const Scrollbar = ({ key, ...props }: ScrollbarProps) => {
return (
<Primitives.Scrollbar
className={clsx(
"bg-medusa-bg-baseflex touch-none select-none p-0.5 transition-colors ease-out",
"data-[orientation=horizontal]:h-2.5 data-[orientation=vertical]:w-2.5 data-[orientation=horizontal]:flex-col"
)}
key={key as Key}
{...props}
/>
)
}
type ThumbProps = React.ComponentProps<typeof Primitives.Thumb>
const Thumb = ({ className, key, ...props }: ThumbProps) => {
return (
<Primitives.Thumb
className={clx(
"bg-medusa-bg-component relative flex-1 rounded-[10px] before:absolute before:left-1/2 before:top-1/2 before:h-full",
"before:min-h-[44px] before:w-full before:min-w-[44px] before:-translate-x-1/2 before:-translate-y-1/2 before:content-['']",
className
)}
key={key as Key}
{...props}
/>
)
}
type ScrollAreaProps = React.ComponentProps<typeof Primitives.Root>
const ScrollArea = ({ children, className }: ScrollAreaProps) => {
return (
<Primitives.Root
className={clx("h-full w-full overflow-hidden", className)}
>
<Primitives.Viewport className="h-full w-full">
{children}
</Primitives.Viewport>
<Scrollbar orientation="vertical">
<Thumb />
</Scrollbar>
<Scrollbar orientation="horizontal">
<Thumb />
</Scrollbar>
<Primitives.Corner />
</Primitives.Root>
)
}
export { ScrollArea }