0
0
Fork 0

Swipeable views (#4105)

* feat: Replace react-swipeable with react-swipeable-views

* fix: iOS 9
This commit is contained in:
Sorin Davidoi 2017-07-09 15:02:26 +02:00 committed by Eugen Rochko
parent caf938562e
commit fc4c74660b
9 changed files with 150 additions and 82 deletions

View file

@ -10,7 +10,7 @@ export default class ColumnHeader extends React.PureComponent {
};
static propTypes = {
title: PropTypes.string.isRequired,
title: PropTypes.node.isRequired,
icon: PropTypes.string.isRequired,
active: PropTypes.bool,
multiColumn: PropTypes.bool,

View file

@ -1,13 +1,19 @@
import React from 'react';
import PropTypes from 'prop-types';
import Column from '../../../components/column';
import ColumnHeader from '../../../components/column_header';
const ColumnLoading = () => (
const ColumnLoading = ({ title = '', icon = ' ' }) => (
<Column>
<ColumnHeader icon=' ' title='' multiColumn={false} />
<ColumnHeader icon={icon} title={title} multiColumn={false} />
<div className='scrollable' />
</Column>
);
ColumnLoading.propTypes = {
title: PropTypes.node,
icon: PropTypes.string,
};
export default ColumnLoading;

View file

@ -3,8 +3,8 @@ import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ReactSwipeable from 'react-swipeable';
import { getPreviousLink, getNextLink } from './tabs_bar';
import ReactSwipeableViews from 'react-swipeable-views';
import { links, getIndex, getLink } from './tabs_bar';
import BundleContainer from '../containers/bundle_container';
import ColumnLoading from './column_loading';
@ -32,21 +32,29 @@ export default class ColumnsArea extends ImmutablePureComponent {
children: PropTypes.node,
};
handleRightSwipe = () => {
const previousLink = getPreviousLink(this.context.router.history.location.pathname);
if (previousLink) {
this.context.router.history.push(previousLink);
}
handleSwipe = (index) => {
window.requestAnimationFrame(() => {
window.requestAnimationFrame(() => {
this.context.router.history.push(getLink(index));
});
});
}
handleLeftSwipe = () => {
const previousLink = getNextLink(this.context.router.history.location.pathname);
renderView = (link, index) => {
const columnIndex = getIndex(this.context.router.history.location.pathname);
const title = link.props.children[1] && React.cloneElement(link.props.children[1]);
const icon = (link.props.children[0] || link.props.children).props.className.split(' ')[2].split('-')[1];
if (previousLink) {
this.context.router.history.push(previousLink);
}
};
const view = (index === columnIndex) ?
React.cloneElement(this.props.children) :
<ColumnLoading title={title} icon={icon} />;
return (
<div className='columns-area' key={index}>
{view}
</div>
);
}
renderLoading = () => {
return <ColumnLoading />;
@ -59,12 +67,14 @@ export default class ColumnsArea extends ImmutablePureComponent {
render () {
const { columns, children, singleColumn } = this.props;
const columnIndex = getIndex(this.context.router.history.location.pathname);
if (singleColumn) {
return (
<ReactSwipeable onSwipedLeft={this.handleLeftSwipe} onSwipedRight={this.handleRightSwipe} delta={30} className='columns-area'>
{children}
</ReactSwipeable>
);
return columnIndex !== -1 ? (
<ReactSwipeableViews index={columnIndex} onChangeIndex={this.handleSwipe} animateTransitions={false} style={{ height: '100%' }}>
{links.map(this.renderView)}
</ReactSwipeableViews>
) : <div className='columns-area'>{children}></div>;
}
return (

View file

@ -1,5 +1,5 @@
import React from 'react';
import ReactSwipeable from 'react-swipeable';
import ReactSwipeableViews from 'react-swipeable-views';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import ExtendedVideoPlayer from '../../../components/extended_video_player';
@ -26,6 +26,10 @@ export default class MediaModal extends ImmutablePureComponent {
index: null,
};
handleSwipe = (index) => {
this.setState({ index: (index) % this.props.media.size });
}
handleNextClick = () => {
this.setState({ index: (this.getIndex() + 1) % this.props.media.size });
}
@ -74,10 +78,12 @@ export default class MediaModal extends ImmutablePureComponent {
}
if (attachment.get('type') === 'image') {
const width = attachment.getIn(['meta', 'original', 'width']) || null;
const height = attachment.getIn(['meta', 'original', 'height']) || null;
content = media.map((image) => {
const width = image.getIn(['meta', 'original', 'width']) || null;
const height = image.getIn(['meta', 'original', 'height']) || null;
content = <ImageLoader previewSrc={attachment.get('preview_url')} src={url} width={width} height={height} />;
return <ImageLoader previewSrc={image.get('preview_url')} src={image.get('url')} width={width} height={height} key={image.get('preview_url')} />;
}).toArray();
} else if (attachment.get('type') === 'gifv') {
content = <ExtendedVideoPlayer src={url} muted controls={false} />;
}
@ -88,9 +94,9 @@ export default class MediaModal extends ImmutablePureComponent {
<div className='media-modal__content'>
<IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={16} />
<ReactSwipeable onSwipedRight={this.handlePrevClick} onSwipedLeft={this.handleNextClick}>
<ReactSwipeableViews onChangeIndex={this.handleSwipe} index={index}>
{content}
</ReactSwipeable>
</ReactSwipeableViews>
</div>
{rightNav}

View file

@ -3,11 +3,9 @@ import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ReactSwipeable from 'react-swipeable';
import ReactSwipeableViews from 'react-swipeable-views';
import classNames from 'classnames';
import Permalink from '../../../components/permalink';
import TransitionMotion from 'react-motion/lib/TransitionMotion';
import spring from 'react-motion/lib/spring';
import ComposeForm from '../../compose/components/compose_form';
import Search from '../../compose/components/search';
import NavigationBar from '../../compose/components/navigation_bar';
@ -227,6 +225,10 @@ export default class OnboardingModal extends React.PureComponent {
}));
}
handleSwipe = (index) => {
this.setState({ currentIndex: index });
}
handleKeyUp = ({ key }) => {
switch (key) {
case 'ArrowLeft':
@ -263,30 +265,18 @@ export default class OnboardingModal extends React.PureComponent {
</button>
);
const styles = pages.map((data, i) => ({
key: `page-${i}`,
data,
style: {
opacity: spring(i === currentIndex ? 1 : 0),
},
}));
return (
<div className='modal-root__modal onboarding-modal'>
<TransitionMotion styles={styles}>
{interpolatedStyles => (
<ReactSwipeable onSwipedRight={this.handlePrev} onSwipedLeft={this.handleNext} className='onboarding-modal__pager'>
{interpolatedStyles.map(({ key, data, style }, i) => {
const className = classNames('onboarding-modal__page__wrapper', {
'onboarding-modal__page__wrapper--active': i === currentIndex,
});
return (
<div key={key} style={style} className={className}>{data}</div>
);
})}
</ReactSwipeable>
)}
</TransitionMotion>
<ReactSwipeableViews index={currentIndex} onChangeIndex={this.handleSwipe} className='onboarding-modal__pager'>
{pages.map((page, i) => {
const className = classNames('onboarding-modal__page__wrapper', {
'onboarding-modal__page__wrapper--active': i === currentIndex,
});
return (
<div key={i} className={className}>{page}</div>
);
})}
</ReactSwipeableViews>
<div className='onboarding-modal__paginator'>
<div>

View file

@ -2,7 +2,7 @@ import React from 'react';
import NavLink from 'react-router-dom/NavLink';
import { FormattedMessage } from 'react-intl';
const links = [
export const links = [
<NavLink className='tabs-bar__link primary' activeClassName='active' to='/statuses/new'><i className='fa fa-fw fa-pencil' /><FormattedMessage id='tabs_bar.compose' defaultMessage='Compose' /></NavLink>,
<NavLink className='tabs-bar__link primary' activeClassName='active' to='/timelines/home'><i className='fa fa-fw fa-home' /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>,
<NavLink className='tabs-bar__link primary' activeClassName='active' to='/notifications'><i className='fa fa-fw fa-bell' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>,
@ -13,25 +13,13 @@ const links = [
<NavLink className='tabs-bar__link primary' activeClassName='active' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started'><i className='fa fa-fw fa-asterisk' /></NavLink>,
];
export function getPreviousLink (path) {
const index = links.findIndex(link => link.props.to === path);
export function getIndex (path) {
return links.findIndex(link => link.props.to === path);
}
if (index > 0) {
return links[index - 1].props.to;
}
return null;
};
export function getNextLink (path) {
const index = links.findIndex(link => link.props.to === path);
if (index !== -1 && index < links.length - 1) {
return links[index + 1].props.to;
}
return null;
};
export function getLink (index) {
return links[index].props.to;
}
export default class TabsBar extends React.Component {

View file

@ -1266,6 +1266,23 @@
.columns-area {
padding: 10px;
}
.react-swipeable-view-container .columns-area {
height: calc(100% - 20px) !important;
}
}
.react-swipeable-view-container {
&,
.columns-area,
.drawer,
.column {
height: 100%;
}
}
.react-swipeable-view-container > * {
height: 100%;
}
.column {
@ -2910,7 +2927,7 @@ button.icon-button.active i.fa-retweet {
video {
max-width: 80vw;
max-height: 80vh;
width: auto;
width: 100%;
height: auto;
}
@ -2938,7 +2955,26 @@ button.icon-button.active i.fa-retweet {
flex-direction: column;
}
.onboarding-modal__pager,
.onboarding-modal__pager {
height: 80vh;
width: 80vw;
max-width: 520px;
max-height: 420px;
.react-swipeable-view-container > div {
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 25px;
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
display: flex;
user-select: text;
}
}
.error-modal__body {
height: 80vh;
width: 80vw;