[add] initial ScrollView

Supports the following props: `children`, `contentContainerStyle`,
`horizontal`, `onScroll`, `scrollEnabled`, `scrollEventThrottle`, and
`style`.

Fix #6
This commit is contained in:
Tom Ashworth
2015-10-20 15:57:51 -07:00
committed by Nicolas Gallagher
parent a1664927ce
commit 894fd0362d
6 changed files with 244 additions and 19 deletions

View File

@@ -1,6 +1,7 @@
# ScrollView
TODO
Scrollable `View` for use with bounded height, either by setting the height of
the view directly (discouraged) or by bounding the height of ancestor views.
## Props
@@ -11,27 +12,29 @@ Child content.
**contentContainerStyle**: style
These styles will be applied to the scroll view content container which wraps
all of the child views. Example:
all of the child views.
**horizontal**: bool = false
When true, the scroll view's children are arranged horizontally in a row instead of vertically in a column. Default: `false`.
When true, the scroll view's children are arranged horizontally in a row
instead of vertically in a column.
**onScroll**: function
Fires at most once per frame during scrolling. The frequency of the events can be contolled using the `scrollEventThrottle` prop.
Fires at most once per frame during scrolling. The frequency of the events can
be contolled using the `scrollEventThrottle` prop.
**scrollEnabled**: bool
**scrollEnabled**: bool = true
When false, the content does not scroll. Default: `true`.
When false, the content does not scroll.
**scrollEventThrottle**: number
**scrollEventThrottle**: number = 0
This controls how often the scroll event will be fired while scrolling (in
events per seconds). A higher number yields better accuracy for code that is
tracking the scroll position, but can lead to scroll performance problems.
Default: `0` (the scroll event will be sent only once each time the view is
scrolled.)
tracking the scroll position, but can lead to scroll performance problems. The
default value is `0`, which means the scroll event will be sent only once each
time the view is scrolled.
**style**: style
@@ -40,20 +43,42 @@ scrolled.)
## Examples
```js
import React, { ScrollView } from 'react-native-web'
import React, { ScrollView, StyleSheet } from 'react-native-web'
const { Component, PropTypes } = React;
import Item from './Item'
class Example extends Component {
static propTypes = {
export default class App extends React.Component {
constructor(props, context) {
super(props, context)
this.state = {
items: Array.from({ length: 20 }).map((_, i) => ({ id: i }))
}
}
static defaultProps = {
onScroll(e) {
console.log(e)
}
render() {
return (
<ScrollView
children={this.state.items.map((item) => <Item {...item} />)}
contentContainerStyle={styles.container}
horizontal
onScroll={(e) => this.onScroll(e)}
scrollEventThrottle={60}
style={styles.root}
/>
)
}
}
const styles = StyleSheet.create({
root: {
borderWidth: '1px'
},
container: {
padding: '10px'
}
})
```

View File

@@ -1,7 +1,7 @@
import GridView from './GridView'
import Heading from './Heading'
import MediaQueryWidget from './MediaQueryWidget'
import React, { Image, StyleSheet, Text, TextInput, Touchable, View } from '../../src'
import React, { Image, StyleSheet, ScrollView, Text, TextInput, Touchable, View } from '../../src'
export default class App extends React.Component {
static propTypes = {
@@ -9,6 +9,13 @@ export default class App extends React.Component {
style: View.propTypes.style
}
constructor(...args) {
super(...args)
this.state = {
scrollEnabled: true
}
}
render() {
const { mediaQuery } = this.props
const rootStyles = {
@@ -152,6 +159,52 @@ export default class App extends React.Component {
)
})}
</GridView>
<Heading size='large'>ScrollView</Heading>
<label>
<input
checked={this.state.scrollEnabled}
onChange={() => this.setState({
scrollEnabled: !this.state.scrollEnabled
})}
type='checkbox'
/> Enable scroll
</label>
<Heading>Default layout</Heading>
<View style={styles.scrollViewContainer}>
<ScrollView
contentContainerStyle={styles.scrollViewContentContainerStyle}
onScroll={e => console.log('ScrollView.onScroll', e)}
scrollEnabled={this.state.scrollEnabled}
scrollEventThrottle={1} // 1 event per second
style={styles.scrollViewStyle}
>
{Array.from({ length: 50 }).map((item, i) => (
<View key={i} style={styles.box}>
<Text>{i}</Text>
</View>
))}
</ScrollView>
</View>
<Heading>Horizontal layout</Heading>
<View style={styles.scrollViewContainer}>
<ScrollView
contentContainerStyle={styles.scrollViewContentContainerStyle}
horizontal
onScroll={e => console.log('ScrollView.onScroll', e)}
scrollEnabled={this.state.scrollEnabled}
scrollEventThrottle={1} // 1 event per second
style={styles.scrollViewStyle}
>
{Array.from({ length: 50 }).map((item, i) => (
<View key={i} style={{...styles.box, ...styles.horizontalBox}}>
<Text>{i}</Text>
</View>
))}
</ScrollView>
</View>
</View>
)
}
@@ -179,6 +232,9 @@ const styles = StyleSheet.create({
justifyContent: 'center',
borderWidth: '1px'
},
horizontalBox: {
width: '50px'
},
boxFull: {
width: '100%'
},
@@ -194,5 +250,14 @@ const styles = StyleSheet.create({
borderWidth: 1,
height: '200px',
justifyContent: 'center'
},
scrollViewContainer: {
height: '200px'
},
scrollViewStyle: {
borderWidth: '1px'
},
scrollViewContentContainerStyle: {
padding: '10px'
}
})

