Реализована анимация фильтрации языков

This commit is contained in:
ErickSkrauch 2017-12-16 19:55:57 +03:00
parent e2c8471ce2
commit b76b98e3bf
2 changed files with 121 additions and 55 deletions

View File

@ -1,8 +1,9 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { TransitionMotion, spring, presets } from 'react-motion';
import { FormattedMessage as Message, intlShape } from 'react-intl';
import classNames from 'classnames';
import { FormattedMessage as Message, intlShape } from 'react-intl';
import { requireLocaleFlag } from 'functions';
import LANGS from 'i18n/index.json';
@ -14,6 +15,7 @@ import styles from './languageSwitcher.scss';
import messages from './languageSwitcher.intl.json';
const improveTranslationUrl = 'http://ely.by/erickskrauch/posts/174943';
const itemHeight = 51;
class LanguageSwitcher extends Component {
static displayName = 'LanguageSwitcher';
@ -22,6 +24,7 @@ class LanguageSwitcher extends Component {
onClose: PropTypes.func,
userLang: PropTypes.string,
changeLang: PropTypes.func,
langs: PropTypes.objectOf(PropTypes.object).isRequired,
};
static contextTypes = {
@ -29,21 +32,18 @@ class LanguageSwitcher extends Component {
};
state = {
items: [],
filter: '',
filteredLangs: this.props.langs,
};
static defaultProps = {
onClose() {}
langs: LANGS,
onClose() {},
};
componentWillMount() {
this.setState({items: LANGS});
}
render() {
const {userLang, onClose} = this.props;
const {items} = this.state;
const firstLocale = Object.keys(this.state.filteredLangs)[0] || null;
return (
<div className={styles.languageSwitcher}>
@ -60,26 +60,40 @@ class LanguageSwitcher extends Component {
<input
className={classNames(
formStyles.lightTextField,
formStyles.greenTextField
formStyles.greenTextField,
)}
placeholder={this.context.intl.formatMessage(messages.startTyping)}
onChange={this.onFilterUpdate()}
onChange={this.onFilterUpdate}
onKeyPress={this.onFilterKeyPress()}
autoFocus
/>
<span className={styles.searchIcon} />
</div>
<div className={styles.languagesList}>
{Object.keys(items).map((locale) => (
<li className={classNames(styles.languageItem, {
[styles.activeLanguageItem]: locale === userLang
})} onClick={this.onChangeLang(locale)} key={locale}
>
{this.renderLanguageItem(locale, items[locale])}
</li>
))}
</div>
<TransitionMotion
defaultStyles={this.getItemsWithDefaultStyles()}
styles={this.getItemsWithStyles()}
willLeave={this.willLeave}
willEnter={this.willEnter}
>
{(items) => (
<div className={styles.languagesList}>
{items.map(({key: locale, data: definition, style}) => (
<li
key={locale}
style={style}
className={classNames(styles.languageItem, {
[styles.activeLanguageItem]: locale === userLang,
[styles.firstLanguageItem]: locale === firstLocale,
})}
onClick={this.onChangeLang(locale)}
>
{this.renderLanguageItem(locale, definition)}
</li>
))}
</div>
)}
</TransitionMotion>
<div className={styles.improveTranslates}>
<div className={styles.improveTranslatesIcon} />
@ -93,7 +107,7 @@ class LanguageSwitcher extends Component {
<a href={improveTranslationUrl} target="_blank">
<Message {...messages.improveTranslatesArticleLink} />
</a>
)
),
}} />
</div>
</div>
@ -122,7 +136,7 @@ class LanguageSwitcher extends Component {
return (
<div className={styles.languageFlex}>
<div className={styles.languageIco} style={{
backgroundImage: `url('${requireLocaleFlag(locale)}')`
backgroundImage: `url('${requireLocaleFlag(locale)}')`,
}} />
<div className={styles.languageCaptions}>
<div className={styles.languageName}>
@ -149,28 +163,26 @@ class LanguageSwitcher extends Component {
setTimeout(this.props.onClose, 300);
}
onFilterUpdate() {
return (event) => {
const value = event.target.value.trim().toLowerCase();
let items = LANGS;
if (value.length !== 0) {
items = Object.keys(items).reduce((prev, next) => {
if (items[next].englishName.toLowerCase().search(value) !== -1
|| items[next].name.toLowerCase().search(value) !== -1
) {
prev[next] = items[next];
}
return prev;
}, {});
onFilterUpdate = (event) => {
const filter = event.target.value.trim().toLowerCase();
const { langs } = this.props;
const result = Object.keys(langs).reduce((previous, key) => {
if (langs[key].englishName.toLowerCase().search(filter) === -1
&& langs[key].name.toLowerCase().search(filter) === -1
) {
return previous;
}
this.setState({
items,
filter: value,
});
};
}
previous[key] = langs[key];
return previous;
}, {});
this.setState({
filter,
filteredLangs: result,
});
};
onFilterKeyPress() {
return (event) => {
@ -178,7 +190,7 @@ class LanguageSwitcher extends Component {
return;
}
const locales = Object.keys(this.state.items);
const locales = Object.keys(this.props.langs);
if (locales.length === 0) {
return;
}
@ -186,13 +198,53 @@ class LanguageSwitcher extends Component {
this.changeLang(locales[0]);
};
}
getItemsWithDefaultStyles = () => Object.keys(this.props.langs).reduce((previous, key) => {
return [
...previous,
{
key,
data: this.props.langs[key],
style: {
height: itemHeight,
opacity: 1,
},
},
];
}, {});
getItemsWithStyles = () => Object.keys({...this.state.filteredLangs}).reduce((previous, key) => [
...previous,
{
key,
data: this.props.langs[key],
style: {
height: spring(itemHeight, presets.gentle),
opacity: spring(1, presets.gentle),
},
},
], []);
willEnter() {
return {
height: 0,
opacity: 1,
};
}
willLeave() {
return {
height: spring(0),
opacity: spring(0),
};
}
}
import { connect } from 'react-redux';
import { changeLang } from 'components/user/actions';
export default connect((state) => ({
userLang: state.user.lang
userLang: state.user.lang,
}), {
changeLang
changeLang,
})(LanguageSwitcher);

View File

@ -2,6 +2,12 @@
@import '~components/ui/fonts.scss';
@import '~components/ui/popup/popup.scss';
@mixin hideFooter {
@media (max-height: 455px) {
@content;
}
}
.languageSwitcher {
composes: popupWrapper from 'components/ui/popup/popup.scss';
@ -36,7 +42,8 @@
pointer-events: none; // Иконка чисто декоративная, так что клик должен проходить сквозь неё
}
$languageListBorderStyle: 1px solid #eee;
$languageListBorderColor: #eee;
$languageListBorderStyle: 1px solid $languageListBorderColor;
.languagesList {
flex-grow: 1;
@ -44,28 +51,35 @@ $languageListBorderStyle: 1px solid #eee;
border-top: $languageListBorderStyle;
border-bottom: $languageListBorderStyle;
margin-bottom: 20px;
@include hideFooter {
& {
margin-bottom: 0;
}
}
}
.languageItem {
padding: 10px;
border-top: $languageListBorderStyle;
font-family: $font-family-title;
transition: .25s;
transition: background-color .25s;
cursor: pointer;
overflow: hidden;
&:first-child {
border-top: none;
}
&:hover {
background: $whiteButtonLight;
background-color: $whiteButtonLight;
}
}
.languageFlex {
box-sizing: border-box;
display: flex;
align-items: center;
padding: 10px;
border-top: $languageListBorderStyle;
.firstLanguageItem & {
border-top: none;
}
}
.languageIco {
@ -140,7 +154,7 @@ $languageListBorderStyle: 1px solid #eee;
display: flex;
flex-shrink: 0;
@media screen and (max-height: 455px) {
@include hideFooter {
& {
display: none;
}