Move away from rnplay to snack, with embedded examples!

Summary:
React Native Playground has been sunset, so I've replaced the examples that previously used it with examples using [Snack](http://snack.expo.io/).

The examples are directly embedded and can be edited live to see updates. The code itself is also in the docs, so we can easily update the docs in one place and we don't have to actually go to a saved app on Snack and update it there.

Run it locally, go to the `Animations` section and the `Direct Manipulation` section.

![screen shot 2017-04-03 at 6 29 51 pm](https://cloud.githubusercontent.com/assets/90494/24638271/ff3ad044-189b-11e7-845d-24b2fb612d95.png)

Open it on your phone, notice that it falls back to just showing plain code.

<img src="https://cloud.githubusercontent.com/assets/90494/24638547/203ec8fc-189e-11e7-99c8-dfabff949f8d.PNG" width="250">

- Get rid of the Expo new user experience dialog that you see when you open a Snack -- is this a dealbreaker
Closes https://github.com/facebook/react-native/pull/13285

Differential Revision: D4828011

Pulled By: hramos

fbshipit-source-id: 684ad24a14deb72abb8587ffbb726d316f126d75
This commit is contained in:
Brent Vatne 2017-04-05 18:55:45 -07:00 committed by Facebook Github Bot
parent 6dbcb47e4c
commit b8542397cd
6 changed files with 439 additions and 169 deletions

View File

@ -26,34 +26,33 @@ The [`Animated`](docs/animated.html) API is designed to make it very easy to con
For example, a container view that fades in when it is mounted may look like this: For example, a container view that fades in when it is mounted may look like this:
```javascript ```SnackPlayer
// FadeInView.js import React from 'react';
import React, { Component } from 'react'; import { Animated, Text, View } from 'react-native';
import {
Animated,
} from 'react-native';
class FadeInView extends Component { class FadeInView extends React.Component {
constructor(props) { state = {
super(props);
this.state = {
fadeAnim: new Animated.Value(0), // Initial value for opacity: 0 fadeAnim: new Animated.Value(0), // Initial value for opacity: 0
};
} }
componentDidMount() { componentDidMount() {
Animated.timing( // Animate over time Animated.timing( // Animate over time
this.state.fadeAnim, // The animated value to drive this.state.fadeAnim, // The animated value to drive
{ {
toValue: 1, // Animate to opacity: 1, or fully opaque toValue: 1, // Animate to opacity: 1 (opaque)
duration: 10000, // Make it take a while
} }
).start(); // Starts the animation ).start(); // Starts the animation
} }
render() { render() {
let { fadeAnim } = this.state;
return ( return (
<Animated.View // Special animatable View <Animated.View // Special animatable View
style={{ style={{
...this.props.style, ...this.props.style,
opacity: this.state.fadeAnim, // Bind opacity to animated value opacity: fadeAnim, // Bind opacity to animated value
}} }}
> >
{this.props.children} {this.props.children}
@ -62,23 +61,20 @@ class FadeInView extends Component {
} }
} }
module.exports = FadeInView; // You can then use your `FadeInView` in place of a `View` in your components:
``` export default class App extends React.Component {
render() {
You can then use your `FadeInView` in place of a `View` in your components, like so:
```javascript
render() {
return ( return (
<View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
<FadeInView style={{width: 250, height: 50, backgroundColor: 'powderblue'}}> <FadeInView style={{width: 250, height: 50, backgroundColor: 'powderblue'}}>
<Text style={{fontSize: 28, textAlign: 'center', margin: 10}}>Fading in</Text> <Text style={{fontSize: 28, textAlign: 'center', margin: 10}}>Fading in</Text>
</FadeInView> </FadeInView>
</View>
) )
}
} }
``` ```
![FadeInView](img/AnimatedFadeInView.gif)
Let's break down what's happening here. Let's break down what's happening here.
In the `FadeInView` constructor, a new `Animated.Value` called `fadeAnim` is initialized as part of `state`. In the `FadeInView` constructor, a new `Animated.Value` called `fadeAnim` is initialized as part of `state`.
The opacity property on the `View` is mapped to this animated value. The opacity property on the `View` is mapped to this animated value.
@ -393,22 +389,29 @@ Note that in order to get this to work on **Android** you need to set the follow
UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true); UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true);
``` ```
![](img/LayoutAnimationExample.gif) ```SnackPlayer
import React from 'react';
import {
NativeModules,
LayoutAnimation,
Text,
TouchableOpacity,
StyleSheet,
View,
} from 'react-native';
```javascript const { UIManager } = NativeModules;
class App extends React.Component {
constructor(props) {
super(props);
this.state = { w: 100, h: 100 };
this._onPress = this._onPress.bind(this);
}
componentWillMount() { UIManager.setLayoutAnimationEnabledExperimental &&
// Animate creation UIManager.setLayoutAnimationEnabledExperimental(true);
LayoutAnimation.spring();
}
_onPress() { export default class App extends React.Component {
state = {
w: 100,
h: 100,
};
_onPress = () => {
// Animate the update // Animate the update
LayoutAnimation.spring(); LayoutAnimation.spring();
this.setState({w: this.state.w + 15, h: this.state.h + 15}) this.setState({w: this.state.w + 15, h: this.state.h + 15})
@ -427,8 +430,30 @@ class App extends React.Component {
); );
} }
} }
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
box: {
width: 200,
height: 200,
backgroundColor: 'red',
},
button: {
backgroundColor: 'black',
paddingHorizontal: 20,
paddingVertical: 15,
marginTop: 15,
},
buttonText: {
color: '#fff',
fontWeight: 'bold',
},
});
``` ```
[Run this example](https://rnplay.org/apps/uaQrGQ)
This example uses a preset value, you can customize the animations as This example uses a preset value, you can customize the animations as
you need, see [LayoutAnimation.js](https://github.com/facebook/react-native/blob/master/Libraries/LayoutAnimation/LayoutAnimation.js) you need, see [LayoutAnimation.js](https://github.com/facebook/react-native/blob/master/Libraries/LayoutAnimation/LayoutAnimation.js)
@ -457,45 +482,12 @@ We could use this in the Rebound example to update the scale - this
might be helpful if the component that we are updating is deeply nested might be helpful if the component that we are updating is deeply nested
and hasn't been optimized with `shouldComponentUpdate`. and hasn't been optimized with `shouldComponentUpdate`.
```javascript If you find your animations with dropping frames (performing below 60 frames
// Back inside of the App component, replace the scrollSpring listener per second), look into using `setNativeProps` or `shouldComponentUpdate` to
// in componentWillMount with this: optimize them. Or you could run the animations on the UI thread rather than
this._scrollSpring.addListener({ the JavaScript thread [with the useNativeDriver
onSpringUpdate: () => { option](http://facebook.github.io/react-native/blog/2017/02/14/using-native-driver-for-animated.html).
if (!this._photo) { return } You may also want to defer any computationally intensive work until after
var v = this._scrollSpring.getCurrentValue(); animations are complete, using the
var newProps = {style: {transform: [{scaleX: v}, {scaleY: v}]}}; [InteractionManager](docs/interactionmanager.html). You can monitor the
this._photo.setNativeProps(newProps); frame rate by using the In-App Developer Menu "FPS Monitor" tool.
},
});
// Lastly, we update the render function to no longer pass in the
// transform via style (avoid clashes when re-rendering) and to set the
// photo ref
render() {
return (
<View style={styles.container}>
<TouchableWithoutFeedback onPressIn={this._onPressIn} onPressOut={this._onPressOut}>
<Image ref={component => this._photo = component}
source={{uri: "img/ReboundExample.png"}}
style={{width: 250, height: 200}} />
</TouchableWithoutFeedback>
</View>
);
}
```
[Run this example](https://rnplay.org/apps/fUqjAg)
It would not make sense to use `setNativeProps` with react-tween-state
because the updated tween values are set on the state automatically by
the library - Rebound on the other hand gives us an updated value for
each frame with the `onSpringUpdate` function.
If you find your animations with dropping frames (performing below 60
frames per second), look into using `setNativeProps` or
`shouldComponentUpdate` to optimize them. You may also want to defer any
computationally intensive work until after animations are complete,
using the
[InteractionManager](docs/interactionmanager.html). You
can monitor the frame rate by using the In-App Developer Menu "FPS
Monitor" tool.

View File

@ -94,7 +94,10 @@ ReactNativeBaseComponent.js](https://github.com/facebook/react/blob/master/src/r
Composite components are not backed by a native view, so you cannot call Composite components are not backed by a native view, so you cannot call
`setNativeProps` on them. Consider this example: `setNativeProps` on them. Consider this example:
```javascript ```SnackPlayer?name=setNativeProps%20with%20Composite%20Components
import React from 'react';
import { Text, TouchableOpacity, View } from 'react-native';
class MyButton extends React.Component { class MyButton extends React.Component {
render() { render() {
return ( return (
@ -105,7 +108,7 @@ class MyButton extends React.Component {
} }
} }
class App extends React.Component { export default class App extends React.Component {
render() { render() {
return ( return (
<TouchableOpacity> <TouchableOpacity>
@ -115,7 +118,6 @@ class App extends React.Component {
} }
} }
``` ```
[Run this example](https://rnplay.org/apps/JXkgmQ)
If you run this you will immediately see this error: `Touchable child If you run this you will immediately see this error: `Touchable child
must either be native or forward setNativeProps to a native component`. must either be native or forward setNativeProps to a native component`.
@ -133,9 +135,12 @@ All we need to do is provide a `setNativeProps` method on our component
that calls `setNativeProps` on the appropriate child with the given that calls `setNativeProps` on the appropriate child with the given
arguments. arguments.
```javascript ```SnackPlayer?name=Forwarding%20setNativeProps
import React from 'react';
import { Text, TouchableOpacity, View } from 'react-native';
class MyButton extends React.Component { class MyButton extends React.Component {
setNativeProps(nativeProps) { setNativeProps = (nativeProps) => {
this._root.setNativeProps(nativeProps); this._root.setNativeProps(nativeProps);
} }
@ -147,8 +152,17 @@ class MyButton extends React.Component {
) )
} }
} }
export default class App extends React.Component {
render() {
return (
<TouchableOpacity>
<MyButton label="Press me!" />
</TouchableOpacity>
)
}
}
``` ```
[Run this example](https://rnplay.org/apps/YJxnEQ)
You can now use `MyButton` inside of `TouchableOpacity`! A sidenote for You can now use `MyButton` inside of `TouchableOpacity`! A sidenote for
clarity: we used the [ref callback](https://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute) syntax here, rather than the traditional string-based ref. clarity: we used the [ref callback](https://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute) syntax here, rather than the traditional string-based ref.
@ -173,22 +187,22 @@ use `setNativeProps` to directly manipulate the TextInput value when
necessary. For example, the following code demonstrates clearing the necessary. For example, the following code demonstrates clearing the
input when you tap a button: input when you tap a button:
```javascript ```SnackPlayer?name=Clear%20text
class App extends React.Component { import React from 'react';
constructor(props) { import { TextInput, Text, TouchableOpacity, View } from 'react-native';
super(props);
this.clearText = this.clearText.bind(this);
}
clearText() { export default class App extends React.Component {
clearText = () => {
this._textInput.setNativeProps({text: ''}); this._textInput.setNativeProps({text: ''});
} }
render() { render() {
return ( return (
<View style={styles.container}> <View style={{flex: 1}}>
<TextInput ref={component => this._textInput = component} <TextInput
style={styles.textInput} /> ref={component => this._textInput = component}
style={{height: 50, flex: 1, marginHorizontal: 20, borderWidth: 1, borderColor: '#ccc'}}
/>
<TouchableOpacity onPress={this.clearText}> <TouchableOpacity onPress={this.clearText}>
<Text>Clear text</Text> <Text>Clear text</Text>
</TouchableOpacity> </TouchableOpacity>
@ -197,7 +211,6 @@ class App extends React.Component {
} }
} }
``` ```
[Run this example](https://rnplay.org/plays/pOI9bA)
## Avoiding conflicts with the render function ## Avoiding conflicts with the render function
@ -205,9 +218,7 @@ If you update a property that is also managed by the render function,
you might end up with some unpredictable and confusing bugs because you might end up with some unpredictable and confusing bugs because
anytime the component re-renders and that property changes, whatever anytime the component re-renders and that property changes, whatever
value was previously set from `setNativeProps` will be completely value was previously set from `setNativeProps` will be completely
ignored and overridden. [See this example](https://rnplay.org/apps/bp1DvQ) ignored and overridden.
for a demonstration of what can happen if these two collide - notice
the jerky animation each 250ms when `setState` triggers a re-render.
## setNativeProps & shouldComponentUpdate ## setNativeProps & shouldComponentUpdate

View File

@ -15,6 +15,7 @@ var Header = require('Header');
var Prism = require('Prism'); var Prism = require('Prism');
var React = require('React'); var React = require('React');
var WebPlayer = require('WebPlayer'); var WebPlayer = require('WebPlayer');
var SnackPlayer = require('SnackPlayer');
/** /**
* Block-Level Grammar * Block-Level Grammar
@ -842,6 +843,12 @@ Parser.prototype.tok = function() {
); );
} }
if (lang && lang.indexOf('SnackPlayer') === 0) {
return (
<SnackPlayer params={lang.split('?')[1]}>{text}</SnackPlayer>
);
}
return <Prism>{text}</Prism>; return <Prism>{text}</Prism>;
} }
case 'table': { case 'table': {

View File

@ -20,9 +20,11 @@ var Site = React.createClass({
const version = Metadata.config.RN_VERSION; const version = Metadata.config.RN_VERSION;
const algoliaVersion = version === 'next' ? 'master' : version; const algoliaVersion = version === 'next' ? 'master' : version;
var basePath = '/react-native/' + (path ? path + '/' : ''); var basePath = '/react-native/' + (path ? path + '/' : '');
var currentYear = (new Date()).getFullYear(); var currentYear = new Date().getFullYear();
var title = this.props.title ? this.props.title : 'React Native | A framework for building native apps using React'; var title = this.props.title
? this.props.title
: 'React Native | A framework for building native apps using React';
var metaTags = [ var metaTags = [
{ charSet: 'utf-8' }, { charSet: 'utf-8' },
@ -35,8 +37,8 @@ var Site = React.createClass({
content: 'width=device-width', content: 'width=device-width',
}, },
// Facebook // Facebook
{ property: 'fb:app_id', content: '1677033832619985', }, { property: 'fb:app_id', content: '1677033832619985' },
{ property: 'fb:admins', content: '121800083', }, { property: 'fb:admins', content: '121800083' },
// Open Graph // Open Graph
{ {
property: 'og:site_name', property: 'og:site_name',
@ -48,15 +50,20 @@ var Site = React.createClass({
}, },
{ {
property: 'og:url', property: 'og:url',
content: 'https://facebook.github.io/react-native/' + (this.props.path ? this.props.path : 'index.html'), content: 'https://facebook.github.io/react-native/' +
(this.props.path ? this.props.path : 'index.html'),
}, },
{ {
property: 'og:image', property: 'og:image',
content: this.props.image ? this.props.image : 'https://facebook.github.io/react-native/img/opengraph.png', content: this.props.image
? this.props.image
: 'https://facebook.github.io/react-native/img/opengraph.png',
}, },
{ {
property: 'og:description', property: 'og:description',
content: this.props.description ? this.props.description : 'A framework for building native apps using React', content: this.props.description
? this.props.description
: 'A framework for building native apps using React',
}, },
// Twitter Cards // Twitter Cards
{ {
@ -69,18 +76,23 @@ var Site = React.createClass({
}, },
]; ];
var typeTags = [{ var typeTags = [
{
property: 'og:type', property: 'og:type',
content: 'website', content: 'website',
}]; },
];
if (this.props.author) { if (this.props.author) {
typeTags = [{ typeTags = [
{
property: 'og:type', property: 'og:type',
content: 'article', content: 'article',
}, { },
{
property: 'article:author', property: 'article:author',
content: this.props.author, content: this.props.author,
}]; },
];
} }
metaTags.push(...typeTags); metaTags.push(...typeTags);
@ -95,27 +107,46 @@ var Site = React.createClass({
<html> <html>
<head> <head>
<title>{title}</title> <title>{title}</title>
{ {metaTags.map((tag, index) => <meta key={index} {...tag} />)}
metaTags.map((tag, index) =>
<meta key={index} {...tag} />)
}
<base href={basePath} /> <base href={basePath} />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/docsearch.js/1/docsearch.min.css" /> <link
rel="stylesheet"
href="https://cdn.jsdelivr.net/docsearch.js/1/docsearch.min.css"
/>
<link rel="shortcut icon" href="img/favicon.png?2" /> <link rel="shortcut icon" href="img/favicon.png?2" />
<link rel="stylesheet" href="css/react-native.css" /> <link rel="stylesheet" href="css/react-native.css" />
<link rel="alternate" type="application/rss+xml" title="React Native Blog" href="https://facebook.github.io/react-native/blog/feed.xml" /> <link
<link href="//cdn-images.mailchimp.com/embedcode/horizontal-slim-10_7.css" rel="stylesheet" type="text/css" /> rel="alternate"
type="application/rss+xml"
title="React Native Blog"
href="https://facebook.github.io/react-native/blog/feed.xml"
/>
<link
href="//cdn-images.mailchimp.com/embedcode/horizontal-slim-10_7.css"
rel="stylesheet"
type="text/css"
/>
<script type="text/javascript" src="//use.typekit.net/vqa1hcx.js" /> <script type="text/javascript" src="//use.typekit.net/vqa1hcx.js" />
<script type="text/javascript">{'try{Typekit.load();}catch(e){}'}</script> <script type="text/javascript">
{'try{Typekit.load();}catch(e){}'}
</script>
</head> </head>
<body> <body>
<script dangerouslySetInnerHTML={{__html: "window.fbAsyncInit = function() {FB.init({appId:'1677033832619985',xfbml:true,version:'v2.7'});};(function(d, s, id){var js, fjs = d.getElementsByTagName(s)[0];if (d.getElementById(id)) {return;}js = d.createElement(s); js.id = id;js.src = '//connect.facebook.net/en_US/sdk.js';fjs.parentNode.insertBefore(js, fjs);}(document, 'script','facebook-jssdk'));"}} /> <script
<script dangerouslySetInnerHTML={{__html: "window.twttr=(function(d,s, id){var js,fjs=d.getElementsByTagName(s)[0],t=window.twttr||{};if(d.getElementById(id))return t;js=d.createElement(s);js.id=id;js.src='https://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js, fjs);t._e = [];t.ready = function(f) {t._e.push(f);};return t;}(document, 'script', 'twitter-wjs'));"}} /> dangerouslySetInnerHTML={{
__html: "window.fbAsyncInit = function() {FB.init({appId:'1677033832619985',xfbml:true,version:'v2.7'});};(function(d, s, id){var js, fjs = d.getElementsByTagName(s)[0];if (d.getElementById(id)) {return;}js = d.createElement(s); js.id = id;js.src = '//connect.facebook.net/en_US/sdk.js';fjs.parentNode.insertBefore(js, fjs);}(document, 'script','facebook-jssdk'));",
}}
/>
<script
dangerouslySetInnerHTML={{
__html: "window.twttr=(function(d,s, id){var js,fjs=d.getElementsByTagName(s)[0],t=window.twttr||{};if(d.getElementById(id))return t;js=d.createElement(s);js.id=id;js.src='https://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js, fjs);t._e = [];t.ready = function(f) {t._e.push(f);};return t;}(document, 'script', 'twitter-wjs'));",
}}
/>
<div className="container"> <div className="container">
<div className="nav-main"> <div className="nav-main">
<div className="wrap"> <div className="wrap">
@ -135,57 +166,128 @@ var Site = React.createClass({
<footer className="nav-footer"> <footer className="nav-footer">
<section className="sitemap"> <section className="sitemap">
<a href="/react-native" className="nav-home"> <a href="/react-native" className="nav-home">
<img src="img/header_logo.png" alt="React Native" width="66" height="58" /> <img
src="img/header_logo.png"
alt="React Native"
width="66"
height="58"
/>
</a> </a>
<div> <div>
<h5><a href="docs/">Docs</a></h5> <h5><a href="docs/">Docs</a></h5>
<a href="docs/getting-started.html">Getting Started</a> <a href="docs/getting-started.html">Getting Started</a>
<a href="docs/tutorial.html">Tutorial</a> <a href="docs/tutorial.html">Tutorial</a>
<a href="docs/integration-with-existing-apps.html">Integration With Existing Apps</a> <a href="docs/integration-with-existing-apps.html">
Integration With Existing Apps
</a>
<a href="docs/more-resources.html">More Resources</a> <a href="docs/more-resources.html">More Resources</a>
</div> </div>
<div> <div>
<h5><a href="/react-native/support.html">Community</a></h5> <h5><a href="/react-native/support.html">Community</a></h5>
<a href="/react-native/showcase.html">Showcase</a> <a href="/react-native/showcase.html">Showcase</a>
<a href="http://www.meetup.com/topics/react-native/" target="_blank">Upcoming Events</a> <a
<a href="https://www.facebook.com/groups/react.native.community" target="_blank">Facebook Group</a> href="http://www.meetup.com/topics/react-native/"
<a href="https://twitter.com/reactnative" target="_blank">Twitter</a> target="_blank">
Upcoming Events
</a>
<a
href="https://www.facebook.com/groups/react.native.community"
target="_blank">
Facebook Group
</a>
<a href="https://twitter.com/reactnative" target="_blank">
Twitter
</a>
</div> </div>
<div> <div>
<h5><a href="/react-native/support.html">Help</a></h5> <h5><a href="/react-native/support.html">Help</a></h5>
<a href="http://stackoverflow.com/questions/tagged/react-native" target="_blank">Stack Overflow</a> <a
<a href="https://discord.gg/0ZcbPKXt5bZjGY5n">Reactiflux Chat</a> href="http://stackoverflow.com/questions/tagged/react-native"
<a href="/react-native/versions.html" target="_blank">Latest Releases</a> target="_blank">
<a href="https://react-native.canny.io/feature-requests" target="_blank">Feature Requests</a> Stack Overflow
</a>
<a href="https://discord.gg/0ZcbPKXt5bZjGY5n">
Reactiflux Chat
</a>
<a href="/react-native/versions.html" target="_blank">
Latest Releases
</a>
<a
href="https://react-native.canny.io/feature-requests"
target="_blank">
Feature Requests
</a>
</div> </div>
<div> <div>
<h5>More</h5> <h5>More</h5>
<a href="/react-native/blog">Blog</a> <a href="/react-native/blog">Blog</a>
<a href="https://github.com/facebook/react-native" target="_blank">GitHub</a> <a
<a href="http://facebook.github.io/react/" target="_blank">React</a> href="https://github.com/facebook/react-native"
target="_blank">
GitHub
</a>
<a href="http://facebook.github.io/react/" target="_blank">
React
</a>
</div> </div>
</section> </section>
<section className="newsletter"> <section className="newsletter">
<div id="mc_embed_signup"> <div id="mc_embed_signup">
<form action="//reactnative.us10.list-manage.com/subscribe/post?u=db0dd948e2b729ee62625b1a8&amp;id=47cd41008f" method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" className="validate" target="_blank" noValidate> <form
action="//reactnative.us10.list-manage.com/subscribe/post?u=db0dd948e2b729ee62625b1a8&amp;id=47cd41008f"
method="post"
id="mc-embedded-subscribe-form"
name="mc-embedded-subscribe-form"
className="validate"
target="_blank"
noValidate>
<div id="mc_embed_signup_scroll"> <div id="mc_embed_signup_scroll">
<label htmlFor="mce-EMAIL"> <label htmlFor="mce-EMAIL">
<h5>Get the React Native Newsletter</h5> <h5>Get the React Native Newsletter</h5>
</label> </label>
<input type="email" value="" name="EMAIL" className="email" id="mce-EMAIL" placeholder="email address" required /> <input
<div style={{position: 'absolute', left: '-5000px'}} aria-hidden="true"> type="email"
<input type="text" name="b_db0dd948e2b729ee62625b1a8_47cd41008f" tabIndex="-1" value="" /> value=""
name="EMAIL"
className="email"
id="mce-EMAIL"
placeholder="email address"
required
/>
<div
style={{ position: 'absolute', left: '-5000px' }}
aria-hidden="true">
<input
type="text"
name="b_db0dd948e2b729ee62625b1a8_47cd41008f"
tabIndex="-1"
value=""
/>
</div> </div>
<div className="clear"> <div className="clear">
<input type="submit" value="Sign up" name="subscribe" id="mc-embedded-subscribe" className="button" /> <input
type="submit"
value="Sign up"
name="subscribe"
id="mc-embedded-subscribe"
className="button"
/>
</div> </div>
</div> </div>
</form> </form>
</div> </div>
</section> </section>
<a href="https://code.facebook.com/projects/" target="_blank" className="fbOpenSource"> <a
<img src="img/oss_logo.png" alt="Facebook Open Source" width="170" height="45"/> href="https://code.facebook.com/projects/"
target="_blank"
className="fbOpenSource">
<img
src="img/oss_logo.png"
alt="Facebook Open Source"
width="170"
height="45"
/>
</a> </a>
<section className="copyright"> <section className="copyright">
Copyright © {currentYear} Facebook Inc. Copyright © {currentYear} Facebook Inc.
@ -194,8 +296,13 @@ var Site = React.createClass({
</div> </div>
<div id="fb-root" /> <div id="fb-root" />
<script type="text/javascript" src="https://cdn.jsdelivr.net/docsearch.js/1/docsearch.min.js" /> <script
<script dangerouslySetInnerHTML={{__html: ` type="text/javascript"
src="https://cdn.jsdelivr.net/docsearch.js/1/docsearch.min.js"
/>
<script
dangerouslySetInnerHTML={{
__html: `
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
@ -213,15 +320,25 @@ var Site = React.createClass({
inputSelector: '#algolia-doc-search', inputSelector: '#algolia-doc-search',
algoliaOptions: { facetFilters: [ "tags:${algoliaVersion}" ], hitsPerPage: 5 } algoliaOptions: { facetFilters: [ "tags:${algoliaVersion}" ], hitsPerPage: 5 }
}); });
`}} /> `,
}}
/>
<script src="js/scripts.js" /> <script src="js/scripts.js" />
{/* Mailchimp Inline form-submission script for the React Native newsletter sign up form */} {/* Mailchimp Inline form-submission script for the React Native newsletter sign up form */}
<script type="text/javascript" src="//s3.amazonaws.com/downloads.mailchimp.com/js/mc-validate.js" /> <script
<script type="text/javascript" dangerouslySetInnerHTML={{__html: "(function($) {window.fnames = new Array(); window.ftypes = new Array();fnames[0]='EMAIL';ftypes[0]='email';fnames[1]='FNAME';ftypes[1]='text';fnames[2]='LNAME';ftypes[2]='text';}(jQuery));var $mcj = jQuery.noConflict(true);"}} /> type="text/javascript"
src="//s3.amazonaws.com/downloads.mailchimp.com/js/mc-validate.js"
/>
<script
type="text/javascript"
dangerouslySetInnerHTML={{
__html: "(function($) {window.fnames = new Array(); window.ftypes = new Array();fnames[0]='EMAIL';ftypes[0]='email';fnames[1]='FNAME';ftypes[1]='text';fnames[2]='LNAME';ftypes[2]='text';}(jQuery));var $mcj = jQuery.noConflict(true);",
}}
/>
<script type="text/javascript" src="https://snack.expo.io/embed.js" />
</body> </body>
</html> </html>
); );
} },
}); });
module.exports = Site; module.exports = Site;

115
website/core/SnackPlayer.js Normal file
View File

@ -0,0 +1,115 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule SnackPlayer
*/
'use strict';
var Prism = require('Prism');
var React = require('React');
const LatestSDKVersion = '15.0.0';
var ReactNativeToExpoSDKVersionMap = {
'0.42': '15.0.0',
'0.41': '14.0.0',
};
/**
* Use the SnackPlayer by including a ```SnackPlayer``` block in markdown.
*
* Optionally, include url parameters directly after the block's language.
* Valid options are name, description, and platform.
*
* E.g.
* ```SnackPlayer?platform=android&name=Hello%20world!
* import React from 'react';
* import { Text } from 'react-native';
*
* export default class App extends React.Component {
* render() {
* return <Text>Hello World!</Text>;
* }
* }
* ```
*/
var SnackPlayer = React.createClass({
contextTypes: {
version: React.PropTypes.number.isRequired,
},
componentDidMount() {
window.ExpoSnack && window.ExpoSnack.initialize();
},
render() {
var code = encodeURIComponent(this.props.children);
var params = this.parseParams(this.props.params);
var platform = params.platform ? params.platform : 'ios';
var name = params.name ? decodeURIComponent(params.name) : 'Example';
var description = params.description
? decodeURIComponent(params.description)
: 'Example usage';
var optionalProps = {};
var { version } = this.context;
if (version === 'next') {
optionalProps['data-snack-sdk-version'] = LatestSDKVersion;
} else {
optionalProps['data-snack-sdk-version'] = ReactNativeToExpoSDKVersionMap[
version
] || LatestSDKVersion;
}
return (
<div className="snack-player">
<div className="mobile-friendly-snack" style={{ display: 'none' }}>
<Prism>
{this.props.children}
</Prism>
</div>
<div
className="desktop-friendly-snack"
style={{ marginTop: 15, marginBottom: 15 }}>
<div
data-snack-name={name}
data-snack-description={description}
data-snack-code={code}
data-snack-platform={platform}
data-snack-preview="true"
{...optionalProps}
style={{
overflow: 'hidden',
background: '#fafafa',
border: '1px solid rgba(0,0,0,.16)',
borderRadius: '4px',
height: '514px',
width: '880px',
}}
/>
</div>
</div>
);
},
parseParams: function(paramString) {
var params = {};
if (paramString) {
var pairs = paramString.split('&');
for (var i = 0; i < pairs.length; i++) {
var pair = pairs[i].split('=');
params[pair[0]] = pair[1];
}
}
return params;
},
});
module.exports = SnackPlayer;

View File

@ -23,7 +23,9 @@
var mobile = isMobile(); var mobile = isMobile();
if (mobile) { if (mobile) {
document.querySelector('.nav-site-wrapper a[data-target]').addEventListener('click', toggleTarget); document
.querySelector('.nav-site-wrapper a[data-target]')
.addEventListener('click', toggleTarget);
} }
var webPlayerList = document.querySelectorAll('.web-player'); var webPlayerList = document.querySelectorAll('.web-player');
@ -33,13 +35,36 @@
webPlayerList[i].classList.add(mobile ? 'mobile' : 'desktop'); webPlayerList[i].classList.add(mobile ? 'mobile' : 'desktop');
if (!mobile) { if (!mobile) {
// Determine location to look up required assets // Determine location to look up required assets
var assetRoot = encodeURIComponent(document.location.origin + '/react-native'); var assetRoot = encodeURIComponent(
document.location.origin + '/react-native'
);
// Set iframe src. Do this dynamically so the iframe never loads on mobile. // Set iframe src. Do this dynamically so the iframe never loads on mobile.
var iframe = webPlayerList[i].querySelector('iframe'); var iframe = webPlayerList[i].querySelector('iframe');
iframe.src = iframe.getAttribute('data-src') + '&assetRoot=' + assetRoot; iframe.src = iframe.getAttribute('data-src') +
'&assetRoot=' +
assetRoot;
}
}
var snackPlayerList = document.querySelectorAll('.snack-player');
// Either show interactive or static code block, depending on desktop or mobile
for (var i = 0; i < snackPlayerList.length; ++i) {
var snackPlayer = snackPlayerList[i];
var snackDesktopPlayer = snackPlayer.querySelectorAll(
'.desktop-friendly-snack'
)[0];
var plainCodeExample = snackPlayer.querySelectorAll(
'.mobile-friendly-snack'
)[0];
if (mobile) {
snackDesktopPlayer.remove();
plainCodeExample.style.display = 'block';
} else {
plainCodeExample.remove();
} }
} }
@ -86,7 +111,9 @@
var toggledTarget; var toggledTarget;
function toggleTarget(event) { function toggleTarget(event) {
var target = document.body.querySelector(event.target.getAttribute('data-target')); var target = document.body.querySelector(
event.target.getAttribute('data-target')
);
if (target) { if (target) {
event.preventDefault(); event.preventDefault();
@ -104,7 +131,8 @@
// Primitive mobile detection // Primitive mobile detection
function isMobile() { function isMobile() {
return ( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ); return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
navigator.userAgent
);
} }
})();
}());