docs: migrate UI docs (#13245)
* docs: create a new UI docs project (#13233) * docs: create a new UI docs project * fix installation errors * docs: migrate UI docs content to new project (#13241) * Fix content * added examples for some components * finish adding examples * lint fix * fix build errors * delete empty files * path fixes + refactor * fix build error
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import { CopyButton, H2, Hr, MarkdownContent, useColorMode } from "docs-ui"
|
||||
import { colors as allColors } from "@/config/colors"
|
||||
import { clx } from "@medusajs/ui"
|
||||
import slugify from "slugify"
|
||||
|
||||
type Color = {
|
||||
name: string
|
||||
code: string
|
||||
}
|
||||
|
||||
type ColorsTable = {
|
||||
[k: string]: {
|
||||
description?: string
|
||||
colors: Color[]
|
||||
}
|
||||
}
|
||||
|
||||
interface ColorBlockProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
colour: Color
|
||||
}
|
||||
|
||||
const ColorBlock = ({ colour, className, ...props }: ColorBlockProps) => {
|
||||
const [mounted, setMounted] = React.useState(false)
|
||||
|
||||
React.useEffect(() => setMounted(true), [])
|
||||
|
||||
if (!mounted) {
|
||||
return (
|
||||
<div className="flex w-fit flex-row items-center gap-x-2">
|
||||
<div
|
||||
className={
|
||||
"border-medusa-border-base h-[48px] w-[48px] rounded-lg border p-1"
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={clx(
|
||||
"bg-medusa-bg-component h-full w-full animate-pulse rounded-[4px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col items-start">
|
||||
<div className="bg-medusa-bg-component h-[20px] w-[85px] animate-pulse rounded-sm" />
|
||||
<div className="bg-medusa-bg-subtle h-[20px] w-[120px] animate-pulse rounded-sm" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-row items-center gap-x-2">
|
||||
<div
|
||||
className={
|
||||
"border-medusa-border-base h-[48px] w-[48px] rounded-lg border p-1"
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={clx("h-full w-full rounded-[4px]", className)}
|
||||
style={{ background: colour.code }}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col items-start">
|
||||
<p className="txt-compact-xsmall-plus text-medusa-fg-basetext-start">
|
||||
{cssVarToTailwindClass(colour.name)}
|
||||
</p>
|
||||
<p className="txt-compact-xsmall text-medusa-fg-subtle">
|
||||
{colour.code}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const cssVarToTailwindClass = (name: string) => {
|
||||
if (name.startsWith("--bg") || name.startsWith("--button")) {
|
||||
return name.replace("-", "bg-ui")
|
||||
}
|
||||
|
||||
if (name.startsWith("--fg")) {
|
||||
return name.replace("-", "text-ui")
|
||||
}
|
||||
|
||||
if (name.startsWith("--border")) {
|
||||
return name.replace("-", "border-ui")
|
||||
}
|
||||
|
||||
if (name.startsWith("--tag")) {
|
||||
if (name.includes("bg")) {
|
||||
return name.replace("-", "bg-ui")
|
||||
}
|
||||
if (name.includes("border")) {
|
||||
return name.replace("-", "border-ui")
|
||||
}
|
||||
if (name.includes("icon") || name.includes("text")) {
|
||||
return name.replace("-", "text-ui")
|
||||
}
|
||||
}
|
||||
|
||||
if (name.startsWith("--contrast") || name.startsWith("--alpha")) {
|
||||
return name.replace("-", "bg-ui")
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
const transformPrefixToTitle = (prefix: string) => {
|
||||
switch (prefix) {
|
||||
case "bg":
|
||||
return "Background"
|
||||
case "fg":
|
||||
return "Foreground"
|
||||
default:
|
||||
return prefix.charAt(0).toUpperCase() + prefix.slice(1)
|
||||
}
|
||||
}
|
||||
|
||||
const getDescriptionOfSection = (title: string) => {
|
||||
switch (title) {
|
||||
case "Alpha":
|
||||
case "Contrast":
|
||||
return "These colors can be used for foreground (using `text-` prefix), background (using `bg-` prefix), and border (using `border-` prefix) elements."
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
const Colors = () => {
|
||||
const { colorMode } = useColorMode()
|
||||
|
||||
const colors: ColorsTable = {}
|
||||
|
||||
for (const [tag, value] of Object.entries(allColors[colorMode])) {
|
||||
const prefixMatch = tag.match(/(--[a-zA-Z]+)/gi)
|
||||
if (!prefixMatch) {
|
||||
return
|
||||
}
|
||||
const prefix = transformPrefixToTitle(prefixMatch[0].replace("--", ""))
|
||||
if (!colors[prefix]) {
|
||||
colors[prefix] = {
|
||||
description: getDescriptionOfSection(prefix),
|
||||
colors: [],
|
||||
}
|
||||
}
|
||||
|
||||
colors[prefix].colors.push({
|
||||
name: tag,
|
||||
code: value as string,
|
||||
})
|
||||
}
|
||||
|
||||
const sortedSections = Object.entries(colors).sort((a, b) => {
|
||||
return a[0].localeCompare(b[0])
|
||||
})
|
||||
|
||||
for (const [, sectionData] of sortedSections) {
|
||||
sectionData.colors.sort((a, b) => {
|
||||
return a.name < b.name ? -1 : 1
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{sortedSections.map(([section, sectionData], index) => (
|
||||
<div className="mb-16" key={`colours-section-${section}`}>
|
||||
<H2 id={slugify(section)}>{section}</H2>
|
||||
{sectionData.description && (
|
||||
<MarkdownContent>{sectionData.description}</MarkdownContent>
|
||||
)}
|
||||
<div className="xs:grid-cols-2 mb-8 grid grid-cols-1 gap-4 gap-y-10 sm:grid-cols-3 ">
|
||||
{sectionData.colors.map((colour) => (
|
||||
<CopyButton
|
||||
text={cssVarToTailwindClass(colour.name)}
|
||||
key={`colours-section-${section}-${colour.name}`}
|
||||
>
|
||||
<ColorBlock colour={colour} />
|
||||
</CopyButton>
|
||||
))}
|
||||
</div>
|
||||
{index !== sortedSections.length - 1 && <Hr />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { Colors }
|
||||
@@ -0,0 +1,86 @@
|
||||
"use client"
|
||||
|
||||
import { Spinner } from "@medusajs/icons"
|
||||
import { Tabs, clx } from "@medusajs/ui"
|
||||
import { CodeBlock } from "docs-ui"
|
||||
import * as React from "react"
|
||||
|
||||
import Feedback from "@/components/Feedback"
|
||||
import { ExampleRegistry } from "@/specs/examples.mjs"
|
||||
|
||||
interface ComponentExampleProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
name: string
|
||||
disableCenterAlignPreview?: boolean
|
||||
hideFeedback?: boolean
|
||||
}
|
||||
|
||||
export function ComponentExample({
|
||||
children,
|
||||
name,
|
||||
disableCenterAlignPreview = false,
|
||||
hideFeedback = false,
|
||||
...props
|
||||
}: ComponentExampleProps) {
|
||||
const Preview = React.useMemo(() => {
|
||||
const Component = ExampleRegistry[name]?.component
|
||||
|
||||
if (!Component) {
|
||||
return <p>Component {name} not found in registry</p>
|
||||
}
|
||||
|
||||
return <Component />
|
||||
}, [name])
|
||||
|
||||
const CodeElement = children as React.ReactElement
|
||||
const Code = JSON.parse(
|
||||
(CodeElement.props as Record<string, string>).codeLinesJSON
|
||||
).join("\n")
|
||||
|
||||
return (
|
||||
<div className="relative my-4 flex flex-col space-y-2" {...props}>
|
||||
<Tabs defaultValue="preview" className="relative mr-auto w-full">
|
||||
<div className="flex flex-col pb-3">
|
||||
<Tabs.List>
|
||||
<Tabs.Trigger value="preview">Preview</Tabs.Trigger>
|
||||
<Tabs.Trigger value="code">Code</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
<Tabs.Content
|
||||
value="preview"
|
||||
className="relative data-[state=active]:mt-4"
|
||||
>
|
||||
<div
|
||||
className={clx(
|
||||
"bg-medusa-bg-base border-medusa-border-base flex max-h-[400px] min-h-[400px]",
|
||||
"w-full overflow-auto justify-center rounded-md border px-10 py-5",
|
||||
!disableCenterAlignPreview && "items-center"
|
||||
)}
|
||||
>
|
||||
<React.Suspense
|
||||
fallback={
|
||||
<div className="text-medusa-fg-muted flex items-center text-sm">
|
||||
<Spinner className="animate-spin" />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{Preview}
|
||||
</React.Suspense>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
<Tabs.Content
|
||||
value="code"
|
||||
className="relative data-[state=active]:mt-4"
|
||||
>
|
||||
<CodeBlock source={Code} lang="tsx" />
|
||||
</Tabs.Content>
|
||||
</div>
|
||||
</Tabs>
|
||||
{!hideFeedback && (
|
||||
<Feedback
|
||||
title={`example ${name}`}
|
||||
question="Was this example helpful?"
|
||||
showDottedSeparator={false}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import { Documentation } from "react-docgen"
|
||||
import { Suspense } from "react"
|
||||
import { Spinner } from "@medusajs/icons"
|
||||
import { PropTable } from "../PropsTable"
|
||||
import { Container } from "@medusajs/ui"
|
||||
import Feedback from "../Feedback"
|
||||
import { H3, MarkdownContent } from "docs-ui"
|
||||
import MDXComponents from "../MDXComponents"
|
||||
import slugify from "slugify"
|
||||
|
||||
type ComponentReferenceProps = {
|
||||
mainComponent: string
|
||||
componentsToShow?: string[]
|
||||
specsSrc?: string
|
||||
hideFeedback?: boolean
|
||||
}
|
||||
|
||||
const ComponentReference = ({
|
||||
mainComponent,
|
||||
componentsToShow = [mainComponent],
|
||||
specsSrc,
|
||||
hideFeedback = false,
|
||||
}: 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
|
||||
const componentName =
|
||||
componentsToShow.length > 1
|
||||
? componentSpec?.displayName || component
|
||||
: ""
|
||||
const componentSlug = slugify(componentName)
|
||||
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 id={componentSlug}>{componentName}</H3>
|
||||
)}
|
||||
{componentSpec.description && (
|
||||
<MarkdownContent components={MDXComponents}>
|
||||
{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>
|
||||
{!hideFeedback && (
|
||||
<Feedback
|
||||
title={`props of ${component}`}
|
||||
question="Was this helpful?"
|
||||
showDottedSeparator={false}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Suspense>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { ComponentReference }
|
||||
@@ -0,0 +1,37 @@
|
||||
"use client"
|
||||
|
||||
import { EditButton as UiEditButton } from "docs-ui"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { useCallback, useEffect, useState } from "react"
|
||||
|
||||
const EditButton = () => {
|
||||
const pathname = usePathname()
|
||||
const [editDate, setEditDate] = useState<string | undefined>()
|
||||
|
||||
const loadEditDate = useCallback(async () => {
|
||||
const generatedEditDates = (await import("../../generated/edit-dates.mjs"))
|
||||
.generatedEditDates
|
||||
setEditDate(
|
||||
(generatedEditDates as Record<string, string>)[
|
||||
`app${pathname.replace(/\/$/, "")}/page.mdx`
|
||||
]
|
||||
)
|
||||
}, [pathname])
|
||||
|
||||
useEffect(() => {
|
||||
void loadEditDate()
|
||||
}, [loadEditDate])
|
||||
|
||||
if (!editDate) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
return (
|
||||
<UiEditButton
|
||||
filePath={`/www/apps/ui/app${pathname.replace(/\/$/, "")}/page.mdx`}
|
||||
editDate={editDate}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditButton
|
||||
@@ -0,0 +1,28 @@
|
||||
"use client"
|
||||
|
||||
import {
|
||||
Feedback as UiFeedback,
|
||||
FeedbackProps as UiFeedbackProps,
|
||||
} from "docs-ui"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { basePathUrl } from "../../utils/base-path-url"
|
||||
import { useMemo } from "react"
|
||||
|
||||
type FeedbackProps = Omit<UiFeedbackProps, "event" | "pathName">
|
||||
|
||||
const Feedback = (props: FeedbackProps) => {
|
||||
const pathname = usePathname()
|
||||
|
||||
const feedbackPathname = useMemo(() => basePathUrl(pathname), [pathname])
|
||||
|
||||
return (
|
||||
<UiFeedback
|
||||
event="survey"
|
||||
pathName={feedbackPathname}
|
||||
question="Was this guide helpful?"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default Feedback
|
||||
@@ -0,0 +1,15 @@
|
||||
import React from "react"
|
||||
import { Card } from "docs-ui"
|
||||
import { basePathUrl } from "@/utils/base-path-url"
|
||||
|
||||
export const FigmaCard = () => {
|
||||
return (
|
||||
<Card
|
||||
title="Medusa UI"
|
||||
text="Colors, type, icons and components"
|
||||
href="https://www.figma.com/community/file/1278648465968635936/Medusa-UI"
|
||||
image={basePathUrl("/images/figma.png")}
|
||||
iconClassName="!p-0"
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
"use client"
|
||||
|
||||
import { Footer as UiFooter } from "docs-ui"
|
||||
import Feedback from "../Feedback"
|
||||
import EditButton from "../EditButton"
|
||||
|
||||
const Footer = () => {
|
||||
return (
|
||||
<UiFooter
|
||||
showPagination={true}
|
||||
feedbackComponent={<Feedback className="my-2" />}
|
||||
editComponent={<EditButton />}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default Footer
|
||||
@@ -0,0 +1,136 @@
|
||||
import { InformationCircleSolid } from "@medusajs/icons"
|
||||
|
||||
import {
|
||||
HookData,
|
||||
HookDataMap,
|
||||
EnumType,
|
||||
FunctionType,
|
||||
ObjectType,
|
||||
} from "@/types/ui"
|
||||
import { InlineCode, Table, Tooltip } from "docs-ui"
|
||||
|
||||
interface HookTableProps {
|
||||
props: HookDataMap
|
||||
isReturn?: boolean
|
||||
}
|
||||
|
||||
const HookTable = ({ props, isReturn = false }: HookTableProps) => {
|
||||
return (
|
||||
<Table className="!mb-0">
|
||||
<Table.Header className="border-t-0">
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Name</Table.HeaderCell>
|
||||
<Table.HeaderCell>Type</Table.HeaderCell>
|
||||
<Table.HeaderCell className="!text-right">
|
||||
{isReturn ? "Description" : "Default"}
|
||||
</Table.HeaderCell>
|
||||
</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} isReturn={isReturn} />
|
||||
))}
|
||||
</Table.Body>
|
||||
</Table>
|
||||
)
|
||||
}
|
||||
|
||||
interface RowProps extends HookData {
|
||||
isReturn?: boolean
|
||||
}
|
||||
|
||||
const Row = ({
|
||||
value,
|
||||
type,
|
||||
description,
|
||||
default: defaultValue,
|
||||
isReturn = false,
|
||||
}: RowProps) => {
|
||||
const isEnum = (t: unknown): t is EnumType => {
|
||||
return (t as EnumType).type !== undefined && (t as EnumType).type === "enum"
|
||||
}
|
||||
|
||||
const isObject = (t: unknown): t is ObjectType => {
|
||||
return (
|
||||
(t as ObjectType).type !== undefined &&
|
||||
(t as ObjectType).type === "object"
|
||||
)
|
||||
}
|
||||
|
||||
const isFunction = (t: unknown): t is FunctionType => {
|
||||
return (
|
||||
(t as FunctionType).type !== undefined &&
|
||||
(t as FunctionType).type === "function"
|
||||
)
|
||||
}
|
||||
|
||||
const isComplexType = isEnum(type) || isObject(type) || isFunction(type)
|
||||
|
||||
return (
|
||||
<Table.Row className="code-body">
|
||||
<Table.Cell>
|
||||
<div className="flex items-center gap-x-1">
|
||||
<InlineCode>{value}</InlineCode>
|
||||
{!isReturn && description && (
|
||||
<Tooltip content={description} className="max-w-[350px] text-left">
|
||||
<InformationCircleSolid className="text-medusa-fg-subtle" />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</Table.Cell>
|
||||
<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>
|
||||
<InformationCircleSolid className="text-medusa-fg-subtle" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
{isObject(type) && (
|
||||
<Tooltip
|
||||
tooltipChildren={<pre>{type.shape}</pre>}
|
||||
className="font-mono max-w-[500px]"
|
||||
tooltipClassName="!text-left"
|
||||
>
|
||||
<div className="flex items-center gap-x-1">
|
||||
<span>{type.name}</span>
|
||||
<InformationCircleSolid className="text-medusa-fg-subtle" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
{isFunction(type) && (
|
||||
<Tooltip
|
||||
tooltipChildren={<pre>{type.signature}</pre>}
|
||||
className="font-mono max-w-[500px]"
|
||||
>
|
||||
<div className="flex items-center gap-x-1">
|
||||
<span>function</span>
|
||||
<InformationCircleSolid className="text-medusa-fg-subtle" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell className="!text-right">
|
||||
{isReturn ? (
|
||||
description ? (
|
||||
<span>{description}</span>
|
||||
) : (
|
||||
<span className="text-medusa-fg-muted"> - </span>
|
||||
)
|
||||
) : defaultValue !== undefined && defaultValue !== null ? (
|
||||
<InlineCode>{defaultValue.toString()}</InlineCode>
|
||||
) : (
|
||||
<span className="text-medusa-fg-muted"> - </span>
|
||||
)}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
)
|
||||
}
|
||||
|
||||
export { HookTable }
|
||||
@@ -0,0 +1,51 @@
|
||||
import { Spinner } from "@medusajs/icons"
|
||||
import { Container } from "@medusajs/ui"
|
||||
import * as React from "react"
|
||||
|
||||
import { HookRegistry } from "@/specs/hooks"
|
||||
import Feedback from "../Feedback"
|
||||
|
||||
type HookValuesProps = {
|
||||
hook: string
|
||||
hideFeedback?: boolean
|
||||
}
|
||||
|
||||
const HookValues = ({ hook, hideFeedback = false }: HookValuesProps) => {
|
||||
const Props = React.useMemo(() => {
|
||||
const Table = HookRegistry[hook]?.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">{hook}</span>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return <Table />
|
||||
}, [hook])
|
||||
|
||||
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>
|
||||
{!hideFeedback && (
|
||||
<Feedback title={`props of ${hook}`} showDottedSeparator={false} />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { HookValues }
|
||||
@@ -0,0 +1,94 @@
|
||||
"use client"
|
||||
|
||||
import * as Icons from "@medusajs/icons"
|
||||
import { Container, Input, Text } from "@medusajs/ui"
|
||||
import clsx from "clsx"
|
||||
import { CopyButton } from "docs-ui"
|
||||
import * as React from "react"
|
||||
|
||||
const iconNames = Object.keys(Icons).filter((name) => name !== "default")
|
||||
|
||||
const IconSearch = () => {
|
||||
const [query, setQuery] = React.useState<string | undefined>("")
|
||||
|
||||
return (
|
||||
<div className="mt-8 flex flex-col gap-y-2">
|
||||
<Input
|
||||
type="search"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
/>
|
||||
<Container>
|
||||
<SearchResults query={query} />
|
||||
</Container>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const SearchResults = ({ query = "" }: { query?: string }) => {
|
||||
const cleanQuery = escapeStringRegexp(query.trim().replace(/\s/g, " "))
|
||||
const results = iconNames.filter((name) =>
|
||||
new RegExp(`\\b${cleanQuery}`, "gi").test(name)
|
||||
)
|
||||
|
||||
if (results.length === 0) {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"text-medusa-text-muted dark:text-medusa-text-muted-dark",
|
||||
"flex min-h-[300px] items-center justify-center"
|
||||
)}
|
||||
>
|
||||
<Text>
|
||||
No results found for{" "}
|
||||
<Text weight={"plus"} asChild>
|
||||
<span>{query}</span>
|
||||
</Text>
|
||||
</Text>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid w-full grid-cols-4 gap-8 md:grid-cols-6 lg:grid-cols-8">
|
||||
{results.map((name) => {
|
||||
return (
|
||||
<div
|
||||
key={name}
|
||||
className="flex h-full w-full items-center justify-center"
|
||||
>
|
||||
<CopyButton text={name} tooltipText={name} handleTouch>
|
||||
<div
|
||||
className={clsx(
|
||||
"border-medusa-border-base",
|
||||
"flex h-10 w-10 items-center justify-center rounded-lg border"
|
||||
)}
|
||||
>
|
||||
<span className="sr-only">Icon named {name}</span>
|
||||
<div
|
||||
className={clsx(
|
||||
"bg-medusa-bg-component text-medusa-fg-base",
|
||||
"flex h-8 w-8 items-center justify-center rounded-[4px]"
|
||||
)}
|
||||
>
|
||||
{React.createElement(Icons[name as keyof typeof Icons])}
|
||||
</div>
|
||||
</div>
|
||||
</CopyButton>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// https://github.com/sindresorhus/escape-string-regexp/blob/main/index.js
|
||||
function escapeStringRegexp(string: unknown) {
|
||||
if (typeof string !== "string") {
|
||||
throw new TypeError("Expected a string")
|
||||
}
|
||||
|
||||
return string.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&").replace(/-/g, "\\x2d")
|
||||
}
|
||||
|
||||
export { IconSearch }
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { MDXComponents as MDXComponentsType } from "mdx/types"
|
||||
import {
|
||||
Link,
|
||||
MDXComponents as UiMdxComponents,
|
||||
InlineThemeImage,
|
||||
InlineIcon,
|
||||
} from "docs-ui"
|
||||
|
||||
const MDXComponents: MDXComponentsType = {
|
||||
...UiMdxComponents,
|
||||
a: Link,
|
||||
InlineThemeImage,
|
||||
InlineIcon,
|
||||
}
|
||||
|
||||
export default MDXComponents
|
||||
@@ -0,0 +1,196 @@
|
||||
"use client"
|
||||
|
||||
import { InformationCircleSolid } from "@medusajs/icons"
|
||||
|
||||
import { PropData, PropDataMap, PropSpecType } from "@/types/ui"
|
||||
import { useCallback, useMemo } from "react"
|
||||
import { InlineCode, MarkdownContent, Table, Tooltip } from "docs-ui"
|
||||
|
||||
type PropTableProps = {
|
||||
props: PropDataMap
|
||||
}
|
||||
|
||||
const PropTable = ({ props }: PropTableProps) => {
|
||||
return (
|
||||
<Table className="!mb-0">
|
||||
<Table.Header className="border-t-0">
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Prop</Table.HeaderCell>
|
||||
<Table.HeaderCell>Type</Table.HeaderCell>
|
||||
<Table.HeaderCell className="!text-right">Default</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body className="border-b-0 [&_tr:last-child]:border-b-0">
|
||||
{Object.entries(props).map(([propName, propData]) => (
|
||||
<Row key={propName} propName={propName} propData={propData} />
|
||||
))}
|
||||
</Table.Body>
|
||||
</Table>
|
||||
)
|
||||
}
|
||||
|
||||
type RowProps = {
|
||||
propName: string
|
||||
propData: PropData
|
||||
}
|
||||
|
||||
type TypeNode = {
|
||||
text: string
|
||||
tooltipContent?: string
|
||||
canBeCopied?: boolean
|
||||
}
|
||||
|
||||
const Row = ({
|
||||
propName,
|
||||
propData: { tsType: tsType, defaultValue, description },
|
||||
}: RowProps) => {
|
||||
const normalizeRaw = (str: string): string => {
|
||||
return str
|
||||
.replaceAll("\\|", "|")
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
}
|
||||
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(" | ")
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
return type.name || ""
|
||||
}, [])
|
||||
const getTypeTooltipContent = useCallback(
|
||||
(type: PropSpecType): string | undefined => {
|
||||
if (
|
||||
(type?.name === "signature" && "type" in type) ||
|
||||
(type?.name === "Array" && type.raw) ||
|
||||
("raw" in type && 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 if ("raw" in element) {
|
||||
typeNodes.push({
|
||||
text: getTypeText(element),
|
||||
tooltipContent: getTypeTooltipContent(element),
|
||||
})
|
||||
} else {
|
||||
typeNodes.push({
|
||||
text: element.name,
|
||||
})
|
||||
}
|
||||
})
|
||||
} else if (tsType) {
|
||||
typeNodes.push({
|
||||
text: getTypeText(tsType),
|
||||
tooltipContent: getTypeTooltipContent(tsType),
|
||||
})
|
||||
}
|
||||
|
||||
return typeNodes
|
||||
}, [tsType, getTypeText, getTypeTooltipContent])
|
||||
|
||||
const defaultVal: string | undefined = defaultValue?.value as string
|
||||
|
||||
return (
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<div className="flex items-center gap-x-1">
|
||||
<InlineCode>{propName}</InlineCode>
|
||||
{description && (
|
||||
<Tooltip
|
||||
tooltipChildren={
|
||||
<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
|
||||
tooltipChildren={<pre>{typeNode.tooltipContent}</pre>}
|
||||
className="font-mono !max-w-none"
|
||||
tooltipClassName="!text-left"
|
||||
>
|
||||
<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>
|
||||
))}
|
||||
</div>
|
||||
</Table.Cell>
|
||||
<Table.Cell className="text-right">
|
||||
{defaultVal && <InlineCode>{defaultVal}</InlineCode>}
|
||||
{!defaultVal && " - "}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
)
|
||||
}
|
||||
|
||||
export { PropTable }
|
||||
Reference in New Issue
Block a user