Commit 7fb4476a authored by Nicolas Frandeboeuf's avatar Nicolas Frandeboeuf
Browse files

Make menu closable everywhere (and fix #522)

parent ddce2ec2
Pipeline #14598 failed with stage
in 13 seconds
......@@ -4,26 +4,23 @@ import { assertHas } from '../../helpers';
export default props => {
assertHas(props, 'backgroundColor');
assertHas(props, 'size');
assertHas(props, 'icon');
assertHas(props, 'title');
assertHas(props, 'subtitle');
assertHas(props, 'content');
return (
<div className={ props.size }>
<div
style={ { backgroundColor: props.backgroundColor } }
className='well'>
<span className="well-icon">
<i className={ `fa fa-${props.icon}` } />
</span>
<span className="operation-amount">
{ props.content }
</span><br />
<span className="well-title">{ props.title }</span><br />
<span className="well-sub">{ props.subtitle }</span>
</div>
<div
className="well"
style={ { backgroundColor: props.backgroundColor } }>
<span className="well-icon">
<i className={ `fa fa-${props.icon}` } />
</span>
<span className="operation-amount">
{ props.content }
</span><br />
<span className="well-title">{ props.title }</span><br />
<span className="well-sub">{ props.subtitle }</span>
</div>
);
};
......@@ -180,7 +180,7 @@ let fillConfirmDelete = (props, showDetails, onDelete) => {
let DetailsModal = props => {
if (props.operation === null) {
return <div />;
return null;
}
let onDelete = props.makeHandleDeleteOperation(props.operation);
......
......@@ -57,8 +57,7 @@ class OperationsComponent extends React.Component {
this.selectModalOperation = this.selectModalOperation.bind(this);
this.detailsModal = null;
this.wells = null;
this.search = null;
this.operationPanel = null;
this.panelHeading = null;
this.thead = null;
}
......@@ -93,12 +92,11 @@ class OperationsComponent extends React.Component {
}
handleWindowResize() {
let wellH = ReactDOM.findDOMNode(this.wells).scrollHeight;
let searchH = ReactDOM.findDOMNode(this.search).scrollHeight;
let panelH = ReactDOM.findDOMNode(this.panelHeading).scrollHeight;
let theadH = ReactDOM.findDOMNode(this.thead).scrollHeight;
let heightAbove = ReactDOM.findDOMNode(this.operationPanel).offsetTop;
heightAbove += ReactDOM.findDOMNode(this.panelHeading).scrollHeight;
heightAbove += ReactDOM.findDOMNode(this.thead).scrollHeight;
this.heightAbove = wellH + searchH + panelH + theadH;
this.heightAbove = heightAbove;
this.operationHeight = computeOperationHeight();
}
......@@ -144,11 +142,8 @@ class OperationsComponent extends React.Component {
let detailsModalCb = node => {
this.detailsModal = node;
};
let wellsCb = node => {
this.wells = node;
};
let searchCb = node => {
this.search = node;
let operationPanelCb = node => {
this.operationPanel = node;
};
let panelHeadingCb = node => {
this.panelHeading = node;
......@@ -167,13 +162,9 @@ class OperationsComponent extends React.Component {
getCategory={ this.props.getCategory }
/>
<div
className="row operation-wells"
ref={ wellsCb }>
<div className="operation-wells">
<AmountWell
backgroundColor={ wellsColors.BALANCE }
size="col-xs-6 col-md-3"
icon="balance-scale"
title={ $t('client.operations.current_balance') }
subtitle={ lastCheckDate }
......@@ -182,7 +173,6 @@ class OperationsComponent extends React.Component {
<AmountWell
backgroundColor={ wellsColors.RECEIVED }
size="col-xs-6 col-md-3"
icon="arrow-down"
title={ $t('client.operations.received') }
subtitle={ filteredSub }
......@@ -191,7 +181,6 @@ class OperationsComponent extends React.Component {
<AmountWell
backgroundColor={ wellsColors.SPENT }
size="col-xs-6 col-md-3"
icon="arrow-up"
title={ $t('client.operations.spent') }
subtitle={ filteredSub }
......@@ -200,7 +189,6 @@ class OperationsComponent extends React.Component {
<AmountWell
backgroundColor={ wellsColors.SAVED }
size="col-xs-6 col-md-3"
icon="database"
title={ $t('client.operations.saved') }
subtitle={ filteredSub }
......@@ -208,9 +196,11 @@ class OperationsComponent extends React.Component {
/>
</div>
<SearchComponent ref={ searchCb } />
<SearchComponent />
<div className="operation-panel panel panel-default">
<div
className="operation-panel panel panel-default"
ref={ operationPanelCb }>
<div
className="panel-heading"
ref={ panelHeadingCb }>
......@@ -249,6 +239,7 @@ class OperationsComponent extends React.Component {
getHeightAbove={ this.computeHeightAbove }
renderItems={ this.renderItems }
onResizeUser={ this.handleWindowResize }
containerId="content"
/>
</table>
</div>
......
......@@ -14,6 +14,9 @@ export default class InfiniteList extends React.Component {
this.state = {
firstItemShown: 0,
// Use window instead of this.container since it does not exist in
// the DOM yet.
lastItemShown: window.innerHeight / itemHeight,
itemHeight
};
......@@ -23,12 +26,13 @@ export default class InfiniteList extends React.Component {
}
componentDidMount() {
window.addEventListener('scroll', this.handleScroll);
this.container = document.getElementById(this.props.containerId);
this.container.addEventListener('scroll', this.handleScroll);
window.addEventListener('resize', this.handleResize);
}
componentWillUnmount() {
window.removeEventListener('scroll', this.handleScroll);
this.container.removeEventListener('scroll', this.handleScroll);
window.removeEventListener('resize', this.handleResize);
}
......@@ -44,8 +48,8 @@ export default class InfiniteList extends React.Component {
let heightAbove = this.props.getHeightAbove();
let topItemH = Math.max(window.scrollY - heightAbove, 0);
let bottomItemH = topItemH + window.innerHeight;
let topItemH = Math.max(this.container.scrollTop - heightAbove, 0);
let bottomItemH = topItemH + this.container.clientHeight;
let itemHeight = this.props.getItemHeight();
let ballast = this.props.ballast;
......@@ -94,5 +98,8 @@ InfiniteList.propTypes = {
renderItems: React.PropTypes.func.isRequired,
// Function called on each window resize.
onResizeUser: React.PropTypes.func
onResizeUser: React.PropTypes.func,
// The list container html identifier
containerId: React.PropTypes.string.isRequired
};
/*!
* Jasny Bootstrap v3.1.0 (http://jasny.github.com/bootstrap)
* Copyright 2011-2014 Arnold Daniels.
* Licensed under Apache-2.0 (https://github.com/jasny/bootstrap/blob/master/LICENSE)
*/
.nav-tabs-bottom {
border-bottom: 0;
border-top: 1px solid #dddddd;
}
.nav-tabs-bottom > li {
margin-bottom: 0;
margin-top: -1px;
}
.nav-tabs-bottom > li > a {
border-radius: 0 0 4px 4px;
}
.nav-tabs-bottom > li > a:hover,
.nav-tabs-bottom > li > a:focus,
.nav-tabs-bottom > li.active > a,
.nav-tabs-bottom > li.active > a:hover,
.nav-tabs-bottom > li.active > a:focus {
border: 1px solid #dddddd;
border-top-color: transparent;
}
.nav-tabs-left {
border-bottom: 0;
border-right: 1px solid #dddddd;
}
.nav-tabs-left > li {
margin-bottom: 0;
margin-right: -1px;
float: none;
}
.nav-tabs-left > li > a {
border-radius: 4px 0 0 4px;
margin-right: 0;
margin-bottom: 2px;
}
.nav-tabs-left > li > a:hover,
.nav-tabs-left > li > a:focus,
.nav-tabs-left > li.active > a,
.nav-tabs-left > li.active > a:hover,
.nav-tabs-left > li.active > a:focus {
border: 1px solid #dddddd;
border-right-color: transparent;
}
.row > .nav-tabs-left {
padding-right: 0;
padding-left: 15px;
margin-right: -1px;
position: relative;
z-index: 1;
}
.row > .nav-tabs-left + .tab-content {
border-left: 1px solid #dddddd;
}
.nav-tabs-right {
border-bottom: 0;
border-left: 1px solid #dddddd;
}
.nav-tabs-right > li {
margin-bottom: 0;
margin-left: -1px;
float: none;
}
.nav-tabs-right > li > a {
border-radius: 0 4px 4px 0;
margin-left: 0;
margin-bottom: 2px;
}
.nav-tabs-right > li > a:hover,
.nav-tabs-right > li > a:focus,
.nav-tabs-right > li.active > a,
.nav-tabs-right > li.active > a:hover,
.nav-tabs-right > li.active > a:focus {
border: 1px solid #dddddd;
border-left-color: transparent;
}
.row > .nav-tabs-right {
padding-left: 0;
padding-right: 15px;
}
.offcanvas {
display: none;
}
.offcanvas.in {
display: block;
}
@media (max-width: 767px) {
.offcanvas-xs {
display: none;
}
.offcanvas-xs.in {
display: block;
}
}
@media (max-width: 991px) {
.offcanvas-sm {
display: none;
}
.offcanvas-sm.in {
display: block;
}
}
@media (max-width: 1199px) {
.offcanvas-md {
display: none;
}
.offcanvas-md.in {
display: block;
}
}
.offcanvas-lg {
display: none;
}
.offcanvas-lg.in {
display: block;
}
.canvas-sliding {
-webkit-transition: top 0.35s, left 0.35s, bottom 0.35s, right 0.35s;
transition: top 0.35s, left 0.35s, bottom 0.35s, right 0.35s;
}
.offcanvas-clone {
height: 0px !important;
width: 0px !important;
overflow: hidden !important;
border: none !important;
margin: 0px !important;
padding: 0px !important;
position: absolute !important;
top: auto !important;
left: auto !important;
bottom: 0px !important;
right: 0px !important;
opacity: 0 !important;
}
......@@ -32,7 +32,7 @@
src: local('Open Sans Bold'), local('OpenSans-Bold'), url('../fonts/opensans/OpenSans-Bold.woff') format('woff');
}
html, body, #main, #app {
html, body, #app {
min-height: 100%;
}
......@@ -66,56 +66,102 @@ ul {
padding: 1em;
}
#main > div > div:last-child {
#app {
padding: 0;
margin: 0;
height: 100vh;
overflow: hidden;
}
/* Main block on the right of the sidebar */
.main-block {
background: #f1f2f7;
#app > div {
height: 100%;
}
/* Mobile display */
@media (max-width: 480px) {
.main-navbar {
margin-bottom: 1em;
}
header {
box-sizing: border-box;
overflow: hidden;
height: 40px;
background-color: #303641;
color: white;
width: 100%;
margin: 0;
}
.main-block {
padding: 7px;
}
main {
height: calc(100vh - 40px);
display: flex;
flex-direction: row;
}
.main-block .operation-wells {
margin-top: 0;
}
main > * {
box-sizing: border-box;
height: 100%;
min-height: 100%;
overflow-y: auto;
}
.main-block .operation-wells > div:nth-child(odd) {
padding-right: 7px;
}
main nav {
background-color: #2b303a;
color: white;
padding: 0;
width: 320px;
flex-shrink: 0;
overflow-y: auto;
overflow-x: hidden;
.main-block .operation-wells > div:nth-child(even) {
padding-left: 7px;
}
z-index: 1000;
display: flex;
flex-direction: column;
.main-block .operation-wells .well {
padding: 10px;
text-align: center;
}
transition: width 0.5s ease-in-out;
}
.main-block .operation-wells .well-icon {
float: none;
display: block;
margin: 0 auto;
height: 40px;
width: 40px;
line-height: 40px;
font-size: 20px;
main nav.menu-hidden {
width: 0;
padding-left: 0;
padding-right: 0;
}
main nav > * {
box-sizing: border-box;
width: 320px;
}
main nav > :first-child {
margin-top: 0;
}
main nav > :last-child {
margin-bottom: 0;
}
#content {
flex: auto;
padding: 1em;
overflow: auto;
background: #f1f2f7;
}
#content > div {
min-width: 600px;
max-width: 1600px;
margin: auto;
}
#content .form-group {
overflow: hidden;
}
@media (max-width: 620px) {
#content {
min-width: 100%;
width: 100%;
overflow-x: hidden;
padding: 7px;
}
.main-block .operation-wells .operation-amount {
font-size: 1.65em;
margin: 0;
word-break: break-all;
#content > div {
min-width: 100%;
}
.panel-body {
......@@ -127,40 +173,46 @@ ul {
}
}
.main-container ul li {
#content ul li {
list-style: none;
}
/* small screen navbar */
.navbar {
display: block;
text-align: center;
/* Navigation bar */
header > * {
display: inline-block;
vertical-align: middle;
margin-top: 0;
margin-right: 0;
font-size: 20px;
}
.main-navbar {
padding: 6px 0;
background-color: #303641;
color: #FFF;
header .menu-toggle {
font-size: 25px;
padding: 0;
margin: 0 15px;
background-color: transparent;
border: 0;
}
.main-navbar a:hover, .main-navbar a:focus {
header h1 {
font-weight: bold;
color: #FFF;
margin-top: 10px;
}
.navbar-brand {
display: inline-block;
float: none;
margin-left: 40px;
font-size: 22px;
font-weight: bold;
header h1 a, header h1 a:hover, header h1 a:focus {
color: #FFF;
}
.navbar-default {
background-color: #FFF;
background-image: none;
border-color: #cfd9db;
box-shadow: none;
header .locale-selector {
width: auto;
margin-right: 5px;
margin-top: 3px;
color: #000;
}
header .locale-selector.form-control {
display: inline-block;
}
.sidebar-navbar-collapse {
......@@ -181,34 +233,6 @@ ul {
border-color: #717171;
}
/* Sidebar on the left */
.sidebar {
background: #2b303a;
height: 100%;
min-height: 100%;
/* Cancel effects of the grid system */
padding-left: 0;
padding-right: 0;
position: fixed;
top: 0px;
bottom: 0px;
left: 0px;
overflow: auto;
clear: both;
z-index: 1000;
display: flex;
flex-direction: column;
}
@media (max-width: 767px) {
.sidebar.offcanvas-xs:not(.in),
.sidebar.offcanvas-sm:not(.in),
.sidebar.offcanvas-md:not(.in) {
display: none;
}
}
/* Sidebar list of sections (operations, settings, etc.) */
.sidebar-section-list ul {
list-style: none;
......@@ -243,33 +267,6 @@ ul {
background: #303641;
}
/* Logo in the sidebar */
.logo {
padding: 20px 0 20px 20px;
color: #fff;
}
.logo a {
color: #fff;
}
.locale-selector {
width: auto;
margin-right: 5px;
color: #000;
}
.locale-selector.form-control {
display: inline-block;
}
.app-title {
line-height: 1.3em;
font-size: 22px;
font-weight: bold;
margin: 0;
}
/* Bank header */
.sidebar-bank-header {
border-bottom: 2px solid #343a45;
......@@ -456,20 +453,19 @@ ul {
}
/* Operations list component */
/* Wells (panels with amount at the top */
/* Wells (panels with amount at the top) */
.operation-wells {
margin-top: 15px;