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

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

View File

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