From 380c0773d17a06af7ec463797ef348cd43ade343 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Wed, 25 Jan 2017 12:16:04 +0100 Subject: [PATCH] ui/SectionList component (#4292) * array chunking utility * add SectionList component * Add TODOs to indicate possible future work * Add missing overlay style (as used in dapps at present) --- js/src/ui/SectionList/index.js | 17 ++++ js/src/ui/SectionList/sectionList.css | 72 +++++++++++++++ js/src/ui/SectionList/sectionList.js | 95 ++++++++++++++++++++ js/src/ui/SectionList/sectionList.spec.js | 103 ++++++++++++++++++++++ js/src/ui/index.js | 4 +- js/src/util/array.js | 26 ++++++ js/src/util/array.spec.js | 29 ++++++ 7 files changed, 345 insertions(+), 1 deletion(-) create mode 100644 js/src/ui/SectionList/index.js create mode 100644 js/src/ui/SectionList/sectionList.css create mode 100644 js/src/ui/SectionList/sectionList.js create mode 100644 js/src/ui/SectionList/sectionList.spec.js create mode 100644 js/src/util/array.js create mode 100644 js/src/util/array.spec.js diff --git a/js/src/ui/SectionList/index.js b/js/src/ui/SectionList/index.js new file mode 100644 index 000000000..25971968d --- /dev/null +++ b/js/src/ui/SectionList/index.js @@ -0,0 +1,17 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default from './sectionList'; diff --git a/js/src/ui/SectionList/sectionList.css b/js/src/ui/SectionList/sectionList.css new file mode 100644 index 000000000..2310a88ac --- /dev/null +++ b/js/src/ui/SectionList/sectionList.css @@ -0,0 +1,72 @@ +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. +/* This file is part of Parity. +/* +/* Parity is free software: you can redistribute it and/or modify +/* it under the terms of the GNU General Public License as published by +/* the Free Software Foundation, either version 3 of the License, or +/* (at your option) any later version. +/* +/* Parity is distributed in the hope that it will be useful, +/* but WITHOUT ANY WARRANTY; without even the implied warranty of +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +/* GNU General Public License for more details. +/* +/* You should have received a copy of the GNU General Public License +/* along with Parity. If not, see . +*/ + +.section { + margin-bottom: 1em; + position: relative; + + .overlay { + background: rgba(0, 0, 0, 0.85); + bottom: 0; + left: 0; + padding: 1.5em; + position: absolute; + right: 0; + top: 0; + z-index: 199; + } + + .row { + display: flex; + justify-content: center; + + /* TODO: As per JS comments, the flex-base could be adjusted in the future to allow for */ + /* case where <> 3 columns are required should the need arrise from a UI pov. */ + .item { + box-sizing: border-box; + cursor: pointer; + flex: 0 1 33.33%; + height: 100%; + opacity: 0.75; + padding: 0.25em; + transition: all 0.75s cubic-bezier(0.23, 1, 0.32, 1); + + /* TODO: The hover and no-hover states can be improved to not "just appear" */ + &:not(:hover) { + & [data-hover="hide"] { + } + + & [data-hover="show"] { + display: none; + } + } + + &:hover { + flex: 0 0 50%; + opacity: 1; + z-index: 100; + + & [data-hover="hide"] { + display: none; + } + + & [data-hover="show"] { + } + } + } + } +} diff --git a/js/src/ui/SectionList/sectionList.js b/js/src/ui/SectionList/sectionList.js new file mode 100644 index 000000000..82e39763b --- /dev/null +++ b/js/src/ui/SectionList/sectionList.js @@ -0,0 +1,95 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import React, { Component, PropTypes } from 'react'; + +import { chunkArray } from '~/util/array'; +import { arrayOrObjectProptype, nodeOrStringProptype } from '~/util/proptypes'; + +import styles from './sectionList.css'; + +// TODO: We probably want this to be passed via props - additional work required in that case to +// support the styling for both the hover and no-hover CSS for the pre/post sizes. Future work only +// if/when required. +const ITEMS_PER_ROW = 3; + +export default class SectionList extends Component { + static propTypes = { + className: PropTypes.string, + items: arrayOrObjectProptype().isRequired, + renderItem: PropTypes.func.isRequired, + overlay: nodeOrStringProptype() + } + + render () { + const { className, items } = this.props; + + if (!items || !items.length) { + return null; + } + + return ( +
+ { this.renderOverlay() } + { chunkArray(items, ITEMS_PER_ROW).map(this.renderRow) } +
+ ); + } + + renderOverlay () { + const { overlay } = this.props; + + if (!overlay) { + return null; + } + + return ( +
+ { overlay } +
+ ); + } + + renderRow = (row, index) => { + return ( +
+ { row.map(this.renderItem) } +
+ ); + } + + renderItem = (item, index) => { + const { renderItem } = this.props; + + // NOTE: Any children that is to be showed or hidden (depending on hover state) + // should have the data-hover="show|hide" attributes. For the current implementation + // this does the trick, however there may be a case for adding a hover attribute + // to an item (mouseEnter/mouseLeave events) and then adjusting the styling with + // :root[hover]/:root:not[hover] for the tragetted elements. Currently it is a + // CSS-only solution to let the browser do all the work via selectors. + return ( +
+ { renderItem(item, index) } +
+ ); + } +} diff --git a/js/src/ui/SectionList/sectionList.spec.js b/js/src/ui/SectionList/sectionList.spec.js new file mode 100644 index 000000000..480b79a52 --- /dev/null +++ b/js/src/ui/SectionList/sectionList.spec.js @@ -0,0 +1,103 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { shallow } from 'enzyme'; +import React from 'react'; +import sinon from 'sinon'; + +import SectionList from './'; + +const ITEMS = ['itemA', 'itemB', 'itemC', 'itemD', 'itemE']; + +let component; +let instance; +let renderItem; + +function render (props = {}) { + renderItem = sinon.stub(); + component = shallow( + + ); + instance = component.instance(); + + return component; +} + +describe('SectionList', () => { + beforeEach(() => { + render(); + }); + + it('renders defaults', () => { + expect(component).to.be.ok; + }); + + it('adds className as specified', () => { + expect(component.hasClass('testClass')).to.be.true; + }); + + describe('instance methods', () => { + describe('renderRow', () => { + let row; + + beforeEach(() => { + sinon.stub(instance, 'renderItem'); + row = instance.renderRow(['testA', 'testB']); + }); + + afterEach(() => { + instance.renderItem.restore(); + }); + + it('renders a row', () => { + expect(row).to.be.ok; + }); + + it('adds a key for the row', () => { + expect(row.key).to.be.ok; + }); + + it('calls renderItem for the items', () => { + expect(instance.renderItem).to.have.been.calledTwice; + }); + }); + + describe('renderItem', () => { + let item; + + beforeEach(() => { + item = instance.renderItem('testItem', 50); + }); + + it('renders an item', () => { + expect(item).to.be.ok; + }); + + it('adds a key for the item', () => { + expect(item.key).to.be.ok; + }); + + it('calls the external renderer', () => { + expect(renderItem).to.have.been.calledWith('testItem', 50); + }); + }); + }); +}); diff --git a/js/src/ui/index.js b/js/src/ui/index.js index d66663d8e..637a16daf 100644 --- a/js/src/ui/index.js +++ b/js/src/ui/index.js @@ -47,6 +47,7 @@ import Page from './Page'; import ParityBackground from './ParityBackground'; import PasswordStrength from './Form/PasswordStrength'; import QrCode from './QrCode'; +import SectionList from './SectionList'; import ShortenedHash from './ShortenedHash'; import SignerIcon from './SignerIcon'; import Tags from './Tags'; @@ -102,8 +103,9 @@ export { PasswordStrength, QrCode, RadioButtons, - ShortenedHash, Select, + ShortenedHash, + SectionList, SignerIcon, Tags, Tooltip, diff --git a/js/src/util/array.js b/js/src/util/array.js new file mode 100644 index 000000000..e433c0163 --- /dev/null +++ b/js/src/util/array.js @@ -0,0 +1,26 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +// http://stackoverflow.com/questions/11318680/split-array-into-chunks-of-n-length +export function chunkArray (array, size) { + return array + .map((item, index) => { + return index % size === 0 + ? array.slice(index, index + size) + : null; + }) + .filter((item) => item); +} diff --git a/js/src/util/array.spec.js b/js/src/util/array.spec.js new file mode 100644 index 000000000..7d760388d --- /dev/null +++ b/js/src/util/array.spec.js @@ -0,0 +1,29 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { chunkArray } from './array'; + +describe('util/array', () => { + describe('chunkArray', () => { + it('splits array into equal chunks', () => { + expect(chunkArray([1, 2, 3, 4], 2)).to.deep.equal([[1, 2], [3, 4]]); + }); + + it('splits array into equal chunks (non-divisible)', () => { + expect(chunkArray([1, 2, 3, 4], 3)).to.deep.equal([[1, 2, 3], [4]]); + }); + }); +});