diff --git a/.buckconfig b/.buckconfig index 934256cb29d4a3..9f6c686b2fc18a 100644 --- a/.buckconfig +++ b/.buckconfig @@ -4,3 +4,6 @@ [maven_repositories] central = https://repo1.maven.org/maven2 + +[alias] + movies = //Examples/Movies/android/app:app diff --git a/.eslintrc b/.eslintrc index 8c33d4d3842db6..d707517a0d6271 100644 --- a/.eslintrc +++ b/.eslintrc @@ -113,7 +113,6 @@ "no-caller": 1, // disallow use of arguments.caller or arguments.callee "no-div-regex": 1, // disallow division operators explicitly at beginning of regular expression (off by default) "no-else-return": 0, // disallow else after a return in an if (off by default) - "no-empty-label": 1, // disallow use of labels for anything other then loops and switches "no-eq-null": 0, // disallow comparisons to null without a type-checking operator (off by default) "no-eval": 1, // disallow use of eval() "no-extend-native": 1, // disallow adding to native types @@ -182,6 +181,8 @@ // These rules are purely matters of style and are quite subjective. "key-spacing": 0, + "keyword-spacing": 1, // enforce spacing before and after keywords + "jsx-quotes": [1, "prefer-double"], "comma-spacing": 0, "no-multi-spaces": 0, "brace-style": 0, // enforce one true brace style (off by default) @@ -205,11 +206,9 @@ "quote-props": 0, // require quotes around object literal property names (off by default) "semi": 1, // require or disallow use of semicolons instead of ASI "sort-vars": 0, // sort variables within the same declaration block (off by default) - "space-after-keywords": 1, // require a space after certain keywords (off by default) "space-in-brackets": 0, // require or disallow spaces inside brackets (off by default) "space-in-parens": 0, // require or disallow spaces inside parentheses (off by default) "space-infix-ops": 1, // require spaces around operators - "space-return-throw-case": 1, // require a space after return, throw, and case "space-unary-ops": [1, { "words": true, "nonwords": false }], // require or disallow spaces before/after unary operators (words on by default, nonwords off by default) "max-nested-callbacks": 0, // specify the maximum depth callbacks can be nested (off by default) "one-var": 0, // allow just one var statement per function (off by default) @@ -227,7 +226,6 @@ "react/display-name": 0, "react/jsx-boolean-value": 0, - "react/jsx-quotes": [1, "double", "avoid-escape"], "react/jsx-no-undef": 1, "react/jsx-sort-props": 0, "react/jsx-uses-react": 0, diff --git a/.flowconfig b/.flowconfig index 78c0582045e172..f3270cc7fd9c6e 100644 --- a/.flowconfig +++ b/.flowconfig @@ -15,11 +15,8 @@ # Ignore react and fbjs where there are overlaps, but don't ignore # anything that react-native relies on .*/node_modules/fbjs/lib/Map.js -.*/node_modules/fbjs/lib/Promise.js .*/node_modules/fbjs/lib/fetch.js .*/node_modules/fbjs/lib/ExecutionEnvironment.js -.*/node_modules/fbjs/lib/isEmpty.js -.*/node_modules/fbjs/lib/crc32.js .*/node_modules/fbjs/lib/ErrorUtils.js # Flow has a built-in definition for the 'react' module which we prefer to use @@ -28,6 +25,11 @@ .*/node_modules/react/lib/React.js .*/node_modules/react/lib/ReactDOM.js +.*/__mocks__/.* +.*/__tests__/.* + +.*/commoner/test/source/widget/share.js + # Ignore commoner tests .*/node_modules/commoner/test/.* @@ -40,14 +42,35 @@ # Ignore Website .*/website/.* +.*/node_modules/is-my-json-valid/test/.*\.json +.*/node_modules/iconv-lite/encodings/tables/.*\.json +.*/node_modules/y18n/test/.*\.json +.*/node_modules/spdx-license-ids/spdx-license-ids.json +.*/node_modules/spdx-exceptions/index.json +.*/node_modules/resolve/test/subdirs/node_modules/a/b/c/x.json +.*/node_modules/resolve/lib/core.json +.*/node_modules/jsonparse/samplejson/.*\.json +.*/node_modules/json5/test/.*\.json +.*/node_modules/ua-parser-js/test/.*\.json +.*/node_modules/builtin-modules/builtin-modules.json +.*/node_modules/binary-extensions/binary-extensions.json +.*/node_modules/url-regex/tlds.json +.*/node_modules/joi/.*\.json +.*/build/.*\.json +.*/\.buckd/.* + [include] [libs] Libraries/react-native/react-native-interface.js +flow/ [options] module.system=haste +esproposal.class_static_fields=enable +esproposal.class_instance_fields=enable + munge_underscores=true module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub' @@ -57,9 +80,9 @@ suppress_type=$FlowIssue suppress_type=$FlowFixMe suppress_type=$FixMe -suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-1]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) -suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-1]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ +suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-2]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) +suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-2]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy [version] -0.21.0 +0.22.0 diff --git a/.gitignore b/.gitignore index fe61f843f91de6..cd545b912fdc72 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,8 @@ project.xcworkspace # Buck .buckd buck-out +/ReactAndroid/src/main/jni/prebuilt/lib/armeabi-v7a/ +/ReactAndroid/src/main/jni/prebuilt/lib/x86/ # Android .idea diff --git a/.travis.yml b/.travis.yml index 67ac1cc42ecd4e..f76d6bad0337de 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,11 +2,6 @@ language: objective-c osx_image: xcode7.2 -cache: - directories: - - node_modules - - .nvm - install: - brew reinstall nvm - mkdir -p .nvm @@ -15,7 +10,7 @@ install: - nvm install 5 - rm -Rf "${TMPDIR}/jest_preprocess_cache" - npm config set spin=false - - npm install -g flow-bin@`node -p "require('fs').readFileSync('.flowconfig', 'utf8').split('[version]')[1].trim()"` + - npm config set progress=false - npm install script: @@ -23,43 +18,30 @@ script: if [ "$TEST_TYPE" = objc ] then - ./scripts/objc-test.sh + travis_retry ./scripts/objc-test.sh elif [ "$TEST_TYPE" = js ] then npm install github@0.2.4 - cat <(echo eslint; npm run lint --silent -- --format=json; echo flow; flow --json) | GITHUB_TOKEN="af6ef0d15709bc91d""06a6217a5a826a226fb57b7" node bots/code-analysis-bot.js - flow check && npm test -- '\/Libraries\/' - - elif [ "$TEST_TYPE" = packager ] - then - - npm test -- '\/packager\/' - - elif [ "$TEST_TYPE" = cli ] + cat <(echo eslint; npm run lint --silent -- --format=json; echo flow; npm run flow --silent -- --json) | GITHUB_TOKEN="af6ef0d15709bc91d""06a6217a5a826a226fb57b7" node bots/code-analysis-bot.js + npm run flow && npm test + # testing js e2e with npm3 + npm install -g npm@3 + npm --version + ./scripts/e2e-test.sh --packager + # testing js e2e with npm2 + rm -rf node_modules + npm install -g npm@2 + npm install + npm --version + ./scripts/e2e-test.sh --packager + + elif [ "$TEST_TYPE" = e2e-objc ] then - npm test -- '\/(local|private|react-native)-cli\/' + travis_retry ./scripts/e2e-test.sh --ios - elif [ "$TEST_TYPE" = build_website ] - then - - cd website - $(which npm) install - ./setup.sh - if [ "$TRAVIS_PULL_REQUEST" = false ] && [ "$TRAVIS_BRANCH" = master ]; then - # Automatically publish the website - echo "machine github.com login reactjs-bot password $GITHUB_TOKEN" >~/.netrc - ./publish.sh - else - # Make sure the website builds without error - node server/generate.js - fi - - elif [ "$TEST_TYPE" = e2e ] - then - ./scripts/e2e-test.sh else echo "Unknown test type: $TEST_TYPE" exit 1 @@ -69,19 +51,16 @@ env: matrix: - TEST_TYPE=objc - TEST_TYPE=js - - TEST_TYPE=packager - - TEST_TYPE=cli - - TEST_TYPE=build_website - - TEST_TYPE=e2e - global: - # $GITHUB_TOKEN - - secure: "HlmG8M2DmBUSBh6KH1yVIe/8gR4iibg4WfcHq1x/xYQxGbvleq7NOo04V6eFHnl9cvZCu+PKH0841WLnGR7c4BBf47GVu/o16nXzggPumHKy++lDzxFPlJ1faMDfjg/5vjbAxRUe7D3y98hQSeGHH4tedc8LvTaFLVu7iiGqvjU=" - # $APPETIZE_TOKEN - - secure: "egsvVSpszTzrNd6bN62DsVAzMiSZI/OHgdizfPryqvqWBf655ztE6XFQSEFNpuIAzSKDDF25ioT8iPfVsbC1iK6HDWHfmqYxML0L+OoU0gi+hV2oKUBFZDZ1fwSnFoWuBdNdMDpLlUxvJp6N1WyfNOB2dxuZUt8eTt48Hi3+Hpc=" - # $S3_TOKEN - - secure: "lY8JZPA0A7zT7L5KF9BBg34XYWIeR/RJiEvE7l7oVr88KnEPtyd//79eHhhVKnUnav7zsk5QJwkcX0MxKTp/dp4G0Am+zOX+sfA8kQrJ+2/+FzFW7AEsW/kHByfaIEIly9DQvUFt4I4oMm8nQZysJLahDgNWglyI3RTuJp//hcY=" + - TEST_TYPE=e2e-objc branches: only: - master - /^.*-stable$/ + +notifications: + email: + recipients: + - bestander@gmail.com + on_failure: change + on_success: change diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3171208c89d3cb..ee69e85cea7199 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,7 +17,7 @@ The core team will be monitoring for pull requests. When we get one, we'll run s *Before* submitting a pull request, please make sure the following is done… 1. Fork the repo and create your branch from `master`. -2. If you've added code that should be tested, add tests! +2. **Describe your test plan in your commit.** If you've added code that should be tested, add tests! 3. If you've changed APIs, update the documentation. 4. Add the copyright notice to the top of any new files you've added. 5. Ensure tests pass on Travis and Circle CI. @@ -73,6 +73,7 @@ Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe #### General +* **Most important: Look around.** Match the style you see used in the rest of the project. This includes formatting, naming things in code, naming things in documentation. * Add trailing commas, * 2 spaces for indentation (no tabs) * "Attractive" diff --git a/Examples/2048/Game2048.js b/Examples/2048/Game2048.js index 239d1dadeca4f9..a6c97a3e3edaa5 100644 --- a/Examples/2048/Game2048.js +++ b/Examples/2048/Game2048.js @@ -53,6 +53,8 @@ class Board extends React.Component { } class Tile extends React.Component { + state: any; + static _getPosition(index): number { return BOARD_PADDING + (index * (CELL_SIZE + CELL_MARGIN * 2) + CELL_MARGIN); } @@ -147,6 +149,7 @@ class GameEndOverlay extends React.Component { class Game2048 extends React.Component { startX: number; startY: number; + state: any; constructor(props: {}) { super(props); diff --git a/Examples/Movies/SearchScreen.js b/Examples/Movies/SearchScreen.js index ad1f9b72bfdd64..2e681ed34f50b5 100644 --- a/Examples/Movies/SearchScreen.js +++ b/Examples/Movies/SearchScreen.js @@ -27,7 +27,7 @@ var { } = React; var TimerMixin = require('react-timer-mixin'); -var invariant = require('invariant'); +var invariant = require('fbjs/lib/invariant'); var dismissKeyboard = require('dismissKeyboard'); var MovieCell = require('./MovieCell'); diff --git a/Examples/Movies/android/app/BUCK b/Examples/Movies/android/app/BUCK new file mode 100644 index 00000000000000..25c73fd5441544 --- /dev/null +++ b/Examples/Movies/android/app/BUCK @@ -0,0 +1,43 @@ +include_defs('//ReactAndroid/DEFS') + +android_binary( + name = 'app', + manifest = 'src/main/AndroidManifest.xml', + keystore = '//keystores:debug', + deps = [ + ':movies-lib', + ], +) + +android_library( + name = 'movies-lib', + srcs = glob(['src/main/java/**/*.java']), + deps = [ + react_native_target('java/com/facebook/csslayout:csslayout'), + react_native_target('java/com/facebook/react:react'), + react_native_target('java/com/facebook/react/devsupport:devsupport'), + react_native_target('java/com/facebook/react/modules/core:core'), + react_native_target('java/com/facebook/react/shell:shell'), + react_native_target('java/com/facebook/react/touch:touch'), + react_native_target('java/com/facebook/react/uimanager:uimanager'), + react_native_target('java/com/facebook/react/uimanager/annotations:annotations'), + react_native_target('java/com/facebook/react/views/image:image'), + react_native_target('java/com/facebook/react/views/recyclerview:recyclerview'), + react_native_target('java/com/facebook/react/views/scroll:scroll'), + react_native_target('java/com/facebook/react/views/text:text'), + react_native_target('java/com/facebook/react/views/view:view'), + # .so files are prebuilt by Gradle with `./gradlew :ReactAndroid:packageReactNdkLibsForBuck` + react_native_target('jni/prebuilt:reactnative-libs'), + react_native_target('jni/prebuilt:android-jsc'), + react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'), + react_native_dep('third-party/java/jsr-305:jsr-305'), + ':res', + ], +) + + +android_resource( + name = 'res', + res = 'src/main/res', + package = 'com.facebook.react.movies', +) diff --git a/Examples/Movies/android/app/src/main/AndroidManifest.xml b/Examples/Movies/android/app/src/main/AndroidManifest.xml index 185ffa5060219d..8aaec60819da5b 100644 --- a/Examples/Movies/android/app/src/main/AndroidManifest.xml +++ b/Examples/Movies/android/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ + getPackages() { + return Arrays.asList( + new MainReactPackage() + ); } } diff --git a/Examples/TicTacToe/TicTacToeApp.js b/Examples/TicTacToe/TicTacToeApp.js index 6d5c460cbea30a..3b562194a04427 100755 --- a/Examples/TicTacToe/TicTacToeApp.js +++ b/Examples/TicTacToe/TicTacToeApp.js @@ -305,7 +305,7 @@ var styles = StyleSheet.create({ textAlign: 'center', }, newGame: { - backgroundColor: '#887766', + backgroundColor: '#887765', padding: 20, borderRadius: 5, }, diff --git a/Examples/UIExplorer/ActionSheetIOSExample.js b/Examples/UIExplorer/ActionSheetIOSExample.js index ccc86fba4a50d3..b749400c037ab2 100644 --- a/Examples/UIExplorer/ActionSheetIOSExample.js +++ b/Examples/UIExplorer/ActionSheetIOSExample.js @@ -20,6 +20,7 @@ var { ActionSheetIOS, StyleSheet, Text, + UIManager, View, } = React; @@ -98,7 +99,6 @@ var ActionSheetTintExample = React.createClass({ } }); - var ShareActionSheetExample = React.createClass({ getInitialState() { return { @@ -121,16 +121,14 @@ var ShareActionSheetExample = React.createClass({ showShareActionSheet() { ActionSheetIOS.showShareActionSheetWithOptions({ - url: 'https://code.facebook.com', + url: this.props.url, message: 'message to go with the shared url', subject: 'a subject to go in the email heading', excludedActivityTypes: [ 'com.apple.UIKit.activity.PostToTwitter' ] }, - (error) => { - console.error(error); - }, + (error) => alert(error), (success, method) => { var text; if (success) { @@ -143,6 +141,50 @@ var ShareActionSheetExample = React.createClass({ } }); +var ShareScreenshotExample = React.createClass({ + getInitialState() { + return { + text: '' + }; + }, + + render() { + return ( + + + Click to show the Share ActionSheet + + + {this.state.text} + + + ); + }, + + showShareActionSheet() { + // Take the snapshot (returns a temp file uri) + UIManager.takeSnapshot('window').then((uri) => { + // Share image data + ActionSheetIOS.showShareActionSheetWithOptions({ + url: uri, + excludedActivityTypes: [ + 'com.apple.UIKit.activity.PostToTwitter' + ] + }, + (error) => alert(error), + (success, method) => { + var text; + if (success) { + text = `Shared via ${method}`; + } else { + text = 'You didn\'t share'; + } + this.setState({text}); + }); + }).catch((error) => alert(error)); + } +}); + var style = StyleSheet.create({ button: { marginBottom: 10, @@ -163,6 +205,20 @@ exports.examples = [ }, { title: 'Show Share Action Sheet', - render(): ReactElement { return ; } + render(): ReactElement { + return ; + } + }, + { + title: 'Share Local Image', + render(): ReactElement { + return ; + } + }, + { + title: 'Share Screenshot', + render(): ReactElement { + return ; + } } ]; diff --git a/Examples/UIExplorer/AlertIOSExample.js b/Examples/UIExplorer/AlertIOSExample.js index a52c1ab79521c3..d7f0bcf0d464fa 100644 --- a/Examples/UIExplorer/AlertIOSExample.js +++ b/Examples/UIExplorer/AlertIOSExample.js @@ -37,7 +37,7 @@ exports.examples = [{ }, { title: 'Prompt Options', - render(): React.Component { + render(): ReactElement { return ; } }, @@ -85,9 +85,13 @@ exports.examples = [{ }]; class PromptOptions extends React.Component { + state: any; + customButtons: Array; + constructor(props) { super(props); + // $FlowFixMe this seems to be a Flow bug, `saveResponse` is defined below this.saveResponse = this.saveResponse.bind(this); this.customButtons = [{ diff --git a/Examples/UIExplorer/AnimatedExample.js b/Examples/UIExplorer/AnimatedExample.js index 5f60963381153e..1068ac00026809 100644 --- a/Examples/UIExplorer/AnimatedExample.js +++ b/Examples/UIExplorer/AnimatedExample.js @@ -39,6 +39,8 @@ exports.examples = [ 'mounts.', render: function() { class FadeInView extends React.Component { + state: any; + constructor(props) { super(props); this.state = { @@ -66,6 +68,8 @@ exports.examples = [ } } class FadeInExample extends React.Component { + state: any; + constructor(props) { super(props); this.state = { diff --git a/Examples/UIExplorer/AnimatedGratuitousApp/AnExApp.js b/Examples/UIExplorer/AnimatedGratuitousApp/AnExApp.js index d713b5f66d903e..c87bc29823bce4 100644 --- a/Examples/UIExplorer/AnimatedGratuitousApp/AnExApp.js +++ b/Examples/UIExplorer/AnimatedGratuitousApp/AnExApp.js @@ -32,6 +32,10 @@ var CIRCLE_MARGIN = 18; var NUM_CIRCLES = 30; class Circle extends React.Component { + state: any; + props: any; + longTimer: number; + _onLongPress: () => void; _toggleIsActive: () => void; constructor(props: Object): void { @@ -156,6 +160,13 @@ class Circle extends React.Component { } class AnExApp extends React.Component { + state: any; + props: any; + + static title = 'Animated - Gratuitous App'; + static description = 'Bunch of Animations - tap a circle to ' + + 'open a view with more animations, or longPress and drag to reorder circles.'; + _onMove: (position: Point) => void; constructor(props: any): void { super(props); @@ -266,10 +277,6 @@ function moveToClosest({activeKey, keys, restLayouts}, position) { } } -AnExApp.title = 'Animated - Gratuitous App'; -AnExApp.description = 'Bunch of Animations - tap a circle to ' + - 'open a view with more animations, or longPress and drag to reorder circles.'; - var styles = StyleSheet.create({ container: { flex: 1, diff --git a/Examples/UIExplorer/AnimatedGratuitousApp/AnExBobble.js b/Examples/UIExplorer/AnimatedGratuitousApp/AnExBobble.js index d3e603738c34d0..39f15a8ec8ea9b 100644 --- a/Examples/UIExplorer/AnimatedGratuitousApp/AnExBobble.js +++ b/Examples/UIExplorer/AnimatedGratuitousApp/AnExBobble.js @@ -36,6 +36,8 @@ var BOBBLE_SPOTS = [...Array(NUM_BOBBLES)].map((_, i) => { // static positions }); class AnExBobble extends React.Component { + state: any; + constructor(props: Object) { super(props); this.state = {}; diff --git a/Examples/UIExplorer/AnimatedGratuitousApp/AnExChained.js b/Examples/UIExplorer/AnimatedGratuitousApp/AnExChained.js index f2c932a2449c1f..596617f1780ec1 100644 --- a/Examples/UIExplorer/AnimatedGratuitousApp/AnExChained.js +++ b/Examples/UIExplorer/AnimatedGratuitousApp/AnExChained.js @@ -26,6 +26,8 @@ var { } = React; class AnExChained extends React.Component { + state: any; + constructor(props: Object) { super(props); this.state = { diff --git a/Examples/UIExplorer/AnimatedGratuitousApp/AnExScroll.js b/Examples/UIExplorer/AnimatedGratuitousApp/AnExScroll.js index f96acb380a8dc0..e7712cfc52e86e 100644 --- a/Examples/UIExplorer/AnimatedGratuitousApp/AnExScroll.js +++ b/Examples/UIExplorer/AnimatedGratuitousApp/AnExScroll.js @@ -12,6 +12,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * @providesModule AnExScroll + * @flow */ 'use strict'; @@ -26,12 +27,7 @@ var { } = React; class AnExScroll extends React.Component { - constructor(props) { - super(props); - this.state = { - scrollX: new Animated.Value(0), - }; - } + state: any = { scrollX: new Animated.Value(0) }; render() { var width = this.props.panelWidth; diff --git a/Examples/UIExplorer/AnimatedGratuitousApp/AnExSet.js b/Examples/UIExplorer/AnimatedGratuitousApp/AnExSet.js index f25301f7756f40..769a0f79a2d25f 100644 --- a/Examples/UIExplorer/AnimatedGratuitousApp/AnExSet.js +++ b/Examples/UIExplorer/AnimatedGratuitousApp/AnExSet.js @@ -31,6 +31,8 @@ var AnExScroll = require('./AnExScroll'); var AnExTilt = require('./AnExTilt'); class AnExSet extends React.Component { + state: any; + constructor(props: Object) { super(props); function randColor() { diff --git a/Examples/UIExplorer/AnimatedGratuitousApp/AnExTilt.js b/Examples/UIExplorer/AnimatedGratuitousApp/AnExTilt.js index 3cea77917088eb..42b5523c011c75 100644 --- a/Examples/UIExplorer/AnimatedGratuitousApp/AnExTilt.js +++ b/Examples/UIExplorer/AnimatedGratuitousApp/AnExTilt.js @@ -26,6 +26,8 @@ var { } = React; class AnExTilt extends React.Component { + state: any; + constructor(props: Object) { super(props); this.state = { diff --git a/Examples/UIExplorer/BorderExample.js b/Examples/UIExplorer/BorderExample.js index 1c547ea6ea21c9..2dedfdd8e6ae1d 100644 --- a/Examples/UIExplorer/BorderExample.js +++ b/Examples/UIExplorer/BorderExample.js @@ -110,8 +110,22 @@ var styles = StyleSheet.create({ borderTopLeftRadius: 10, borderBottomRightRadius: 20, borderColor: 'black', - elevation: 10 - } + elevation: 10, + }, + border11: { + width: 0, + height: 0, + borderStyle: 'solid', + overflow: 'hidden', + borderTopWidth: 50, + borderRightWidth: 0, + borderBottomWidth: 50, + borderLeftWidth: 100, + borderTopColor: 'transparent', + borderRightColor: 'transparent', + borderBottomColor: 'transparent', + borderLeftColor: 'red', + }, }); exports.title = 'Border'; @@ -209,4 +223,11 @@ exports.examples = [ return ; } }, + { + title: 'CSS Trick - Triangle', + description: 'create a triangle by manipulating border colors and widths', + render() { + return ; + } + }, ]; diff --git a/Examples/UIExplorer/ExampleTypes.js b/Examples/UIExplorer/ExampleTypes.js index ac9deaadfaf768..691f4c6cf22f72 100644 --- a/Examples/UIExplorer/ExampleTypes.js +++ b/Examples/UIExplorer/ExampleTypes.js @@ -18,7 +18,7 @@ export type Example = { title: string, - render: () => ?ReactElement, + render: () => ?ReactElement, description?: string, platform?: string; }; diff --git a/Examples/UIExplorer/ImageEditingExample.js b/Examples/UIExplorer/ImageEditingExample.js index 1a8c931221d526..868bbd25f333da 100644 --- a/Examples/UIExplorer/ImageEditingExample.js +++ b/Examples/UIExplorer/ImageEditingExample.js @@ -47,10 +47,11 @@ type ImageCropData = { offset: ImageOffset; size: ImageSize; displaySize?: ?ImageSize; - resizeMode?: ?any; + resizeMode?: ?any; }; class SquareImageCropper extends React.Component { + state: any; _isMounted: boolean; _transformData: ImageCropData; diff --git a/Examples/UIExplorer/ImageExample.js b/Examples/UIExplorer/ImageExample.js index 1a9af6c877cd6a..350844ec88b2c4 100644 --- a/Examples/UIExplorer/ImageExample.js +++ b/Examples/UIExplorer/ImageExample.js @@ -381,37 +381,41 @@ exports.examples = [ 'rendered within the frame.', render: function() { return ( - - - - Contain - - - - - - Cover - - - - - - Stretch - - - + + {[smallImage, fullImage].map((image, index) => { + return + + + Contain + + + + + + Cover + + + + + + Stretch + + + + ; + })} ); }, @@ -455,7 +459,7 @@ exports.examples = [ { title: 'Image Size', render: function() { - return ; + return ; }, platform: 'ios', }, diff --git a/Examples/UIExplorer/IntentAndroidExample.android.js b/Examples/UIExplorer/LinkingExample.js similarity index 87% rename from Examples/UIExplorer/IntentAndroidExample.android.js rename to Examples/UIExplorer/LinkingExample.js index e655bd9650c802..fbf36bfc071dc8 100644 --- a/Examples/UIExplorer/IntentAndroidExample.android.js +++ b/Examples/UIExplorer/LinkingExample.js @@ -15,7 +15,7 @@ var React = require('react-native'); var { - IntentAndroid, + Linking, StyleSheet, Text, TouchableNativeFeedback, @@ -30,9 +30,9 @@ var OpenURLButton = React.createClass({ }, handleClick: function() { - IntentAndroid.canOpenURL(this.props.url, (supported) => { + Linking.canOpenURL(this.props.url).then(supported => { if (supported) { - IntentAndroid.openURL(this.props.url); + Linking.openURL(this.props.url); } else { console.log('Don\'t know how to open URI: ' + this.props.url); } @@ -54,8 +54,8 @@ var OpenURLButton = React.createClass({ var IntentAndroidExample = React.createClass({ statics: { - title: 'IntentAndroid', - description: 'Shows how to use Android Intents to open URLs.', + title: 'Linking', + description: 'Shows how to use Linking to open URLs.', }, render: function() { @@ -64,7 +64,9 @@ var IntentAndroidExample = React.createClass({ + + ); }, diff --git a/Examples/UIExplorer/ListViewGridLayoutExample.js b/Examples/UIExplorer/ListViewGridLayoutExample.js index ac7d56b8611272..4d8017335d5642 100644 --- a/Examples/UIExplorer/ListViewGridLayoutExample.js +++ b/Examples/UIExplorer/ListViewGridLayoutExample.js @@ -67,6 +67,9 @@ var ListViewGridLayoutExample = React.createClass({ ); diff --git a/Examples/UIExplorer/ListViewPagingExample.js b/Examples/UIExplorer/ListViewPagingExample.js index f6cdcae62aa476..a1406c52ff95ab 100644 --- a/Examples/UIExplorer/ListViewPagingExample.js +++ b/Examples/UIExplorer/ListViewPagingExample.js @@ -32,7 +32,6 @@ var { UIManager, } = NativeModules; -var PAGE_SIZE = 4; var THUMB_URLS = [ require('./Thumbnails/like.png'), require('./Thumbnails/dislike.png'), @@ -182,8 +181,8 @@ var ListViewPagingExample = React.createClass({ renderSectionHeader={this.renderSectionHeader} renderRow={this.renderRow} initialListSize={10} - pageSize={PAGE_SIZE} - scrollRenderAheadDistance={2000} + pageSize={4} + scrollRenderAheadDistance={500} /> ); }, diff --git a/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/BreadcrumbNavSample.js b/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/BreadcrumbNavSample.js new file mode 100644 index 00000000000000..15251a4cf2f7e7 --- /dev/null +++ b/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/BreadcrumbNavSample.js @@ -0,0 +1,164 @@ +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +'use strict'; + +var React = require('react-native'); +var { + NavigationExperimental, + StyleSheet, + ScrollView, + Text, + TouchableHighlight, + TouchableOpacity +} = React; + +var _getRandomRoute = function() { + return { + title: '#' + Math.ceil(Math.random() * 1000), + }; +}; + +var Navigator = NavigationExperimental.LegacyNavigator; + +class NavButton extends React.Component { + render() { + return ( + + {this.props.text} + + ); + } +} + +var BreadcrumbNavSample = React.createClass({ + + componentWillMount: function() { + this._navBarRouteMapper = { + rightContentForRoute: function(route, navigator) { + return null; + }, + titleContentForRoute: function(route, navigator) { + return ( + navigator.push(_getRandomRoute())}> + {route.title} + + ); + }, + iconForRoute: function(route, navigator) { + return ( + { navigator.popToRoute(route); }} + style={styles.crumbIconPlaceholder} + /> + ); + }, + separatorForRoute: function(route, navigator) { + return ( + + ); + } + }; + }, + + _renderScene: function(route, navigator) { + return ( + + { navigator.push(_getRandomRoute()); }} + text="Push" + /> + { navigator.immediatelyResetRouteStack([_getRandomRoute(), _getRandomRoute()]); }} + text="Reset w/ 2 scenes" + /> + { navigator.popToTop(); }} + text="Pop to top" + /> + { navigator.replace(_getRandomRoute()); }} + text="Replace" + /> + { this.props.navigator.pop(); }} + text="Close breadcrumb example" + /> + + ); + }, + + render: function() { + return ( + + } + /> + ); + }, + + + +}); + +var styles = StyleSheet.create({ + scene: { + paddingTop: 50, + flex: 1, + }, + button: { + backgroundColor: 'white', + padding: 15, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: '#CDCDCD', + }, + buttonText: { + fontSize: 17, + fontWeight: '500', + }, + container: { + overflow: 'hidden', + backgroundColor: '#dddddd', + flex: 1, + }, + titleText: { + fontSize: 18, + color: '#666666', + textAlign: 'center', + fontWeight: 'bold', + lineHeight: 32, + }, + crumbIconPlaceholder: { + flex: 1, + backgroundColor: '#666666', + }, + crumbSeparatorPlaceholder: { + flex: 1, + backgroundColor: '#aaaaaa', + }, +}); + +module.exports = BreadcrumbNavSample; diff --git a/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/JumpingNavSample.js b/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/JumpingNavSample.js new file mode 100644 index 00000000000000..aa2b9fd7df6cc6 --- /dev/null +++ b/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/JumpingNavSample.js @@ -0,0 +1,218 @@ +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +'use strict'; + +var React = require('react-native'); +var { + NavigationExperimental, + StyleSheet, + ScrollView, + TabBarIOS, + Text, + TouchableHighlight, + View, +} = React; + +var _getRandomRoute = function() { + return { + randNumber: Math.ceil(Math.random() * 1000), + }; +}; + +var Navigator = NavigationExperimental.LegacyNavigator; + +class NavButton extends React.Component { + render() { + return ( + + {this.props.text} + + ); + } +} + +var ROUTE_STACK = [ + _getRandomRoute(), + _getRandomRoute(), + _getRandomRoute(), +]; +var INIT_ROUTE_INDEX = 1; + +class JumpingNavBar extends React.Component { + constructor(props) { + super(props); + this.state = { + tabIndex: props.initTabIndex, + }; + } + handleWillFocus(route) { + var tabIndex = ROUTE_STACK.indexOf(route); + this.setState({ tabIndex, }); + } + render() { + return ( + + + { + this.props.onTabIndex(0); + this.setState({ tabIndex: 0, }); + }}> + + + { + this.props.onTabIndex(1); + this.setState({ tabIndex: 1, }); + }}> + + + { + this.props.onTabIndex(2); + this.setState({ tabIndex: 2, }); + }}> + + + + + ); + } +} + +var JumpingNavSample = React.createClass({ + render: function() { + return ( + { + this._navigator = navigator; + }} + initialRoute={ROUTE_STACK[INIT_ROUTE_INDEX]} + initialRouteStack={ROUTE_STACK} + renderScene={this.renderScene} + configureScene={() => ({ + ...Navigator.SceneConfigs.HorizontalSwipeJump, + })} + navigationBar={ + { this.navBar = navBar; }} + initTabIndex={INIT_ROUTE_INDEX} + routeStack={ROUTE_STACK} + onTabIndex={(index) => { + this._navigator.jumpTo(ROUTE_STACK[index]); + }} + /> + } + /> + ); + }, + + renderScene: function(route, navigator) { + var backBtn; + var forwardBtn; + if (ROUTE_STACK.indexOf(route) !== 0) { + backBtn = ( + { + navigator.jumpBack(); + }} + text="jumpBack" + /> + ); + } + if (ROUTE_STACK.indexOf(route) !== ROUTE_STACK.length - 1) { + forwardBtn = ( + { + navigator.jumpForward(); + }} + text="jumpForward" + /> + ); + } + return ( + + #{route.randNumber} + {backBtn} + {forwardBtn} + { + navigator.jumpTo(ROUTE_STACK[1]); + }} + text="jumpTo middle route" + /> + { + this.props.navigator.pop(); + }} + text="Exit Navigation Example" + /> + { + this.props.navigator.push({ + message: 'Came from jumping example', + }); + }} + text="Nav Menu" + /> + + ); + }, +}); + +var styles = StyleSheet.create({ + button: { + backgroundColor: 'white', + padding: 15, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: '#CDCDCD', + }, + buttonText: { + fontSize: 17, + fontWeight: '500', + }, + appContainer: { + overflow: 'hidden', + backgroundColor: '#dddddd', + flex: 1, + }, + messageText: { + fontSize: 17, + fontWeight: '500', + padding: 15, + marginTop: 50, + marginLeft: 15, + }, + scene: { + flex: 1, + paddingTop: 20, + backgroundColor: '#EAEAEA', + }, + tabs: { + height: 50, + } +}); + +module.exports = JumpingNavSample; diff --git a/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/LegacyNavigatorExample.js b/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/LegacyNavigatorExample.js new file mode 100644 index 00000000000000..ed4169751418e9 --- /dev/null +++ b/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/LegacyNavigatorExample.js @@ -0,0 +1,212 @@ +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +'use strict'; + +var React = require('react-native'); +var { + NavigationExperimental, + ScrollView, + StyleSheet, + Text, + TouchableHighlight, +} = React; + +var BreadcrumbNavSample = require('./BreadcrumbNavSample'); +var NavigationBarSample = require('./NavigationBarSample'); +var JumpingNavSample = require('./JumpingNavSample'); + +var Navigator = NavigationExperimental.LegacyNavigator; + +class NavButton extends React.Component { + render() { + return ( + + {this.props.text} + + ); + } +} + +class NavMenu extends React.Component { + render() { + return ( + + {this.props.message} + { + this.props.navigator.push({ + message: 'Swipe right to dismiss', + sceneConfig: Navigator.SceneConfigs.FloatFromRight, + }); + }} + text="Float in from right" + /> + { + this.props.navigator.push({ + message: 'Swipe down to dismiss', + sceneConfig: Navigator.SceneConfigs.FloatFromBottom, + }); + }} + text="Float in from bottom" + /> + { + this.props.navigator.pop(); + }} + text="Pop" + /> + { + this.props.navigator.popToTop(); + }} + text="Pop to top" + /> + { + this.props.navigator.push({ id: 'navbar' }); + }} + text="Navbar Example" + /> + { + this.props.navigator.push({ id: 'jumping' }); + }} + text="Jumping Example" + /> + { + this.props.navigator.push({ id: 'breadcrumbs' }); + }} + text="Breadcrumbs Example" + /> + { + this.props.onExampleExit(); + }} + text="Exit Example" + /> + + ); + } +} + +var TabBarExample = React.createClass({ + + statics: { + title: ' (Experimental)', + description: 'Experimental navigator that ports the API of the current ' + + 'Navigator component', + }, + + renderScene: function(route, nav) { + switch (route.id) { + case 'navbar': + return ; + case 'breadcrumbs': + return ; + case 'jumping': + return ; + default: + return ( + + ); + } + }, + + render: function() { + return ( + { + if (route.sceneConfig) { + return route.sceneConfig; + } + return Navigator.SceneConfigs.FloatFromBottom; + }} + /> + ); + }, + + + componentWillUnmount: function() { + this._listeners && this._listeners.forEach(listener => listener.remove()); + }, + + _setNavigatorRef: function(navigator) { + if (navigator !== this._navigator) { + this._navigator = navigator; + + if (navigator) { + var callback = (event) => { + console.log( + `TabBarExample: event ${event.type}`, + { + route: JSON.stringify(event.data.route), + target: event.target, + type: event.type, + } + ); + }; + // Observe focus change events from the owner. + this._listeners = [ + navigator.navigationContext.addListener('willfocus', callback), + navigator.navigationContext.addListener('didfocus', callback), + ]; + } + } + }, +}); + +var styles = StyleSheet.create({ + messageText: { + fontSize: 17, + fontWeight: '500', + padding: 15, + marginTop: 50, + marginLeft: 15, + }, + container: { + flex: 1, + }, + button: { + backgroundColor: 'white', + padding: 15, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: '#CDCDCD', + }, + buttonText: { + fontSize: 17, + fontWeight: '500', + }, + scene: { + flex: 1, + paddingTop: 20, + backgroundColor: '#ccc', + } +}); + +TabBarExample.external = true; + +module.exports = TabBarExample; diff --git a/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/NavigationBarSample.js b/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/NavigationBarSample.js new file mode 100644 index 00000000000000..f479b2a83e7ee5 --- /dev/null +++ b/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/NavigationBarSample.js @@ -0,0 +1,201 @@ +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +'use strict'; + + +var React = require('react-native'); +var { + NavigationExperimental, + ScrollView, + StyleSheet, + Text, + TouchableHighlight, + TouchableOpacity, +} = React; + +var Navigator = NavigationExperimental.LegacyNavigator; + +class NavButton extends React.Component { + render() { + return ( + + {this.props.text} + + ); + } +} + +var NavigationBarRouteMapper = { + + LeftButton: function(route, navigator, index, navState) { + if (index === 0) { + return null; + } + + var previousRoute = navState.routeStack[index - 1]; + return ( + navigator.pop()} + style={styles.navBarLeftButton}> + + {previousRoute.title} + + + ); + }, + + RightButton: function(route, navigator, index, navState) { + return ( + navigator.push(newRandomRoute())} + style={styles.navBarRightButton}> + + Next + + + ); + }, + + Title: function(route, navigator, index, navState) { + return ( + + {route.title} [{index}] + + ); + }, + +}; + +function newRandomRoute() { + return { + title: '#' + Math.ceil(Math.random() * 1000), + }; +} + +var NavigationBarSample = React.createClass({ + + componentWillMount: function() { + var navigator = this.props.navigator; + + var callback = (event) => { + console.log( + `NavigationBarSample : event ${event.type}`, + { + route: JSON.stringify(event.data.route), + target: event.target, + type: event.type, + } + ); + }; + + // Observe focus change events from this component. + this._listeners = [ + navigator.navigationContext.addListener('willfocus', callback), + navigator.navigationContext.addListener('didfocus', callback), + ]; + }, + + componentWillUnmount: function() { + this._listeners && this._listeners.forEach(listener => listener.remove()); + }, + + render: function() { + return ( + ( + + {route.content} + { + navigator.immediatelyResetRouteStack([ + newRandomRoute(), + newRandomRoute(), + newRandomRoute(), + ]); + }} + text="Reset w/ 3 scenes" + /> + { + this.props.navigator.pop(); + }} + text="Exit NavigationBar Example" + /> + + )} + navigationBar={ + + } + /> + ); + }, + +}); + +var styles = StyleSheet.create({ + messageText: { + fontSize: 17, + fontWeight: '500', + padding: 15, + marginTop: 50, + marginLeft: 15, + }, + button: { + backgroundColor: 'white', + padding: 15, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: '#CDCDCD', + }, + buttonText: { + fontSize: 17, + fontWeight: '500', + }, + navBar: { + backgroundColor: 'white', + }, + navBarText: { + fontSize: 16, + marginVertical: 10, + }, + navBarTitleText: { + color: '#373E4D', + fontWeight: '500', + marginVertical: 9, + }, + navBarLeftButton: { + paddingLeft: 10, + }, + navBarRightButton: { + paddingRight: 10, + }, + navBarButtonText: { + color: '#5890FF', + }, + scene: { + flex: 1, + paddingTop: 20, + backgroundColor: '#EAEAEA', + }, +}); + +module.exports = NavigationBarSample; diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationAnimatedExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationAnimatedExample.js index e9f85ed1cb680c..c27f3170d613b5 100644 --- a/Examples/UIExplorer/NavigationExperimental/NavigationAnimatedExample.js +++ b/Examples/UIExplorer/NavigationExperimental/NavigationAnimatedExample.js @@ -15,6 +15,7 @@ var React = require('react-native'); var { + Animated, NavigationExperimental, StyleSheet, ScrollView, @@ -29,24 +30,36 @@ var { } = NavigationExperimental; const NavigationBasicReducer = NavigationReducer.StackReducer({ - initialStates: [ - {key: 'First Route'} - ], - matchAction: action => true, - actionStateMap: actionString => ({key: actionString}), + getPushedReducerForAction: (action) => { + if (action.type === 'push') { + return (state) => state || {key: action.key}; + } + return null; + }, + getReducerForState: (initialState) => (state) => state || initialState, + initialState: { + key: 'AnimatedExampleStackKey', + index: 0, + children: [ + {key: 'First Route'}, + ], + }, }); class NavigationAnimatedExample extends React.Component { componentWillMount() { - this._renderNavigated = this._renderNavigated.bind(this); + this._renderNavigation = this._renderNavigation.bind(this); + this._renderCard = this._renderCard.bind(this); + this._renderScene = this._renderScene.bind(this); + this._renderHeader = this._renderHeader.bind(this); } render() { return ( { this.navRootContainer = navRootContainer; }} - persistenceKey="NavigationAnimatedExampleState" - renderNavigation={this._renderNavigated} + persistenceKey="NavigationAnimExampleState" + renderNavigation={this._renderNavigation} /> ); } @@ -56,7 +69,7 @@ class NavigationAnimatedExample extends React.Component { this.navRootContainer.handleNavigation(NavigationRootContainer.getBackAction()) ); } - _renderNavigated(navigationState, onNavigate) { + _renderNavigation(navigationState, onNavigate) { if (!navigationState) { return null; } @@ -64,40 +77,56 @@ class NavigationAnimatedExample extends React.Component { ( - state.key} - /> - )} - renderScene={(state, index, position, layout) => ( - - - - { - onNavigate('Route #' + navigationState.children.length); - }} - /> - - - - )} + renderOverlay={this._renderHeader} + applyAnimation={(pos, navState) => { + Animated.timing(pos, {toValue: navState.index, duration: 1000}).start(); + }} + renderScene={this._renderCard} /> ); } + + _renderHeader(/*NavigationSceneRendererProps*/ props) { + return ( + state.key} + /> + ); + } + + _renderCard(/*NavigationSceneRendererProps*/ props) { + return ( + + ); + } + + _renderScene(/*NavigationSceneRendererProps*/ props) { + return ( + + + { + props.onNavigate({ + type: 'push', + key: 'Route #' + props.scenes.length, + }); + }} + /> + + + ); + } } const styles = StyleSheet.create({ diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationBasicExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationBasicExample.js index 8f8669fb4c238c..447ab2051fba08 100644 --- a/Examples/UIExplorer/NavigationExperimental/NavigationBasicExample.js +++ b/Examples/UIExplorer/NavigationExperimental/NavigationBasicExample.js @@ -26,12 +26,21 @@ const { } = NavigationExperimental; const StackReducer = NavigationReducer.StackReducer; -const NavigationBasicReducer = StackReducer({ - initialStates: [ - {key: 'first_page'} - ], - matchAction: action => true, - actionStateMap: action => ({key: action}), +const NavigationBasicReducer = NavigationReducer.StackReducer({ + getPushedReducerForAction: (action) => { + if (action.type === 'push') { + return (state) => state || {key: action.key}; + } + return null; + }, + getReducerForState: (initialState) => (state) => state || initialState, + initialState: { + key: 'BasicExampleStackKey', + index: 0, + children: [ + {key: 'First Route'}, + ], + }, }); const NavigationBasicExample = React.createClass({ @@ -51,13 +60,13 @@ const NavigationBasicExample = React.createClass({ { - onNavigate('page #' + navState.children.length); + onNavigate({ type: 'push', key: 'page #' + navState.children.length }); }} /> { - onNavigate(StackReducer.PopAction()); + onNavigate(NavigationRootContainer.getBackAction()); }} /> { + switch (action.type) { + case 'RootContainerInitialAction': + return initialState; + + case 'push': + return NavigationStateUtils.push(currentState, {key: action.key}); + + case 'back': + case 'pop': + return currentState.index > 0 ? + NavigationStateUtils.pop(currentState) : + currentState; + + default: + return currentState; + } + }; +} + +const ExampleReducer = reduceNavigationState({ + index: 0, + key: 'exmaple', + children: [{key: 'First Route'}], +}); + +class NavigationCardStackExample extends React.Component { + + constructor(props, context) { + super(props, context); + this.state = {isHorizontal: true}; + } + + componentWillMount() { + this._renderNavigation = this._renderNavigation.bind(this); + this._renderScene = this._renderScene.bind(this); + this._toggleDirection = this._toggleDirection.bind(this); + } + + render() { + return ( + + ); + } + + _renderNavigation(navigationState, onNavigate) { + return ( + + ); + } + + _renderScene(/*NavigationSceneRendererProps*/ props) { + return ( + + + + { + props.onNavigate({ + type: 'push', + key: 'Route ' + props.scenes.length, + }); + }} + /> + { + props.onNavigate({ + type: 'pop', + }); + }} + /> + + + ); + } + + _toggleDirection() { + this.setState({ + isHorizontal: !this.state.isHorizontal, + }); + } + + _onNavigate(action) { + if (action && action.type === 'back') { + this._pop(); + } + } +} + +const styles = StyleSheet.create({ + main: { + flex: 1, + }, + scrollView: { + marginTop: 64 + }, +}); + +module.exports = NavigationCardStackExample; diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationCompositionExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationCompositionExample.js index ee8472242c4a8f..084dc1f39b0fe7 100644 --- a/Examples/UIExplorer/NavigationExperimental/NavigationCompositionExample.js +++ b/Examples/UIExplorer/NavigationExperimental/NavigationCompositionExample.js @@ -10,57 +10,77 @@ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ + * + * @flow + */ 'use strict'; const React = require('react-native'); +const NavigationExampleRow = require('./NavigationExampleRow'); +const NavigationExampleTabBar = require('./NavigationExampleTabBar'); + const { NavigationExperimental, ScrollView, StyleSheet, View, } = React; + const { AnimatedView: NavigationAnimatedView, - Card: NavigationCard, + CardStack: NavigationCardStack, Container: NavigationContainer, - RootContainer: NavigationRootContainer, Header: NavigationHeader, Reducer: NavigationReducer, + RootContainer: NavigationRootContainer, View: NavigationView, } = NavigationExperimental; -const NavigationExampleRow = require('./NavigationExampleRow'); -const NavigationExampleTabBar = require('./NavigationExampleTabBar'); + + +import type { + NavigationParentState, + NavigationSceneRenderer, + NavigationSceneRendererProps, +} from 'NavigationTypeDefinition'; + +type Action = { + isExitAction?: boolean, +}; const ExampleExitAction = () => ({ isExitAction: true, }); -ExampleExitAction.match = (action) => ( + +ExampleExitAction.match = (action: Action) => ( action && action.isExitAction === true ); -const ExamplePageAction = (type) => ({ +const PageAction = (type) => ({ type, isPageAction: true, }); -ExamplePageAction.match = (action) => ( + +PageAction.match = (action) => ( action && action.isPageAction === true ); -const ExampleSettingsPageAction = (type) => ({ - ...ExamplePageAction(type), - isSettingsPageAction: true, +const ExampleProfilePageAction = (type) => ({ + ...PageAction(type), + isProfilePageAction: true, }); -ExampleSettingsPageAction.match = (action) => ( - action && action.isSettingsPageAction === true + +ExampleProfilePageAction.match = (action) => ( + action && action.isProfilePageAction === true ); -const ExampleInfoAction = () => ExamplePageAction('InfoPage'); +const ExampleInfoAction = () => PageAction('InfoPage'); -const ExampleNotifSettingsAction = () => ExampleSettingsPageAction('NotifSettingsPage'); +const ExampleNotifProfileAction = () => ExampleProfilePageAction('NotifProfilePage'); const _jsInstanceUniqueId = '' + Date.now(); + let _uniqueIdCount = 0; + function pageStateActionMap(action) { return { key: 'page-' + _jsInstanceUniqueId + '-' + (_uniqueIdCount++), @@ -68,147 +88,150 @@ function pageStateActionMap(action) { }; } -function getTabActionMatcher(key) { - return function (action) { - if (!ExamplePageAction.match(action)) { - return false; - } - if (ExampleSettingsPageAction.match(action)) { - return key === 'settings'; - } - return true; - }; -} - -var ExampleTabs = [ - { - label: 'Account', - reducer: NavigationReducer.StackReducer({ - initialStates: [ - {type: 'AccountPage', key: 'base'} - ], - key: 'account', - matchAction: getTabActionMatcher('account'), - actionStateMap: pageStateActionMap, +const ExampleAppReducer = NavigationReducer.TabsReducer({ + key: 'AppNavigationState', + initialIndex: 0, + tabReducers: [ + NavigationReducer.StackReducer({ + getPushedReducerForAction: (action) => { + if (PageAction.match(action) && !ExampleProfilePageAction.match(action)) { + return (state) => (state || pageStateActionMap(action)); + } + return null; + }, + initialState: { + key: 'notifs', + index: 0, + children: [ + {key: 'base', type: 'NotifsPage'}, + ], + }, }), - }, - { - label: 'Notifications', - reducer: NavigationReducer.StackReducer({ - initialStates: [ - {type: 'NotifsPage', key: 'base'} - ], - key: 'notifs', - matchAction: getTabActionMatcher('notifs'), - actionStateMap: pageStateActionMap, + NavigationReducer.StackReducer({ + getPushedReducerForAction: (action) => { + if (PageAction.match(action) && !ExampleProfilePageAction.match(action)) { + return (state) => (state || pageStateActionMap(action)); + } + return null; + }, + initialState: { + key: 'settings', + index: 0, + children: [ + {key: 'base', type: 'SettingsPage'}, + ], + }, }), - }, - { - label: 'Settings', - reducer: NavigationReducer.StackReducer({ - initialStates: [ - {type: 'SettingsPage', key: 'base'} - ], - key: 'settings', - matchAction: getTabActionMatcher('settings'), - actionStateMap: pageStateActionMap, + NavigationReducer.StackReducer({ + getPushedReducerForAction: (action) => { + if (PageAction.match(action) || ExampleProfilePageAction.match(action)) { + return (state) => (state || pageStateActionMap(action)); + } + return null; + }, + initialState: { + key: 'profile', + index: 0, + children: [ + {key: 'base', type: 'ProfilePage'}, + ], + }, }), - }, -]; - -const ExampleAppReducer = NavigationReducer.TabsReducer({ - tabReducers: ExampleTabs.map(tab => tab.reducer), + ], }); function stateTypeTitleMap(pageState) { switch (pageState.type) { - case 'AccountPage': - return 'Account Page'; + case 'ProfilePage': + return 'Profile Page'; case 'NotifsPage': return 'Notifications'; case 'SettingsPage': return 'Settings'; case 'InfoPage': return 'Info Page'; - case 'NotifSettingsPage': - return 'Notification Settings'; + case 'NotifProfilePage': + return 'Page in Profile'; } } class ExampleTabScreen extends React.Component { + _renderCard: NavigationSceneRenderer; + _renderHeader: NavigationSceneRenderer; + _renderScene: NavigationSceneRenderer; + + componentWillMount() { + this._renderHeader = this._renderHeader.bind(this); + this._renderScene = this._renderScene.bind(this); + } + render() { return ( - ); } - _renderHeader(position, layout) { + _renderHeader(props: NavigationSceneRendererProps) { return ( stateTypeTitleMap(state)} /> ); } - _renderScene(child, index, position, layout) { + + _renderScene(props: NavigationSceneRendererProps) { + const {onNavigate} = props; return ( - - - { - this.props.onNavigate(ExampleInfoAction()); - }} - /> - { - this.props.onNavigate(ExampleNotifSettingsAction()); - }} - /> - { - this.props.onNavigate(ExampleExitAction()); - }} - /> - - + + { + onNavigate(ExampleInfoAction()); + }} + /> + { + onNavigate(ExampleNotifProfileAction()); + }} + /> + { + onNavigate(ExampleExitAction()); + }} + /> + ); } } ExampleTabScreen = NavigationContainer.create(ExampleTabScreen); class NavigationCompositionExample extends React.Component { + navRootContainer: NavigationRootContainer; + render() { return ( { this.navRootContainer = navRootContainer; }} renderNavigation={this.renderApp.bind(this)} /> ); } - handleBackAction() { + handleBackAction(): boolean { return ( this.navRootContainer && this.navRootContainer.handleNavigation(NavigationRootContainer.getBackAction()) ); } - renderApp(navigationState, onNavigate) { + renderApp(navigationState: NavigationParentState, onNavigate: Function) { if (!navigationState) { return null; } diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationExperimentalExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationExperimentalExample.js index 4aade5303c173f..0781570da28df0 100644 --- a/Examples/UIExplorer/NavigationExperimental/NavigationExperimentalExample.js +++ b/Examples/UIExplorer/NavigationExperimental/NavigationExperimentalExample.js @@ -30,11 +30,13 @@ var NavigationExampleRow = require('./NavigationExampleRow'); var EXAMPLES = { 'Tabs': require('./NavigationTabsExample'), 'Basic': require('./NavigationBasicExample'), - 'Animated Card Stack': require('./NavigationAnimatedExample'), + 'Animated Example': require('./NavigationAnimatedExample'), 'Composition': require('./NavigationCompositionExample'), + 'Card Stack Example': require('./NavigationCardStackExample'), + 'Tic Tac Toe': require('./NavigationTicTacToeExample'), }; -var EXAMPLE_STORAGE_KEY = 'NavigationExampleExample'; +var EXAMPLE_STORAGE_KEY = 'NavigationExperimentalExample'; var NavigationExperimentalExample = React.createClass({ statics: { @@ -45,13 +47,13 @@ var NavigationExperimentalExample = React.createClass({ getInitialState: function() { return { - exampe: null, + example: null, }; }, componentDidMount() { AsyncStorage.getItem(EXAMPLE_STORAGE_KEY, (err, example) => { - if (err || !example) { + if (err || !example || !EXAMPLES[example]) { this.setState({ example: 'menu', }); diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationTicTacToeExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationTicTacToeExample.js new file mode 100644 index 00000000000000..d0604ec08eae3e --- /dev/null +++ b/Examples/UIExplorer/NavigationExperimental/NavigationTicTacToeExample.js @@ -0,0 +1,314 @@ +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * @providesModule NavigationTicTacToeExample + * @flow + */ +'use strict'; + +var React = require('react-native'); +var { + NavigationExperimental, + StyleSheet, + Text, + TouchableHighlight, + View, +} = React; +const { + Container: NavigationContainer, + RootContainer: NavigationRootContainer, +} = NavigationExperimental; + +type GameGrid = Array>; + +const evenOddPlayerMap = ['O', 'X']; +const rowLeterMap = ['a', 'b', 'c']; + +function parseGame(game: string): GameGrid { + const gameTurns = game ? game.split('-') : []; + const grid = Array(3); + for (let i = 0; i < 3; i++) { + const row = Array(3); + for (let j = 0; j < 3; j++) { + const turnIndex = gameTurns.indexOf(rowLeterMap[i]+j); + if (turnIndex === -1) { + row[j] = null; + } else { + row[j] = evenOddPlayerMap[turnIndex % 2]; + } + } + grid[i] = row; + } + return grid; +} + +function playTurn(game: string, row: number, col: number): string { + const turn = rowLeterMap[row] + col; + return game ? (game + '-' + turn) : turn; +} + +function getWinner(gameString: string): ?string { + const game = parseGame(gameString); + for (var i = 0; i < 3; i++) { + if (game[i][0] !== null && game[i][0] === game[i][1] && + game[i][0] === game[i][2]) { + return game[i][0]; + } + } + for (var i = 0; i < 3; i++) { + if (game[0][i] !== null && game[0][i] === game[1][i] && + game[0][i] === game[2][i]) { + return game[0][i]; + } + } + if (game[0][0] !== null && game[0][0] === game[1][1] && + game[0][0] === game[2][2]) { + return game[0][0]; + } + if (game[0][2] !== null && game[0][2] === game[1][1] && + game[0][2] === game[2][0]) { + return game[0][2]; + } + return null; +} + +function isGameOver(gameString: string): boolean { + if (getWinner(gameString)) { + return true; + } + const game = parseGame(gameString); + for (var i = 0; i < 3; i++) { + for (var j = 0; j < 3; j++) { + if (game[i][j] === null) { + return false; + } + } + } + return true; +} + +class Cell extends React.Component { + props: any; + cellStyle() { + switch (this.props.player) { + case 'X': + return styles.cellX; + case 'O': + return styles.cellO; + default: + return null; + } + } + textStyle() { + switch (this.props.player) { + case 'X': + return styles.cellTextX; + case 'O': + return styles.cellTextO; + default: + return {}; + } + } + render() { + return ( + + + + {this.props.player} + + + + ); + } +} + +function GameEndOverlay(props) { + if (!isGameOver(props.game)) { + return ; + } + const winner = getWinner(props.game); + return ( + + + {winner ? winner + ' wins!' : 'It\'s a tie!'} + + props.onNavigate(GameActions.Reset())} + underlayColor="transparent" + activeOpacity={0.5}> + + New Game + + + + ); +} +GameEndOverlay = NavigationContainer.create(GameEndOverlay); + +function TicTacToeGame(props) { + var rows = parseGame(props.game).map((cells, row) => + + {cells.map((player, col) => + props.onNavigate(GameActions.Turn(row, col))} + /> + )} + + ); + return ( + + + Close + + EXTREME T3 + + {rows} + + + + ); +} +TicTacToeGame = NavigationContainer.create(TicTacToeGame); + +const GameActions = { + Turn: (row, col) => ({type: 'TicTacToeTurnAction', row, col }), + Reset: (row, col) => ({type: 'TicTacToeResetAction' }), +}; + +function GameReducer(lastGame: ?string, action: Object): string { + if (!lastGame) { + lastGame = ''; + } + if (action.type === 'TicTacToeResetAction') { + return ''; + } + if (!isGameOver(lastGame) && action.type === 'TicTacToeTurnAction') { + return playTurn(lastGame, action.row, action.col); + } + return lastGame; +} + +class NavigationTicTacToeExample extends React.Component { + static GameView = TicTacToeGame; + static GameReducer = GameReducer; + static GameActions = GameActions; + render() { + return ( + ( + + )} + /> + ); + } +} + +const styles = StyleSheet.create({ + closeButton: { + position: 'absolute', + left: 10, + top: 30, + fontSize: 14, + }, + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'white' + }, + title: { + fontFamily: 'Chalkduster', + fontSize: 39, + marginBottom: 20, + }, + board: { + padding: 5, + backgroundColor: '#47525d', + borderRadius: 10, + }, + row: { + flexDirection: 'row', + }, + cell: { + width: 80, + height: 80, + borderRadius: 5, + backgroundColor: '#7b8994', + margin: 5, + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + cellX: { + backgroundColor: '#72d0eb', + }, + cellO: { + backgroundColor: '#7ebd26', + }, + cellText: { + fontSize: 50, + fontFamily: 'AvenirNext-Bold', + }, + cellTextX: { + color: '#19a9e5', + }, + cellTextO: { + color: '#b9dc2f', + }, + overlay: { + position: 'absolute', + top: 0, + bottom: 0, + left: 0, + right: 0, + backgroundColor: 'rgba(221, 221, 221, 0.5)', + flex: 1, + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + }, + overlayMessage: { + fontSize: 40, + marginBottom: 20, + marginLeft: 20, + marginRight: 20, + fontFamily: 'AvenirNext-DemiBold', + textAlign: 'center', + }, + newGame: { + backgroundColor: '#887766', + padding: 20, + borderRadius: 5, + }, + newGameText: { + color: 'white', + fontSize: 20, + fontFamily: 'AvenirNext-DemiBold', + }, +}); + +module.exports = NavigationTicTacToeExample; diff --git a/Examples/UIExplorer/Navigator/NavigationBarSample.js b/Examples/UIExplorer/Navigator/NavigationBarSample.js index 25f45f0ab30366..ff44f35220fdcb 100644 --- a/Examples/UIExplorer/Navigator/NavigationBarSample.js +++ b/Examples/UIExplorer/Navigator/NavigationBarSample.js @@ -24,8 +24,6 @@ var { TouchableOpacity, } = React; -var cssVar = require('cssVar'); - class NavButton extends React.Component { render() { return ( @@ -178,7 +176,7 @@ var styles = StyleSheet.create({ marginVertical: 10, }, navBarTitleText: { - color: cssVar('fbui-bluegray-60'), + color: '#373E4D', fontWeight: '500', marginVertical: 9, }, @@ -189,7 +187,7 @@ var styles = StyleSheet.create({ paddingRight: 10, }, navBarButtonText: { - color: cssVar('fbui-accent-blue'), + color: '#5890FF', }, scene: { flex: 1, diff --git a/Examples/UIExplorer/Navigator/NavigatorExample.js b/Examples/UIExplorer/Navigator/NavigatorExample.js index d15939886f062e..7346af4542d495 100644 --- a/Examples/UIExplorer/Navigator/NavigatorExample.js +++ b/Examples/UIExplorer/Navigator/NavigatorExample.js @@ -10,7 +10,9 @@ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ + * + * @providesModule NavigatorExample + */ 'use strict'; var React = require('react-native'); diff --git a/Examples/UIExplorer/NavigatorIOSColorsExample.js b/Examples/UIExplorer/NavigatorIOSColorsExample.js index 4078cac6db6421..1735633c9d3497 100644 --- a/Examples/UIExplorer/NavigatorIOSColorsExample.js +++ b/Examples/UIExplorer/NavigatorIOSColorsExample.js @@ -16,7 +16,7 @@ var React = require('react-native'); var { NavigatorIOS, - StatusBarIOS, + StatusBar, StyleSheet, Text, View @@ -45,7 +45,7 @@ var NavigatorIOSColors = React.createClass({ render: function() { // Set StatusBar with light contents to get better contrast - StatusBarIOS.setStyle('light-content'); + StatusBar.setBarStyle('light-content'); return ( ', rightButtonTitle: 'Done', onRightButtonPress: () => { - StatusBarIOS.setStyle('default'); + StatusBar.setBarStyle('default'); this.props.onExampleExit(); }, passProps: { diff --git a/Examples/UIExplorer/NavigatorIOSExample.js b/Examples/UIExplorer/NavigatorIOSExample.js index 08d227c3f0a0f0..3c6f19f94dfcc1 100644 --- a/Examples/UIExplorer/NavigatorIOSExample.js +++ b/Examples/UIExplorer/NavigatorIOSExample.js @@ -15,11 +15,12 @@ */ 'use strict'; -var React = require('react-native'); -var ViewExample = require('./ViewExample'); -var createExamplePage = require('./createExamplePage'); -var { +const React = require('react-native'); +const ViewExample = require('./ViewExample'); +const createExamplePage = require('./createExamplePage'); +const { AlertIOS, + NavigatorIOS, ScrollView, StyleSheet, Text, @@ -27,8 +28,7 @@ var { View, } = React; -var EmptyPage = React.createClass({ - +const EmptyPage = React.createClass({ render: function() { return ( @@ -38,41 +38,24 @@ var EmptyPage = React.createClass({ ); }, - }); -var NavigatorIOSExample = React.createClass({ - - statics: { - title: '', - description: 'iOS navigation capabilities', - }, - +const NavigatorIOSExamplePage = React.createClass({ render: function() { var recurseTitle = 'Recurse Navigation'; - if (!this.props.topExampleRoute) { + if (!this.props.depth || this.props.depth === 1) { recurseTitle += ' - more examples here'; } return ( - - - - - See <UIExplorerApp> for top-level usage. - - - - - {this._renderRow(recurseTitle, () => { this.props.navigator.push({ title: NavigatorIOSExample.title, - component: NavigatorIOSExample, + component: NavigatorIOSExamplePage, backButtonTitle: 'Custom Back', - passProps: {topExampleRoute: this.props.topExampleRoute || this.props.route}, + passProps: {depth: this.props.depth ? this.props.depth + 1 : 1}, }); })} {this._renderRow('Push View Example', () => { @@ -122,40 +105,39 @@ var NavigatorIOSExample = React.createClass({ {this._renderRow('Pop to top', () => { this.props.navigator.popToTop(); })} - {this._renderRow('Replace here', () => { - var prevRoute = this.props.route; - this.props.navigator.replace({ - title: 'New Navigation', - component: EmptyPage, - rightButtonTitle: 'Undo', - onRightButtonPress: () => this.props.navigator.replace(prevRoute), - passProps: { - text: 'The component is replaced, but there is currently no ' + - 'way to change the right button or title of the current route', - } - }); - })} + {this._renderReplace()} {this._renderReplacePrevious()} {this._renderReplacePreviousAndPop()} - {this._renderPopToTopNavExample()} + {this._renderRow('Exit NavigatorIOS Example', this.props.onExampleExit)} ); }, - _renderPopToTopNavExample: function() { - if (!this.props.topExampleRoute) { + _renderReplace: function() { + if (!this.props.depth) { + // this is to avoid replacing the top of the stack return null; } - return this._renderRow('Pop to top NavigatorIOSExample', () => { - this.props.navigator.popToRoute(this.props.topExampleRoute); + return this._renderRow('Replace here', () => { + var prevRoute = this.props.route; + this.props.navigator.replace({ + title: 'New Navigation', + component: EmptyPage, + rightButtonTitle: 'Undo', + onRightButtonPress: () => this.props.navigator.replace(prevRoute), + passProps: { + text: 'The component is replaced, but there is currently no ' + + 'way to change the right button or title of the current route', + } + }); }); }, _renderReplacePrevious: function() { - if (!this.props.topExampleRoute) { - // this is to avoid replacing the UIExplorerList at the top of the stack + if (!this.props.depth || this.props.depth < 2) { + // this is to avoid replacing the top of the stack return null; } return this._renderRow('Replace previous', () => { @@ -171,8 +153,8 @@ var NavigatorIOSExample = React.createClass({ }, _renderReplacePreviousAndPop: function() { - if (!this.props.topExampleRoute) { - // this is to avoid replacing the UIExplorerList at the top of the stack + if (!this.props.depth || this.props.depth < 2) { + // this is to avoid replacing the top of the stack return null; } return this._renderRow('Replace previous and pop', () => { @@ -203,7 +185,34 @@ var NavigatorIOSExample = React.createClass({ }, }); -var styles = StyleSheet.create({ +const NavigatorIOSExample = React.createClass({ + statics: { + title: '', + description: 'iOS navigation capabilities', + external: true, + }, + + render: function() { + const {onExampleExit} = this.props; + return ( + + ); + }, +}); + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, customWrapperStyle: { backgroundColor: '#bbdddd', }, diff --git a/Examples/UIExplorer/NetInfoExample.js b/Examples/UIExplorer/NetInfoExample.js index a79ed67e2043c9..97af9e1e00bed4 100644 --- a/Examples/UIExplorer/NetInfoExample.js +++ b/Examples/UIExplorer/NetInfoExample.js @@ -134,8 +134,8 @@ const IsConnectionExpensive = React.createClass({ }; }, _checkIfExpensive() { - NetInfo.isConnectionExpensive( - (isConnectionExpensive) => { this.setState({isConnectionExpensive}); } + NetInfo.isConnectionExpensive().then( + isConnectionExpensive => { this.setState({isConnectionExpensive}); } ); }, render() { diff --git a/Examples/UIExplorer/PickerAndroidExample.js b/Examples/UIExplorer/PickerAndroidExample.js index 0f76f6e8480a34..cdbc649626b4de 100644 --- a/Examples/UIExplorer/PickerAndroidExample.js +++ b/Examples/UIExplorer/PickerAndroidExample.js @@ -44,8 +44,6 @@ const PickerExample = React.createClass({ }; }, - displayName: 'Android Picker', - render: function() { return ( diff --git a/Examples/UIExplorer/PointerEventsExample.js b/Examples/UIExplorer/PointerEventsExample.js index 735a5952d49a45..4ca8e9a164dcc7 100644 --- a/Examples/UIExplorer/PointerEventsExample.js +++ b/Examples/UIExplorer/PointerEventsExample.js @@ -182,7 +182,7 @@ var BoxOnlyExample = React.createClass({ }); type ExampleClass = { - Component: ReactClass, + Component: ReactClass, title: string, description: string, }; diff --git a/Examples/UIExplorer/PullToRefreshViewAndroidExample.android.js b/Examples/UIExplorer/PullToRefreshViewAndroidExample.android.js deleted file mode 100644 index 3d22f7608e93f9..00000000000000 --- a/Examples/UIExplorer/PullToRefreshViewAndroidExample.android.js +++ /dev/null @@ -1,128 +0,0 @@ -/** -* The examples provided by Facebook are for non-commercial testing and -* evaluation purposes only. -* -* Facebook reserves all rights not expressly granted. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL -* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN -* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -* -*/ -'use strict'; - -const React = require('react-native'); -const { - ScrollView, - StyleSheet, - PullToRefreshViewAndroid, - Text, - TouchableWithoutFeedback, - View, -} = React; - -const styles = StyleSheet.create({ - row: { - borderColor: 'grey', - borderWidth: 1, - padding: 20, - backgroundColor: '#3a5795', - margin: 5, - }, - text: { - alignSelf: 'center', - color: '#fff', - - }, - layout: { - flex: 1, - }, - scrollview: { - flex: 1, - }, -}); - -const Row = React.createClass({ - _onClick: function() { - this.props.onClick(this.props.data); - }, - render: function() { - return ( - - - - {this.props.data.text + ' (' + this.props.data.clicks + ' clicks)'} - - - - ); - }, -}); -const PullToRefreshViewAndroidExample = React.createClass({ - statics: { - title: '', - description: 'Container that adds pull-to-refresh support to its child view.' - }, - - getInitialState() { - return { - isRefreshing: false, - loaded: 0, - rowData: Array.from(new Array(20)).map( - (val, i) => ({text: 'Initial row' + i, clicks: 0}) - ), - }; - }, - - _onClick(row) { - row.clicks++; - this.setState({ - rowData: this.state.rowData, - }); - }, - - render() { - const rows = this.state.rowData.map((row, ii) => { - return ; - }); - return ( - - - {rows} - - - ); - }, - - _onRefresh() { - this.setState({isRefreshing: true}); - setTimeout(() => { - // prepend 10 items - const rowData = Array.from(new Array(10)) - .map((val, i) => ({ - text: 'Loaded row' + (+this.state.loaded + i), - clicks: 0, - })) - .concat(this.state.rowData); - - this.setState({ - loaded: this.state.loaded + 10, - isRefreshing: false, - rowData: rowData, - }); - }, 5000); - }, - -}); - - -module.exports = PullToRefreshViewAndroidExample; diff --git a/Examples/UIExplorer/PushNotificationIOSExample.js b/Examples/UIExplorer/PushNotificationIOSExample.js index bd6109f1f09bd4..9fcf8cd1c343ed 100644 --- a/Examples/UIExplorer/PushNotificationIOSExample.js +++ b/Examples/UIExplorer/PushNotificationIOSExample.js @@ -84,6 +84,8 @@ class NotificationExample extends React.Component { } class NotificationPermissionExample extends React.Component { + state: any; + constructor(props) { super(props); this.state = {permissions: null}; @@ -126,7 +128,7 @@ exports.description = 'Apple PushNotification and badge value'; exports.examples = [ { title: 'Badge Number', - render(): React.Component { + render(): ReactElement { PushNotificationIOS.requestPermissions(); return ( @@ -145,13 +147,13 @@ exports.examples = [ }, { title: 'Push Notifications', - render(): React.Component { + render(): ReactElement { return ; } }, { title: 'Notifications Permissions', - render(): React.Component { + render(): ReactElement { return ; } }]; diff --git a/Examples/UIExplorer/RCTRootViewIOSExample.js b/Examples/UIExplorer/RCTRootViewIOSExample.js index 69ecab0a7655a1..5aa56cf881e44d 100644 --- a/Examples/UIExplorer/RCTRootViewIOSExample.js +++ b/Examples/UIExplorer/RCTRootViewIOSExample.js @@ -82,7 +82,7 @@ exports.description = 'Examples that show useful methods when embedding React Na exports.examples = [ { title: 'Updating app properties in runtime', - render(): React.Component { + render(): ReactElement { return ( ); @@ -90,7 +90,7 @@ exports.examples = [ }, { title: 'RCTRootView\'s size flexibility', - render(): React.Component { + render(): ReactElement { return ( ); diff --git a/Examples/UIExplorer/RootViewSizeFlexibilityExampleApp.js b/Examples/UIExplorer/RootViewSizeFlexibilityExampleApp.js new file mode 100644 index 00000000000000..9c48357033fb99 --- /dev/null +++ b/Examples/UIExplorer/RootViewSizeFlexibilityExampleApp.js @@ -0,0 +1,77 @@ +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * @flow + */ +'use strict'; + +const React = require('react-native'); +const { + StyleSheet, + Text, + TouchableHighlight, + View, +} = React; + +class RootViewSizeFlexibilityExampleApp extends React.Component { + state: any; + + constructor(props: {toggled: boolean}) { + super(props); + this.state = { toggled: false }; + } + + _onPressButton() { + this.setState({ toggled: !this.state.toggled }); + } + + render() { + const viewStyle = this.state.toggled ? styles.bigContainer : styles.smallContainer; + + return ( + + + + + React Native Button + + + + + ); + } + +} + +const styles = StyleSheet.create({ + bigContainer: { + flex: 1, + height: 60, + backgroundColor: 'gray', + }, + smallContainer: { + flex: 1, + height: 40, + backgroundColor: 'gray', + }, + center: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + whiteText: { + color: 'white', + } +}); + +module.exports = RootViewSizeFlexibilityExampleApp; diff --git a/Examples/UIExplorer/SetPropertiesExampleApp.js b/Examples/UIExplorer/SetPropertiesExampleApp.js index 748bbc3ccb4a6d..f5dc9c8eab39b0 100644 --- a/Examples/UIExplorer/SetPropertiesExampleApp.js +++ b/Examples/UIExplorer/SetPropertiesExampleApp.js @@ -13,17 +13,18 @@ * * @flow */ - 'use strict'; -var React = require('React'); -var Text = require('Text'); -var View = require('View'); +const React = require('react-native'); +const { + Text, + View, +} = React; -var SetPropertiesExampleApp = React.createClass({ +class SetPropertiesExampleApp extends React.Component { - render: function() { - var wrapperStyle = { + render() { + const wrapperStyle = { backgroundColor: this.props.color, flex: 1, alignItems: 'center', @@ -37,7 +38,8 @@ var SetPropertiesExampleApp = React.createClass({ ); - }, -}); + } + +} module.exports = SetPropertiesExampleApp; diff --git a/Examples/UIExplorer/SnapshotExample.js b/Examples/UIExplorer/SnapshotExample.js new file mode 100644 index 00000000000000..d843acee80d3fb --- /dev/null +++ b/Examples/UIExplorer/SnapshotExample.js @@ -0,0 +1,73 @@ +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * @flow + */ +'use strict'; + +var React = require('react-native'); +var { + Image, + StyleSheet, + Text, + UIManager, + View, +} = React; + +var ScreenshotExample = React.createClass({ + getInitialState() { + return { + uri: undefined, + }; + }, + + render() { + return ( + + + Click to take a screenshot + + + + ); + }, + + takeScreenshot() { + UIManager + .takeSnapshot('window', {format: 'jpeg', quality: 0.8}) // See UIManager.js for options + .then((uri) => this.setState({uri})) + .catch((error) => alert(error)); + } +}); + +var style = StyleSheet.create({ + button: { + marginBottom: 10, + fontWeight: '500', + }, + image: { + flex: 1, + height: 300, + resizeMode: 'contain', + backgroundColor: 'black', + }, +}); + +exports.title = 'Snapshot / Screenshot'; +exports.description = 'API to capture images from the screen.'; +exports.examples = [ + { + title: 'Take screenshot', + render(): ReactElement { return ; } + }, +]; diff --git a/Examples/UIExplorer/StatusBarExample.js b/Examples/UIExplorer/StatusBarExample.js index 094148c4d71dfd..e33dbd71a98b39 100644 --- a/Examples/UIExplorer/StatusBarExample.js +++ b/Examples/UIExplorer/StatusBarExample.js @@ -57,12 +57,16 @@ const showHideTransitions = [ 'slide', ]; +function getValue(values: Array, index: number): any { + return values[index % values.length]; +} + const StatusBarExample = React.createClass({ getInitialState(): State { return { animated: true, - backgroundColor: this._getValue(colors, 0), - showHideTransition: this._getValue(showHideTransitions, 0), + backgroundColor: getValue(colors, 0), + showHideTransition: getValue(showHideTransitions, 0), }; }, @@ -70,10 +74,6 @@ const StatusBarExample = React.createClass({ _barStyleIndex: 0, _showHideTransitionIndex: 0, - _getValue(values: Array, index: number): any { - return values[index % values.length]; - }, - render() { return ( @@ -110,10 +110,10 @@ const StatusBarExample = React.createClass({ style={styles.wrapper} onPress={() => { this._barStyleIndex++; - this.setState({barStyle: this._getValue(barStyles, this._barStyleIndex)}); + this.setState({barStyle: getValue(barStyles, this._barStyleIndex)}); }}> - style: '{this._getValue(barStyles, this._barStyleIndex)}' + style: '{getValue(barStyles, this._barStyleIndex)}' @@ -138,13 +138,13 @@ const StatusBarExample = React.createClass({ this._showHideTransitionIndex++; this.setState({ showHideTransition: - this._getValue(showHideTransitions, this._showHideTransitionIndex), + getValue(showHideTransitions, this._showHideTransitionIndex), }); }}> showHideTransition: - '{this._getValue(showHideTransitions, this._showHideTransitionIndex)}' + '{getValue(showHideTransitions, this._showHideTransitionIndex)}' @@ -155,10 +155,10 @@ const StatusBarExample = React.createClass({ style={styles.wrapper} onPress={() => { this._colorIndex++; - this.setState({backgroundColor: this._getValue(colors, this._colorIndex)}); + this.setState({backgroundColor: getValue(colors, this._colorIndex)}); }}> - backgroundColor: '{this._getValue(colors, this._colorIndex)}' + backgroundColor: '{getValue(colors, this._colorIndex)}' @@ -181,11 +181,116 @@ const StatusBarExample = React.createClass({ }, }); +const StatusBarStaticExample = React.createClass({ + _colorIndex: 0, + _barStyleIndex: 0, + _showHideTransitionIndex: 0, + + getInitialState() { + return { + backgroundColor: getValue(colors, 0), + barStyle: getValue(barStyles, 0), + hidden: false, + networkActivityIndicatorVisible: false, + translucent: false, + }; + }, + + render() { + return ( + + + { + const hidden = !this.state.hidden; + StatusBar.setHidden(hidden, 'slide'); + this.setState({hidden}); + }}> + + hidden: {this.state.hidden ? 'true' : 'false'} + + + + iOS + + { + this._barStyleIndex++; + const barStyle = getValue(barStyles, this._barStyleIndex); + StatusBar.setBarStyle(barStyle, true); + this.setState({barStyle}); + }}> + + style: '{getValue(barStyles, this._barStyleIndex)}' + + + + + { + const networkActivityIndicatorVisible = !this.state.networkActivityIndicatorVisible; + StatusBar.setNetworkActivityIndicatorVisible(networkActivityIndicatorVisible); + this.setState({networkActivityIndicatorVisible}); + }}> + + + networkActivityIndicatorVisible: + {this.state.networkActivityIndicatorVisible ? 'true' : 'false'} + + + + + Android + + { + this._colorIndex++; + const backgroundColor = getValue(colors, this._colorIndex); + StatusBar.setBackgroundColor(backgroundColor, true); + this.setState({backgroundColor}); + }}> + + backgroundColor: '{getValue(colors, this._colorIndex)}' + + + + + { + const translucent = !this.state.translucent; + const backgroundColor = !this.state.translucent ? 'rgba(0, 0, 0, 0.4)' : 'black'; + StatusBar.setTranslucent(translucent); + StatusBar.setBackgroundColor(backgroundColor, true); + this.setState({ + translucent, + backgroundColor, + }); + }}> + + translucent: {this.state.translucent ? 'true' : 'false'} + + + + + ); + }, +}); + exports.examples = [{ - title: 'Status Bar', + title: 'StatusBar', render() { return ; }, +}, { + title: 'StatusBar static API', + render() { + return ; + }, }]; var styles = StyleSheet.create({ diff --git a/Examples/UIExplorer/TextInputExample.android.js b/Examples/UIExplorer/TextInputExample.android.js index 3652b974c8fc19..0c5f1c0c8698c6 100644 --- a/Examples/UIExplorer/TextInputExample.android.js +++ b/Examples/UIExplorer/TextInputExample.android.js @@ -75,17 +75,24 @@ var TextEventsExample = React.createClass({ class AutoExpandingTextInput extends React.Component { constructor(props) { super(props); - this.state = {text: '', height: 0}; + this.state = { + text: `React Native enables you to build world-class application experiences on native platforms using a consistent developer experience based on JavaScript and React. The focus of React Native is on developer efficiency across all the platforms you care about — learn once, write anywhere. Facebook uses React Native in multiple production apps and will continue investing in React Native.`, + height: 0, + }; } render() { return ( { + this.setState({ + height: event.nativeEvent.contentSize.height, + }); + }} onChange={(event) => { this.setState({ text: event.nativeEvent.text, - height: event.nativeEvent.contentSize.height, }); }} style={[styles.default, {height: Math.max(35, this.state.height)}]} @@ -178,6 +185,60 @@ class TokenizedTextExample extends React.Component { } } +var BlurOnSubmitExample = React.createClass({ + focusNextField(nextField) { + this.refs[nextField].focus(); + }, + + render: function() { + return ( + + this.focusNextField('2')} + /> + this.focusNextField('3')} + /> + this.focusNextField('4')} + /> + this.focusNextField('5')} + /> + + + ); + } +}); + var styles = StyleSheet.create({ multiline: { height: 60, @@ -286,6 +347,10 @@ exports.examples = [ return {examples}; } }, + { + title: 'Blur on submit', + render: function(): ReactElement { return ; }, + }, { title: 'Event handling', render: function(): ReactElement { return ; }, diff --git a/Examples/UIExplorer/TextInputExample.ios.js b/Examples/UIExplorer/TextInputExample.ios.js index eac262d7706157..239197dbacbb9e 100644 --- a/Examples/UIExplorer/TextInputExample.ios.js +++ b/Examples/UIExplorer/TextInputExample.ios.js @@ -97,6 +97,8 @@ var TextEventsExample = React.createClass({ }); class AutoExpandingTextInput extends React.Component { + state: any; + constructor(props) { super(props); this.state = {text: '', height: 0}; @@ -120,6 +122,8 @@ class AutoExpandingTextInput extends React.Component { } class RewriteExample extends React.Component { + state: any; + constructor(props) { super(props); this.state = {text: ''}; @@ -149,6 +153,8 @@ class RewriteExample extends React.Component { } class RewriteExampleInvalidCharacters extends React.Component { + state: any; + constructor(props) { super(props); this.state = {text: ''}; @@ -170,6 +176,8 @@ class RewriteExampleInvalidCharacters extends React.Component { } class TokenizedTextExample extends React.Component { + state: any; + constructor(props) { super(props); this.state = {text: 'Hello #World'}; diff --git a/Examples/UIExplorer/TouchableExample.js b/Examples/UIExplorer/TouchableExample.js index 515a0c2b858c03..5b28f2e134d40c 100644 --- a/Examples/UIExplorer/TouchableExample.js +++ b/Examples/UIExplorer/TouchableExample.js @@ -24,6 +24,8 @@ var { TouchableHighlight, TouchableOpacity, UIManager, + Platform, + TouchableNativeFeedback, View, } = React; @@ -55,7 +57,7 @@ exports.examples = [ activeOpacity={1} animationVelocity={0} underlayColor="rgb(210, 230, 255)" - onPress={() => console.log('custom THW text - hightlight')}> + onPress={() => console.log('custom THW text - highlight')}> Tap Here For Custom Highlight! @@ -93,7 +95,21 @@ exports.examples = [ return ; }, platform: 'ios', -}]; +}, { + title: 'Touchable Hit Slop', + description: ' components accept hitSlop prop which extends the touch area ' + + 'without changing the view bounds.', + render: function(): ReactElement { + return ; + }, + }, { + title: 'Disabled Touchable*', + description: ' components accept disabled prop which prevents ' + + 'any interaction with component', + render: function(): ReactElement { + return ; + }, + }]; var TextOnPressBox = React.createClass({ getInitialState: function() { @@ -243,6 +259,114 @@ var ForceTouchExample = React.createClass({ }, }); +var TouchableHitSlop = React.createClass({ + getInitialState: function() { + return { + timesPressed: 0, + }; + }, + onPress: function() { + this.setState({ + timesPressed: this.state.timesPressed + 1, + }); + }, + render: function() { + var log = ''; + if (this.state.timesPressed > 1) { + log = this.state.timesPressed + 'x onPress'; + } else if (this.state.timesPressed > 0) { + log = 'onPress'; + } + + return ( + + + + + Press Outside This View + + + + + + {log} + + + + ); + } +}); + +var TouchableDisabled = React.createClass({ + render: function() { + return ( + + + Disabled TouchableOpacity + + + + Enabled TouchableOpacity + + + console.log('custom THW text - highlight')}> + + Disabled TouchableHighlight + + + + console.log('custom THW text - highlight')}> + + Disabled TouchableHighlight + + + + {Platform.OS === 'android' && + console.log('custom TNF has been clicked')} + background={TouchableNativeFeedback.SelectableBackground()}> + + + Enabled TouchableNativeFeedback + + + + } + + {Platform.OS === 'android' && + console.log('custom TNF has been clicked')} + background={TouchableNativeFeedback.SelectableBackground()}> + + + Disabled TouchableNativeFeedback + + + + } + + ); + } +}); + var heartImage = {uri: 'https://pbs.twimg.com/media/BlXBfT3CQAA6cVZ.png:small'}; var styles = StyleSheet.create({ @@ -261,9 +385,23 @@ var styles = StyleSheet.create({ text: { fontSize: 16, }, + block: { + padding: 10, + }, button: { color: '#007AFF', }, + disabledButton: { + color: '#007AFF', + opacity: 0.5, + }, + nativeFeedbackButton: { + textAlign: 'center', + margin: 10, + }, + hitSlopButton: { + color: 'white', + }, wrapper: { borderRadius: 8, }, @@ -271,6 +409,10 @@ var styles = StyleSheet.create({ borderRadius: 8, padding: 6, }, + hitSlopWrapper: { + backgroundColor: 'red', + marginVertical: 30, + }, logBox: { padding: 20, margin: 10, diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj index e6dfc36585b773..95f9215cee9840 100644 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 1300627F1B59179B0043FE5A /* RCTGzipTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1300627E1B59179B0043FE5A /* RCTGzipTests.m */; }; + 13129DD41C85F87C007D611C /* RCTModuleInitNotificationRaceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13129DD31C85F87C007D611C /* RCTModuleInitNotificationRaceTests.m */; }; 1323F1891C04AB9F0091BED0 /* bunny.png in Resources */ = {isa = PBXBuildFile; fileRef = 1323F1851C04AB9F0091BED0 /* bunny.png */; }; 1323F18A1C04AB9F0091BED0 /* flux@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1323F1861C04AB9F0091BED0 /* flux@3x.png */; }; 1323F18B1C04AB9F0091BED0 /* hawk.png in Resources */ = {isa = PBXBuildFile; fileRef = 1323F1871C04AB9F0091BED0 /* hawk.png */; }; @@ -18,6 +19,7 @@ 1341802C1AA9178B003F314A /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1341802B1AA91779003F314A /* libRCTNetwork.a */; }; 134454601AAFCABD003F0779 /* libRCTAdSupport.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1344545A1AAFCAAE003F0779 /* libRCTAdSupport.a */; }; 134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 134A8A251AACED6A00945AAE /* libRCTGeolocation.a */; }; + 134CB92A1C85A38800265FA6 /* RCTModuleInitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 134CB9291C85A38800265FA6 /* RCTModuleInitTests.m */; }; 138D6A181B53CD440074A87E /* RCTShadowViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 138D6A161B53CD440074A87E /* RCTShadowViewTests.m */; }; 138DEE241B9EDFB6007F4EA5 /* libRCTCameraRoll.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 138DEE091B9EDDDB007F4EA5 /* libRCTCameraRoll.a */; }; 1393D0381B68CD1300E1B601 /* RCTModuleMethodTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1393D0371B68CD1300E1B601 /* RCTModuleMethodTests.m */; }; @@ -178,6 +180,7 @@ /* Begin PBXFileReference section */ 004D289E1AAF61C70097A701 /* UIExplorerUnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UIExplorerUnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 1300627E1B59179B0043FE5A /* RCTGzipTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGzipTests.m; sourceTree = ""; }; + 13129DD31C85F87C007D611C /* RCTModuleInitNotificationRaceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleInitNotificationRaceTests.m; sourceTree = ""; }; 1323F1851C04AB9F0091BED0 /* bunny.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = bunny.png; sourceTree = ""; }; 1323F1861C04AB9F0091BED0 /* flux@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "flux@3x.png"; sourceTree = ""; }; 1323F1871C04AB9F0091BED0 /* hawk.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = hawk.png; sourceTree = ""; }; @@ -188,6 +191,7 @@ 134180261AA91779003F314A /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../../Libraries/Network/RCTNetwork.xcodeproj; sourceTree = ""; }; 134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAdSupport.xcodeproj; path = ../../Libraries/AdSupport/RCTAdSupport.xcodeproj; sourceTree = ""; }; 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../../Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = ""; }; + 134CB9291C85A38800265FA6 /* RCTModuleInitTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleInitTests.m; sourceTree = ""; }; 138D6A161B53CD440074A87E /* RCTShadowViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTShadowViewTests.m; sourceTree = ""; }; 138DEE021B9EDDDB007F4EA5 /* RCTCameraRoll.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTCameraRoll.xcodeproj; path = ../../Libraries/CameraRoll/RCTCameraRoll.xcodeproj; sourceTree = ""; }; 1393D0371B68CD1300E1B601 /* RCTModuleMethodTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleMethodTests.m; sourceTree = ""; }; @@ -422,6 +426,8 @@ 144D21231B2204C5006DB32B /* RCTImageUtilTests.m */, 13DB03471B5D2ED500C27245 /* RCTJSONTests.m */, 13DF61B51B67A45000EDB188 /* RCTMethodArgumentTests.m */, + 134CB9291C85A38800265FA6 /* RCTModuleInitTests.m */, + 13129DD31C85F87C007D611C /* RCTModuleInitNotificationRaceTests.m */, 1393D0371B68CD1300E1B601 /* RCTModuleMethodTests.m */, 138D6A161B53CD440074A87E /* RCTShadowViewTests.m */, 1497CFAB1B21F5E400C1F8F2 /* RCTUIManagerTests.m */, @@ -882,7 +888,9 @@ 1300627F1B59179B0043FE5A /* RCTGzipTests.m in Sources */, 1497CFAF1B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m in Sources */, 1497CFAE1B21F5E400C1F8F2 /* RCTJSCExecutorTests.m in Sources */, + 13129DD41C85F87C007D611C /* RCTModuleInitNotificationRaceTests.m in Sources */, 1497CFAD1B21F5E400C1F8F2 /* RCTBridgeTests.m in Sources */, + 134CB92A1C85A38800265FA6 /* RCTModuleInitTests.m in Sources */, 1497CFB11B21F5E400C1F8F2 /* RCTEventDispatcherTests.m in Sources */, 1497CFB31B21F5E400C1F8F2 /* RCTUIManagerTests.m in Sources */, 13DB03481B5D2ED500C27245 /* RCTJSONTests.m in Sources */, diff --git a/Examples/UIExplorer/UIExplorer/AppDelegate.m b/Examples/UIExplorer/UIExplorer/AppDelegate.m index 577e6dd94b5d1c..b0d19a59c170ac 100644 --- a/Examples/UIExplorer/UIExplorer/AppDelegate.m +++ b/Examples/UIExplorer/UIExplorer/AppDelegate.m @@ -16,6 +16,7 @@ #import "RCTBridge.h" #import "RCTJavaScriptLoader.h" +#import "RCTLinkingManager.h" #import "RCTRootView.h" @interface AppDelegate() @@ -80,6 +81,14 @@ - (NSURL *)sourceURLForBridge:(__unused RCTBridge *)bridge return sourceURL; } + +- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url + sourceApplication:(NSString *)sourceApplication annotation:(id)annotation +{ + return [RCTLinkingManager application:application openURL:url + sourceApplication:sourceApplication annotation:annotation]; +} + - (void)loadSourceForBridge:(RCTBridge *)bridge withBlock:(RCTSourceLoadBlock)loadCallback { diff --git a/Examples/UIExplorer/UIExplorer/Info.plist b/Examples/UIExplorer/UIExplorer/Info.plist index 9e91b1cef8cc6b..d2e82af921427a 100644 --- a/Examples/UIExplorer/UIExplorer/Info.plist +++ b/Examples/UIExplorer/UIExplorer/Info.plist @@ -18,10 +18,28 @@ 1.0 CFBundleSignature ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + com.reactjs.ios + CFBundleURLSchemes + + rnuiexplorer + + + CFBundleVersion 1 LSRequiresIPhoneOS + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSLocationWhenInUseUsageDescription You need to add NSLocationWhenInUseUsageDescription key in Info.plist to enable geolocation, otherwise it is going to *fail silently*! UILaunchStoryboardName @@ -38,11 +56,5 @@ UIViewControllerBasedStatusBarAppearance - NSAppTransportSecurity - - - NSAllowsArbitraryLoads - - diff --git a/Examples/UIExplorer/UIExplorerActions.js b/Examples/UIExplorer/UIExplorerActions.js new file mode 100644 index 00000000000000..8f7a59f4e23e30 --- /dev/null +++ b/Examples/UIExplorer/UIExplorerActions.js @@ -0,0 +1,51 @@ +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * @flow + */ +'use strict'; + +export type UIExplorerListWithFilterAction = { + type: 'UIExplorerListWithFilterAction', + filter: ?string; +}; + +export type UIExplorerExampleAction = { + type: 'UIExplorerExampleAction', + openExample: string; +}; + +import type {BackAction} from 'NavigationRootContainer'; + +export type UIExplorerAction = BackAction | UIExplorerListWithFilterAction | UIExplorerExampleAction; + +function ExampleListWithFilter(filter: ?string): UIExplorerListWithFilterAction { + return { + type: 'UIExplorerListWithFilterAction', + filter, + }; +} + +function ExampleAction(openExample: string): UIExplorerExampleAction { + return { + type: 'UIExplorerExampleAction', + openExample, + }; +} + +const UIExplorerActions = { + ExampleListWithFilter, + ExampleAction, +}; + +module.exports = UIExplorerActions; diff --git a/Examples/UIExplorer/UIExplorerApp.android.js b/Examples/UIExplorer/UIExplorerApp.android.js index cdf92c6848de45..026567bf5d9d79 100644 --- a/Examples/UIExplorer/UIExplorerApp.android.js +++ b/Examples/UIExplorer/UIExplorerApp.android.js @@ -16,87 +16,119 @@ */ 'use strict'; -var React = require('react-native'); -var { +const React = require('react-native'); +const { AppRegistry, BackAndroid, Dimensions, DrawerLayoutAndroid, + NavigationExperimental, StyleSheet, ToolbarAndroid, View, StatusBar, } = React; -var UIExplorerList = require('./UIExplorerList.android'); +const { + RootContainer: NavigationRootContainer, +} = NavigationExperimental; +const UIExplorerActions = require('./UIExplorerActions'); +const UIExplorerExampleList = require('./UIExplorerExampleList'); +const UIExplorerList = require('./UIExplorerList'); +const UIExplorerNavigationReducer = require('./UIExplorerNavigationReducer'); +const UIExplorerStateTitleMap = require('./UIExplorerStateTitleMap'); var DRAWER_WIDTH_LEFT = 56; -var UIExplorerApp = React.createClass({ - getInitialState: function() { - return { - example: this._getUIExplorerHome(), - }; - }, - - _getUIExplorerHome: function() { - return { - title: 'UIExplorer', - component: this._renderHome(), - }; - }, +class UIExplorerApp extends React.Component { + componentWillMount() { + BackAndroid.addEventListener('hardwareBackPress', this._handleBackButtonPress.bind(this)); + } - componentWillMount: function() { - BackAndroid.addEventListener('hardwareBackPress', this._handleBackButtonPress); - }, + render() { + return ( + { this._navigationRootRef = navRootRef; }} + reducer={UIExplorerNavigationReducer} + renderNavigation={this._renderApp.bind(this)} + /> + ); + } - render: function() { + _renderApp(navigationState, onNavigate) { + if (!navigationState) { + return null; + } return ( { + this._overrideBackPressForDrawerLayout = true; + }} + onDrawerClose={() => { + this._overrideBackPressForDrawerLayout = false; + }} ref={(drawer) => { this.drawer = drawer; }} - renderNavigationView={this._renderNavigationView}> - {this._renderNavigation()} + renderNavigationView={this._renderDrawerContent.bind(this, onNavigate)}> + {this._renderNavigation(navigationState, onNavigate)} - ); - }, + ); + } - _renderNavigationView: function() { + _renderDrawerContent(onNavigate) { return ( - { + this.drawer && this.drawer.closeDrawer(); + onNavigate(action); + }} /> ); - }, + } - onSelectExample: function(example) { - this.drawer.closeDrawer(); - if (example.title === this._getUIExplorerHome().title) { - example = this._getUIExplorerHome(); + _renderNavigation(navigationState, onNavigate) { + if (navigationState.externalExample) { + var Component = UIExplorerList.Modules[navigationState.externalExample]; + return ( + { + onNavigate(NavigationRootContainer.getBackAction()); + }} + ref={(example) => { this._exampleRef = example; }} + /> + ); } - this.setState({ - example: example, - }); - }, + const {stack} = navigationState; + const title = UIExplorerStateTitleMap(stack.children[stack.index]); + const index = stack.children.length <= 1 ? 1 : stack.index; - _renderHome: function() { - var onSelectExample = this.onSelectExample; - return React.createClass({ - render: function() { - return ( - + - ); - } - }); - }, - - _renderNavigation: function() { - var Component = this.state.example.component; + this.drawer.openDrawer()} + style={styles.toolbar} + title={title} + /> + { this._exampleRef = example; }} + /> + + ); + } return ( this.drawer.openDrawer()} style={styles.toolbar} - title={this.state.example.title} + title={title} /> - { this._exampleRef = example; }} + ); - }, + } - _handleBackButtonPress: function() { + _handleBackButtonPress() { + if (this._overrideBackPressForDrawerLayout) { + // This hack is necessary because drawer layout provides an imperative API + // with open and close methods. This code would be cleaner if the drawer + // layout provided an `isOpen` prop and allowed us to pass a `onDrawerClose` handler. + this.drawer && this.drawer.closeDrawer(); + return true; + } if ( this._exampleRef && this._exampleRef.handleBackAction && @@ -124,15 +164,16 @@ var UIExplorerApp = React.createClass({ ) { return true; } - if (this.state.example.title !== this._getUIExplorerHome().title) { - this.onSelectExample(this._getUIExplorerHome()); - return true; + if (this._navigationRootRef) { + return this._navigationRootRef.handleNavigation( + NavigationRootContainer.getBackAction() + ); } return false; - }, -}); + } +} -var styles = StyleSheet.create({ +const styles = StyleSheet.create({ container: { flex: 1, }, diff --git a/Examples/UIExplorer/UIExplorerApp.ios.js b/Examples/UIExplorer/UIExplorerApp.ios.js index f93cc2a66a64e8..384ea1c18c73d7 100644 --- a/Examples/UIExplorer/UIExplorerApp.ios.js +++ b/Examples/UIExplorer/UIExplorerApp.ios.js @@ -16,137 +16,179 @@ */ 'use strict'; -var React = require('react-native'); -var UIExplorerList = require('./UIExplorerList.ios'); -var { +const React = require('react-native'); +const UIExplorerActions = require('./UIExplorerActions'); +const UIExplorerList = require('./UIExplorerList.ios'); +const UIExplorerExampleList = require('./UIExplorerExampleList'); +const UIExplorerNavigationReducer = require('./UIExplorerNavigationReducer'); +const UIExplorerStateTitleMap = require('./UIExplorerStateTitleMap'); + +const { + Alert, + Animated, AppRegistry, - NavigatorIOS, + NavigationExperimental, + SnapshotViewIOS, StyleSheet, Text, TouchableHighlight, View, - StatusBar, } = React; -var UIExplorerApp = React.createClass({ +const { + CardStack: NavigationCardStack, + Header: NavigationHeader, + Reducer: NavigationReducer, + RootContainer: NavigationRootContainer, +} = NavigationExperimental; - getInitialState: function() { - return { - openExternalExample: (null: ?React.Component), - }; - }, +import type { Value } from 'Animated'; + +import type { NavigationSceneRendererProps } from 'NavigationTypeDefinition'; + +import type { UIExplorerNavigationState } from './UIExplorerNavigationReducer'; + +import type { UIExplorerExample } from './UIExplorerList.ios'; - render: function() { - if (this.state.openExternalExample) { - var Example = this.state.openExternalExample; +function PathActionMap(path: string): ?Object { + // Warning! Hacky parsing for example code. Use a library for this! + const exampleParts = path.split('/example/'); + const exampleKey = exampleParts[1]; + if (exampleKey) { + if (!UIExplorerList.Modules[exampleKey]) { + Alert.alert(`${exampleKey} example could not be found!`); + return null; + } + return UIExplorerActions.ExampleAction(exampleKey); + } + return null; +} + +function URIActionMap(uri: ?string): ?Object { + // Warning! Hacky parsing for example code. Use a library for this! + if (!uri) { + return null; + } + const parts = uri.split('rnuiexplorer:/'); + if (!parts[1]) { + return null; + } + const path = parts[1]; + return PathActionMap(path); +} + +class UIExplorerApp extends React.Component { + _navigationRootRef: ?NavigationRootContainer; + _renderNavigation: Function; + _renderOverlay: Function; + _renderScene: Function; + _renderCard: Function; + componentWillMount() { + this._renderNavigation = this._renderNavigation.bind(this); + this._renderOverlay = this._renderOverlay.bind(this); + this._renderScene = this._renderScene.bind(this); + } + render() { + return ( + { this._navigationRootRef = navRootRef; }} + renderNavigation={this._renderNavigation} + linkingActionMap={URIActionMap} + /> + ); + } + _renderNavigation(navigationState: UIExplorerNavigationState, onNavigate: Function) { + if (!navigationState) { + return null; + } + if (navigationState.externalExample) { + var Component = UIExplorerList.Modules[navigationState.externalExample]; return ( - { - this.setState({ openExternalExample: null, }); + onNavigate(NavigationRootContainer.getBackAction()); }} /> ); } - + const {stack} = navigationState; return ( - - - { - this.setState({ openExternalExample: example, }); - }, - } - }} - itemWrapperStyle={styles.itemWrapper} - tintColor="#008888" - /> - + ); } -}); - -var styles = StyleSheet.create({ - container: { - flex: 1, - }, - itemWrapper: { - backgroundColor: '#eaeaea', - }, - bigContainer: { - flex: 1, - height: 60, - backgroundColor: 'gray', - }, - smallContainer: { - flex: 1, - height: 40, - backgroundColor: 'gray', - }, - center: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - }, - whiteText: { - color: 'white', - } -}); - -var SetPropertiesExampleApp = React.createClass({ - - render: function() { - var wrapperStyle = { - backgroundColor: this.props.color, - flex: 1, - alignItems: 'center', - justifyContent: 'center' - }; + _renderOverlay(props: NavigationSceneRendererProps): ReactElement { return ( - - - Embedded React Native view - - + ); - }, -}); + } -var RootViewSizeFlexibilityExampleApp = React.createClass({ + _renderScene(props: NavigationSceneRendererProps): ?ReactElement { + const state = props.scene.navigationState; + if (state.key === 'AppList') { + return ( + + ); + } - getInitialState: function () { - return { toggled: false }; - }, + const Example = UIExplorerList.Modules[state.key]; + if (Example) { + const Component = UIExplorerExampleList.makeRenderable(Example); + return ( + + + + ); + } + return null; + } +} - _onPressButton: function() { - this.setState({ toggled: !this.state.toggled }); +const styles = StyleSheet.create({ + container: { + flex: 1, }, - - render: function() { - var viewStyle = this.state.toggled ? styles.bigContainer : styles.smallContainer; - - return ( - - - - - React Native Button - - - - - ); + exampleContainer: { + flex: 1, + paddingTop: 60, }, }); -AppRegistry.registerComponent('SetPropertiesExampleApp', () => SetPropertiesExampleApp); -AppRegistry.registerComponent('RootViewSizeFlexibilityExampleApp', () => RootViewSizeFlexibilityExampleApp); +AppRegistry.registerComponent('SetPropertiesExampleApp', () => require('./SetPropertiesExampleApp')); +AppRegistry.registerComponent('RootViewSizeFlexibilityExampleApp', () => require('./RootViewSizeFlexibilityExampleApp')); AppRegistry.registerComponent('UIExplorerApp', () => UIExplorerApp); -UIExplorerList.registerComponents(); + +// Register suitable examples for snapshot tests +UIExplorerList.ComponentExamples.concat(UIExplorerList.APIExamples).forEach((Example: UIExplorerExample) => { + const ExampleModule = Example.module; + if (ExampleModule.displayName) { + var Snapshotter = React.createClass({ + render: function() { + var Renderable = UIExplorerExampleList.makeRenderable(ExampleModule); + return ( + + + + ); + }, + }); + AppRegistry.registerComponent(ExampleModule.displayName, () => Snapshotter); + } +}); module.exports = UIExplorerApp; diff --git a/Examples/UIExplorer/UIExplorerExampleList.js b/Examples/UIExplorer/UIExplorerExampleList.js new file mode 100644 index 00000000000000..4a81314c293c3c --- /dev/null +++ b/Examples/UIExplorer/UIExplorerExampleList.js @@ -0,0 +1,218 @@ +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * @flow + */ +'use strict'; + +const React = require('react-native'); +const UIExplorerActions = require('./UIExplorerActions'); +const { + ListView, + NavigationExperimental, + StyleSheet, + Text, + TextInput, + TouchableHighlight, + View, +} = React; +const createExamplePage = require('./createExamplePage'); +const { + Container: NavigationContainer, +} = NavigationExperimental; + +import type { + UIExplorerExample, +} from './UIExplorerList.ios' + +const ds = new ListView.DataSource({ + rowHasChanged: (r1, r2) => r1 !== r2, + sectionHeaderHasChanged: (h1, h2) => h1 !== h2, +}); + +class UIExplorerExampleList extends React.Component { + constuctor(props: { + disableTitleRow: ?boolean, + onNavigate: Function, + filter: ?string, + list: { + ComponentExamples: Array, + APIExamples: Array, + }, + style: ?any, + }) { + + } + render(): ?ReactElement { + const filterText = this.props.filter || ''; + const filterRegex = new RegExp(String(filterText), 'i'); + const filter = (example) => filterRegex.test(example.module.title); + + const dataSource = ds.cloneWithRowsAndSections({ + components: this.props.list.ComponentExamples.filter(filter), + apis: this.props.list.APIExamples.filter(filter), + }); + return ( + + {this._renderTitleRow()} + {this._renderTextInput()} + + + ); + } + + _renderTitleRow(): ?ReactElement { + if (!this.props.displayTitleRow) { + return null; + } + return this._renderRow( + 'UIExplorer', + 'React Native Examples', + 'home_key', + () => { + this.props.onNavigate( + UIExplorerActions.ExampleListWithFilter('') + ); + } + ); + } + + _renderTextInput(): ?ReactElement { + if (this.props.disableSearch) { + return null; + } + return ( + + { + this.props.onNavigate(UIExplorerActions.ExampleListWithFilter(text)); + }} + placeholder="Search..." + style={[styles.searchTextInput, this.props.searchTextInputStyle]} + testID="explorer_search" + value={this.props.filter} + /> + + ); + } + + _renderSectionHeader(data: any, section: string): ?ReactElement { + return ( + + {section.toUpperCase()} + + ); + } + + _renderExampleRow(example: {key: string, module: Object}): ?ReactElement { + return this._renderRow( + example.module.title, + example.module.description, + example.key, + () => this._handleRowPress(example.key) + ); + } + + _renderRow(title: string, description: string, key: ?string, handler: ?Function): ?ReactElement { + return ( + + + + + {title} + + + {description} + + + + + + ); + } + + _handleRowPress(exampleKey: string): void { + this.props.onNavigate(UIExplorerActions.ExampleAction(exampleKey)) + } +} + +function makeRenderable(example: any): ReactClass { + return example.examples ? + createExamplePage(null, example) : + example; +} + +UIExplorerExampleList = NavigationContainer.create(UIExplorerExampleList); +UIExplorerExampleList.makeRenderable = makeRenderable; + +var styles = StyleSheet.create({ + listContainer: { + flex: 1, + }, + list: { + backgroundColor: '#eeeeee', + }, + sectionHeader: { + padding: 5, + fontWeight: '500', + fontSize: 11, + }, + group: { + backgroundColor: 'white', + }, + row: { + backgroundColor: 'white', + justifyContent: 'center', + paddingHorizontal: 15, + paddingVertical: 8, + }, + separator: { + height: StyleSheet.hairlineWidth, + backgroundColor: '#bbbbbb', + marginLeft: 15, + }, + rowTitleText: { + fontSize: 17, + fontWeight: '500', + }, + rowDetailText: { + fontSize: 15, + color: '#888888', + lineHeight: 20, + }, + searchRow: { + backgroundColor: '#eeeeee', + padding: 10, + }, + searchTextInput: { + backgroundColor: 'white', + borderColor: '#cccccc', + borderRadius: 3, + borderWidth: 1, + paddingLeft: 8, + height: 35, + }, +}); + +module.exports = UIExplorerExampleList; diff --git a/Examples/UIExplorer/UIExplorerList.android.js b/Examples/UIExplorer/UIExplorerList.android.js index 8959a0919f9a63..68be333e51095d 100644 --- a/Examples/UIExplorer/UIExplorerList.android.js +++ b/Examples/UIExplorer/UIExplorerList.android.js @@ -15,103 +15,171 @@ */ 'use strict'; -var React = require('react-native'); -var { - StyleSheet, - View, -} = React; -var UIExplorerListBase = require('./UIExplorerListBase'); +export type UIExplorerExample = { + key: string; + module: React.Component; +}; -var COMPONENTS = [ - require('./ImageExample'), - require('./ListViewExample'), - require('./PickerAndroidExample'), - require('./ProgressBarAndroidExample'), - require('./PullToRefreshViewAndroidExample.android'), - require('./RefreshControlExample'), - require('./ScrollViewSimpleExample'), - require('./StatusBarExample'), - require('./SwitchExample'), - require('./TextExample.android'), - require('./TextInputExample.android'), - require('./ToolbarAndroidExample'), - require('./TouchableExample'), - require('./ViewExample'), - require('./ViewPagerAndroidExample.android'), - require('./WebViewExample'), +var ComponentExamples: Array = [ + { + key: 'ImageExample', + module: require('./ImageExample'), + }, + { + key: 'ListViewExample', + module: require('./ListViewExample'), + }, + { + key: 'PickerAndroidExample', + module: require('./PickerAndroidExample'), + }, + { + key: 'ProgressBarAndroidExample', + module: require('./ProgressBarAndroidExample'), + }, + { + key: 'RefreshControlExample', + module: require('./RefreshControlExample'), + }, + { + key: 'ScrollViewSimpleExample', + module: require('./ScrollViewSimpleExample'), + }, + { + key: 'StatusBarExample', + module: require('./StatusBarExample'), + }, + { + key: 'SwitchExample', + module: require('./SwitchExample'), + }, + { + key: 'TextExample', + module: require('./TextExample'), + }, + { + key: 'TextInputExample', + module: require('./TextInputExample'), + }, + { + key: 'ToolbarAndroidExample', + module: require('./ToolbarAndroidExample'), + }, + { + key: 'TouchableExample', + module: require('./TouchableExample'), + }, + { + key: 'ViewExample', + module: require('./ViewExample'), + }, + { + key: 'ViewPagerAndroidExample', + module: require('./ViewPagerAndroidExample'), + }, + { + key: 'WebViewExample', + module: require('./WebViewExample'), + }, ]; -var APIS = [ - require('./AccessibilityAndroidExample.android'), - require('./AlertExample').AlertExample, - require('./AppStateExample'), - require('./BorderExample'), - require('./CameraRollExample'), - require('./ClipboardExample'), - require('./DatePickerAndroidExample'), - require('./GeolocationExample'), - require('./ImageEditingExample'), - require('./IntentAndroidExample.android'), - require('./LayoutEventsExample'), - require('./LayoutExample'), - require('./NavigationExperimental/NavigationExperimentalExample'), - require('./NetInfoExample'), - require('./PanResponderExample'), - require('./PointerEventsExample'), - require('./TimePickerAndroidExample'), - require('./TimerExample'), - require('./ToastAndroidExample.android'), - require('./XHRExample'), +const APIExamples = [ + { + key: 'AccessibilityAndroidExample', + module: require('./AccessibilityAndroidExample'), + }, + { + key: 'AlertExample', + module: require('./AlertExample').AlertExample, + }, + { + key: 'AppStateExample', + module: require('./AppStateExample'), + }, + { + key: 'BorderExample', + module: require('./BorderExample'), + }, + { + key: 'CameraRollExample', + module: require('./CameraRollExample'), + }, + { + key: 'ClipboardExample', + module: require('./ClipboardExample'), + }, + { + key: 'DatePickerAndroidExample', + module: require('./DatePickerAndroidExample'), + }, + { + key: 'GeolocationExample', + module: require('./GeolocationExample'), + }, + { + key: 'ImageEditingExample', + module: require('./ImageEditingExample'), + }, + { + key: 'LayoutEventsExample', + module: require('./LayoutEventsExample'), + }, + { + key: 'LinkingExample', + module: require('./LinkingExample'), + }, + { + key: 'LayoutExample', + module: require('./LayoutExample'), + }, + { + key: 'NavigationExperimentalExample', + module: require('./NavigationExperimental/NavigationExperimentalExample'), + }, + { + key: 'NetInfoExample', + module: require('./NetInfoExample'), + }, + { + key: 'PanResponderExample', + module: require('./PanResponderExample'), + }, + { + key: 'PointerEventsExample', + module: require('./PointerEventsExample'), + }, + { + key: 'TimePickerAndroidExample', + module: require('./TimePickerAndroidExample'), + }, + { + key: 'TimerExample', + module: require('./TimerExample'), + }, + { + key: 'ToastAndroidExample', + module: require('./ToastAndroidExample'), + }, + { + key: 'VibrationExample', + module: require('./VibrationExample'), + }, + { + key: 'XHRExample', + module: require('./XHRExample'), + }, ]; -type Props = { - onSelectExample: Function, - isInDrawer: bool, -}; - -class UIExplorerList extends React.Component { - props: Props; - - render() { - return ( - - ); - } +const Modules = {}; - renderAdditionalView(renderRow, renderTextInput): React.Component { - if (this.props.isInDrawer) { - var homePage = renderRow({ - title: 'UIExplorer', - description: 'List of examples', - }, -1); - return ( - - {homePage} - - ); - } - return renderTextInput(styles.searchTextInput); - } - - onPressRow(example: any) { - var Component = UIExplorerListBase.makeRenderable(example); - this.props.onSelectExample({ - title: Component.title, - component: Component, - }); - } -} - -var styles = StyleSheet.create({ - searchTextInput: { - padding: 2, - }, +APIExamples.concat(ComponentExamples).forEach(Example => { + Modules[Example.key] = Example.module; }); +const UIExplorerList = { + APIExamples, + ComponentExamples, + Modules, +}; + module.exports = UIExplorerList; diff --git a/Examples/UIExplorer/UIExplorerList.ios.js b/Examples/UIExplorer/UIExplorerList.ios.js index 5c33d7f645e539..8d770d1f40be7a 100644 --- a/Examples/UIExplorer/UIExplorerList.ios.js +++ b/Examples/UIExplorer/UIExplorerList.ios.js @@ -15,151 +15,255 @@ */ 'use strict'; -var React = require('react-native'); -var { - AppRegistry, - Settings, - SnapshotViewIOS, - StyleSheet, -} = React; - -import type { NavigationContext } from 'NavigationContext'; - -var UIExplorerListBase = require('./UIExplorerListBase'); - -var COMPONENTS = [ - require('./ActivityIndicatorIOSExample'), - require('./DatePickerIOSExample'), - require('./ImageExample'), - require('./LayoutEventsExample'), - require('./ListViewExample'), - require('./ListViewGridLayoutExample'), - require('./ListViewPagingExample'), - require('./MapViewExample'), - require('./ModalExample'), - require('./Navigator/NavigatorExample'), - require('./NavigatorIOSColorsExample'), - require('./NavigatorIOSExample'), - require('./PickerIOSExample'), - require('./ProgressViewIOSExample'), - require('./RefreshControlExample'), - require('./ScrollViewExample'), - require('./SegmentedControlIOSExample'), - require('./SliderIOSExample'), - require('./StatusBarExample'), - require('./SwitchExample'), - require('./TabBarIOSExample'), - require('./TextExample.ios'), - require('./TextInputExample.ios'), - require('./TouchableExample'), - require('./TransparentHitTestExample'), - require('./ViewExample'), - require('./WebViewExample'), -]; +export type UIExplorerExample = { + key: string; + module: Object; +}; -var APIS = [ - require('./AccessibilityIOSExample'), - require('./ActionSheetIOSExample'), - require('./AdSupportIOSExample'), - require('./AlertIOSExample'), - require('./AnimatedExample'), - require('./AnimatedGratuitousApp/AnExApp'), - require('./AppStateIOSExample'), - require('./AppStateExample'), - require('./AsyncStorageExample'), - require('./BorderExample'), - require('./BoxShadowExample'), - require('./CameraRollExample'), - require('./ClipboardExample'), - require('./GeolocationExample'), - require('./ImageEditingExample'), - require('./LayoutExample'), - require('./NavigationExperimental/NavigationExperimentalExample'), - require('./NetInfoExample'), - require('./PanResponderExample'), - require('./PointerEventsExample'), - require('./PushNotificationIOSExample'), - require('./RCTRootViewIOSExample'), - require('./StatusBarIOSExample'), - require('./TimerExample'), - require('./TransformExample'), - require('./VibrationIOSExample'), - require('./XHRExample.ios'), +var ComponentExamples: Array = [ + { + key: 'ActivityIndicatorIOSExample', + module: require('./ActivityIndicatorIOSExample'), + }, + { + key: 'DatePickerIOSExample', + module: require('./DatePickerIOSExample'), + }, + { + key: 'ImageExample', + module: require('./ImageExample'), + }, + { + key: 'LayoutEventsExample', + module: require('./LayoutEventsExample'), + }, + { + key: 'ListViewExample', + module: require('./ListViewExample'), + }, + { + key: 'ListViewGridLayoutExample', + module: require('./ListViewGridLayoutExample'), + }, + { + key: 'ListViewPagingExample', + module: require('./ListViewPagingExample'), + }, + { + key: 'MapViewExample', + module: require('./MapViewExample'), + }, + { + key: 'ModalExample', + module: require('./ModalExample'), + }, + { + key: 'NavigatorExample', + module: require('./Navigator/NavigatorExample'), + }, + { + key: 'NavigatorIOSColorsExample', + module: require('./NavigatorIOSColorsExample'), + }, + { + key: 'NavigatorIOSExample', + module: require('./NavigatorIOSExample'), + }, + { + key: 'PickerIOSExample', + module: require('./PickerIOSExample'), + }, + { + key: 'ProgressViewIOSExample', + module: require('./ProgressViewIOSExample'), + }, + { + key: 'RefreshControlExample', + module: require('./RefreshControlExample'), + }, + { + key: 'ScrollViewExample', + module: require('./ScrollViewExample'), + }, + { + key: 'SegmentedControlIOSExample', + module: require('./SegmentedControlIOSExample'), + }, + { + key: 'SliderIOSExample', + module: require('./SliderIOSExample'), + }, + { + key: 'StatusBarExample', + module: require('./StatusBarExample'), + }, + { + key: 'SwitchExample', + module: require('./SwitchExample'), + }, + { + key: 'TabBarIOSExample', + module: require('./TabBarIOSExample'), + }, + { + key: 'TextExample', + module: require('./TextExample.ios'), + }, + { + key: 'TextInputExample', + module: require('./TextInputExample.ios'), + }, + { + key: 'TouchableExample', + module: require('./TouchableExample'), + }, + { + key: 'TransparentHitTestExample', + module: require('./TransparentHitTestExample'), + }, + { + key: 'ViewExample', + module: require('./ViewExample'), + }, + { + key: 'WebViewExample', + module: require('./WebViewExample'), + }, ]; -type Props = { - navigator: { - navigationContext: NavigationContext, - push: (route: {title: string, component: ReactClass}) => void, +var APIExamples: Array = [ + { + key: 'AccessibilityIOSExample', + module: require('./AccessibilityIOSExample'), }, - onExternalExampleRequested: Function, -}; - -class UIExplorerList extends React.Component { - props: Props; - - render() { - return ( - - ); - } - - renderAdditionalView(renderRow: Function, renderTextInput: Function): React.Component { - return renderTextInput(styles.searchTextInput); - } - - search(text: mixed) { - Settings.set({searchText: text}); - } - - _openExample(example: any) { - if (example.external) { - this.props.onExternalExampleRequested(example); - return; - } - - var Component = UIExplorerListBase.makeRenderable(example); - this.props.navigator.push({ - title: Component.title, - component: Component, - }); - } - - onPressRow(example: any) { - this._openExample(example); - } + { + key: 'ActionSheetIOSExample', + module: require('./ActionSheetIOSExample'), + }, + { + key: 'AdSupportIOSExample', + module: require('./AdSupportIOSExample'), + }, + { + key: 'AlertIOSExample', + module: require('./AlertIOSExample'), + }, + { + key: 'AnimatedExample', + module: require('./AnimatedExample'), + }, + { + key: 'AnExApp', + module: require('./AnimatedGratuitousApp/AnExApp'), + }, + { + key: 'AppStateIOSExample', + module: require('./AppStateIOSExample'), + }, + { + key: 'AppStateExample', + module: require('./AppStateExample'), + }, + { + key: 'AsyncStorageExample', + module: require('./AsyncStorageExample'), + }, + { + key: 'BorderExample', + module: require('./BorderExample'), + }, + { + key: 'BoxShadowExample', + module: require('./BoxShadowExample'), + }, + { + key: 'CameraRollExample', + module: require('./CameraRollExample'), + }, + { + key: 'ClipboardExample', + module: require('./ClipboardExample'), + }, + { + key: 'GeolocationExample', + module: require('./GeolocationExample'), + }, + { + key: 'ImageEditingExample', + module: require('./ImageEditingExample'), + }, + { + key: 'LayoutExample', + module: require('./LayoutExample'), + }, + { + key: 'LinkingExample', + module: require('./LinkingExample'), + }, + { + key: 'NavigationExperimentalExample', + module: require('./NavigationExperimental/NavigationExperimentalExample'), + }, + { + key: 'NavigationExperimentalLegacyNavigatorExample', + module: require('./NavigationExperimental/LegacyNavigator/LegacyNavigatorExample'), + }, + { + key: 'NetInfoExample', + module: require('./NetInfoExample'), + }, + { + key: 'PanResponderExample', + module: require('./PanResponderExample'), + }, + { + key: 'PointerEventsExample', + module: require('./PointerEventsExample'), + }, + { + key: 'PushNotificationIOSExample', + module: require('./PushNotificationIOSExample'), + }, + { + key: 'RCTRootViewIOSExample', + module: require('./RCTRootViewIOSExample'), + }, + { + key: 'SnapshotExample', + module: require('./SnapshotExample'), + }, + { + key: 'StatusBarIOSExample', + module: require('./StatusBarIOSExample'), + }, + { + key: 'TimerExample', + module: require('./TimerExample'), + }, + { + key: 'TransformExample', + module: require('./TransformExample'), + }, + { + key: 'VibrationExample', + module: require('./VibrationExample'), + }, + { + key: 'XHRExample', + module: require('./XHRExample.ios'), + }, +]; - // Register suitable examples for snapshot tests - static registerComponents() { - COMPONENTS.concat(APIS).forEach((Example) => { - if (Example.displayName) { - var Snapshotter = React.createClass({ - render: function() { - var Renderable = UIExplorerListBase.makeRenderable(Example); - return ( - - - - ); - }, - }); - AppRegistry.registerComponent(Example.displayName, () => Snapshotter); - } - }); - } -} +const Modules = {}; -var styles = StyleSheet.create({ - searchTextInput: { - height: 30, - }, +APIExamples.concat(ComponentExamples).forEach(Example => { + Modules[Example.key] = Example.module; }); +const UIExplorerList = { + APIExamples, + ComponentExamples, + Modules, +}; + module.exports = UIExplorerList; diff --git a/Examples/UIExplorer/UIExplorerListBase.js b/Examples/UIExplorer/UIExplorerListBase.js deleted file mode 100644 index 17ecd8dc2c02b0..00000000000000 --- a/Examples/UIExplorer/UIExplorerListBase.js +++ /dev/null @@ -1,190 +0,0 @@ -/** - * The examples provided by Facebook are for non-commercial testing and - * evaluation purposes only. - * - * Facebook reserves all rights not expressly granted. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL - * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN - * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * @flow - */ -'use strict'; - -var React = require('react-native'); -var { - ListView, - StyleSheet, - Text, - TextInput, - TouchableHighlight, - View, -} = React; -var createExamplePage = require('./createExamplePage'); - -var ds = new ListView.DataSource({ - rowHasChanged: (r1, r2) => r1 !== r2, - sectionHeaderHasChanged: (h1, h2) => h1 !== h2, -}); - -class UIExplorerListBase extends React.Component { - constructor(props: any) { - super(props); - this.state = { - dataSource: ds.cloneWithRowsAndSections({ - components: [], - apis: [], - }), - searchText: this.props.searchText || '', - }; - } - - componentDidMount(): void { - this.search(this.state.searchText); - } - - render() { - var topView = this.props.renderAdditionalView && - this.props.renderAdditionalView(this.renderRow.bind(this), this.renderTextInput.bind(this)); - - return ( - - {topView} - - - ); - } - - renderTextInput(searchTextInputStyle: any) { - return ( - - - - ); - } - - _renderSectionHeader(data: any, section: string) { - return ( - - {section.toUpperCase()} - - ); - } - - renderRow(example: any, i: number) { - return ( - - this.onPressRow(example)}> - - - {example.title} - - - {example.description} - - - - - - ); - } - - search(text: mixed): void { - this.props.search && this.props.search(text); - - var regex = new RegExp(String(text), 'i'); - var filter = (component) => regex.test(component.title); - - this.setState({ - dataSource: ds.cloneWithRowsAndSections({ - components: this.props.components.filter(filter), - apis: this.props.apis.filter(filter), - }), - searchText: text, - }); - } - - onPressRow(example: any): void { - this.props.onPressRow && this.props.onPressRow(example); - } - - static makeRenderable(example: any): ReactClass { - return example.examples ? - createExamplePage(null, example) : - example; - } -} - -var styles = StyleSheet.create({ - listContainer: { - flex: 1, - }, - list: { - backgroundColor: '#eeeeee', - }, - sectionHeader: { - padding: 5, - fontWeight: '500', - fontSize: 11, - }, - group: { - backgroundColor: 'white', - }, - row: { - backgroundColor: 'white', - justifyContent: 'center', - paddingHorizontal: 15, - paddingVertical: 8, - }, - separator: { - height: StyleSheet.hairlineWidth, - backgroundColor: '#bbbbbb', - marginLeft: 15, - }, - rowTitleText: { - fontSize: 17, - fontWeight: '500', - }, - rowDetailText: { - fontSize: 15, - color: '#888888', - lineHeight: 20, - }, - searchRow: { - backgroundColor: '#eeeeee', - paddingTop: 75, - paddingLeft: 10, - paddingRight: 10, - paddingBottom: 10, - }, - searchTextInput: { - backgroundColor: 'white', - borderColor: '#cccccc', - borderRadius: 3, - borderWidth: 1, - paddingLeft: 8, - }, -}); - -module.exports = UIExplorerListBase; diff --git a/Examples/UIExplorer/UIExplorerNavigationReducer.js b/Examples/UIExplorer/UIExplorerNavigationReducer.js new file mode 100644 index 00000000000000..854e5b36eed75a --- /dev/null +++ b/Examples/UIExplorer/UIExplorerNavigationReducer.js @@ -0,0 +1,106 @@ +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * @flow + */ +'use strict'; + +const React = require('react-native'); +// $FlowFixMe : This is a platform-forked component, and flow seems to only run on iOS? +const UIExplorerList = require('./UIExplorerList'); +const { + NavigationExperimental, +} = React; +const { + Reducer: NavigationReducer, +} = NavigationExperimental; +const StackReducer = NavigationReducer.StackReducer; + +import type {NavigationState} from 'NavigationTypeDefinition'; + +import type {UIExplorerAction} from './UIExplorerActions'; + +export type UIExplorerNavigationState = { + externalExample: ?string; + stack: NavigationState; +}; + +const UIExplorerStackReducer = StackReducer({ + getPushedReducerForAction: (action, lastState) => { + if (action.type === 'UIExplorerExampleAction' && UIExplorerList.Modules[action.openExample]) { + if (lastState.children.find(child => child.key === action.openExample)) { + // The example is already open, we should avoid pushing examples twice + return null; + } + return (state) => state || {key: action.openExample}; + } + return null; + }, + getReducerForState: (initialState) => (state) => state || initialState, + initialState: { + key: 'UIExplorerMainStack', + index: 0, + children: [ + {key: 'AppList'}, + ], + }, +}); + +function UIExplorerNavigationReducer(lastState: ?UIExplorerNavigationState, action: any): UIExplorerNavigationState { + if (!lastState) { + return { + externalExample: null, + stack: UIExplorerStackReducer(null, action), + }; + } + if (action.type === 'UIExplorerListWithFilterAction') { + return { + externalExample: null, + stack: { + key: 'UIExplorerMainStack', + index: 0, + children: [ + { + key: 'AppList', + filter: action.filter, + }, + ], + }, + }; + } + if (action.type === 'BackAction' && lastState.externalExample) { + return { + ...lastState, + externalExample: null, + }; + } + if (action.type === 'UIExplorerExampleAction') { + const ExampleModule = UIExplorerList.Modules[action.openExample]; + if (ExampleModule && ExampleModule.external) { + return { + ...lastState, + externalExample: action.openExample, + }; + } + } + const newStack = UIExplorerStackReducer(lastState.stack, action); + if (newStack !== lastState.stack) { + return { + externalExample: null, + stack: newStack, + } + } + return lastState; +} + +module.exports = UIExplorerNavigationReducer; diff --git a/Examples/UIExplorer/UIExplorerPage.js b/Examples/UIExplorer/UIExplorerPage.js index 2c74497a7fef3b..14bf2bed010e68 100644 --- a/Examples/UIExplorer/UIExplorerPage.js +++ b/Examples/UIExplorer/UIExplorerPage.js @@ -37,9 +37,9 @@ var UIExplorerPage = React.createClass({ var ContentWrapper; var wrapperProps = {}; if (this.props.noScroll) { - ContentWrapper = (View: ReactClass); + ContentWrapper = (View: ReactClass); } else { - ContentWrapper = (ScrollView: ReactClass); + ContentWrapper = (ScrollView: ReactClass); wrapperProps.automaticallyAdjustContentInsets = !this.props.title; wrapperProps.keyboardShouldPersistTaps = true; wrapperProps.keyboardDismissMode = 'interactive'; diff --git a/Examples/UIExplorer/UIExplorerStateTitleMap.js b/Examples/UIExplorer/UIExplorerStateTitleMap.js new file mode 100644 index 00000000000000..a7facd838e6a90 --- /dev/null +++ b/Examples/UIExplorer/UIExplorerStateTitleMap.js @@ -0,0 +1,33 @@ +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * @flow + */ +'use strict'; + +// $FlowFixMe : This is a platform-forked component, and flow seems to only run on iOS? +const UIExplorerList = require('./UIExplorerList'); + +import type {NavigationState} from 'NavigationTypeDefinition'; + +function StateTitleMap(state: NavigationState): string { + if (UIExplorerList.Modules[state.key]) { + return UIExplorerList.Modules[state.key].title + } + if (state.key === 'AppList') { + return 'UIExplorer'; + } + return 'Unknown'; +} + +module.exports = StateTitleMap; diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m index 31fc8f74ee9e1b..08a1a1e37fec81 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m @@ -24,8 +24,12 @@ #define RUN_RUNLOOP_WHILE(CONDITION) \ { \ NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:5]; \ - while ((CONDITION) && [timeout timeIntervalSinceNow] > 0) { \ + while ((CONDITION)) { \ [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; \ + if ([timeout timeIntervalSinceNow] <= 0) { \ + XCTFail(@"Runloop timed out before condition was met"); \ + break; \ + } \ } \ } @@ -37,6 +41,8 @@ @interface TestExecutor : NSObject @implementation TestExecutor +@synthesize valid = _valid; + RCT_EXPORT_MODULE() - (void)setUp {} @@ -51,7 +57,7 @@ - (instancetype)init - (BOOL)isValid { - return YES; + return _valid; } - (void)flushedQueue:(RCTJavaScriptCallback)onComplete @@ -94,7 +100,10 @@ - (void)injectJSONText:(NSString *)script onComplete(nil); } -- (void)invalidate {} +- (void)invalidate +{ + _valid = NO; +} @end @@ -128,7 +137,7 @@ - (void)setUp [_bridge invalidate]; [_bridge setUp]; - _jsExecutor = [_bridge.batchedBridge valueForKey:@"javaScriptExecutor"]; + _jsExecutor = _bridge.batchedBridge.javaScriptExecutor; XCTAssertNotNil(_jsExecutor); } @@ -139,10 +148,8 @@ - (void)tearDown _testMethodCalled = NO; [_bridge invalidate]; + RUN_RUNLOOP_WHILE(_jsExecutor.isValid); _bridge = nil; - - RUN_RUNLOOP_WHILE(_jsExecutor != nil); - XCTAssertNotNil(_jsExecutor); } - (void)testHookRegistration diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m index 5713d03473a7ca..5a7cc12bd28eee 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m @@ -47,10 +47,9 @@ - (void)testImageLoading return nil; }]; - RCTImageLoader *imageLoader = [RCTImageLoader new]; - NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader, imageLoader]; } launchOptions:nil]; + NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader]; } launchOptions:nil]; - [imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:RCTResizeModeContain progressBlock:^(int64_t progress, int64_t total) { + [bridge.imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:RCTResizeModeContain progressBlock:^(int64_t progress, int64_t total) { XCTAssertEqual(progress, 1); XCTAssertEqual(total, 1); } completionBlock:^(NSError *loadError, id loadedImage) { @@ -78,10 +77,9 @@ - (void)testImageLoaderUsesImageURLLoaderWithHighestPriority return nil; }]; - RCTImageLoader *imageLoader = [RCTImageLoader new]; - NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader1, loader2, imageLoader]; } launchOptions:nil]; + NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader1, loader2]; } launchOptions:nil]; - [imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:RCTResizeModeContain progressBlock:^(int64_t progress, int64_t total) { + [bridge.imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:RCTResizeModeContain progressBlock:^(int64_t progress, int64_t total) { XCTAssertEqual(progress, 1); XCTAssertEqual(total, 1); } completionBlock:^(NSError *loadError, id loadedImage) { @@ -103,10 +101,9 @@ - (void)testImageDecoding return nil; }]; - RCTImageLoader *imageLoader = [RCTImageLoader new]; - NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder, imageLoader]; } launchOptions:nil]; + NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder]; } launchOptions:nil]; - RCTImageLoaderCancellationBlock cancelBlock = [imageLoader decodeImageData:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:RCTResizeModeStretch completionBlock:^(NSError *decodeError, id decodedImage) { + RCTImageLoaderCancellationBlock cancelBlock = [bridge.imageLoader decodeImageDataWithoutClipping:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:RCTResizeModeStretch completionBlock:^(NSError *decodeError, id decodedImage) { XCTAssertEqualObjects(decodedImage, image); XCTAssertNil(decodeError); }]; @@ -133,10 +130,9 @@ - (void)testImageLoaderUsesImageDecoderWithHighestPriority return nil; }]; - RCTImageLoader *imageLoader = [RCTImageLoader new]; - NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder1, decoder2, imageLoader]; } launchOptions:nil]; + NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder1, decoder2]; } launchOptions:nil]; - RCTImageLoaderCancellationBlock cancelBlock = [imageLoader decodeImageData:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:RCTResizeModeStretch completionBlock:^(NSError *decodeError, id decodedImage) { + RCTImageLoaderCancellationBlock cancelBlock = [bridge.imageLoader decodeImageDataWithoutClipping:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:RCTResizeModeStretch completionBlock:^(NSError *decodeError, id decodedImage) { XCTAssertEqualObjects(decodedImage, image); XCTAssertNil(decodeError); }]; diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitNotificationRaceTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitNotificationRaceTests.m new file mode 100644 index 00000000000000..77a997a4baa59c --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitNotificationRaceTests.m @@ -0,0 +1,129 @@ +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import +#import + +#import "RCTBridge.h" +#import "RCTBridge+Private.h" +#import "RCTBridgeModule.h" +#import "RCTUtils.h" +#import "RCTUIManager.h" +#import "RCTViewManager.h" + +#define RUN_RUNLOOP_WHILE(CONDITION) \ +{ \ + NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:5]; \ + while ((CONDITION)) { \ + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; \ + if ([timeout timeIntervalSinceNow] <= 0) { \ + XCTFail(@"Runloop timed out before condition was met"); \ + break; \ + } \ + } \ +} + +@interface RCTTestViewManager : RCTViewManager +@end + +@implementation RCTTestViewManager + +RCT_EXPORT_MODULE() + +- (NSArray *)customDirectEventTypes +{ + return @[@"foo"]; +} + +@end + + +@interface RCTNotificationObserverModule : NSObject + +@property (nonatomic, assign) BOOL didDetectViewManagerInit; + +@end + +@implementation RCTNotificationObserverModule + +@synthesize bridge = _bridge; + +RCT_EXPORT_MODULE() + +- (void)setBridge:(RCTBridge *)bridge +{ + _bridge = bridge; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didInitViewManager:) name:RCTDidInitializeModuleNotification object:nil]; +} + +- (void)didInitViewManager:(NSNotification *)note +{ + id module = note.userInfo[@"module"]; + if ([module isKindOfClass:[RCTTestViewManager class]]) { + _didDetectViewManagerInit = YES; + } +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +@end + + +@interface RCTModuleInitNotificationRaceTests : XCTestCase +{ + RCTBridge *_bridge; + RCTNotificationObserverModule *_notificationObserver; +} +@end + +@implementation RCTModuleInitNotificationRaceTests + +- (NSURL *)sourceURLForBridge:(__unused RCTBridge *)bridge +{ + return nil; +} + +- (NSArray *)extraModulesForBridge:(__unused RCTBridge *)bridge +{ + return @[[RCTTestViewManager new], _notificationObserver]; +} + +- (void)setUp +{ + [super setUp]; + + _notificationObserver = [RCTNotificationObserverModule new]; + _bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:nil]; +} + +- (void)tearDown +{ + [super tearDown]; + + _notificationObserver = nil; + id jsExecutor = _bridge.batchedBridge.javaScriptExecutor; + [_bridge invalidate]; + RUN_RUNLOOP_WHILE(jsExecutor.isValid); + _bridge = nil; +} + +- (void)testViewManagerNotInitializedBeforeSetBridgeModule +{ + RUN_RUNLOOP_WHILE(!_notificationObserver.didDetectViewManagerInit); +} + +@end diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitTests.m new file mode 100644 index 00000000000000..83a549736809ff --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitTests.m @@ -0,0 +1,261 @@ +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import +#import + +#import "RCTBridge.h" +#import "RCTBridge+Private.h" +#import "RCTBridgeModule.h" +#import "RCTUtils.h" + +#define RUN_RUNLOOP_WHILE(CONDITION) \ +{ \ + NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:5]; \ + while ((CONDITION)) { \ + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; \ + if ([timeout timeIntervalSinceNow] <= 0) { \ + XCTFail(@"Runloop timed out before condition was met"); \ + break; \ + } \ + } \ +} + + +@interface RCTTestInjectedModule : NSObject +@end + +@implementation RCTTestInjectedModule + +@synthesize bridge = _bridge; +@synthesize methodQueue = _methodQueue; + +RCT_EXPORT_MODULE() + +@end + + +@interface RCTTestCustomInitModule : NSObject + +@property (nonatomic, assign) BOOL initializedOnMainThread; + +@end + +@implementation RCTTestCustomInitModule + +@synthesize bridge = _bridge; +@synthesize methodQueue = _methodQueue; + +RCT_EXPORT_MODULE() + +- (id)init +{ + if ((self = [super init])) { + _initializedOnMainThread = [NSThread isMainThread]; + } + return self; +} + +@end + + +@interface RCTTestCustomSetBridgeModule : NSObject + +@property (nonatomic, assign) BOOL setBridgeOnMainThread; + +@end + +@implementation RCTTestCustomSetBridgeModule + +@synthesize bridge = _bridge; +@synthesize methodQueue = _methodQueue; + +RCT_EXPORT_MODULE() + +- (void)setBridge:(RCTBridge *)bridge +{ + _bridge = bridge; + _setBridgeOnMainThread = [NSThread isMainThread]; +} + +@end + + +@interface RCTTestExportConstantsModule : NSObject + +@property (nonatomic, assign) BOOL exportedConstants; +@property (nonatomic, assign) BOOL exportedConstantsOnMainThread; + +@end + +@implementation RCTTestExportConstantsModule + +@synthesize bridge = _bridge; +@synthesize methodQueue = _methodQueue; + +RCT_EXPORT_MODULE() + +- (NSDictionary *)constantsToExport +{ + _exportedConstants = YES; + _exportedConstantsOnMainThread = [NSThread isMainThread]; + return @{ @"foo": @"bar" }; +} + +@end + + +@interface RCTLazyInitModule : NSObject +@end + +@implementation RCTLazyInitModule + +@synthesize bridge = _bridge; +@synthesize methodQueue = _methodQueue; + +RCT_EXPORT_MODULE() + +@end + + +@interface RCTModuleInitTests : XCTestCase +{ + RCTBridge *_bridge; + BOOL _injectedModuleInitNotificationSent; + BOOL _customInitModuleNotificationSent; + BOOL _customSetBridgeModuleNotificationSent; + BOOL _exportConstantsModuleNotificationSent; + BOOL _lazyInitModuleNotificationSent; + BOOL _lazyInitModuleNotificationSentOnMainThread; + BOOL _viewManagerModuleNotificationSent; + RCTTestInjectedModule *_injectedModule; +} +@end + +@implementation RCTModuleInitTests + +- (NSURL *)sourceURLForBridge:(__unused RCTBridge *)bridge +{ + return nil; +} + +- (NSArray *)extraModulesForBridge:(__unused RCTBridge *)bridge +{ + return @[_injectedModule]; +} + +- (void)setUp +{ + [super setUp]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(moduleDidInit:) name:RCTDidInitializeModuleNotification object:nil]; + + _injectedModuleInitNotificationSent = NO; + _customInitModuleNotificationSent = NO; + _customSetBridgeModuleNotificationSent = NO; + _exportConstantsModuleNotificationSent = NO; + _lazyInitModuleNotificationSent = NO; + _viewManagerModuleNotificationSent = NO; + _injectedModule = [RCTTestInjectedModule new]; + _bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:nil]; +} + +- (void)tearDown +{ + [super tearDown]; + + [[NSNotificationCenter defaultCenter] removeObserver:self name:RCTDidInitializeModuleNotification object:nil]; + + id jsExecutor = _bridge.batchedBridge.javaScriptExecutor; + [_bridge invalidate]; + RUN_RUNLOOP_WHILE(jsExecutor.isValid); + _bridge = nil; +} + +- (void)moduleDidInit:(NSNotification *)note +{ + id module = note.userInfo[@"module"]; + if ([module isKindOfClass:[RCTTestInjectedModule class]]) { + _injectedModuleInitNotificationSent = YES; + } else if ([module isKindOfClass:[RCTTestCustomInitModule class]]) { + _customInitModuleNotificationSent = YES; + } else if ([module isKindOfClass:[RCTTestCustomSetBridgeModule class]]) { + _customSetBridgeModuleNotificationSent = YES; + } else if ([module isKindOfClass:[RCTTestExportConstantsModule class]]) { + _exportConstantsModuleNotificationSent = YES; + } else if ([module isKindOfClass:[RCTLazyInitModule class]]) { + _lazyInitModuleNotificationSent = YES; + _lazyInitModuleNotificationSentOnMainThread = [NSThread isMainThread]; + } +} + +- (void)testInjectedModulesInitializedDuringBridgeInit +{ + XCTAssertEqual(_injectedModule, [_bridge moduleForClass:[RCTTestInjectedModule class]]); + XCTAssertEqual(_injectedModule.bridge, _bridge.batchedBridge); + XCTAssertNotNil(_injectedModule.methodQueue); + RUN_RUNLOOP_WHILE(!_injectedModuleInitNotificationSent); + XCTAssertTrue(_injectedModuleInitNotificationSent); +} + +- (void)testCustomInitModuleInitializedAtBridgeStartup +{ + RUN_RUNLOOP_WHILE(!_customInitModuleNotificationSent); + XCTAssertTrue(_customInitModuleNotificationSent); + RCTTestCustomInitModule *module = [_bridge moduleForClass:[RCTTestCustomInitModule class]]; + XCTAssertTrue(module.initializedOnMainThread); + XCTAssertEqual(module.bridge, _bridge.batchedBridge); + XCTAssertNotNil(module.methodQueue); +} + +- (void)testCustomSetBridgeModuleInitializedAtBridgeStartup +{ + RUN_RUNLOOP_WHILE(!_customSetBridgeModuleNotificationSent); + XCTAssertTrue(_customSetBridgeModuleNotificationSent); + RCTTestCustomSetBridgeModule *module = [_bridge moduleForClass:[RCTTestCustomSetBridgeModule class]]; + XCTAssertTrue(module.setBridgeOnMainThread); + XCTAssertEqual(module.bridge, _bridge.batchedBridge); + XCTAssertNotNil(module.methodQueue); +} + +- (void)testExportConstantsModuleInitializedAtBridgeStartup +{ + RUN_RUNLOOP_WHILE(!_exportConstantsModuleNotificationSent); + XCTAssertTrue(_exportConstantsModuleNotificationSent); + RCTTestExportConstantsModule *module = [_bridge moduleForClass:[RCTTestExportConstantsModule class]]; + RUN_RUNLOOP_WHILE(!module.exportedConstants); + XCTAssertTrue(module.exportedConstants); + XCTAssertTrue(module.exportedConstantsOnMainThread); + XCTAssertEqual(module.bridge, _bridge.batchedBridge); + XCTAssertNotNil(module.methodQueue); +} + +- (void)testLazyInitModuleNotInitializedDuringBridgeInit +{ + XCTAssertFalse(_lazyInitModuleNotificationSent); + + __block RCTLazyInitModule *module; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + module = [_bridge moduleForClass:[RCTLazyInitModule class]]; + }); + + RUN_RUNLOOP_WHILE(!module); + XCTAssertTrue(_lazyInitModuleNotificationSent); + XCTAssertFalse(_lazyInitModuleNotificationSentOnMainThread); + XCTAssertNotNil(module); + XCTAssertEqual(module.bridge, _bridge.batchedBridge); + XCTAssertNotNil(module.methodQueue); +} + +@end diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m index f2715947a4eaf8..cfe3959bc756e6 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m @@ -17,11 +17,23 @@ #import "RCTShadowView.h" @interface RCTShadowViewTests : XCTestCase - +@property (nonatomic, strong) RCTShadowView *parentView; @end @implementation RCTShadowViewTests +- (void)setUp +{ + [super setUp]; + + self.parentView = [self _shadowViewWithStyle:^(css_style_t *style) { + style->flex_direction = CSS_FLEX_DIRECTION_COLUMN; + style->dimensions[0] = 440; + style->dimensions[1] = 440; + }]; + self.parentView.reactTag = @1; // must be valid rootView tag +} + // Just a basic sanity test to ensure css-layout is applied correctly in the context of our shadow view hierarchy. // // ==================================== @@ -69,33 +81,87 @@ - (void)testApplyingLayoutRecursivelyToShadowView style->flex = 1; }]; - RCTShadowView *parentView = [self _shadowViewWithStyle:^(css_style_t *style) { - style->flex_direction = CSS_FLEX_DIRECTION_COLUMN; - style->padding[0] = 10; - style->padding[1] = 10; - style->padding[2] = 10; - style->padding[3] = 10; - style->dimensions[0] = 440; - style->dimensions[1] = 440; - }]; + self.parentView.cssNode->style.padding[0] = 10; + self.parentView.cssNode->style.padding[1] = 10; + self.parentView.cssNode->style.padding[2] = 10; + self.parentView.cssNode->style.padding[3] = 10; + + [self.parentView insertReactSubview:headerView atIndex:0]; + [self.parentView insertReactSubview:mainView atIndex:1]; + [self.parentView insertReactSubview:footerView atIndex:2]; - [parentView insertReactSubview:headerView atIndex:0]; - [parentView insertReactSubview:mainView atIndex:1]; - [parentView insertReactSubview:footerView atIndex:2]; + [self.parentView collectRootUpdatedFrames]; - parentView.reactTag = @1; // must be valid rootView tag - [parentView collectRootUpdatedFrames]; + XCTAssertTrue(CGRectEqualToRect([self.parentView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(0, 0, 440, 440))); + XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets([self.parentView paddingAsInsets], UIEdgeInsetsMake(10, 10, 10, 10))); - XCTAssertTrue(CGRectEqualToRect([parentView measureLayoutRelativeToAncestor:parentView], CGRectMake(0, 0, 440, 440))); - XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets([parentView paddingAsInsets], UIEdgeInsetsMake(10, 10, 10, 10))); + XCTAssertTrue(CGRectEqualToRect([headerView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(10, 10, 420, 100))); + XCTAssertTrue(CGRectEqualToRect([mainView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(10, 120, 420, 200))); + XCTAssertTrue(CGRectEqualToRect([footerView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(10, 330, 420, 100))); - XCTAssertTrue(CGRectEqualToRect([headerView measureLayoutRelativeToAncestor:parentView], CGRectMake(10, 10, 420, 100))); - XCTAssertTrue(CGRectEqualToRect([mainView measureLayoutRelativeToAncestor:parentView], CGRectMake(10, 120, 420, 200))); - XCTAssertTrue(CGRectEqualToRect([footerView measureLayoutRelativeToAncestor:parentView], CGRectMake(10, 330, 420, 100))); + XCTAssertTrue(CGRectEqualToRect([leftView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(10, 120, 100, 200))); + XCTAssertTrue(CGRectEqualToRect([centerView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(120, 120, 200, 200))); + XCTAssertTrue(CGRectEqualToRect([rightView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(330, 120, 100, 200))); +} - XCTAssertTrue(CGRectEqualToRect([leftView measureLayoutRelativeToAncestor:parentView], CGRectMake(10, 120, 100, 200))); - XCTAssertTrue(CGRectEqualToRect([centerView measureLayoutRelativeToAncestor:parentView], CGRectMake(120, 120, 200, 200))); - XCTAssertTrue(CGRectEqualToRect([rightView measureLayoutRelativeToAncestor:parentView], CGRectMake(330, 120, 100, 200))); +- (void)testAssignsSuggestedWidthDimension +{ + [self _withShadowViewWithStyle:^(css_style_t *style) { + style->position[CSS_LEFT] = 0; + style->position[CSS_TOP] = 0; + style->dimensions[CSS_HEIGHT] = 10; + } + assertRelativeLayout:CGRectMake(0, 0, 3, 10) + withIntrinsicContentSize:CGSizeMake(3, UIViewNoIntrinsicMetric)]; +} + +- (void)testAssignsSuggestedHeightDimension +{ + [self _withShadowViewWithStyle:^(css_style_t *style) { + style->position[CSS_LEFT] = 0; + style->position[CSS_TOP] = 0; + style->dimensions[CSS_WIDTH] = 10; + } + assertRelativeLayout:CGRectMake(0, 0, 10, 4) + withIntrinsicContentSize:CGSizeMake(UIViewNoIntrinsicMetric, 4)]; +} + +- (void)testDoesNotOverrideDimensionStyleWithSuggestedDimensions +{ + [self _withShadowViewWithStyle:^(css_style_t *style) { + style->position[CSS_LEFT] = 0; + style->position[CSS_TOP] = 0; + style->dimensions[CSS_WIDTH] = 10; + style->dimensions[CSS_HEIGHT] = 10; + } + assertRelativeLayout:CGRectMake(0, 0, 10, 10) + withIntrinsicContentSize:CGSizeMake(3, 4)]; +} + +- (void)testDoesNotAssignSuggestedDimensionsWhenStyledWithFlexAttribute +{ + float parentWidth = self.parentView.cssNode->style.dimensions[CSS_WIDTH]; + float parentHeight = self.parentView.cssNode->style.dimensions[CSS_HEIGHT]; + [self _withShadowViewWithStyle:^(css_style_t *style) { + style->flex = 1; + } + assertRelativeLayout:CGRectMake(0, 0, parentWidth, parentHeight) + withIntrinsicContentSize:CGSizeMake(3, 4)]; +} + +- (void)_withShadowViewWithStyle:(void(^)(css_style_t *style))styleBlock + assertRelativeLayout:(CGRect)expectedRect + withIntrinsicContentSize:(CGSize)contentSize +{ + RCTShadowView *view = [self _shadowViewWithStyle:styleBlock]; + [self.parentView insertReactSubview:view atIndex:0]; + view.intrinsicContentSize = contentSize; + [self.parentView collectRootUpdatedFrames]; + CGRect actualRect = [view measureLayoutRelativeToAncestor:self.parentView]; + XCTAssertTrue(CGRectEqualToRect(expectedRect, actualRect), + @"Expected layout to be %@, got %@", + NSStringFromCGRect(expectedRect), + NSStringFromCGRect(actualRect)); } - (RCTShadowView *)_shadowViewWithStyle:(void(^)(css_style_t *style))styleBlock diff --git a/Examples/UIExplorer/VibrationExample.js b/Examples/UIExplorer/VibrationExample.js new file mode 100644 index 00000000000000..11f17d368b86b7 --- /dev/null +++ b/Examples/UIExplorer/VibrationExample.js @@ -0,0 +1,54 @@ +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * @flow + */ +'use strict'; + +var React = require('react-native'); +var { + StyleSheet, + View, + Text, + TouchableHighlight, + Vibration, +} = React; + +exports.framework = 'React'; +exports.title = 'Vibration'; +exports.description = 'Vibration API'; +exports.examples = [{ + title: 'Vibration.vibrate()', + render() { + return ( + Vibration.vibrate()}> + + Vibrate + + + ); + }, +}]; + +var styles = StyleSheet.create({ + wrapper: { + borderRadius: 5, + marginBottom: 5, + }, + button: { + backgroundColor: '#eeeeee', + padding: 10, + }, +}); diff --git a/Examples/UIExplorer/WebViewExample.js b/Examples/UIExplorer/WebViewExample.js index f7346149395cd8..1b5f836df601ef 100644 --- a/Examples/UIExplorer/WebViewExample.js +++ b/Examples/UIExplorer/WebViewExample.js @@ -20,6 +20,7 @@ var { StyleSheet, Text, TextInput, + TouchableWithoutFeedback, TouchableOpacity, View, WebView @@ -160,6 +161,60 @@ var WebViewExample = React.createClass({ }); +var Button = React.createClass({ + _handlePress: function() { + if (this.props.enabled !== false && this.props.onPress) { + this.props.onPress(); + } + }, + render: function() { + return ( + + + {this.props.text} + + + ); + } +}); + +var ScaledWebView = React.createClass({ + + getInitialState: function() { + return { + scalingEnabled: true, + } + }, + + render: function() { + return ( + + + + { this.state.scalingEnabled ? +