Sidebar header/section styles

This commit is contained in:
Tom Russell 2018-10-04 22:50:33 +01:00
parent 8c0452920d
commit df91d98383
4 changed files with 253 additions and 184 deletions

View File

@ -1,38 +1,44 @@
import React, { Component, Fragment } from 'react'; import React, { Component, Fragment } from 'react';
import { Link, NavLink, Redirect } from 'react-router-dom'; import { Link, NavLink, Redirect } from 'react-router-dom';
import queryString from 'query-string';
import ErrorBox from './error-box'; import ErrorBox from './error-box';
import InfoBox from './info-box'; import InfoBox from './info-box';
import Sidebar from './sidebar'; import Sidebar from './sidebar';
import { HelpIcon, CloseIcon, SaveIcon } from './icons';
import CONFIG from './fields-config.json'; import CONFIG from './fields-config.json';
class BuildingEdit extends Component { const BuildingEdit = (props) => {
render() { if (!props.user){
if (!this.props.user){ return <Redirect to="/sign-up.html" />
return <Redirect to="/sign-up.html" /> }
} if (!props.building_id){
if (!this.props.building_id){
return (
<Sidebar title="Building Not Found" back="/map/date_year.html">
<InfoBox msg="We can't find that one anywhere - try the map again?" />
<div className="buttons-container">
<Link to="/map/date_year.html" className="btn btn-secondary">Back to maps</Link>
</div>
</Sidebar>
);
}
return ( return (
<Sidebar title={`Edit Building`} back={`/building/${this.props.building_id}.html`}> <Sidebar title="Building Not Found" back="/map/date_year.html">
{ <InfoBox msg="We can't find that one anywhere - try the map again?" />
CONFIG.map((conf_props) => { <div className="buttons-container">
return <EditForm {...conf_props} {...this.props} key={conf_props.slug} /> <Link to="/map/date_year.html" className="btn btn-secondary">Back to maps</Link>
}) </div>
}
</Sidebar> </Sidebar>
); );
} }
const search = (props.location && props.location.search)?
queryString.parse(props.location.search):
{};
return (
<Sidebar title={`Edit Building`} back={`/building/${props.building_id}.html`}>
{
CONFIG.map((conf_props) => {
return <EditForm
{...conf_props} {...props}
search={search} key={conf_props.slug} />
})
}
</Sidebar>
);
} }
class EditForm extends Component { class EditForm extends Component {
@ -89,50 +95,80 @@ class EditForm extends Component {
} }
render() { render() {
const match = true; const match = this.props.search.cat === this.props.slug;
return ( return (
<section className={(this.props.inactive)? "data-section inactive": "data-section"}> <section className={(this.props.inactive)? "data-section inactive": "data-section"}>
<NavLink className="bullet-prefix" to={(match)? '#': `#${this.props.slug}`} <header className={(match? "active " : "") + "bullet-prefix section-header"}>
isActive={() => match}> <NavLink
<h3 className="h3">{this.props.title}</h3> to={`/building/${this.props.building_id}/edit.html` + ((match)? '': `?cat=${this.props.slug}`)}
</NavLink> isActive={() => match}>
<form action={`/building/${this.props.building_id}.html#${this.props.slug}`} <h3 className="h3">{this.props.title}</h3>
method="GET" onSubmit={this.handleSubmit}> </NavLink>
<ErrorBox msg={this.state.error} /> {
<Fragment>{ this.props.help?
this.props.fields.map((props) => { <a className="icon-button help" title="Find out more" href={this.props.help}
var el; target="_blank" rel="noopener noreferrer">
switch (props.type) { <HelpIcon />
case "text": </a>
el = <TextInput {...props} handleChange={this.handleChange} : null
value={this.state[props.slug]} key={props.slug} /> }
break; {
case "number": match?
el = <NumberInput {...props} handleChange={this.handleChange} <Fragment>
value={this.state[props.slug]} key={props.slug} /> <NavLink className="icon-button save" title="Save Changes"
break; to={`/building/${this.props.building_id}.html?cat=${this.props.slug}`}>
case "like": <SaveIcon />
el = <LikeButton {...props} handleLike={this.handleLike} </NavLink>
value={this.state[props.slug]} key={props.slug} /> <NavLink className="icon-button close" title="Cancel"
break; to={`/building/${this.props.building_id}.html?cat=${this.props.slug}`}>
default: <CloseIcon />
el = null </NavLink>
break; </Fragment>
} : null
return el }
}) </header>
}</Fragment> { (match && this.props.intro)? <p className="data-intro">{ this.props.intro }</p> : null }
<Fragment>{ {
(this.props.inactive)? match?
null : ( <form action={`/building/${this.props.building_id}.html?cat=${this.props.slug}`}
<div className="buttons-container"> method="GET" onSubmit={this.handleSubmit}>
<Link to={`/building/${this.props.building_id}.html#${this.props.slug}`} <ErrorBox msg={this.state.error} />
className="btn btn-secondary">Cancel</Link> <Fragment>{
<button type="submit" className="btn btn-primary">Save</button> this.props.fields.map((props) => {
</div> var el;
) switch (props.type) {
}</Fragment> case "text":
</form> el = <TextInput {...props} handleChange={this.handleChange}
value={this.state[props.slug]} key={props.slug} />
break;
case "number":
el = <NumberInput {...props} handleChange={this.handleChange}
value={this.state[props.slug]} key={props.slug} />
break;
case "like":
el = <LikeButton {...props} handleLike={this.handleLike}
value={this.state[props.slug]} key={props.slug} />
break;
default:
el = null
break;
}
return el
})
}</Fragment>
<Fragment>{
(this.props.inactive)?
null : (
<div className="buttons-container">
<Link to={`/building/${this.props.building_id}.html#${this.props.slug}`}
className="btn btn-secondary">Cancel</Link>
<button type="submit" className="btn btn-primary">Save</button>
</div>
)
}</Fragment>
</form>
: null
}
</section> </section>
) )
} }

View File

@ -1,5 +1,6 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import { Link, NavLink } from 'react-router-dom'; import { Link, NavLink } from 'react-router-dom';
import queryString from 'query-string';
import Sidebar from './sidebar'; import Sidebar from './sidebar';
import Tooltip from './tooltip'; import Tooltip from './tooltip';
@ -20,19 +21,19 @@ const BuildingView = (props) => {
</Sidebar> </Sidebar>
); );
} }
const hash = (props.location && props.location.hash)? props.location.hash.replace('#', ''): undefined; const search = (props.location && props.location.search)? queryString.parse(props.location.search): {};
return ( return (
<Sidebar title={`View Building`} back="/map/date_year.html"> <Sidebar title={`View Building`} back="/map/date_year.html">
{ {
CONFIG.map(section_props => ( CONFIG.map(section_props => (
<DataSection <DataSection
title={section_props.title} slug={section_props.slug} hash={hash} key={section_props.slug} search={search}
building_id={props.building_id} building_id={props.building_id}
intro={section_props.intro} {...section_props}>
helpLink={section_props.help}>
{ {
section_props.fields.map(field_props => ( section_props.fields.map(field_props => (
<DataEntry <DataEntry
key={field_props.slug}
title={field_props.title} title={field_props.title}
value={props[field_props.slug]} value={props[field_props.slug]}
tooltip={field_props.tooltip} /> tooltip={field_props.tooltip} />
@ -47,23 +48,26 @@ const BuildingView = (props) => {
const DataSection = (props) => { const DataSection = (props) => {
const match = props.hash && props.slug.match(props.hash); const match = props.search.cat === props.slug;
return ( return (
<section id={props.slug} className={(props.inactive)? "data-section inactive": "data-section"}> <section id={props.slug} className={(props.inactive)? "data-section inactive": "data-section"}>
<header className="bullet-prefix section-header"> <header className={(match? "active " : "") + "bullet-prefix section-header"}>
<NavLink to={(match)? '#': `#${props.slug}`} isActive={() => match}> <NavLink
to={`/building/${props.building_id}.html` + ((match)? '': `?cat=${props.slug}`)}
isActive={() => match}>
<h3 className="h3">{props.title}</h3> <h3 className="h3">{props.title}</h3>
</NavLink> </NavLink>
{ {
props.helpLink? props.help?
<a className="icon-button help" title="Find out more" href={props.helpLink} target="_blank" rel="noopener noreferrer"> <a className="icon-button help" title="Find out more" href={props.help} target="_blank" rel="noopener noreferrer">
<HelpIcon /> <HelpIcon />
</a> </a>
: null : null
} }
{ {
!props.inactive? !props.inactive?
<NavLink className="icon-button edit" title="Edit data" to={`/building/${props.building_id}/edit.html#${props.slug}`}> <NavLink className="icon-button edit" title="Edit data"
to={`/building/${props.building_id}/edit.html?cat=${props.slug}`}>
<EditIcon /> <EditIcon />
</NavLink> </NavLink>
: null : null

View File

@ -1,73 +1,129 @@
import React, { Component, Fragment } from 'react'; import React, { Fragment } from 'react';
import { Link, NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
import Sidebar from './sidebar'; import Sidebar from './sidebar';
import { HelpIcon } from './icons';
import './legend.css'; import './legend.css';
const data_map = [ import CONFIG from './fields-config.json';
{
slug: 'date_year',
label: 'Age',
elements: [
{
slug: 'date_year',
label: 'Year Built',
elements: [
{ color: '#f0eaba', text: '≥2000' },
{ color: '#fae269', text: '19802000' },
{ color: '#fbaf27', text: '19601980' },
{ color: '#e6711d', text: '19401960' },
{ color: '#d73d3a', text: '19201940' },
{ color: '#ba221c', text: '19001920' },
{ color: '#bb859b', text: '18801900' },
{ color: '#8b3654', text: '18601880' },
{ color: '#8f5385', text: '18401860' },
{ color: '#56619b', text: '18201840' },
{ color: '#6793b2', text: '18001820' },
{ color: '#83c3b3', text: '17801800' },
{ color: '#adc88f', text: '17601780' },
{ color: '#83a663', text: '17401760' },
{ color: '#77852d', text: '17201740' },
{ color: '#69814e', text: '17001720' },
{ color: '#d0c291', text: '16801700' },
{ color: '#918158', text: '16601680' },
{ color: '#7a5732', text: '<1660' },
]
}
]
},
{
slug: 'size_storeys',
label: 'Size',
elements: [
{
slug: 'size_storeys',
label: 'Number of storeys',
elements: [
{ color: '#ffc584', text: '≥20' },
{ color: '#f46259', text: '1020' },
{ color: '#da456a', text: '510' },
{ color: '#a8327d', text: '4' },
{ color: '#7c2383', text: '3' },
{ color: '#5b167f', text: '2' },
{ color: '#360f69', text: '1' },
]
}
]
}
];
const LegendItem = (props) => (
<li> const LEGEND_CONFIG = {
<span className="key" style={ { background: props.color } }>-</span> age: [
{ props.text } {
</li> title: 'Year Built',
); slug: 'date_year',
elements: [
{ color: '#f0eaba', text: '≥2000' },
{ color: '#fae269', text: '19802000' },
{ color: '#fbaf27', text: '19601980' },
{ color: '#e6711d', text: '19401960' },
{ color: '#d73d3a', text: '19201940' },
{ color: '#ba221c', text: '19001920' },
{ color: '#bb859b', text: '18801900' },
{ color: '#8b3654', text: '18601880' },
{ color: '#8f5385', text: '18401860' },
{ color: '#56619b', text: '18201840' },
{ color: '#6793b2', text: '18001820' },
{ color: '#83c3b3', text: '17801800' },
{ color: '#adc88f', text: '17601780' },
{ color: '#83a663', text: '17401760' },
{ color: '#77852d', text: '17201740' },
{ color: '#69814e', text: '17001720' },
{ color: '#d0c291', text: '16801700' },
{ color: '#918158', text: '16601680' },
{ color: '#7a5732', text: '<1660' },
]
}
],
size: [
{
title: 'Number of storeys',
slug: 'size_storeys',
elements: [
{ color: '#ffc584', text: '≥20' },
{ color: '#f46259', text: '1020' },
{ color: '#da456a', text: '510' },
{ color: '#a8327d', text: '4' },
{ color: '#7c2383', text: '3' },
{ color: '#5b167f', text: '2' },
{ color: '#360f69', text: '1' },
]
}
],
like: [
{
title: 'Like Me!',
slug: 'like',
elements: [
{ color: '#f65400', text: 'Liked' },
]
}
]
};
const Legend = (props) => {
var data_layer = undefined;
if (props.match && props.match.params && props.match.params.map) {
data_layer = props.match.params.map;
}
return (
<Sidebar title="Maps">
{
CONFIG.map((data_group) => (
<LegendGroup {...data_group} maps={LEGEND_CONFIG[data_group.slug]}
data_layer={data_layer} key={data_group.slug} />
))
}
</Sidebar>
);
}
const LegendGroup = (props) => {
const match = props.data_layer === props.slug;
const inactive = props.inactive || !props.maps;
return (
<section className={(inactive? "inactive ": "") + "data-section legend"}>
<header className="bullet-prefix section-header">
<NavLink
to={`/map/${props.slug}.html`}
isActive={() => match}>
<h3 className="h3">{props.title}</h3>
</NavLink>
{
props.help?
<a className="icon-button help" title="Find out more" href={props.help} target="_blank" rel="noopener noreferrer">
<HelpIcon />
</a>
: null
}
</header>
{ (match && props.intro)? <p className="data-intro">{ props.intro }</p> : null }
<dl className="data-list">
{
(match && props.maps)?
props.maps.map((data_section) => (
<LegendSection {...data_section} key={data_section.slug}>
{
data_section.elements.map((data_item) => (
<LegendItem {...data_item} key={data_item.color} />
))
}
</LegendSection>
))
: null
}
</dl>
</section>
)
};
const LegendSection = (props) => ( const LegendSection = (props) => (
<Fragment> <Fragment>
<dt> <dt>
<Link to={`/map/${ props.slug }.html`}>{ props.label }</Link> { props.title }
</dt> </dt>
<dd> <dd>
<ul className="data-legend"> <ul className="data-legend">
@ -77,50 +133,11 @@ const LegendSection = (props) => (
</Fragment> </Fragment>
); );
const LegendGroup = (props) => ( const LegendItem = (props) => (
<div className="data-section legend"> <li>
<NavLink className={`bullet-prefix ${ props.slug }`} to={`/map/${ props.slug }.html`}> <span className="key" style={ { background: props.color } }>-</span>
<h3 className="h3">{ props.label }</h3> { props.text }
</NavLink> </li>
<dl className="data-list">
{ props.children }
</dl>
</div>
); );
class Legend extends Component {
render() {
var data_layer = undefined;
if (this.props.match && this.props.match.params && this.props.match.params.map) {
data_layer = this.props.match.params.map;
}
return (
<Sidebar title="Maps">
{
data_map.map((data_group) => (
<LegendGroup {...data_group} key={data_group.slug}>
{
( data_layer.match(data_group.slug) )
? data_group.elements.map((data_section) => (
<LegendSection {...data_section} key={data_section.slug}>
{
( data_layer.match(data_section.slug) )
? data_section.elements.map((data_item) => (
<LegendItem {...data_item} key={data_item.color} />
))
: null
}
</LegendSection>
))
: null
}
</LegendGroup>
))
}
</Sidebar>
);
}
}
export default Legend; export default Legend;

View File

@ -15,7 +15,7 @@
display: block; display: block;
position: relative; position: relative;
padding: 0.6rem 0.5rem 0.5rem 2.25rem; padding: 0.6rem 0.5rem 0.5rem 2.25rem;
cursor: pointer; /* cursor: pointer; */
text-decoration: none; text-decoration: none;
color: #222; color: #222;
transition: background-color 0.2s; transition: background-color 0.2s;
@ -23,6 +23,9 @@
.bullet-prefix h3 { .bullet-prefix h3 {
display: inline-block; display: inline-block;
} }
.bullet-prefix a:first-child {
color: #222;
}
.bullet-prefix.active, .bullet-prefix.active,
.bullet-prefix:hover { .bullet-prefix:hover {
color: #222; color: #222;
@ -30,7 +33,7 @@
background-color: #eeeeee; background-color: #eeeeee;
} }
.bullet-prefix::before { .bullet-prefix::before {
display: inline-block; display: block;
position: absolute; position: absolute;
left: 0.55rem; left: 0.55rem;
top: 0.3rem; top: 0.3rem;
@ -51,7 +54,7 @@
content: '\25BC'; content: '\25BC';
} }
.bullet-prefix.active:hover::before { .bullet-prefix.active:hover::before {
content: '\25A0'; content: '\25B2';
} }
.icon-button { .icon-button {
@ -84,13 +87,22 @@
background-color: #fff; background-color: #fff;
} }
.icon-button.edit:hover svg { .icon-button.edit:hover svg {
color: rgb(11, 247, 255); color: rgb(11, 225, 225);
} }
.icon-button.help:hover svg { .icon-button.help:hover svg {
color: rgb(255, 11, 222); color: rgb(0, 81, 255)
} }
.icon-button.tooltip-hint:hover svg { .icon-button.tooltip-hint:hover svg {
color: rgb(11, 255, 72); color: rgb(255, 11, 245);
}
.icon-button.close svg {
margin-top: -1px;
}
.icon-button.close:hover svg {
color: rgb(255, 72, 11)
}
.icon-button.save:hover svg {
color: rgb(11, 225, 72);
} }
.section-header .icon-button { .section-header .icon-button {
float: right; float: right;