From fa6e6ccc07293d08da50490081295f7bb2618931 Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Sat, 9 Apr 2016 11:12:46 -0700 Subject: [PATCH] Display component methods on the website and tweak the documentation Summary:The website now displays public methods on components. This was implemented mostly in react-docgen via #66. This adds a component that is used by the component and API doc pages to display documentation for a method. It also adds some missing documentation and tweak some existing one to integrate with this feature. I also prefixed some component methods with an '_' so they don't show up in the doc. **Test plan (required)** Tested every component page locally to make sure the methods doc was displayed properly. Tested an API page to make sure it still worked properly. Closes https://github.com/facebook/react-native/pull/6890 Differential Revision: D3159911 Pulled By: vjeux fb-gh-sync-id: 1e6a4640cda6794496d9844c1af6a1451c017dcc fbshipit-source-id: 1e6a4640cda6794496d9844c1af6a1451c017dcc --- .../DrawerLayoutAndroid.android.js | 8 +- .../Components/Navigation/NavigatorIOS.ios.js | 48 +++-- Libraries/Components/ScrollView/ScrollView.js | 10 +- Libraries/Components/StatusBar/StatusBar.js | 2 +- Libraries/Components/TextInput/TextInput.js | 6 + .../Touchable/TouchableHighlight.js | 6 +- .../TouchableNativeFeedback.android.js | 41 ++-- .../Components/Touchable/TouchableOpacity.js | 5 +- Libraries/Components/WebView/WebView.ios.js | 30 ++- .../CustomComponents/ListView/ListView.js | 9 +- .../CustomComponents/Navigator/Navigator.js | 65 ++++--- website/layout/AutodocsLayout.js | 184 ++++++++++++------ website/package.json | 2 +- website/server/extractDocs.js | 33 +++- 14 files changed, 297 insertions(+), 152 deletions(-) diff --git a/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js b/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js index 63944161b05ce2..114442d8ca656d 100644 --- a/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js +++ b/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js @@ -223,7 +223,10 @@ var DrawerLayoutAndroid = React.createClass({ } }, - openDrawer: function() { + /** + * Opens the drawer. + */ + openDrawer: function(test: number) { UIManager.dispatchViewManagerCommand( this._getDrawerLayoutHandle(), UIManager.AndroidDrawerLayout.Commands.openDrawer, @@ -231,6 +234,9 @@ var DrawerLayoutAndroid = React.createClass({ ); }, + /** + * Closes the drawer. + */ closeDrawer: function() { UIManager.dispatchViewManagerCommand( this._getDrawerLayoutHandle(), diff --git a/Libraries/Components/Navigation/NavigatorIOS.ios.js b/Libraries/Components/Navigation/NavigatorIOS.ios.js index 6ed33686076377..226de91f14d7ba 100644 --- a/Libraries/Components/Navigation/NavigatorIOS.ios.js +++ b/Libraries/Components/Navigation/NavigatorIOS.ios.js @@ -143,20 +143,6 @@ type Event = Object; * }); * ``` * - * A navigation object contains the following functions: - * - * - `push(route)` - Navigate forward to a new route - * - `pop()` - Go back one page - * - `popN(n)` - Go back N pages at once. When N=1, behavior matches `pop()` - * - `replace(route)` - Replace the route for the current page and immediately - * load the view for the new route - * - `replacePrevious(route)` - Replace the route/view for the previous page - * - `replacePreviousAndPop(route)` - Replaces the previous route/view and - * transitions back to it - * - `resetTo(route)` - Replaces the top item and popToTop - * - `popToRoute(route)` - Go back to the item for a particular route object - * - `popToTop()` - Go back to the top item - * * Navigator functions are also available on the NavigatorIOS component: * * ``` @@ -492,6 +478,9 @@ var NavigatorIOS = React.createClass({ this.navigationContext.emit('willfocus', {route: route}); }, + /** + * Navigate forward to a new route + */ push: function(route: Route) { invariant(!!route, 'Must supply route to push'); // Make sure all previous requests are caught up first. Otherwise reject. @@ -514,6 +503,9 @@ var NavigatorIOS = React.createClass({ } }, + /** + * Go back N pages at once. When N=1, behavior matches `pop()` + */ popN: function(n: number) { if (n === 0) { return; @@ -535,6 +527,9 @@ var NavigatorIOS = React.createClass({ } }, + /** + * Go back one page + */ pop: function() { this.popN(1); }, @@ -574,23 +569,30 @@ var NavigatorIOS = React.createClass({ }, /** - * Replaces the top of the navigation stack. + * Replace the route for the current page and immediately + * load the view for the new route. */ replace: function(route: Route) { this.replaceAtIndex(route, -1); }, /** - * Replace the current route's parent. + * Replace the route/view for the previous page. */ replacePrevious: function(route: Route) { this.replaceAtIndex(route, -2); }, + /** + * Go back to the top item + */ popToTop: function() { this.popToRoute(this.state.routeStack[0]); }, + /** + * Go back to the item for a particular route object + */ popToRoute: function(route: Route) { var indexOfRoute = this.state.routeStack.indexOf(route); invariant( @@ -601,6 +603,9 @@ var NavigatorIOS = React.createClass({ this.popN(numToPop); }, + /** + * Replaces the previous route/view and transitions back to it. + */ replacePreviousAndPop: function(route: Route) { // Make sure all previous requests are caught up first. Otherwise reject. if (this.state.requestedTopOfStack !== this.state.observedTopOfStack) { @@ -618,6 +623,9 @@ var NavigatorIOS = React.createClass({ }); }, + /** + * Replaces the top item and popToTop + */ resetTo: function(route: Route) { invariant(!!route, 'Must supply route to push'); // Make sure all previous requests are caught up first. Otherwise reject. @@ -628,7 +636,7 @@ var NavigatorIOS = React.createClass({ this.popToRoute(route); }, - handleNavigationComplete: function(e: Event) { + _handleNavigationComplete: function(e: Event) { if (this._toFocusOnNavigationComplete) { this._getFocusEmitter().emit('focus', this._toFocusOnNavigationComplete); this._toFocusOnNavigationComplete = null; @@ -663,7 +671,7 @@ var NavigatorIOS = React.createClass({ ); }, - renderNavigationStackItems: function() { + _renderNavigationStackItems: function() { var shouldRecurseToNavigator = this.state.makingNavigatorRequest || this.state.updatingAllIndicesAtOrBeyond !== null; @@ -678,7 +686,7 @@ var NavigatorIOS = React.createClass({ style={styles.transitioner} vertical={this.props.vertical} requestedTopOfStack={this.state.requestedTopOfStack} - onNavigationComplete={this.handleNavigationComplete}> + onNavigationComplete={this._handleNavigationComplete}> {items} @@ -688,7 +696,7 @@ var NavigatorIOS = React.createClass({ render: function() { return ( - {this.renderNavigationStackItems()} + {this._renderNavigationStackItems()} ); }, diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index 387ee46f94e14b..4e92411bba1c6e 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -341,6 +341,9 @@ var ScrollView = React.createClass({ this.refs[SCROLLVIEW].setNativeProps(props); }, + /** + * Deprecated. Use `RefreshControl` instead. + */ endRefreshing: function() { RCTScrollViewManager.endRefreshing( ReactNative.findNodeHandle(this) @@ -367,9 +370,10 @@ var ScrollView = React.createClass({ /** * Scrolls to a given x, y offset, either immediately or with a smooth animation. + * * Syntax: * - * scrollTo(options: {x: number = 0; y: number = 0; animated: boolean = true}) + * `scrollTo(options: {x: number = 0; y: number = 0; animated: boolean = true})` * * Note: The weird argument signature is due to the fact that, for historical reasons, * the function also accepts separate arguments as as alternative to the options object. @@ -397,7 +401,7 @@ var ScrollView = React.createClass({ this.scrollTo({x, y, animated: false}); }, - handleScroll: function(e: Object) { + _handleScroll: function(e: Object) { if (__DEV__) { if (this.props.onScroll && !this.props.scrollEventThrottle && Platform.OS === 'ios') { console.log( @@ -480,7 +484,7 @@ var ScrollView = React.createClass({ onStartShouldSetResponder: this.scrollResponderHandleStartShouldSetResponder, onStartShouldSetResponderCapture: this.scrollResponderHandleStartShouldSetResponderCapture, onScrollShouldSetResponder: this.scrollResponderHandleScrollShouldSetResponder, - onScroll: this.handleScroll, + onScroll: this._handleScroll, onResponderGrant: this.scrollResponderHandleResponderGrant, onResponderTerminationRequest: this.scrollResponderHandleTerminationRequest, onResponderTerminate: this.scrollResponderHandleTerminate, diff --git a/Libraries/Components/StatusBar/StatusBar.js b/Libraries/Components/StatusBar/StatusBar.js index 082512ee9492a6..3a7bb1a93955a6 100644 --- a/Libraries/Components/StatusBar/StatusBar.js +++ b/Libraries/Components/StatusBar/StatusBar.js @@ -164,7 +164,7 @@ const StatusBar = React.createClass({ StatusBarManager.setNetworkActivityIndicatorVisible(visible); }, - setBackgroundColor(color, animated?: boolean) { + setBackgroundColor(color: string, animated?: boolean) { if (Platform.OS !== 'android') { console.warn('`setBackgroundColor` is only available on Android'); return; diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index 1b34b88bb74882..44641d875af16d 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -319,6 +319,9 @@ var TextInput = React.createClass({ AndroidTextInput.viewConfig : {})) : Object), + /** + * Returns if the input is currently focused. + */ isFocused: function(): boolean { return TextInputState.currentlyFocusedField() === ReactNative.findNodeHandle(this.refs.input); @@ -368,6 +371,9 @@ var TextInput = React.createClass({ isInAParentText: React.PropTypes.bool }, + /** + * Removes all text from the input. + */ clear: function() { this.setNativeProps({text: ''}); }, diff --git a/Libraries/Components/Touchable/TouchableHighlight.js b/Libraries/Components/Touchable/TouchableHighlight.js index a15501bebba8e2..63314e408d9e00 100644 --- a/Libraries/Components/Touchable/TouchableHighlight.js +++ b/Libraries/Components/Touchable/TouchableHighlight.js @@ -94,7 +94,7 @@ var TouchableHighlight = React.createClass({ getDefaultProps: () => DEFAULT_PROPS, // Performance optimization to avoid constantly re-generating these objects. - computeSyntheticState: function(props) { + _computeSyntheticState: function(props) { return { activeProps: { style: { @@ -115,7 +115,7 @@ var TouchableHighlight = React.createClass({ getInitialState: function() { return merge( - this.touchableGetInitialState(), this.computeSyntheticState(this.props) + this.touchableGetInitialState(), this._computeSyntheticState(this.props) ); }, @@ -133,7 +133,7 @@ var TouchableHighlight = React.createClass({ if (nextProps.activeOpacity !== this.props.activeOpacity || nextProps.underlayColor !== this.props.underlayColor || nextProps.style !== this.props.style) { - this.setState(this.computeSyntheticState(nextProps)); + this.setState(this._computeSyntheticState(nextProps)); } }, diff --git a/Libraries/Components/Touchable/TouchableNativeFeedback.android.js b/Libraries/Components/Touchable/TouchableNativeFeedback.android.js index f174ab3d39cfa2..17a70652cb823c 100644 --- a/Libraries/Components/Touchable/TouchableNativeFeedback.android.js +++ b/Libraries/Components/Touchable/TouchableNativeFeedback.android.js @@ -83,36 +83,39 @@ var TouchableNativeFeedback = React.createClass({ /** * Determines the type of background drawable that's going to be used to * display feedback. It takes an object with `type` property and extra data - * depending on the `type`. It's recommended to use one of the following - * static methods to generate that dictionary: - * - * 1) TouchableNativeFeedback.SelectableBackground() - will create object - * that represents android theme's default background for selectable - * elements (?android:attr/selectableItemBackground) - * - * 2) TouchableNativeFeedback.SelectableBackgroundBorderless() - will create - * object that represent android theme's default background for borderless - * selectable elements (?android:attr/selectableItemBackgroundBorderless). - * Available on android API level 21+ - * - * 3) TouchableNativeFeedback.Ripple(color, borderless) - will create - * object that represents ripple drawable with specified color (as a - * string). If property `borderless` evaluates to true the ripple will - * render outside of the view bounds (see native actionbar buttons as an - * example of that behavior). This background type is available on Android - * API level 21+ + * depending on the `type`. It's recommended to use one of the static + * methods to generate that dictionary. */ background: backgroundPropType, }, statics: { + /** + * Creates an object that represents android theme's default background for + * selectable elements (?android:attr/selectableItemBackground). + */ SelectableBackground: function() { return {type: 'ThemeAttrAndroid', attribute: 'selectableItemBackground'}; }, + /** + * Creates an object that represent android theme's default background for borderless + * selectable elements (?android:attr/selectableItemBackgroundBorderless). + * Available on android API level 21+. + */ SelectableBackgroundBorderless: function() { return {type: 'ThemeAttrAndroid', attribute: 'selectableItemBackgroundBorderless'}; }, - Ripple: function(color, borderless) { + /** + * Creates an object that represents ripple drawable with specified color (as a + * string). If property `borderless` evaluates to true the ripple will + * render outside of the view bounds (see native actionbar buttons as an + * example of that behavior). This background type is available on Android + * API level 21+. + * + * @param color The ripple color + * @param borderless If the ripple can render outside it's bounds + */ + Ripple: function(color: string, borderless: boolean) { return {type: 'RippleAndroid', color: processColor(color), borderless: borderless}; }, }, diff --git a/Libraries/Components/Touchable/TouchableOpacity.js b/Libraries/Components/Touchable/TouchableOpacity.js index 29ceda28947ea1..f6ac619f49b3be 100644 --- a/Libraries/Components/Touchable/TouchableOpacity.js +++ b/Libraries/Components/Touchable/TouchableOpacity.js @@ -81,7 +81,10 @@ var TouchableOpacity = React.createClass({ ensurePositiveDelayProps(nextProps); }, - setOpacityTo: function(value) { + /** + * Animate the touchable to a new opacity. + */ + setOpacityTo: function(value: number) { Animated.timing( this.state.anim, {toValue: value, duration: 150} diff --git a/Libraries/Components/WebView/WebView.ios.js b/Libraries/Components/WebView/WebView.ios.js index 60abb8ffac01c9..814c1e20d75a8c 100644 --- a/Libraries/Components/WebView/WebView.ios.js +++ b/Libraries/Components/WebView/WebView.ios.js @@ -312,9 +312,9 @@ var WebView = React.createClass({ decelerationRate={decelerationRate} contentInset={this.props.contentInset} automaticallyAdjustContentInsets={this.props.automaticallyAdjustContentInsets} - onLoadingStart={this.onLoadingStart} - onLoadingFinish={this.onLoadingFinish} - onLoadingError={this.onLoadingError} + onLoadingStart={this._onLoadingStart} + onLoadingFinish={this._onLoadingFinish} + onLoadingError={this._onLoadingError} onShouldStartLoadWithRequest={onShouldStartLoadWithRequest} scalesPageToFit={this.props.scalesPageToFit} allowsInlineMediaPlayback={this.props.allowsInlineMediaPlayback} @@ -329,6 +329,9 @@ var WebView = React.createClass({ ); }, + /** + * Go forward one page in the webview's history. + */ goForward: function() { UIManager.dispatchViewManagerCommand( this.getWebViewHandle(), @@ -337,6 +340,9 @@ var WebView = React.createClass({ ); }, + /** + * Go back one page in the webview's history. + */ goBack: function() { UIManager.dispatchViewManagerCommand( this.getWebViewHandle(), @@ -345,6 +351,9 @@ var WebView = React.createClass({ ); }, + /** + * Reloads the current page. + */ reload: function() { UIManager.dispatchViewManagerCommand( this.getWebViewHandle(), @@ -357,23 +366,26 @@ var WebView = React.createClass({ * We return an event with a bunch of fields including: * url, title, loading, canGoBack, canGoForward */ - updateNavigationState: function(event: Event) { + _updateNavigationState: function(event: Event) { if (this.props.onNavigationStateChange) { this.props.onNavigationStateChange(event.nativeEvent); } }, + /** + * Returns the native webview node. + */ getWebViewHandle: function(): any { return ReactNative.findNodeHandle(this.refs[RCT_WEBVIEW_REF]); }, - onLoadingStart: function(event: Event) { + _onLoadingStart: function(event: Event) { var onLoadStart = this.props.onLoadStart; onLoadStart && onLoadStart(event); - this.updateNavigationState(event); + this._updateNavigationState(event); }, - onLoadingError: function(event: Event) { + _onLoadingError: function(event: Event) { event.persist(); // persist this event because we need to store it var {onError, onLoadEnd} = this.props; onError && onError(event); @@ -386,14 +398,14 @@ var WebView = React.createClass({ }); }, - onLoadingFinish: function(event: Event) { + _onLoadingFinish: function(event: Event) { var {onLoad, onLoadEnd} = this.props; onLoad && onLoad(event); onLoadEnd && onLoadEnd(event); this.setState({ viewState: WebViewState.IDLE, }); - this.updateNavigationState(event); + this._updateNavigationState(event); }, }); diff --git a/Libraries/CustomComponents/ListView/ListView.js b/Libraries/CustomComponents/ListView/ListView.js index 93ead1501704b4..9b2b0f803e64a1 100644 --- a/Libraries/CustomComponents/ListView/ListView.js +++ b/Libraries/CustomComponents/ListView/ListView.js @@ -252,6 +252,11 @@ var ListView = React.createClass({ this.refs[SCROLLVIEW_REF].getScrollResponder(); }, + /** + * Scrolls to a given x, y offset, either immediately or with a smooth animation. + * + * See `ScrollView#scrollTo`. + */ scrollTo: function(...args) { this.refs[SCROLLVIEW_REF] && this.refs[SCROLLVIEW_REF].scrollTo && @@ -334,7 +339,7 @@ var ListView = React.createClass({ }); }, - onRowHighlighted: function(sectionID, rowID) { + _onRowHighlighted: function(sectionID, rowID) { this.setState({highlightedRow: {sectionID, rowID}}); }, @@ -400,7 +405,7 @@ var ListView = React.createClass({ dataSource.getRowData(sectionIdx, rowIdx), sectionID, rowID, - this.onRowHighlighted + this._onRowHighlighted )} />; bodyComponents.push(row); diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js index 365b6919956210..45658d998cc1f4 100644 --- a/Libraries/CustomComponents/Navigator/Navigator.js +++ b/Libraries/CustomComponents/Navigator/Navigator.js @@ -42,7 +42,6 @@ var TimerMixin = require('react-timer-mixin'); var View = require('View'); var clamp = require('clamp'); -var deprecatedPropType = require('deprecatedPropType'); var flattenStyle = require('flattenStyle'); var invariant = require('fbjs/lib/invariant'); var rebound = require('rebound'); @@ -159,30 +158,6 @@ var GESTURE_ACTIONS = [ * } * /> * ``` - * - * ### Navigator Methods - * - * If you have a ref to the Navigator element, you can invoke several methods - * on it to trigger navigation: - * - * - `getCurrentRoutes()` - returns the current list of routes - * - `jumpBack()` - Jump backward without unmounting the current scene - * - `jumpForward()` - Jump forward to the next scene in the route stack - * - `jumpTo(route)` - Transition to an existing scene without unmounting - * - `push(route)` - Navigate forward to a new scene, squashing any scenes - * that you could `jumpForward` to - * - `pop()` - Transition back and unmount the current scene - * - `replace(route)` - Replace the current scene with a new route - * - `replaceAtIndex(route, index)` - Replace a scene as specified by an index - * - `replacePrevious(route)` - Replace the previous scene - * - `resetTo(route)` - Navigate to a new scene and reset route stack - * - `immediatelyResetRouteStack(routeStack)` - Reset every scene with an - * array of routes - * - `popToRoute(route)` - Pop to a particular scene, as specified by its - * route. All scenes after it will be unmounted - * - `popToTop()` - Pop to the first scene in the stack, unmounting every - * other scene - * */ var Navigator = React.createClass({ @@ -366,6 +341,8 @@ var Navigator = React.createClass({ }, /** + * Reset every scene with an array of routes. + * * @param {RouteStack} nextRouteStack Next route stack to reinitialize. This * doesn't accept stack item `id`s, which implies that all existing items are * destroyed, and then potentially recreated according to `routeStack`. Does @@ -898,6 +875,9 @@ var Navigator = React.createClass({ this._transitionTo(destIndex); }, + /** + * Transition to an existing scene without unmounting + */ jumpTo: function(route) { var destIndex = this.state.routeStack.indexOf(route); invariant( @@ -907,14 +887,24 @@ var Navigator = React.createClass({ this._jumpN(destIndex - this.state.presentedIndex); }, + /** + * Jump forward to the next scene in the route stack. + */ jumpForward: function() { this._jumpN(1); }, + /** + * Jump backward without unmounting the current scene. + */ jumpBack: function() { this._jumpN(-1); }, + /** + * Navigate forward to a new scene, squashing any scenes that you could + * `jumpForward` to. + */ push: function(route) { invariant(!!route, 'Must supply route to push'); var activeLength = this.state.presentedIndex + 1; @@ -956,6 +946,9 @@ var Navigator = React.createClass({ ); }, + /** + * Transition back and unmount the current scene. + */ pop: function() { if (this.state.transitionQueue.length) { // This is the workaround to prevent user from firing multiple `pop()` @@ -973,7 +966,7 @@ var Navigator = React.createClass({ }, /** - * Replace a route in the navigation stack. + * Replace a scene as specified by an index * * `index` specifies the route in the stack that should be replaced. * If it's negative, it counts from the back. @@ -1008,23 +1001,30 @@ var Navigator = React.createClass({ }, /** - * Replaces the current scene in the stack. + * Replace the current scene with a new route. */ replace: function(route) { this.replaceAtIndex(route, this.state.presentedIndex); }, /** - * Replace the current route's parent. + * Replace the previous scene. */ replacePrevious: function(route) { this.replaceAtIndex(route, this.state.presentedIndex - 1); }, + /** + * Pop to the first scene in the stack, unmounting every other scene. + */ popToTop: function() { this.popToRoute(this.state.routeStack[0]); }, + /** + * Pop to a particular scene, as specified by its route. + * All scenes after it will be unmounted. + */ popToRoute: function(route) { var indexOfRoute = this.state.routeStack.indexOf(route); invariant( @@ -1035,6 +1035,9 @@ var Navigator = React.createClass({ this._popN(numToPop); }, + /** + * Replace the previous scene and pop to it. + */ replacePreviousAndPop: function(route) { if (this.state.routeStack.length < 2) { return; @@ -1043,6 +1046,9 @@ var Navigator = React.createClass({ this.pop(); }, + /** + * Navigate to a new scene and reset route stack. + */ resetTo: function(route) { invariant(!!route, 'Must supply route to push'); this.replaceAtIndex(route, 0, () => { @@ -1054,6 +1060,9 @@ var Navigator = React.createClass({ }); }, + /** + * Returns the current list of routes. + */ getCurrentRoutes: function() { // Clone before returning to avoid caller mutating the stack return this.state.routeStack.slice(); diff --git a/website/layout/AutodocsLayout.js b/website/layout/AutodocsLayout.js index b73b54a0728a2c..97b084ac9e2702 100644 --- a/website/layout/AutodocsLayout.js +++ b/website/layout/AutodocsLayout.js @@ -37,12 +37,22 @@ function renderType(type) { return 'enum(' + type.value.map((v) => renderEnumValue(v.value)).join(', ') + ')'; } + if (type.name === '$Enum') { + if (type.elements[0].signature.properties) { + return type.elements[0].signature.properties.map(p => `'${p.key}'`).join(' | '); + } + return type.name; + } + if (type.name === 'shape') { return '{' + Object.keys(type.value).map((key => key + ': ' + renderType(type.value[key]))).join(', ') + '}'; } if (type.name === 'union') { - return type.value.map(renderType).join(', '); + if (type.value) { + return type.value.map(renderType).join(', '); + } + return type.elements.map(renderType).join(' | '); } if (type.name === 'arrayOf') { @@ -74,6 +84,11 @@ function renderType(type) { if (type.name === 'func') { return 'function'; } + + if (type.name === 'signature') { + return type.raw; + } + return type.name; } @@ -106,6 +121,18 @@ function sortByPlatform(props, nameA, nameB) { return 0; } +function removeCommentsFromDocblock(docblock) { + return docblock + .trim('\n ') + .replace(/^\/\*+/, '') + .replace(/\*\/$/, '') + .split('\n') + .map(function(line) { + return line.trim().replace(/^\* ?/, ''); + }) + .join('\n'); +} + var ComponentDoc = React.createClass({ renderProp: function(name, prop) { return ( @@ -226,6 +253,35 @@ var ComponentDoc = React.createClass({ } }, + renderMethod: function(method) { + return ( + + ); + }, + + renderMethods: function(methods) { + if (!methods || !methods.length) { + return null; + } + return ( + + Methods +
+ {methods.filter((method) => { + return method.name[0] !== '_'; + }).map(this.renderMethod)} +
+
+ ); + }, + render: function() { var content = this.props.content; this.extractPlatformFromProps(content.props); @@ -236,71 +292,23 @@ var ComponentDoc = React.createClass({ Props {this.renderProps(content.props, content.composes)} + {this.renderMethods(content.methods)} ); } }); var APIDoc = React.createClass({ - removeCommentsFromDocblock: function(docblock) { - return docblock - .trim('\n ') - .replace(/^\/\*+/, '') - .replace(/\*\/$/, '') - .split('\n') - .map(function(line) { - return line.trim().replace(/^\* ?/, ''); - }) - .join('\n'); - }, - - renderTypehintRec: function(typehint) { - if (typehint.type === 'simple') { - return typehint.value; - } - - if (typehint.type === 'generic') { - return this.renderTypehintRec(typehint.value[0]) + '<' + this.renderTypehintRec(typehint.value[1]) + '>'; - } - - return JSON.stringify(typehint); - - }, - - renderTypehint: function(typehint) { - try { - var typehint = JSON.parse(typehint); - } catch(e) { - return typehint; - } - - return this.renderTypehintRec(typehint); - }, renderMethod: function(method) { return ( -
-
- {method.modifiers.length && - {method.modifiers.join(' ') + ' '} - || ''} - {method.name} - - ({method.params - .map((param) => { - var res = param.name; - if (param.typehint) { - res += ': ' + this.renderTypehint(param.typehint); - } - return res; - }) - .join(', ')}) - -
- {method.docblock && - {this.removeCommentsFromDocblock(method.docblock)} - } -
+ ); }, @@ -332,7 +340,7 @@ var APIDoc = React.createClass({ } {property.docblock && - {this.removeCommentsFromDocblock(property.docblock)} + {removeCommentsFromDocblock(property.docblock)} } ); @@ -371,7 +379,7 @@ var APIDoc = React.createClass({
    {cls.docblock && - {this.removeCommentsFromDocblock(cls.docblock)} + {removeCommentsFromDocblock(cls.docblock)} } {this.renderMethods(cls.methods)} {this.renderProperties(cls.properties)} @@ -394,7 +402,7 @@ var APIDoc = React.createClass({ return (
    - {this.removeCommentsFromDocblock(content.docblock)} + {removeCommentsFromDocblock(content.docblock)} {this.renderMethods(content.methods)} {this.renderProperties(content.properties)} @@ -404,6 +412,62 @@ var APIDoc = React.createClass({ } }); +var Method = React.createClass({ + renderTypehintRec: function(typehint) { + if (typehint.type === 'simple') { + return typehint.value; + } + + if (typehint.type === 'generic') { + return this.renderTypehintRec(typehint.value[0]) + '<' + this.renderTypehintRec(typehint.value[1]) + '>'; + } + + return JSON.stringify(typehint); + + }, + + renderTypehint: function(typehint) { + if (typeof typehint === 'object' && typehint.name) { + return renderType(typehint); + } + try { + var typehint = JSON.parse(typehint); + } catch (e) { + return typehint; + } + + return this.renderTypehintRec(typehint); + }, + + render: function() { + return ( +
    +
    + {this.props.modifiers.length && + {this.props.modifiers.join(' ') + ' '} + || ''} + {this.props.name} + + ({this.props.params + .map((param) => { + var res = param.name; + if (param.type) { + res += ': ' + this.renderTypehint(param.type); + } + return res; + }) + .join(', ')}) + {this.props.returns && ': ' + this.renderTypehint(this.props.returns.type)} + +
    + {this.props.description && + {this.props.description} + } +
    + ); + }, +}); + var EmbeddedSimulator = React.createClass({ render: function() { if (!this.props.shouldRender) { diff --git a/website/package.json b/website/package.json index 31600f115dcc0e..270037f3cadfdf 100644 --- a/website/package.json +++ b/website/package.json @@ -13,7 +13,7 @@ "mkdirp": "^0.5.1", "optimist": "0.6.0", "react": "~0.13.0", - "react-docgen": "^2.0.1", + "react-docgen": "^2.8.0", "react-page-middleware": "git://github.com/facebook/react-page-middleware.git", "request": "^2.69.0", "semver-compare": "^1.0.0" diff --git a/website/server/extractDocs.js b/website/server/extractDocs.js index 6b8c14ad5968ea..602eaf2c276380 100644 --- a/website/server/extractDocs.js +++ b/website/server/extractDocs.js @@ -68,12 +68,36 @@ function getExample(componentName, componentPlatform) { }; } +// Add methods that should not appear in the components documentation. +var methodsBlacklist = [ + // Native methods mixin. + 'getInnerViewNode', + 'setNativeProps', + // Touchable mixin. + 'touchableHandlePress' , + 'touchableHandleActivePressIn', + 'touchableHandleActivePressOut', + 'touchableHandleLongPress', + 'touchableGetPressRectOffset', + 'touchableGetHitSlop', + 'touchableGetHighlightDelayMS', + 'touchableGetLongPressDelayMS', + 'touchableGetPressOutDelayMS', + // Scrollable mixin. + 'getScrollableNode', + 'getScrollResponder', +]; + +function filterMethods(method) { + return method.name[0] !== '_' && methodsBlacklist.indexOf(method.name) === -1; +} + // Determines whether a component should have a link to a runnable example function isRunnable(componentName, componentPlatform) { var path = '../Examples/UIExplorer/' + componentName + 'Example.js'; if (!fs.existsSync(path)) { - path = '../Examples/UIExplorer/' + componentName + 'Example.'+ componentPlatform +'.js'; + path = '../Examples/UIExplorer/' + componentName + 'Example.' + componentPlatform + '.js'; if (!fs.existsSync(path)) { return false; } @@ -92,9 +116,6 @@ function shouldDisplayInSidebar(componentName) { } function getNextComponent(i) { - var next; - var filepath = all[i]; - if (all[i + 1]) { var nextComponentName = getNameFromPath(all[i + 1]); @@ -125,6 +146,10 @@ function componentsToMarkdown(type, json, filepath, i, styles) { } json.example = getExample(componentName, componentPlatform); + if (json.methods) { + json.methods = json.methods.filter(filterMethods); + } + // Put Flexbox into the Polyfills category var category = (type === 'style' ? 'Polyfills' : type + 's'); var next = getNextComponent(i);