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() {
|
render() {
|
||||||
return (
|
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 {
|
.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() {
|
render() {
|
||||||
return (
|
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() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<thead className='grid-field-header-component'>
|
|
||||||
<GridFieldRowComponent>{this.props.children}</GridFieldRowComponent>
|
<GridFieldRowComponent>{this.props.children}</GridFieldRowComponent>
|
||||||
</thead>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ class GridFieldRowComponent extends SilverStripeComponent {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
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.
|
This component is used to display structured data in an extendible table layout.
|
||||||
|
|
@ -1,16 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import SilverStripeComponent from 'silverstripe-component';
|
import SilverStripeComponent from 'silverstripe-component';
|
||||||
|
|
||||||
class GridFieldComponent extends SilverStripeComponent {
|
class GridFieldTableComponent extends SilverStripeComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<table className='grid-field-component [ table ]'>
|
<ul className='grid-field-table-component [ list-group ]'>
|
||||||
{this.generateHeader()}
|
{this.generateHeader()}
|
||||||
<tbody>
|
|
||||||
{this.generateRows()}
|
{this.generateRows()}
|
||||||
</tbody>
|
</ul>
|
||||||
</table>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,10 +54,10 @@ class GridFieldComponent extends SilverStripeComponent {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GridFieldComponent.propTypes = {
|
GridFieldTableComponent.propTypes = {
|
||||||
data: React.PropTypes.object,
|
data: React.PropTypes.object,
|
||||||
header: React.PropTypes.object,
|
header: React.PropTypes.object,
|
||||||
rows: React.PropTypes.array
|
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 React from 'react';
|
||||||
import SilverStripeComponent from 'silverstripe-component';
|
import SilverStripeComponent from 'silverstripe-component';
|
||||||
import NorthHeader from '../../components/north-header';
|
import NorthHeader from '../../components/north-header';
|
||||||
import GridField from '../../components/grid-field';
|
import GridField from '../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';
|
|
||||||
|
|
||||||
class CampaignAdminContainer extends SilverStripeComponent {
|
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() {
|
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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<NorthHeader></NorthHeader>
|
<NorthHeader></NorthHeader>
|
||||||
<GridField header={header} rows={rows}></GridField>
|
<GridField></GridField>
|
||||||
</div>
|
</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
|
* components
|
||||||
* ------------------------------ */
|
* ------------------------------ */
|
||||||
@import "../components/grid-field/styles";
|
@import "../components/grid-field-table/styles";
|
||||||
@import "../components/grid-field-cell/styles";
|
@import "../components/grid-field-cell/styles";
|
||||||
@import "../components/grid-field-header/styles";
|
@import "../components/grid-field-header/styles";
|
||||||
@import "../components/grid-field-header-cell/styles";
|
@import "../components/grid-field-header-cell/styles";
|
||||||
@import "../components/grid-field-row/styles";
|
@import "../components/grid-field-row/styles";
|
||||||
@import "../components/north-header/styles";
|
@import "../components/north-header/styles";
|
||||||
|
@import "../components/north-header-breadcrumbs/styles";
|
||||||
|
@ -81,6 +81,7 @@
|
|||||||
},
|
},
|
||||||
"babel": {
|
"babel": {
|
||||||
"presets": [
|
"presets": [
|
||||||
|
"react",
|
||||||
"es2015"
|
"es2015"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user