Shared utility functions for frameworks to implement docs in Storybook
—
Tools for extracting and processing component properties from various docgen sources including react-docgen, vue-docgen-api, and other component analysis tools.
Main function for extracting structured property information from components using docgen metadata.
/**
* Extracts component properties from docgen information
* @param component - Component with attached docgen metadata
* @param section - Docgen section to extract (e.g., 'props', 'events')
* @returns Array of extracted properties with metadata
*/
function extractComponentProps(
component: Component,
section: string
): ExtractedProp[];
interface ExtractedProp {
/** Property definition with all metadata */
propDef: PropDef;
/** Original docgen information */
docgenInfo: DocgenInfo;
/** Parsed JSDoc tags */
jsDocTags?: ExtractedJsDoc;
/** Detected type system */
typeSystem: TypeSystem;
}
type ExtractProps = (component: Component, section: string) => ExtractedProp[];The extraction process handles both array and object-based docgen formats, automatically detecting the type system and processing JSDoc comments.
Usage Examples:
import { extractComponentProps } from "@storybook/docs-tools";
// Extract props from React component
const reactComponent = {
__docgenInfo: {
props: {
name: {
type: { name: 'string' },
required: true,
description: 'User name @param name - The user identifier'
},
age: {
type: { name: 'number' },
required: false,
description: 'User age in years',
defaultValue: { value: '18' }
}
}
}
};
const props = extractComponentProps(reactComponent, 'props');
console.log(props);
// [
// {
// propDef: { name: 'name', type: { summary: 'string' }, required: true, ... },
// docgenInfo: { type: { name: 'string' }, required: true, ... },
// jsDocTags: { params: [{ name: 'name', description: 'The user identifier' }] },
// typeSystem: TypeSystem.JAVASCRIPT
// },
// { ... }
// ]
// Extract events from Vue component
const vueComponent = {
__docgenApi: [
{
name: 'click',
type: { names: ['CustomEvent'] },
description: 'Emitted when button is clicked'
}
]
};
const events = extractComponentProps(vueComponent, 'events');
// Framework-agnostic extraction
function extractAllComponentInfo(component: Component) {
const props = extractComponentProps(component, 'props');
const events = extractComponentProps(component, 'events');
const slots = extractComponentProps(component, 'slots');
return { props, events, slots };
}Utility for extracting component-level descriptions from docgen metadata.
/**
* Extracts component description from docgen metadata
* @param component - Component with docgen information
* @returns Component description string or empty string
*/
function extractComponentDescription(component?: Component): string;Usage Examples:
import { extractComponentDescription } from "@storybook/docs-tools";
// Extract description from various docgen formats
const componentWithDescription = {
__docgenInfo: {
description: 'A reusable button component with multiple variants'
}
};
const description = extractComponentDescription(componentWithDescription);
console.log(description); // "A reusable button component with multiple variants"
// Handle components without descriptions
const componentWithoutDescription = {};
const emptyDescription = extractComponentDescription(componentWithoutDescription);
console.log(emptyDescription); // ""
// Use in documentation generation
function generateComponentDocs(component: Component) {
const description = extractComponentDescription(component);
const props = extractComponentProps(component, 'props');
return {
name: component.displayName || component.name,
description,
properties: props.map(prop => prop.propDef)
};
}Specialized functions for handling different docgen section formats.
/**
* Processes docgen section when it's an array format (vue-docgen-api style)
* @param docgenSection - Array of docgen entries
* @returns Array of extracted properties
*/
function extractComponentSectionArray(docgenSection: any): ExtractedProp[];
/**
* Processes docgen section when it's an object format (react-docgen style)
* @param docgenSection - Object mapping prop names to docgen info
* @returns Array of extracted properties
*/
function extractComponentSectionObject(docgenSection: any): ExtractedProp[];Usage Examples:
// Handle array format (Vue docgen)
const vueDocgenArray = [
{
name: 'modelValue',
type: { names: ['string', 'number'] },
description: 'The input value'
},
{
name: 'placeholder',
type: { names: ['string'] },
description: 'Placeholder text'
}
];
const vueProps = extractComponentSectionArray(vueDocgenArray);
// Handle object format (React docgen)
const reactDocgenObject = {
value: {
type: { name: 'string' },
required: true,
description: 'Input value'
},
onChange: {
type: { name: 'func' },
required: true,
description: 'Change handler function'
}
};
const reactProps = extractComponentSectionObject(reactDocgenObject);
// Unified processing
function processDocgenSection(section: any): ExtractedProp[] {
if (Array.isArray(section)) {
return extractComponentSectionArray(section);
}
return extractComponentSectionObject(section);
}The extraction system uses factory functions to create appropriate property definitions based on the detected type system.
/**
* Factory function type for creating PropDef objects
*/
type PropDefFactory = (
propName: string,
docgenInfo: DocgenInfo,
jsDocParsingResult?: JsDocParsingResult
) => PropDef;
/**
* Gets appropriate PropDef factory for the detected type system
* @param typeSystem - Detected type system
* @returns Factory function for creating PropDef objects
*/
function getPropDefFactory(typeSystem: TypeSystem): PropDefFactory;Usage Examples:
import { getPropDefFactory, TypeSystem } from "@storybook/docs-tools";
// Create factory for TypeScript components
const tsFactory = getPropDefFactory(TypeSystem.TYPESCRIPT);
const tsPropDef = tsFactory('userName', {
type: { name: 'string' },
tsType: { name: 'string', raw: 'string' },
required: true,
description: 'The user name',
defaultValue: { value: '""' }
});
// Create factory for Flow components
const flowFactory = getPropDefFactory(TypeSystem.FLOW);
const flowPropDef = flowFactory('count', {
type: { name: 'number' },
flowType: { name: 'number', raw: 'number' },
required: false,
description: 'Item count',
defaultValue: { value: '0' }
});
// Framework integration
function createFrameworkExtractor(framework: string) {
return (component: Component, section: string) => {
const docgenSection = getDocgenSection(component, section);
if (!docgenSection) return [];
const typeSystem = detectTypeSystem(component, framework);
const factory = getPropDefFactory(typeSystem);
return processDocgenWithFactory(docgenSection, factory);
};
}Lower-level utilities for working directly with docgen information attached to components.
/**
* Checks if a component has docgen information attached
* @param component - Component to check
* @returns true if component has docgen info
*/
function hasDocgen<T>(component: Component): boolean;
/**
* Validates that a docgen section contains valid information
* @param docgenSection - Section to validate
* @returns true if section is valid
*/
function isValidDocgenSection(docgenSection: any): boolean;
/**
* Extracts a specific docgen section from a component
* @param component - Component with docgen information
* @param section - Section name to extract (e.g., 'props', 'events')
* @returns Docgen section data or undefined
*/
function getDocgenSection(component: Component, section: string): any;
/**
* Extracts component description from docgen information
* @param component - Component to extract description from
* @returns Component description string
*/
function getDocgenDescription(component: Component): string;
/**
* Safely converts objects to strings for display
* @param obj - Object to convert
* @returns String representation
*/
function str(obj: any): string;
/**
* Checks if a default value should be ignored in documentation
* @param value - Default value to check
* @returns true if value should be ignored
*/
function isDefaultValueBlacklisted(value: string): boolean;Usage Examples:
import {
hasDocgen,
isValidDocgenSection,
getDocgenSection,
getDocgenDescription,
str,
isDefaultValueBlacklisted
} from "@storybook/docs-tools";
// Check if component has docgen information
function processComponent(component: Component) {
if (!hasDocgen(component)) {
console.log('No docgen information found');
return null;
}
// Extract description
const description = getDocgenDescription(component);
console.log('Component description:', description);
// Get props section
const propsSection = getDocgenSection(component, 'props');
if (isValidDocgenSection(propsSection)) {
// Process props
Object.entries(propsSection).forEach(([propName, propInfo]) => {
const propData = propInfo as any;
// Convert to string safely
const typeString = str(propData.type);
// Check default value
if (propData.defaultValue && !isDefaultValueBlacklisted(propData.defaultValue.value)) {
console.log(`${propName}: ${typeString} = ${propData.defaultValue.value}`);
} else {
console.log(`${propName}: ${typeString}`);
}
});
}
}
// Framework-specific processing
function processReactComponent(component: any) {
const docgenInfo = component.__docgenInfo;
if (hasDocgen({ __docgenInfo: docgenInfo })) {
const description = getDocgenDescription({ __docgenInfo: docgenInfo });
const props = getDocgenSection({ __docgenInfo: docgenInfo }, 'props');
return {
description,
hasProps: isValidDocgenSection(props),
propsCount: props ? Object.keys(props).length : 0
};
}
return null;
}
// Safe value processing
function processDefaultValue(defaultValue: any) {
const valueString = str(defaultValue);
if (isDefaultValueBlacklisted(valueString)) {
return null; // Don't show blacklisted values
}
return valueString;
}The extraction system automatically detects which type system is being used based on available docgen information.
// Type system detection logic
function detectTypeSystem(docgenInfo: DocgenInfo): TypeSystem {
if (docgenInfo.type != null) {
return TypeSystem.JAVASCRIPT;
}
if (docgenInfo.flowType != null) {
return TypeSystem.FLOW;
}
if (docgenInfo.tsType != null) {
return TypeSystem.TYPESCRIPT;
}
return TypeSystem.UNKNOWN;
}
// Usage in extraction
const extractedProps = extractComponentProps(component, 'props');
extractedProps.forEach(prop => {
console.log(`${prop.propDef.name}: ${prop.typeSystem}`);
});Install with Tessl CLI
npx tessl i tessl/npm-storybook--docs-tools