mirror of
https://github.com/bellingcat/ukraine-timemap.git
synced 2026-06-13 05:48:36 +03:00
Language menu (#48)
* refactor(Toolbar): change the way z-order of elements is expressed
**Before:**
`.toolbar` was placed over the content of `#toolbar-wrapper`.
**After:**
`.toolbar-panels` are placed behind the content of `#toolbar-wrapper`.
**Reason:**
allows descendants of `.toolbar` to place themselves behind it.
Context
-------
Previously we had the following setup
(`*` means the root of stacking context, `-` means normal child):
* div#toolbar-wrapper { z-index: 10, position: fixed }
* div.toolbar { z-index: 10, position: relative }
- div.toolbar-tabs
- div.toolbar-tab
- div.toolbar-tab
- div.toolbar-tab
* div.toolbar-panels { position: fixed }
* div.map-wrapper { position: fixed }
* div.cover-container { z-index: 501, position: absolute }
That was achivieving what was necessary:
- `#toolbar-wrapper` tree is placed over the `.map-wrapper`,
- `.cover-container` tree is placed over the `#toolbar-wrapper`,
- `.toolbar` tree is placed over `.toolbar-panels`
Problem
-------
The practical problem with it was not being able to make descendants
of `.toolbar` go behind it when sliding away (as they were within
`.toolbar`'s own stacking context).
We needed to add a small language menu which would be positioned next
to the button that opens it, and slide away behind the toolbar like
other toolbar-panels.
Solution
--------
I changed the stacking to be the following:
* div#toolbar-wrapper { z-index: 10, position: fixed }
- div.toolbar
- div.toolbar-tabs
- div.toolbar-tab
- div.toolbar-tab
- div.toolbar-tab
* div.toolbar-panels { z-index: -1, position: fixed }
* div.map-wrapper { position: fixed }
* div.cover-container { z-index: 501, position: absolute }
This explicitly places `.toolbar-panels` behind everything else within
the stacking context created by `#toolbar-wrapper`.
Outcome
-------
Now `.language-menu` can easily be added as:
* div#toolbar-wrapper { z-index: 10, position: fixed }
- div.toolbar
- div.toolbar-tabs
- div.toolbar-tab
- div.toolbar-tab { position: relative }
* div.language-menu { z-index: -1, position: absolute }
- div.toolbar-tab
* div.toolbar-panels { z-index: -1, position: fixed }
* feat(Toolbar): add language menu (button and a sliding out panel)
This commit is contained in:
@@ -94,7 +94,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"en": {
|
"en": {
|
||||||
|
"language_label": "Language",
|
||||||
"language_short": "Eng",
|
"language_short": "Eng",
|
||||||
|
"language_long": "English",
|
||||||
"tiles": {
|
"tiles": {
|
||||||
"default": "Map",
|
"default": "Map",
|
||||||
"satellite": "Sat"
|
"satellite": "Sat"
|
||||||
@@ -212,9 +214,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ru": {
|
"ru": {
|
||||||
"language_short": "Рус"
|
"language_label": "Язык",
|
||||||
|
"language_short": "Рус",
|
||||||
|
"language_long": "Русский"
|
||||||
},
|
},
|
||||||
"uk": {
|
"uk": {
|
||||||
"language_short": "Укр"
|
"language_label": "Мова",
|
||||||
|
"language_short": "Укр",
|
||||||
|
"language_long": "Українська"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,18 +24,25 @@ import {
|
|||||||
import { ToolbarButton } from "./controls/atoms/ToolbarButton";
|
import { ToolbarButton } from "./controls/atoms/ToolbarButton";
|
||||||
import { FullscreenToggle } from "./controls/FullScreenToggle";
|
import { FullscreenToggle } from "./controls/FullScreenToggle";
|
||||||
import { LanguageSwitch } from "./controls/LanguageSwitch";
|
import { LanguageSwitch } from "./controls/LanguageSwitch";
|
||||||
|
import { ToolbarLanguageMenu } from "./controls/LanguageMenu";
|
||||||
import DownloadPanel from "./controls/DownloadPanel";
|
import DownloadPanel from "./controls/DownloadPanel";
|
||||||
|
|
||||||
class Toolbar extends React.Component {
|
class Toolbar extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.onSelectFilter = this.onSelectFilter.bind(this);
|
this.onSelectFilter = this.onSelectFilter.bind(this);
|
||||||
this.state = { _selected: -1 };
|
this.state = { _selected: -1, isLanguageMenuActive: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
selectTab(selected) {
|
selectTab(selected) {
|
||||||
const _selected = this.state._selected === selected ? -1 : selected;
|
const _selected = this.state._selected === selected ? -1 : selected;
|
||||||
this.setState({ _selected });
|
this.setState({ _selected, isLanguageMenuActive: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLanguageMenuActive(isActive) {
|
||||||
|
isActive
|
||||||
|
? this.setState({ isLanguageMenuActive: true, _selected: -1 })
|
||||||
|
: this.setState({ isLanguageMenuActive: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
onSelectFilter(key, matchingKeys) {
|
onSelectFilter(key, matchingKeys) {
|
||||||
@@ -210,6 +217,17 @@ class Toolbar extends React.Component {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
renderToolbarLanguageTab() {
|
||||||
|
return (
|
||||||
|
<ToolbarLanguageMenu
|
||||||
|
isActive={this.state.isLanguageMenuActive}
|
||||||
|
setIsActive={(isActive) => this.setIsLanguageMenuActive(isActive)}
|
||||||
|
language={this.props.language}
|
||||||
|
languages={this.props.languages}
|
||||||
|
setLanguage={this.props.actions.toggleLanguage}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderToolbarCategoryTabs(idxs) {
|
renderToolbarCategoryTabs(idxs) {
|
||||||
const { categories: panelCategories } = this.props.toolbarCopy.panels;
|
const { categories: panelCategories } = this.props.toolbarCopy.panels;
|
||||||
@@ -304,6 +322,9 @@ class Toolbar extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
<div className="toolbar-tabs">
|
<div className="toolbar-tabs">
|
||||||
<TabList>
|
<TabList>
|
||||||
|
{this.props.languages?.length > 1
|
||||||
|
? this.renderToolbarLanguageTab()
|
||||||
|
: null}
|
||||||
{narrativesExist
|
{narrativesExist
|
||||||
? this.renderToolbarTab(
|
? this.renderToolbarTab(
|
||||||
narrativesIdx,
|
narrativesIdx,
|
||||||
|
|||||||
59
src/components/controls/LanguageMenu.js
Normal file
59
src/components/controls/LanguageMenu.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { ToolbarButton } from "./atoms/ToolbarButton";
|
||||||
|
import copy from "../../common/data/copy.json";
|
||||||
|
|
||||||
|
export function LanguageMenu({
|
||||||
|
isActive,
|
||||||
|
language: currentLanguage,
|
||||||
|
languages,
|
||||||
|
setLanguage,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
isActive
|
||||||
|
? "toolbar-panel language-menu active"
|
||||||
|
: "toolbar-panel language-menu"
|
||||||
|
}
|
||||||
|
children={languages.map((language) => (
|
||||||
|
<div
|
||||||
|
key={language}
|
||||||
|
onClick={() => setLanguage(language)}
|
||||||
|
className={
|
||||||
|
language !== currentLanguage
|
||||||
|
? "language-option"
|
||||||
|
: "language-option selected"
|
||||||
|
}
|
||||||
|
children={copy[language].language_long}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ToolbarLanguageMenu({
|
||||||
|
isActive,
|
||||||
|
setIsActive,
|
||||||
|
language,
|
||||||
|
languages,
|
||||||
|
setLanguage,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="toolbar-menu">
|
||||||
|
<ToolbarButton
|
||||||
|
isActive={isActive}
|
||||||
|
onClick={() => setIsActive(!isActive)}
|
||||||
|
iconKey="translate"
|
||||||
|
label={copy[language].language_label}
|
||||||
|
/>
|
||||||
|
<LanguageMenu
|
||||||
|
isActive={isActive}
|
||||||
|
language={language}
|
||||||
|
languages={languages}
|
||||||
|
setLanguage={(language) => {
|
||||||
|
setIsActive(false);
|
||||||
|
setLanguage(language);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
23
src/scss/languagemenu.scss
Normal file
23
src/scss/languagemenu.scss
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
.language-menu {
|
||||||
|
color: $midwhite;
|
||||||
|
font-size: 1.25em;
|
||||||
|
.language-option:not(:first-child) {
|
||||||
|
border-top: solid 1px $midwhite;
|
||||||
|
}
|
||||||
|
.language-option {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: 0.2s ease;
|
||||||
|
padding: 1em 1em;
|
||||||
|
background-color: $darkgrey;
|
||||||
|
&.selected {
|
||||||
|
background-color: $black;
|
||||||
|
color: $offwhite;
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
background-color: $midgrey;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
color: $offwhite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,3 +15,4 @@
|
|||||||
@import "search";
|
@import "search";
|
||||||
@import "satelliteoverlaytoggle";
|
@import "satelliteoverlaytoggle";
|
||||||
@import "languageswitch";
|
@import "languageswitch";
|
||||||
|
@import "languagemenu";
|
||||||
|
|||||||
@@ -26,7 +26,6 @@
|
|||||||
font-size: $normal;
|
font-size: $normal;
|
||||||
font-weight: 100;
|
font-weight: 100;
|
||||||
transition: 0.2s ease;
|
transition: 0.2s ease;
|
||||||
z-index: $header;
|
|
||||||
|
|
||||||
button {
|
button {
|
||||||
background: #222222;
|
background: #222222;
|
||||||
@@ -277,6 +276,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.toolbar-panels {
|
.toolbar-panels {
|
||||||
|
z-index: -1;
|
||||||
width: 440px;
|
width: 440px;
|
||||||
top: 15px;
|
top: 15px;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@@ -522,6 +522,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Toolbar-menu consists of a toolbar-button and a panel.
|
||||||
|
// Panel is positioned next to the button.
|
||||||
|
// Panel slides behind the toolbar.
|
||||||
|
.toolbar-menu {
|
||||||
|
position: relative;
|
||||||
|
.toolbar-panel {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: -1;
|
||||||
|
visibility: hidden;
|
||||||
|
&.active {
|
||||||
|
left: 100%;
|
||||||
|
right: auto;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
transition: 0.2s ease;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.search-content {
|
.search-content {
|
||||||
.item {
|
.item {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|||||||
Reference in New Issue
Block a user