Skip to content

Commit

Permalink
feat(story-tags): add story filter bar (#344)
Browse files Browse the repository at this point in the history
* feat(story-tags): add story filter bar

* feat(story-tags): translate tags
  • Loading branch information
pwambach committed May 20, 2020
1 parent b602a14 commit 0026709
Show file tree
Hide file tree
Showing 15 changed files with 348 additions and 17 deletions.
21 changes: 20 additions & 1 deletion i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,24 @@
"closeStory": "Geschichte schließen",
"removeCompare": "Vergleich entfernen",
"dataInfo": "Dateninformation",
"storiesSelected": "{numberSelected, number} {numberSelected, plural, one {Geschichte} other {Geschichten}} ausgewählt"
"storiesSelected": "{numberSelected, number} {numberSelected, plural, one {Geschichte} other {Geschichten}} ausgewählt",
"resetFilters": "Filter löschen",
"tags.nature": "Natur",
"tags.biomass": "Biomasse",
"tags.university": "Universität",
"tags.school": "Schule",
"tags.ocean": "Ozean",
"tags.water": "Wasser",
"tags.weather": "Wetter",
"tags.heating": "Erwärmung",
"tags.satellite": "Satellit",
"tags.temperature": "Temperatur",
"tags.seasurface": "Meeresoverfläche",
"tags.earth": "Erde",
"tags.wind": "Wind",
"tags.planet": "Planet",
"tags.landcover": "Landfläche",
"tags.aerosol": "Aerosol",
"tags.globalwarming": "Globale Erwärmung",
"tags.co2": "CO2"
}
21 changes: 20 additions & 1 deletion i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,24 @@
"closeStory": "Close story",
"removeCompare": "Remove Compare",
"dataInfo": "Data Information",
"storiesSelected": "{numberSelected, number} {numberSelected, plural, one {Story} other {Stories}} selected"
"storiesSelected": "{numberSelected, number} {numberSelected, plural, one {Story} other {Stories}} selected",
"resetFilters": "Reset Filters",
"tags.nature": "Nature",
"tags.biomass": "Biomass",
"tags.university": "University",
"tags.school": "School",
"tags.ocean": "Ocean",
"tags.water": "Water",
"tags.weather": "Weather",
"tags.heating": "Heating",
"tags.satellite": "Satellite",
"tags.temperature": "Temperature",
"tags.seasurface": "Sea Surface",
"tags.earth": "Earth",
"tags.wind": "Wind",
"tags.planet": "Planet",
"tags.landcover": "Land cover",
"tags.aerosol": "Aerosol",
"tags.globalwarming": "Global Warming",
"tags.co2": "CO2"
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import React, {FunctionComponent} from 'react';

