Refactor and restyle UI (#645)

Tweaks to visual output and code organisation related to styles of:

- sidebar (category grid, data view/edit, section headers and action buttons)
- header/menu
- map controls (search box)
- layout (sidebar, menu, header sizes)
This commit is contained in:
Maciej Ziarkowski 2020-12-27 23:51:32 +00:00 committed by GitHub
parent 9219f76c12
commit 3a0c852088
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 408 additions and 288 deletions

View File

@ -1,57 +1,37 @@
/**
* Data categories
*/
.data-category-list {
padding: 0px 0 10px 9px;
list-style: none;
margin: 0;
text-align: left;
max-width: 480px;
}
.navbar .data-category-list {
padding: 0px 0 0px 15px;
}
.data-category-list li {
position: relative;
display: inline-block;
vertical-align: bottom;
width: 110px;
height: 110px;
margin: 2px;
box-shadow: 0 0 2px 3px #ffffff;
transition: box-shadow 0.2s;
}
.navbar .data-category-list li {
width: 105px;
height: 105px;
}
.data-category-list li:nth-child(4n) {
margin-right: 0;
}
.data-category-list li:hover {
box-shadow: 0 0 2px 3px #00ffff;
z-index: 1;
}
.data-category-list a {
color: #222;
text-decoration: none;
}
.data-category-list .category-link {
display: flex;
justify-content: center;
align-items: center;
padding: 0.1em;
margin-bottom: 10px;
padding: 2px;
width: 100%;
height: 100%;
}
.data-category-list .category-link:hover,
.data-category-list .category-link:active,
.data-category-list .category-link:focus {
color: #222;
height: 340px;
max-height: 340px;
max-width: 470px;
display: flex;
flex-direction: row;
flex-wrap: wrap;
list-style: none;
}
.data-category-list .category {
text-align: center;
font-size: 1em;
margin: 0 0 0.5em;
.data-category-list li {
box-sizing: border-box;
flex-basis: 7rem;
flex-grow: 1;
max-height: 7em;
margin: 2px;
}
@media(min-width:734px) {
.data-category-list li {
flex-basis: 24%;
height: 6.75rem;
flex-grow: 0;
max-height: 6.75rem;
}
.data-category-list li:hover {
box-shadow: 0 0 2px 3px #00ffff;
z-index: 1;
}
}

View File

@ -1,5 +1,7 @@
import React from 'react';
import { NavLink } from 'react-router-dom';
import { CategoryLink } from './category-link';
import { ListWrapper } from '../components/list-wrapper';
import './categories.css';
@ -9,8 +11,8 @@ interface CategoriesProps {
}
const Categories: React.FC<CategoriesProps> = (props) => (
<ol className="data-category-list">
<Category
<ListWrapper className='data-category-list'>
<CategoryLink
title="Location"
slug="location"
help="https://pages.colouring.london/location"
@ -18,7 +20,7 @@ const Categories: React.FC<CategoriesProps> = (props) => (
mode={props.mode}
building_id={props.building_id}
/>
<Category
<CategoryLink
title="Current Use"
slug="use"
help="https://pages.colouring.london/use"
@ -26,7 +28,7 @@ const Categories: React.FC<CategoriesProps> = (props) => (
mode={props.mode}
building_id={props.building_id}
/>
<Category
<CategoryLink
title="Type"
slug="type"
help="https://pages.colouring.london/buildingtypology"
@ -34,7 +36,7 @@ const Categories: React.FC<CategoriesProps> = (props) => (
mode={props.mode}
building_id={props.building_id}
/>
<Category
<CategoryLink
title="Age"
slug="age"
help="https://pages.colouring.london/age"
@ -42,7 +44,7 @@ const Categories: React.FC<CategoriesProps> = (props) => (
mode={props.mode}
building_id={props.building_id}
/>
<Category
<CategoryLink
title="Size &amp; Shape"
slug="size"
help="https://pages.colouring.london/shapeandsize"
@ -50,7 +52,7 @@ const Categories: React.FC<CategoriesProps> = (props) => (
mode={props.mode}
building_id={props.building_id}
/>
<Category
<CategoryLink
title="Construction"
slug="construction"
help="https://pages.colouring.london/construction"
@ -58,7 +60,7 @@ const Categories: React.FC<CategoriesProps> = (props) => (
mode={props.mode}
building_id={props.building_id}
/>
<Category
<CategoryLink
title="Streetscape"
slug="streetscape"
help="https://pages.colouring.london/greenery"
@ -66,7 +68,7 @@ const Categories: React.FC<CategoriesProps> = (props) => (
mode={props.mode}
building_id={props.building_id}
/>
<Category
<CategoryLink
title="Team"
slug="team"
help="https://pages.colouring.london/team"
@ -74,7 +76,7 @@ const Categories: React.FC<CategoriesProps> = (props) => (
mode={props.mode}
building_id={props.building_id}
/>
<Category
<CategoryLink
title="Planning"
slug="planning"
help="https://pages.colouring.london/planning"
@ -82,7 +84,7 @@ const Categories: React.FC<CategoriesProps> = (props) => (
mode={props.mode}
building_id={props.building_id}
/>
<Category
<CategoryLink
title="Sustainability"
slug="sustainability"
help="https://pages.colouring.london/sustainability"
@ -90,7 +92,7 @@ const Categories: React.FC<CategoriesProps> = (props) => (
mode={props.mode}
building_id={props.building_id}
/>
<Category
<CategoryLink
title="Dynamics"
slug="dynamics"
help="https://pages.colouring.london/dynamics"
@ -98,7 +100,7 @@ const Categories: React.FC<CategoriesProps> = (props) => (
mode={props.mode}
building_id={props.building_id}
/>
<Category
<CategoryLink
title="Community"
slug="community"
help="https://pages.colouring.london/community"
@ -106,38 +108,7 @@ const Categories: React.FC<CategoriesProps> = (props) => (
mode={props.mode}
building_id={props.building_id}
/>
</ol>
</ListWrapper>
);
interface CategoryProps {
mode: 'view' | 'edit' | 'multi-edit';
building_id?: number;
slug: string;
title: string;
help: string;
inactive: boolean;
}
const Category: React.FC<CategoryProps> = (props) => {
let categoryLink = `/${props.mode}/${props.slug}`;
if (props.building_id != undefined) categoryLink += `/${props.building_id}`;
return (
<li className={`category-block ${props.slug} background-${props.slug}`}>
<NavLink
className="category-link"
to={categoryLink}
title={
(props.inactive)?
'Coming soon… Click more info for details.'
: 'View/Edit Map'
}>
<div className="category-title-container">
<h3 className="category">{props.title}</h3>
</div>
</NavLink>
</li>
);
};
export default Categories;

View File

@ -0,0 +1,22 @@
.category-link {
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
/* padding: 0.1em; */
width: 100%;
height: 100%;
}
a.category-link {
color: #222;
text-decoration: none;
}
.category-title {
display: block;
text-align: center;
font-size: 1em;
margin: 0;
}

View File

@ -0,0 +1,33 @@
import React from 'react';
import { NavLink } from 'react-router-dom';
import './category-link.css';
interface CategoryLinkProps {
mode: 'view' | 'edit' | 'multi-edit';
building_id?: number;
slug: string;
title: string;
help: string;
inactive: boolean;
}
const CategoryLink: React.FC<CategoryLinkProps> = (props) => {
let categoryLink = `/${props.mode}/${props.slug}`;
if (props.building_id != undefined) categoryLink += `/${props.building_id}`;
return (
<NavLink
className={`category-link background-${props.slug}`}
to={categoryLink}
title={
(props.inactive)?
'Coming soon… Click more info for details.'
: 'View/Edit Map'
}>
<h3 className="category-title">{props.title}</h3>
</NavLink>
);
};
export { CategoryLink };

View File

@ -0,0 +1,53 @@
/**
* Data section headers
*/
.section-header {
padding: 0 0.5em;
display: flex;
flex-direction: row;
justify-content: space-between;
text-decoration: none;
color: #222;
z-index: 1000;
position: sticky;
top: 0;
}
.section-header .h2 {
display: inline-block;
flex-basis: 150px;
flex-shrink: 0;
flex-grow: 1;
margin: 0.75rem 0 0.5em 0.1em;
font-size: 1.5rem;
padding: 0;
}
.section-header .h2 a.icon-button.back {
line-height: 1em;
vertical-align: top;
margin-right: 0.3em;
}
.section-header .section-header-actions {
display: inline-block;
flex-basis: 200px;
display: flex;
flex-flow: row wrap;
align-items: center;
justify-content: flex-end;
margin-left: -5px;
}
.section-header-actions .icon-button {
display: inline-block;
font-size: 0.8333rem;
margin-left: 10px;
}
.section-header-actions .icon-button:hover svg,
.section-header-actions .icon-button:active svg {
color: rgb(11, 225, 225);
}

View File

@ -1,15 +1,27 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { BackIcon } from '../components/icons';
import './container-header.css';
interface ContainerHeaderProps {
cat?: string;
backLink: string;
backLink?: string;
title: string;
}
const ContainerHeader: React.FunctionComponent<ContainerHeaderProps> = (props) => (
<header className={`section-header view ${props.cat ? props.cat : ''} ${props.cat ? `background-${props.cat}` : ''}`}>
<h2 className="h2">{props.title}</h2>
<nav className="icon-buttons">
<h2 className="h2">
{props.backLink &&
<Link className="icon-button back" to={props.backLink}>
<BackIcon />
</Link>
}
{props.title}
</h2>
<nav className="section-header-actions">
{props.children}
</nav>
</header>

View File

@ -14,4 +14,5 @@
.data-entry-group-body {
padding-left: 1rem;
padding-bottom: 1rem;
}

View File

@ -0,0 +1,43 @@
.data-title {
margin-top: 1em;
margin-bottom: 0.2em;
}
.data-title * {
font-size: 0.8333rem;
}
.data-title-text {
float: left;
}
.data-title-actions {
float: right;
}
.data-title:after {
content:"";
display: table;
clear: both;
}
.data-title label {
margin: 0;
padding: 0;
display: inline-block;
vertical-align: middle;
}
.data-title-actions .icon-button {
font-size: 0.8333rem;
margin-left: 0.7em;
vertical-align: baseline;
}
.data-title-actions .icon-button input[type=checkbox] {
margin: 0;
margin-left: 0.3em;
padding: 0;
vertical-align: middle;
}

View File

@ -3,6 +3,7 @@ import React from 'react';
import Tooltip from '../../components/tooltip';
import { CopyProps } from '../data-containers/category-view-props';
import './data-title.css';
interface DataTitleProps {
title: string;
@ -30,22 +31,24 @@ interface DataTitleCopyableProps {
const DataTitleCopyable: React.FunctionComponent<DataTitleCopyableProps> = (props) => {
return (
<div className="data-title">
{ props.tooltip? <Tooltip text={ props.tooltip } /> : null }
{ (props.copy && props.copy.copying && props.slug && !props.disabled)?
<div className="icon-buttons">
<label className="icon-button copy">
Copy
<input
type="checkbox"
checked={props.copy.copyingKey(props.slug)}
onChange={() => props.copy.toggleCopyAttribute(props.slug)}/>
</label>
</div>
: null
}
<label htmlFor={props.slug}>
{ props.title }
</label>
<div className="data-title-text">
<label htmlFor={props.slug}>
{ props.title }
</label>
</div>
<div className="data-title-actions icon-buttons">
{ (props.copy && props.copy.copying && props.slug && !props.disabled)?
<label className="icon-button copy">
Copy
<input
type="checkbox"
checked={props.copy.copyingKey(props.slug)}
onChange={() => props.copy.toggleCopyAttribute(props.slug)}/>
</label>
: null
}
{ props.tooltip? <Tooltip text={ props.tooltip } /> : null }
</div>
</div>
);
};

View File

@ -1,4 +1,5 @@
.verification-container {
margin-top: 0.5em;
font-size: 0.83333rem;
margin-bottom: 1em;
}

View File

@ -262,7 +262,6 @@ const withCopyEdit = (WrappedComponent: React.ComponentType<CategoryViewProps>)
className="data-section">
<ContainerHeader
cat={this.props.cat}
backLink={headerBackLink}
title={this.props.title}
>
{

View File

@ -6,160 +6,94 @@
top: 75px;
left: 0;
bottom: 0;
width: 95%;
width: calc(100% - 40px);
border-right: 1px solid #000;
z-index: 1001;
transition: transform 0.3s;
transform: translateX(0);
width: 65%;
min-width: 280px;
max-width: 95%;
max-width: calc(100vw - 40px);
border-right: 1px solid #000;
}
.info-container.offscreen {
transform: translateX(-100%);
}
.info-container-collapse {
position: absolute;
right: -32px;
top: 2rem;
left: 100%;
top: 4rem;
width: 2rem;
padding: 2.5rem 0rem;
border-radius: 0 .25rem .25rem 0;
}
@media (min-width: 990px){
@media (min-width: 510px) {
.info-container {
width: 480px; /* to match .main-header menu width */
max-width: 470px;
}
}
@media (min-width: 734px){
.info-container {
width: 470px;
}
}
@media (min-width: 990px){
.info-container.offscreen {
transform: translateX(0);
}
.info-container-collapse {
display: none;
}
}
.info-container-inner {
overflow-y: auto;
width: 100%;
height: 100%;
padding: 0 0 2em;
overflow-y: scroll;
background: #fff;
}
/**
* Data section headers
*/
.section-header {
display: block;
position: relative;
clear: both;
text-decoration: none;
color: #222;
padding: 0.75rem 0.25rem 0.5rem 0.75rem;
z-index: 1000;
position: sticky;
top: 0;
}
.section-header h2,
.section-header .icon-buttons {
display: inline-block;
}
.section-header .h2 {
font-size: 1.5rem;
margin: 0.25rem 0;
}
.section-header .icon-buttons {
position: absolute;
right: 0;
top: 7px;
padding: 0.7rem 0.5rem 0.5rem 0;
}
.icon-buttons .icon-button {
margin-left: 7px;
}
/**
* Icon buttons
*/
.icon-button {
.icon-button {
padding: 0;
display: inline-block;
background-color: transparent;
font-size: 0.8333rem;
outline: none;
border: none;
color: #222;
vertical-align: top;
}
.icon-button:hover {
color: #222;
text-decoration: none;
cursor: pointer;
}
.icon-button.tooltip-hint {
padding: 0;
}
.icon-button svg {
margin-left: 0.3em;
background-color: transparent;
transition: background-color color 0.2s;
display: inline-block;
font-size: 1.2em;
color: #222;
margin-top: 2px;
width: 30px;
height: 30px;
padding: 6px;
border-radius: 15px;
margin: 0 0.05rem;
display: inline-block;
vertical-align: middle;
}
.svg-inline--fa.fa-w-11,
.svg-inline--fa.fa-w-16,
.svg-inline--fa.fa-w-18,
.svg-inline--fa.fa-w-8 {
width: 30px;
}
.icon-button.edit:active svg,
.icon-button.edit:hover svg,
.icon-button.view:active svg,
.icon-button.view:hover svg {
color: rgb(11, 225, 225);
}
.icon-button.help,
.icon-button.copy,
.icon-button.history {
margin-top: 4px;
}
.data-section label .icon-buttons .icon-button.copy {
margin-top: 0px;
}
.icon-button.copy:hover,
.icon-button.help:hover {
color: rgb(0, 81, 255)
}
.icon-button.tooltip-hint.active svg,
.icon-button.tooltip-hint:hover svg {
color: rgb(255, 11, 245);
}
.icon-button.close-edit svg {
margin-top: -1px;
}
.icon-button.close-edit:hover svg {
color: rgb(255, 72, 11)
}
.icon-button.save:hover svg {
color: rgb(11, 225, 72);
}
.data-title .icon-buttons {
float: right;
}
/* Back button */
.icon-button.back,
.icon-button.back:hover {
padding: 5px 1px;
background-color: transparent;
}
.icon-button.back:hover svg {
background-color: transparent;
}
/**
* Data list sections
@ -203,8 +137,6 @@
}
.data-list dt,
.data-section label {
display: block;
margin: 0.5em 0 0;
font-size: 0.8333rem;
font-weight: normal;
color: #555;

View File

@ -0,0 +1,23 @@
import React from 'react';
interface ListWrapperProps {
listType?: 'ul' | 'ol';
className?: string;
elementClassName?: string;
}
export const ListWrapper: React.FC<ListWrapperProps> = (props) => {
const ListTag=props.listType ?? 'ol';
return (
<ListTag className={props.className}>
{
React.Children.map(props.children, (elem) => (
<li className={props.elementClassName}>
{elem}
</li>
))
}
</ListTag>);
};

View File

@ -4,24 +4,28 @@
.tooltip-wrap {
float: right;
position: relative;
top: -3px;
right: -4px;
}
.tooltip-wrap .icon-button svg {
margin-top: -2px;
margin: 0;
padding: 0;
padding-right: 0.3em;
}
.tooltip {
text-transform: none;
text-align: left;
width: 11em;
right: 0;
top: 27px;
top: 100%;
opacity: 1;
}
.tooltip .arrow {
right: 7px;
right: 0.4em;
}
.tooltip a {
color: rgb(255, 11, 245);
}
.icon-button.tooltip-hint.active svg,
.icon-button.tooltip-hint:hover svg {
color: rgb(255, 11, 245);
}

View File

@ -6,13 +6,18 @@
position: absolute;
top: 0;
left: 0;
height: 76px;
height: 76px; /* sync with map-container positioning */
width: 100%;
min-width: 320px; /* to avoid logo line wrap*/
text-decoration: none;
}
@media (min-width: 990px){
.main-header {
width: 480px; /* to match .info-container menu width */
width: 470px;
}
.nav-header {
border-right: 1px solid #000;
}
}
.main-header.navbar {
@ -25,7 +30,6 @@
width: 100%;
padding: 12px;
background: #fff;
border-right: 1px solid #000;
}
.nav-header a {
padding: 0;
@ -51,7 +55,11 @@
transition: transform 0.3s;
transform: translateY(0);
background: #fff;
border-right: 1px solid #000;
}
@media (min-width: 990px){
.navbar-collapse {
border-right: 1px solid #000;
}
}
.main-header .navbar-collapse > ul:last-child {
padding-bottom: 5rem;

View File

@ -1,13 +1,15 @@
.map-container {
position: absolute;
top: 0;
top: 76px; /* sync with header positioning */
bottom: 0;
left: 0;
right: 0;
min-width: 320px;
}
@media (min-width: 990px) {
.map-container {
left: 480px;
top: 0;
left: 470px;
}
}
.leaflet-container {
@ -32,6 +34,7 @@
left: 0.5rem;
z-index: 1000;
padding: 0.5rem 0.75rem;
width: 250px;
background: #fff;
border: 1px solid #fff;
border-radius: 4px;

View File

@ -3,48 +3,62 @@
top: 0.5rem;
left: 0.5rem;
z-index: 1000;
max-width:80%;
max-width:250px;
}
.search-box form,
.search-box .search-box-results {
.search-box-pane {
max-height: 2.4rem;
overflow: hidden;
border-radius: 4px;
background: #fff;
box-shadow: 0px 0px 1px 1px #222222;
display: flex;
flex-flow: row nowrap;
}
.search-box button {
margin-bottom: 0;
.search-box .collapse-btn {
width: 2rem;
flex-shrink: 0;
line-height: 2.3rem;
text-align: center;
background-color: transparent;
border: none;
}
.search-box .collapse-btn.active {
cursor: pointer;
}
.search-box input.form-control {
flex: 1;
padding: 0.1rem 0.1rem;
max-width: 157px;
}
.search-box .btn {
padding: 0.25rem 0.2rem;
margin: 0.1rem;
margin-left: 0;
}
.search-box .search-box-results {
position: absolute;
top: 100%;
right: 0;
left: 2rem;
margin-top: 0.25rem;
list-style: none;
padding: 0;
overflow: hidden;
border-radius: 4px;
background: #fff;
box-shadow: 0px 0px 1px 1px #222222;
}
.search-box-result {
display: block;
padding: 0.25rem 0.5rem;
border-bottom: 1px solid #eee;
}
.collapse-btn {
position: absolute;
top: 0.5rem;
left: 0.5rem;
z-index: 1000;
background: #fff;
border-radius: 2px;
box-shadow: 0px 0px 1px 1px #222222;
}
.search-box .form-control {
padding: 0.1rem 0.1rem;
width: 180px;
}
.search-box .btn {
padding: 0.25rem 0.2rem;
margin-right: 0.1rem;
}
@media (min-width: 990px) {
/* The following is a fix (?) for the truncation of the "Search for postcode" text */
.form-inline .form-control {
width: 200px;
}
}
}

View File

@ -54,11 +54,14 @@ class SearchBox extends Component<SearchBoxProps, SearchBoxState> {
// Update search term
handleChange(e) {
const targetValue = e.target.value;
this.setState({
q: e.target.value
q: targetValue
});
// If the clear icon has been clicked, clear results list as well
if(e.target.value === '') {
// Clear results if the query is changed sufficiently or deleted
if(targetValue === '' || !this.state.q.startsWith(targetValue) ) {
this.clearResults();
}
}
@ -137,16 +140,20 @@ class SearchBox extends Component<SearchBoxProps, SearchBoxState> {
// On a real mobile device onResize() gets called when the virtual keyboard pops up (e.g. when entering search text)
// so be careful what states are changed in this method (i.e. don't collapse the search box here)
onResize(e) {
this.setState({smallScreen: (e.target.innerWidth < 768)});
this.setState({smallScreen: (e.target.innerWidth < 990)});
}
render() {
// if the current state is collapsed (and a mobile device) just render the icon
if(this.state.collapsedSearch && this.state.smallScreen){
return(
<div className="collapse-btn" onClick={this.expandSearch}>
<SearchIcon />
</div>
<div className="search-box">
<div className="search-box-pane">
<div className="collapse-btn active" onClick={this.expandSearch}>
<SearchIcon />
</div>
</div>
</div>
);
}
@ -177,22 +184,25 @@ class SearchBox extends Component<SearchBoxProps, SearchBoxState> {
: null;
return (
<div className="search-box" onKeyDown={this.handleKeyPress}>
<form onSubmit={this.search} className="form-inline">
<div onClick={this.state.smallScreen ? this.expandSearch : null}>
<div className="search-box-pane">
<div className={`collapse-btn ${this.state.smallScreen ? 'active' : ''}`} onClick={this.state.smallScreen ? this.expandSearch : null}>
<SearchIcon/>
</div>
<input
className="form-control"
type="search"
id="search-box-q"
name="q"
value={this.state.q}
placeholder="Search for a postcode"
aria-label="Search for a postcode"
onChange={this.handleChange}
/>
<button className="btn btn-outline-dark" type="submit">Search</button>
</form>
<form onSubmit={this.search} className="form-inline d-flex flex-nowrap">
<input
className="form-control"
type="search"
id="search-box-q"
name="q"
value={this.state.q}
placeholder="Type a postcode..."
aria-label="Type a postcode..."
onChange={this.handleChange}
maxLength={8}
/>
<button className="search-btn btn btn-outline-dark" type="submit">Search</button>
</form>
</div>
{ resultsList }
</div>
);

View File

@ -9,9 +9,9 @@
margin: 1em 1em;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
flex-grow: 1;
height: 4em;
}
.image-row img {
@ -19,4 +19,12 @@
max-height: 100%;
max-width: 100%;
align-self: flex-start;
}
.cl-logo {
height: 4em;
}
.turing-logo {
height: 4em;
}

View File

@ -32,8 +32,8 @@ const Welcome = () => (
Start Colouring Here!
</Link>
<div className="image-row">
<img src="images/logo-cc.jpg" alt="Colouring Cities Research Programme"></img>
<img src="images/logo-turing.jpg" alt="Alan Turing Institute"></img>
<img className="cl-logo" src="images/logo-cc.jpg" alt="Colouring Cities Research Programme"></img>
<img className="turing-logo" src="images/logo-turing.jpg" alt="Alan Turing Institute"></img>
</div>
<div className="image-row">
<img src="images/supporter-logos.png" alt="Colouring London collaborating organisations: The Bartlett UCL, Ordnance Survey, Historic England, Greater London Authority" />

View File

@ -23,7 +23,7 @@ article {
@media (min-width: 990px) {
article {
top: 0px;
left: 480px;
left: 470px;
}
}
article section {