diff --git a/client/src/app/demo/components-demo-page.js b/client/src/app/demo/components-demo-page.js index 04473348..363d4873 100644 --- a/client/src/app/demo/components-demo-page.js +++ b/client/src/app/demo/components-demo-page.js @@ -13,6 +13,7 @@ const Widget = require('core-components/widget'); const DropDown = require('core-components/drop-down'); const Menu = require('core-components/menu'); const Tooltip = require('core-components/tooltip'); +const Table = require('core-components/table'); let dropDownItems = [{content: 'English'}, {content: 'Spanish'}, {content: 'German'}, {content: 'Portuguese'}, {content: 'Japanese'}]; let secondaryMenuItems = [ @@ -51,7 +52,7 @@ let DemoPage = React.createClass({ { title: 'Checkbox', render: ( - + ) }, { @@ -101,6 +102,30 @@ let DemoPage = React.createClass({ Open Modal ) + }, + { + title: 'Table', + render: ( + + ) } ], @@ -117,7 +142,7 @@ let DemoPage = React.createClass({ renderElements: function () { return this.elements.map((element) => { return ( -
+

{element.title}

diff --git a/client/src/app/main/dashboard/dashboard-list-tickets/dashboard-list-tickets-page.js b/client/src/app/main/dashboard/dashboard-list-tickets/dashboard-list-tickets-page.js index e4ef056a..40707fa8 100644 --- a/client/src/app/main/dashboard/dashboard-list-tickets/dashboard-list-tickets-page.js +++ b/client/src/app/main/dashboard/dashboard-list-tickets/dashboard-list-tickets-page.js @@ -21,7 +21,7 @@ class DashboardListTicketsPage extends React.Component { return (
-
+
); } diff --git a/client/src/core-components/__tests__/table-test.js b/client/src/core-components/__tests__/table-test.js new file mode 100644 index 00000000..99131317 --- /dev/null +++ b/client/src/core-components/__tests__/table-test.js @@ -0,0 +1,108 @@ +const Menu = ReactMock(); +const Table = requireUnit('core-components/table', { + 'core-components/menu': Menu +}); + +describe('Table component', function () { + let table, menu, tr, th; + + function renderTable(props = {}) { + let headers = [ + {value: 'Header 1', key: 'header1'}, + {value: 'Header 2', key: 'header2'} + ]; + let rows = [ + {header1: 'Row1 header1', header2: 'Row1 header2'}, + {header1: 'Row2 header1', header2: 'Row2 header2'}, + {header1: 'Row3 header1', header2: 'Row3 header2'}, + {header1: 'Row4 header1', header2: 'Row4 header2'}, + {header1: 'Row5 header1', header2: 'Row5 header2'}, + {header1: 'Row6 header1', header2: 'Row6 header2'}, + {header1: 'Row7 header1', header2: 'Row7 header2'}, + {header1: 'Row8 header1', header2: 'Row8 header2'} + ]; + + table = TestUtils.renderIntoDocument( +
+ ); + + menu = TestUtils.scryRenderedComponentsWithType(table, Menu)[0]; + tr = TestUtils.scryRenderedDOMComponentsWithTag(table, 'tr'); + th = TestUtils.scryRenderedDOMComponentsWithTag(table, 'th'); + } + + + it('should render a table of elements', function () { + renderTable(); + expect(tr.length).to.equal(9); + + expect(tr[0].children[0]).to.equal(th[0]); + expect(tr[0].children[1]).to.equal(th[1]); + + expect(tr[1].children[0].textContent).to.equal('Row1 header1'); + expect(tr[1].children[1].textContent).to.equal('Row1 header2'); + + expect(tr[2].children[0].textContent).to.equal('Row2 header1'); + expect(tr[2].children[1].textContent).to.equal('Row2 header2'); + + expect(tr[3].children[0].textContent).to.equal('Row3 header1'); + expect(tr[3].children[1].textContent).to.equal('Row3 header2'); + + expect(tr[4].children[0].textContent).to.equal('Row4 header1'); + expect(tr[4].children[1].textContent).to.equal('Row4 header2'); + + expect(tr[5].children[0].textContent).to.equal('Row5 header1'); + expect(tr[5].children[1].textContent).to.equal('Row5 header2'); + + expect(tr[6].children[0].textContent).to.equal('Row6 header1'); + expect(tr[6].children[1].textContent).to.equal('Row6 header2'); + + expect(tr[7].children[0].textContent).to.equal('Row7 header1'); + expect(tr[7].children[1].textContent).to.equal('Row7 header2'); + + expect(tr[8].children[0].textContent).to.equal('Row8 header1'); + expect(tr[8].children[1].textContent).to.equal('Row8 header2'); + + expect(menu).to.equal(undefined); + }); + + it('should render a table limited by page size with menu', function () { + renderTable({pageSize: 3}); + expect(tr.length).to.equal(4); + + expect(tr[0].children[0]).to.equal(th[0]); + expect(tr[0].children[1]).to.equal(th[1]); + + expect(tr[1].children[0].textContent).to.equal('Row1 header1'); + expect(tr[1].children[1].textContent).to.equal('Row1 header2'); + + expect(tr[2].children[0].textContent).to.equal('Row2 header1'); + expect(tr[2].children[1].textContent).to.equal('Row2 header2'); + + expect(tr[3].children[0].textContent).to.equal('Row3 header1'); + expect(tr[3].children[1].textContent).to.equal('Row3 header2'); + + expect(menu.props.type).to.equal('navigation'); + expect(menu.props.items).to.deep.equal([{content: 1}, {content: 2}, {content: 3}]); + + menu.props.onItemClick(1); + tr = TestUtils.scryRenderedDOMComponentsWithTag(table, 'tr'); + + expect(tr.length).to.equal(4); + expect(tr[1].children[0].textContent).to.equal('Row4 header1'); + expect(tr[1].children[1].textContent).to.equal('Row4 header2'); + expect(tr[2].children[0].textContent).to.equal('Row5 header1'); + expect(tr[2].children[1].textContent).to.equal('Row5 header2'); + expect(tr[3].children[0].textContent).to.equal('Row6 header1'); + expect(tr[3].children[1].textContent).to.equal('Row6 header2'); + + menu.props.onItemClick(2); + tr = TestUtils.scryRenderedDOMComponentsWithTag(table, 'tr'); + + expect(tr.length).to.equal(3); + expect(tr[1].children[0].textContent).to.equal('Row7 header1'); + expect(tr[1].children[1].textContent).to.equal('Row7 header2'); + expect(tr[2].children[0].textContent).to.equal('Row8 header1'); + expect(tr[2].children[1].textContent).to.equal('Row8 header2'); + }); +}); \ No newline at end of file diff --git a/client/src/core-components/checkbox.js b/client/src/core-components/checkbox.js index 4c4bfe32..c148783a 100644 --- a/client/src/core-components/checkbox.js +++ b/client/src/core-components/checkbox.js @@ -58,7 +58,7 @@ class CheckBox extends React.Component { getClass() { let classes = { 'checkbox': true, - 'checkbox_checked': this.state.checked, + 'checkbox_checked': this.getValue(), [this.props.className]: (this.props.className) }; diff --git a/client/src/core-components/menu.js b/client/src/core-components/menu.js index ff837dbb..18158405 100644 --- a/client/src/core-components/menu.js +++ b/client/src/core-components/menu.js @@ -11,9 +11,9 @@ class Menu extends React.Component { id: React.PropTypes.string, itemsRole: React.PropTypes.string, header: React.PropTypes.string, - type: React.PropTypes.oneOf(['primary', 'secondary']), + type: React.PropTypes.oneOf(['primary', 'secondary', 'navigation']), items: React.PropTypes.arrayOf(React.PropTypes.shape({ - content: React.PropTypes.string.isRequired, + content: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.number]), icon: React.PropTypes.string })).isRequired, selectedIndex: React.PropTypes.number, @@ -22,10 +22,13 @@ class Menu extends React.Component { static defaultProps = { type: 'primary', - selectedIndex: 0, tabbable: false }; + state = { + selectedIndex: this.props.selectedIndex || 0 + }; + render() { return (
@@ -80,7 +83,8 @@ class Menu extends React.Component { getClass() { let classes = { 'menu': true, - 'menu_secondary': (this.props.type === 'secondary') + 'menu_secondary': (this.props.type === 'secondary'), + 'menu_navigation': (this.props.type === 'navigation') }; classes[this.props.className] = true; @@ -90,7 +94,7 @@ class Menu extends React.Component { getItemProps(index) { return { - id: this.props.id + '__' + index, + id: (this.props.id) ? this.props.id + '__' + index : null, className: this.getItemClass(index), onClick: this.onItemClick.bind(this, index), tabIndex: (this.props.tabbable) ? '0' : null, @@ -103,7 +107,7 @@ class Menu extends React.Component { getItemClass(index) { let classes = { 'menu__list-item': true, - 'menu__list-item_selected': (this.props.selectedIndex === index) + 'menu__list-item_selected': (this.getSelectedIndex() === index) }; return classNames(classes); @@ -118,7 +122,15 @@ class Menu extends React.Component { } } + getSelectedIndex() { + return (this.props.selectedIndex !== undefined) ? this.props.selectedIndex : this.state.selectedIndex; + } + onItemClick(index) { + this.setState({ + selectedIndex: index + }); + if (this.props.onItemClick) { this.props.onItemClick(index); } diff --git a/client/src/core-components/menu.scss b/client/src/core-components/menu.scss index a5f45593..ab2c661b 100644 --- a/client/src/core-components/menu.scss +++ b/client/src/core-components/menu.scss @@ -42,4 +42,40 @@ border-top-left-radius: 4px; } } + + &_navigation { + text-align: center; + background-color: transparent; + + .menu__list { + background-color: transparent; + display: inline-block; + } + + .menu__list-item { + background-color: white; + cursor: pointer; + display: inline-block; + padding: 8px; + + &:first-child { + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + } + + &:last-child { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + } + } + + .menu__list-item_selected { + background-color: $grey; + color: $primary-blue; + } + + .menu__list-item:hover { + color: $primary-blue; + } + } } \ No newline at end of file diff --git a/client/src/core-components/table.js b/client/src/core-components/table.js index 806d1830..d63d40aa 100644 --- a/client/src/core-components/table.js +++ b/client/src/core-components/table.js @@ -1,6 +1,9 @@ import React from 'react'; +import _ from 'lodash'; import classNames from 'classnames'; +import Menu from 'core-components/menu'; + class Table extends React.Component { static propTypes = { headers: React.PropTypes.arrayOf(React.PropTypes.shape({ @@ -9,6 +12,7 @@ class Table extends React.Component { className: React.PropTypes.string })), rows: React.PropTypes.arrayOf(React.PropTypes.object), + pageSize: React.PropTypes.number, type: React.PropTypes.oneOf(['default']) }; @@ -16,18 +20,25 @@ class Table extends React.Component { type: 'default' }; + state = { + page: 1 + }; + render() { return ( -
- - - {this.props.headers.map(this.renderHeaderColumn.bind(this))} - - - - {this.props.rows.map(this.renderRow.bind(this))} - -
+
+ + + + {this.props.headers.map(this.renderHeaderColumn.bind(this))} + + + + {this.props.rows.map(this.renderRow.bind(this))} + +
+ {(this.props.pageSize && this.props.rows.length > this.props.pageSize) ? this.renderNavigation() : null} +
); } @@ -43,13 +54,16 @@ class Table extends React.Component { } renderRow(row, index) { - let headersKeys = this.props.headers.map(header => header.key); + const headersKeys = this.props.headers.map(header => header.key); + const minIndex = this.props.pageSize * (this.state.page - 1); + const maxIndex = this.props.pageSize * this.state.page; + const shouldRenderRow = !this.props.pageSize || (index >= minIndex && index < maxIndex); - return ( + return (shouldRenderRow) ? ( {headersKeys.map(this.renderCell.bind(this, row))} - ); + ) : null; } renderCell(row, key, index) { @@ -62,6 +76,21 @@ class Table extends React.Component { {row[key]} ); } + + renderNavigation() { + const pages = Math.ceil(this.props.rows.length / this.props.pageSize) + 1; + const items = _.range(1, pages).map((index) => {return {content: index};}); + + return ( + + ); + } + + onNavigationItemClick(index) { + this.setState({ + page: index + 1 + }); + } getRowClass(row) { let classes = { diff --git a/client/src/core-components/table.scss b/client/src/core-components/table.scss index 2055d5dd..f732d5ae 100644 --- a/client/src/core-components/table.scss +++ b/client/src/core-components/table.scss @@ -1,6 +1,7 @@ @import "../scss/vars"; .table { + margin-bottom: 0; &__header { background-color: $primary-blue; @@ -43,4 +44,8 @@ border: 0; padding: 10px; } + + &__navigation { + margin-top: 10px; + } } \ No newline at end of file