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
@@ -0,0 +1,78 @@
import { Text } from "@medusajs/ui"
import { allDocs } from "contentlayer/generated"
import { notFound } from "next/navigation"
import { Mdx } from "@/components/mdx-components"
import { siteConfig } from "@/config/site"
import { Metadata } from "next"
interface DocPageProps {
params: {
slug: string[]
}
}
async function getDocFromParams({ params }: DocPageProps) {
const slug = params.slug?.join("/") || ""
const doc = allDocs.find((doc) => doc.slugAsParams === slug)
if (!doc) {
null
}
return doc
}
export async function generateMetadata({
params,
}: DocPageProps): Promise<Metadata> {
const doc = await getDocFromParams({ params })
if (!doc) {
return {}
}
const title = `${doc.title} - ${siteConfig.name}`
return {
title: title,
description: doc.description,
metadataBase: new URL(
process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
),
}
}
export async function generateStaticParams(): Promise<
DocPageProps["params"][]
> {
return allDocs.map((doc) => ({
slug: doc.slugAsParams.split("/"),
}))
}
export default async function DocPage({ params }: DocPageProps) {
const doc = await getDocFromParams({ params })
if (!doc) {
notFound()
}
return (
<div className="flex flex-col">
<h1 className="h1-docs text-medusa-fg-base dark:text-medusa-fg-base-dark mb-2">
{doc.title}
</h1>
<Text
className="text-medusa-fg-subtle dark:text-medusa-fg-subtle-dark mb-6"
size="large"
>
{doc.description}
</Text>
<div>
<Mdx code={doc.body.code} />
</div>
</div>
)
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

+34
View File
@@ -0,0 +1,34 @@
import { MetadataRoute } from "next"
import { docsConfig } from "@/config/docs"
import { absoluteUrl } from "@/lib/absolute-url"
import { SidebarItemType } from "docs-ui"
export default function sitemap(): MetadataRoute.Sitemap {
const now = new Date()
const items: Array<{
url: string
lastModified?: string | Date
}> = []
function pushItems(newItems: SidebarItemType[]) {
newItems.forEach((item) => {
if (item.path) {
items.push({
url: absoluteUrl(item.path),
lastModified: now,
})
}
if (item.children) {
pushItems(item.children)
}
})
}
pushItems(docsConfig.sidebar.top)
pushItems(docsConfig.sidebar.bottom)
return items
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

+52
View File
@@ -0,0 +1,52 @@
import type { Metadata } from "next"
import { Inter, Roboto_Mono } from "next/font/google"
import { Navbar } from "@/components/navbar"
import { Providers } from "@/providers"
import { siteConfig } from "@/config/site"
import "../styles/globals.css"
import { Sidebar } from "docs-ui"
const inter = Inter({ subsets: ["latin"], variable: "--inter" })
const robotoMono = Roboto_Mono({
subsets: ["latin"],
variable: "--font-roboto-mono",
})
export const metadata: Metadata = {
title: siteConfig.name,
description: siteConfig.description,
metadataBase: new URL(
process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
),
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className="h-full w-full">
<head />
<body
className={` bg-docs-bg dark:bg-docs-bg-dark h-screen w-full ${inter.variable} ${robotoMono.variable}`}
>
<Providers>
<div className="w-full">
<Navbar />
<div className="max-w-xxl grid w-full grid-cols-1 px-6 lg:mx-auto lg:grid-cols-[280px_1fr]">
<Sidebar expandItems={true} />
<div className="relative flex w-full flex-1 items-start justify-center px-4 pb-8 pt-16 md:px-8 lg:px-16 lg:py-[112px]">
<main className="h-full w-full lg:max-w-[720px] lg:px-px">
{children}
</main>
</div>
</div>
</div>
</Providers>
</body>
</html>
)
}
+4
View File
@@ -0,0 +1,4 @@
User-Agent: *
Allow: /
Sitemap: https://docs.medusajs.com/ui/assets/sitemap.xml
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>https://docs.medusajs.com/ui/assets/sitemap.xml</loc>
</sitemap>
<sitemap>
<loc>https://docs.medusajs.com/sitemap-docs.xml</loc>
</sitemap>
</sitemapindex>
+171
View File
@@ -0,0 +1,171 @@
"use client"
import { Copy, clx } from "@medusajs/ui"
import React from "react"
import { useColorMode } from "docs-ui"
import { colors as allColors } from "../config/colors"
type Color = {
name: string
code: string
}
type ColorsTable = {
backgrounds: Color[]
foregrounds: Color[]
borders: Color[]
buttons: Color[]
code: Color[]
tags: Color[]
}
const PREFIXES: { [k: string]: keyof ColorsTable } = {
"--bg": "backgrounds",
"--fg": "foregrounds",
"--border": "borders",
"--button": "buttons",
"--code": "code",
"--tag": "tags",
}
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 dark:border-medusa-border-base-dark h-[48px] w-[48px] rounded-lg border p-1"
}
>
<div
className={clx(
"bg-medusa-bg-component dark:bg-medusa-bg-component-dark h-full w-full animate-pulse rounded-[4px]",
className
)}
{...props}
/>
</div>
<div className="flex flex-col items-start">
<div className="bg-medusa-bg-component dark:bg-medusa-bg-component-dark h-[20px] w-[85px] animate-pulse rounded-sm" />
<div className="bg-medusa-bg-subtle dark:bg-medusa-bg-subtle-dark 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 dark:border-medusa-border-base-dark 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-base dark:text-medusa-fg-base-dark text-start">
{cssVarToTailwindClass(colour.name)}
</p>
<p className="txt-compact-xsmall text-medusa-fg-subtle dark:text-medusa-fg-subtle-dark">
{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") || name.startsWith("--code")) {
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")
}
}
return name
}
const Colors = () => {
const { colorMode } = useColorMode()
const colors: ColorsTable = {
backgrounds: [],
foregrounds: [],
borders: [],
buttons: [],
code: [],
tags: [],
}
for (const [tag, value] of Object.entries(allColors[colorMode])) {
const prefix = tag.match(/(--[a-zA-Z]+)/gi)
if (prefix && Object.keys(PREFIXES).includes(prefix[0])) {
if (!tag.includes("gradient")) {
colors[PREFIXES[prefix[0]]].push({
name: tag,
code: value as string,
})
}
}
}
for (const [, section] of Object.entries(colors)) {
section.sort((a, b) => {
return a.name < b.name ? -1 : 1
})
}
return (
<div>
{Object.entries(colors).map(([section, colors]) => (
<div className="mb-16" key={`colours-section-${section}`}>
<h2 className="h2-docs mb-4 mt-10 text-medusa-fg-base dark:text-medusa-fg-base-dark">
{section.charAt(0).toUpperCase() + section.slice(1)}
</h2>
<hr className="mb-4" />
<div className="xs:grid-cols-2 mb-8 grid grid-cols-1 gap-4 gap-y-10 sm:grid-cols-3 ">
{colors.map((colour) => (
<Copy
content={cssVarToTailwindClass(colour.name)}
key={`colours-section-${section}-${colour.name}`}
>
<ColorBlock colour={colour} />
</Copy>
))}
</div>
</div>
))}
</div>
)
}
export { Colors }
@@ -0,0 +1,72 @@
"use client"
import { Spinner } from "@medusajs/icons"
import * as React from "react"
import { ExampleRegistry } from "../registries/example-registry"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/tabs"
import { Feedback } from "./feedback"
import clsx from "clsx"
import { CodeBlock } from "docs-ui"
interface ComponentExampleProps extends React.HTMLAttributes<HTMLDivElement> {
name: string
}
export function ComponentExample({
children,
name,
...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 = CodeElement.props.code
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 items-center justify-between pb-3">
<TabsList className="">
<TabsTrigger value="preview">Preview</TabsTrigger>
<TabsTrigger value="code">Code</TabsTrigger>
</TabsList>
<TabsContent value="preview" className="relative">
<div
className={clsx(
"bg-docs-bg border-medusa-border-base flex max-h-[400px] min-h-[400px]",
"dark:bg-docs-bg-dark dark:border-medusa-border-base-dark",
"w-full items-center justify-center overflow-auto rounded-md border px-10 py-5"
)}
>
<React.Suspense
fallback={
<div className="text-medusa-fg-muted dark:text-medusa-fg-muted-dark flex items-center text-sm">
<Spinner className="animate-spin" />
</div>
}
>
{Preview}
</React.Suspense>
</div>
</TabsContent>
<TabsContent value="code" className="relative ">
<CodeBlock source={Code} lang="tsx" />
</TabsContent>
</div>
</Tabs>
<Feedback
title={`example ${name}`}
question="Was this example helpful?"
/>
</div>
)
}
@@ -0,0 +1,48 @@
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 dark:text-medusa-fg-muted-dark flex flex-1 items-center justify-center">
<Spinner className="animate-spin" />
</div>
}
>
{Props}
</React.Suspense>
</Container>
<Feedback title={`props of ${component}`} />
</>
)
}
export { ComponentProps }
+34
View File
@@ -0,0 +1,34 @@
"use client"
import {
Feedback as UiFeedback,
FeedbackProps as UiFeedbackProps,
formatReportLink,
} from "docs-ui"
import { usePathname } from "next/navigation"
import { absoluteUrl } from "@/lib/absolute-url"
import clsx from "clsx"
export type FeedbackProps = {
title: string
} & Partial<UiFeedbackProps>
export const Feedback = ({ title, ...props }: FeedbackProps) => {
const pathname = usePathname()
return (
<UiFeedback
event="survey"
pathName={absoluteUrl(pathname)}
reportLink={formatReportLink("UI Docs", title)}
extraData={{
section: title,
}}
{...props}
className={clsx(
"text-medusa-fg-subtle dark:text-medusa-fg-subtle-dark",
props.className
)}
/>
)
}
@@ -0,0 +1,6 @@
import { BorderedIcon } from "docs-ui"
import { basePathUrl } from "@/lib/base-path-url"
export const FigmaIcon = () => {
return <BorderedIcon icon={basePathUrl("/images/figma.png")} />
}
+94
View File
@@ -0,0 +1,94 @@
import { InformationCircleSolid } from "@medusajs/icons"
import { Table, Tooltip } from "@medusajs/ui"
import { HookData, HookDataMap } from "@/types/hooks"
import { EnumType, FunctionType, ObjectType } from "@/types/props"
const HookTable = ({ props }: { props: HookDataMap }) => {
return (
<Table>
<Table.Header className="border-t-0">
<Table.Row>
<Table.HeaderCell>Value</Table.HeaderCell>
<Table.HeaderCell>Type</Table.HeaderCell>
<Table.HeaderCell>Description</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} />
))}
</Table.Body>
</Table>
)
}
const Row = ({ value, type, description }: HookData) => {
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>{value}</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 dark:text-medusa-fg-subtle-dark" />
</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 dark:text-medusa-fg-subtle-dark" />
</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 dark:text-medusa-fg-subtle-dark" />
</div>
</Tooltip>
)}
</Table.Cell>
<Table.Cell>{description}</Table.Cell>
</Table.Row>
)
}
export { HookTable }
@@ -0,0 +1,48 @@
import { Spinner } from "@medusajs/icons"
import { Container } from "@medusajs/ui"
import * as React from "react"
import { HookRegistry } from "@/registries/hook-registry"
import { Feedback } from "./feedback"
type HookValuesProps = {
hook: string
}
const HookValues = ({ hook }: 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 dark:text-medusa-fg-muted-dark flex flex-1 items-center justify-center">
<Spinner className="animate-spin" />
</div>
}
>
{Props}
</React.Suspense>
</Container>
<Feedback title={`props of ${hook}`} />
</>
)
}
export { HookValues }
@@ -0,0 +1,94 @@
"use client"
import * as Icons from "@medusajs/icons"
import { Container, Input, Text, Tooltip } from "@medusajs/ui"
import clsx from "clsx"
import * as React from "react"
const iconNames = Object.keys(Icons).filter((name) => name !== "default")
const IconSearch = React.memo(function 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"
>
<Tooltip content={name}>
<div
className={clsx(
"border-medusa-border-base dark:border-medusa-border-base-dark",
"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",
"dark:bg-medusa-bg-component-dark dark:text-medusa-fg-base-dark",
"flex h-8 w-8 items-center justify-center rounded-[4px]"
)}
>
{React.createElement(Icons[name as keyof typeof Icons])}
</div>
</div>
</Tooltip>
</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,150 @@
"use client"
import { Text, clx } from "@medusajs/ui"
import { useMDXComponent } from "next-contentlayer/hooks"
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 clsx from "clsx"
import { NextLink, Card, BorderedIcon, CodeMdx, CodeBlock } from "docs-ui"
interface MdxProps {
code: string
}
const components = {
h1: ({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => {
return (
<h1
className={clx(
"h1-docs text-medusa-fg-base dark:text-medusa-fg-base-dark",
className
)}
{...props}
/>
)
},
h2: ({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => {
return (
<h2
className={clx(
"h2-docs mb-4 mt-16 text-medusa-fg-base dark:text-medusa-fg-base-dark",
className
)}
{...props}
/>
)
},
h3: ({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => {
return (
<h3
className={clx(
"h3-docs mb-2 mt-10 text-medusa-fg-base dark:text-medusa-fg-base-dark",
className
)}
{...props}
/>
)
},
p: ({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) => {
return (
<Text
className={clx(
"text-medusa-fg-subtle dark:text-medusa-fg-subtle-dark mb-docs_1.5",
className
)}
{...props}
/>
)
},
a: ({
className,
href,
...props
}: React.AnchorHTMLAttributes<HTMLAnchorElement>) => {
const isInternal = href && href?.startsWith("/")
if (isInternal) {
return <NextLink className={className} href={href} {...props} />
}
return (
<a
className={clx(
"txt-medium text-medusa-fg-interactive hover:text-medusa-fg-interactive-hover",
"dark:text-medusa-fg-interactive-dark dark:hover:text-medusa-fg-interactive-hover-dark",
className
)}
href={href}
rel="noopener noreferrer"
target="_blank"
{...props}
/>
)
},
code: CodeMdx,
ul: ({
className,
children,
...props
}: React.HTMLAttributes<HTMLUListElement>) => {
return (
<ul
{...props}
className={clsx("list-disc px-docs_1 mb-docs_1.5", className)}
>
{children}
</ul>
)
},
li: ({
className,
children,
...props
}: React.HTMLAttributes<HTMLElement>) => {
return (
<li
className={clx(
"text-medusa-fg-subtle dark:text-medusa-fg-subtle-dark",
className
)}
{...props}
>
<Text>{children}</Text>
</li>
)
},
hr: ({ className, ...props }: React.HTMLAttributes<HTMLHRElement>) => {
return <hr className={clx("mb-4", className)} {...props} />
},
HookValues,
ComponentProps,
CodeBlock,
ComponentExample,
PackageInstall,
IconSearch,
Feedback,
Colors,
Card,
BorderedIcon,
FigmaIcon,
}
const Mdx = ({ code }: MdxProps) => {
const Component = useMDXComponent(code)
return (
<div>
<Component components={components} />
</div>
)
}
export { Mdx }
+26
View File
@@ -0,0 +1,26 @@
"use client"
import { docsConfig } from "@/config/docs"
import { Navbar as UiNavbar, useSidebar } from "docs-ui"
import { basePathUrl } from "@/lib/base-path-url"
const Navbar = () => {
const { mobileSidebarOpen, setMobileSidebarOpen } = useSidebar()
return (
<UiNavbar
logo={{
light: basePathUrl("/images/logo-icon.png"),
dark: basePathUrl("/images/logo-icon-dark.png"),
}}
items={docsConfig.mainNav}
mobileMenuButton={{
setMobileSidebarOpen,
mobileSidebarOpen,
}}
className="!z-[99]"
/>
)
}
export { Navbar }
@@ -0,0 +1,35 @@
import { clx } from "@medusajs/ui"
import { CodeTabs } from "docs-ui"
type PackageInstallProps = {
packageName: string
devDependency?: boolean
version?: string
className?: string
}
const PackageInstall = ({
packageName,
devDependency = false,
version,
className,
}: PackageInstallProps) => {
const pkg = `${packageName}${version ? `@${version}` : ""}`
const yarn = `yarn add ${devDependency ? "-D " : ""}${pkg}`
const npm = `npm install ${pkg} ${devDependency ? "--save-dev" : "--save"}`
const pnpm = `pnpm add ${devDependency ? "-D " : ""}${pkg}`
return (
<CodeTabs
tabs={[
{ code: { lang: "bash", source: yarn }, label: "npm", value: "npm" },
{ code: { lang: "bash", source: npm }, label: "yarn", value: "yarn" },
{ code: { lang: "bash", source: pnpm }, label: "pnpm", value: "pnpm" },
]}
className={clx("my-4", className)}
/>
)
}
export { PackageInstall }
+129
View File
@@ -0,0 +1,129 @@
"use client"
import { InformationCircleSolid } from "@medusajs/icons"
import { Table, Tooltip } from "@medusajs/ui"
import {
EnumType,
FunctionType,
ObjectType,
PropData,
PropDataMap,
} from "@/types/props"
type PropTableProps = {
props: PropDataMap
}
const PropTable = ({ props }: PropTableProps) => {
return (
<Table>
<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">
{/* eslint-disable-next-line react/prop-types */}
{props.map((propData, index) => (
<Row key={index} {...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"
}
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 defaultValueRenderer = (
v: string | number | boolean | null | undefined
) => {
if (v === undefined) {
return "-"
}
if (typeof v === "boolean") {
return v ? "true" : "false"
}
if (v === null) {
return "null"
}
if (typeof v === "string") {
return `"${v}"`
}
return v
}
const isComplexType = isEnum(type) || isObject(type) || isFunction(type)
return (
<Table.Row className="code-body">
<Table.Cell>{prop}</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 dark:text-medusa-fg-subtle-dark" />
</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 dark:text-medusa-fg-subtle-dark" />
</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 dark:text-medusa-fg-subtle-dark" />
</div>
</Tooltip>
)}
</Table.Cell>
<Table.Cell className="text-right">
{defaultValueRenderer(defaultValue)}
</Table.Cell>
</Table.Row>
)
}
export { PropTable }
@@ -0,0 +1,57 @@
"use client"
import { clx } from "@medusajs/ui"
import * as Primitives from "@radix-ui/react-scroll-area"
import clsx from "clsx"
type ScrollbarProps = React.ComponentProps<typeof Primitives.Scrollbar>
const Scrollbar = (props: ScrollbarProps) => {
return (
<Primitives.Scrollbar
className={clsx(
"bg-medusa-bg-base dark:bg-medusa-bg-base-dark flex 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"
)}
{...props}
/>
)
}
type ThumbProps = React.ComponentProps<typeof Primitives.Thumb>
const Thumb = ({ className, ...props }: ThumbProps) => {
return (
<Primitives.Thumb
className={clx(
"bg-medusa-bg-component dark:bg-medusa-bg-component-dark 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
)}
{...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 }
+59
View File
@@ -0,0 +1,59 @@
"use client"
import { clx } from "@medusajs/ui"
import * as Primitives from "@radix-ui/react-tabs"
import * as React from "react"
const Tabs = Primitives.Root
const TabsList = React.forwardRef<
React.ElementRef<typeof Primitives.List>,
React.ComponentPropsWithoutRef<typeof Primitives.List>
// eslint-disable-next-line react/prop-types
>(({ className, ...props }, ref) => (
<Primitives.List
ref={ref}
className={clx(
"inline-flex h-10 w-full items-end justify-start rounded-none bg-transparent p-0",
className
)}
{...props}
/>
))
TabsList.displayName = "TabsList"
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof Primitives.Trigger>,
React.ComponentPropsWithoutRef<typeof Primitives.Trigger>
// eslint-disable-next-line react/prop-types
>(({ className, ...props }, ref) => (
<Primitives.Trigger
ref={ref}
className={clx(
"text-medusa-fg-subtle txt-compact-small-plus rounded-full px-3 py-1.5 transition-all",
"data-[state=active]:shadow-card-rest dark:data-[state=active]:shadow-card-rest-dark data-[state=active]:text-medusa-fg-base",
"dark:text-medusa-fg-subtle-dark dark:data-[state=active]:text-medusa-fg-base-dark",
className
)}
{...props}
/>
))
TabsTrigger.displayName = "TabsTrigger"
const TabsContent = React.forwardRef<
React.ElementRef<typeof Primitives.Content>,
React.ComponentPropsWithoutRef<typeof Primitives.Content>
// eslint-disable-next-line react/prop-types
>(({ className, ...props }, ref) => (
<Primitives.Content
ref={ref}
className={clx(
"w-full rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 data-[state=active]:mt-4",
className
)}
{...props}
/>
))
TabsContent.displayName = "TabsContent"
export { Tabs, TabsContent, TabsList, TabsTrigger }
+204
View File
@@ -0,0 +1,204 @@
export const colors = {
dark: {
"--bg-highlight-hover": "rgba(30, 58, 138, 1)",
"--border-danger": "rgba(190, 18, 60, 1)",
"--border-interactive": "rgba(96, 165, 250, 1)",
"--bg-highlight": "rgba(23, 37, 84, 1)",
"--fg-interactive-hover": "rgba(59, 130, 246, 1)",
"--fg-error": "rgba(251, 113, 133, 1)",
"--bg-interactive": "rgba(96, 165, 250, 1)",
"--border-error": "rgba(244, 63, 94, 1)",
"--button-danger": "rgba(159, 18, 57, 1)",
"--button-danger-gradient-from": "rgba(255, 255, 255, 1)",
"--button-danger-gradient-to": "rgba(255, 255, 255, 0)",
"--bg-overlay": "rgba(10, 10, 10, 0.7)",
"--fg-interactive": "rgba(96, 165, 250, 1)",
"--fg-on-inverted": "rgba(10, 10, 10, 1)",
"--button-danger-pressed": "rgba(159, 18, 57, 1)",
"--button-danger-pressed-gradient-from": "rgba(255, 255, 255, 0)",
"--button-danger-pressed-gradient-to": "rgba(255, 255, 255, 1)",
"--button-neutral-pressed": "rgba(38, 38, 38, 1)",
"--button-neutral-pressed-gradient-from": "rgba(255, 255, 255, 0)",
"--button-neutral-pressed-gradient-to": "rgba(255, 255, 255, 1)",
"--tag-red-border": "rgba(136, 19, 55, 1)",
"--tag-red-bg": "rgba(76, 5, 25, 1)",
"--button-transparent-hover": "rgba(38, 38, 38, 1)",
"--tag-blue-text": "rgba(96, 165, 250, 1)",
"--button-inverted": "rgba(238, 238, 238, 1)",
"--button-inverted-gradient-from": "rgba(0, 0, 0, 0)",
"--button-inverted-gradient-to": "rgba(0, 0, 0, 1)",
"--tag-orange-text": "rgba(251, 191, 36, 1)",
"--tag-green-text": "rgba(52, 211, 153, 1)",
"--button-transparent": "rgba(0, 0, 0, 0)",
"--button-transparent-pressed": "rgba(48, 48, 48, 1)",
"--tag-neutral-bg": "rgba(48, 48, 48, 1)",
"--code-text-subtle": "rgba(110, 110, 110, 1)",
"--code-bg-base": "rgba(30, 30, 30, 1)",
"--tag-orange-border": "rgba(120, 53, 15, 1)",
"--border-base": "rgba(48, 48, 48, 1)",
"--bg-component": "rgba(38, 38, 38, 1)",
"--code-text-base": "rgba(238, 238, 238, 1)",
"--tag-green-border": "rgba(6, 78, 59, 1)",
"--bg-base": "rgba(23, 23, 23, 1)",
"--fg-on-color": "rgba(238, 238, 238, 1)",
"--tag-red-text": "rgba(251, 113, 133, 1)",
"--tag-green-bg-hover": "rgba(6, 78, 59, 1)",
"--tag-purple-bg-hover": "rgba(76, 29, 149, 1)",
"--tag-red-bg-hover": "rgba(136, 19, 55, 1)",
"--border-strong": "rgba(55, 55, 55, 1)",
"--fg-subtle": "rgba(163, 163, 163, 1)",
"--fg-base": "rgba(238, 238, 238, 1)",
"--code-border": "rgba(48, 48, 48, 1)",
"--border-transparent": "rgba(238, 238, 238, 0)",
"--tag-orange-icon": "rgba(245, 158, 11, 1)",
"--tag-purple-bg": "rgba(46, 16, 100, 1)",
"--bg-base-hover": "rgba(38, 38, 38, 1)",
"--bg-subtle-hover": "rgba(23, 23, 23, 1)",
"--tag-blue-bg": "rgba(23, 37, 84, 1)",
"--fg-disabled": "rgba(63, 63, 63, 1)",
"--bg-subtle": "rgba(10, 10, 10, 1)",
"--tag-green-bg": "rgba(2, 44, 34, 1)",
"--bg-subtle-pressed": "rgba(38, 38, 38, 1)",
"--tag-blue-border": "rgba(30, 58, 138, 1)",
"--fg-muted": "rgba(110, 110, 110, 1)",
"--tag-purple-border": "rgba(76, 29, 149, 1)",
"--tag-blue-bg-hover": "rgba(30, 42, 138, 1)",
"--code-bg-header": "rgba(23, 23, 23, 1)",
"--border-loud": "rgba(238, 238, 238, 1)",
"--bg-base-pressed": "rgba(48, 48, 48, 1)",
"--tag-orange-bg": "rgba(69, 26, 3, 1)",
"--bg-disabled": "rgba(38, 38, 38, 1)",
"--tag-orange-bg-hover": "rgba(120, 53, 15, 1)",
"--code-icon": "rgba(74, 74, 74, 1)",
"--bg-field-hover": "rgba(48, 48, 48, 1)",
"--bg-field": "rgba(38, 38, 38, 1)",
"--button-neutral": "rgba(38, 38, 38, 1)",
"--button-neutral-gradient-from": "rgba(255, 255, 255, 1)",
"--button-neutral-gradient-to": "rgba(255, 255, 255, 0)",
"--button-inverted-pressed": "rgba(238, 238, 238, 1)",
"--button-inverted-pressed-gradient-from": "rgba(0, 0, 0, 1)",
"--button-inverted-pressed-gradient-to": "rgba(0, 0, 0, 0)",
"--button-neutral-hover": "rgba(38, 38, 38, 1)",
"--button-neutral-hover-gradient-from": "rgba(255, 255, 255, 1)",
"--button-neutral-hover-gradient-to": "rgba(255, 255, 255, 0)",
"--button-inverted-hover": "rgba(238, 238, 238, 1)",
"--button-inverted-hover-gradient-from": "rgba(0, 0, 0, 0)",
"--button-inverted-hover-gradient-to": "rgba(0, 0, 0, 1)",
"--button-danger-hover": "rgba(159, 18, 57, 1)",
"--button-danger-hover-gradient-from": "rgba(255, 255, 255, 1)",
"--button-danger-hover-gradient-to": "rgba(255, 255, 255, 0)",
"--bg-switch-off-hover": "rgba(74, 74, 74, 1)",
"--bg-switch-off": "rgba(55, 55, 55, 1)",
"--tag-neutral-bg-hover": "rgba(63, 63, 63, 1)",
"--tag-neutral-border": "rgba(63, 63, 63, 1)",
"--tag-blue-icon": "rgba(59, 130, 246, 1)",
"--tag-red-icon": "rgba(244, 63, 94, 1)",
"--tag-neutral-icon": "rgba(129, 129, 129, 1)",
"--tag-purple-icon": "rgba(139, 92, 246, 1)",
"--tag-purple-text": "rgba(167, 139, 250, 1)",
"--tag-neutral-text": "rgba(163, 163, 163, 1)",
"--tag-green-icon": "rgba(16, 185, 129, 1)",
},
light: {
"--code-text-base": "rgba(249, 250, 251, 1)",
"--tag-green-bg": "rgba(209, 250, 229, 1)",
"--border-strong": "rgba(209, 213, 219, 1)",
"--border-interactive": "rgba(59, 130, 246, 1)",
"--bg-highlight": "rgba(239, 246, 255, 1)",
"--bg-base-hover": "rgba(249, 250, 251, 1)",
"--tag-neutral-bg": "rgba(243, 244, 246, 1)",
"--tag-red-bg": "rgba(255, 228, 230, 1)",
"--tag-orange-bg": "rgba(254, 244, 199, 1)",
"--bg-base": "rgba(255, 255, 255, 1)",
"--border-base": "rgba(229, 231, 235, 1)",
"--tag-green-icon": "rgba(5, 150, 105, 1)",
"--tag-purple-bg-hover": "rgba(221, 214, 254, 1)",
"--tag-blue-border": "rgba(191, 219, 254, 1)",
"--tag-orange-icon": "rgba(217, 119, 6, 1)",
"--tag-purple-bg": "rgba(237, 233, 254, 1)",
"--bg-subtle": "rgba(249, 250, 251, 1)",
"--tag-purple-text": "rgba(109, 40, 217, 1)",
"--tag-blue-bg": "rgba(219, 234, 254, 1)",
"--tag-blue-icon": "rgba(37, 99, 235, 1)",
"--bg-component": "rgba(241, 243, 245, 1)",
"--border-error": "rgba(225, 29, 72, 1)",
"--bg-field-hover": "rgba(243, 244, 246, 1)",
"--bg-field": "rgba(249, 250, 251, 1)",
"--border-loud": "rgba(3, 7, 18, 1)",
"--fg-on-inverted": "rgba(255, 255, 255, 1)",
"--fg-on-color": "rgba(255, 255, 255, 1)",
"--button-danger-pressed": "rgba(225, 29, 72, 1)",
"--button-danger-pressed-gradient-from": "rgba(255, 255, 255, 0)",
"--button-danger-pressed-gradient-to": "rgba(255, 255, 255, 1)",
"--button-transparent": "rgba(255, 255, 255, 0)",
"--bg-interactive": "rgba(59, 130, 246, 1)",
"--fg-interactive-hover": "rgba(37, 99, 235, 1)",
"--fg-interactive": "rgba(59, 130, 246, 1)",
"--fg-error": "rgba(225, 29, 72, 1)",
"--border-danger": "rgba(190, 18, 60, 1)",
"--border-transparent": "rgba(3, 7, 18, 0)",
"--bg-overlay": "rgba(3, 7, 18, 0.4)",
"--fg-base": "rgba(3, 7, 18, 1)",
"--fg-disabled": "rgba(209, 213, 219, 1)",
"--bg-subtle-pressed": "rgba(229, 231, 235, 1)",
"--fg-subtle": "rgba(75, 85, 99, 1)",
"--fg-muted": "rgba(156, 163, 175, 1)",
"--bg-base-pressed": "rgba(243, 244, 246, 1)",
"--bg-subtle-hover": "rgba(243, 244, 246, 1)",
"--tag-neutral-border": "rgba(229, 231, 235, 1)",
"--tag-green-bg-hover": "rgba(167, 243, 208, 1)",
"--tag-blue-bg-hover": "rgba(191, 219, 254, 1)",
"--tag-red-icon": "rgba(225, 29, 72, 1)",
"--tag-neutral-text": "rgba(75, 85, 99, 1)",
"--tag-red-bg-hover": "rgba(254, 205, 211, 1)",
"--tag-red-text": "rgba(190, 18, 60, 1)",
"--tag-purple-icon": "rgba(124, 58, 237, 1)",
"--tag-blue-text": "rgba(29, 78, 216, 1)",
"--tag-orange-bg-hover": "rgba(253, 230, 138, 1)",
"--tag-neutral-bg-hover": "rgba(229, 231, 235, 1)",
"--tag-purple-border": "rgba(221, 214, 254, 1)",
"--tag-orange-text": "rgba(180, 83, 9, 1)",
"--tag-neutral-icon": "rgba(107, 114, 128, 1)",
"--tag-orange-border": "rgba(253, 230, 138, 1)",
"--tag-red-border": "rgba(254, 205, 211, 1)",
"--tag-green-border": "rgba(167, 243, 208, 1)",
"--tag-green-text": "rgba(4, 120, 87, 1)",
"--bg-disabled": "rgba(243, 244, 246, 1)",
"--button-transparent-hover": "rgba(249, 250, 251, 1)",
"--button-transparent-pressed": "rgba(243, 244, 246, 1)",
"--bg-switch-off-hover": "rgba(229, 231, 235, 1)",
"--bg-switch-off-hover-gradient-from": "rgba(3, 7, 18, 0)",
"--bg-switch-off-hover-gradient-to": "rgba(3, 7, 18, 1)",
"--bg-highlight-hover": "rgba(219, 234, 254, 1)",
"--code-bg-base": "rgba(17, 24, 39, 1)",
"--code-bg-header": "rgba(31, 41, 55, 1)",
"--code-text-subtle": "rgba(156, 163, 175, 1)",
"--code-border": "rgba(55, 65, 81, 1)",
"--code-icon": "rgba(107, 114, 128, 1)",
"--button-inverted": "rgba(10, 10, 10, 1)",
"--button-inverted-gradient-from": "rgba(255, 255, 255, 1)",
"--button-inverted-gradient-to": "rgba(255, 255, 255, 0)",
"--button-neutral-pressed": "rgba(255, 255, 255, 1)",
"--button-neutral-pressed-gradient-from": "rgba(10, 10, 10, 1)",
"--button-neutral-pressed-gradient-to": "rgba(10, 10, 10, 0)",
"--button-inverted-pressed": "rgba(10, 10, 10, 1)",
"--button-inverted-pressed-gradient-from": "rgba(255, 255, 255, 0)",
"--button-inverted-pressed-gradient-to": "rgba(255, 255, 255, 1)",
"--button-inverted-hover": "rgba(10, 10, 10, 1)",
"--button-inverted-hover-gradient-from": "rgba(255, 255, 255, 1)",
"--button-inverted-hover-gradient-to": "rgba(255, 255, 255, 0)",
"--button-neutral": "rgba(255, 255, 255, 1)",
"--button-neutral-gradient-from": "rgba(10, 10, 10, 0)",
"--button-neutral-gradient-to": "rgba(10, 10, 10, 1)",
"--button-neutral-hover": "rgba(255, 255, 255, 1)",
"--button-neutral-hover-gradient-from": "rgba(10, 10, 10, 0)",
"--button-neutral-hover-gradient-to": "rgba(10, 10, 10, 1)",
"--button-danger": "rgba(225, 29, 72, 1)",
"--button-danger-gradient-from": "rgba(255, 255, 255, 1)",
"--button-danger-gradient-to": "rgba(255, 255, 255, 0)",
"--button-danger-hover": "rgba(225, 29, 72, 1)",
"--button-danger-hover-gradient-from": "rgba(255, 255, 255, 1)",
"--button-danger-hover-gradient-to": "rgba(255, 255, 255, 0)",
"--bg-switch-off": "rgba(229, 231, 235, 1)",
},
}
+286
View File
@@ -0,0 +1,286 @@
import { NavbarLinkProps, SidebarSectionItemsType } from "docs-ui"
type DocsConfig = {
mainNav: NavbarLinkProps[]
sidebar: SidebarSectionItemsType
}
export const docsConfig: DocsConfig = {
mainNav: [
{
label: "Docs",
target: "_blank",
rel: "noreferrer",
href: `${process.env.NEXT_PUBLIC_DOCS_URL}`,
},
{
label: "User Guide",
target: "_blank",
rel: "noreferrer",
href: `${process.env.NEXT_PUBLIC_DOCS_URL}/user-guide`,
},
{
label: "Store API",
target: "_blank",
rel: "noreferrer",
href: `${process.env.NEXT_PUBLIC_DOCS_URL}/api/store`,
},
{
label: "Admin API",
target: "_blank",
rel: "noreferrer",
href: `${process.env.NEXT_PUBLIC_DOCS_URL}/api/admin`,
},
{
label: "UI",
target: "_blank",
rel: "noreferrer",
href: `${process.env.NEXT_PUBLIC_DOCS_URL}/ui`,
activeValuePattern: new RegExp("/ui"),
},
],
sidebar: {
top: [
{
title: "Getting Started",
children: [
{
title: "Introduction",
path: "/",
loaded: true,
isPathHref: true,
},
],
},
{
title: "Installation",
children: [
{
title: "Medusa Admin Extension",
path: "/installation/medusa-admin-extension",
isPathHref: true,
},
{
title: "Standalone Project",
path: "/installation/standalone-project",
isPathHref: true,
},
],
},
],
bottom: [
{
title: "Colors",
children: [
{
title: "Overview",
path: "/colors/overview",
isPathHref: true,
},
],
},
{
title: "Icons",
children: [
{
title: "Overview",
path: "/icons/overview",
isPathHref: true,
},
],
},
{
title: "Components",
children: [
{
title: "Avatar",
path: "/components/avatar",
isPathHref: true,
},
{
title: "Badge",
path: "/components/badge",
isPathHref: true,
},
{
title: "Button",
path: "/components/button",
isPathHref: true,
},
{
title: "Calendar",
path: "/components/calendar",
isPathHref: true,
},
{
title: "Checkbox",
path: "/components/checkbox",
isPathHref: true,
},
{
title: "Code Block",
path: "/components/code-block",
isPathHref: true,
},
{
title: "Command",
path: "/components/command",
isPathHref: true,
},
{
title: "Container",
path: "/components/container",
isPathHref: true,
},
{
title: "Copy",
path: "/components/copy",
isPathHref: true,
},
{
title: "Date Picker",
path: "/components/date-picker",
isPathHref: true,
},
{
title: "Drawer",
path: "/components/drawer",
isPathHref: true,
},
{
title: "Dropdown Menu",
path: "/components/dropdown-menu",
isPathHref: true,
},
{
title: "Focus Modal",
path: "/components/focus-modal",
isPathHref: true,
},
{
title: "Heading",
path: "/components/heading",
isPathHref: true,
},
{
title: "Input",
path: "/components/input",
isPathHref: true,
},
{
title: "Kbd",
path: "/components/kbd",
isPathHref: true,
},
{
title: "Label",
path: "/components/label",
isPathHref: true,
},
{
title: "Prompt",
path: "/components/prompt",
isPathHref: true,
},
{
title: "Radio Group",
path: "/components/radio-group",
isPathHref: true,
},
{
title: "Select",
path: "/components/select",
isPathHref: true,
},
{
title: "Switch",
path: "/components/switch",
isPathHref: true,
},
{
title: "Table",
path: "/components/table",
isPathHref: true,
},
{
title: "Text",
path: "/components/text",
isPathHref: true,
},
{
title: "Textarea",
path: "/components/textarea",
isPathHref: true,
},
{
title: "Toast",
path: "/components/toast",
isPathHref: true,
},
{
title: "Tooltip",
path: "/components/tooltip",
isPathHref: true,
},
],
},
{
title: "Hooks",
children: [
{
title: "usePrompt",
path: "/hooks/use-prompt",
isPathHref: true,
},
{
title: "useToast",
path: "/hooks/use-toast",
isPathHref: true,
},
{
title: "useToggleState",
path: "/hooks/use-toggle-state",
isPathHref: true,
},
],
},
{
title: "Utils",
children: [
{
title: "clx",
path: "/utils/clx",
isPathHref: true,
},
],
},
],
mobile: [
{
title: "Docs",
path: `${process.env.NEXT_PUBLIC_DOCS_URL}`,
isPathHref: true,
},
{
title: "User Guide",
path: `${process.env.NEXT_PUBLIC_DOCS_URL}/user-guide`,
isPathHref: true,
},
{
title: "Store API",
path: `${process.env.NEXT_PUBLIC_DOCS_URL}/api/store`,
isPathHref: true,
},
{
title: "Admin API",
path: `${process.env.NEXT_PUBLIC_DOCS_URL}/api/admin`,
isPathHref: true,
},
{
title: "UI",
path: `${process.env.NEXT_PUBLIC_DOCS_URL}/ui`,
isPathHref: true,
},
],
},
}
+13
View File
@@ -0,0 +1,13 @@
type SiteConfig = {
name: string
url: string
ogImage: string
description: string
}
export const siteConfig: SiteConfig = {
name: "Medusa UI",
url: `${process.env.NEXT_PUBLIC_DOCS_URL}/ui`,
ogImage: `${process.env.NEXT_PUBLIC_DOCS_URL}/ui/images/medusa-og.png`,
description: "Primitives for building Medusa applications.",
}
@@ -0,0 +1,17 @@
---
title: "Colors"
description: "The color system used in Medusa UI"
---
## Overview
---
These are most of the colors used in Medusa UI and across Medusa Admin. Use the theme
switcher in the navbar to see how the colours react in light/dark themes.
You can click on any of the blocks to copy the Tailwind class name.
<Colors />
<Feedback title={"Colors"} />
@@ -0,0 +1,30 @@
---
title: "Avatar"
description: "An image element with a fallback for representing the user."
component: true
---
<ComponentExample name="avatar-demo" />
## Usage
---
```tsx
import { Avatar } from "@medusajs/ui"
```
```tsx
<Avatar
src="https://avatars.githubusercontent.com/u/10656202?v=4"
fallback="M"
/>
```
## API Reference
---
This component is based on the [Radix UI Avatar](https://www.radix-ui.com/primitives/docs/components/avatar) primitive.
<ComponentProps component="avatar" />
@@ -0,0 +1,71 @@
---
title: "Badge"
description: "Displays a badge"
component: true
---
<ComponentExample name="badge-demo" />
## Usage
---
```tsx
import { Badge } from "@medusajs/ui"
```
```tsx
<Badge>Badge</Badge>
```
## API Reference
---
This component is based on the `div` element and supports all props of this element.
<ComponentProps component="badge" />
## Examples
---
### Grey
<ComponentExample name="badge-grey" />
### Red
<ComponentExample name="badge-red" />
### Green
<ComponentExample name="badge-green" />
### Blue
<ComponentExample name="badge-blue" />
### Orange
<ComponentExample name="badge-orange" />
### Purple
<ComponentExample name="badge-purple" />
### Small
<ComponentExample name="badge-small" />
### Large
<ComponentExample name="badge-large" />
### Rounded
<ComponentExample name="badge-rounded" />
### Icon
<ComponentExample name="badge-icon" />
@@ -0,0 +1,63 @@
---
title: "Button"
description: "Displays a button component"
component: true
---
<ComponentExample name="button-demo" />
## Usage
---
```tsx
import { Button } from "@medusajs/ui"
```
```tsx
<Button>Button</Button>
```
## API Reference
---
This component is based on the `button` element and supports all props of this element.
<ComponentProps component="button" />
## Examples
---
### Primary
<ComponentExample name="button-primary" />
### Secondary
<ComponentExample name="button-secondary" />
### Transparent
<ComponentExample name="button-transparent" />
### Danger
<ComponentExample name="button-danger" />
### Disabled
<ComponentExample name="button-disabled" />
### With Icon
<ComponentExample name="button-with-icon" />
### Icon Only
<ComponentExample name="button-icon-only" />
### Loading
<ComponentExample name="button-loading" />
@@ -0,0 +1,39 @@
---
title: "Calendar"
description: "A calendar component that allows picking of a single date or a range of dates."
component: true
---
<ComponentExample name="calendar-demo" />
## Usage
---
```tsx
import { Calendar } from "@medusajs/ui"
```
```tsx
<Calendar />
```
## API Reference
---
This component is based on the [react-date-picker](https://www.npmjs.com/package/react-date-picker) package.
<ComponentProps component="calendar" />
## Examples
---
### Single Date
<ComponentExample name="calendar-single" />
### Range
<ComponentExample name="calendar-range" />
@@ -0,0 +1,45 @@
---
title: "Checkbox"
description: "A control that allows the user to toggle between checked and not checked."
component: true
---
<ComponentExample name="checkbox-demo" />
## Usage
---
```tsx
import { Checkbox } from "@medusajs/ui"
```
```tsx
<Checkbox />
```
## API Reference
---
This component is based on the [Radix UI Checkbox](https://www.radix-ui.com/primitives/docs/components/checkbox) primitive.
## Examples
---
### Default
<ComponentExample name="checkbox-default" />
### Checked
<ComponentExample name="checkbox-checked" />
### Disabled
<ComponentExample name="checkbox-disabled" />
### Indeterminate
<ComponentExample name="checkbox-indeterminate" />
@@ -0,0 +1,77 @@
---
title: "Code Block"
description: "Allows you to render highlighted code snippets"
component: true
---
<ComponentExample name="code-block-demo" />
## Usage
---
```tsx
import { CodeBlock } from "@medusajs/ui"
```
```tsx
<CodeBlock
snippets={[
{
language: "tsx",
label: "Label",
code: "import { useProduct } from \"medusa-react\"",
},
]}
>
<CodeBlock.Header />
<CodeBlock.Body />
</CodeBlock>
```
## API Reference
---
### CodeBlock
This component is based on the `div` element and supports all props of this element.
<ComponentProps component="code-block" />
### CodeBlock.Header
This component is based on the `div` element and supports all props of this element.
<ComponentProps component="code-block-header" />
### CodeBlock.Header.Meta
This component is based on the `div` element and supports all props of this element.
### CodeBlock.Body
This component is based on the `div` element and supports all props of this element.
<ComponentProps component="code-block-body" />
## Examples
---
### Single snippet
If you want to only show a code sample for one language or API, you can choose to hide the snippet labels:
<ComponentExample name="code-block-single" />
### No Header
You could also choose to omit the header entirely:
<ComponentExample name="code-block-no-header" />
### No Line Numbers
<ComponentExample name="code-block-no-lines" />
@@ -0,0 +1,27 @@
---
title: "Command"
description: "Renders an unhighlighted code block, useful for one-liners or endpoints"
component: true
---
<ComponentExample name="command-demo" />
## Usage
---
```tsx
import { Command } from "@medusajs/ui"
```
```tsx
<Command>
<code>yarn add @medusajs/ui</code>
</Command>
```
## API Reference
---
This component is based on the `div` element and supports all props of this element.
@@ -0,0 +1,33 @@
---
title: "Container"
description: "A box used to organise content."
component: true
---
<ComponentExample name="container-demo" />
## Usage
---
```tsx
import { Container } from "@medusajs/ui"
```
```tsx
<Container>Container</Container>
```
## API Reference
---
This component is based on the `div` element and supports all props of this element.
## Examples
---
### In a layout
<ComponentExample name="container-layout" />
@@ -0,0 +1,41 @@
---
title: "Copy"
description: "Displays a tooltipped button that puts string contents into the user's clipboard."
component: true
---
<ComponentExample name="copy-demo" />
## Usage
---
```tsx
import { Copy } from "@medusajs/ui"
```
```tsx
<Copy content="yarn add @medusajs/ui" />
```
## API Reference
---
This component is based on the `button` element and supports all props of this element.
<ComponentProps component="copy" />
## Examples
---
### With custom display
<ComponentExample name="copy-custom-display" />
### As child
Using the `asChild` prop, you can render the `<Copy/>` as it's child. This is useful if you want to render a custom button, to prevent rendering a button inside a button.
<ComponentExample name="copy-as-child" />
@@ -0,0 +1,64 @@
---
title: "Date Picker"
description: "A date picker component with range and presets."
component: true
---
<ComponentExample name="date-picker-demo" />
## Usage
---
```tsx
import { DatePicker } from "@medusajs/ui"
```
```tsx
<DatePicker />
```
## API Reference
---
This component is based on the [Calendar](/components/calendar) component and [Radix UI Popover](https://www.radix-ui.com/primitives/docs/components/popover).
<ComponentProps component="date-picker" />
## Examples
---
### Single
<ComponentExample name="date-picker-single" />
### Single with time
<ComponentExample name="date-picker-single-time" />
### Single with presets
<ComponentExample name="date-picker-single-presets" />
### Single with presets and time
<ComponentExample name="date-picker-single-presets-time" />
### Range
<ComponentExample name="date-picker-range" />
### Range with time
<ComponentExample name="date-picker-range-time" />
### Range with presets
<ComponentExample name="date-picker-range-presets" />
### Range with presets and time
<ComponentExample name="date-picker-range-presets-time" />
@@ -0,0 +1,34 @@
---
title: "Drawer"
description: "A triggerable drawer that overlaps whatever page it's on."
component: true
---
<ComponentExample name="drawer-demo" />
## Usage
---
```tsx
import { Drawer } from "@medusajs/ui"
```
```tsx
<Drawer>
<Drawer.Trigger>Trigger</Drawer.Trigger>
<Drawer.Content>
<Drawer.Header>
<Drawer.Title>Drawer Title</Drawer.Title>
</Drawer.Header>
<Drawer.Body>Body</Drawer.Body>
<Drawer.Footer>Footer</Drawer.Footer>
</Drawer.Content>
</Drawer>
```
## API Reference
---
This component is based on the [Radix UI Dialog](https://www.radix-ui.com/primitives/docs/components/dialog) primitives.
@@ -0,0 +1,50 @@
---
title: "Dropdown Menu"
description: "Displays a menu to the user—such as a set of actions or functions—triggered by a button."
component: true
---
<ComponentExample name="dropdown-menu-demo" />
## Usage
---
```tsx
import { DropdownMenu } from "@medusajs/ui"
```
```tsx
<DropdownMenu>
<DropdownMenu.Trigger>Trigger</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Item>Edit</DropdownMenu.Item>
<DropdownMenu.Item>Add</DropdownMenu.Item>
<DropdownMenu.Item>Delete</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu>
```
## API Reference
---
This component is based on the [Radix UI Dropdown Menu](https://www.radix-ui.com/primitives/docs/components/dropdown-menu) primitive.
### DropdownMenu.Shortcut
This component is based on the `span` element and supports all props of this element.
### DropdownMenu.Hint
This component is based on the `span` element and supports all props of this element.
## Examples
---
### Sorting
Implementing collection sorting choices using a Dropdown Menu:
<ComponentExample name="dropdown-menu-sorting" />
@@ -0,0 +1,39 @@
---
title: "Focus Modal"
description: "A modal component that spans the whole screen"
component: true
---
<ComponentExample name="focus-modal-demo" />
## Usage
---
```tsx
import { FocusModal } from "@medusajs/ui"
```
```tsx
<FocusModal>
<FocusModal.Trigger>Trigger</FocusModal.Trigger>
<FocusModal.Content>
<FocusModal.Header>Title</FocusModal.Header>
<FocusModal.Body>Content</FocusModal.Body>
</FocusModal.Content>
</FocusModal>
```
### API Reference
---
This component is based on the [Radix UI Dialog](https://www.radix-ui.com/primitives/docs/components/dialog) primitives.
### FocusModal.Header
This component is based on the `div` element and supports all props of this element.
### FocusModal.Body
This component is based on the `div` element and supports all props of this element.
@@ -0,0 +1,27 @@
---
title: "Heading"
description: "Text component used for page titles and other headers"
component: true
---
<ComponentExample name="heading-demo" />
## Usage
---
```tsx
import { Heading } from "@medusajs/ui"
```
```tsx
<Heading>A Title</Heading>
```
## API Reference
---
This component is based on the `heading` element and supports all props of this element.
<ComponentProps component="heading" />
@@ -0,0 +1,53 @@
---
title: "Input"
description: "Renders a form input field"
component: true
---
<ComponentExample name="input-demo" />
## Usage
---
```tsx
import { Input } from "@medusajs/ui"
```
```tsx
<Input placeholder="Placeholder" id="input-id" />
```
## API Reference
---
This component is based on the `input` element and supports all props of this element.
<ComponentProps component="input" />
## Examples
---
### Password
<ComponentExample name="input-password" />
### Search
<ComponentExample name="input-search" />
### Disabled
<ComponentExample name="input-disabled" />
### Small
<ComponentExample name="input-small" />
### Error state
You can leverage the native `aria-invalid` property to show an error state on your input:
<ComponentExample name="input-error" />
@@ -0,0 +1,26 @@
---
title: "Kbd"
description: "Renders a badge-styled kbd element"
component: true
---
<ComponentExample name="kbd-demo" />
## Usage
---
```tsx
import { Kbd } from "@medusajs/ui"
```
```tsx
<Kbd>Ctrl + Shift + A</Kbd>
```
## API Reference
---
This component is based on the `kbd` element and supports all props of this element.
@@ -0,0 +1,64 @@
---
title: "Label"
description: "Renders an accessible label associated with controls."
component: true
---
<ComponentExample name="label-demo" />
## Usage
---
```tsx
import { Label } from "@medusajs/ui"
```
```tsx
<Label>Label</Label>
```
## API Reference
---
This component is based on the [Radix UI Label](https://www.radix-ui.com/primitives/docs/components/label) primitive.
<ComponentProps component="label" />
## Examples
---
### Base Regular
<ComponentExample name="label-base-regular" />
### Base Plus
<ComponentExample name="label-base-plus" />
### Large Regular
<ComponentExample name="label-large-regular" />
### Large Plus
<ComponentExample name="label-large-plus" />
### Small Regular
<ComponentExample name="label-small-regular" />
### Small Plus
<ComponentExample name="label-small-plus" />
### X-Small Regular
<ComponentExample name="label-xsmall-regular" />
### X-Small Plus
<ComponentExample name="label-xsmall-plus" />
@@ -0,0 +1,45 @@
---
title: "Prompt"
description: "Displays a dialog triggered by a button"
component: true
---
<ComponentExample name="prompt-demo" />
## Usage
---
```tsx
import { Prompt } from "@medusajs/ui"
```
```tsx
<Prompt>
<Prompt.Trigger>Trigger</Prompt.Trigger>
<Prompt.Content>
<Prompt.Header>
<Prompt.Title>Title</Prompt.Title>
<Prompt.Description>Description</Prompt.Description>
</Prompt.Header>
<Prompt.Footer>
<Prompt.Cancel>Cancel</Prompt.Cancel>
<Prompt.Action>Delete</Prompt.Action>
</Prompt.Footer>
</Prompt.Content>
</Prompt>
```
## API Reference
---
This component is based on the [Radix UI Alert Dialog](https://www.radix-ui.com/primitives/docs/components/alert-dialog) primitives.
### Prompt.Header
This component is based on the `div` element and supports all props of this element.
### Prompt.Footer
This component is based on the `div` element and supports all props of this element.
@@ -0,0 +1,41 @@
---
title: "Radio Group"
description: "A set of checkable buttons—known as radio buttons—where no more than one of the buttons can be checked at a time."
component: true
---
<ComponentExample name="radio-group-demo" />
## Usage
---
```tsx
import { RadioGroup } from "@medusajs/ui"
```
```tsx
<RadioGroup>
<RadioGroup.Item value="1" id="radio_1" />
<RadioGroup.Item value="2" id="radio_2" />
<RadioGroup.Item value="3" id="radio_3" />
</RadioGroup>
```
## API Reference
---
This component is based on the [Radix UI Radio Group](https://www.radix-ui.com/primitives/docs/components/radio-group) primitives.
## Examples
---
### With Descriptions
<ComponentExample name="radio-group-descriptions" />
### With a disabled item
<ComponentExample name="radio-group-disabled" />
@@ -0,0 +1,86 @@
---
title: "Select"
description: "Displays a list of options for the user to pick from—triggered by a button."
component: true
---
<ComponentExample name="select-demo" />
## Usage
---
```tsx
import { Select } from "@medusajs/ui"
```
```tsx
<Select>
<Select.Trigger>
<Select.Value placeholder="Placeholder" />
</Select.Trigger>
<Select.Content>
{items.map((item) => (
<Select.Item key={item.value} value={item.value}>
{item.label}
</Select.Item>
))}
</Select.Content>
</Select>
```
## API Reference
---
This component is based on [Radix UI Select](https://www.radix-ui.com/primitives/docs/components/dropdown-menu).
### Select
Contains all the parts of the select component.
<ComponentProps component="select" />
### Select.Trigger
The trigger that toggles the select.
### Select.Value
Displays the selected value, or a placeholder if no value is selected.
<ComponentProps component="select-value" />
### Select.Group
Groups multiple items together.
### Select.Label
Used to label a group of items.
### Select.Item
An item in the select.
<ComponentProps component="select-item" />
## Examples
---
### Small
<ComponentExample name="select-small" />
### Disabled
<ComponentExample name="select-disabled" />
### Grouped Items
<ComponentExample name="select-grouped-items" />
### Controlled
<ComponentExample name="select-controlled" />
@@ -0,0 +1,47 @@
---
title: "Switch"
description: "A control that allows the user to toggle between checked and not checked."
component: true
---
<ComponentExample name="switch-demo" />
## Usage
---
```tsx
import { Switch } from "@medusajs/ui"
```
```tsx
<Switch />
```
## API Reference
---
This component is based on the [Radix UI Switch](https://www.radix-ui.com/primitives/docs/components/switch) primitive.
<ComponentProps component="switch" />
## Examples
---
### Small
<ComponentExample name="switch-small" />
### Disabled
<ComponentExample name="switch-disabled" />
### Checked
<ComponentExample name="switch-checked" />
### Checked Disabled
<ComponentExample name="switch-checked-disabled" />
@@ -0,0 +1,52 @@
---
title: "Table"
description: "A Table component for displaying data."
---
<ComponentExample name="table-demo" />
## Usage
---
```tsx
import { Table } from "@medusajs/ui"
```
```tsx
<Table>
<Table.Header>
<Table.Row>
<Table.HeaderCell>#</Table.HeaderCell>
<Table.HeaderCell>Customer</Table.HeaderCell>
<Table.HeaderCell>Email</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
<Table.Row>
<Table.Cell>1</Table.Cell>
<Table.Cell>Emil Larsson</Table.Cell>
<Table.Cell>emil2738@gmail.com</Table.Cell>
</Table.Row>
</Table.Body>
</Table>
```
## API Reference
---
This component is based on the `table` element and its various children, and supports all props of those elements.
- `Table`: `table`
- `Table.Header`: `thead`
- `Table.Row`: `tr`
- `Table.HeaderCell`: `th`
- `Table.Body`: `tbody`
- `Table.Cell`: `td`
### Pagination
This component is based on the `div` element and supports all props of this element.
<ComponentProps component="table-pagination" />
@@ -0,0 +1,36 @@
---
title: "Text"
description: "Displays basic text."
component: true
---
<ComponentExample name="text-demo" />
## Usage
---
```tsx
import { Text } from "@medusajs/ui"
```
```tsx
<Text>Text</Text>
```
## API Reference
---
This component is based on the `p` element and supports all props of this element.
<ComponentProps component="text" />
## Examples
---
### All variations
<ComponentExample name="text-examples" />
@@ -0,0 +1,25 @@
---
title: "Textarea"
description: "Displays a form textarea."
component: true
---
<ComponentExample name="textarea-demo" />
## Usage
---
```tsx
import { Textarea } from "@medusajs/ui"
```
```tsx
<Textarea />
```
## API Reference
---
This component is based on the `textarea` element and supports all props of this element.
@@ -0,0 +1,73 @@
---
title: "Toast"
description: "A succinct message that is displayed temporarily."
component: true
---
<ComponentExample name="toaster-demo" />
## Usage
---
Add the Toaster component somewhere in your tree
```tsx
import { Toaster } from "@medusajs/ui"
```
```tsx
<Toaster />
```
The `useToast` hook returns a `toast` function that you can use to display a toast.
```tsx
import { useToast } from "@medusajs/ui"
```
```tsx
const { toast } = useToast()
return (
<Button
onClick={() =>
toast({
title: "Toast title",
description: "Toast body",
})
}
>
Trigger
</Button>
)
```
## API Reference
This component is based on the [Radix UI Toast](https://www.radix-ui.com/primitives/docs/components/toast) primitives.
## Examples
---
### Warning
<ComponentExample name="toaster-warning" />
### Error
<ComponentExample name="toaster-error" />
### Success
<ComponentExample name="toaster-success" />
### Loading
<ComponentExample name="toaster-loading" />
### With Action
<ComponentExample name="toaster-with-action" />
@@ -0,0 +1,27 @@
---
title: "Tooltip"
description: "A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it."
component: true
---
<ComponentExample name="tooltip-demo" />
## Usage
```tsx
import { Tooltip } from "@medusajs/ui"
```
```tsx
<Tooltip content="Tooltip content">Trigger</Tooltip>
```
## API Reference
---
This component is based on the [Radix UI Tooltip](https://www.radix-ui.com/primitives/docs/components/tooltip) primitive.
<ComponentProps component="tooltip" />
@@ -0,0 +1,53 @@
---
title: "usePrompt"
description: ""
component: true
---
This hook can be used to prompt the user for confirmation of an action.
## Usage
---
```tsx
import { usePrompt } from "@medusajs/ui"
```
```tsx
const dialog = usePrompt()
const actionFunction = async () => {
const confirmed = await dialog({
title: "Are you sure?",
description: "Please confirm this action",
})
}
```
## API Reference
---
### Call Signature
`usePrompt()`
### usePrompt return
<HookValues hook="usePrompt" />
### PromptProps
<HookValues hook="PromptProps" />
## Examples
---
### Basic
<ComponentExample name="use-prompt-demo" />
### With Verification
<ComponentExample name="use-prompt-verification" />
@@ -0,0 +1,30 @@
---
title: "useToast"
description: ""
component: true
---
This hook is used to display and manage toasts. To learn how to make it usable and to see some examples, please check the [Toast component page](/components/toast).
```tsx
import { useToast } from "@medusajs/ui"
```
## API Reference
---
### Call Signature
`useToast()`
### useToast return
<HookValues hook="useToast" />
### ToasterToast
Important type used when pushing or retrieving toasts. This extends the [Radix UI Toast Root](https://www.radix-ui.com/primitives/docs/components/toast#root) primitive type.
<HookValues hook="ToasterToast" />
@@ -0,0 +1,39 @@
---
title: "useToggleState"
description: ""
component: true
---
This is a simple hook that can be used to keep track of a boolean value and toggle between its two states.
It can be useful to, for example, display or hide the same `FocusModal` component
via multiple triggers, for example, in a table.
## Usage
```tsx
import { useToggleState } from "@medusajs/ui"
```
```tsx
const [state, open, close, toggle] = useToggleState()
```
## API Reference
---
### Call Signature
`useToggleState(initial: boolean = false)`
### Returns
<HookValues hook="useToggleState" />
## Examples
---
<ComponentExample name="use-toggle-state-demo" />
@@ -0,0 +1,27 @@
---
title: "Icons"
description: "React components for Medusa UI icons."
---
## Overview
---
Below is a list of all the icons available in the Medusa UI design system.
<IconSearch />
## Installation
---
The icons are available as an [npm package](https://www.npmjs.com/package/@medusajs/icons). To install the package run:
<PackageInstall packageName="@medusajs/icons" />
## Usage
---
```tsx
import { Sun, Moon } from "@medusajs/icons"
```
<Feedback title={"Icons"} />
+44
View File
@@ -0,0 +1,44 @@
---
title: "Introduction"
description: "Primitives for building Medusa applications."
---
Welcome to Medusa UI, a React implementation of the Medusa design system.
It is a collection of components, hooks, utility functions, icons, and [Tailwind CSS](https://tailwindcss.com/) classes that can be used to build
a consistent user interface across the Medusa Admin and other Medusa applications.
<Card
title="Medusa UI"
text="Colors, type, icons and components"
href="https://www.figma.com/community/file/1278648465968635936/Medusa-UI"
icon={
<FigmaIcon />
}
/>
## Packages
---
Medusa UI is split into multiple packages. Each package is published to npm
and can be installed separately.
- `@medusajs/ui` - React components, hooks, and utility functions used
in Medusa UI.
- `@medusajs/ui-preset` - Tailwind CSS preset containing all the classes
used in Medusa UI.
- `@medusajs/icons` - Icons used in Medusa UI.
## About
---
At its core, Medusa UI is a styled and slightly opinionated implementation of [Radix Primitives](https://www.radix-ui.com/primitives).
Our team have also referenced the fantastic [shadcn/ui](https://ui.shadcn.com/) for inspiration in certain implementations.
Our team strongly believe in keeping the components simple and
composable, much like Medusa's foundation. This allows you to build whatever you need. Our team have tried to avoid overloading
the component API and, instead, leveraged the native HTML API, which gets implemented
and respected accordingly, and passed to the underlying elements.
<Feedback title={"Introduction"} />
@@ -0,0 +1,7 @@
---
title: "Installation"
description: "How to install and setup Medusa UI."
---
@@ -0,0 +1,20 @@
---
title: "Medusa Admin Extension"
description: "How to install and use Medusa UI for building Admin extensions."
---
## Installation
---
The `@medusajs/ui` package is a already installed as a dependency of the `@medusajs/admin` package. Due to this you can simply import the package and use it in your local Admin extensions.
If you are building a Admin extension as part of a Medusa plugin, you can install the package as a dependency of your plugin.
<PackageInstall packageName="@medusajs/ui" />
## Configuration
---
The configuration of the UI package is handled by the `@medusajs/admin` package. Therefore, you do not need to any additional configuration to use the UI package in your Admin extensions.
<Feedback title={"Install in Medusa Admin Extension"} />
@@ -0,0 +1,81 @@
---
title: "Standalone Project"
description: "How to install and use Medusa UI in a standalone project."
---
## Installation
---
Medusa UI is a React UI library and while it's intended for usage within Medusa projects, it can also be used in any React project.
### Install Medusa UI
Install the React UI library with the following command:
<PackageInstall packageName="@medusajs/ui" />
### Configuring Tailwind CSS
The components are styled using Tailwind CSS, and in order to use them, you will need to install Tailwind CSS in your project as well.
For more information on how to install Tailwind CSS, please refer to the [Tailwind CSS documentation](https://tailwindcss.com/docs/installation).
All of the classes used for Medusa UI are shipped as a Tailwind CSS customization.
You can install it with the following command:
<PackageInstall packageName="@medusajs/ui-preset" devDependency />
After you have installed Tailwind CSS and the Medusa UI preset, you need to add the following to your `tailwind.config.js`file:
```tsx
module.exports = {
presets: [require("@medusajs/ui-preset")],
// ...
}
```
In order for the styles to be applied correctly to the components, you will also need to ensure that
`@medusajs/ui` is included in the content field of your `tailwind.config.js` file:
```tsx
module.exports = {
content: [
// ...
"./node_modules/@medusajs/ui/dist/**/*.{js,jsx,ts,tsx}",
],
// ...
}
```
If you are working within a monorepo, you may need to add the path to the `@medusajs/ui` package in your `tailwind.config.js` like so:
```tsx
const path = require("path")
const uiPath = path.resolve(
require.resolve("@medusajs/ui"),
"../..",
"\*_/_.{js,jsx,ts,tsx}"
)
module.exports = {
content: [
// ...
uiPath,
],
// ...
}
```
## Start building
---
You are now ready to start building your application with Medusa UI. You can import the components like so:
```tsx
import { Button, Drawer } from "@medusajs/ui"
```
<Feedback title={"Install in Standalone Project"} />
@@ -0,0 +1,42 @@
---
title: "clx"
description: "Utility function for working with classNames."
---
## Usage
---
The `clx` function is a utility function for working with classNames. It is built using [clsx](https://www.npmjs.com/package/clsx) and [tw-merge](https://www.npmjs.com/package/tw-merge) and is intended to be used with [Tailwind CSS](https://tailwindcss.com/).
```tsx
import { clx } from "@medusajs/ui"
type BoxProps = {
className?: string
children: React.ReactNode
mt?: "sm" | "md" | "lg"
}
const Box = ({ className, children }: BoxProps) => {
return (
<div
className={clx(
"flex items-center justify-center",
{
"mt-4": mt === "sm",
"mt-8": mt === "md",
"mt-12": mt === "lg",
},
className
)}
>
{children}
</div>
)
}
```
In the above example the utility is used to apply a base style, a margin top that is dependent on the `mt` prop and a custom className.
The Box component accepts a `className` prop that is merged with the other classNames, and the underlying usage of `tw-merge` ensures that all Tailwind CSS classes are merged without style conflicts.
<Feedback title={"clx"} />
+10
View File
@@ -0,0 +1,10 @@
import { Avatar } from "@medusajs/ui"
export default function AvatarDemo() {
return (
<Avatar
src="https://avatars.githubusercontent.com/u/10656202?v=4"
fallback="M"
/>
)
}
+5
View File
@@ -0,0 +1,5 @@
import { Badge } from "@medusajs/ui"
export default function BadgeBlue() {
return <Badge color="blue">Badge</Badge>
}
+5
View File
@@ -0,0 +1,5 @@
import { Badge } from "@medusajs/ui"
export default function BadgeDemo() {
return <Badge>Badge</Badge>
}
+5
View File
@@ -0,0 +1,5 @@
import { Badge } from "@medusajs/ui"
export default function BadgeGreen() {
return <Badge color="green">Badge</Badge>
}
+5
View File
@@ -0,0 +1,5 @@
import { Badge } from "@medusajs/ui"
export default function BadgeGrey() {
return <Badge color="grey">Badge</Badge>
}
+10
View File
@@ -0,0 +1,10 @@
import { BuildingTax } from "@medusajs/icons"
import { Badge } from "@medusajs/ui"
export default function BadgeIcon() {
return (
<Badge type="icon">
<BuildingTax />
</Badge>
)
}
+5
View File
@@ -0,0 +1,5 @@
import { Badge } from "@medusajs/ui"
export default function BadgeLarge() {
return <Badge size="large">Badge</Badge>
}
@@ -0,0 +1,5 @@
import { Badge } from "@medusajs/ui"
export default function BadgeOrange() {
return <Badge color="orange">Badge</Badge>
}
@@ -0,0 +1,5 @@
import { Badge } from "@medusajs/ui"
export default function BadgePurple() {
return <Badge color="purple">Badge</Badge>
}
+5
View File
@@ -0,0 +1,5 @@
import { Badge } from "@medusajs/ui"
export default function BadgeRed() {
return <Badge color="red">Badge</Badge>
}
@@ -0,0 +1,5 @@
import { Badge } from "@medusajs/ui"
export default function BadgeRounded() {
return <Badge type="rounded">Badge</Badge>
}
+5
View File
@@ -0,0 +1,5 @@
import { Badge } from "@medusajs/ui"
export default function BadgeSmall() {
return <Badge size="small">Badge</Badge>
}
@@ -0,0 +1,5 @@
import { Button } from "@medusajs/ui"
export default function ButtonDanger() {
return <Button variant="danger">Button</Button>
}
+5
View File
@@ -0,0 +1,5 @@
import { Button } from "@medusajs/ui"
export default function ButtonDemo() {
return <Button>Button</Button>
}
@@ -0,0 +1,5 @@
import { Button } from "@medusajs/ui"
export default function ButtonDisabled() {
return <Button disabled={true}>Button</Button>
}
@@ -0,0 +1,10 @@
import { PlusMini } from "@medusajs/icons"
import { Button } from "@medusajs/ui"
export default function ButtonIconOnly() {
return (
<Button format="icon">
<PlusMini />
</Button>
)
}
@@ -0,0 +1,5 @@
import { Button } from "@medusajs/ui"
export default function ButtonLoading() {
return <Button isLoading={true}>Button</Button>
}
@@ -0,0 +1,5 @@
import { Button } from "@medusajs/ui"
export default function ButtonPrimary() {
return <Button>Button</Button>
}
@@ -0,0 +1,5 @@
import { Button } from "@medusajs/ui"
export default function ButtonSecondary() {
return <Button variant="secondary">Button</Button>
}
@@ -0,0 +1,5 @@
import { Button } from "@medusajs/ui"
export default function ButtonTransparent() {
return <Button variant="transparent">Button</Button>
}
@@ -0,0 +1,10 @@
import { PlusMini } from "@medusajs/icons"
import { Button } from "@medusajs/ui"
export default function ButtonWithIcon() {
return (
<Button>
Button <PlusMini />
</Button>
)
}
@@ -0,0 +1,8 @@
import { Calendar } from "@medusajs/ui"
import * as React from "react"
export default function CalendarDemo() {
const [date, setDate] = React.useState<Date | undefined>()
return <Calendar selected={date} onSelect={setDate} />
}
@@ -0,0 +1,13 @@
import { Calendar } from "@medusajs/ui"
import * as React from "react"
import type { DateRange } from "react-day-picker"
export default function CalendarRange() {
const [dates, setDates] = React.useState<DateRange | undefined>()
const setDateRange = (range: DateRange | undefined) => {
setDates(range)
}
return <Calendar selected={dates} onSelect={setDateRange} mode="range" />
}
@@ -0,0 +1,8 @@
import { Calendar } from "@medusajs/ui"
import * as React from "react"
export default function CalendarSingle() {
const [date, setDate] = React.useState<Date | undefined>()
return <Calendar selected={date} onSelect={setDate} />
}
@@ -0,0 +1,12 @@
import { Checkbox, Label } from "@medusajs/ui"
export default function CheckboxChecked() {
return (
<div className="flex items-center space-x-2">
<Checkbox id="billing-shipping-checked" checked={true} />
<Label htmlFor="billing-shipping-checked">
Billing address same as shipping address
</Label>
</div>
)
}
@@ -0,0 +1,12 @@
import { Checkbox, Label } from "@medusajs/ui"
export default function CheckboxDefault() {
return (
<div className="flex items-center space-x-2">
<Checkbox id="billing-shipping-default" />
<Label htmlFor="billing-shipping-default">
Billing address same as shipping address
</Label>
</div>
)
}
@@ -0,0 +1,12 @@
import { Checkbox, Label } from "@medusajs/ui"
export default function CheckboxDemo() {
return (
<div className="flex items-center space-x-2">
<Checkbox id="billing-shipping" />
<Label htmlFor="billing-shipping">
Billing address same as shipping address
</Label>
</div>
)
}
@@ -0,0 +1,12 @@
import { Checkbox, Label } from "@medusajs/ui"
export default function CheckboxDisabled() {
return (
<div className="flex items-center space-x-2">
<Checkbox id="billing-shipping-disabled" disabled={true} />
<Label htmlFor="billing-shipping-disabled">
Billing address same as shipping address
</Label>
</div>
)
}
@@ -0,0 +1,12 @@
import { Checkbox, Label } from "@medusajs/ui"
export default function CheckboxIndeterminate() {
return (
<div className="flex items-center space-x-2">
<Checkbox id="billing-shipping-indeterminate" checked={"indeterminate"} />
<Label htmlFor="billing-shipping-indeterminate">
Billing address same as shipping address
</Label>
</div>
)
}
@@ -0,0 +1,34 @@
import { CodeBlock, Label } from "@medusajs/ui"
const snippets = [
{
label: "cURL",
language: "markdown",
code: `curl -H 'x-publishable-key: YOUR_API_KEY' 'http://localhost:9000/store/products/PRODUCT_ID'`,
},
{
label: "Medusa JS Client",
language: "jsx",
code: `// Install the JS Client in your storefront project: @medusajs/medusa-js\n\nimport Medusa from "@medusajs/medusa-js"\n\nconst medusa = new Medusa({ publishableApiKey: "YOUR_API_KEY"})\nconst product = await medusa.products.retrieve("PRODUCT_ID")\nconsole.log(product.id)`,
},
{
label: "Medusa React",
language: "tsx",
code: `// Install the React SDK and required dependencies in your storefront project:\n// medusa-react @tanstack/react-query @medusajs/medusa\n\nimport { useProduct } from "medusa-react"\n\nconst { product } = useProduct("PRODUCT_ID")\nconsole.log(product.id)`,
},
]
export default function CodeBlockDemo() {
return (
<div className="w-full">
<CodeBlock snippets={snippets}>
<CodeBlock.Header>
<CodeBlock.Header.Meta>
<Label weight={"plus"}>/product-detail.js</Label>
</CodeBlock.Header.Meta>
</CodeBlock.Header>
<CodeBlock.Body />
</CodeBlock>
</div>
)
}
@@ -0,0 +1,19 @@
import { CodeBlock } from "@medusajs/ui"
const snippets = [
{
label: "Medusa React",
language: "tsx",
code: `import { useProduct } from "medusa-react"\n\nconst { product } = useProduct("PRODUCT_ID")\nconsole.log(product.id)`,
},
]
export default function CodeBlockNoHeader() {
return (
<div className="w-full">
<CodeBlock snippets={snippets}>
<CodeBlock.Body />
</CodeBlock>
</div>
)
}
@@ -0,0 +1,24 @@
import { CodeBlock, Label } from "@medusajs/ui"
const snippets = [
{
label: "Medusa React",
language: "tsx",
code: `import { useProduct } from "medusa-react"\n\nconst { product } = useProduct("PRODUCT_ID")\nconsole.log(product.id)`,
},
]
export default function CodeBlockNoLines() {
return (
<div className="w-full">
<CodeBlock snippets={snippets}>
<CodeBlock.Header>
<CodeBlock.Header.Meta>
<Label weight={"plus"}>/product-detail.js</Label>
</CodeBlock.Header.Meta>
</CodeBlock.Header>
<CodeBlock.Body hideLineNumbers={true} />
</CodeBlock>
</div>
)
}
@@ -0,0 +1,24 @@
import { CodeBlock, Label } from "@medusajs/ui"
const snippets = [
{
label: "Medusa React",
language: "tsx",
code: `import { useProduct } from "medusa-react"\n\nconst { product } = useProduct("PRODUCT_ID")\nconsole.log(product.id)`,
},
]
export default function CodeBlockSingle() {
return (
<div className="w-full">
<CodeBlock snippets={snippets}>
<CodeBlock.Header hideLabels={true}>
<CodeBlock.Header.Meta>
<Label weight={"plus"}>/product-detail.js</Label>
</CodeBlock.Header.Meta>
</CodeBlock.Header>
<CodeBlock.Body />
</CodeBlock>
</div>
)
}
+16
View File
@@ -0,0 +1,16 @@
import { Badge, Command } from "@medusajs/ui"
export default function CommandDemo() {
return (
<div className="w-full">
<Command>
<Badge color="green">Get</Badge>
<code>localhost:9000/store/products</code>
<Command.Copy
content="localhost:9000/store/products"
className="ml-auto"
/>
</Command>
</div>
)
}
@@ -0,0 +1,5 @@
import { Container } from "@medusajs/ui"
export default function ContainerDemo() {
return <Container>Content</Container>
}
@@ -0,0 +1,22 @@
import { Container, Heading } from "@medusajs/ui"
export default function ContainerLayout() {
return (
<div className="flex h-full w-full">
<div className="border-ui-border-base w-full max-w-[216px] border-r p-4">
<Heading level="h3">Menubar</Heading>
</div>
<div className="flex w-full flex-col gap-y-2 px-8 pb-8 pt-6">
<Container>
<Heading>Section 1</Heading>
</Container>
<Container>
<Heading>Section 2</Heading>
</Container>
<Container>
<Heading>Section 3</Heading>
</Container>
</div>
</div>
)
}
@@ -0,0 +1,15 @@
import { PlusMini } from "@medusajs/icons"
import { Button, Copy, Text } from "@medusajs/ui"
export default function CopyAsChild() {
return (
<div className="flex items-center gap-x-2">
<Text>Copy command</Text>
<Copy content="yarn add @medusajs/ui" asChild>
<Button format="icon" size="small" className="text-ui-fg-on-inverted">
<PlusMini />
</Button>
</Copy>
</div>
)
}
@@ -0,0 +1,9 @@
import { Code, Copy } from "@medusajs/ui"
export default function CopyDemo() {
return (
<Copy content="yarn add @medusajs/ui">
<Code>yarn add @medusajs/ui</Code>
</Copy>
)
}

Some files were not shown because too many files have changed in this diff Show More