diff --git a/website/layout/AutodocsLayout.js b/website/layout/AutodocsLayout.js index c918b7d624..9ee1d01f4d 100644 --- a/website/layout/AutodocsLayout.js +++ b/website/layout/AutodocsLayout.js @@ -17,6 +17,7 @@ var React = require('React'); var Site = require('Site'); var slugify = require('slugify'); +var styleReferencePattern = /^[^.]+\.propTypes\.style$/; var ComponentDoc = React.createClass({ renderType: function(type) { @@ -40,12 +41,20 @@ var ComponentDoc = React.createClass({ } if (type.name === 'custom') { + if (styleReferencePattern.test(type.raw)) { + var name = type.raw.substring(0, type.raw.indexOf('.')); + return {name}#style + } if (type.raw === 'EdgeInsetsPropType') { return '{top: number, left: number, bottom: number, right: number}'; } return type.raw; } + if (type.name === 'stylesheet') { + return 'style'; + } + if (type.name === 'func') { return 'function'; } @@ -63,6 +72,8 @@ var ComponentDoc = React.createClass({ {this.renderType(prop.type)} } + {prop.type && prop.type.name === 'stylesheet' && + this.renderStylesheetProps(prop.type.value)} {prop.description && {prop.description}} ); @@ -78,6 +89,41 @@ var ComponentDoc = React.createClass({ ); }, + renderStylesheetProps: function(stylesheetName) { + var style = this.props.content.styles[stylesheetName]; + return ( +
+ {(style.composes || []).map((name) => { + var link; + if (name !== 'LayoutPropTypes') { + name = name.replace('StylePropTypes', ''); + link = + {name}#style...; + } else { + link = + {name}...; + } + return ( +
+
{link}
+
+ ); + })} + {Object.keys(style.props).sort().map((name) => +
+
+ {name} + {' '} + {style.props[name].type && + {this.renderType(style.props[name].type)} + } +
+
+ )} +
+ ); + }, + renderProps: function(props, composes) { return (
@@ -197,7 +243,11 @@ var APIDoc = React.createClass({ var Autodocs = React.createClass({ render: function() { var metadata = this.props.metadata; - var content = JSON.parse(this.props.children); + var docs = JSON.parse(this.props.children); + var content = docs.type === 'component' || docs.type === 'style' ? + : + ; + return (
@@ -205,11 +255,9 @@ var Autodocs = React.createClass({

{metadata.title}

- {content.type === 'component' ? - : - } + {content} - {content.fullDescription} + {docs.fullDescription}
{metadata.previous && ← Prev} diff --git a/website/package.json b/website/package.json index 1f2cd7159b..a3f1d4827f 100644 --- a/website/package.json +++ b/website/package.json @@ -11,7 +11,7 @@ "mkdirp": "*", "optimist": "0.6.0", "react": "~0.12.0", - "react-docgen": "^1.0.0", + "react-docgen": "^1.1.0", "react-page-middleware": "git://github.com/facebook/react-page-middleware.git", "request": "*" } diff --git a/website/server/docgenHelpers.js b/website/server/docgenHelpers.js new file mode 100644 index 0000000000..663727eed9 --- /dev/null +++ b/website/server/docgenHelpers.js @@ -0,0 +1,67 @@ +"use strict"; +var b = require('react-docgen/node_modules/recast').types.builders; +var docgen = require('react-docgen'); + +function stylePropTypeHandler(documentation, path) { + var propTypesPath = docgen.utils.getPropertyValuePath(path, 'propTypes'); + if (!propTypesPath) { + return; + } + propTypesPath = docgen.utils.resolveToValue(propTypesPath); + if (!propTypesPath || propTypesPath.node.type !== 'ObjectExpression') { + return; + } + + // Check if the there is a style prop + propTypesPath.get('properties').each(function(propertyPath) { + if (propertyPath.node.type !== 'Property' || + docgen.utils.getPropertyName(propertyPath) !== 'style') { + return; + } + var valuePath = docgen.utils.resolveToValue(propertyPath.get('value')); + // If it's a call to StyleSheetPropType, do stuff + if (valuePath.node.type !== 'CallExpression' || + valuePath.node.callee.name !== 'StyleSheetPropType') { + return; + } + // Get type of style sheet + var styleSheetModule = docgen.utils.resolveToModule( + valuePath.get('arguments', 0) + ); + if (styleSheetModule) { + var propDescriptor = documentation.getPropDescriptor('style'); + propDescriptor.type = {name: 'stylesheet', value: styleSheetModule}; + } + }); +} + +function findExportedOrFirst(node, recast) { + return docgen.resolver.findExportedReactCreateClassCall(node, recast) || + docgen.resolver.findAllReactCreateClassCalls(node, recast)[0]; +} + +function findExportedObject(ast, recast) { + var objPath; + recast.visit(ast, { + visitAssignmentExpression: function(path) { + if (!objPath && docgen.utils.isExportsOrModuleAssignment(path)) { + objPath = docgen.utils.resolveToValue(path.get('right')); + } + return false; + } + }); + + if (objPath) { + var b = recast.types.builders; + // This is a bit hacky, but easier than replicating the default propType + // handler. All this does is convert `{...}` to `{propTypes: {...}}`. + objPath.replace(b.objectExpression([ + b.property('init', b.literal('propTypes'), objPath.node) + ])); + } + return objPath; +} + +exports.stylePropTypeHandler = stylePropTypeHandler; +exports.findExportedOrFirst = findExportedOrFirst; +exports.findExportedObject = findExportedObject; diff --git a/website/server/extractDocs.js b/website/server/extractDocs.js index 539da110ab..d5a44f6fe1 100644 --- a/website/server/extractDocs.js +++ b/website/server/extractDocs.js @@ -7,7 +7,8 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -var docs = require('react-docgen'); +var docgen = require('react-docgen'); +var docgenHelpers = require('./docgenHelpers'); var fs = require('fs'); var path = require('path'); var slugify = require('../core/slugify'); @@ -21,7 +22,7 @@ function getNameFromPath(filepath) { return filepath; } -function componentsToMarkdown(type, json, filepath, i) { +function componentsToMarkdown(type, json, filepath, i, styles) { var componentName = getNameFromPath(filepath); var docFilePath = '../docs/' + componentName + '.md'; @@ -29,6 +30,9 @@ function componentsToMarkdown(type, json, filepath, i) { json.fullDescription = fs.readFileSync(docFilePath).toString(); } json.type = type; + if (styles) { + json.styles = styles; + } var res = [ '---', @@ -84,20 +88,34 @@ var apis = [ '../Libraries/Vibration/VibrationIOS.ios.js', ]; -var all = components.concat(apis); +var styles = [ + '../Libraries/StyleSheet/LayoutPropTypes.js', + '../Libraries/Components/View/ViewStylePropTypes.js', + '../Libraries/Text/TextStylePropTypes.js', + '../Libraries/Image/ImageStylePropTypes.js', +]; + +var all = components.concat(apis).concat(styles.slice(0, 1)); +var styleDocs = styles.slice(1).reduce(function(docs, filepath) { + docs[path.basename(filepath).replace(path.extname(filepath), '')] = + docgen.parse( + fs.readFileSync(filepath), + docgenHelpers.findExportedObject, + [docgen.handlers.propTypeHandler] + ); + return docs; +}, {}); module.exports = function() { var i = 0; return [].concat( components.map(function(filepath) { - var json = docs.parse( + var json = docgen.parse( fs.readFileSync(filepath), - function(node, recast) { - return docs.resolver.findExportedReactCreateClassCall(node, recast) || - docs.resolver.findAllReactCreateClassCalls(node, recast)[0]; - } + docgenHelpers.findExportedOrFirst, + docgen.defaultHandlers.concat(docgenHelpers.stylePropTypeHandler) ); - return componentsToMarkdown('component', json, filepath, i++); + return componentsToMarkdown('component', json, filepath, i++, styleDocs); }), apis.map(function(filepath) { try { @@ -107,6 +125,14 @@ module.exports = function() { var json = {}; } return componentsToMarkdown('api', json, filepath, i++); + }), + styles.slice(0, 1).map(function(filepath) { + var json = docgen.parse( + fs.readFileSync(filepath), + docgenHelpers.findExportedObject, + [docgen.handlers.propTypeHandler] + ); + return componentsToMarkdown('style', json, filepath, i++); }) ); }; diff --git a/website/src/react-native/css/react-native.css b/website/src/react-native/css/react-native.css index 35906a6520..ebef0965cb 100644 --- a/website/src/react-native/css/react-native.css +++ b/website/src/react-native/css/react-native.css @@ -901,7 +901,13 @@ div[data-twttr-id] iframe { background-color: hsl(198, 100%, 96%); } -.prop:nth-child(2n) { +.compactProps { + border-left: 2px solid hsl(198, 100%, 94%); + margin-left: 20px; + padding-left: 5px; +} + +.props > .prop:nth-child(2n) { background-color: hsl(198, 100%, 94%); } @@ -910,15 +916,30 @@ div[data-twttr-id] iframe { font-size: 16px; } +.compactProps .propTitle { + font-size: 14px; + margin-bottom: 0; + margin-top: 0; +} + .prop { padding: 5px 10px; } +.compactProps .prop { + padding: 3px 10px; +} + .propType { font-weight: normal; font-size: 15px; } +.compactProps .propType { + font-weight: normal; + font-size: 13px; +} + #content { display: none;