docs: generate documentation for UI components (#5849)

* added tool to generate spec files for React components

* use typedoc for missing descriptions and types

* improvements and fixes

* improvements

* added doc comments for half of the components

* add custom resolver + more doc comments

* added all tsdocs

* general improvements

* add specs to UI docs

* added github action

* remove unnecessary api route

* Added readme for react-docs-generator

* remove comment

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

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

* remove description of aria fields + add generate script

---------

Co-authored-by: Kasper Fabricius Kristensen <45367945+kasperkristensen@users.noreply.github.com>
This commit is contained in:
Shahed Nasser
2023-12-13 16:02:41 +02:00
committed by GitHub
co-authored by Kasper Fabricius Kristensen
parent edc49bfe1d
commit 245e5c9a69
288 changed files with 6029 additions and 1447 deletions
+51
View File
@@ -116,4 +116,55 @@ jobs:
labels: "type: chore"
add-paths: www/apps/api-reference/specs
branch: "docs/generate-api-ref"
branch-suffix: "timestamp"
ui:
runs-on: ubuntu-latest
if: ${{ github.event_name == 'release' || github.event.inputs.referenceName == 'all' || github.event.inputs.referenceName == 'ui' }}
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- name: Checkout
uses: actions/checkout@v3
with:
token: ${{ secrets.REFERENCE_PAT }}
fetch-depth: 0
- name: Setup Node.js environment
uses: actions/setup-node@v3
with:
node-version: "16.10.0"
cache: "yarn"
- name: Install dependencies
uses: ./.github/actions/cache-deps
with:
extension: reference
- name: Build Packages
run: yarn build
- name: Install Workspace dependencies
run: yarn install
working-directory: docs-util
- name: Build Workspace dependencies
run: yarn build
working-directory: docs-util
- name: Generate UI Specs
run: yarn generate:ui
working-directory: docs-util/packages/react-docs-generator
- name: Create Pull Request
uses: peter-evans/create-pull-request@v4
with:
commit-message: "chore(docs): Generated UI Reference"
base: "develop"
title: "chore(docs): Updated UI Reference"
labels: "type: chore"
add-paths: www/apps/ui/src/specs
branch: "docs/generate-ui-ref"
branch-suffix: "timestamp"
@@ -0,0 +1,18 @@
# react-docs-generator
Tool that generates documentation for React components. It's built with [react-docgen](https://react-docgen.dev/) and [Typedoc](https://typedoc.org/).
## Usage
```bash
yarn start --src ./path/to/src --output ./path/to/output/dir
```
### Options
- `--src <srcPath>`: (required) A path to a file containing React components or a directory to scan its sub-directories and files for components.
- `--output <outputPath>`: (required) Path to the directory to store the output in.
- `--clean`: If used, the output directory is emptied before generating the new output.
- `--tsconfigPath <path>`: Path to a TS Config file which is used by Typedoc. By default, the file at `docs-util/packages/typedoc-config/extended-tsconfig/ui.json` is used.
- `--disable-typedoc`: Whether to disable Typedoc and generate the spec file using `react-docgen` only. Useful for debugging.
- `--verbose-typedoc`: Whether to show the output of Typedoc. By default, it's disabeld.
@@ -0,0 +1,36 @@
{
"name": "react-docs-generator",
"license": "MIT",
"scripts": {
"start": "ts-node src/index.ts",
"generate:ui": "yarn start --src ../../../packages/design-system/ui/src --output ../../../www/apps/ui/src/specs --clean",
"build": "tsc",
"watch": "tsc --watch",
"prepublishOnly": "cross-env NODE_ENV=production tsc --build"
},
"publishConfig": {
"access": "public"
},
"version": "0.0.0",
"type": "module",
"exports": "./dist/index.js",
"bin": "dist/index.js",
"dependencies": {
"chalk": "^5.3.0",
"commander": "^11.1.0",
"glob": "^10.3.10",
"react-docgen": "^7.0.1",
"resolve": "^1.22.8",
"ts-node": "^10.9.1",
"typedoc": "^0.25.4",
"typedoc-plugin-custom": "*",
"typescript": "5.2",
"utils": "*"
},
"devDependencies": {
"@types/node": "^20.9.4"
},
"peerDependencies": {
"typedoc": "0.25.x"
}
}
@@ -0,0 +1,578 @@
/* eslint-disable no-case-declarations */
import { Documentation } from "react-docgen"
import {
FunctionSignatureType,
ObjectSignatureType,
TSFunctionSignatureType,
TypeDescriptor,
} from "react-docgen/dist/Documentation.js"
import { Comment } from "typedoc"
import {
Application,
Context,
Converter,
DeclarationReflection,
ProjectReflection,
Reflection,
SignatureReflection,
SomeType,
SourceReference,
} from "typedoc"
import {
getFunctionType,
getProjectChild,
getType,
getTypeChildren,
} from "utils"
type MappedReflectionSignature = {
source: SourceReference
signatures: SignatureReflection[]
}
type Options = {
tsconfigPath: string
disable?: boolean
verbose?: boolean
}
type TsType = TypeDescriptor<TSFunctionSignatureType>
type ExcludeExternalOptions = {
parentReflection: DeclarationReflection
childReflection: DeclarationReflection
signature?: SignatureReflection
propDescription?: string
}
const MAX_LEVEL = 3
export default class TypedocManager {
private app: Application | undefined
private options: Options
private mappedReflectionSignatures: MappedReflectionSignature[]
private project: ProjectReflection | undefined
private getTypeOptions = {
hideLink: true,
wrapBackticks: false,
escape: false,
}
constructor(options: Options) {
this.options = options
this.mappedReflectionSignatures = []
}
async setup(filePath: string): Promise<ProjectReflection | undefined> {
if (this.options.disable) {
return
}
this.app = await Application.bootstrapWithPlugins({
entryPoints: [filePath],
tsconfig: this.options.tsconfigPath,
plugin: ["typedoc-plugin-custom"],
enableInternalResolve: true,
logLevel: this.options.verbose ? "Verbose" : "None",
})
// This listener sets the content of mappedReflectionSignatures
this.app.converter.on(
Converter.EVENT_RESOLVE,
(context: Context, reflection: Reflection) => {
if (reflection instanceof DeclarationReflection) {
if (reflection.sources?.length && reflection.signatures?.length) {
this.mappedReflectionSignatures.push({
source: reflection.sources[0],
signatures: reflection.signatures,
})
}
}
}
)
this.project = await this.app.convert()
return this.project
}
tryFillWithTypedocData(
spec: Documentation,
reflectionPathName: string[]
): Documentation {
if (!this.isProjectSetUp()) {
return spec
}
if (!spec.props) {
spec.props = {}
}
// since the component may be a child of an exported component
// we use the reflectionPathName to retrieve the component
// by its "reflection path"
const reflection = this.project?.getChildByName(
reflectionPathName
) as DeclarationReflection
if (!reflection) {
return spec
}
// retrieve the signature of the reflection
// this is helpful to retrieve the props of the component
const mappedSignature = reflection.sources?.length
? this.getMappedSignatureFromSource(reflection.sources[0])
: undefined
if (
mappedSignature?.signatures[0].parameters?.length &&
mappedSignature.signatures[0].parameters[0].type
) {
const signature = mappedSignature.signatures[0]
// get the props of the component from the
// first parameter in the signature.
const props = getTypeChildren(
signature.parameters![0].type!,
this.project
)
// this stores props that should be removed from the
// spec
const propsToRemove = new Set<string>()
// loop over props in the spec to either add missing descriptions or
// push a prop into the `propsToRemove` set.
Object.entries(spec.props!).forEach(([propName, propDetails]) => {
// retrieve the reflection of the prop
const reflectionPropType = props.find(
(propType) => propType.name === propName
)
if (!reflectionPropType) {
// if the reflection doesn't exist and the
// prop doesn't have a description, it should
// be removed.
if (!propDetails.description) {
propsToRemove.add(propName)
}
return
}
// if the component has the `@excludeExternal` tag,
// the prop is external, and it doesn't have the
// `@keep` tag, the prop is removed.
if (
this.shouldExcludeExternal({
parentReflection: reflection,
childReflection: reflectionPropType,
propDescription: propDetails.description,
signature,
})
) {
propsToRemove.add(propName)
return
}
// if the prop doesn't have description, retrieve it using Typedoc
propDetails.description =
propDetails.description || this.getDescription(reflectionPropType)
// if the prop still doesn't have description, remove it.
if (!propDetails.description) {
propsToRemove.add(propName)
} else {
propDetails.description = this.normalizeDescription(
propDetails.description
)
}
})
// delete props in the `propsToRemove` set from the specs.
propsToRemove.forEach((prop) => delete spec.props![prop])
// try to add missing props
props
.filter(
(prop) =>
// Filter out props that are already in the
// specs, are already in the `propsToRemove` set
// (meaning they've been removed), don't have a
// comment, are React props, and are external props (if
// the component excludes them).
!Object.hasOwn(spec.props!, prop.name) &&
!propsToRemove.has(prop.name) &&
this.getReflectionComment(prop) &&
!this.isFromReact(prop) &&
!this.shouldExcludeExternal({
parentReflection: reflection,
childReflection: prop,
signature,
})
)
.forEach((prop) => {
// If the prop has description (retrieved)
// through Typedoc, it's added into the spec.
const description = this.normalizeDescription(
this.getDescription(prop)
)
if (!description) {
return
}
spec.props![prop.name] = {
description: this.normalizeDescription(this.getDescription(prop)),
required: !prop.flags.isOptional,
tsType: prop.type
? this.getTsType(prop.type)
: prop.signatures?.length
? this.getFunctionTsType(prop.signatures[0])
: undefined,
}
if (!spec.props![prop.name].tsType) {
delete spec.props![prop.name].tsType
}
})
}
return spec
}
isProjectSetUp() {
return this.project && this.mappedReflectionSignatures
}
getMappedSignatureFromSource(
origSource: SourceReference
): MappedReflectionSignature | undefined {
return this.mappedReflectionSignatures.find(({ source }) => {
return (
source.fileName === origSource.fileName &&
source.line === origSource.line &&
source.character === origSource.character
)
})
}
// Retrieves the `tsType` stored in a spec's prop
// The format is based on the expected format of React Docgen.
getTsType(reflectionType: SomeType, level = 1): TsType {
const rawValue = getType({
reflectionType,
...this.getTypeOptions,
})
if (level > MAX_LEVEL) {
return {
name: rawValue,
}
}
switch (reflectionType.type) {
case "array": {
const elements = this.getTsType(reflectionType.elementType, level + 1)
return {
name: "Array",
elements: [elements],
raw: rawValue,
}
}
case "reference":
const referenceReflection: DeclarationReflection | undefined =
(reflectionType.reflection as DeclarationReflection) ||
getProjectChild(this.project!, reflectionType.name)
const elements: TsType[] = []
if (referenceReflection?.children) {
referenceReflection.children?.forEach((child) => {
if (!child.type) {
return
}
elements.push(this.getTsType(child.type, level + 1))
})
} else if (referenceReflection?.type) {
elements.push(this.getTsType(referenceReflection.type, level + 1))
}
return {
name: reflectionType.name,
elements: elements,
raw: rawValue,
}
case "reflection":
const reflection = reflectionType.declaration
if (reflection.signatures?.length) {
return this.getFunctionTsType(
reflection.signatures[0],
rawValue,
level + 1
)
} else {
const typeData: ObjectSignatureType = {
name: "signature",
type: "object",
raw: rawValue,
signature: {
properties: [],
},
}
reflection.children?.map((property) => {
typeData.signature.properties.push({
key: property.name,
value: property.type
? this.getTsType(property.type, level + 1)
: {
name: "unknown",
},
description: this.getDescription(property),
})
})
return typeData
}
case "literal":
if (reflectionType.value === null) {
return {
name: `null`,
}
}
return {
name: "literal",
value: rawValue,
}
case "union":
case "intersection":
return {
name: reflectionType.type,
raw: rawValue,
elements: this.getElementsTypes(reflectionType.types, level),
}
case "tuple":
return {
name: "tuple",
raw: rawValue,
elements: this.getElementsTypes(reflectionType.elements, level),
}
default:
return {
name: rawValue,
}
}
}
// Retrieves the TsType of nested elements. (Helpful for
// Reflection types like `intersection` or `union`).
getElementsTypes(elements: SomeType[], level = 1): TsType[] {
const elementData: TsType[] = []
elements.forEach((element) => {
elementData.push(this.getTsType(element, level + 1))
})
return elementData
}
// Removes tags like `@keep` and `@ignore` from a
// prop or component's description. These aren't removed
// by React Docgen.
normalizeDescription(description: string): string {
return description
.replace("@keep", "")
.replace("@ignore", "")
.replace("@excludeExternal", "")
.trim()
}
// Retrieve the description of a reflection (component or prop)
// through its summary.
getDescription(reflection: DeclarationReflection): string {
let commentDisplay = this.getReflectionComment(reflection)?.summary
if (!commentDisplay) {
const signature = reflection.signatures?.find(
(sig) => this.getReflectionComment(sig)?.summary.length
)
if (signature) {
commentDisplay = this.getReflectionComment(signature)!.summary
}
}
return commentDisplay?.map(({ text }) => text).join("") || ""
}
// Retrieves the TsType for a function
getFunctionTsType(
signature: SignatureReflection,
rawValue?: string,
level = 1
): TsType {
if (!rawValue) {
rawValue = getFunctionType({
modelSignatures: [signature],
...this.getTypeOptions,
})
}
const typeData: FunctionSignatureType = {
name: "signature",
type: "function",
raw: rawValue!,
signature: {
arguments: [],
return: undefined,
},
}
signature.parameters?.forEach((parameter) => {
const parameterType = parameter.type
? this.getTsType(parameter.type, level + 1)
: undefined
typeData.signature.arguments.push({
name: parameter.name,
type: parameterType,
rest: parameter.flags.isRest,
})
})
typeData.signature.return = signature.type
? this.getTsType(signature.type, level + 1)
: undefined
return typeData
}
// Checks if a TsType only has a `name` field.
doesOnlyHaveName(obj: TsType): boolean {
const primitiveTypes = ["string", "number", "object", "boolean", "function"]
const keys = Object.keys(obj)
return (
keys.length === 1 &&
keys[0] === "name" &&
!primitiveTypes.includes(obj.name)
)
}
// retrieves a reflection by the provided name
// and check if its type is ReactNode
// this is useful for the CustomResolver to check
// if a variable is a React component.
isReactComponent(name: string): boolean {
const reflection = this.getReflectionByName(name)
if (
!reflection ||
!(reflection instanceof DeclarationReflection) ||
!reflection.signatures
) {
return false
}
return reflection.signatures.some(
(signature) =>
signature.type?.type === "reference" &&
signature.type.name === "ReactNode"
)
}
// Returns the TsType of a child of a reflection.
resolveChildType(reflectionName: string, childName: string): TsType | null {
if (!this.project) {
return null
}
const reflection = this.getReflectionByName(reflectionName)
if (!reflection) {
return null
}
let childReflection: DeclarationReflection | undefined
if (reflection.children) {
childReflection = reflection.getChildByName(
childName
) as DeclarationReflection
} else if (reflection.type) {
getTypeChildren(reflection.type, this.project).some((child) => {
if (child.name === childName) {
childReflection = child
return true
}
return false
})
}
if (
!childReflection ||
!("type" in childReflection) ||
(this.isFromReact(childReflection as DeclarationReflection) &&
!this.getReflectionComment(childReflection)?.summary)
) {
return null
}
return this.getTsType(childReflection.type as SomeType)
}
// used to check if a reflection (typically of a prop)
// is inherited from React (for example, the className prop)
isFromReact(reflection: DeclarationReflection) {
// check first if the reflection has the `@keep` modifier
if (this.getReflectionComment(reflection)?.hasModifier("@keep")) {
return false
}
return reflection.sources?.some((source) =>
source.fileName.includes("@types/react")
)
}
// Checks if a parent reflection has the `@excludeExternal`
// which means external child reflections should be ignored.
// external child reflections aren't ignored if they have the
// `@keep` tag.
shouldExcludeExternal({
parentReflection,
childReflection,
propDescription,
signature,
}: ExcludeExternalOptions): boolean {
const parentHasExcludeExternalsModifier =
this.getReflectionComment(parentReflection)?.hasModifier(
"@excludeExternal"
) ||
(signature &&
this.getReflectionComment(signature)?.hasModifier(
"@excludeExternal"
)) ||
false
const childHasKeepModifier =
this.getReflectionComment(childReflection)?.hasModifier("@keep") ||
propDescription?.includes("@keep")
const childHasExternalSource =
childReflection.sources?.some((source) =>
source.fileName.startsWith("node_modules")
) || false
return (
parentHasExcludeExternalsModifier &&
!childHasKeepModifier &&
childHasExternalSource
)
}
// Gets comments of a reflection.
getReflectionComment(reflection: Reflection): Comment | undefined {
if (reflection.comment) {
return reflection.comment
}
if (
reflection instanceof DeclarationReflection &&
reflection.signatures?.length
) {
return reflection.signatures.find(
(signature) => signature.comment !== undefined
)?.comment
}
return undefined
}
// Gets a reflection by its name.
getReflectionByName(name: string): DeclarationReflection | undefined {
return this.project
? (Object.values(this.project?.reflections || {}).find(
(ref) => ref.name === name
) as DeclarationReflection)
: undefined
}
}
@@ -0,0 +1,115 @@
import path from "path"
import readFiles from "../utils/read-files.js"
import { builtinHandlers, parse } from "react-docgen"
import { existsSync, mkdirSync, rmSync, writeFileSync } from "fs"
import TypedocManager from "../classes/typedoc-manager.js"
import chalk from "chalk"
import CustomResolver from "../resolvers/custom-resolver.js"
import argsPropHandler from "../handlers/argsPropHandler.js"
type GenerateOptions = {
src: string
output: string
clean?: boolean
tsconfigPath: string
disableTypedoc: boolean
verboseTypedoc: boolean
}
export default async function ({
src,
output,
clean,
tsconfigPath,
disableTypedoc,
verboseTypedoc,
}: GenerateOptions) {
const fileContents = readFiles(src)
let outputExists = existsSync(output)
if (clean && outputExists) {
// remove the directory which will be created in the next condition block
rmSync(output, { recursive: true, force: true })
outputExists = false
}
// create output directory if it doesn't exist
if (!outputExists) {
mkdirSync(output)
}
// optionally use typedoc to add missing props, descriptions
// or types.
const typedocManager = new TypedocManager({
tsconfigPath,
disable: disableTypedoc,
verbose: verboseTypedoc,
})
await typedocManager.setup(src)
for (const [filePath, fileContent] of fileContents) {
try {
const relativePath = path.resolve(filePath)
// retrieve the specs of a file
const specs = parse(fileContent, {
filename: relativePath,
resolver: new CustomResolver(typedocManager),
handlers: [
// Built-in handlers
builtinHandlers.childContextTypeHandler,
builtinHandlers.codeTypeHandler,
builtinHandlers.componentDocblockHandler,
builtinHandlers.componentMethodsHandler,
builtinHandlers.contextTypeHandler,
builtinHandlers.defaultPropsHandler,
builtinHandlers.displayNameHandler,
builtinHandlers.propDocblockHandler,
builtinHandlers.propTypeCompositionHandler,
builtinHandlers.propTypeHandler,
// Custom handlers
(documentation, componentPath) =>
argsPropHandler(documentation, componentPath, typedocManager),
],
babelOptions: {
ast: true,
},
})
// write each of the specs into output directory
specs.forEach((spec) => {
if (!spec.displayName) {
return
}
const specNameSplit = spec.displayName.split(".")
let filePath = output
if (spec.description) {
spec.description = typedocManager.normalizeDescription(
spec.description
)
}
// if typedoc isn't disabled, this method will try to fill
// missing descriptions and types, and add missing props.
spec = typedocManager.tryFillWithTypedocData(spec, specNameSplit)
// put the spec in a sub-directory
filePath = path.join(filePath, specNameSplit[0])
if (!existsSync(filePath)) {
mkdirSync(filePath)
}
// write spec to output path
writeFileSync(
path.join(filePath, `${spec.displayName}.json`),
JSON.stringify(spec, null, 2)
)
console.log(chalk.green(`Created spec file for ${spec.displayName}.`))
})
} catch (e) {
console.error(chalk.red(`Failed to parse ${filePath}: ${e}`))
}
}
}
@@ -0,0 +1,126 @@
import { utils, DocumentationBuilder, NodePath } from "react-docgen"
import { ComponentNode } from "react-docgen/dist/resolver/index.js"
import { getDocblock } from "react-docgen/dist/utils/docblock.js"
import TypedocManager from "../classes/typedoc-manager.js"
import emptyPropDescriptor from "../utils/empty-prop-descriptor.js"
import { Node } from "@babel/core"
import isEmptyPropDescriptor from "../utils/is-empty-prop-descriptor.js"
function resolveDocumentation(
documentation: DocumentationBuilder,
path: NodePath<ComponentNode>,
typedocManager?: TypedocManager
) {
if (!path.isObjectExpression() && !path.isObjectPattern()) {
return
}
path.get("properties").forEach((propertyPath) => {
if (propertyPath.isSpreadElement() || propertyPath.isRestElement()) {
const resolvedValuePath = utils.resolveToValue(
propertyPath.get("argument")
) as NodePath<ComponentNode>
resolveDocumentation(documentation, resolvedValuePath)
} else if (
propertyPath.isObjectProperty() ||
propertyPath.isObjectMethod()
) {
const propertyName = utils.getPropertyName(propertyPath)
const propDescriptor = propertyName
? documentation.getPropDescriptor(propertyName)
: undefined
const description =
propDescriptor?.description || getDocblock(propertyPath)
const propExists = propertyName !== null && propDescriptor !== undefined
const shouldRemoveProp =
description?.includes("@ignore") ||
(!description &&
(!propDescriptor || isEmptyPropDescriptor(propDescriptor)))
// remove property if it doesn't have a description or
// if its description includes the `@ignore` tag.
if (!propExists || shouldRemoveProp) {
if (shouldRemoveProp && propExists) {
// prop is removed if its descriptor is empty,
// so we empty it to remove it.
emptyPropDescriptor(propDescriptor)
}
return
}
// set description
utils.setPropDescription(documentation, propertyPath)
// set type if missing
if (!propDescriptor.tsType && typedocManager) {
const typeAnnotation = utils.getTypeAnnotation(path)
if (typeAnnotation?.isTSTypeReference) {
const typeName = typeAnnotation.get("typeName")
if (
!Array.isArray(typeName) &&
typeName.hasNode() &&
typeName.isIdentifier()
) {
const tsType = typedocManager.resolveChildType(
typeName.node.name,
propertyName
)
if (tsType) {
propDescriptor.tsType = tsType
}
}
}
} else if (
propDescriptor.tsType &&
typedocManager?.doesOnlyHaveName(propDescriptor.tsType)
) {
// see if the type needs to be resolved.
const typeReflection = typedocManager?.getReflectionByName(
propDescriptor.tsType.name
)
if (typeReflection && typeReflection.type) {
propDescriptor.tsType =
typedocManager?.getTsType(typeReflection.type) ||
propDescriptor.tsType
}
}
}
})
}
/**
* A handler that resolves props from arguments and
* sets their description, type, etc...
*/
const argsPropHandler = (
documentation: DocumentationBuilder,
componentDefinition: NodePath<ComponentNode>,
typedocManager?: TypedocManager
) => {
let componentParams: NodePath<Node>[] = []
if (componentDefinition.isCallExpression()) {
const args = componentDefinition.get("arguments")
args.forEach((arg) => {
const params = arg.get("params")
if (Array.isArray(params)) {
componentParams.push(...params)
} else {
componentParams.push(params)
}
})
} else if (componentDefinition.isArrowFunctionExpression()) {
componentParams = componentDefinition.get("params")
}
componentParams.forEach((param) => {
const resolvedParam = utils.resolveToValue(param) as NodePath<ComponentNode>
if (!resolvedParam) {
return
}
// set description and type of prop
resolveDocumentation(documentation, resolvedParam, typedocManager)
})
}
export default argsPropHandler
@@ -0,0 +1,38 @@
#!/usr/bin/env node
import { program } from "commander"
import generate from "./commands/generate.js"
import path from "path"
import { fileURLToPath } from "url"
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
program
.description("Generate React specs used for documentation purposes.")
.requiredOption(
"-s, --src <srcPath>",
"Path to a file containing a React component or a directory of React components."
)
.requiredOption("-o, --output <outputPath>", "Path to the output directory.")
.option(
"--clean",
"Clean the output directory before creating the new specs",
false
)
.option(
"--tsconfigPath <tsconfigPath>",
"Path to TSConfig file.",
path.join(
__dirname,
"..",
"..",
"typedoc-config",
"extended-tsconfig",
"ui.json"
)
)
.option("--disable-typedoc", "Whether to disable Typedoc", false)
.option("--verbose-typedoc", "Whether to show Typedoc logs.", false)
.parse()
void generate(program.opts())
@@ -0,0 +1,105 @@
import { visitors } from "@babel/traverse"
import { utils, builtinResolvers, FileState, NodePath } from "react-docgen"
import { ComponentNodePath } from "react-docgen/dist/resolver/index.js"
import TypedocManager from "../classes/typedoc-manager.js"
type State = {
foundDefinitions: Set<ComponentNodePath>
}
/**
* This resolver extends react-docgen's FindAllDefinitionsResolver
* + adds the ability to resolve variable components such as:
*
* ```tsx
* const Value = SelectPrimitive.Value
* Value.displayName = "Select.Value"
* ```
*/
export default class CustomResolver
implements builtinResolvers.FindAllDefinitionsResolver
{
private typedocManager: TypedocManager
constructor(typedocManager: TypedocManager) {
this.typedocManager = typedocManager
}
resolve(file: FileState): ComponentNodePath[] {
const state = {
foundDefinitions: new Set<ComponentNodePath>(),
}
file.traverse(
visitors.explode({
FunctionDeclaration: { enter: this.statelessVisitor },
FunctionExpression: { enter: this.statelessVisitor },
ObjectMethod: { enter: this.statelessVisitor },
ArrowFunctionExpression: { enter: this.statelessVisitor },
ClassExpression: { enter: this.classVisitor },
ClassDeclaration: { enter: this.classVisitor },
VariableDeclaration: {
enter: (path, state: State) => {
const found = path.node.declarations.some((declaration) => {
if (
"name" in declaration.id &&
this.typedocManager.isReactComponent(declaration.id.name) &&
declaration.init
) {
const init = path.get("declarations")[0].get("init")
if (init.isMemberExpression()) {
state.foundDefinitions.add(
path as unknown as ComponentNodePath
)
return true
}
return false
}
})
if (found) {
return path.skip()
}
},
},
CallExpression: {
enter: (path, state: State) => {
const argument = path.get("arguments")[0]
if (!argument) {
return
}
if (utils.isReactForwardRefCall(path)) {
// If the the inner function was previously identified as a component
// replace it with the parent node
const inner = utils.resolveToValue(argument) as ComponentNodePath
state.foundDefinitions.delete(inner)
state.foundDefinitions.add(path)
// Do not traverse into arguments
return path.skip()
} else if (utils.isReactCreateClassCall(path)) {
const resolvedPath = utils.resolveToValue(argument)
if (resolvedPath.isObjectExpression()) {
state.foundDefinitions.add(resolvedPath)
}
// Do not traverse into arguments
return path.skip()
}
},
},
}),
state
)
return Array.from(state.foundDefinitions)
}
classVisitor(path: NodePath, state: State) {
if (utils.isReactComponentClass(path)) {
utils.normalizeClassDefinition(path)
state.foundDefinitions.add(path)
}
path.skip()
}
statelessVisitor(path: NodePath, state: State) {
if (utils.isStatelessComponent(path)) {
state.foundDefinitions.add(path)
}
path.skip()
}
}
@@ -0,0 +1,8 @@
import { PropDescriptor } from "react-docgen/dist/Documentation.js"
export default function emptyPropDescriptor(propDescriptor: PropDescriptor) {
const objKeys = Object.keys(propDescriptor)
objKeys.forEach((key) => {
delete propDescriptor[key as keyof PropDescriptor]
})
}
@@ -0,0 +1,19 @@
import { PropDescriptor } from "react-docgen/dist/Documentation.js"
export default function isEmptyPropDescriptor(propDescriptor: PropDescriptor) {
const objKeys = Object.keys(propDescriptor)
return (
objKeys.length === 0 ||
objKeys.every((objKey) => {
const value = propDescriptor[objKey as keyof PropDescriptor]
switch (typeof value) {
case "string":
return value.length === 0
case "object":
return Object.keys(value).length === 0
default:
return false
}
})
)
}
@@ -0,0 +1,5 @@
const BLACKLISTED_PROPS = ["className", "children"]
export default function isPropBlacklisted(propName: string) {
return BLACKLISTED_PROPS.includes(propName)
}
@@ -0,0 +1,20 @@
import { statSync, readFileSync } from "fs"
import { globSync } from "glob"
export default function readFiles(path: string): Map<string, string> {
const files = new Map<string, string>()
// check if path is for a file
const fileStats = statSync(path)
if (fileStats.isFile()) {
files.set(path, readFileSync(path, "utf-8"))
} else {
const filePaths = globSync(`${path}/**/*.{tsx,jsx}`, {
ignore: [`${path}/**/*.spec.*`, `${path}/**/*.stories.*`],
})
filePaths.forEach((filePath) => {
files.set(filePath, readFileSync(filePath, "utf-8"))
})
}
return files
}
@@ -0,0 +1,20 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"target": "ESNext",
"module": "Node16",
"moduleResolution": "node16",
"outDir": "./dist",
"rootDir": "./src",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"resolveJsonModule": true,
},
"include": ["src", "../typedoc-plugin-custom/src/types"],
"ts-node": {
"esm": true,
"experimentalSpecifierResolution": "node",
"transpileOnly": true
}
}
+1 -1
View File
@@ -23,7 +23,7 @@
"glob": "^10.3.10",
"randomcolor": "^0.6.2",
"ts-node": "^10.9.1",
"typedoc": "0.25.1",
"typedoc": "^0.25.4",
"typedoc-config": "*",
"typedoc-monorepo-link-types": "^0.0.2",
"typedoc-plugin-custom": "*",
@@ -25,6 +25,14 @@
{
"tagName": "@docHideSignature",
"syntaxKind": "modifier"
},
{
"tagName": "@keep",
"syntaxKind": "modifier"
},
{
"tagName": "@excludeExternal",
"syntaxKind": "modifier"
}
]
}
@@ -0,0 +1,6 @@
{
"$schema": "http://json.schemastore.org/tsconfig",
"extends": [
"../../../../packages/design-system/ui/tsconfig.esm.json"
]
}
@@ -180,8 +180,8 @@ function addComments(schema: Schema, reflection: Reflection) {
"type" in reflection
? getTypeChildren(reflection.type as SomeType, reflection.project)
: "children" in reflection
? (reflection.children as DeclarationReflection[])
: []
? (reflection.children as DeclarationReflection[])
: []
Object.entries(schema.properties).forEach(([key, value]) => {
const childItem =
@@ -22,9 +22,7 @@ let hasMonkeyPatched = false
export function load(app: Application) {
if (hasMonkeyPatched) {
throw new Error(
"typedoc-plugin-missing-exports cannot be loaded multiple times"
)
throw new Error("typedoc-plugin-custom cannot be loaded multiple times")
}
hasMonkeyPatched = true
@@ -0,0 +1,6 @@
export declare module "typedoc" {
declare interface TypeDocOptionMap {
enableInternalResolve: boolean
internalModule: string
}
}
@@ -4,5 +4,6 @@
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src"]
"include": ["src", "src/types"],
"typeRoots": ["./src/types/index.d.ts"]
}
@@ -1,7 +1,8 @@
import * as Handlebars from "handlebars"
import { PageEvent } from "typedoc"
import { MarkdownTheme } from "../../theme"
import { escapeChars, getDisplayName } from "../../utils"
import { getDisplayName } from "../../utils"
import { escapeChars } from "utils"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper("breadcrumbs", function (this: PageEvent) {
@@ -3,7 +3,7 @@ import * as Handlebars from "handlebars"
import * as Path from "path"
import { CommentDisplayPart } from "typedoc/dist/lib/models/comments/comment"
import { MarkdownTheme } from "../../theme"
import { escapeChars } from "../../utils"
import { escapeChars } from "utils"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper("comment", function (parts: CommentDisplayPart[]) {
@@ -30,8 +30,8 @@ export default function (theme: MarkdownTheme) {
typeof part.target === "string"
? part.target
: "url" in part.target
? Handlebars.helpers.relativeURL(part.target.url)
: ""
? Handlebars.helpers.relativeURL(part.target.url)
: ""
const wrap = part.tag === "@linkcode" ? "`" : ""
result.push(
url ? `[${wrap}${part.text}${wrap}](${url})` : part.text
@@ -7,12 +7,8 @@ import {
ReflectionType,
} from "typedoc"
import { MarkdownTheme } from "../../theme"
import {
escapeChars,
memberSymbol,
stripComments,
stripLineBreaks,
} from "../../utils"
import { memberSymbol, stripComments } from "../../utils"
import { escapeChars, stripLineBreaks } from "utils"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper(
@@ -1,5 +1,5 @@
import * as Handlebars from "handlebars"
import { escapeChars } from "../../utils"
import { escapeChars } from "utils"
export default function () {
Handlebars.registerHelper("escape", function (str: string) {
@@ -1,7 +1,8 @@
import * as Handlebars from "handlebars"
import { PageEvent, ParameterReflection, ReflectionKind } from "typedoc"
import { MarkdownTheme } from "../../theme"
import { escapeChars, getDisplayName } from "../../utils"
import { getDisplayName } from "../../utils"
import { escapeChars } from "utils"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper(
@@ -4,8 +4,9 @@ import {
ReflectionKind,
SignatureReflection,
} from "typedoc"
import { getHTMLChar, memberSymbol } from "../../utils"
import { memberSymbol } from "../../utils"
import { MarkdownTheme } from "../../theme"
import { getHTMLChar } from "utils"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper(
@@ -5,7 +5,7 @@ import {
ReflectionGroup,
} from "typedoc"
import { MarkdownTheme } from "../../theme"
import { escapeChars } from "../../utils"
import { escapeChars } from "utils"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper(
@@ -1,7 +1,7 @@
import * as Handlebars from "handlebars"
import { SignatureReflection } from "typedoc"
import { ArrayType, ReferenceType } from "typedoc/dist/lib/models/types"
import { escapeChars } from "../../utils"
import { escapeChars } from "utils"
export default function () {
Handlebars.registerHelper(
@@ -1,10 +1,10 @@
import * as Handlebars from "handlebars"
import { DeclarationReflection, ReflectionType } from "typedoc"
import { MarkdownTheme } from "../../theme"
import { escapeChars, stripLineBreaks } from "../../utils"
import { parseParams } from "../../utils/params-utils"
import { ReflectionParameterType } from "../../types"
import reflectionFormatter from "../../utils/reflection-formatter"
import { escapeChars, stripLineBreaks } from "utils"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper(
@@ -4,7 +4,7 @@ import {
getTableHeaders,
reflectionTableFormatter,
} from "../../utils/reflection-formatter"
import { hasTypes } from "../../utils/type-utils"
import { hasTypes } from "utils"
export default function () {
Handlebars.registerHelper(
@@ -14,7 +14,7 @@ import {
UnionType,
UnknownType,
} from "typedoc"
import getType, { Collapse } from "../../utils/type-utils"
import { Collapse, getType } from "utils"
export default function () {
Handlebars.registerHelper(
@@ -43,6 +43,7 @@ export default function () {
reflectionType: this,
collapse,
wrapBackticks: emphasis,
getRelativeUrlMethod: Handlebars.helpers.relativeURL,
})
}
)
@@ -6,6 +6,7 @@ import {
ReflectionKind,
SignatureReflection,
} from "typedoc"
import { stripLineBreaks } from "utils"
export function formatContents(contents: string) {
return (
@@ -16,19 +17,6 @@ export function formatContents(contents: string) {
)
}
export function getHTMLChar(str: string) {
return str
.replace(/</g, "&#60;")
.replace(/{/g, "&#123;")
.replace(/}/g, "&#125;")
.replace(/>/g, "&#62;")
}
export function escapeChars(str: string, escapeBackticks = true) {
const result = getHTMLChar(str).replace(/_/g, "\\_").replace(/\|/g, "\\|")
return escapeBackticks ? result.replace(/`/g, "\\`") : result
}
export function memberSymbol(
reflection: DeclarationReflection | ParameterReflection | SignatureReflection
) {
@@ -57,17 +45,6 @@ export function stripComments(str: string) {
.replace(/^\s+|\s+$|(\s)+/g, "$1")
}
export function stripLineBreaks(str: string) {
return str
? str
.replace(/\n/g, " ")
.replace(/\r/g, " ")
.replace(/\t/g, " ")
.replace(/[\s]{2,}/g, " ")
.trim()
: ""
}
export function stripCode(str: string) {
return stripLineBreaks(str.replace("```ts", "").replace("```", ""))
}
@@ -6,10 +6,14 @@ import {
ReflectionType,
} from "typedoc"
import * as Handlebars from "handlebars"
import { stripCode, stripLineBreaks } from "../utils"
import { stripCode } from "../utils"
import { Parameter, ParameterStyle, ReflectionParameterType } from "../types"
import getType, { getReflectionType } from "./type-utils"
import { getTypeChildren } from "utils"
import {
getReflectionType,
getType,
getTypeChildren,
stripLineBreaks,
} from "utils"
import { MarkdownTheme } from "../theme"
const ALLOWED_KINDS: ReflectionKind[] = [
@@ -124,12 +128,14 @@ export function reflectionComponentFormatter({
reflectionType: reflection.type,
collapse: "object",
project: reflection.project,
getRelativeUrlMethod: Handlebars.helpers.relativeURL,
})
: getReflectionType({
reflectionType: reflection,
collapse: "object",
wrapBackticks: true,
project: reflection.project,
getRelativeUrlMethod: Handlebars.helpers.relativeURL,
}),
description: comments
? Handlebars.helpers.comments(comments, true, false)
@@ -7,14 +7,13 @@ import {
TypeParameterReflection,
} from "typedoc"
import * as Handlebars from "handlebars"
import getType from "./type-utils"
import { Parameter } from "../types"
import {
getDefaultValue,
reflectionComponentFormatter,
} from "./reflection-formatter"
import { MarkdownTheme } from "../theme"
import { getProjectChild, getTypeChildren } from "utils"
import { getProjectChild, getType, getTypeChildren } from "utils"
type ReturnReflectionComponentFormatterParams = {
reflectionType: SomeType
@@ -37,11 +36,13 @@ export function returnReflectionComponentFormatter({
wrapBackticks: false,
hideLink: true,
project,
getRelativeUrlMethod: Handlebars.helpers.relativeURL,
})
const type = getType({
reflectionType: reflectionType,
collapse: "object",
project,
getRelativeUrlMethod: Handlebars.helpers.relativeURL,
})
const componentItem: Parameter[] = []
const canRetrieveChildren = level + 1 <= (maxLevel || MarkdownTheme.MAX_LEVEL)
@@ -22,12 +22,15 @@ export function getTypeChildren(
break
case "reference":
// eslint-disable-next-line no-case-declarations
const referencedReflection =
reflectionType.reflection && "children" in reflectionType.reflection
const referencedReflection = reflectionType.reflection
? "children" in reflectionType.reflection
? reflectionType.reflection
: project
? getProjectChild(project, reflectionType.name)
? project.getReflectionById(reflectionType.reflection.id)
: undefined
: project
? getProjectChild(project, reflectionType.name)
: undefined
if (referencedReflection instanceof DeclarationReflection) {
if (referencedReflection.children) {
@@ -7,6 +7,7 @@ import {
IntersectionType,
IntrinsicType,
LiteralType,
ParameterReflection,
ProjectReflection,
QueryType,
ReferenceType,
@@ -19,10 +20,13 @@ import {
UnionType,
UnknownType,
} from "typedoc"
import * as Handlebars from "handlebars"
import { ReflectionParameterType } from "../types"
import { escapeChars, getHTMLChar } from "../utils"
import { getProjectChild } from "utils"
import { escapeChars, getHTMLChar } from "./str-utils"
import { getProjectChild } from "./get-project-child"
export type ReflectionParameterType =
| ParameterReflection
| DeclarationReflection
| TypeParameterReflection
export type Collapse = "object" | "function" | "all" | "none"
@@ -33,12 +37,10 @@ export type TypeOptions<T = SomeType> = {
hideLink?: boolean
escape?: boolean
project?: ProjectReflection
getRelativeUrlMethod?: (url: string) => string
}
export default function getType({
reflectionType,
...options
}: TypeOptions): string {
export function getType({ reflectionType, ...options }: TypeOptions): string {
if (reflectionType instanceof ReferenceType) {
return getReferenceType({
reflectionType,
@@ -304,6 +306,7 @@ export function getReferenceType({
hideLink = false,
escape,
project,
getRelativeUrlMethod,
...options
}: TypeOptions<ReferenceType>): string {
escape = getShouldEscape(wrapBackticks, escape)
@@ -321,9 +324,9 @@ export function getReferenceType({
if (modelReflection?.url) {
reflection.push(
shouldShowLink
? `[${modelReflection.name}](${Handlebars.helpers.relativeURL(
modelReflection.url
)})`
? `[${modelReflection.name}](${
getRelativeUrlMethod?.(modelReflection.url) || modelReflection.url
})`
: getFormattedStr(modelReflection.name, false, escape)
)
} else {
+2
View File
@@ -1,2 +1,4 @@
export * from "./get-type-children"
export * from "./get-project-child"
export * from "./get-type-str"
export * from "./str-utils"
+23
View File
@@ -0,0 +1,23 @@
export function getHTMLChar(str: string) {
return str
.replace(/</g, "&#60;")
.replace(/{/g, "&#123;")
.replace(/}/g, "&#125;")
.replace(/>/g, "&#62;")
}
export function escapeChars(str: string, escapeBackticks = true) {
const result = getHTMLChar(str).replace(/_/g, "\\_").replace(/\|/g, "\\|")
return escapeBackticks ? result.replace(/`/g, "\\`") : result
}
export function stripLineBreaks(str: string) {
return str
? str
.replace(/\n/g, " ")
.replace(/\r/g, " ")
.replace(/\t/g, " ")
.replace(/[\s]{2,}/g, " ")
.trim()
: ""
}
+213 -8
View File
@@ -52,6 +52,16 @@ __metadata:
languageName: node
linkType: hard
"@babel/code-frame@npm:^7.23.5":
version: 7.23.5
resolution: "@babel/code-frame@npm:7.23.5"
dependencies:
"@babel/highlight": ^7.23.4
chalk: ^2.4.2
checksum: a10e843595ddd9f97faa99917414813c06214f4d9205294013e20c70fbdf4f943760da37dec1d998bf3e6fc20fa2918a47c0e987a7e458663feb7698063ad7c6
languageName: node
linkType: hard
"@babel/compat-data@npm:^7.22.9":
version: 7.23.3
resolution: "@babel/compat-data@npm:7.23.3"
@@ -59,6 +69,29 @@ __metadata:
languageName: node
linkType: hard
"@babel/core@npm:^7.18.9":
version: 7.23.5
resolution: "@babel/core@npm:7.23.5"
dependencies:
"@ampproject/remapping": ^2.2.0
"@babel/code-frame": ^7.23.5
"@babel/generator": ^7.23.5
"@babel/helper-compilation-targets": ^7.22.15
"@babel/helper-module-transforms": ^7.23.3
"@babel/helpers": ^7.23.5
"@babel/parser": ^7.23.5
"@babel/template": ^7.22.15
"@babel/traverse": ^7.23.5
"@babel/types": ^7.23.5
convert-source-map: ^2.0.0
debug: ^4.1.0
gensync: ^1.0.0-beta.2
json5: ^2.2.3
semver: ^6.3.1
checksum: 311a512a870ee330a3f9a7ea89e5df790b2b5af0b1bd98b10b4edc0de2ac440f0df4d69ea2c0ee38a4b89041b9a495802741d93603be7d4fd834ec8bb6970bd2
languageName: node
linkType: hard
"@babel/core@npm:^7.23.0":
version: 7.23.3
resolution: "@babel/core@npm:7.23.3"
@@ -108,6 +141,18 @@ __metadata:
languageName: node
linkType: hard
"@babel/generator@npm:^7.23.5":
version: 7.23.5
resolution: "@babel/generator@npm:7.23.5"
dependencies:
"@babel/types": ^7.23.5
"@jridgewell/gen-mapping": ^0.3.2
"@jridgewell/trace-mapping": ^0.3.17
jsesc: ^2.5.1
checksum: 14c6e874f796c4368e919bed6003bb0adc3ce837760b08f9e646d20aeb5ae7d309723ce6e4f06bcb4a2b5753145446c8e4425851380f695e40e71e1760f49e7b
languageName: node
linkType: hard
"@babel/helper-compilation-targets@npm:^7.22.15":
version: 7.22.15
resolution: "@babel/helper-compilation-targets@npm:7.22.15"
@@ -221,6 +266,17 @@ __metadata:
languageName: node
linkType: hard
"@babel/helpers@npm:^7.23.5":
version: 7.23.5
resolution: "@babel/helpers@npm:7.23.5"
dependencies:
"@babel/template": ^7.22.15
"@babel/traverse": ^7.23.5
"@babel/types": ^7.23.5
checksum: a37e2728eb4378a4888e5d614e28de7dd79b55ac8acbecd0e5c761273e2a02a8f33b34b1932d9069db55417ace2937cbf8ec37c42f1030ce6d228857d7ccaa4f
languageName: node
linkType: hard
"@babel/highlight@npm:^7.23.4":
version: 7.23.4
resolution: "@babel/highlight@npm:7.23.4"
@@ -232,6 +288,15 @@ __metadata:
languageName: node
linkType: hard
"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.5":
version: 7.23.5
resolution: "@babel/parser@npm:7.23.5"
bin:
parser: ./bin/babel-parser.js
checksum: 3356aa90d7bafb4e2c7310e7c2c3d443c4be4db74913f088d3d577a1eb914ea4188e05fd50a47ce907a27b755c4400c4e3cbeee73dbeb37761f6ca85954f5a20
languageName: node
linkType: hard
"@babel/parser@npm:^7.22.15, @babel/parser@npm:^7.23.3, @babel/parser@npm:^7.23.4":
version: 7.23.4
resolution: "@babel/parser@npm:7.23.4"
@@ -252,6 +317,24 @@ __metadata:
languageName: node
linkType: hard
"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.23.5":
version: 7.23.5
resolution: "@babel/traverse@npm:7.23.5"
dependencies:
"@babel/code-frame": ^7.23.5
"@babel/generator": ^7.23.5
"@babel/helper-environment-visitor": ^7.22.20
"@babel/helper-function-name": ^7.23.0
"@babel/helper-hoist-variables": ^7.22.5
"@babel/helper-split-export-declaration": ^7.22.6
"@babel/parser": ^7.23.5
"@babel/types": ^7.23.5
debug: ^4.1.0
globals: ^11.1.0
checksum: c5ea793080ca6719b0a1612198fd25e361cee1f3c14142d7a518d2a1eeb5c1d21f7eec1b26c20ea6e1ddd8ed12ab50b960ff95ffd25be353b6b46e1b54d6f825
languageName: node
linkType: hard
"@babel/traverse@npm:^7.23.3, @babel/traverse@npm:^7.23.4":
version: 7.23.4
resolution: "@babel/traverse@npm:7.23.4"
@@ -270,6 +353,17 @@ __metadata:
languageName: node
linkType: hard
"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.7, @babel/types@npm:^7.23.5":
version: 7.23.5
resolution: "@babel/types@npm:7.23.5"
dependencies:
"@babel/helper-string-parser": ^7.23.4
"@babel/helper-validator-identifier": ^7.22.20
to-fast-properties: ^2.0.0
checksum: 7dd5e2f59828ed046ad0b06b039df2524a8b728d204affb4fc08da2502b9dd3140b1356b5166515d229dc811539a8b70dcd4bc507e06d62a89f4091a38d0b0fb
languageName: node
linkType: hard
"@babel/types@npm:^7.22.15, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0, @babel/types@npm:^7.23.3, @babel/types@npm:^7.23.4, @babel/types@npm:^7.8.3":
version: 7.23.4
resolution: "@babel/types@npm:7.23.4"
@@ -900,6 +994,54 @@ __metadata:
languageName: node
linkType: hard
"@types/babel__core@npm:^7.18.0":
version: 7.20.5
resolution: "@types/babel__core@npm:7.20.5"
dependencies:
"@babel/parser": ^7.20.7
"@babel/types": ^7.20.7
"@types/babel__generator": "*"
"@types/babel__template": "*"
"@types/babel__traverse": "*"
checksum: bdee3bb69951e833a4b811b8ee9356b69a61ed5b7a23e1a081ec9249769117fa83aaaf023bb06562a038eb5845155ff663e2d5c75dd95c1d5ccc91db012868ff
languageName: node
linkType: hard
"@types/babel__generator@npm:*":
version: 7.6.7
resolution: "@types/babel__generator@npm:7.6.7"
dependencies:
"@babel/types": ^7.0.0
checksum: 2427203864ef231857e102eeb32b731a419164863983119cdd4dac9f1503c2831eb4262d05ade95d4574aa410b94c16e54e36a616758452f685a34881f4596d9
languageName: node
linkType: hard
"@types/babel__template@npm:*":
version: 7.4.4
resolution: "@types/babel__template@npm:7.4.4"
dependencies:
"@babel/parser": ^7.1.0
"@babel/types": ^7.0.0
checksum: cc84f6c6ab1eab1427e90dd2b76ccee65ce940b778a9a67be2c8c39e1994e6f5bbc8efa309f6cea8dc6754994524cd4d2896558df76d92e7a1f46ecffee7112b
languageName: node
linkType: hard
"@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.18.0":
version: 7.20.4
resolution: "@types/babel__traverse@npm:7.20.4"
dependencies:
"@babel/types": ^7.20.7
checksum: e76cb4974c7740fd61311152dc497e7b05c1c46ba554aab875544ab0a7457f343cafcad34ba8fb2ff543ab0e012ef2d3fa0c13f1a4e9a4cd9c4c703c7a2a8d62
languageName: node
linkType: hard
"@types/doctrine@npm:^0.0.9":
version: 0.0.9
resolution: "@types/doctrine@npm:0.0.9"
checksum: cdaca493f13c321cf0cacd1973efc0ae74569633145d9e6fc1128f32217a6968c33bea1f858275239fe90c98f3be57ec8f452b416a9ff48b8e8c1098b20fa51c
languageName: node
linkType: hard
"@types/json-schema@npm:^7.0.12":
version: 7.0.15
resolution: "@types/json-schema@npm:7.0.15"
@@ -930,6 +1072,13 @@ __metadata:
languageName: node
linkType: hard
"@types/resolve@npm:^1.20.2":
version: 1.20.6
resolution: "@types/resolve@npm:1.20.6"
checksum: a9b0549d816ff2c353077365d865a33655a141d066d0f5a3ba6fd4b28bc2f4188a510079f7c1f715b3e7af505a27374adce2a5140a3ece2a059aab3d6e1a4244
languageName: node
linkType: hard
"@types/semver@npm:^7.5.0":
version: 7.5.6
resolution: "@types/semver@npm:7.5.6"
@@ -2911,6 +3060,13 @@ __metadata:
languageName: node
linkType: hard
"min-indent@npm:^1.0.1":
version: 1.0.1
resolution: "min-indent@npm:1.0.1"
checksum: 7e207bd5c20401b292de291f02913230cb1163abca162044f7db1d951fa245b174dc00869d40dd9a9f32a885ad6a5f3e767ee104cf278f399cb4e92d3f582d5c
languageName: node
linkType: hard
"minimatch@npm:^3.0.2, minimatch@npm:^3.0.3, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2":
version: 3.1.2
resolution: "minimatch@npm:3.1.2"
@@ -3524,6 +3680,46 @@ __metadata:
languageName: node
linkType: hard
"react-docgen@npm:^7.0.1":
version: 7.0.1
resolution: "react-docgen@npm:7.0.1"
dependencies:
"@babel/core": ^7.18.9
"@babel/traverse": ^7.18.9
"@babel/types": ^7.18.9
"@types/babel__core": ^7.18.0
"@types/babel__traverse": ^7.18.0
"@types/doctrine": ^0.0.9
"@types/resolve": ^1.20.2
doctrine: ^3.0.0
resolve: ^1.22.1
strip-indent: ^4.0.0
checksum: 870c1193211f14497bf7a96137f96840dc058842ca75ff7251d91e88c3c71d7a41d5f1a124cc1b53bfbf1f2b6b58bfccc4dd6e22592814a5155d3894953274be
languageName: node
linkType: hard
"react-docs-generator@workspace:packages/react-docs-generator":
version: 0.0.0-use.local
resolution: "react-docs-generator@workspace:packages/react-docs-generator"
dependencies:
"@types/node": ^20.9.4
chalk: ^5.3.0
commander: ^11.1.0
glob: ^10.3.10
react-docgen: ^7.0.1
resolve: ^1.22.8
ts-node: ^10.9.1
typedoc: ^0.25.4
typedoc-plugin-custom: "*"
typescript: 5.2
utils: "*"
peerDependencies:
typedoc: 0.25.x
bin:
react-docs-generator: dist/index.js
languageName: unknown
linkType: soft
"readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0":
version: 3.6.2
resolution: "readable-stream@npm:3.6.2"
@@ -3608,7 +3804,7 @@ __metadata:
languageName: node
linkType: hard
"resolve@npm:^1.20.0":
"resolve@npm:^1.20.0, resolve@npm:^1.22.1, resolve@npm:^1.22.8":
version: 1.22.8
resolution: "resolve@npm:1.22.8"
dependencies:
@@ -3621,7 +3817,7 @@ __metadata:
languageName: node
linkType: hard
"resolve@patch:resolve@^1.20.0#~builtin<compat/resolve>":
"resolve@patch:resolve@^1.20.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.22.1#~builtin<compat/resolve>, resolve@patch:resolve@^1.22.8#~builtin<compat/resolve>":
version: 1.22.8
resolution: "resolve@patch:resolve@npm%3A1.22.8#~builtin<compat/resolve>::version=1.22.8&hash=07638b"
dependencies:
@@ -3719,7 +3915,7 @@ __metadata:
glob: ^10.3.10
randomcolor: ^0.6.2
ts-node: ^10.9.1
typedoc: 0.25.1
typedoc: ^0.25.4
typedoc-config: "*"
typedoc-monorepo-link-types: ^0.0.2
typedoc-plugin-custom: "*"
@@ -3917,6 +4113,15 @@ __metadata:
languageName: node
linkType: hard
"strip-indent@npm:^4.0.0":
version: 4.0.0
resolution: "strip-indent@npm:4.0.0"
dependencies:
min-indent: ^1.0.1
checksum: 6b1fb4e22056867f5c9e7a6f3f45922d9a2436cac758607d58aeaac0d3b16ec40b1c43317de7900f1b8dd7a4107352fa47fb960f2c23566538c51e8585c8870e
languageName: node
linkType: hard
"strip-json-comments@npm:^3.1.1":
version: 3.1.1
resolution: "strip-json-comments@npm:3.1.1"
@@ -4323,19 +4528,19 @@ __metadata:
languageName: node
linkType: hard
"typedoc@npm:0.25.1":
version: 0.25.1
resolution: "typedoc@npm:0.25.1"
"typedoc@npm:^0.25.4":
version: 0.25.4
resolution: "typedoc@npm:0.25.4"
dependencies:
lunr: ^2.3.9
marked: ^4.3.0
minimatch: ^9.0.3
shiki: ^0.14.1
peerDependencies:
typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x
typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x
bin:
typedoc: bin/typedoc
checksum: 11d91302378d618ce451cc5857fcfd4d7923a20075c15ef7b7be663f827d3c2e01260b23ddb97d0d2d47b414b5ed3953a8fabc1adcb582c093d667cf704b5e48
checksum: 2790b3f16b26a477cfc9b72f81b4f209c885c554f9c7b2557d5d85ea4c8bd6a51584af8d29eec123a26398c4986f52b0c3c7a9c32352a223c33a96bd211f288f
languageName: node
linkType: hard
@@ -52,12 +52,34 @@ interface AvatarProps
fallback: string
}
/**
* This component is based on the [Radix UI Avatar](https://www.radix-ui.com/primitives/docs/components/avatar) primitive.
*/
const Avatar = React.forwardRef<
React.ElementRef<typeof Primitives.Root>,
AvatarProps
>(
(
{ src, fallback, variant = "rounded", size = "base", className, ...props },
{
/**
* The URL of the image used in the Avatar.
*/
src,
/**
* Text to show in the avatar if the URL provided in `src` can't be opened.
*/
fallback,
/**
* The style of the avatar.
*/
variant = "rounded",
/**
* The size of the avatar's border radius.
*/
size = "base",
className,
...props
}: AvatarProps,
ref
) => {
return (
@@ -49,16 +49,32 @@ interface BadgeProps
asChild?: boolean
}
/**
* This component is based on the `div` element and supports all of its props
*/
const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(
(
{
className,
/**
* The badge's size.
*/
size = "base",
/**
* The style of the badge's border radius.
*/
rounded = "base",
/**
* The badge's color.
*/
color = "grey",
/**
* Whether to remove the wrapper `span` element and use the
* passed child element instead.
*/
asChild = false,
...props
},
}: BadgeProps,
ref
) => {
const Component = asChild ? Slot : "span"
@@ -59,18 +59,34 @@ interface ButtonProps
asChild?: boolean
}
/**
* This component is based on the `button` element and supports all of its props
*/
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
(
{
/**
* The button's style.
*/
variant = "primary",
/**
* The button's size.
*/
size = "base",
className,
/**
* Whether to remove the wrapper `button` element and use the
* passed child element instead.
*/
asChild = false,
children,
/**
* Whether to show a loading spinner.
*/
isLoading = false,
disabled,
...props
},
}: ButtonProps,
ref
) => {
const Component = asChild ? Slot : "button"
@@ -22,6 +22,9 @@ type KeysToOmit = "showWeekNumber" | "captionLayout" | "mode"
type SingleProps = OmitKeys<DayPickerSingleProps, KeysToOmit>
type RangeProps = OmitKeys<DayPickerRangeProps, KeysToOmit>
/**
* @interface
*/
type CalendarProps =
| ({
mode: "single"
@@ -33,10 +36,29 @@ type CalendarProps =
mode: "range"
} & RangeProps)
/**
* This component is based on the [react-date-picker](https://www.npmjs.com/package/react-date-picker) package.
*
* @excludeExternal
*/
const Calendar = ({
/**
* @ignore
*/
className,
/**
* @ignore
*/
classNames,
/**
* The calendar's mode.
*/
mode = "single",
/**
* Whether to show days of previous and next months.
*
* @keep
*/
showOutsideDays = true,
...props
}: CalendarProps) => {
@@ -6,6 +6,9 @@ import * as React from "react"
import { clx } from "@/utils/clx"
/**
* This component is based on the [Radix UI Checkbox](https://www.radix-ui.com/primitives/docs/components/checkbox) primitive.
*/
const Checkbox = React.forwardRef<
React.ElementRef<typeof Primitives.Root>,
React.ComponentPropsWithoutRef<typeof Primitives.Root>
@@ -6,10 +6,25 @@ import { Copy } from "@/components/copy"
import { clx } from "@/utils/clx"
export type CodeSnippet = {
/**
* The label of the code snippet's tab.
*/
label: string
/**
* The language of the code snippet. For example, `tsx`.
*/
language: string
/**
* The code snippet.
*/
code: string
/**
* Whether to hide the line numbers shown as the side of the code snippet.
*/
hideLineNumbers?: boolean
/**
* Whether to hide the copy button.
*/
hideCopy?: boolean
}
@@ -36,7 +51,13 @@ type RootProps = {
snippets: CodeSnippet[]
}
/**
* This component is based on the `div` element and supports all of its props
*/
const Root = ({
/**
* The code snippets.
*/
snippets,
className,
children,
@@ -58,14 +79,21 @@ const Root = ({
</CodeBlockContext.Provider>
)
}
Root.displayName = "CodeBlock"
type HeaderProps = {
hideLabels?: boolean
}
/**
* This component is based on the `div` element and supports all of its props
*/
const HeaderComponent = ({
children,
className,
/**
* Whether to hide the code snippets' labels.
*/
hideLabels = false,
...props
}: React.HTMLAttributes<HTMLDivElement> & HeaderProps) => {
@@ -98,7 +126,11 @@ const HeaderComponent = ({
</div>
)
}
HeaderComponent.displayName = "CodeBlock.Header"
/**
* This component is based on the `div` element and supports all of its props
*/
const Meta = ({
className,
...props
@@ -110,9 +142,13 @@ const Meta = ({
/>
)
}
Meta.displayName = "CodeBlock.Header.Meta"
const Header = Object.assign(HeaderComponent, { Meta })
/**
* This component is based on the `div` element and supports all of its props
*/
const Body = ({
className,
...props
@@ -240,6 +276,7 @@ const Body = ({
</div>
)
}
Body.displayName = "CodeBlock.Body"
const CodeBlock = Object.assign(Root, { Body, Header, Meta })
@@ -1,6 +1,9 @@
import { clx } from "@/utils/clx"
import * as React from "react"
/**
* This component is based on the `code` element and supports all of its props
*/
const Code = React.forwardRef<
HTMLElement,
React.ComponentPropsWithoutRef<"code">
@@ -7,17 +7,32 @@ import * as React from "react"
import { Kbd } from "@/components/kbd"
import { clx } from "@/utils/clx"
type CommandBarProps = React.PropsWithChildren<{
interface CommandBarProps extends React.PropsWithChildren {
open?: boolean
onOpenChange?: (open: boolean) => void
defaultOpen?: boolean
disableAutoFocus?: boolean
}>
}
/**
* The root component of the command bar. This component manages the state of the command bar.
*/
const Root = ({
/**
* Whether to open (show) the command bar.
*/
open = false,
/**
* Specify a function to handle the change of `open`'s value.
*/
onOpenChange,
/**
* Whether the command bar is open by default.
*/
defaultOpen = false,
/**
* Whether to disable focusing automatically on the command bar when it's opened.
*/
disableAutoFocus = true,
children,
}: CommandBarProps) => {
@@ -53,6 +68,10 @@ const Root = ({
}
Root.displayName = "CommandBar"
/**
* The value component of the command bar. This component is used to display a value,
* such as the number of selected items which the commands will act on.
*/
const Value = React.forwardRef<
HTMLDivElement,
React.ComponentPropsWithoutRef<"div">
@@ -70,6 +89,9 @@ const Value = React.forwardRef<
})
Value.displayName = "CommandBar.Value"
/**
* The bar component of the command bar. This component is used to display the commands.
*/
const Bar = React.forwardRef<
HTMLDivElement,
React.ComponentPropsWithoutRef<"div">
@@ -88,6 +110,9 @@ const Bar = React.forwardRef<
})
Bar.displayName = "CommandBar.Bar"
/**
* The seperator component of the command bar. This component is used to display a seperator between commands.
*/
const Seperator = React.forwardRef<
HTMLDivElement,
Omit<React.ComponentPropsWithoutRef<"div">, "children">
@@ -112,9 +137,29 @@ interface CommandProps
shortcut: string
}
/**
* The command component of the command bar. This component is used to display a command, as well as registering the keyboad shortcut.
*/
const Command = React.forwardRef<HTMLButtonElement, CommandProps>(
(
{ className, type = "button", label, action, shortcut, disabled, ...props },
{
className,
type = "button",
/**
* The command's label.
*/
label,
/**
* The function to execute when the command is triggered.
*/
action,
/**
* The command's shortcut
*/
shortcut,
disabled,
...props
}: CommandProps,
ref
) => {
React.useEffect(() => {
@@ -4,6 +4,9 @@ import { Copy } from "@/components/copy"
import { clx } from "@/utils/clx"
import React from "react"
/**
* This component is based on the div element and supports all of its props
*/
const CommandComponent = ({
className,
...props
@@ -19,6 +22,7 @@ const CommandComponent = ({
/>
)
}
CommandComponent.displayName = "Command"
const Command = Object.assign(CommandComponent, { Copy })
@@ -2,6 +2,9 @@ import * as React from "react"
import { clx } from "@/utils/clx"
/**
* This component is based on the `div` element and supports all of its props
*/
const Container = React.forwardRef<
HTMLDivElement,
React.ComponentPropsWithoutRef<"div">
@@ -7,15 +7,31 @@ import { Slot } from "@radix-ui/react-slot"
import copy from "copy-to-clipboard"
import React, { useState } from "react"
type CopyProps = {
type CopyProps = React.HTMLAttributes<HTMLButtonElement> & {
content: string
asChild?: boolean
}
/**
* This component is based on the `button` element and supports all of its props
*/
const Copy = React.forwardRef<
HTMLButtonElement,
React.HTMLAttributes<HTMLButtonElement> & CopyProps
>(({ children, className, content, asChild = false, ...props }, ref) => {
CopyProps
>(({
children,
className,
/**
* The content to copy.
*/
content,
/**
* Whether to remove the wrapper `button` element and use the
* passed child element instead.
*/
asChild = false,
...props
}: CopyProps, ref) => {
const [done, setDone] = useState(false)
const [open, setOpen] = useState(false)
const [text, setText] = useState("Copy")
@@ -34,9 +34,31 @@ interface CurrencyInputProps
code: string
}
/**
* This component is based on the input element and supports all of its props
*
* @excludeExternal
*/
const CurrencyInput = React.forwardRef<HTMLInputElement, CurrencyInputProps>(
(
{ size = "base", symbol, code, disabled, onInvalid, className, ...props },
{
/**
* The input's size.
*/
size = "base",
/**
* The symbol to show in the input.
*/
symbol,
/**
* The currency code to show in the input.
*/
code,
disabled,
onInvalid,
className,
...props
}: CurrencyInputProps,
ref
) => {
const innerRef = React.useRef<HTMLInputElement>(null)
@@ -34,13 +34,27 @@ const displayVariants = cva({
},
})
interface DisplayProps extends React.ComponentProps<"button"> {
placeholder?: string
size?: "small" | "base"
}
const Display = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<"button"> & {
placeholder?: string
size?: "small" | "base"
}
>(({ className, children, placeholder, size = "base", ...props }, ref) => {
DisplayProps
>(({
className,
children,
/**
* Placeholder of the date picker's input.
*/
placeholder,
/**
* The size of the date picker's input.
*/
size = "base",
...props
}: DisplayProps, ref) => {
return (
<Primitives.Trigger asChild>
<button
@@ -88,14 +102,23 @@ const Flyout = React.forwardRef<
Flyout.displayName = "DatePicker.Flyout"
interface Preset {
/**
* The preset's label.
*/
label: string
}
interface DatePreset extends Preset {
/**
* The preset's selected date.
*/
date: Date
}
interface DateRangePreset extends Preset {
/**
* The preset's selected date range.
*/
dateRange: DateRange
}
@@ -106,8 +129,17 @@ type PresetContainerProps<TPreset extends Preset, TValue> = {
}
const PresetContainer = <TPreset extends Preset, TValue>({
/**
* Selectable preset configurations.
*/
presets,
/**
* A function that handles the event when a preset is selected.
*/
onSelect,
/**
* The currently selected preset.
*/
currentValue,
}: PresetContainerProps<TPreset, TValue>) => {
const isDateRangePresets = (preset: any): preset is DateRangePreset => {
@@ -203,6 +235,7 @@ const PresetContainer = <TPreset extends Preset, TValue>({
</ul>
)
}
PresetContainer.displayName = "DatePicker.PresetContainer"
const formatDate = (date: Date, includeTime?: boolean) => {
const usesAmPm = !isBrowserLocaleClockType24h()
@@ -219,21 +252,60 @@ const formatDate = (date: Date, includeTime?: boolean) => {
}
type CalendarProps = {
/**
* The year to start showing the dates from.
*/
fromYear?: number
/**
* The year to show dates to.
*/
toYear?: number
/**
* The month to start showing dates from.
*/
fromMonth?: Date
/**
* The month to show dates to.
*/
toMonth?: Date
/**
* The day to start showing dates from.
*/
fromDay?: Date
/**
* The day to show dates to.
*/
toDay?: Date
/**
* The date to show dates from.
*/
fromDate?: Date
/**
* The date to show dates to.
*/
toDate?: Date
}
interface PickerProps extends CalendarProps {
/**
* The class name to apply on the date picker.
*/
className?: string
/**
* Whether the date picker's input is disabled.
*/
disabled?: boolean
/**
* Whether the date picker's input is required.
*/
required?: boolean
/**
* The date picker's size.
*/
size?: "small" | "base"
/**
* Whether to show a time picker along with the date picker.
*/
showTimePicker?: boolean
id?: string
"aria-invalid"?: boolean
@@ -466,11 +538,23 @@ interface RangeProps extends PickerProps {
}
const RangeDatePicker = ({
/**
* The date range selected by default.
*/
defaultValue,
/**
* The selected date range.
*/
value,
/**
* A function to handle the change in the selected date range.
*/
onChange,
size = "base",
showTimePicker,
/**
* Provide selectable preset configurations.
*/
presets,
disabled,
className,
@@ -732,6 +816,14 @@ const RangeDatePicker = ({
)
}
/**
* @interface
*
* @prop presets - Provide selectable preset configurations.
* @prop defaultValue - The date selected by default.
* @prop value - The selected date.
* @prop onChange - A function to handle the change in the selected date.
*/
type DatePickerProps = (
| {
mode?: "single"
@@ -891,7 +983,17 @@ const validatePresets = (
}
}
const DatePicker = ({ mode = "single", ...props }: DatePickerProps) => {
/**
* This component is based on the [Calendar](https://docs.medusajs.com/ui/components/calendar)
* component and [Radix UI Popover](https://www.radix-ui.com/primitives/docs/components/popover).
*/
const DatePicker = ({
/**
* The date picker's mode.
*/
mode = "single",
...props
}: DatePickerProps) => {
if (props.presets) {
validatePresets(props.presets, props)
}
@@ -10,12 +10,15 @@ import { Kbd } from "@/components/kbd"
import { Text } from "@/components/text"
import { clx } from "@/utils/clx"
/**
* This component is based on the [Radix UI Dialog](https://www.radix-ui.com/primitives/docs/components/dialog) primitives.
*/
const DrawerRoot = (
props: React.ComponentPropsWithoutRef<typeof DrawerPrimitives.Root>
) => {
return <DrawerPrimitives.Root {...props} />
}
DrawerRoot.displayName = "Drawer.Root"
DrawerRoot.displayName = "Drawer"
const DrawerTrigger = React.forwardRef<
React.ElementRef<typeof DrawerPrimitives.Trigger>,
@@ -6,21 +6,39 @@ import * as React from "react"
import { clx } from "@/utils/clx"
/**
* This component is based on the [Radix UI Dropdown Menu](https://www.radix-ui.com/primitives/docs/components/dropdown-menu) primitive.
*/
const Root = Primitives.Root
Root.displayName = "DropdownMenu.Root"
Root.displayName = "DropdownMenu"
/**
* This component is based on the [Radix UI Dropdown Menu Trigger](https://www.radix-ui.com/primitives/docs/components/dropdown-menu#trigger) primitive.
*/
const Trigger = Primitives.Trigger
Trigger.displayName = "DropdownMenu.Trigger"
/**
* This component is based on the [Radix UI Dropdown Menu Group](https://www.radix-ui.com/primitives/docs/components/dropdown-menu#group) primitive.
*/
const Group = Primitives.Group
Group.displayName = "DropdownMenu.Group"
/**
* This component is based on the [Radix UI Dropdown Menu Sub](https://www.radix-ui.com/primitives/docs/components/dropdown-menu#sub) primitive.
*/
const SubMenu = Primitives.Sub
SubMenu.displayName = "DropdownMenu.SubMenu"
/**
* This component is based on the [Radix UI Dropdown Menu RadioGroup](https://www.radix-ui.com/primitives/docs/components/dropdown-menu#radiogroup) primitive.
*/
const RadioGroup = Primitives.RadioGroup
RadioGroup.displayName = "DropdownMenu.RadioGroup"
/**
* This component is based on the [Radix UI Dropdown Menu SubTrigger](https://www.radix-ui.com/primitives/docs/components/dropdown-menu#subtrigger) primitive.
*/
const SubMenuTrigger = React.forwardRef<
React.ElementRef<typeof Primitives.SubTrigger>,
React.ComponentPropsWithoutRef<typeof Primitives.SubTrigger>
@@ -39,6 +57,9 @@ const SubMenuTrigger = React.forwardRef<
))
SubMenuTrigger.displayName = "DropdownMenu.SubMenuTrigger"
/**
* This component is based on the [Radix UI Dropdown Menu SubContent](https://www.radix-ui.com/primitives/docs/components/dropdown-menu#subcontent) primitive.
*/
const SubMenuContent = React.forwardRef<
React.ElementRef<typeof Primitives.SubContent>,
React.ComponentPropsWithoutRef<typeof Primitives.SubContent>
@@ -58,6 +79,9 @@ const SubMenuContent = React.forwardRef<
))
SubMenuContent.displayName = "DropdownMenu.SubMenuContent"
/**
* This component is based on the [Radix UI Dropdown Menu Content](https://www.radix-ui.com/primitives/docs/components/dropdown-menu#content) primitive.
*/
const Content = React.forwardRef<
React.ElementRef<typeof Primitives.Content>,
React.ComponentPropsWithoutRef<typeof Primitives.Content>
@@ -90,6 +114,9 @@ const Content = React.forwardRef<
)
Content.displayName = "DropdownMenu.Content"
/**
* This component is based on the [Radix UI Dropdown Menu Item](https://www.radix-ui.com/primitives/docs/components/dropdown-menu#item) primitive.
*/
const Item = React.forwardRef<
React.ElementRef<typeof Primitives.Item>,
React.ComponentPropsWithoutRef<typeof Primitives.Item>
@@ -105,6 +132,9 @@ const Item = React.forwardRef<
))
Item.displayName = "DropdownMenu.Item"
/**
* This component is based on the [Radix UI Dropdown Menu CheckboxItem](https://www.radix-ui.com/primitives/docs/components/dropdown-menu#checkboxitem) primitive.
*/
const CheckboxItem = React.forwardRef<
React.ElementRef<typeof Primitives.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof Primitives.CheckboxItem>
@@ -128,6 +158,9 @@ const CheckboxItem = React.forwardRef<
))
CheckboxItem.displayName = "DropdownMenu.CheckboxItem"
/**
* This component is based on the [Radix UI Dropdown Menu RadioItem](https://www.radix-ui.com/primitives/docs/components/dropdown-menu#radioitem) primitive.
*/
const RadioItem = React.forwardRef<
React.ElementRef<typeof Primitives.RadioItem>,
React.ComponentPropsWithoutRef<typeof Primitives.RadioItem>
@@ -150,6 +183,9 @@ const RadioItem = React.forwardRef<
))
RadioItem.displayName = "DropdownMenu.RadioItem"
/**
* This component is based on the [Radix UI Dropdown Menu Label](https://www.radix-ui.com/primitives/docs/components/dropdown-menu#label) primitive.
*/
const Label = React.forwardRef<
React.ElementRef<typeof Primitives.Label>,
React.ComponentPropsWithoutRef<typeof Primitives.Label>
@@ -165,6 +201,9 @@ const Label = React.forwardRef<
))
Label.displayName = "DropdownMenu.Label"
/**
* This component is based on the [Radix UI Dropdown Menu Separator](https://www.radix-ui.com/primitives/docs/components/dropdown-menu#separator) primitive.
*/
const Separator = React.forwardRef<
React.ElementRef<typeof Primitives.Separator>,
React.ComponentPropsWithoutRef<typeof Primitives.Separator>
@@ -177,6 +216,9 @@ const Separator = React.forwardRef<
))
Separator.displayName = "DropdownMenu.Separator"
/**
* This component is based on the `span` element and supports all of its props
*/
const Shortcut = ({
className,
...props
@@ -193,6 +235,9 @@ const Shortcut = ({
}
Shortcut.displayName = "DropdownMenu.Shortcut"
/**
* This component is based on the `span` element and supports all of its props
*/
const Hint = ({
className,
...props
@@ -8,8 +8,20 @@ import { IconButton } from "@/components/icon-button"
import { Kbd } from "@/components/kbd"
import { clx } from "@/utils/clx"
/**
* @prop defaultOpen - Whether the modal is opened by default.
* @prop open - Whether the modal is opened.
* @prop onOpenChange - A function to handle when the modal is opened or closed.
*/
interface FocusModalRootProps
extends React.ComponentPropsWithoutRef<typeof FocusModalPrimitives.Root> {
}
/**
* This component is based on the [Radix UI Dialog](https://www.radix-ui.com/primitives/docs/components/dialog) primitives.
*/
const FocusModalRoot = (
props: React.ComponentPropsWithoutRef<typeof FocusModalPrimitives.Root>
props: FocusModalRootProps
) => {
return <FocusModalPrimitives.Root {...props} />
}
@@ -72,6 +84,9 @@ const FocusModalContent = React.forwardRef<
})
FocusModalContent.displayName = "FocusModal.Content"
/**
* This component is based on the `div` element and supports all of its props
*/
const FocusModalHeader = React.forwardRef<
HTMLDivElement,
React.ComponentPropsWithoutRef<"div">
@@ -99,6 +114,9 @@ const FocusModalHeader = React.forwardRef<
})
FocusModalHeader.displayName = "FocusModal.Header"
/**
* This component is based on the `div` element and supports all of its props
*/
const FocusModalBody = React.forwardRef<
HTMLDivElement,
React.ComponentPropsWithoutRef<"div">
@@ -17,11 +17,22 @@ const headingVariants = cva({
},
})
type HeadingProps = VariantProps<typeof headingVariants> &
React.HTMLAttributes<HTMLHeadingElement>
interface HeadingProps extends VariantProps<typeof headingVariants>,
React.HTMLAttributes<HTMLHeadingElement> {}
const Heading = ({ level, className, ...props }: HeadingProps) => {
const Component = level ? level : "h1"
/**
* This component is based on the heading element (`h1`, `h2`, etc...) depeneding on the specified level
* and supports all of its props
*/
const Heading = ({
/**
* The heading level which specifies which heading element is used.
*/
level = "h1",
className,
...props
}: HeadingProps) => {
const Component = level || "h1"
return (
<Component
@@ -17,11 +17,19 @@ const hintVariants = cva({
},
})
type HintProps = VariantProps<typeof hintVariants> &
React.ComponentPropsWithoutRef<"span">
interface HintProps extends VariantProps<typeof hintVariants>,
React.ComponentPropsWithoutRef<"span"> {}
const Hint = React.forwardRef<HTMLSpanElement, HintProps>(
({ className, variant = "info", children, ...props }, ref) => {
({
className,
/**
* The hint's style.
*/
variant = "info",
children,
...props
}: HintProps, ref) => {
return (
<span
ref={ref}
@@ -22,16 +22,29 @@ interface IconBadgeProps
asChild?: boolean
}
/**
* This component is based on the `span` element and supports all of its props
*/
const IconBadge = React.forwardRef<HTMLSpanElement, IconBadgeProps>(
(
{
children,
className,
/**
* The badge's color.
*/
color = "grey",
/**
* The badge's size.
*/
size = "base",
/**
* Whether to remove the wrapper `span` element and use the
* passed child element instead.
*/
asChild = false,
...props
},
}: IconBadgeProps,
ref
) => {
const Component = asChild ? Slot : "span"
@@ -46,18 +46,34 @@ interface IconButtonProps
isLoading?: boolean
}
/**
* This component is based on the `button` element and supports all of its props
*/
const IconButton = React.forwardRef<HTMLButtonElement, IconButtonProps>(
(
{
/**
* The button's style.
*/
variant = "primary",
/**
* The button's size.
*/
size = "base",
/**
* Whether to remove the wrapper `button` element and use the
* passed child element instead.
*/
asChild = false,
className,
children,
/**
* Whether to show a loading spinner.
*/
isLoading = false,
disabled,
...props
},
}: IconButtonProps,
ref
) => {
const Component = asChild ? Slot : "button"
@@ -29,11 +29,24 @@ const inputVariants = cva({
},
})
interface InputProps extends VariantProps<typeof inputVariants>,
Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> {}
/**
* This component is based on the `input` element and supports all of its props
*/
const Input = React.forwardRef<
HTMLInputElement,
VariantProps<typeof inputVariants> &
Omit<React.InputHTMLAttributes<HTMLInputElement>, "size">
>(({ className, type, size = "base", ...props }, ref) => {
InputProps
>(({
className,
type,
/**
* The input's size.
*/
size = "base",
...props
}: InputProps, ref) => {
const [typeState, setTypeState] = React.useState(type)
const isPassword = type === "password"
@@ -2,6 +2,9 @@ import * as React from "react"
import { clx } from "@/utils/clx"
/**
* This component is based on the `kbd` element and supports all of its props
*/
const Kbd = React.forwardRef<
HTMLElement,
React.ComponentPropsWithoutRef<"kbd">
@@ -30,8 +30,22 @@ interface LabelProps
extends React.ComponentPropsWithoutRef<"label">,
VariantProps<typeof labelVariants> {}
/**
* This component is based on the [Radix UI Label](https://www.radix-ui.com/primitives/docs/components/label) primitive.
*/
const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
({ className, size = "base", weight = "regular", ...props }, ref) => {
({
className,
/**
* The label's size.
*/
size = "base",
/**
* The label's font weight.
*/
weight = "regular",
...props
}: LabelProps, ref) => {
return (
<Primitives.Root
ref={ref}
@@ -3,6 +3,9 @@ import * as React from "react"
import { clx } from "@/utils/clx"
/**
* This component is based on the [Radix UI Popover](https://www.radix-ui.com/primitives/docs/components/popover) primitves.
*/
const Root = (
props: React.ComponentPropsWithoutRef<typeof Primitives.Root>
) => {
@@ -34,19 +37,34 @@ const Close = React.forwardRef<
})
Close.displayName = "Popover.Close"
interface ContentProps extends React.ComponentPropsWithoutRef<typeof Primitives.Content> {}
/**
* @excludeExternal
*/
const Content = React.forwardRef<
React.ElementRef<typeof Primitives.Content>,
React.ComponentPropsWithoutRef<typeof Primitives.Content>
ContentProps
>(
(
{
className,
/**
* The distance in pixels from the anchor.
*/
sideOffset = 8,
/**
* The preferred side of the anchor to render against when open.
* Will be reversed when collisions occur and `avoidCollisions` is enabled.
*/
side = "bottom",
/**
* The preferred alignment against the anchor. May change when collisions occur.
*/
align = "start",
collisionPadding,
...props
},
}: ContentProps,
ref
) => {
return (
@@ -13,6 +13,9 @@ import { ProgressStatus } from "@/types"
import { clx } from "@/utils/clx"
import { IconButton } from "../icon-button"
/**
* This component is based on the [Radix UI Accordion](https://radix-ui.com/primitives/docs/components/accordion) primitves.
*/
const Root = (props: React.ComponentPropsWithoutRef<typeof Primitves.Root>) => {
return <Primitves.Root {...props} />
}
@@ -44,7 +47,13 @@ interface StatusIndicatorProps extends React.ComponentPropsWithoutRef<"span"> {
status: ProgressStatus
}
const ProgressIndicator = ({ status, ...props }: StatusIndicatorProps) => {
const ProgressIndicator = ({
/**
* The current status.
*/
status,
...props
}: StatusIndicatorProps) => {
const Icon = React.useMemo(() => {
switch (status) {
case "not-started":
@@ -67,29 +76,39 @@ const ProgressIndicator = ({ status, ...props }: StatusIndicatorProps) => {
</span>
)
}
ProgressIndicator.displayName = "ProgressAccordion.ProgressIndicator"
const Header = React.forwardRef<
React.ElementRef<typeof Primitves.Header>,
HeaderProps
>(({ className, status = "not-started", children, ...props }, ref) => {
return (
<Primitves.Header
ref={ref}
className={clx(
"h3-core text-ui-fg-base group flex w-full flex-1 items-center gap-4 px-8",
className
)}
{...props}
>
<ProgressIndicator status={status} />
{children}
<Primitves.Trigger asChild className="ml-auto">
<IconButton variant="transparent">
<Plus className="transform transition-transform group-data-[state=open]:rotate-45" />
</IconButton>
</Primitves.Trigger>
</Primitves.Header>
)
>((
{
className,
/**
* The current status.
*/
status = "not-started",
children,
...props
}: HeaderProps, ref) => {
return (
<Primitves.Header
ref={ref}
className={clx(
"h3-core text-ui-fg-base group flex w-full flex-1 items-center gap-4 px-8",
className
)}
{...props}
>
<ProgressIndicator status={status} />
{children}
<Primitves.Trigger asChild className="ml-auto">
<IconButton variant="transparent">
<Plus className="transform transition-transform group-data-[state=open]:rotate-45" />
</IconButton>
</Primitves.Trigger>
</Primitves.Header>
)
})
Header.displayName = "ProgressAccordion.Header"
@@ -11,6 +11,11 @@ import * as React from "react"
import { ProgressStatus } from "@/types"
import { clx } from "@/utils/clx"
/**
* This component is based on the [Radix UI Tabs](https://radix-ui.com/primitives/docs/components/tabs) primitves.
*
* @excludeExternal
*/
const ProgressTabsRoot = (props: ProgressTabsPrimitives.TabsProps) => {
return <ProgressTabsPrimitives.Root {...props} />
}
@@ -18,10 +23,17 @@ ProgressTabsRoot.displayName = "ProgressTabs"
interface IndicatorProps
extends Omit<React.ComponentPropsWithoutRef<"span">, "children"> {
/**
* The current status.
*/
status?: ProgressStatus
}
const ProgressIndicator = ({ status, className, ...props }: IndicatorProps) => {
const ProgressIndicator = ({
status,
className,
...props
}: IndicatorProps) => {
const Icon = React.useMemo(() => {
switch (status) {
case "not-started":
@@ -47,6 +59,7 @@ const ProgressIndicator = ({ status, className, ...props }: IndicatorProps) => {
</span>
)
}
ProgressIndicator.displayName = "ProgressTabs.ProgressIndicator"
interface ProgressTabsTriggerProps
extends Omit<
@@ -59,7 +72,7 @@ interface ProgressTabsTriggerProps
const ProgressTabsTrigger = React.forwardRef<
React.ElementRef<typeof ProgressTabsPrimitives.Trigger>,
ProgressTabsTriggerProps
>(({ className, children, status = "not-started", ...props }, ref) => (
>(({ className, children, status = "not-started", ...props }: ProgressTabsTriggerProps, ref) => (
<ProgressTabsPrimitives.Trigger
ref={ref}
className={clx(
@@ -7,8 +7,11 @@ import { Button } from "@/components/button"
import { Heading } from "@/components/heading"
import { clx } from "@/utils/clx"
/**
* This component is based on the [Radix UI Alert Dialog](https://www.radix-ui.com/primitives/docs/components/alert-dialog) primitives.
*/
const Root = Primitives.AlertDialog
Root.displayName = "Prompt.Root"
Root.displayName = "Prompt"
const Trigger = Primitives.Trigger
Trigger.displayName = "Prompt.Trigger"
@@ -111,6 +114,9 @@ const Cancel = React.forwardRef<
})
Cancel.displayName = "Prompt.Cancel"
/**
* This component is based on the `div` element and supports all of its props
*/
const Header = ({
className,
...props
@@ -122,7 +128,11 @@ const Header = ({
/>
)
}
Header.displayName = "Prompt.Header"
/**
* This component is based on the `div` element and supports all of its props
*/
const Footer = ({
className,
...props
@@ -134,6 +144,7 @@ const Footer = ({
/>
)
}
Footer.displayName = "Prompt.Footer"
const Prompt = Object.assign(Root, {
Trigger,
@@ -7,6 +7,9 @@ import { clx } from "@/utils/clx"
import { Hint } from "../hint"
import { Label } from "../label"
/**
* This component is based on the [Radix UI Radio Group](https://www.radix-ui.com/primitives/docs/components/radio-group) primitives.
*/
const Root = React.forwardRef<
React.ElementRef<typeof Primitives.Root>,
React.ComponentPropsWithoutRef<typeof Primitives.Root>
@@ -19,7 +22,7 @@ const Root = React.forwardRef<
/>
)
})
Root.displayName = "RadioGroup.Root"
Root.displayName = "RadioGroup"
const Indicator = React.forwardRef<
React.ElementRef<typeof Primitives.Indicator>,
@@ -28,17 +28,38 @@ const useSelectContext = () => {
return context
}
const Root = ({ children, size = "base", ...props }: SelectProps) => {
/**
* This component is based on [Radix UI Select](https://www.radix-ui.com/primitives/docs/components/select).
* It also accepts all props of the HTML `select` component.
*/
const Root = ({
children,
/**
* The select's size.
*/
size = "base",
...props
}: SelectProps) => {
return (
<SelectContext.Provider value={React.useMemo(() => ({ size }), [size])}>
<SelectPrimitive.Root {...props}>{children}</SelectPrimitive.Root>
</SelectContext.Provider>
)
}
Root.displayName = "Select"
/**
* Groups multiple items together.
*/
const Group = SelectPrimitive.Group
Group.displayName = "Select.Group"
/**
* Displays the selected value, or a placeholder if no value is selected.
* It's based on [Radix UI Select Value](https://www.radix-ui.com/primitives/docs/components/select#value).
*/
const Value = SelectPrimitive.Value
Value.displayName = "Select.Value"
const triggerVariants = cva({
base: clx(
@@ -59,6 +80,9 @@ const triggerVariants = cva({
},
})
/**
* The trigger that toggles the select.
*/
const Trigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
@@ -78,7 +102,7 @@ const Trigger = React.forwardRef<
</SelectPrimitive.Trigger>
)
})
Trigger.displayName = SelectPrimitive.Trigger.displayName
Trigger.displayName = "Select.Trigger"
const Content = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
@@ -127,8 +151,11 @@ const Content = React.forwardRef<
</SelectPrimitive.Portal>
)
)
Content.displayName = SelectPrimitive.Content.displayName
Content.displayName = "Select.Content"
/**
* Used to label a group of items.
*/
const Label = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
@@ -142,8 +169,12 @@ const Label = React.forwardRef<
{...props}
/>
))
Label.displayName = SelectPrimitive.Label.displayName
Label.displayName = "Select.Label"
/**
* An item in the select. It's based on [Radix UI Select Item](https://www.radix-ui.com/primitives/docs/components/select#item)
* and accepts its props.
*/
const Item = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
@@ -177,7 +208,7 @@ const Item = React.forwardRef<
</SelectPrimitive.Item>
)
})
Item.displayName = SelectPrimitive.Item.displayName
Item.displayName = "Select.Item"
const Separator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
@@ -189,7 +220,7 @@ const Separator = React.forwardRef<
{...props}
/>
))
Separator.displayName = SelectPrimitive.Separator.displayName
Separator.displayName = "Select.Separator"
const Select = Object.assign(Root, {
Group,
@@ -15,8 +15,19 @@ interface StatusBadgeProps
color?: "green" | "red" | "blue" | "orange" | "grey" | "purple"
}
/**
* This component is based on the span element and supports all of its props
*/
const StatusBadge = React.forwardRef<HTMLSpanElement, StatusBadgeProps>(
({ children, className, color = "grey", ...props }, ref) => {
({
children,
className,
/**
* The status's color.
*/
color = "grey",
...props
}: StatusBadgeProps, ref) => {
const StatusIndicator = {
green: EllipseGreenSolid,
red: EllipseRedSolid,
@@ -40,10 +40,20 @@ interface SwitchProps
>,
VariantProps<typeof switchVariants> {}
/**
* This component is based on the [Radix UI Switch](https://www.radix-ui.com/primitives/docs/components/switch) primitive.
*/
const Switch = React.forwardRef<
React.ElementRef<typeof Primitives.Root>,
SwitchProps
>(({ className, size = "base", ...props }, ref) => (
>(({
className,
/**
* The switch's size.
*/
size = "base",
...props
}: SwitchProps, ref) => (
<Primitives.Root
className={clx(switchVariants({ size }), className)}
{...props}
@@ -4,6 +4,18 @@ import * as React from "react"
import { Button } from "@/components/button"
import { clx } from "@/utils/clx"
/**
* This component is based on the table element and its various children:
*
* - `Table`: `table`
* - `Table.Header`: `thead`
* - `Table.Row`: `tr`
* - `Table.HeaderCell`: `th`
* - `Table.Body`: `tbody`
* - `Table.Cell`: `td`
*
* Each component supports the props or attributes of its equivalent HTML element.
*/
const Root = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
@@ -87,20 +99,49 @@ interface TablePaginationProps extends React.HTMLAttributes<HTMLDivElement> {
nextPage: () => void
}
/**
* This component is based on the `div` element and supports all of its props
*/
const Pagination = React.forwardRef<HTMLDivElement, TablePaginationProps>(
(
{
className,
/**
* The total number of items.
*/
count,
/**
* The number of items per page.
*/
pageSize,
/**
* The total number of pages.
*/
pageCount,
/**
* The current page index.
*/
pageIndex,
/**
* Whether there's a previous page that can be navigated to.
*/
canPreviousPage,
/**
* Whether there's a next page that can be navigated to.
*/
canNextPage,
/**
* A function that handles navigating to the next page.
* This function should handle retrieving data for the next page.
*/
nextPage,
/**
* A function that handles navigating to the previous page.
* This function should handle retrieving data for the previous page.
*/
previousPage,
...props
},
}: TablePaginationProps,
ref
) => {
const { from, to } = React.useMemo(() => {
@@ -5,6 +5,9 @@ import * as React from "react"
import { clx } from "@/utils/clx"
/**
* This component is based on the [Radix UI Tabs](https://radix-ui.com/primitives/docs/components/tabs) primitves
*/
const TabsRoot = (
props: React.ComponentPropsWithoutRef<typeof TabsPrimitives.Root>
) => {
@@ -93,19 +93,41 @@ interface TextProps
as?: "p" | "span" | "div"
}
/**
* This component is based on the `p` element and supports all of its props
*/
const Text = React.forwardRef<HTMLParagraphElement, TextProps>(
(
{
className,
/**
* Whether to remove the wrapper `button` element and use the
* passed child element instead.
*/
asChild = false,
/**
* The wrapper element to use when `asChild` is disabled.
*/
as = "p",
/**
* The text's size.
*/
size = "base",
/**
* The text's font weight.
*/
weight = "regular",
/**
* The text's font family.
*/
family = "sans",
/**
* The text's line height.
*/
leading = "normal",
children,
...props
},
}: TextProps,
ref
) => {
const Component = asChild ? Slot : as
@@ -3,6 +3,9 @@ import * as React from "react"
import { clx } from "@/utils/clx"
import { inputBaseStyles } from "../input"
/**
* This component is based on the `textarea` element and supports all of its props
*/
const Textarea = React.forwardRef<
HTMLTextAreaElement,
React.ComponentPropsWithoutRef<"textarea">
@@ -71,8 +71,18 @@ type TimeInputProps = Omit<
"label" | "shouldForceLeadingZeros" | "description" | "errorMessage"
>
/**
* This component is based on the `div` element and supports all of its props.
*/
const TimeInput = React.forwardRef<HTMLDivElement, TimeInputProps>(
({ hourCycle, ...props }: TimeInputProps, ref) => {
({
/**
* The time's format. If no value is specified, the format is
* set based on the user's locale.
*/
hourCycle,
...props
}: TimeInputProps, ref) => {
const innerRef = React.useRef<HTMLDivElement>(null)
React.useImperativeHandle<HTMLDivElement | null, HTMLDivElement | null>(
@@ -28,7 +28,7 @@ const ToastViewport = React.forwardRef<
{...props}
/>
))
ToastViewport.displayName = "ToastViewport"
ToastViewport.displayName = "Toast.Viewport"
interface ActionProps {
label: string
@@ -45,6 +45,11 @@ interface ToastProps
disableDismiss?: boolean
}
/**
* This component is based on the [Radix UI Toast](https://www.radix-ui.com/primitives/docs/components/toast) primitives.
*
* @excludeExternal
*/
const Toast = React.forwardRef<
React.ElementRef<typeof Primitives.Root>,
ToastProps
@@ -52,13 +57,28 @@ const Toast = React.forwardRef<
(
{
className,
/**
* The toast's style.
*/
variant,
/**
* The toast's title.
*/
title,
/**
* The toast's content.
*/
description,
/**
* The actions to show in the toast as buttons.
*/
action,
/**
* Whether to hide the Close button.
*/
disableDismiss = false,
...props
},
}: ToastProps,
ref
) => {
let Icon = undefined
@@ -17,6 +17,11 @@ interface TooltipProps
maxWidth?: number
}
/**
* This component is based on the [Radix UI Tooltip](https://www.radix-ui.com/primitives/docs/components/tooltip) primitive.
*
* @excludeExternal
*/
const Tooltip = ({
children,
content,
@@ -24,6 +29,9 @@ const Tooltip = ({
defaultOpen,
onOpenChange,
delayDuration,
/**
* The maximum width of the tooltip.
*/
maxWidth = 220,
className,
side,
@@ -6,7 +6,7 @@ import { Input } from "@/components/input"
import { Label } from "@/components/label"
import { Prompt } from "@/components/prompt"
export type RenderPromptProps = {
export interface RenderPromptProps {
open: boolean
title: string
description: string
@@ -18,13 +18,37 @@ export type RenderPromptProps = {
}
export const RenderPrompt = ({
/**
* @ignore
*/
open,
/**
* The prompt's title.
*/
title,
/**
* The prompt's description.
*/
description,
/**
* The text the user has to input in order to confirm the action.
*/
verificationText,
/**
* The label for the Cancel button.
*/
cancelText = "Cancel",
/**
* Label for the Confirm button.
*/
confirmText = "Confirm",
/**
* @ignore
*/
onConfirm,
/**
* @ignore
*/
onCancel,
}: RenderPromptProps) => {
const [userInput, setUserInput] = React.useState("")
@@ -108,3 +132,4 @@ export const RenderPrompt = ({
</Prompt>
)
}
RenderPrompt.displayName = "RenderPrompt"
+6
View File
@@ -1,6 +1,12 @@
export type ProgressStatus = "not-started" | "in-progress" | "completed"
export type DateRange = {
/**
* The range's start date.
*/
from: Date | undefined
/**
* The range's end date.
*/
to?: Date | undefined
}
@@ -1,11 +1,15 @@
"use client"
import getSectionId from "@/utils/get-section-id"
import fetcher from "@/utils/swr-fetcher"
import type { OpenAPIV3 } from "openapi-types"
import useSWR from "swr"
import type { Operation, PathsObject } from "@/types/openapi"
import { SidebarItemSections, useSidebar, type SidebarItemType } from "docs-ui"
import {
SidebarItemSections,
useSidebar,
type SidebarItemType,
swrFetcher,
} from "docs-ui"
import { Fragment, useEffect, useMemo } from "react"
import dynamic from "next/dynamic"
import type { TagOperationProps } from "../Operation"
@@ -45,7 +49,7 @@ const TagPaths = ({ tag, className }: TagPathsProps) => {
!Object.keys(paths).length
? getLinkWithBasePath(`/tag?tagName=${tagSlugName}&area=${area}`)
: null,
fetcher,
swrFetcher,
{
errorRetryInterval: 2000,
}
@@ -3,13 +3,12 @@
import type { OpenAPIV3 } from "openapi-types"
import { useEffect, useState } from "react"
import useSWR from "swr"
import fetcher from "@/utils/swr-fetcher"
import { useBaseSpecs } from "@/providers/base-specs"
import dynamic from "next/dynamic"
import type { TagSectionProps } from "./Section"
import { useArea } from "@/providers/area"
import getLinkWithBasePath from "@/utils/get-link-with-base-path"
import { SidebarItemSections, useSidebar } from "docs-ui"
import { SidebarItemSections, swrFetcher, useSidebar } from "docs-ui"
import getSectionId from "@/utils/get-section-id"
import { ExpandedDocument } from "@/types/openapi"
import getTagChildSidebarItems from "@/utils/get-tag-child-sidebar-items"
@@ -38,7 +37,7 @@ const Tags = () => {
loadData && !baseSpecs
? getLinkWithBasePath(`/base-specs?area=${area}&expand=${expand}`)
: null,
fetcher,
swrFetcher,
{
errorRetryInterval: 2000,
}
+1
View File
@@ -45,6 +45,7 @@
},
"devDependencies": {
"eslint": "^8.49.0",
"react-docgen": "^7.0.1",
"ts-node": "^10.9.1"
}
}
@@ -1,48 +0,0 @@
import { Spinner } from "@medusajs/icons"
import { Container } from "@medusajs/ui"
import * as React from "react"
import { PropRegistry } from "@/registries/prop-registry"
import { Feedback } from "./feedback"
type ComponentPropsProps = {
component: string
}
const ComponentProps = ({ component }: ComponentPropsProps) => {
const Props = React.useMemo(() => {
const Table = PropRegistry[component]?.table
if (!Table) {
return (
<div className="flex min-h-[200px] w-full items-center justify-center">
<p className="txt-compact-small">
No API reference found for{" "}
<span className="txt-compact-small-plus">{component}</span>
</p>
</div>
)
}
return <Table />
}, [component])
return (
<>
<Container className="mb-6 mt-8 overflow-hidden p-0">
<React.Suspense
fallback={
<div className="text-medusa-fg-muted flex flex-1 items-center justify-center">
<Spinner className="animate-spin" />
</div>
}
>
{Props}
</React.Suspense>
</Container>
<Feedback title={`props of ${component}`} />
</>
)
}
export { ComponentProps }
@@ -0,0 +1,81 @@
import { Documentation } from "react-docgen"
import { Suspense } from "react"
import { Spinner } from "@medusajs/icons"
import { PropTable } from "./props-table"
import { Container, clx } from "@medusajs/ui"
import { Feedback } from "./feedback"
import { MarkdownContent } from "docs-ui"
import { components } from "./mdx-components"
type ComponentReferenceProps = {
mainComponent: string
componentsToShow?: string[]
specsSrc?: string
}
const ComponentReference = ({
mainComponent,
componentsToShow = [mainComponent],
specsSrc,
}: ComponentReferenceProps) => {
if (!specsSrc) {
return <></>
}
const specs = JSON.parse(specsSrc) as Documentation[]
return (
<>
{componentsToShow.map((component, index) => {
const componentSpec = specs?.find(
(spec) => spec.displayName === component
)
const hasProps =
componentSpec?.props && Object.keys(componentSpec.props).length > 0
return (
<Suspense
fallback={
<div className="text-medusa-fg-muted flex flex-1 items-center justify-center">
<Spinner className="animate-spin" />
</div>
}
key={index}
>
{componentSpec && (
<>
{componentsToShow.length > 1 && (
<h3 className={clx("h3-docs mb-2 mt-10 text-medusa-fg-base")}>
{componentSpec.displayName || component}
</h3>
)}
{componentSpec.description && (
<MarkdownContent components={components}>
{componentSpec.description}
</MarkdownContent>
)}
{hasProps && (
<>
<Container className="mb-6 mt-8 overflow-hidden p-0">
<Suspense
fallback={
<div className="text-medusa-fg-muted flex flex-1 items-center justify-center">
<Spinner className="animate-spin" />
</div>
}
>
<PropTable props={componentSpec.props!} />
</Suspense>
</Container>
<Feedback title={`props of ${component}`} />
</>
)}
</>
)}
</Suspense>
)
})}
</>
)
}
export { ComponentReference }
+4 -1
View File
@@ -3,6 +3,7 @@ import { Table, Tooltip } from "@medusajs/ui"
import { HookData, HookDataMap } from "@/types/hooks"
import { EnumType, FunctionType, ObjectType } from "@/types/props"
import { InlineCode } from "docs-ui"
const HookTable = ({ props }: { props: HookDataMap }) => {
return (
@@ -47,7 +48,9 @@ const Row = ({ value, type, description }: HookData) => {
return (
<Table.Row className="code-body">
<Table.Cell>{value}</Table.Cell>
<Table.Cell>
<InlineCode>{value}</InlineCode>
</Table.Cell>
<Table.Cell>
{!isComplexType && type.toString()}
{isEnum(type) && (
@@ -6,12 +6,12 @@ import * as React from "react"
import { Colors } from "@/components/colors"
import { ComponentExample } from "@/components/component-example"
import { ComponentProps } from "@/components/component-props"
import { HookValues } from "@/components/hook-values"
import { IconSearch } from "@/components/icon-search"
import { PackageInstall } from "@/components/package-install"
import { Feedback } from "@/components/feedback"
import { FigmaIcon } from "@/components/figma-icon"
import { ComponentReference } from "@/components/component-reference"
import clsx from "clsx"
import { NextLink, Card, BorderedIcon, CodeMdx, CodeBlock } from "docs-ui"
@@ -120,7 +120,6 @@ const components = {
return <hr className={clx("mb-4", className)} {...props} />
},
HookValues,
ComponentProps,
CodeBlock,
ComponentExample,
PackageInstall,
@@ -130,6 +129,7 @@ const components = {
Card,
BorderedIcon,
FigmaIcon,
ComponentReference,
}
const Mdx = ({ code }: MdxProps) => {
@@ -142,4 +142,4 @@ const Mdx = ({ code }: MdxProps) => {
)
}
export { Mdx }
export { Mdx, components }
+136 -79
View File
@@ -3,13 +3,9 @@
import { InformationCircleSolid } from "@medusajs/icons"
import { Table, Tooltip } from "@medusajs/ui"
import {
EnumType,
FunctionType,
ObjectType,
PropData,
PropDataMap,
} from "@/types/props"
import { PropData, PropDataMap, PropSpecType } from "@/types/props"
import { useCallback, useMemo } from "react"
import { InlineCode, MarkdownContent } from "docs-ui"
type PropTableProps = {
props: PropDataMap
@@ -26,101 +22,162 @@ const PropTable = ({ props }: PropTableProps) => {
</Table.Row>
</Table.Header>
<Table.Body className="border-b-0 [&_tr:last-child]:border-b-0">
{/* eslint-disable-next-line react/prop-types */}
{props.map((propData, index) => (
<Row key={index} {...propData} />
{Object.entries(props).map(([propName, propData]) => (
<Row key={propName} propName={propName} propData={propData} />
))}
</Table.Body>
</Table>
)
}
const Row = ({ prop, type, defaultValue }: PropData) => {
const isEnum = (t: unknown): t is EnumType => {
return (t as EnumType).type !== undefined && (t as EnumType).type === "enum"
}
type RowProps = {
propName: string
propData: PropData
}
const isObject = (t: unknown): t is ObjectType => {
return (
(t as ObjectType).type !== undefined &&
(t as ObjectType).type === "object"
)
}
type TypeNode = {
text: string
tooltipContent?: string
canBeCopied?: boolean
}
const isFunction = (t: unknown): t is FunctionType => {
return (
(t as FunctionType).type !== undefined &&
(t as FunctionType).type === "function"
)
const Row = ({
propName,
propData: { tsType: tsType, defaultValue, description },
}: RowProps) => {
const normalizeRaw = (str: string): string => {
return str.replace("\\|", "|")
}
const defaultValueRenderer = (
v: string | number | boolean | null | undefined
) => {
if (v === undefined) {
return "-"
const getTypeRaw = useCallback((type: PropSpecType): string => {
let raw = "raw" in type ? type.raw || type.name : type.name
if ("type" in type) {
if (type.type === "object") {
raw = `{\n ${type.signature.properties
.map((property) => `${property.key}: ${property.value.name}`)
.join("\n ")}\n}`
} else {
raw = type.raw
}
} else if (type.name === "Array" && "elements" in type) {
raw = type.elements.map((element) => getTypeRaw(element)).join(" | ")
}
if (typeof v === "boolean") {
return v ? "true" : "false"
return normalizeRaw(raw)
}, [])
const getTypeText = useCallback((type: PropSpecType): string => {
if (type?.name === "signature" && "type" in type) {
return type.type
} else if (type?.name === "Array" && type.raw) {
return normalizeRaw(type.raw) || "array"
}
if (v === null) {
return "null"
return type.name || ""
}, [])
const getTypeTooltipContent = useCallback(
(type: PropSpecType): string | undefined => {
if (type?.name === "signature" && "type" in type) {
return getTypeRaw(type)
} else if (type?.name === "Array" && type.raw) {
return getTypeRaw(type)
}
return undefined
},
[getTypeRaw]
)
const typeNodes = useMemo((): TypeNode[] => {
const typeNodes: TypeNode[] = []
if (tsType?.name === "union" && "elements" in tsType) {
tsType.elements.forEach((element) => {
if (
("elements" in element && element.elements.length) ||
"signature" in element
) {
const elementTypeText = getTypeText(element)
const elementTooltipContent = getTypeTooltipContent(element)
typeNodes.push({
text: elementTypeText,
tooltipContent:
elementTypeText !== elementTooltipContent
? elementTooltipContent
: undefined,
})
} else if ("value" in element) {
typeNodes.push({
text: element.value,
canBeCopied: true,
})
} else {
typeNodes.push({
text: element.name,
})
}
})
} else if (tsType) {
typeNodes.push({
text: getTypeText(tsType),
tooltipContent: getTypeTooltipContent(tsType),
})
}
if (typeof v === "string") {
return `"${v}"`
}
return typeNodes
}, [tsType, getTypeText, getTypeTooltipContent])
return v
}
const isComplexType = isEnum(type) || isObject(type) || isFunction(type)
const defaultVal: string | undefined = defaultValue?.value as string
return (
<Table.Row className="code-body">
<Table.Cell>{prop}</Table.Cell>
<Table.Row>
<Table.Cell>
{!isComplexType && type.toString()}
{isEnum(type) && (
<Tooltip
content={type.values.map((v) => `"${v}"`).join(" | ")}
className="font-mono"
>
<div className="flex items-center gap-x-1">
<span>enum</span>
<div className="flex items-center gap-x-1">
<InlineCode>{propName}</InlineCode>
{description && (
<Tooltip
content={
<MarkdownContent
allowedElements={["a", "code"]}
unwrapDisallowed={true}
>
{description}
</MarkdownContent>
}
>
<InformationCircleSolid className="text-medusa-fg-subtle" />
</Tooltip>
)}
</div>
</Table.Cell>
<Table.Cell>
<div className="flex items-center flex-wrap gap-1 py-1">
{typeNodes.map((typeNode, index) => (
<div key={index} className="flex items-center gap-x-1">
{index > 0 && <span>|</span>}
{typeNode.tooltipContent && (
<Tooltip
content={<pre>{typeNode.tooltipContent}</pre>}
className="font-mono !max-w-none"
>
<div className="flex items-center gap-x-1">
<code>{typeNode.text}</code>
<InformationCircleSolid className="text-medusa-fg-subtle" />
</div>
</Tooltip>
)}
{!typeNode.tooltipContent && (
<>
{typeNode.canBeCopied && (
<InlineCode>{typeNode.text}</InlineCode>
)}
{!typeNode.canBeCopied && <code>{typeNode.text}</code>}
</>
)}
</div>
</Tooltip>
)}
{isObject(type) && (
<Tooltip
content={<pre>{type.shape}</pre>}
className="font-mono"
maxWidth={500}
>
<div className="flex items-center gap-x-1">
<span>{type.name}</span>
<InformationCircleSolid className="text-medusa-fg-subtle" />
</div>
</Tooltip>
)}
{isFunction(type) && (
<Tooltip
content={<pre>{type.signature}</pre>}
className="font-mono"
maxWidth={500}
>
<div className="flex items-center gap-x-1">
<span>function</span>
<InformationCircleSolid className="text-medusa-fg-subtle" />
</div>
</Tooltip>
)}
))}
</div>
</Table.Cell>
<Table.Cell className="text-right">
{defaultValueRenderer(defaultValue)}
{defaultVal && <InlineCode>{defaultVal}</InlineCode>}
{!defaultVal && " - "}
</Table.Cell>
</Table.Row>
)
@@ -1,60 +0,0 @@
"use client"
import { clx } from "@medusajs/ui"
import * as Primitives from "@radix-ui/react-scroll-area"
import clsx from "clsx"
import { Key } from "@/types/props"
type ScrollbarProps = React.ComponentProps<typeof Primitives.Scrollbar>
const Scrollbar = ({ key, ...props }: ScrollbarProps) => {
return (
<Primitives.Scrollbar
className={clsx(
"bg-medusa-bg-baseflex touch-none select-none p-0.5 transition-colors ease-out",
"data-[orientation=horizontal]:h-2.5 data-[orientation=vertical]:w-2.5 data-[orientation=horizontal]:flex-col"
)}
key={key as Key}
{...props}
/>
)
}
type ThumbProps = React.ComponentProps<typeof Primitives.Thumb>
const Thumb = ({ className, key, ...props }: ThumbProps) => {
return (
<Primitives.Thumb
className={clx(
"bg-medusa-bg-component relative flex-1 rounded-[10px] before:absolute before:left-1/2 before:top-1/2 before:h-full",
"before:min-h-[44px] before:w-full before:min-w-[44px] before:-translate-x-1/2 before:-translate-y-1/2 before:content-['']",
className
)}
key={key as Key}
{...props}
/>
)
}
type ScrollAreaProps = React.ComponentProps<typeof Primitives.Root>
const ScrollArea = ({ children, className }: ScrollAreaProps) => {
return (
<Primitives.Root
className={clx("h-full w-full overflow-hidden", className)}
>
<Primitives.Viewport className="h-full w-full">
{children}
</Primitives.Viewport>
<Scrollbar orientation="vertical">
<Thumb />
</Scrollbar>
<Scrollbar orientation="horizontal">
<Thumb />
</Scrollbar>
<Primitives.Corner />
</Primitives.Root>
)
}
export { ScrollArea }
@@ -25,6 +25,4 @@ import { Avatar } from "@medusajs/ui"
---
This component is based on the [Radix UI Avatar](https://www.radix-ui.com/primitives/docs/components/avatar) primitive.
<ComponentProps component="avatar" />
<ComponentReference mainComponent="Avatar" />
@@ -22,9 +22,7 @@ import { Badge } from "@medusajs/ui"
---
This component is based on the `div` element and supports all props of this element.
<ComponentProps component="badge" />
<ComponentReference mainComponent="Badge" />
## Examples
@@ -22,9 +22,7 @@ import { Button } from "@medusajs/ui"
---
This component is based on the `button` element and supports all props of this element.
<ComponentProps component="button" />
<ComponentReference mainComponent="Button" />
## Examples
@@ -22,9 +22,7 @@ import { Calendar } from "@medusajs/ui"
---
This component is based on the [react-date-picker](https://www.npmjs.com/package/react-date-picker) package.
<ComponentProps component="calendar" />
<ComponentReference mainComponent="Calendar" />
## Examples
@@ -22,7 +22,7 @@ import { Checkbox } from "@medusajs/ui"
---
This component is based on the [Radix UI Checkbox](https://www.radix-ui.com/primitives/docs/components/checkbox) primitive.
<ComponentReference mainComponent="Checkbox" />
## Examples
@@ -34,25 +34,12 @@ import { CodeBlock } from "@medusajs/ui"
---
### 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.
<ComponentReference mainComponent="CodeBlock" componentsToShow={[
"CodeBlock",
"CodeBlock.Header",
"CodeBlock.Header.Meta",
"CodeBlock.Body"
]} />
## Examples
@@ -38,26 +38,10 @@ import { CommandBar } from "@medusajs/ui"
---
### CommandBar
The root component of the command bar. This component manages the state of the command bar.
<ComponentProps component="command-bar" />
### CommandBar.Bar
The bar component of the command bar. This component is used to display the commands.
### CommandBar.Value
The value component of the command bar. This component is used to display a value, such as the number of selected items which the commands will act on.
### CommandBar.Seperator
The seperator component of the command bar. This component is used to display a seperator between commands.
### CommandBar.Command
The command component of the command bar. This component is used to display a command, as well as registering the keyboad shortcut.
<ComponentProps component="command-bar-command" />
<ComponentReference mainComponent="CommandBar" componentsToShow={[
"CommandBar",
"CommandBar.Bar",
"CommandBar.Value",
"CommandBar.Seperator",
"CommandBar.Command"
]} />
@@ -24,4 +24,4 @@ import { Command } from "@medusajs/ui"
---
This component is based on the `div` element and supports all props of this element.
<ComponentReference mainComponent="Command" />
@@ -22,7 +22,7 @@ import { Container } from "@medusajs/ui"
---
This component is based on the `div` element and supports all props of this element.
<ComponentReference mainComponent="Container" />
## Examples
@@ -22,9 +22,7 @@ import { Copy } from "@medusajs/ui"
---
This component is based on the `button` element and supports all props of this element.
<ComponentProps component="copy" />
<ComponentReference mainComponent="Copy" />
## Examples
@@ -22,9 +22,7 @@ import { CurrencyInput } from "@medusajs/ui"
---
This component is based on the `input` element and supports all props of this element.
<ComponentProps component="currency-input" />
<ComponentReference mainComponent="CurrencyInput" />
## Examples
@@ -22,9 +22,7 @@ import { DatePicker } from "@medusajs/ui"
---
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" />
<ComponentReference mainComponent="DatePicker" />
## Examples

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