diff --git a/.github/workflows/generate-references.yml b/.github/workflows/generate-references.yml index cdeb216bb2..d86bdf6000 100644 --- a/.github/workflows/generate-references.yml +++ b/.github/workflows/generate-references.yml @@ -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" \ No newline at end of file diff --git a/docs-util/packages/react-docs-generator/README.md b/docs-util/packages/react-docs-generator/README.md new file mode 100644 index 0000000000..eedbbc2ac8 --- /dev/null +++ b/docs-util/packages/react-docs-generator/README.md @@ -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 `: (required) A path to a file containing React components or a directory to scan its sub-directories and files for components. +- `--output `: (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 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. \ No newline at end of file diff --git a/docs-util/packages/react-docs-generator/package.json b/docs-util/packages/react-docs-generator/package.json new file mode 100644 index 0000000000..e507bb1f69 --- /dev/null +++ b/docs-util/packages/react-docs-generator/package.json @@ -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" + } +} diff --git a/docs-util/packages/react-docs-generator/src/classes/typedoc-manager.ts b/docs-util/packages/react-docs-generator/src/classes/typedoc-manager.ts new file mode 100644 index 0000000000..0751755b95 --- /dev/null +++ b/docs-util/packages/react-docs-generator/src/classes/typedoc-manager.ts @@ -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 + +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 { + 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() + + // 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 + } +} diff --git a/docs-util/packages/react-docs-generator/src/commands/generate.ts b/docs-util/packages/react-docs-generator/src/commands/generate.ts new file mode 100644 index 0000000000..278d9aeadd --- /dev/null +++ b/docs-util/packages/react-docs-generator/src/commands/generate.ts @@ -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}`)) + } + } +} diff --git a/docs-util/packages/react-docs-generator/src/handlers/argsPropHandler.ts b/docs-util/packages/react-docs-generator/src/handlers/argsPropHandler.ts new file mode 100644 index 0000000000..5c128deccd --- /dev/null +++ b/docs-util/packages/react-docs-generator/src/handlers/argsPropHandler.ts @@ -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, + 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 + 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, + typedocManager?: TypedocManager +) => { + let componentParams: NodePath[] = [] + 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 + + if (!resolvedParam) { + return + } + + // set description and type of prop + resolveDocumentation(documentation, resolvedParam, typedocManager) + }) +} + +export default argsPropHandler diff --git a/docs-util/packages/react-docs-generator/src/index.ts b/docs-util/packages/react-docs-generator/src/index.ts new file mode 100644 index 0000000000..d125d15124 --- /dev/null +++ b/docs-util/packages/react-docs-generator/src/index.ts @@ -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 ", + "Path to a file containing a React component or a directory of React components." + ) + .requiredOption("-o, --output ", "Path to the output directory.") + .option( + "--clean", + "Clean the output directory before creating the new specs", + false + ) + .option( + "--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()) diff --git a/docs-util/packages/react-docs-generator/src/resolvers/custom-resolver.ts b/docs-util/packages/react-docs-generator/src/resolvers/custom-resolver.ts new file mode 100644 index 0000000000..81c73d1a6f --- /dev/null +++ b/docs-util/packages/react-docs-generator/src/resolvers/custom-resolver.ts @@ -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 +} +/** + * 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(), + } + 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() + } +} diff --git a/docs-util/packages/react-docs-generator/src/utils/empty-prop-descriptor.ts b/docs-util/packages/react-docs-generator/src/utils/empty-prop-descriptor.ts new file mode 100644 index 0000000000..3f47342655 --- /dev/null +++ b/docs-util/packages/react-docs-generator/src/utils/empty-prop-descriptor.ts @@ -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] + }) +} diff --git a/docs-util/packages/react-docs-generator/src/utils/is-empty-prop-descriptor.ts b/docs-util/packages/react-docs-generator/src/utils/is-empty-prop-descriptor.ts new file mode 100644 index 0000000000..cafb1ce7b4 --- /dev/null +++ b/docs-util/packages/react-docs-generator/src/utils/is-empty-prop-descriptor.ts @@ -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 + } + }) + ) +} diff --git a/docs-util/packages/react-docs-generator/src/utils/is-prop-blacklisted.ts b/docs-util/packages/react-docs-generator/src/utils/is-prop-blacklisted.ts new file mode 100644 index 0000000000..1189b99d2b --- /dev/null +++ b/docs-util/packages/react-docs-generator/src/utils/is-prop-blacklisted.ts @@ -0,0 +1,5 @@ +const BLACKLISTED_PROPS = ["className", "children"] + +export default function isPropBlacklisted(propName: string) { + return BLACKLISTED_PROPS.includes(propName) +} diff --git a/docs-util/packages/react-docs-generator/src/utils/read-files.ts b/docs-util/packages/react-docs-generator/src/utils/read-files.ts new file mode 100644 index 0000000000..54ae910680 --- /dev/null +++ b/docs-util/packages/react-docs-generator/src/utils/read-files.ts @@ -0,0 +1,20 @@ +import { statSync, readFileSync } from "fs" +import { globSync } from "glob" + +export default function readFiles(path: string): Map { + const files = new Map() + // 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 +} diff --git a/docs-util/packages/react-docs-generator/tsconfig.json b/docs-util/packages/react-docs-generator/tsconfig.json new file mode 100644 index 0000000000..aa0499d772 --- /dev/null +++ b/docs-util/packages/react-docs-generator/tsconfig.json @@ -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 + } +} diff --git a/docs-util/packages/scripts/package.json b/docs-util/packages/scripts/package.json index bd241e75dc..458eed9efa 100644 --- a/docs-util/packages/scripts/package.json +++ b/docs-util/packages/scripts/package.json @@ -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": "*", diff --git a/docs-util/packages/typedoc-config/extended-tsconfig/tsdoc.json b/docs-util/packages/typedoc-config/extended-tsconfig/tsdoc.json index e7fc481823..f8aaecc581 100644 --- a/docs-util/packages/typedoc-config/extended-tsconfig/tsdoc.json +++ b/docs-util/packages/typedoc-config/extended-tsconfig/tsdoc.json @@ -25,6 +25,14 @@ { "tagName": "@docHideSignature", "syntaxKind": "modifier" + }, + { + "tagName": "@keep", + "syntaxKind": "modifier" + }, + { + "tagName": "@excludeExternal", + "syntaxKind": "modifier" } ] } \ No newline at end of file diff --git a/docs-util/packages/typedoc-config/extended-tsconfig/ui.json b/docs-util/packages/typedoc-config/extended-tsconfig/ui.json new file mode 100644 index 0000000000..2a37f022f6 --- /dev/null +++ b/docs-util/packages/typedoc-config/extended-tsconfig/ui.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json.schemastore.org/tsconfig", + "extends": [ + "../../../../packages/design-system/ui/tsconfig.esm.json" + ] +} \ No newline at end of file diff --git a/docs-util/packages/typedoc-plugin-custom/src/parse-oas-schema-plugin.ts b/docs-util/packages/typedoc-plugin-custom/src/parse-oas-schema-plugin.ts index 1b81de2a58..c470eb9b55 100644 --- a/docs-util/packages/typedoc-plugin-custom/src/parse-oas-schema-plugin.ts +++ b/docs-util/packages/typedoc-plugin-custom/src/parse-oas-schema-plugin.ts @@ -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 = diff --git a/docs-util/packages/typedoc-plugin-custom/src/resolve-references-plugin.ts b/docs-util/packages/typedoc-plugin-custom/src/resolve-references-plugin.ts index 914a42c681..c5eb0a2b76 100644 --- a/docs-util/packages/typedoc-plugin-custom/src/resolve-references-plugin.ts +++ b/docs-util/packages/typedoc-plugin-custom/src/resolve-references-plugin.ts @@ -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 diff --git a/docs-util/packages/typedoc-plugin-custom/src/types/index.d.ts b/docs-util/packages/typedoc-plugin-custom/src/types/index.d.ts new file mode 100644 index 0000000000..4a4105c1c6 --- /dev/null +++ b/docs-util/packages/typedoc-plugin-custom/src/types/index.d.ts @@ -0,0 +1,6 @@ +export declare module "typedoc" { + declare interface TypeDocOptionMap { + enableInternalResolve: boolean + internalModule: string + } +} diff --git a/docs-util/packages/typedoc-plugin-custom/tsconfig.json b/docs-util/packages/typedoc-plugin-custom/tsconfig.json index b3b4792cd9..d9c48aada9 100644 --- a/docs-util/packages/typedoc-plugin-custom/tsconfig.json +++ b/docs-util/packages/typedoc-plugin-custom/tsconfig.json @@ -4,5 +4,6 @@ "outDir": "./dist", "rootDir": "./src" }, - "include": ["src"] + "include": ["src", "src/types"], + "typeRoots": ["./src/types/index.d.ts"] } \ No newline at end of file diff --git a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/breadcrumbs.ts b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/breadcrumbs.ts index f7151ebbc2..246787a696 100644 --- a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/breadcrumbs.ts +++ b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/breadcrumbs.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) { diff --git a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/comment.ts b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/comment.ts index 6f63846091..55ef4866bd 100644 --- a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/comment.ts +++ b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/comment.ts @@ -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 diff --git a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/declaration-title.ts b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/declaration-title.ts index cccd6a0fb1..3b359f6a1c 100644 --- a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/declaration-title.ts +++ b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/declaration-title.ts @@ -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( diff --git a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/escape.ts b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/escape.ts index 6e736455a2..9cc1f1fcbf 100644 --- a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/escape.ts +++ b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/escape.ts @@ -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) { diff --git a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/reflection-title.ts b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/reflection-title.ts index 65ef9a2abc..eb8f16d9f8 100644 --- a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/reflection-title.ts +++ b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/reflection-title.ts @@ -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( diff --git a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/signature-title.ts b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/signature-title.ts index c4c11008a2..cce2772513 100644 --- a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/signature-title.ts +++ b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/signature-title.ts @@ -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( diff --git a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/toc.ts b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/toc.ts index 3f9f88b2c8..8c489fbacd 100644 --- a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/toc.ts +++ b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/toc.ts @@ -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( diff --git a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/type-and-parent.ts b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/type-and-parent.ts index 1676aee90a..e414d5a188 100644 --- a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/type-and-parent.ts +++ b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/type-and-parent.ts @@ -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( diff --git a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/type-declaration-object-literal.ts b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/type-declaration-object-literal.ts index fc3fa773d6..d2bee02ca7 100644 --- a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/type-declaration-object-literal.ts +++ b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/type-declaration-object-literal.ts @@ -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( diff --git a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/type-parameter-table.ts b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/type-parameter-table.ts index e3d36b8886..b1f68852a5 100644 --- a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/type-parameter-table.ts +++ b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/type-parameter-table.ts @@ -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( diff --git a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/type.ts b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/type.ts index e042e0adb1..0bf5349a24 100644 --- a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/type.ts +++ b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/type.ts @@ -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, }) } ) diff --git a/docs-util/packages/typedoc-plugin-markdown-medusa/src/utils.ts b/docs-util/packages/typedoc-plugin-markdown-medusa/src/utils.ts index 462b0c684b..dfb919d6e8 100644 --- a/docs-util/packages/typedoc-plugin-markdown-medusa/src/utils.ts +++ b/docs-util/packages/typedoc-plugin-markdown-medusa/src/utils.ts @@ -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, ">") -} - -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("```", "")) } diff --git a/docs-util/packages/typedoc-plugin-markdown-medusa/src/utils/reflection-formatter.ts b/docs-util/packages/typedoc-plugin-markdown-medusa/src/utils/reflection-formatter.ts index f835eaf829..18f6259394 100644 --- a/docs-util/packages/typedoc-plugin-markdown-medusa/src/utils/reflection-formatter.ts +++ b/docs-util/packages/typedoc-plugin-markdown-medusa/src/utils/reflection-formatter.ts @@ -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) diff --git a/docs-util/packages/typedoc-plugin-markdown-medusa/src/utils/return-reflection-formatter.ts b/docs-util/packages/typedoc-plugin-markdown-medusa/src/utils/return-reflection-formatter.ts index 547b52a17e..c9e31995b5 100644 --- a/docs-util/packages/typedoc-plugin-markdown-medusa/src/utils/return-reflection-formatter.ts +++ b/docs-util/packages/typedoc-plugin-markdown-medusa/src/utils/return-reflection-formatter.ts @@ -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) diff --git a/docs-util/packages/utils/src/get-type-children.ts b/docs-util/packages/utils/src/get-type-children.ts index 708d2670c5..547c7193b5 100644 --- a/docs-util/packages/utils/src/get-type-children.ts +++ b/docs-util/packages/utils/src/get-type-children.ts @@ -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) { diff --git a/docs-util/packages/typedoc-plugin-markdown-medusa/src/utils/type-utils.ts b/docs-util/packages/utils/src/get-type-str.ts similarity index 96% rename from docs-util/packages/typedoc-plugin-markdown-medusa/src/utils/type-utils.ts rename to docs-util/packages/utils/src/get-type-str.ts index d1e7d457ef..5d9daabceb 100644 --- a/docs-util/packages/typedoc-plugin-markdown-medusa/src/utils/type-utils.ts +++ b/docs-util/packages/utils/src/get-type-str.ts @@ -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 = { 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): 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 { diff --git a/docs-util/packages/utils/src/index.ts b/docs-util/packages/utils/src/index.ts index da5d78440f..0abd2ab755 100644 --- a/docs-util/packages/utils/src/index.ts +++ b/docs-util/packages/utils/src/index.ts @@ -1,2 +1,4 @@ export * from "./get-type-children" export * from "./get-project-child" +export * from "./get-type-str" +export * from "./str-utils" diff --git a/docs-util/packages/utils/src/str-utils.ts b/docs-util/packages/utils/src/str-utils.ts new file mode 100644 index 0000000000..50753431dd --- /dev/null +++ b/docs-util/packages/utils/src/str-utils.ts @@ -0,0 +1,23 @@ +export function getHTMLChar(str: string) { + return str + .replace(//g, ">") +} + +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() + : "" +} diff --git a/docs-util/yarn.lock b/docs-util/yarn.lock index fe7d31ce3d..d6d5d38322 100644 --- a/docs-util/yarn.lock +++ b/docs-util/yarn.lock @@ -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": +"resolve@patch:resolve@^1.20.0#~builtin, resolve@patch:resolve@^1.22.1#~builtin, resolve@patch:resolve@^1.22.8#~builtin": version: 1.22.8 resolution: "resolve@patch:resolve@npm%3A1.22.8#~builtin::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 diff --git a/packages/design-system/ui/src/components/avatar/avatar.tsx b/packages/design-system/ui/src/components/avatar/avatar.tsx index 528120455a..33e39d6c21 100644 --- a/packages/design-system/ui/src/components/avatar/avatar.tsx +++ b/packages/design-system/ui/src/components/avatar/avatar.tsx @@ -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, 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 ( diff --git a/packages/design-system/ui/src/components/badge/badge.tsx b/packages/design-system/ui/src/components/badge/badge.tsx index 1878e291f2..aa89028573 100644 --- a/packages/design-system/ui/src/components/badge/badge.tsx +++ b/packages/design-system/ui/src/components/badge/badge.tsx @@ -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( ( { 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" diff --git a/packages/design-system/ui/src/components/button/button.tsx b/packages/design-system/ui/src/components/button/button.tsx index 5a0041016a..367de6b11b 100644 --- a/packages/design-system/ui/src/components/button/button.tsx +++ b/packages/design-system/ui/src/components/button/button.tsx @@ -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( ( { + /** + * 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" diff --git a/packages/design-system/ui/src/components/calendar/calendar.tsx b/packages/design-system/ui/src/components/calendar/calendar.tsx index 3143b22ecc..f205e84f0c 100644 --- a/packages/design-system/ui/src/components/calendar/calendar.tsx +++ b/packages/design-system/ui/src/components/calendar/calendar.tsx @@ -22,6 +22,9 @@ type KeysToOmit = "showWeekNumber" | "captionLayout" | "mode" type SingleProps = OmitKeys type RangeProps = OmitKeys +/** + * @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) => { diff --git a/packages/design-system/ui/src/components/checkbox/checkbox.tsx b/packages/design-system/ui/src/components/checkbox/checkbox.tsx index fb2a7195a7..251293f3b6 100644 --- a/packages/design-system/ui/src/components/checkbox/checkbox.tsx +++ b/packages/design-system/ui/src/components/checkbox/checkbox.tsx @@ -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, React.ComponentPropsWithoutRef diff --git a/packages/design-system/ui/src/components/code-block/code-block.tsx b/packages/design-system/ui/src/components/code-block/code-block.tsx index 8371c15487..cb065dd6a4 100644 --- a/packages/design-system/ui/src/components/code-block/code-block.tsx +++ b/packages/design-system/ui/src/components/code-block/code-block.tsx @@ -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 = ({ ) } +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 & HeaderProps) => { @@ -98,7 +126,11 @@ const HeaderComponent = ({ ) } +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 = ({ ) } +Body.displayName = "CodeBlock.Body" const CodeBlock = Object.assign(Root, { Body, Header, Meta }) diff --git a/packages/design-system/ui/src/components/code/code.tsx b/packages/design-system/ui/src/components/code/code.tsx index 86459019fc..62cfcfc717 100644 --- a/packages/design-system/ui/src/components/code/code.tsx +++ b/packages/design-system/ui/src/components/code/code.tsx @@ -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"> diff --git a/packages/design-system/ui/src/components/command-bar/command-bar.tsx b/packages/design-system/ui/src/components/command-bar/command-bar.tsx index d77cecc828..9142fb6126 100644 --- a/packages/design-system/ui/src/components/command-bar/command-bar.tsx +++ b/packages/design-system/ui/src/components/command-bar/command-bar.tsx @@ -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, "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( ( - { 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(() => { diff --git a/packages/design-system/ui/src/components/command/command.tsx b/packages/design-system/ui/src/components/command/command.tsx index f3a715f6f9..8f3873ddb1 100644 --- a/packages/design-system/ui/src/components/command/command.tsx +++ b/packages/design-system/ui/src/components/command/command.tsx @@ -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 }) diff --git a/packages/design-system/ui/src/components/container/container.tsx b/packages/design-system/ui/src/components/container/container.tsx index edeb27a4f8..45b1fbbaaa 100644 --- a/packages/design-system/ui/src/components/container/container.tsx +++ b/packages/design-system/ui/src/components/container/container.tsx @@ -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"> diff --git a/packages/design-system/ui/src/components/copy/copy.tsx b/packages/design-system/ui/src/components/copy/copy.tsx index cddf31c091..9eceab6771 100644 --- a/packages/design-system/ui/src/components/copy/copy.tsx +++ b/packages/design-system/ui/src/components/copy/copy.tsx @@ -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 & { 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 & 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") diff --git a/packages/design-system/ui/src/components/currency-input/currency-input.tsx b/packages/design-system/ui/src/components/currency-input/currency-input.tsx index 5ec855795e..e3667b6005 100644 --- a/packages/design-system/ui/src/components/currency-input/currency-input.tsx +++ b/packages/design-system/ui/src/components/currency-input/currency-input.tsx @@ -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( ( - { 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(null) diff --git a/packages/design-system/ui/src/components/date-picker/date-picker.tsx b/packages/design-system/ui/src/components/date-picker/date-picker.tsx index 611e2d658c..a05a0055ee 100644 --- a/packages/design-system/ui/src/components/date-picker/date-picker.tsx +++ b/packages/design-system/ui/src/components/date-picker/date-picker.tsx @@ -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 (