mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
GridField and header styles
This commit is contained in:
parent
f8c17bed3b
commit
2abe24f818
15
admin/javascript/src/__mocks__/silverstripe-component.js
Normal file
15
admin/javascript/src/__mocks__/silverstripe-component.js
Normal file
@ -0,0 +1,15 @@
|
||||
import { Component } from 'react';
|
||||
|
||||
export default class SilverStripeComponent extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
||||
}
|
||||
};
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
@ -6,9 +6,7 @@ class GridFieldHeaderComponent extends SilverStripeComponent {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<thead className='grid-field-header-component'>
|
||||
<GridFieldRowComponent>{this.props.children}</GridFieldRowComponent>
|
||||
</thead>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
# GridField
|
||||
# GridFieldTableComponent
|
||||
|
||||
This component is used to display structured data in an extendible table layout.
|
||||
|
@ -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;
|
@ -0,0 +1,5 @@
|
||||
.grid-field-table-component {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: space-between;
|
||||
}
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,3 +0,0 @@
|
||||
.grid-field-component {
|
||||
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
jest.dontMock('../index');
|
||||
|
||||
describe('GridFieldComponent', () => {
|
||||
|
||||
});
|
@ -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()} />
|
||||
}
|
||||
|
||||
...
|
||||
```
|
@ -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;
|
@ -0,0 +1,5 @@
|
||||
.breadcrumbs-wrapper {
|
||||
.sep {
|
||||
margin: 0 4px;
|
||||
}
|
||||
}
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
5
admin/javascript/src/components/north-header/README.md
Normal file
5
admin/javascript/src/components/north-header/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
#NorthHeader
|
||||
|
||||
The main header for sections in the CMS.
|
||||
|
||||
## Props
|
30
admin/javascript/src/components/north-header/index.js
Normal file
30
admin/javascript/src/components/north-header/index.js
Normal 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;
|
4
admin/javascript/src/components/north-header/styles.scss
Normal file
4
admin/javascript/src/components/north-header/styles.scss
Normal file
@ -0,0 +1,4 @@
|
||||
.north-header-component {
|
||||
@extend .cms-content-header;
|
||||
width: 100%;
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
jest.dontMock('../index');
|
||||
|
||||
describe('NorthHeaderComponent', () => {
|
||||
|
||||
});
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
3
admin/javascript/src/sections/grid-field/README.md
Normal file
3
admin/javascript/src/sections/grid-field/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# GridField
|
||||
|
||||
General purpose component for tabular data.
|
56
admin/javascript/src/sections/grid-field/index.js
Normal file
56
admin/javascript/src/sections/grid-field/index.js
Normal 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;
|
@ -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";
|
||||
|
@ -81,6 +81,7 @@
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
"react",
|
||||
"es2015"
|
||||
]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user