GridField and header styles

This commit is contained in:
scott1702 2016-03-18 14:42:45 +13:00 committed by Ingo Schommer
parent f8c17bed3b
commit 2abe24f818
27 changed files with 361 additions and 69 deletions

View File

@ -0,0 +1,15 @@
import { Component } from 'react';
export default class SilverStripeComponent extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
}
componentWillUnmount() {
}
};

View File

@ -5,7 +5,7 @@ class GridFieldCellComponent extends SilverStripeComponent {
render() {
return (
<td className='grid-field-cell-component'>{this.props.children}</td>
<div className='grid-field-cell-component'>{this.props.children}</div>
);
}

View File

@ -1,3 +1,11 @@
.grid-field-cell-component {
display: flex;
flex-flow: row nowrap;
flex-grow: 1;
flex-basis: 0;
word-wrap: break-word;
overflow-wrap: break-word;
word-break: break-word;
padding: $grid-y*2 $grid-x*2;
line-height: 20px;
}

View File

@ -5,7 +5,7 @@ class GridFieldHeaderCellComponent extends SilverStripeComponent {
render() {
return (
<th className='grid-field-header-cell-component'>{this.props.children}</th>
<div className='grid-field-header-cell-component'>{this.props.children}</div>
);
}

View File

@ -0,0 +1,12 @@
.grid-field-header-cell-component {
text-transform: uppercase;
display: flex;
flex-flow: row nowrap;
flex-grow: 1;
flex-basis: 0;
word-wrap: break-word;
overflow-wrap: break-word;
word-break: break-word;
padding: $grid-y*2 $grid-x*2;
line-height: 20px;
}

View File

@ -6,9 +6,7 @@ class GridFieldHeaderComponent extends SilverStripeComponent {
render() {
return (
<thead className='grid-field-header-component'>
<GridFieldRowComponent>{this.props.children}</GridFieldRowComponent>
</thead>
);
}

View File

@ -5,7 +5,7 @@ class GridFieldRowComponent extends SilverStripeComponent {
render() {
return (
<tr className='grid-field-row-component'>{this.props.children}</tr>
<li className='grid-field-row-component [ list-group-item ]'>{this.props.children}</li>
);
}

View File

@ -1,3 +1,16 @@
.grid-field-row-component {
.grid-field-table-component {
li.grid-field-row-component {
display: flex;
flex-flow: row nowrap;
width: 100%;
border: 0;
box-shadow: inset 0 -1px 0 0 $gray-lighter;
margin: 0;
padding: 0;
// Header row
&:first-child {
background: none;
}
}
}

View File

@ -1,4 +1,4 @@
# GridField
# GridFieldTableComponent
This component is used to display structured data in an extendible table layout.

View File

@ -1,16 +1,14 @@
import React from 'react';
import SilverStripeComponent from 'silverstripe-component';
class GridFieldComponent extends SilverStripeComponent {
class GridFieldTableComponent extends SilverStripeComponent {
render() {
return (
<table className='grid-field-component [ table ]'>
<ul className='grid-field-table-component [ list-group ]'>
{this.generateHeader()}
<tbody>
{this.generateRows()}
</tbody>
</table>
</ul>
);
}
@ -56,10 +54,10 @@ class GridFieldComponent extends SilverStripeComponent {
}
GridFieldComponent.propTypes = {
GridFieldTableComponent.propTypes = {
data: React.PropTypes.object,
header: React.PropTypes.object,
rows: React.PropTypes.array
};
export default GridFieldComponent;
export default GridFieldTableComponent;

View File

@ -0,0 +1,5 @@
.grid-field-table-component {
display: flex;
flex-flow: column nowrap;
justify-content: space-between;
}

View File

@ -0,0 +1,66 @@
jest.dontMock('../index');
const React = require('react'),
ReactTestUtils = require('react-addons-test-utils'),
GridFieldTableComponent = require('../index.js').default;
describe('GridFieldTableComponent', () => {
var props;
beforeEach(function () {
props = {
}
});
describe('generateHeader()', function () {
var gridfield;
it('should return props.header if it is set', function () {
props.header = <div className='header'></div>;
gridfield = ReactTestUtils.renderIntoDocument(
<GridFieldTableComponent {...props} />
);
expect(gridfield.generateHeader().props.className).toBe('header');
});
it('should generate and return a header from props.data if it is set', function () {
});
it('should return null if props.header and props.data are both not set', function () {
gridfield = ReactTestUtils.renderIntoDocument(
<GridFieldTableComponent {...props} />
);
expect(gridfield.generateHeader()).toBe(null);
});
});
describe('generateRows()', function () {
var gridfield;
it('should return props.rows if it is set', function () {
props.rows = ['row1'];
gridfield = ReactTestUtils.renderIntoDocument(
<GridFieldTableComponent {...props} />
);
expect(gridfield.generateRows()[0]).toBe('row1');
});
it('should generate and return rows from props.data if it is set', function () {
});
it('should return null if props.rows and props.data are both not set', function () {
gridfield = ReactTestUtils.renderIntoDocument(
<GridFieldTableComponent {...props} />
);
expect(gridfield.generateRows()).toBe(null);
});
});
});

View File

@ -1,3 +0,0 @@
.grid-field-component {
}

View File

@ -1,5 +0,0 @@
jest.dontMock('../index');
describe('GridFieldComponent', () => {
});

View File

@ -0,0 +1,36 @@
#NorthHeaderBreadcrumbs
The breadcrumbs for the current section of the CMS.
## Props
### Crumbs (array)
An array of objects, each object should have a `text` and `href` key.
```
import NorthHeaderBreadcrumbsComponent from 'north-header-breadcrumbs';
...
getBreadcrumbs() {
var breadcrumbs = [
{
text: 'Pages',
href: 'admin/pages'
},
{
text: 'About us',
href: 'admin/pages/show/2'
}
];
return breadcrumbs;
}
render() {
return <NorthHeaderBreadcrumbsComponent crumbs={this.getBreadcrumbs()} />
}
...
```

View File

@ -0,0 +1,40 @@
import React from 'react';
import SilverStripeComponent from 'silverstripe-component';
class NorthHeaderBreadcrumbsComponent extends SilverStripeComponent {
render() {
return (
<div className="cms-content-header-info">
<div className="breadcrumbs-wrapper">
<h2 id="page-title-heading">
{this.getBreadcrumbs()}
</h2>
</div>
</div>
);
}
getBreadcrumbs() {
if (typeof this.props.crumbs === 'undefined') {
return null;
}
var breadcrumbs = this.props.crumbs.map((crumb, index, crumbs) => {
// If its the last item in the array
if (index === crumbs.length - 1) {
return <span key={index} className="crumb last">{crumb.text}</span>;
} else {
return [
<a key={index} className="cms-panel-link crumb" href={crumb.href}>{crumb.text}</a>,
<span className="sep">/</span>
];
}
});
return breadcrumbs;
}
}
export default NorthHeaderBreadcrumbsComponent;

View File

@ -0,0 +1,5 @@
.breadcrumbs-wrapper {
.sep {
margin: 0 4px;
}
}

View File

@ -0,0 +1,40 @@
jest.dontMock('../index');
const React = require('react'),
ReactTestUtils = require('react-addons-test-utils'),
NorthHeaderBreadcrumbsComponent = require('../index').default;
describe('NorthHeaderBreadcrumbsComponent', () => {
var props;
beforeEach(() => {
props = {};
});
describe('getBreadcrumbs()', () => {
var northHeaderBreadcrumbs;
it('should convert the props.crumbs array into jsx to be rendered', () => {
props.crumbs = [
{ text: 'breadcrumb1', href: 'href1'},
{ text: 'breadcrumb2', href: 'href2'}
];
northHeaderBreadcrumbs = ReactTestUtils.renderIntoDocument(
<NorthHeaderBreadcrumbsComponent {...props} />
);
expect(northHeaderBreadcrumbs.getBreadcrumbs()[0][0].props.children).toBe('breadcrumb1');
expect(northHeaderBreadcrumbs.getBreadcrumbs()[0][1].props.children).toBe('/');
expect(northHeaderBreadcrumbs.getBreadcrumbs()[1].props.children).toBe('breadcrumb2');
});
it('should return null if props.crumbs is not set', () => {
northHeaderBreadcrumbs = ReactTestUtils.renderIntoDocument(
<NorthHeaderBreadcrumbsComponent {...props} />
);
expect(northHeaderBreadcrumbs.getBreadcrumbs()).toBe(null);
});
});
});

View File

@ -0,0 +1,5 @@
#NorthHeader
The main header for sections in the CMS.
## Props

View File

@ -0,0 +1,30 @@
import React from 'react';
import NorthHeaderBreadcrumbsComponent from '../north-header-breadcrumbs';
import SilverStripeComponent from 'silverstripe-component';
class NorthHeaderComponent extends SilverStripeComponent {
render() {
return (
<div className="north-header-component">
<NorthHeaderBreadcrumbsComponent crumbs={this.getBreadcrumbs()}/>
</div>
);
}
getBreadcrumbs() {
return [
{
text: 'Campaigns',
href: 'admin/campaigns'
},
{
text: 'March release',
href: 'admin/campaigns/show/1'
}
]
}
}
export default NorthHeaderComponent;

View File

@ -0,0 +1,4 @@
.north-header-component {
@extend .cms-content-header;
width: 100%;
}

View File

@ -0,0 +1,5 @@
jest.dontMock('../index');
describe('NorthHeaderComponent', () => {
});

View File

@ -1,56 +1,15 @@
import React from 'react';
import SilverStripeComponent from 'silverstripe-component';
import NorthHeader from '../../components/north-header';
import GridField from '../../components/grid-field';
import GridFieldHeader from '../../components/grid-field-header';
import GridFieldHeaderCell from '../../components/grid-field-header-cell';
import GridFieldRow from '../../components/grid-field-row';
import GridFieldCell from '../../components/grid-field-cell';
import GridField from '../grid-field';
class CampaignAdminContainer extends SilverStripeComponent {
constructor(props) {
super(props);
// TODO: This will be an AJAX call and it's response stored in state.
this.mockData = {
campaigns: [
{
title: 'SilverStripe 4.0 release',
description: 'All the stuff related to the 4.0 announcement',
changes: 20
},
{
title: 'March release',
description: 'march release stuff',
changes: 2
},
{
title: 'About us',
description: 'The team',
changes: 1345
}
]
};
}
render() {
const columnNames = ['title', 'changes', 'description'];
const headerCells = columnNames.map((columnName, i) => <GridFieldHeaderCell key={i}>{columnName}</GridFieldHeaderCell>);
const header = <GridFieldHeader>{headerCells}</GridFieldHeader>;
const rows = this.mockData.campaigns.map((campaign, i) => {
const cells = columnNames.map((columnName, i) => {
return <GridFieldCell key={i}>{campaign[columnName]}</GridFieldCell>
});
return <GridFieldRow key={i}>{cells}</GridFieldRow>;
});
return (
<div>
<NorthHeader></NorthHeader>
<GridField header={header} rows={rows}></GridField>
<GridField></GridField>
</div>
);
}

View File

@ -0,0 +1,3 @@
# GridField
General purpose component for tabular data.

View File

@ -0,0 +1,56 @@
import React from 'react';
import SilverStripeComponent from 'silverstripe-component';
import GridFieldTable from '../../components/grid-field-table';
import GridFieldHeader from '../../components/grid-field-header';
import GridFieldHeaderCell from '../../components/grid-field-header-cell';
import GridFieldRow from '../../components/grid-field-row';
import GridFieldCell from '../../components/grid-field-cell';
class GridField extends SilverStripeComponent {
constructor(props) {
super(props);
// TODO: This will be an AJAX call and it's response stored in state.
this.mockData = {
campaigns: [
{
title: 'SilverStripe 4.0 release',
description: 'All the stuff related to the 4.0 announcement',
changes: 20
},
{
title: 'March release',
description: 'march release stuff',
changes: 2
},
{
title: 'About us',
description: 'The team',
changes: 1345
}
]
};
}
render() {
const columnNames = ['title', 'changes', 'description'];
const headerCells = columnNames.map((columnName, i) => <GridFieldHeaderCell key={i}>{columnName}</GridFieldHeaderCell>);
const header = <GridFieldHeader>{headerCells}</GridFieldHeader>;
const rows = this.mockData.campaigns.map((campaign, i) => {
const cells = columnNames.map((columnName, i) => {
return <GridFieldCell key={i}>{campaign[columnName]}</GridFieldCell>
});
return <GridFieldRow key={i}>{cells}</GridFieldRow>;
});
return (
<GridFieldTable header={header} rows={rows}></GridFieldTable>
);
}
}
export default GridField;

View File

@ -6,9 +6,10 @@
/** -----------------------------
* components
* ------------------------------ */
@import "../components/grid-field/styles";
@import "../components/grid-field-table/styles";
@import "../components/grid-field-cell/styles";
@import "../components/grid-field-header/styles";
@import "../components/grid-field-header-cell/styles";
@import "../components/grid-field-row/styles";
@import "../components/north-header/styles";
@import "../components/north-header-breadcrumbs/styles";

View File

@ -81,6 +81,7 @@
},
"babel": {
"presets": [
"react",
"es2015"
]
}