diff options
| author | Andrew <saintruler@gmail.com> | 2019-03-11 21:00:02 +0400 |
|---|---|---|
| committer | Andrew <saintruler@gmail.com> | 2019-03-11 21:00:02 +0400 |
| commit | 7e7dd5244e8d26485ad7950a89c04c98c4fef83f (patch) | |
| tree | 810730c4650392080fb87a78d3b527201e89fe4b /frontend/app/components | |
Initial commit/
Diffstat (limited to 'frontend/app/components')
27 files changed, 1807 insertions, 0 deletions
diff --git a/frontend/app/components/avatar/index.js b/frontend/app/components/avatar/index.js new file mode 100644 index 0000000..b01800a --- /dev/null +++ b/frontend/app/components/avatar/index.js @@ -0,0 +1,66 @@ +import React from 'react'; +import { + Image, + View, +} from 'react-native'; +import { + RkComponent, + RkText, + RkTheme, +} from 'react-native-ui-kitten'; +import { FontAwesome } from '../../assets/icons'; + +export class Avatar extends RkComponent { + componentName = 'Avatar'; + typeMapping = { + container: {}, + image: {}, + badge: {}, + badgeText: {}, + }; + + getBadgeStyle = (badgeProps) => { + switch (badgeProps) { + case 'like': + return { + symbol: FontAwesome.heart, + backgroundColor: RkTheme.current.colors.badge.likeBackground, + color: RkTheme.current.colors.badge.likeForeground, + }; + case 'follow': + return { + symbol: FontAwesome.plus, + backgroundColor: RkTheme.current.colors.badge.plusBackground, + color: RkTheme.current.colors.badge.plusForeground, + }; + default: return {}; + } + }; + + renderImg = (styles) => ( + <View> + <Image style={styles.image} source={this.props.img} /> + { this.props.badge && this.renderBadge(styles.badge)} + </View> + ); + + renderBadge = (style, textStyle) => { + const badgeStyle = this.getBadgeStyle(this.props.badge); + return ( + <View style={[style, { backgroundColor: badgeStyle.backgroundColor }]}> + <RkText rkType='awesome' style={[textStyle, { color: badgeStyle.color }]}> + {badgeStyle.symbol} + </RkText> + </View> + ); + }; + + render() { + const { container, ...other } = this.defineStyles(); + return ( + <View style={[container, this.props.style]}> + {this.renderImg(other)} + </View> + ); + } +} diff --git a/frontend/app/components/avatar/types.js b/frontend/app/components/avatar/types.js new file mode 100644 index 0000000..2d732d8 --- /dev/null +++ b/frontend/app/components/avatar/types.js @@ -0,0 +1,49 @@ +export const AvatarTypes = () => ({ + _base: { + container: { + alignItems: 'center', + flexDirection: 'row', + }, + image: { + width: 40, + height: 40, + }, + badge: { + width: 15, + height: 15, + borderRadius: 7.5, + alignItems: 'center', + justifyContent: 'center', + position: 'absolute', + bottom: -2, + right: -2, + }, + badgeText: { + backgroundColor: 'transparent', + fontSize: 9, + }, + }, + big: { + image: { + width: 110, + height: 110, + borderRadius: 55, + marginBottom: 19, + }, + container: { + flexDirection: 'column', + }, + }, + small: { + image: { + width: 32, + height: 32, + borderRadius: 16, + }, + }, + circle: { + image: { + borderRadius: 20, + }, + }, +}); diff --git a/frontend/app/components/cardInput.js b/frontend/app/components/cardInput.js new file mode 100644 index 0000000..b286d56 --- /dev/null +++ b/frontend/app/components/cardInput.js @@ -0,0 +1,67 @@ +import React from 'react'; +import { + RkButton, + RkTextInput, + RkText, + RkStyleSheet, +} from 'react-native-ui-kitten'; +import { FontAwesome } from '../assets/icons'; + +export class CardInput extends React.Component { + state = { + hidden: true, + cardNumber: '', + }; + + formatCreditNumber = (number, isHidden) => ( + isHidden + ? number.replace(/\D/g, '') + : number.replace(/[^\dA-Z]/g, '').replace(/(.{4})/g, '$1 ').trim() + ); + + onInputLabelPressed = () => { + this.setState({ + hidden: !this.state.hidden, + cardNumber: this.formatCreditNumber(this.state.cardNumber, !this.state.hidden), + }); + }; + + onInputChanged = (text) => { + this.setState({ + cardNumber: this.formatCreditNumber(text, this.state.hidden), + }); + }; + + renderInputLabel = () => ( + <RkButton + style={styles.button} + rkType='clear' + onPress={this.onInputLabelPressed}> + <RkText style={styles.icon} rkType='awesome secondaryColor'>{FontAwesome.slashEye}</RkText> + </RkButton> + ); + + render = () => ( + <RkTextInput + autoCapitalize='none' + rkType='bordered rounded iconRight' + autoCorrect={false} + label={this.renderInputLabel()} + secureTextEntry={this.state.hidden} + onChangeText={this.onInputChanged} + value={this.state.cardNumber} + keyboardType='numeric' + maxLength={19} + {...this.props} + /> + ); +} + +let styles = RkStyleSheet.create({ + icon: { + fontSize: 24, + }, + button: { + right: 17, + }, +}); diff --git a/frontend/app/components/charts/areaChart.js b/frontend/app/components/charts/areaChart.js new file mode 100644 index 0000000..be40960 --- /dev/null +++ b/frontend/app/components/charts/areaChart.js @@ -0,0 +1,120 @@ +import React from 'react'; +import { + View, + Dimensions, +} from 'react-native'; +import { + RkComponent, + RkTheme, + RkText, +} from 'react-native-ui-kitten'; + +import { + VictoryChart, + VictoryAxis, + VictoryArea, + VictoryScatter, + VictoryGroup, +} from 'victory-native'; + + +export class AreaChart extends RkComponent { + state = { + data: [ + { x: 1, y: 1 }, + { x: 2, y: 2 }, + { x: 3, y: 1 }, + { x: 4, y: 2 }, + { x: 5, y: 3 }, + { x: 6, y: 3 }, + { x: 7, y: 4 }, + { x: 8, y: 3 }, + { x: 9, y: 2 }, + { x: 10, y: 4 }, + ], + }; + + componentWillMount() { + this.size = Dimensions.get('window').width; + } + + componentDidMount() { + this.setStateInterval = setInterval(() => { + let positive = Math.random() > 0.5; + let newValue = this.state.data[this.state.data.length - 1].y; + if (newValue > 3) { + positive = false; + } else if (newValue < 2) { + positive = true; + } + newValue = positive ? newValue + 1 : newValue - 1; + const newData = this.state.data.map((d, i) => ({ + x: d.x, + y: i === this.state.data.length - 1 ? newValue : this.state.data[i + 1].y, + })); + this.setState({ + data: newData, + }); + }, 3000); + } + + componentWillUnmount() { + clearInterval(this.setStateInterval); + } + + render = () => ( + <View> + <RkText rkType='header4'>REAL TIME VISITORS</RkText> + <VictoryChart + padding={{ + top: 20, left: 40, right: 5, bottom: 5, + }} + width={this.size - 60}> + <VictoryAxis + tickValues={[]} + style={{ + axis: { stroke: 'transparent' }, + }} + /> + <VictoryAxis + dependentAxis + tickValues={['50', '100', '150', '200']} + style={{ + axis: { stroke: 'transparent' }, + grid: { stroke: RkTheme.current.colors.disabled, strokeWidth: 0.5 }, + tickLabels: { + fontSize: 14, + stroke: RkTheme.current.colors.text.secondary, + fill: RkTheme.current.colors.text.secondary, + fontFamily: RkTheme.current.fonts.family.regular, + strokeWidth: 0.5, + }, + }} + /> + <VictoryGroup data={this.state.data}> + <VictoryArea + style={{ + data: { + fill: RkTheme.current.colors.charts.area.fill, + fillOpacity: 0.5, + stroke: RkTheme.current.colors.charts.area.stroke, + strokeOpacity: 0.8, + strokeWidth: 1.5, + }, + }} + /> + <VictoryScatter + style={{ + data: { + fill: 'white', + stroke: RkTheme.current.colors.charts.area.stroke, + strokeOpacity: 0.8, + strokeWidth: 1.5, + }, + }} + /> + </VictoryGroup> + </VictoryChart> + </View> + ); +} diff --git a/frontend/app/components/charts/areaSmoothedChart.js b/frontend/app/components/charts/areaSmoothedChart.js new file mode 100644 index 0000000..54ca435 --- /dev/null +++ b/frontend/app/components/charts/areaSmoothedChart.js @@ -0,0 +1,118 @@ +import React from 'react'; +import { + View, + Dimensions, +} from 'react-native'; +import { + RkComponent, + RkText, + RkTheme, +} from 'react-native-ui-kitten'; +import { + VictoryChart, + VictoryAxis, + VictoryArea, +} from 'victory-native'; + +export class AreaSmoothedChart extends RkComponent { + state = { + data: [ + [ + { x: 1, y: 1.0, key: 0 }, + { x: 2, y: 1.5, key: 1 }, + { x: 3, y: 1.0, key: 2 }, + { x: 4, y: 0.5, key: 3 }, + { x: 5, y: 1.0, key: 4 }, + { x: 6, y: 2.0, key: 5 }, + { x: 7, y: 2.5, key: 6 }, + ], + [ + { x: 1, y: 1.5, key: 0 }, + { x: 2, y: 2.0, key: 1 }, + { x: 3, y: 1.5, key: 2 }, + { x: 4, y: 0.8, key: 3 }, + { x: 5, y: 1.5, key: 4 }, + { x: 6, y: 2.6, key: 5 }, + { x: 7, y: 3.3, key: 6 }, + ], + [ + { x: 1, y: 2.0, key: 0 }, + { x: 2, y: 2.5, key: 1 }, + { x: 3, y: 2.0, key: 2 }, + { x: 4, y: 1.1, key: 3 }, + { x: 5, y: 2.0, key: 4 }, + { x: 6, y: 3.2, key: 5 }, + { x: 7, y: 4.0, key: 6 }, + ], + [ + { x: 1, y: 2.5, key: 0 }, + { x: 2, y: 3.0, key: 1 }, + { x: 3, y: 2.5, key: 2 }, + { x: 4, y: 1.4, key: 3 }, + { x: 5, y: 2.5, key: 4 }, + { x: 6, y: 3.7, key: 5 }, + { x: 7, y: 4.7, key: 6 }, + ], + ], + }; + colors = RkTheme.current.colors.charts.followersArea; + + componentWillMount() { + this.size = Dimensions.get('window').width; + } + + renderChartAreas = () => this.state.data.reverse().map((area, index) => ( + <VictoryArea + key={`${area.length * index}`} + interpolation="natural" + style={{ + data: { + fill: this.colors[index], + stroke: this.colors[index], + }, + }} + data={area} + /> + )); + + render = () => ( + <View> + <RkText rkType='header4'>NEW FOLLOWERS</RkText> + <VictoryChart + padding={{ + top: 20, left: 40, right: 15, bottom: 40, + }} + width={this.size - 60}> + <VictoryAxis + tickValues={['Sun', 'Mon', 'Tue', ' Wed', 'Thu', 'Fri', 'Sat']} + style={{ + axis: { stroke: 'transparent' }, + tickLabels: { + fontSize: 14, + stroke: RkTheme.current.colors.text.secondary, + fill: RkTheme.current.colors.text.secondary, + fontFamily: RkTheme.current.fonts.family.regular, + strokeWidth: 0.5, + }, + }} + /> + <VictoryAxis + dependentAxis + tickValues={['10k', '20k', '30k', '40k']} + style={{ + axis: { stroke: 'transparent' }, + grid: { stroke: RkTheme.current.colors.disabled, strokeWidth: 0.5 }, + tickLabels: { + fontSize: 14, + stroke: RkTheme.current.colors.text.secondary, + fill: RkTheme.current.colors.text.secondary, + fontFamily: RkTheme.current.fonts.family.regular, + strokeWidth: 0.5, + }, + }} + /> + {this.renderChartAreas()} + </VictoryChart> + </View> + ) +} diff --git a/frontend/app/components/charts/doughnutChart.js b/frontend/app/components/charts/doughnutChart.js new file mode 100644 index 0000000..5b67ba7 --- /dev/null +++ b/frontend/app/components/charts/doughnutChart.js @@ -0,0 +1,125 @@ +import React from 'react'; +import { View } from 'react-native'; +import { + RkComponent, + RkText, + RkTheme, + RkStyleSheet, +} from 'react-native-ui-kitten'; +import { VictoryPie } from 'victory-native'; +import { Svg, Text as SvgText } from 'react-native-svg'; +import { scale } from '../../utils/scale'; + +export class DoughnutChart extends RkComponent { + state = { + selected: 0, + data: [ + { + x: 1, + y: 240, + title: '24%', + name: 'Likes', + color: RkTheme.current.colors.charts.doughnut[0], + }, + { + x: 2, + y: 270, + title: '27%', + name: 'Comments', + color: RkTheme.current.colors.charts.doughnut[1], + }, + { + x: 3, + y: 170, + title: '17%', + name: 'Shares', + color: RkTheme.current.colors.charts.doughnut[2], + }, + { + x: 4, + y: 320, + title: '32%', + name: 'People', + color: RkTheme.current.colors.charts.doughnut[3], + }, + ], + }; + size = 300; + fontSize = 40; + + computeColors = () => this.state.data.map(i => i.color); + + onPeopleChartPressed = (event, props) => { + this.setState({ + selected: props.index, + }); + }; + + renderMarkdown = () => this.state.data.map(this.renderMarkdownItem); + + renderMarkdownItem = (item) => ( + <View key={item.name} style={styles.legendItem}> + <View style={[styles.itemBadge, { backgroundColor: item.color }]} /> + <RkText rkType="primary3">{item.name}</RkText> + </View> + ); + + render = () => ( + <View> + <RkText rkType='header4'>AUDIENCE OVERVIEW</RkText> + <View style={{ alignSelf: 'center' }}> + <Svg width={scale(this.size)} height={scale(this.size)}> + <VictoryPie + labels={[]} + width={scale(this.size)} + height={scale(this.size)} + colorScale={this.computeColors()} + data={this.state.data} + standalone={false} + padding={scale(25)} + innerRadius={scale(70)} + events={[{ + target: 'data', + eventHandlers: { + onPressIn: this.onPeopleChartPressed, + }, + }]} + /> + <SvgText + textAnchor="middle" + verticalAnchor="middle" + x={scale(this.size / 2)} + y={scale(this.size / 2)} + height={scale(this.fontSize)} + fontSize={scale(this.fontSize)} + fontFamily={RkTheme.current.fonts.family.regular} + stroke={RkTheme.current.colors.text.base} + fill={RkTheme.current.colors.text.base}> + {this.state.data[this.state.selected].title} + </SvgText> + </Svg> + </View> + <View style={styles.legendContainer}> + {this.renderMarkdown()} + </View> + </View> + ); +} + +const styles = RkStyleSheet.create(() => ({ + legendContainer: { + flexDirection: 'row', + flexWrap: 'wrap', + justifyContent: 'space-around', + }, + legendItem: { + flexDirection: 'row', + alignItems: 'center', + }, + itemBadge: { + width: 10, + height: 10, + borderRadius: 5, + marginRight: 5, + }, +})); diff --git a/frontend/app/components/charts/index.js b/frontend/app/components/charts/index.js new file mode 100644 index 0000000..c4438e8 --- /dev/null +++ b/frontend/app/components/charts/index.js @@ -0,0 +1,4 @@ +export * from './progessChart'; +export * from './doughnutChart'; +export * from './areaChart'; +export * from './areaSmoothedChart'; diff --git a/frontend/app/components/charts/progessChart.js b/frontend/app/components/charts/progessChart.js new file mode 100644 index 0000000..bd70dcf --- /dev/null +++ b/frontend/app/components/charts/progessChart.js @@ -0,0 +1,96 @@ +import React from 'react'; +import { View } from 'react-native'; +import { + RkComponent, + RkText, + RkTheme, + RkStyleSheet, +} from 'react-native-ui-kitten'; +import { VictoryPie } from 'victory-native'; +import { Svg, Text as SvgText } from 'react-native-svg'; +import { scale } from '../../utils/scale'; + +export class ProgressChart extends RkComponent { + state = { + percents: 72, + }; + size = 120; + fontSize = 25; + + componentDidMount() { + this.setStateInterval = setInterval(this.updatePercent, 1500); + } + + componentWillUnmount() { + clearInterval(this.setStateInterval); + } + + updatePercent = () => { + let positive = Math.random() > 0.5; + if (this.state.percents > 95) { + positive = false; + } else if (this.state.percents < 60) { + positive = true; + } + this.setState({ + percents: positive ? this.state.percents + 1 : this.state.percents - 1, + }); + }; + + getChartData = () => [ + { x: 1, y: this.state.percents }, + { x: 2, y: 100 - this.state.percents }, + ]; + + onChartFill = (data) => { + const themeColor = RkTheme.current.colors.charts.followersProgress; + return data.x === 1 ? themeColor : 'transparent'; + }; + + render = () => ( + <View> + <RkText rkType='header4'>FOLLOWERS</RkText> + <View style={styles.chartContainer}> + <Svg width={scale(this.size)} height={scale(this.size)}> + <VictoryPie + labels={[]} + padding={0} + standalone={false} + width={scale(this.size)} + height={scale(this.size)} + style={{ data: { fill: this.onChartFill } }} + data={this.getChartData()} + cornerRadius={scale(25)} + innerRadius={scale(40)} + /> + <SvgText + textAnchor="middle" + verticalAnchor="middle" + x={scale(this.size / 2)} + y={scale(this.size / 2)} + height={scale(this.fontSize)} + fontSize={scale(this.fontSize)} + fontFamily={RkTheme.current.fonts.family.regular} + stroke={RkTheme.current.colors.text.base} + fill={RkTheme.current.colors.text.base}> + {`${this.state.percents}%`} + </SvgText> + </Svg> + <View> + <RkText rkType='header4'>REACH</RkText> + <RkText rkType='header2'>1 500 356</RkText> + <RkText rkType='secondary2'>+6 per day in average</RkText> + </View> + </View> + </View> + ); +} + +const styles = RkStyleSheet.create(() => ({ + chartContainer: { + flexDirection: 'row', + justifyContent: 'space-around', + alignItems: 'center', + marginTop: 10, + }, +})); diff --git a/frontend/app/components/ellipsis.js b/frontend/app/components/ellipsis.js new file mode 100644 index 0000000..1cdcae1 --- /dev/null +++ b/frontend/app/components/ellipsis.js @@ -0,0 +1,30 @@ +import React from 'react'; +import { View } from 'react-native'; +import { RkStyleSheet } from 'react-native-ui-kitten'; + +export class Ellipsis extends React.Component { + render() { + return ( + <View style={styles.container}> + <View style={styles.dot} /> + <View style={styles.dot} /> + <View style={styles.dot} /> + </View> + ); + } +} + +let styles = RkStyleSheet.create(theme => ({ + container: { + flexDirection: 'row', + marginHorizontal: 5, + marginVertical: 10, + }, + dot: { + height: 5.5, + width: 5.5, + borderRadius: 3, + backgroundColor: theme.colors.text.base, + marginHorizontal: 2.5, + }, +})); diff --git a/frontend/app/components/findFriends.js b/frontend/app/components/findFriends.js new file mode 100644 index 0000000..0e54034 --- /dev/null +++ b/frontend/app/components/findFriends.js @@ -0,0 +1,64 @@ +import React from 'react'; +import { + StyleSheet, + TouchableOpacity, + View, + ViewPropTypes, +} from 'react-native'; +import { + RkText, + RkTheme, +} from 'react-native-ui-kitten'; +import PropTypes from 'prop-types'; +import { FontAwesome } from '../assets/icons'; + +export class FindFriends extends React.Component { + static propTypes = { + selected: PropTypes.bool, + color: PropTypes.string, + icon: PropTypes.node.isRequired, + text: PropTypes.string.isRequired, + onPress: PropTypes.func, + style: ViewPropTypes.style, + }; + static defaultProps = { + selected: false, + color: RkTheme.current.colors.text.base, + onPress: (() => null), + style: {}, + }; + + render = () => { + const color = this.props.selected ? this.props.color : RkTheme.current.colors.disabled; + return ( + <TouchableOpacity style={[styles.wrapper, this.props.style]} onPress={this.props.onPress}> + <View style={styles.container}> + <View style={styles.text}> + <RkText rkType='awesome' style={[styles.icon, { color }]}>{this.props.icon}</RkText> + <RkText rkType='header6' style={{ color }}>{`Find Friends With ${this.props.text}`}</RkText> + </View> + <RkText rkType='awesome small' style={{ color }}>{FontAwesome.chevronRight}</RkText> + </View> + </TouchableOpacity> + ); + }; +} + +let styles = StyleSheet.create({ + wrapper: { + flex: 1, + }, + container: { + flex: 1, + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingVertical: 18, + }, + text: { + flexDirection: 'row', + }, + icon: { + width: 35, + }, +}); diff --git a/frontend/app/components/gallery.js b/frontend/app/components/gallery.js new file mode 100644 index 0000000..bcc57ea --- /dev/null +++ b/frontend/app/components/gallery.js @@ -0,0 +1,78 @@ +import React from 'react'; +import { + View, + FlatList, + Dimensions, + StyleSheet, +} from 'react-native'; +import { + RkText, + RkButton, + RkModalImg, +} from 'react-native-ui-kitten'; +import PropTypes from 'prop-types'; +import { Ellipsis } from './ellipsis'; +import { SocialBar } from './socialBar'; + +export class Gallery extends React.Component { + static propTypes = { + items: PropTypes.arrayOf(PropTypes.node).isRequired, + }; + + constructor(props) { + super(props); + const itemSize = (Dimensions.get('window').width - 12) / 3; + this.state = { + data: this.props.items, + itemSize, + }; + } + + extractItemKey = (index) => `${index}`; + + renderHeader = (options) => ( + <View style={styles.header}> + <RkButton rkType='clear contrast' onPress={options.closeImage}>Close</RkButton> + <RkText rkType='header4'>{`${options.pageNumber}/${options.totalPages}`}</RkText> + <RkButton rkType='clear'> + <Ellipsis /> + </RkButton> + </View> + ); + + renderFooter = () => ( + <SocialBar /> + ); + + renderItem = ({ index }) => ( + <RkModalImg + style={{ width: this.state.itemSize, height: this.state.itemSize }} + renderHeader={this.renderHeader} + renderFooter={this.renderFooter} + source={this.props.items} + index={index} + /> + ); + + render = () => ( + <View style={styles.images}> + <FlatList + data={this.state.data} + numColumns={3} + keyExtractor={this.extractItemKey} + renderItem={this.renderItem} + /> + </View> + ); +} + +const styles = StyleSheet.create({ + images: { + flexDirection: 'row', + paddingHorizontal: 0.5, + }, + header: { + flexDirection: 'row', + justifyContent: 'space-between', + }, +}); diff --git a/frontend/app/components/gradientButton/index.js b/frontend/app/components/gradientButton/index.js new file mode 100644 index 0000000..92ebd79 --- /dev/null +++ b/frontend/app/components/gradientButton/index.js @@ -0,0 +1,45 @@ +import React from 'react'; +import { LinearGradient } from 'expo'; +import { + RkButton, + RkText, + RkComponent, +} from 'react-native-ui-kitten'; + +export class GradientButton extends RkComponent { + componentName = 'GradientButton'; + typeMapping = { + button: {}, + gradient: {}, + text: {}, + }; + + renderContent = (textStyle) => { + const hasText = this.props.text === undefined; + return hasText ? this.props.children : this.renderText(textStyle); + }; + + renderText = (textStyle) => ( + <RkText style={textStyle}>{this.props.text}</RkText> + ); + + render() { + const { button, gradient, text: textStyle } = this.defineStyles(); + const { style, rkType, ...restProps } = this.props; + const colors = this.props.colors || this.extractNonStyleValue(gradient, 'colors'); + return ( + <RkButton + rkType='stretch' + style={[button, style]} + {...restProps}> + <LinearGradient + colors={colors} + start={{ x: 0.0, y: 0.5 }} + end={{ x: 1, y: 0.5 }} + style={[gradient]}> + {this.renderContent(textStyle)} + </LinearGradient> + </RkButton> + ); + } +} diff --git a/frontend/app/components/gradientButton/types.js b/frontend/app/components/gradientButton/types.js new file mode 100644 index 0000000..62bcf22 --- /dev/null +++ b/frontend/app/components/gradientButton/types.js @@ -0,0 +1,48 @@ +import { scaleVertical } from '../../utils/scale'; + +export const GradientButtonTypes = (theme) => ({ + _base: { + button: { + alignItems: 'stretch', + paddingVertical: 0, + paddingHorizontal: 0, + height: scaleVertical(40), + borderRadius: 20, + }, + gradient: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + borderRadius: 20, + colors: theme.colors.gradients.base, + }, + text: { + backgroundColor: 'transparent', + color: theme.colors.text.inverse, + }, + }, + large: { + button: { + alignSelf: 'stretch', + height: scaleVertical(56), + borderRadius: 28, + }, + gradient: { + borderRadius: 28, + }, + }, + statItem: { + button: { + flex: 1, + borderRadius: 5, + marginHorizontal: 10, + height: null, + alignSelf: 'auto', + }, + gradient: { + flex: 1, + borderRadius: 5, + padding: 10, + }, + }, +}); diff --git a/frontend/app/components/index.js b/frontend/app/components/index.js new file mode 100644 index 0000000..786a99b --- /dev/null +++ b/frontend/app/components/index.js @@ -0,0 +1,14 @@ +export * from './avatar'; +export * from './gradientButton'; +export * from './charts'; +export * from './socialBar'; +export * from './switch/index'; +export * from './ellipsis'; +export * from './gallery'; +export * from './socialSetting'; +export * from './findFriends'; +export * from './progressBar'; +export * from './navBar'; +export * from './paginationIndicator'; +export * from './passwordTextInput'; +export * from './cardInput'; diff --git a/frontend/app/components/navBar.js b/frontend/app/components/navBar.js new file mode 100644 index 0000000..2947209 --- /dev/null +++ b/frontend/app/components/navBar.js @@ -0,0 +1,145 @@ +import React from 'react'; +import { + StyleSheet, + View, +} from 'react-native'; +import { DrawerActions } from 'react-navigation'; +import PropTypes from 'prop-types'; +import _ from 'lodash'; +import { RkText, RkButton, RkStyleSheet } from 'react-native-ui-kitten'; +import { FontAwesome } from '../assets/icons'; +import { UIConstants } from '../config/appConstants'; +import NavigationType from '../config/navigation/propTypes'; + +export class NavBar extends React.Component { + static propTypes = { + navigation: NavigationType.isRequired, + headerProps: PropTypes.shape().isRequired, + }; + + onNavigationLeftMenuButtonPressed = () => { + this.props.navigation.dispatch(DrawerActions.openDrawer()); + }; + + onNavigationLeftBackButtonPressed = () => { + this.props.navigation.goBack(); + }; + + renderTitleItem = (title, options) => { + const isCustom = options !== undefined; + return isCustom ? this.renderCustomTitleItem(options) : this.renderNavigationTitleItem(title); + }; + + renderLeftItem = (options) => { + const isCustom = options !== undefined; + return isCustom ? this.renderCustomLeftItem(options) : this.renderNavigationLeftItem(); + }; + + renderRightItem = (options) => { + const isCustom = options !== undefined; + return isCustom ? this.renderCustomRightItem(options) : this.renderNavigationRightItem(); + }; + + renderNavigationTitleItem = (title) => ( + <View style={styles.title}> + <RkText>{title}</RkText> + </View> + ); + + renderNavigationLeftBackItem = () => ( + <RkButton + rkType='clear' + style={styles.menu} + onPress={this.onNavigationLeftBackButtonPressed}> + <RkText rkType='awesome hero'>{FontAwesome.chevronLeft}</RkText> + </RkButton> + ); + + renderNavigationLeftMenuItem = () => ( + <RkButton + rkType='clear' + style={styles.menu} + onPress={this.onNavigationLeftMenuButtonPressed}> + <RkText rkType='awesome'>{FontAwesome.bars}</RkText> + </RkButton> + ); + + renderNavigationLeftItemContent = (sceneIndex) => { + const isFirstScene = sceneIndex === 0; + return isFirstScene ? this.renderNavigationLeftMenuItem() : this.renderNavigationLeftBackItem(); + }; + + renderNavigationLeftItem = () => { + const sceneIndex = _.findIndex(this.props.headerProps.scenes, { isActive: true }); + return ( + <View style={styles.left}> + {this.renderNavigationLeftItemContent(sceneIndex)} + </View> + ); + }; + + renderNavigationRightItem = () => undefined; + + renderCustomTitleItem = (options) => ( + <View + style={styles.title}> + {options} + </View> + ); + + renderCustomLeftItem = (options) => ( + <View style={styles.left}>{options}</View> + ); + + renderCustomRightItem = (options) => ( + <View style={styles.right}>{options}</View> + ); + + render() { + const { options } = this.props.headerProps.scene.descriptor; + return ( + <View style={styles.layout}> + <View style={styles.container}> + {this.renderTitleItem(options.title, options.headerTitle)} + {this.renderLeftItem(options.headerLeft)} + {this.renderRightItem(options.headerRight)} + </View> + </View> + ); + } +} + +const styles = RkStyleSheet.create(theme => ({ + layout: { + backgroundColor: theme.colors.screen.base, + paddingTop: UIConstants.StatusbarHeight, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: theme.colors.border.base, + }, + container: { + flexDirection: 'row', + height: UIConstants.AppbarHeight, + + }, + left: { + position: 'absolute', + top: 0, + bottom: 0, + justifyContent: 'center', + }, + right: { + position: 'absolute', + right: 0, + top: 0, + bottom: 0, + justifyContent: 'center', + }, + title: { + ...StyleSheet.absoluteFillObject, + justifyContent: 'center', + alignItems: 'center', + }, + menu: { + width: 40, + }, +})); diff --git a/frontend/app/components/paginationIndicator.js b/frontend/app/components/paginationIndicator.js new file mode 100644 index 0000000..6a043d0 --- /dev/null +++ b/frontend/app/components/paginationIndicator.js @@ -0,0 +1,49 @@ +import React from 'react'; +import { View } from 'react-native'; +import { RkStyleSheet } from 'react-native-ui-kitten'; +import PropTypes from 'prop-types'; + +export class PaginationIndicator extends React.Component { + static propTypes = { + current: PropTypes.number, + length: PropTypes.number.isRequired, + }; + static defaultProps = { + current: 0, + }; + + renderIndicatorItem = (index, selected) => ( + <View style={selected ? [styles.base, styles.selected] : styles.base} key={index} /> + ); + + renderIndicators = () => { + const indicators = []; + for (let i = 0; i < this.props.length; i += 1) { + indicators.push(this.renderIndicatorItem(i, i === this.props.current)); + } + return indicators; + }; + + render = () => ( + <View style={styles.container}> + {this.renderIndicators()} + </View> + ); +} + +const styles = RkStyleSheet.create(theme => ({ + container: { + flexDirection: 'row', + }, + base: { + width: 8, + height: 8, + borderRadius: 5, + borderColor: theme.colors.brand, + borderWidth: 1, + marginHorizontal: 5, + }, + selected: { + backgroundColor: theme.colors.brand, + }, +})); diff --git a/frontend/app/components/passwordTextInput.js b/frontend/app/components/passwordTextInput.js new file mode 100644 index 0000000..5590caf --- /dev/null +++ b/frontend/app/components/passwordTextInput.js @@ -0,0 +1,47 @@ +import React from 'react'; +import { + RkButton, + RkTextInput, + RkText, + RkStyleSheet, +} from 'react-native-ui-kitten'; +import { FontAwesome } from '../assets/icons'; + +export class PasswordTextInput extends React.Component { + state = { + hidden: true, + }; + + onInputLabelPressed = () => { + this.setState({ hidden: !this.state.hidden }); + }; + + renderInputLabel = () => ( + <RkButton + style={styles.button} + rkType='clear' + onPress={this.onInputLabelPressed}> + <RkText style={styles.icon} rkType='awesome secondaryColor'>{FontAwesome.slashEye}</RkText> + </RkButton> + ); + + render = () => ( + <RkTextInput + autoCapitalize='none' + rkType='bordered rounded iconRight' + autoCorrect={false} + label={this.renderInputLabel()} + secureTextEntry={this.state.hidden} + {...this.props} + /> + ); +} + +const styles = RkStyleSheet.create({ + icon: { + fontSize: 24, + }, + button: { + right: 17, + }, +}); diff --git a/frontend/app/components/picker/datePicker.js b/frontend/app/components/picker/datePicker.js new file mode 100644 index 0000000..4225613 --- /dev/null +++ b/frontend/app/components/picker/datePicker.js @@ -0,0 +1,103 @@ +import React from 'react'; +import { RkPicker } from 'react-native-ui-kitten'; + +export const DatePart = Object.freeze({ YEAR: 1, MONTH: 2, DAY: 3 }); + +export class DatePicker extends React.Component { + componentName = 'DatePicker'; + + state = { + data: { + days: DatePicker.generateArrayFromRange(1, 31), + years: DatePicker.generateArrayFromRange(2000, 2030), + months: [ + { key: 1, value: 'Jun' }, { key: 2, value: 'Feb' }, + { key: 3, value: 'Mar' }, { key: 4, value: 'Apr' }, + { key: 5, value: 'May' }, { key: 6, value: 'Jun' }, + { key: 7, value: 'Jul' }, { key: 8, value: 'Aug' }, + { key: 9, value: 'Sep' }, { key: 10, value: 'Oct' }, + { key: 11, value: 'Nov' }, { key: 12, value: 'Dec' }, + ], + }, + }; + + onDatePickerConfirm = (date) => { + let resultDate = {}; + if (this.props.customDateParts) { + let i = 0; + if (this.props.customDateParts.includes(DatePart.MONTH)) { + resultDate.month = date[i += 1]; + } + if (this.props.customDateParts.includes(DatePart.DAY)) { + resultDate.day = date[i += 1]; + } + if (this.props.customDateParts.includes(DatePart.YEAR)) { + resultDate.year = date[i]; + } + } else { + resultDate = { month: date[0], day: date[1], year: date[2] }; + } + this.props.onConfirm(resultDate); + }; + + static generateArrayFromRange(start, finish) { + return Array(...Array((finish - start) + 1)).map((_, i) => start + i); + } + + findElementByKey(key, array) { + let element = array[0]; + array.forEach((value) => { + if (value.key === key) element = value; + }); + return element; + } + + render() { + const { + onConfirm, + selectedYear, + selectedMonth, + selectedDay, + customDateParts, + ...props + } = this.props; + + let data = [this.state.data.months, this.state.data.days, this.state.data.years]; + let selectedOptions = [ + this.findElementByKey(selectedMonth, this.state.data.months), + selectedDay || 1, + selectedYear || 2000, + ]; + if (customDateParts) { + selectedOptions = []; + data = []; + if (customDateParts.includes(DatePart.MONTH)) { + data.push(this.state.data.months); + selectedOptions.push(this.findElementByKey(selectedMonth, this.state.data.months)); + } + if (customDateParts.includes(DatePart.DAY)) { + data.push(this.state.data.days); + selectedOptions.push(selectedDay || 1); + } + if (customDateParts.includes(DatePart.YEAR)) { + data.push(this.state.data.years); + selectedOptions.push(selectedYear || 2000); + } + } + return ( + <RkPicker + rkType='highlight' + title='Set Date' + data={data} + onConfirm={this.onDatePickerConfirm} + selectedOptions={selectedOptions} + optionRkType='subtitle small' + selectedOptionRkType='header4' + titleTextRkType='header4' + cancelTextRkType='light' + confirmTextRkType='' + {...props} + /> + ); + } +} diff --git a/frontend/app/components/picker/index.js b/frontend/app/components/picker/index.js new file mode 100644 index 0000000..6d1605a --- /dev/null +++ b/frontend/app/components/picker/index.js @@ -0,0 +1 @@ +export * from './datePicker'; diff --git a/frontend/app/components/progressBar.js b/frontend/app/components/progressBar.js new file mode 100644 index 0000000..dd3a3a1 --- /dev/null +++ b/frontend/app/components/progressBar.js @@ -0,0 +1,63 @@ +import React from 'react'; +import { + StyleSheet, + View, + Animated, + Easing, + ViewPropTypes, +} from 'react-native'; +import PropTypes from 'prop-types'; +import { RkTheme } from 'react-native-ui-kitten'; + +export class ProgressBar extends React.Component { + static propTypes = { + width: PropTypes.number.isRequired, + progress: PropTypes.number, + color: PropTypes.string, + style: ViewPropTypes.style, + }; + static defaultProps = { + progress: 0, + color: RkTheme.current.colors.accent, + style: {}, + }; + + state = { + progress: new Animated.Value(0), + }; + + componentDidUpdate(prevProps) { + if (this.props.progress >= 0 && this.props.progress !== prevProps.progress) { + this.animate(this.props.progress); + } + } + + animate = (endValue) => { + Animated.timing(this.state.progress, { + easing: Easing.inOut(Easing.ease), + duration: 500, + toValue: endValue, + }).start(); + }; + + render() { + const width = this.state.progress.interpolate({ + inputRange: [0, 1], + outputRange: [0, this.props.width], + }); + return ( + <View style={[styles.container, this.props.style, { width: this.props.width }]}> + <Animated.View style={[styles.value, { width }, { backgroundColor: this.props.color }]} /> + </View> + ); + } +} + +const styles = StyleSheet.create({ + container: { + height: 3, + }, + value: { + height: 3, + }, +}); diff --git a/frontend/app/components/socialBar/index.js b/frontend/app/components/socialBar/index.js new file mode 100644 index 0000000..5cf3fc2 --- /dev/null +++ b/frontend/app/components/socialBar/index.js @@ -0,0 +1,61 @@ +import React from 'react'; +import { View } from 'react-native'; +import { + RkText, + RkButton, + RkComponent, +} from 'react-native-ui-kitten'; +import { FontAwesome } from '../../assets/icons'; + +export class SocialBar extends RkComponent { + componentName = 'SocialBar'; + typeMapping = { + container: {}, + section: {}, + icon: {}, + label: {}, + }; + static data = { + comments: '26', + is_solved: "Doesn't solved", + }; + + constructor(props) { + super(props); + this.state = { + comments: this.props.comments, + is_solved: this.props.is_solved || SocialBar.data.is_solved, + }; + } + + + onCommentButtonPressed = () => { + }; + + + render() { + const { + container, section, icon, label, + } = this.defineStyles(); + + const comments = this.state.comments + (this.props.showLabel ? ' Comments' : ''); + const is_solved = this.state.is_solved + (this.props.showLabel ? '' : ''); + + return ( + <View style={container}> + <View style={section}> + <RkButton rkType='clear' onPress={this.onCommentButtonPressed}> + <RkText rkType='awesome hintColor' style={icon}>{FontAwesome.comment}</RkText> + <RkText rkType='primary4 hintColor' style={label}>{comments}</RkText> + </RkButton> + </View> + <View style={section}> + <RkButton rkType='clear' > + <RkText rkType='awesome hintColor' style={icon}>{FontAwesome.slashEye}</RkText> + <RkText rkType='primary4 hintColor' style={label}>{is_solved}</RkText> + </RkButton> + </View> + </View> + ); + } +} diff --git a/frontend/app/components/socialBar/types.js b/frontend/app/components/socialBar/types.js new file mode 100644 index 0000000..3aa638d --- /dev/null +++ b/frontend/app/components/socialBar/types.js @@ -0,0 +1,43 @@ +export const SocialBarTypes = (theme) => ({ + _base: { + container: { + justifyContent: 'center', + alignItems: 'center', + flexDirection: 'row', + flex: 1, + }, + section: { + justifyContent: 'center', + flexDirection: 'row', + flex: 1, + }, + icon: { + fontSize: 20, + }, + label: { + marginLeft: 8, + alignSelf: 'flex-end', + }, + }, + leftAligned: { + section: { + alignItems: 'flex-start', + justifyContent: 'flex-start', + }, + label: { + color: theme.colors.text.inverse, + }, + icon: { + color: theme.colors.text.inverse, + }, + }, + space: { + container: { + justifyContent: 'space-between', + paddingHorizontal: 10, + }, + section: { + flex: -1, + }, + }, +}); diff --git a/frontend/app/components/socialSetting.js b/frontend/app/components/socialSetting.js new file mode 100644 index 0000000..1c2f2a3 --- /dev/null +++ b/frontend/app/components/socialSetting.js @@ -0,0 +1,66 @@ +import React from 'react'; +import { + View, + StyleSheet, +} from 'react-native'; +import { + RkText, + RkTheme, +} from 'react-native-ui-kitten'; +import PropTypes from 'prop-types'; +import { RkSwitch } from './switch/index'; + +export class SocialSetting extends React.Component { + static propTypes = { + name: PropTypes.string.isRequired, + icon: PropTypes.node.isRequired, + selected: PropTypes.bool, + tintColor: PropTypes.string, + }; + static defaultProps = { + selected: true, + tintColor: RkTheme.current.colors.accent, + }; + + constructor(props) { + super(props); + this.state = { + selected: this.props.selected, + }; + } + + onSwitchValueChanged = (value) => { + this.setState({ selected: value }); + }; + + render() { + const color = this.state.selected ? this.props.tintColor : RkTheme.current.colors.disabled; + return ( + <View style={styles.container}> + <View style={styles.left}> + <RkText rkType='awesome large' style={[styles.icon, { color }]}>{this.props.icon}</RkText> + <RkText rkType='small' style={{ color }}>{this.props.name}</RkText> + </View> + <RkSwitch value={this.state.selected} onValueChange={this.onSwitchValueChanged} /> + </View> + ); + } +} + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + flex: 1, + justifyContent: 'space-between', + alignItems: 'center', + paddingVertical: 14, + }, + left: { + flexDirection: 'row', + alignItems: 'center', + }, + icon: { + width: 35, + alignItems: 'center', + }, +}); diff --git a/frontend/app/components/switch/index.android.js b/frontend/app/components/switch/index.android.js new file mode 100644 index 0000000..2750545 --- /dev/null +++ b/frontend/app/components/switch/index.android.js @@ -0,0 +1,171 @@ +/* + * + * This is modified version of https://github.com/poberwong/react-native-switch-pro + * Copyright (c) 2016 PoberWong + * + */ +import React from 'react'; +import { + Animated, + Easing, + PanResponder, +} from 'react-native'; +import { RkComponent } from 'react-native-ui-kitten'; + +const width = 52; +const height = 32; +const animationDuration = 200; +const offLeftValue = -2; +const onLeftValue = 20; + +export class RkSwitch extends RkComponent { + componentName = 'RkSwitch'; + typeMapping = { + container: { + onColor: 'onColor', + offColor: 'offColor', + }, + thumb: {}, + }; + selectedType = 'selected'; + + constructor(props) { + super(props); + this.offset = width - height; + this.handlerSize = height; + this.state = { + name: this.props.name, + value: this.props.value, + toggleable: true, + alignItems: this.props.value ? 'flex-end' : 'flex-start', + left: this.props.value ? onLeftValue : offLeftValue, + handlerAnimation: new Animated.Value(this.handlerSize), + switchAnimation: new Animated.Value(this.props.value ? -1 : 1), + }; + } + + componentWillReceiveProps(nextProps) { + const { value } = this.state; + if (nextProps === this.props) { + return; + } + if (typeof nextProps.value !== 'undefined' && nextProps.value !== value) { + this.toggleSwitch(true); + } + } + + componentWillMount() { + this.panResponder = PanResponder.create({ + onStartShouldSetPanResponder: () => true, + onStartShouldSetPanResponderCapture: () => true, + onMoveShouldSetPanResponder: () => true, + onMoveShouldSetPanResponderCapture: () => true, + onPanResponderTerminationRequest: () => true, + onPanResponderGrant: this.onPanResponderGrant, + onPanResponderMove: this.onPanResponderMove, + onPanResponderRelease: this.onPanResponderRelease, + }); + } + + onPanResponderGrant = () => { + this.animateHandler(height * 0.9); + }; + + onPanResponderMove = (evt, gestureState) => { + const { value } = this.state; + + this.setState({ + toggleable: value ? (gestureState.dx < 10) : (gestureState.dx > -10), + }); + }; + + onPanResponderRelease = () => { + const { toggleable } = this.state; + const { disabled, onValueChange } = this.props; + + if (toggleable && !disabled) { + if (onValueChange) { + this.toggleSwitch(onValueChange); + } + } + }; + + toggleSwitch = (result, callback = () => null) => { + const { value, switchAnimation } = this.state; + const toValue = !value; + + this.animateHandler(this.handlerSize); + + this.animateSwitch(toValue, () => { + callback(toValue); + this.setState({ + value: toValue, + left: toValue ? onLeftValue : offLeftValue, + }); + switchAnimation.setValue(toValue ? -1 : 1); + }); + }; + + animateSwitch = (value, callback = () => null) => { + const { switchAnimation } = this.state; + + Animated.timing( + switchAnimation, + { + toValue: value ? this.offset : -this.offset, + duration: animationDuration, + easing: Easing.linear, + }, + ).start(callback); + }; + + animateHandler = (value, callback = () => null) => { + const { handlerAnimation } = this.state; + + Animated.timing( + handlerAnimation, + { + toValue: value, + duration: animationDuration, + easing: Easing.linear, + }, + ).start(callback); + }; + + render() { + const { + switchAnimation, handlerAnimation, left, value, + } = this.state; + const { + style, + ...rest + } = this.props; + + const type = value ? this.selectedType : ''; + const { container, thumb } = this.defineStyles(type); + const onColor = this.extractNonStyleValue(container, 'onColor'); + const offColor = this.extractNonStyleValue(container, 'offColor'); + + const interpolatedBackgroundColor = switchAnimation.interpolate({ + inputRange: value ? [-this.offset, -1] : [1, this.offset], + outputRange: [offColor, onColor], + }); + + return ( + <Animated.View + {...rest} + {...this.panResponder.panHandlers} + style={[style, container, { + backgroundColor: interpolatedBackgroundColor, + }]}> + <Animated.View style={[thumb, { + position: 'absolute', + left, + height: handlerAnimation, + transform: [{ translateX: switchAnimation }], + }]} + /> + </Animated.View> + ); + } +} diff --git a/frontend/app/components/switch/index.ios.js b/frontend/app/components/switch/index.ios.js new file mode 100644 index 0000000..068cbfb --- /dev/null +++ b/frontend/app/components/switch/index.ios.js @@ -0,0 +1,36 @@ +import React from 'react'; +import { Switch } from 'react-native'; +import { RkComponent } from 'react-native-ui-kitten'; + + +export class RkSwitch extends RkComponent { + componentName = 'RkSwitch'; + typeMapping = { + container: { + onColor: 'onColor', + offColor: 'offColor', + }, + main: {}, + }; + selectedType = 'selected'; + + constructor(props) { + super(props); + this.onChange = this.props.onValueChange ? + this.props.onValueChange + : () => true; + } + + render() { + const { container } = this.defineStyles(); + const onColor = this.extractNonStyleValue(container, 'onColor'); + return ( + <Switch + style={this.props.style} + value={this.props.value} + onValueChange={(value) => this.onChange(value)} + onTintColor={onColor} + /> + ); + } +} diff --git a/frontend/app/components/switch/types.js b/frontend/app/components/switch/types.js new file mode 100644 index 0000000..96b116e --- /dev/null +++ b/frontend/app/components/switch/types.js @@ -0,0 +1,35 @@ +export const SwitchTypes = (theme) => ({ + _base: { + container: { + width: 52, + height: 32, + overflow: 'hidden', + justifyContent: 'center', + borderRadius: 16, + borderWidth: 1, + borderColor: theme.colors.border.secondary, + onColor: theme.colors.primary, + offColor: { + android: theme.colors.screen.base, + ios: theme.colors.border.base, + }, + }, + thumb: { + position: 'absolute', + height: 32, + width: 32, + borderWidth: 1, + borderColor: theme.colors.border.secondary, + backgroundColor: theme.colors.screen.base, + borderRadius: 16, + }, + }, + selected: { + thumb: { + borderColor: theme.colors.primary, + }, + container: { + borderColor: theme.colors.primary, + }, + }, +}); diff --git a/frontend/app/components/walkthrough.js b/frontend/app/components/walkthrough.js new file mode 100644 index 0000000..7fdb247 --- /dev/null +++ b/frontend/app/components/walkthrough.js @@ -0,0 +1,63 @@ +import React from 'react'; +import { + View, + FlatList, + Dimensions, + StyleSheet, +} from 'react-native'; +import PropTypes from 'prop-types'; + +export class Walkthrough extends React.Component { + static propTypes = { + children: PropTypes.arrayOf(PropTypes.element).isRequired, + onChanged: PropTypes.func, + }; + static defaultProps = { + onChanged: (() => null), + }; + + constructor(props) { + super(props); + this.itemWidth = Dimensions.get('window').width; + } + + extractItemKey = (item) => `${this.props.children.indexOf(item)}`; + + onScrollEnd = (e) => { + const { contentOffset } = e.nativeEvent; + const viewSize = e.nativeEvent.layoutMeasurement; + const pageNum = Math.floor(contentOffset.x / viewSize.width); + this.props.onChanged(pageNum); + }; + + renderItem = ({ item }) => ( + <View style={[styles.item, { width: this.itemWidth }]}> + {item} + </View> + ); + + render = () => ( + <FlatList + style={styles.list} + data={this.props.children} + onMomentumScrollEnd={this.onScrollEnd} + keyExtractor={this.extractItemKey} + pagingEnabled + horizontal + renderSeparator={() => null} + showsHorizontalScrollIndicator={false} + showsVerticalScrollIndicator={false} + directionalLockEnabled + renderItem={this.renderItem} + /> + ); +} + +const styles = StyleSheet.create({ + list: { + flex: 1, + }, + item: { + flex: 1, + }, +}); |