UNPKG

@storybook/react

Version:

Storybook for React: Develop React Component in isolation with Hot Reloading.

181 lines (149 loc) 5.42 kB
import isPlainObject from 'lodash/isPlainObject'; import isFunction from 'lodash/isFunction'; import isString from 'lodash/isString'; // @ts-ignore import reactElementToJSXString from 'react-element-to-jsx-string'; import { createSummaryValue, isTooLongForDefaultValueSummary } from '@storybook/docs-tools'; import { inspectValue } from '../inspection'; import { generateObject } from './generateObject'; import { generateArray } from './generateArray'; import { getPrettyElementIdentifier, getPrettyFuncIdentifier } from './prettyIdentifier'; import { OBJECT_CAPTION, FUNCTION_CAPTION, ELEMENT_CAPTION } from '../captions'; import { isHtmlTag } from '../isHtmlTag'; function isReactElement(element) { return element.$$typeof != null; } export function extractFunctionName(func, propName) { const { name } = func; // Comparison with the prop name is to discard inferred function names. if (name !== '' && name !== 'anonymous' && name !== propName) { return name; } return null; } const stringResolver = rawDefaultProp => { return createSummaryValue(JSON.stringify(rawDefaultProp)); }; function generateReactObject(rawDefaultProp) { const { type } = rawDefaultProp; const { displayName } = type; const jsx = reactElementToJSXString(rawDefaultProp, {}); if (displayName != null) { const prettyIdentifier = getPrettyElementIdentifier(displayName); return createSummaryValue(prettyIdentifier, jsx); } if (isString(type)) { // This is an HTML element. if (isHtmlTag(type)) { const jsxCompact = reactElementToJSXString(rawDefaultProp, { tabStop: 0 }); const jsxSummary = jsxCompact.replace(/\r?\n|\r/g, ''); if (!isTooLongForDefaultValueSummary(jsxSummary)) { return createSummaryValue(jsxSummary); } } } return createSummaryValue(ELEMENT_CAPTION, jsx); } const objectResolver = rawDefaultProp => { if (isReactElement(rawDefaultProp) && rawDefaultProp.type != null) { return generateReactObject(rawDefaultProp); } if (isPlainObject(rawDefaultProp)) { const inspectionResult = inspectValue(JSON.stringify(rawDefaultProp)); return generateObject(inspectionResult); } if (Array.isArray(rawDefaultProp)) { const inspectionResult = inspectValue(JSON.stringify(rawDefaultProp)); return generateArray(inspectionResult); } return createSummaryValue(OBJECT_CAPTION); }; const functionResolver = (rawDefaultProp, propDef) => { let isElement = false; let inspectionResult; // Try to display the name of the component. The body of the component is omitted since the code has been transpiled. if (isFunction(rawDefaultProp.render)) { isElement = true; } else if (rawDefaultProp.prototype != null && isFunction(rawDefaultProp.prototype.render)) { isElement = true; } else { let innerElement; try { inspectionResult = inspectValue(rawDefaultProp.toString()); const { hasParams, params } = inspectionResult.inferredType; if (hasParams) { // It might be a functional component accepting props. if (params.length === 1 && params[0].type === 'ObjectPattern') { innerElement = rawDefaultProp({}); } } else { innerElement = rawDefaultProp(); } if (innerElement != null) { if (isReactElement(innerElement)) { isElement = true; } } } catch (e) {// do nothing. } } const funcName = extractFunctionName(rawDefaultProp, propDef.name); if (funcName != null) { if (isElement) { return createSummaryValue(getPrettyElementIdentifier(funcName)); } if (inspectionResult != null) { inspectionResult = inspectValue(rawDefaultProp.toString()); } const { hasParams } = inspectionResult.inferredType; return createSummaryValue(getPrettyFuncIdentifier(funcName, hasParams)); } return createSummaryValue(isElement ? ELEMENT_CAPTION : FUNCTION_CAPTION); }; const defaultResolver = rawDefaultProp => { return createSummaryValue(rawDefaultProp.toString()); }; const DEFAULT_TYPE_RESOLVERS = { string: stringResolver, object: objectResolver, function: functionResolver, default: defaultResolver }; export function createTypeResolvers(customResolvers = {}) { return Object.assign({}, DEFAULT_TYPE_RESOLVERS, customResolvers); } // When react-docgen cannot provide a defaultValue we take it from the raw defaultProp. // It works fine for types that are not transpiled. For the types that are transpiled, we can only provide partial support. // This means that: // - The detail might not be available. // - Identifiers might not be "prettified" for all the types. export function createDefaultValueFromRawDefaultProp(rawDefaultProp, propDef, typeResolvers = DEFAULT_TYPE_RESOLVERS) { try { // Keep the extra () otherwise it will fail for functions. switch (typeof rawDefaultProp) { case 'string': return typeResolvers.string(rawDefaultProp, propDef); case 'object': return typeResolvers.object(rawDefaultProp, propDef); case 'function': { return typeResolvers.function(rawDefaultProp, propDef); } default: return typeResolvers.default(rawDefaultProp, propDef); } } catch (e) { // eslint-disable-next-line no-console console.error(e); } return null; }