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:
co-authored by
Kasper Fabricius Kristensen
parent
edc49bfe1d
commit
245e5c9a69
@@ -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 }
|
||||
@@ -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 }
|
||||
|
||||
@@ -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 }
|
||||
Reference in New Issue
Block a user