export const DownloadIcon: FunctionComponent = () => (
export const ArrowLeftIcon: FunctionComponent = () => (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M15 9H19L12 16L5 9H9V3H15V9ZM5 20V18H19V20H5Z"
d="M15.41 16.59L10.83 12L15.41 7.41L14 6L8 12L14 18L15.41 16.59Z"
fill="currentColor"
/>
</svg>
);
15 changes: 15 additions & 0 deletions src/scripts/components/icons/arrow-right-icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React, {FunctionComponent} from 'react';

export const ArrowRightIcon: FunctionComponent = () => (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M10.0001 6L8.59009 7.41L13.1701 12L8.59009 16.59L10.0001 18L16.0001 12L10.0001 6Z"
fill="currentColor"
/>
</svg>
);
15 changes: 15 additions & 0 deletions src/scripts/components/icons/check-icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React, {FunctionComponent} from 'react';

export const CheckIcon: FunctionComponent = () => (
<svg
width="18"
height="18"
viewBox="0 0 18 12"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M6.75 9.12738L3.6225 5.99988L2.5575 7.05738L6.75 11.2499L15.75 2.24988L14.6925 1.19238L6.75 9.12738Z"
fill="#00AE9D"
/>
</svg>
);
2 changes: 2 additions & 0 deletions src/scripts/components/stories-selector/stories-selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, {FunctionComponent} from 'react';
import {useIntl} from 'react-intl';

import StoryList from '../story-list/story-list';
import StoryFilter from '../story-filter/story-filter';
import Header from '../header/header';
import Share from '../share/share';

Expand All @@ -20,6 +21,7 @@ const StoriesSelector: FunctionComponent = () => {
title={intl.formatMessage({id: 'storyMode'})}>
<Share />
</Header>
<StoryFilter />
<StoryList mode={StoryMode.Stories} />
</div>
);
Expand Down
75 changes: 75 additions & 0 deletions src/scripts/components/story-filter/story-filter.styl
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
@require '../../../variables.styl'

.storyFilter
display: flex
flex-direction: row
align-items: center
padding: emCalc(24px) emCalc(26px) 0 emCalc(26px)

.arrowIcon
display: flex
justify-content: center
align-items: center
padding: 0 0.25em
width: emCalc(48px)
color: $textDefault
cursor: pointer

&:hover
svg
color: white

.tagScrollerOuter
position: relative
flex-grow: 1
overflow-x: hidden

&:after
position: absolute
top: 0
left: 0
width: 100%
height: 100%
background: linear-gradient(to right, $black 0%, transparent 1%, transparent 99%, $black 100%)
content: ''
pointer-events: none

.tagScrollerInner
display: flex
flex-direction: row

.tag
display: flex
flex-direction: row
align-items: center
margin-right: emCalc(24px)
min-height: emCalc(24px)
color: $textDefault
text-transform: uppercase
white-space: nowrap
font-size: 0.9em
cursor: pointer

svg
margin-right: emCalc(4px)
width: emCalc(24px)
height: emCalc(24px)
transform: translateY(-1px)

.selected
color: $textColor

.resetButton
flex-grow: 0
padding-top: emCalc(2px)
outline: none
border: none
background: transparent
color: $textColor
white-space: nowrap
font-size: 1rem
cursor: pointer

&[disabled]
color: $darkGrey4
cursor: default
124 changes: 124 additions & 0 deletions src/scripts/components/story-filter/story-filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import React, {FunctionComponent, useState, useEffect, useRef} from 'react';
import {FormattedMessage} from 'react-intl';
import {useSelector, useDispatch} from 'react-redux';
import cx from 'classnames';
import {ArrowLeftIcon} from '../icons/arrow-left-icon';
import {ArrowRightIcon} from '../icons/arrow-right-icon';
import {CheckIcon} from '../icons/check-icon';

import {storyListSelector} from '../../selectors/story/list';
import {selectedTagsSelector} from '../../selectors/story/selected-tags';
import setSelectedStoryTags from '../../actions/set-selected-story-tags';

import styles from './story-filter.styl';

let translateValue = 0;
const scrollSpeed = 4; // pixels per frame

const StoryFilter: FunctionComponent = () => {
const dispatch = useDispatch();
const stories = useSelector(storyListSelector);
const selectedTags = useSelector(selectedTagsSelector);
const innerRef = useRef<HTMLDivElement>(null);
const [scrollLeft, setScrollLeft] = useState(false);
const [scrollRight, setScrollRight] = useState(false);
const maxScroll =
(innerRef.current?.scrollWidth || 0) - (innerRef.current?.offsetWidth || 0);

const allTags: string[] = stories
.map(({tags}) => tags)
.filter(Boolean)
// @ts-ignore Array.flat()
.flat();
const uniqTags = Array.from(new Set(allTags));
const isSelected = (tag: string) => selectedTags.includes(tag);
const getTagClasses = (tag: string) =>
cx(styles.tag, isSelected(tag) && styles.selected);

const toggleTag = (tag: string) => {
const newTags = selectedTags.includes(tag)
? selectedTags.filter(oldTag => oldTag !== tag)
: selectedTags.concat([tag]);

dispatch(setSelectedStoryTags(newTags));
};
const resetTags = () => dispatch(setSelectedStoryTags([]));

// handle left side scrolling
useEffect(() => {
if (!scrollLeft) {
return () => {};
}

const raf = {id: 0}; // keep reference to requestAniationFrame id
const loop = () => {
if (innerRef.current && translateValue < 0) {
translateValue += scrollSpeed;
innerRef.current.style.transform = `translateX(${translateValue}px)`;
raf.id = requestAnimationFrame(loop);
}
};

loop();

return () => cancelAnimationFrame(raf.id);
}, [scrollLeft]);

// handle right side scrolling
useEffect(() => {
if (!scrollRight) {
return () => {};
}

const raf = {id: 0}; // keep reference to requestAniationFrame id
const loop = () => {
if (innerRef.current && translateValue > maxScroll * -1) {
translateValue -= scrollSpeed;
innerRef.current.style.transform = `translateX(${translateValue}px)`;
raf.id = requestAnimationFrame(loop);
}
};

loop();

return () => cancelAnimationFrame(raf.id);
}, [scrollRight, maxScroll]);

return (
<div className={styles.storyFilter}>
<div
className={styles.arrowIcon}
onMouseEnter={() => setScrollLeft(true)}
onMouseLeave={() => setScrollLeft(false)}>
<ArrowLeftIcon />
</div>
<div className={styles.tagScrollerOuter}>
<div className={styles.tagScrollerInner} ref={innerRef}>
{uniqTags.map(tag => (
<div
className={getTagClasses(tag)}
onClick={() => toggleTag(tag)}
key={tag}>
{isSelected(tag) && <CheckIcon />}
<FormattedMessage id={`tags.${tag}`} />
</div>
))}
</div>
</div>
<div
className={styles.arrowIcon}
onMouseEnter={() => setScrollRight(true)}
onMouseLeave={() => setScrollRight(false)}>
<ArrowRightIcon />
</div>
<button
disabled={selectedTags.length === 0}
className={styles.resetButton}
onClick={resetTags}>
<FormattedMessage id="resetFilters" />
</button>
</div>
);
};

export default StoryFilter;
3 changes: 1 addition & 2 deletions src/scripts/components/story-list-item/story-list-item.styl
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
flex-direction: column
justify-content: flex-end
margin: 5px
height: 100%
height: 300px
border: 2px solid $darkGrey1
border-radius: 4px
background-size: cover
Expand Down Expand Up @@ -34,6 +32,7 @@
.imageInfo
display: flex
flex-direction: column
margin-top: emCalc(130px)
padding: 16px
height: 40%
background-color: $darkGrey1
Expand Down
4 changes: 4 additions & 0 deletions src/scripts/components/story-tags/story-tags.styl
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@

.tags
display: flex
flex-wrap: wrap

.tag
margin-top: 0.5em
margin-right: 0.5em
padding: 0.2em 1em
border: 1px solid $darkGrey4
border-radius: 12px
color: $textDefault
text-transform: uppercase
font-size: 0.8em
cursor: pointer

&:hover
background: $darkGrey2
Expand Down
3 changes: 2 additions & 1 deletion src/scripts/components/story-tags/story-tags.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, {FunctionComponent} from 'react';
import {FormattedMessage} from 'react-intl';
import {useDispatch} from 'react-redux';
import cx from 'classnames';

Expand Down Expand Up @@ -34,7 +35,7 @@ const StoryTags: FunctionComponent<Props> = ({tags, selected}) => {
event.stopPropagation();
toggleTag(tag);
}}>
{tag}
<FormattedMessage id={`tags.${tag}`} />
</span>
))}
</div>
Expand Down
Loading

0 comments on commit 0026709

Please sign in to comment.