View File

@@ -17,6 +17,7 @@
},
"dependencies": {
"inline-style-prefixer": "^0.3.3",
"lodash.debounce": "^3.1.1",
"react-tappable": "^0.7.1",
"react-textarea-autosize": "^3.0.0"
},

View File

@@ -0,0 +1,4 @@
import View from '../View'
export default {
...(View.stylePropTypes)
}

View File

@@ -1 +1,11 @@
/* eslint-env mocha */
import * as utils from '../../../modules/specHelpers'
import ScrollView from '../'
suite('components/ScrollView', () => {
test('prop "style"', () => {
utils.assertProps.style(ScrollView)
})
})

View File

@@ -1,18 +1,138 @@
import { pickProps } from '../../modules/filterObjectProps'
import debounce from 'lodash.debounce'
import React, { PropTypes } from 'react'
import ScrollViewStylePropTypes from './ScrollViewStylePropTypes'
import StyleSheet from '../../modules/StyleSheet'
import View from '../View'
const scrollViewStyleKeys = Object.keys(ScrollViewStylePropTypes)
const styles = StyleSheet.create({
initial: {
flexGrow: 1,
flexShrink: 1,
overflow: 'scroll'
},
initialContentContainer: {
flexGrow: 1,
flexShrink: 1
},
row: {
flexDirection: 'row'
}
})
class ScrollView extends React.Component {
static propTypes = {
children: PropTypes.any
children: PropTypes.any,
contentContainerStyle: PropTypes.shape(ScrollViewStylePropTypes),
horizontal: PropTypes.bool,
onScroll: PropTypes.func,
scrollEnabled: PropTypes.bool,
scrollEventThrottle: PropTypes.number,
style: PropTypes.shape(ScrollViewStylePropTypes)
}
static defaultProps = {
className: ''
contentContainerStyle: styles.initialContentContainer,
horizontal: false,
scrollEnabled: true,
scrollEventThrottle: 0,
style: styles.initial
}
constructor(...args) {
super(...args)
this._debouncedOnScrollEnd = debounce(this._onScrollEnd, 100)
this.state = {
isScrolling: false
}
}
_onScroll(e) {
const { scrollEventThrottle } = this.props
const { isScrolling, scrollLastTick } = this.state
// A scroll happened, so the scroll bumps the debounce.
this._debouncedOnScrollEnd(e)
if (isScrolling) {
// Scroll last tick may have changed, check if we need to notify
if (this._shouldEmitScrollEvent(scrollLastTick, scrollEventThrottle)) {
this._onScrollTick(e)
}
} else {
// Weren't scrolling, so we must have just started
this._onScrollStart(e)
}
}
_onScrollStart() {
this.setState({
isScrolling: true,
scrollLastTick: Date.now()
})
}
_onScrollTick(e) {
const { onScroll } = this.props
this.setState({
scrollLastTick: Date.now()
})
if (onScroll) onScroll(e)
}
_onScrollEnd(e) {
const { onScroll } = this.props
this.setState({
isScrolling: false
})
if (onScroll) onScroll(e)
}
_shouldEmitScrollEvent(lastTick, eventThrottle) {
const timeSinceLastTick = Date.now() - lastTick
return (eventThrottle > 0 && timeSinceLastTick >= (1000 / eventThrottle))
}
_maybePreventScroll(e) {
const { scrollEnabled } = this.props
if (!scrollEnabled) e.preventDefault()
}
render() {
const {
children,
contentContainerStyle,
horizontal,
style
} = this.props
const resolvedStyle = pickProps(style, scrollViewStyleKeys)
const resolvedContentContainerStyle = pickProps(contentContainerStyle, scrollViewStyleKeys)
return (
<View {...this.props} />
<View
_className='ScrollView'
onScroll={(e) => this._onScroll(e)}
onTouchMove={(e) => this._maybePreventScroll(e)}
onWheel={(e) => this._maybePreventScroll(e)}
style={{
...styles.initial,
...resolvedStyle
}}
>
{children ? (
<View
children={children}
style={{
...styles.initialContentContainer,
...resolvedContentContainerStyle,
...(horizontal && styles.row)
}}
/>
) : null}
</View>
)
}
}