mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Add generic React components
Includes moving some components from AssetAdmin
This commit is contained in:
parent
b0ba742c1f
commit
0ca090a391
@ -92,27 +92,27 @@ class CampaignAdmin extends LeftAndMain implements PermissionProvider {
|
||||
"customValidationMessage": "",
|
||||
"attributes": [],
|
||||
"data": {
|
||||
'collectionReadUrl': {
|
||||
'url': 'admin\/campaigns\/items',
|
||||
'method': 'GET'
|
||||
"collectionReadUrl": {
|
||||
"url": "admin\/campaigns\/items",
|
||||
"method": "GET"
|
||||
},
|
||||
'itemReadUrl': {
|
||||
'url': 'admin\/campaigns\/item\/:id',
|
||||
'method': 'GET'
|
||||
"itemReadUrl": {
|
||||
"url": "admin\/campaigns\/item\/:id",
|
||||
"method": "GET"
|
||||
},
|
||||
'itemUpdateUrl': {
|
||||
'url': 'admin\/campaigns\/item\/:id',
|
||||
'method': 'PUT'
|
||||
"itemUpdateUrl": {
|
||||
"url": "admin\/campaigns\/item\/:id",
|
||||
"method": "PUT"
|
||||
},
|
||||
'itemCreateUrl': {
|
||||
'url': 'admin\/campaigns\/item\/:id',
|
||||
'method': 'POST'
|
||||
"itemCreateUrl": {
|
||||
"url": "admin\/campaigns\/item\/:id",
|
||||
"method": "POST"
|
||||
},
|
||||
'itemDeleteUrl': {
|
||||
'url': 'admin\/campaigns\/item\/:id',
|
||||
'method': 'DELETE'
|
||||
"itemDeleteUrl": {
|
||||
"url": "admin\/campaigns\/item\/:id",
|
||||
"method": "DELETE"
|
||||
},
|
||||
'editFormSchemaUrl': 'admin\/campaigns\/schema\/DetailEditForm'
|
||||
"editFormSchemaUrl": "admin\/campaigns\/schema\/DetailEditForm"
|
||||
}
|
||||
}, {
|
||||
"name": "SecurityID",
|
||||
|
@ -18,6 +18,7 @@ if(typeof(ss) == 'undefined' || typeof(ss.i18n) == 'undefined') {
|
||||
"ModelAdmin.REALLYDELETE": "Do you really want to delete?",
|
||||
"ModelAdmin.DELETED": "Deleted",
|
||||
"ModelAdmin.VALIDATIONERROR": "Validation Error",
|
||||
"LeftAndMain.PAGEWASDELETED": "This page was deleted. To edit a page, select it from the left."
|
||||
"LeftAndMain.PAGEWASDELETED": "This page was deleted. To edit a page, select it from the left.",
|
||||
"Campaigns.ADDCAMPAIGN": "Add campaign"
|
||||
});
|
||||
}
|
@ -13,5 +13,6 @@
|
||||
"ModelAdmin.REALLYDELETE": "Do you really want to delete?",
|
||||
"ModelAdmin.DELETED": "Deleted",
|
||||
"ModelAdmin.VALIDATIONERROR": "Validation Error",
|
||||
"LeftAndMain.PAGEWASDELETED": "This page was deleted. To edit a page, select it from the left."
|
||||
"LeftAndMain.PAGEWASDELETED": "This page was deleted. To edit a page, select it from the left.",
|
||||
"Campaigns.ADDCAMPAIGN": "Add campaign"
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
import { Component } from 'react';
|
||||
|
||||
export default class SilverStripeComponent extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
||||
}
|
||||
};
|
37
admin/javascript/src/components/action/README.md
Normal file
37
admin/javascript/src/components/action/README.md
Normal file
@ -0,0 +1,37 @@
|
||||
# Action
|
||||
|
||||
This component is used to display a button which is linked to an action.
|
||||
|
||||
## Props
|
||||
|
||||
### handleClick (function)
|
||||
|
||||
The handler for when a button is clicked, is passed the click event as the only argument.
|
||||
|
||||
### text (string)
|
||||
|
||||
The text to be shown in the button.
|
||||
|
||||
### type (string)
|
||||
|
||||
The type of button to be shown, adds a class to the button.
|
||||
|
||||
Accepted values are:
|
||||
* 'danger'
|
||||
* 'success'
|
||||
* 'primary'
|
||||
* 'link'
|
||||
* 'secondary'
|
||||
* 'complete'
|
||||
|
||||
### icon (string)
|
||||
|
||||
The icon to be used on the button, adds font-icon-{this.props.icon} class to the button. See available icons [here](../../../../fonts/incon-reference.html).
|
||||
|
||||
### loading (boolean)
|
||||
|
||||
If true, replaces the text/icon with a loading icon.
|
||||
|
||||
### disabled (boolean)
|
||||
|
||||
If true, gives the button a visually disabled state and disables click events.
|
109
admin/javascript/src/components/action/index.js
Normal file
109
admin/javascript/src/components/action/index.js
Normal file
@ -0,0 +1,109 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import SilverStripeComponent from '../../SilverStripeComponent';
|
||||
|
||||
class ActionComponent extends SilverStripeComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<button className={this.getButtonClasses()} onClick={this.handleClick}>
|
||||
{this.getLoadingIcon()}
|
||||
{this.props.text}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the necessary button classes based on the given props
|
||||
*
|
||||
* @returns string
|
||||
*/
|
||||
getButtonClasses() {
|
||||
var buttonClasses = 'btn';
|
||||
|
||||
// If there is no text
|
||||
if (typeof this.props.text === 'undefined') {
|
||||
buttonClasses += ' no-text';
|
||||
}
|
||||
|
||||
// Add 'type' class
|
||||
if (this.props.type === 'danger') {
|
||||
buttonClasses += ' btn-danger';
|
||||
} else if (this.props.type === 'success') {
|
||||
buttonClasses += ' btn-success';
|
||||
} else if (this.props.type === 'primary') {
|
||||
buttonClasses += ' btn-primary';
|
||||
} else if (this.props.type === 'link') {
|
||||
buttonClasses += ' btn-link';
|
||||
} else if (this.props.type === 'secondary') {
|
||||
buttonClasses += ' btn-secondary';
|
||||
} else if (this.props.type === 'complete') {
|
||||
buttonClasses += ' btn-success-outline';
|
||||
}
|
||||
|
||||
// Add icon class
|
||||
if (typeof this.props.icon !== 'undefined') {
|
||||
buttonClasses += ` font-icon-${this.props.icon}`;
|
||||
}
|
||||
|
||||
// Add loading class
|
||||
if (this.props.loading === true) {
|
||||
buttonClasses += ' btn--loading';
|
||||
}
|
||||
|
||||
// Add disabled class
|
||||
if (this.props.disabled === true) {
|
||||
buttonClasses += ' disabled';
|
||||
}
|
||||
|
||||
return buttonClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns markup for the loading icon
|
||||
*
|
||||
* @returns object|null
|
||||
*/
|
||||
getLoadingIcon() {
|
||||
if (this.props.loading) {
|
||||
return (
|
||||
<div className="btn__loading-icon" >
|
||||
<svg viewBox="0 0 44 12">
|
||||
<circle cx="6" cy="6" r="6" />
|
||||
<circle cx="22" cy="6" r="6" />
|
||||
<circle cx="38" cy="6" r="6" />
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler triggered when a user clicks the button.
|
||||
*
|
||||
* @param object event
|
||||
* @returns null
|
||||
*/
|
||||
handleClick(event) {
|
||||
if (typeof this.props.handleClick === 'function' && this.props.disabled !== true) {
|
||||
this.props.handleClick(event);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
ActionComponent.propTypes = {
|
||||
type: React.PropTypes.string,
|
||||
icon: React.PropTypes.string,
|
||||
text: React.PropTypes.string
|
||||
};
|
||||
|
||||
export default ActionComponent;
|
123
admin/javascript/src/components/action/styles.scss
Normal file
123
admin/javascript/src/components/action/styles.scss
Normal file
@ -0,0 +1,123 @@
|
||||
// General buttons
|
||||
.btn {
|
||||
height: 32px;
|
||||
margin-right: 1rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
// Button icons
|
||||
.btn[class*="font-icon-"]::before {
|
||||
font-size: 16px;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
margin-right: 6px;
|
||||
line-height: 13px;
|
||||
}
|
||||
|
||||
.no-text[class*="font-icon-"]::before {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
margin-right: 1rem;
|
||||
|
||||
.btn {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
border-left: 1px solid darken($btn-success-bg, 6%);
|
||||
|
||||
&:first-child {
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SVG loading icon
|
||||
.btn__loading-icon {
|
||||
float: left;
|
||||
margin: 0 4px 0 0;
|
||||
height: 20px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: $btn-padding-y;
|
||||
transform: translate(-50%);
|
||||
|
||||
svg {
|
||||
width: 24px;
|
||||
height: 20px;
|
||||
|
||||
circle {
|
||||
width: 4px;
|
||||
height: 5px;
|
||||
animation: loading-icon 1.2s infinite ease-in-out both;
|
||||
fill: $gray;
|
||||
transform-origin: 50% 50%;
|
||||
}
|
||||
|
||||
circle:nth-child(1) {
|
||||
animation-delay: -.32s;
|
||||
}
|
||||
|
||||
circle:nth-child(2) {
|
||||
animation-delay: -.16s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn--loading {
|
||||
> span,
|
||||
&::before {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes loading-icon {
|
||||
0%, 80%, 100% { transform: scale(0); }
|
||||
40% { transform: scale(1); }
|
||||
}
|
||||
|
||||
// Specific button types
|
||||
.btn-link {
|
||||
&:hover,
|
||||
&:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
border-color: transparent;
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
background-color: $gray-lighter;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-success-outline {
|
||||
border-color: lighten($brand-success,10%);
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
color: $brand-success;
|
||||
background-image: none;
|
||||
background-color: transparent;
|
||||
border-color: lighten($brand-success,10%);
|
||||
}
|
||||
|
||||
svg circle {
|
||||
fill: $brand-success;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
border-bottom-color: $btn-success-shadow;
|
||||
|
||||
svg circle {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1 @@
|
||||
|
17
admin/javascript/src/components/form-action/README.md
Normal file
17
admin/javascript/src/components/form-action/README.md
Normal file
@ -0,0 +1,17 @@
|
||||
# FormActionComponent
|
||||
|
||||
Used for form actions. For example a submit button.
|
||||
|
||||
## Props
|
||||
|
||||
### className
|
||||
|
||||
CSS class names to use on the button. Defaults to `btn btn-primary`
|
||||
|
||||
### label (required)
|
||||
|
||||
The text to display on the button.
|
||||
|
||||
### type
|
||||
|
||||
Used for the button's `type` attribute. Defaults to `button`
|
27
admin/javascript/src/components/form-action/index.js
Normal file
27
admin/javascript/src/components/form-action/index.js
Normal file
@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import SilverStripeComponent from '../../SilverStripeComponent';
|
||||
|
||||
class FormActionComponent extends SilverStripeComponent {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<button type={this.props.type} className={this.props.className}>
|
||||
{this.props.label}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
FormActionComponent.propTypes = {
|
||||
className: React.PropTypes.string,
|
||||
label: React.PropTypes.string.isRequired,
|
||||
type: React.PropTypes.string
|
||||
};
|
||||
|
||||
FormActionComponent.defaultProps = {
|
||||
className: 'btn btn-primary',
|
||||
type: 'button'
|
||||
};
|
||||
|
||||
export default FormActionComponent;
|
23
admin/javascript/src/components/form-builder/README.md
Normal file
23
admin/javascript/src/components/form-builder/README.md
Normal file
@ -0,0 +1,23 @@
|
||||
# FormBuilderComponent
|
||||
|
||||
Used to generate forms, made up of field components and actions, from FormFieldSchema data.
|
||||
|
||||
This component will be moved to Framweork or CMS when dependency injection is implemented.
|
||||
|
||||
## PropTypes
|
||||
|
||||
### actions
|
||||
|
||||
Actions the component can dispatch. This should include but is not limited to:
|
||||
|
||||
#### setSchema
|
||||
|
||||
An action to call when the response from fetching schema data is returned. This would normally be a simple action to set the store's `schema` key to the returned data.
|
||||
|
||||
### formSchemaUrl
|
||||
|
||||
The schema URL where the form will be scaffolded from e.g. '/admin/pages/schema/1'.
|
||||
|
||||
### schema
|
||||
|
||||
JSON schema representing the form. Used as the blueprint for generating the form.
|
230
admin/javascript/src/components/form-builder/index.js
Normal file
230
admin/javascript/src/components/form-builder/index.js
Normal file
@ -0,0 +1,230 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import $ from 'jQuery';
|
||||
import * as schemaActions from '../../state/schema/actions';
|
||||
import SilverStripeComponent from '../../SilverStripeComponent';
|
||||
import FormComponent from '../form';
|
||||
import TextField from '../text-field';
|
||||
|
||||
// Using this to map field types to components until we implement dependency injection.
|
||||
var fakeInjector = {
|
||||
|
||||
/**
|
||||
* Components registered with the fake DI container.
|
||||
*/
|
||||
components: {
|
||||
'TextField': TextField
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the component matching the passed component name.
|
||||
* Used when a component type is provided bt the form schema.
|
||||
*
|
||||
* @param string componentName - The name of the component to get from the injector.
|
||||
*
|
||||
* @return object|null
|
||||
*/
|
||||
getComponentByName: function (componentName) {
|
||||
return this.components[componentName];
|
||||
},
|
||||
|
||||
/**
|
||||
* Default data type to component mappings.
|
||||
* Used as a fallback when no component type is provided in the form schema.
|
||||
*
|
||||
* @param string dataType - The data type provided by the form schema.
|
||||
*
|
||||
* @return object|null
|
||||
*/
|
||||
getComponentByDataType: function (dataType) {
|
||||
switch (dataType) {
|
||||
case 'String':
|
||||
return this.components.TextField;
|
||||
case 'Hidden':
|
||||
return this.components.TextField;
|
||||
case 'Text':
|
||||
// Textarea field (not implemented)
|
||||
return null;
|
||||
case 'HTML':
|
||||
// HTML editor field (not implemented)
|
||||
return null;
|
||||
case 'Integer':
|
||||
// Numeric field (not implemented)
|
||||
return null;
|
||||
case 'Decimal':
|
||||
// Numeric field (not implemented)
|
||||
return null;
|
||||
case 'MultiSelect':
|
||||
// Radio field (not implemented)
|
||||
return null;
|
||||
case 'SingleSelect':
|
||||
// Dropdown field (not implemented)
|
||||
return null;
|
||||
case 'Date':
|
||||
// DateTime field (not implemented)
|
||||
return null;
|
||||
case 'DateTime':
|
||||
// DateTime field (not implemented)
|
||||
return null;
|
||||
case 'Time':
|
||||
// DateTime field (not implemented)
|
||||
return null;
|
||||
case 'Boolean':
|
||||
// Checkbox field (not implemented)
|
||||
return null;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class FormBuilderComponent extends SilverStripeComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.formSchemaPromise = null;
|
||||
this.isFetching = false;
|
||||
|
||||
this.fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches data used to generate a form. This can be form schema and or form state data.
|
||||
* When the response comes back the data is saved to state.
|
||||
*
|
||||
* @param boolean schema - If form schema data should be returned in the response.
|
||||
* @param boolean state - If form state data should be returned in the response.
|
||||
*
|
||||
* @return object - Promise from the AJAX request.
|
||||
*/
|
||||
fetch(schema = true, state = false) {
|
||||
var headerValues = [];
|
||||
|
||||
if (this.isFetching === true) {
|
||||
return this.formSchemaPromise;
|
||||
}
|
||||
|
||||
if (schema === true) {
|
||||
headerValues.push('schema');
|
||||
}
|
||||
|
||||
if (state === true) {
|
||||
headerValues.push('state');
|
||||
}
|
||||
|
||||
this.formSchemaPromise = $.ajax({
|
||||
method: 'GET',
|
||||
headers: { 'X-FormSchema-Request': headerValues.join() },
|
||||
url: this.props.formSchemaUrl
|
||||
}).done((data, status, xhr) => {
|
||||
this.isFetching = false;
|
||||
this.props.actions.setSchema(data);
|
||||
});
|
||||
|
||||
this.isFetching = true;
|
||||
|
||||
return this.formSchemaPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets form schema for the FormBuilder.
|
||||
*
|
||||
* @return object|undefined
|
||||
*/
|
||||
getFormSchema() {
|
||||
return this.props.schema.forms.find(function (form) {
|
||||
return form.schema.schema_url === this.props.formSchemaUrl;
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a list of schema fields to their React Component.
|
||||
* Only top level form fields are handled here, composite fields (TabSets etc),
|
||||
* are responsible for mapping and rendering their children.
|
||||
*
|
||||
* @param array fields
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
mapFieldsToComponents(fields) {
|
||||
return fields.map((field, i) => {
|
||||
|
||||
const Component = field.component !== null
|
||||
? fakeInjector.getComponentByName(field.component)
|
||||
: fakeInjector.getComponentByDataType(field.type);
|
||||
|
||||
if (Component === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Props which every form field receives.
|
||||
let props = {
|
||||
attributes: field.attributes,
|
||||
data: field.data,
|
||||
description: field.description,
|
||||
extraClass: field.extraClass,
|
||||
fields: field.children,
|
||||
id: field.id,
|
||||
name: field.name
|
||||
};
|
||||
|
||||
// Structural fields (like TabSets) are not posted back to the server and don't receive some props.
|
||||
if (field.type !== 'Structural') {
|
||||
props.rightTitle = field.rightTitle;
|
||||
props.leftTitle = field.leftTitle;
|
||||
props.readOnly = field.readOnly;
|
||||
props.disabled = field.disabled;
|
||||
props.customValidationMessage = field.customValidationMessage;
|
||||
}
|
||||
|
||||
// Dropdown and Radio fields need some options...
|
||||
if (field.type === 'MultiSelect' || field.type === 'SingleSelect') {
|
||||
props.source = field.source;
|
||||
}
|
||||
|
||||
return <Component key={i} {...props} />
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
// If the response from fetching the initial data
|
||||
// hasn't come back yet, don't render anything.
|
||||
if (this.props.schema.forms.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const schema = this.getFormSchema().schema;
|
||||
|
||||
const formProps = {
|
||||
actions: schema.actions,
|
||||
attributes: schema.attributes,
|
||||
data: schema.data,
|
||||
fields: schema.fields,
|
||||
mapFieldsToComponents: this.mapFieldsToComponents
|
||||
};
|
||||
|
||||
return <FormComponent {...formProps} />
|
||||
}
|
||||
}
|
||||
|
||||
FormBuilderComponent.propTypes = {
|
||||
actions: React.PropTypes.object.isRequired,
|
||||
formSchemaUrl: React.PropTypes.string.isRequired,
|
||||
schema: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
schema: state.schema
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators(schemaActions, dispatch)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(FormBuilderComponent);
|
@ -0,0 +1,31 @@
|
||||
jest.unmock('../../../SilverStripeComponent');
|
||||
jest.unmock('../');
|
||||
|
||||
import { FormBuilderComponent } from '../';
|
||||
|
||||
describe('FormBuilderComponent', () => {
|
||||
|
||||
describe('getFormSchema()', () => {
|
||||
|
||||
var formBuilder;
|
||||
|
||||
beforeEach(() => {
|
||||
const props = {
|
||||
store: {
|
||||
getState: () => {}
|
||||
},
|
||||
actions: {},
|
||||
formSchemaUrl: 'admin/assets/schema/1',
|
||||
schema: { forms: [{ schema: { id: '1', schema_url: 'admin/assets/schema/1' } }] }
|
||||
};
|
||||
|
||||
formBuilder = new FormBuilderComponent(props);
|
||||
});
|
||||
|
||||
it('should return the form schema for the FormBuilder', () => {
|
||||
const form = formBuilder.getFormSchema();
|
||||
expect(form.schema.id).toBe('1');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
37
admin/javascript/src/components/form/README.md
Normal file
37
admin/javascript/src/components/form/README.md
Normal file
@ -0,0 +1,37 @@
|
||||
# FormComponent
|
||||
|
||||
The FormComponent is used to render forms in SilverStripe. The only time you should need to use `FormComponent` directly is when you're composing custom layouts. Forms can be automatically generated from a schema using the `FormBuilder` component.
|
||||
|
||||
This component should be moved to Framework when dependency injection is implemented.
|
||||
|
||||
## Props
|
||||
|
||||
### actions (required)
|
||||
|
||||
A list of objects representing the form actions. For example the submit button.
|
||||
|
||||
### attributes (required)
|
||||
|
||||
An object of HTML attributes for the form. For example:
|
||||
|
||||
```js
|
||||
{
|
||||
action: 'admin/assets/EditForm',
|
||||
class: 'cms-edit-form root-form AssetAdmin LeftAndMain',
|
||||
enctype: 'multipart/form-data',
|
||||
id: 'Form_EditForm',
|
||||
method: 'POST'
|
||||
}
|
||||
```
|
||||
|
||||
### data
|
||||
|
||||
Ad hoc data passed to the front-end from the server.
|
||||
|
||||
### fields (required)
|
||||
|
||||
A list of field objects to display in the form. These objects should be transformed to Components using the `this.props.mapFieldsToComponents` method.
|
||||
|
||||
### mapFieldsToComponents (required)
|
||||
|
||||
A function that maps each schema field (`this.props.fields`) to the component responsibe for render it.
|
59
admin/javascript/src/components/form/index.js
Normal file
59
admin/javascript/src/components/form/index.js
Normal file
@ -0,0 +1,59 @@
|
||||
import React from 'react';
|
||||
import SilverStripeComponent from '../../SilverStripeComponent';
|
||||
import FormActionComponent from '../form-action';
|
||||
|
||||
class FormComponent extends SilverStripeComponent {
|
||||
|
||||
/**
|
||||
* Gets the components responsible for perfoming actions on the form.
|
||||
* For example form submission.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
getFormActionComponents() {
|
||||
return this.props.actions.map((action) => {
|
||||
return <FormActionComponent {...action} />;
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const attr = this.props.attributes;
|
||||
const fields = this.props.mapFieldsToComponents(this.props.fields);
|
||||
const actions = this.getFormActionComponents();
|
||||
|
||||
return (
|
||||
<form id={attr.id} className={attr.className} encType={attr.enctype} method={attr.method} action={attr.action}>
|
||||
{fields &&
|
||||
<fieldset className='form-group'>
|
||||
{fields}
|
||||
</fieldset>
|
||||
}
|
||||
|
||||
{actions &&
|
||||
<div className='actions-fix-btm'>
|
||||
<div className='btn-group' role='group'>
|
||||
{actions}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
FormComponent.propTypes = {
|
||||
actions: React.PropTypes.array.isRequired,
|
||||
attributes: React.PropTypes.shape({
|
||||
action: React.PropTypes.string.isRequired,
|
||||
className: React.PropTypes.string.isRequired,
|
||||
enctype: React.PropTypes.string.isRequired,
|
||||
id: React.PropTypes.string.isRequired,
|
||||
method: React.PropTypes.string.isRequired
|
||||
}),
|
||||
data: React.PropTypes.array,
|
||||
fields: React.PropTypes.array.isRequired,
|
||||
mapFieldsToComponents: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default FormComponent;
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import SilverStripeComponent from 'silverstripe-component';
|
||||
import SilverStripeComponent from '../../SilverStripeComponent';
|
||||
|
||||
class GridFieldCellComponent extends SilverStripeComponent {
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import SilverStripeComponent from 'silverstripe-component';
|
||||
import SilverStripeComponent from '../../SilverStripeComponent';
|
||||
|
||||
class GridFieldHeaderCellComponent extends SilverStripeComponent {
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import SilverStripeComponent from 'silverstripe-component';
|
||||
import SilverStripeComponent from '../../SilverStripeComponent';
|
||||
import GridFieldRowComponent from '../grid-field-row';
|
||||
|
||||
class GridFieldHeaderComponent extends SilverStripeComponent {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import SilverStripeComponent from 'silverstripe-component';
|
||||
import SilverStripeComponent from '../../SilverStripeComponent';
|
||||
|
||||
class GridFieldRowComponent extends SilverStripeComponent {
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import SilverStripeComponent from 'silverstripe-component';
|
||||
import SilverStripeComponent from '../../SilverStripeComponent';
|
||||
|
||||
class GridFieldTableComponent extends SilverStripeComponent {
|
||||
|
||||
|
48
admin/javascript/src/components/hidden-field/index.js
Normal file
48
admin/javascript/src/components/hidden-field/index.js
Normal file
@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import SilverStripeComponent from '../../SilverStripeComponent';
|
||||
|
||||
class HiddenFieldComponent extends SilverStripeComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className='field hidden'>
|
||||
<input {...this.getInputProps()} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
getInputProps() {
|
||||
return {
|
||||
className: ['hidden', this.props.extraClass].join(' '),
|
||||
id: this.props.name,
|
||||
name: this.props.name,
|
||||
onChange: this.props.onChange,
|
||||
type: 'hidden',
|
||||
value: this.props.value
|
||||
};
|
||||
}
|
||||
|
||||
handleChange(event) {
|
||||
if (typeof this.props.onChange === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.onChange();
|
||||
}
|
||||
}
|
||||
|
||||
HiddenFieldComponent.propTypes = {
|
||||
label: React.PropTypes.string,
|
||||
extraClass: React.PropTypes.string,
|
||||
name: React.PropTypes.string.isRequired,
|
||||
onChange: React.PropTypes.func,
|
||||
value: React.PropTypes.string
|
||||
};
|
||||
|
||||
export default HiddenFieldComponent;
|
21
admin/javascript/src/components/hidden-field/readme.md
Normal file
21
admin/javascript/src/components/hidden-field/readme.md
Normal file
@ -0,0 +1,21 @@
|
||||
# Hidden Field Component
|
||||
|
||||
Generates an `<input type="hidden" />`
|
||||
|
||||
## Props
|
||||
|
||||
### extraClass
|
||||
|
||||
Addition CSS classes to apply to the `<input>` element.
|
||||
|
||||
### name (required)
|
||||
|
||||
Used for the field's `name` attribute.
|
||||
|
||||
### onChange
|
||||
|
||||
Handler function called when the field's value changes.
|
||||
|
||||
### value
|
||||
|
||||
The field's value.
|
3
admin/javascript/src/components/hidden-field/styles.scss
Normal file
3
admin/javascript/src/components/hidden-field/styles.scss
Normal file
@ -0,0 +1,3 @@
|
||||
.field.hidden {
|
||||
display: none;
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import SilverStripeComponent from 'silverstripe-component';
|
||||
import SilverStripeComponent from '../../SilverStripeComponent';
|
||||
|
||||
class NorthHeaderBreadcrumbsComponent extends SilverStripeComponent {
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import NorthHeaderBreadcrumbsComponent from '../north-header-breadcrumbs';
|
||||
import SilverStripeComponent from 'silverstripe-component';
|
||||
import SilverStripeComponent from '../../SilverStripeComponent';
|
||||
|
||||
class NorthHeaderComponent extends SilverStripeComponent {
|
||||
|
||||
|
55
admin/javascript/src/components/text-field/index.js
Normal file
55
admin/javascript/src/components/text-field/index.js
Normal file
@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
import SilverStripeComponent from '../../SilverStripeComponent';
|
||||
|
||||
class TextFieldComponent extends SilverStripeComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className='field text'>
|
||||
{this.props.label &&
|
||||
<label className='left' htmlFor={'gallery_' + this.props.name}>
|
||||
{this.props.label}
|
||||
</label>
|
||||
}
|
||||
<div className='middleColumn'>
|
||||
<input {...this.getInputProps()} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
getInputProps() {
|
||||
return {
|
||||
className: ['text', this.props.extraClass].join(' '),
|
||||
id: `gallery_${this.props.name}`,
|
||||
name: this.props.name,
|
||||
onChange: this.props.onChange,
|
||||
type: 'text',
|
||||
value: this.props.value
|
||||
};
|
||||
}
|
||||
|
||||
handleChange(event) {
|
||||
if (typeof this.props.onChange === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.onChange();
|
||||
}
|
||||
}
|
||||
|
||||
TextFieldComponent.propTypes = {
|
||||
label: React.PropTypes.string,
|
||||
extraClass: React.PropTypes.string,
|
||||
name: React.PropTypes.string.isRequired,
|
||||
onChange: React.PropTypes.func,
|
||||
value: React.PropTypes.string
|
||||
};
|
||||
|
||||
export default TextFieldComponent;
|
25
admin/javascript/src/components/text-field/readme.md
Normal file
25
admin/javascript/src/components/text-field/readme.md
Normal file
@ -0,0 +1,25 @@
|
||||
# Text Field Component
|
||||
|
||||
Generates an editable text field.
|
||||
|
||||
## Props
|
||||
|
||||
### label
|
||||
|
||||
The label text to display with the field.
|
||||
|
||||
### extraClass
|
||||
|
||||
Addition CSS classes to apply to the `<input>` element.
|
||||
|
||||
### name (required)
|
||||
|
||||
Used for the field's `name` attribute.
|
||||
|
||||
### onChange
|
||||
|
||||
Handler function called when the field's value changes.
|
||||
|
||||
### value
|
||||
|
||||
The field's value.
|
@ -0,0 +1,37 @@
|
||||
jest.unmock('react');
|
||||
jest.unmock('react-addons-test-utils');
|
||||
jest.unmock('../');
|
||||
|
||||
import React from 'react';
|
||||
import ReactTestUtils from 'react-addons-test-utils';
|
||||
import TextFieldComponent from '../';
|
||||
|
||||
describe('TextFieldComponent', function() {
|
||||
|
||||
var props;
|
||||
|
||||
beforeEach(function () {
|
||||
props = {
|
||||
label: '',
|
||||
name: '',
|
||||
value: '',
|
||||
onChange: jest.genMockFunction()
|
||||
};
|
||||
});
|
||||
|
||||
describe('handleChange()', function () {
|
||||
var textField;
|
||||
|
||||
beforeEach(function () {
|
||||
textField = ReactTestUtils.renderIntoDocument(
|
||||
<TextFieldComponent {...props} />
|
||||
);
|
||||
});
|
||||
|
||||
it('should call the onChange function on props', function () {
|
||||
textField.handleChange();
|
||||
|
||||
expect(textField.props.onChange.mock.calls.length).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,20 +1,39 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import SilverStripeComponent from 'silverstripe-component';
|
||||
import NorthHeader from '../../components/north-header';
|
||||
import GridField from '../grid-field';
|
||||
import ActionButton from 'action-button';
|
||||
import i18n from 'i18n';
|
||||
import NorthHeader from 'north-header';
|
||||
import GridField from 'grid-field';
|
||||
|
||||
class CampaignAdminContainer extends SilverStripeComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.addCampaign = this.addCampaign.bind(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<NorthHeader></NorthHeader>
|
||||
<GridField></GridField>
|
||||
<NorthHeader />
|
||||
|
||||
<ActionButton
|
||||
text={i18n._t('Campaigns.ADDCAMPAIGN')}
|
||||
type={'secondary'}
|
||||
icon={'plus-circled'}
|
||||
handleClick={this.addCampaign} />
|
||||
|
||||
<GridField />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
addCampaign() {
|
||||
//Add campaign
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
CampaignAdminContainer.propTypes = {
|
||||
|
@ -1,3 +0,0 @@
|
||||
.CampaignAdmin {
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import SilverStripeComponent from 'silverstripe-component';
|
||||
import SilverStripeComponent from '../../SilverStripeComponent';
|
||||
import GridFieldTable from '../../components/grid-field-table';
|
||||
import GridFieldHeader from '../../components/grid-field-header';
|
||||
import GridFieldHeaderCell from '../../components/grid-field-header-cell';
|
||||
|
5
admin/javascript/src/state/schema/README.md
Normal file
5
admin/javascript/src/state/schema/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Schema state
|
||||
|
||||
Manages state associated with the FormFieldSchema.
|
||||
|
||||
When dependency injection is implemented, this will be moved into either Framework or CMS. We can't moveit there sooner because there is no way of extending state.
|
5
admin/javascript/src/state/schema/action-types.js
Normal file
5
admin/javascript/src/state/schema/action-types.js
Normal file
@ -0,0 +1,5 @@
|
||||
const ACTION_TYPES = {
|
||||
SET_SCHEMA: 'SET_SCHEMA'
|
||||
};
|
||||
|
||||
export default ACTION_TYPES;
|
15
admin/javascript/src/state/schema/actions.js
Normal file
15
admin/javascript/src/state/schema/actions.js
Normal file
@ -0,0 +1,15 @@
|
||||
import ACTION_TYPES from './action-types';
|
||||
|
||||
/**
|
||||
* Sets the schema being used to generate the curent layout.
|
||||
*
|
||||
* @param string schema - JSON schema for the layout.
|
||||
*/
|
||||
export function setSchema(schema) {
|
||||
return (dispatch, getState) => {
|
||||
return dispatch ({
|
||||
type: ACTION_TYPES.SET_SCHEMA,
|
||||
payload: schema
|
||||
});
|
||||
}
|
||||
}
|
33
admin/javascript/src/state/schema/reducer.js
Normal file
33
admin/javascript/src/state/schema/reducer.js
Normal file
@ -0,0 +1,33 @@
|
||||
import deepFreeze from 'deep-freeze';
|
||||
import ACTION_TYPES from './action-types';
|
||||
|
||||
const initialState = deepFreeze({
|
||||
forms: []
|
||||
});
|
||||
|
||||
export default function schemaReducer(state = initialState, action = null) {
|
||||
|
||||
switch (action.type) {
|
||||
|
||||
case ACTION_TYPES.SET_SCHEMA:
|
||||
if (state.forms.length === 0) {
|
||||
return deepFreeze(Object.assign({}, state, { forms: [action.payload] }));
|
||||
}
|
||||
|
||||
// Replace the form which has a matching `schema.id` property.
|
||||
return deepFreeze(Object.assign({}, state, {
|
||||
forms: state.forms.map((form) => {
|
||||
if (form.schema.id === action.payload.schema.id) {
|
||||
// Only replace the `schema` key incase other actions have updated other keys.
|
||||
return Object.assign({}, form, action.payload);
|
||||
}
|
||||
|
||||
return form;
|
||||
})
|
||||
}));
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
}
|
72
admin/javascript/src/state/schema/tests/reducer-test.js
Normal file
72
admin/javascript/src/state/schema/tests/reducer-test.js
Normal file
@ -0,0 +1,72 @@
|
||||
jest.unmock('deep-freeze');
|
||||
jest.unmock('../action-types.js');
|
||||
jest.unmock('../reducer.js');
|
||||
|
||||
import schemaReducer from '../reducer.js';
|
||||
import ACTION_TYPES from '../action-types';
|
||||
|
||||
describe('schemaReducer', () => {
|
||||
|
||||
describe('SET_SCHEMA', () => {
|
||||
|
||||
it('should create a new form when none exist', () => {
|
||||
const initialState = { forms: [] };
|
||||
const serverResponse = { id : 'TestForm' };
|
||||
|
||||
const nextState = schemaReducer(initialState, {
|
||||
type: ACTION_TYPES.SET_SCHEMA,
|
||||
payload: { schema: serverResponse }
|
||||
});
|
||||
|
||||
expect(nextState.forms.length).toBe(1);
|
||||
expect(JSON.stringify(nextState.forms[0])).toBe(JSON.stringify({ schema: { id: 'TestForm' } }));
|
||||
});
|
||||
|
||||
it('should update an existing form', () => {
|
||||
const initialState = {
|
||||
forms: [{
|
||||
schema: {
|
||||
id: 'TestForm',
|
||||
name: 'TestForm'
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
const serverResponse = { id: 'TestForm', name: 'BetterTestForm' };
|
||||
|
||||
const nextState = schemaReducer(initialState, {
|
||||
type: ACTION_TYPES.SET_SCHEMA,
|
||||
payload: { schema: serverResponse }
|
||||
});
|
||||
|
||||
expect(nextState.forms.length).toBe(1);
|
||||
expect(JSON.stringify(nextState.forms[0])).toBe(JSON.stringify({ schema: { id: 'TestForm', name: 'BetterTestForm' } }));
|
||||
});
|
||||
|
||||
it("should only update the the form's 'schema' key", () => {
|
||||
const initialState = {
|
||||
forms: [{
|
||||
schema: {
|
||||
id: 'TestForm',
|
||||
name: 'TestForm'
|
||||
},
|
||||
state: {
|
||||
error: 'Oops!'
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
const serverResponse = { id: 'TestForm', name: 'BetterTestForm' };
|
||||
|
||||
const nextState = schemaReducer(initialState, {
|
||||
type: ACTION_TYPES.SET_SCHEMA,
|
||||
payload: { schema: serverResponse }
|
||||
});
|
||||
|
||||
expect(nextState.forms.length).toBe(1);
|
||||
expect(JSON.stringify(nextState.forms[0])).toBe(JSON.stringify({ schema: { id: 'TestForm', name: 'BetterTestForm' }, state: { error: 'Oops!' } }));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
@ -13,3 +13,5 @@
|
||||
@import "../components/grid-field-row/styles";
|
||||
@import "../components/north-header/styles";
|
||||
@import "../components/north-header-breadcrumbs/styles";
|
||||
@import "../components/action/styles";
|
||||
@import "../components/hidden-field/styles";
|
||||
|
@ -326,7 +326,7 @@ form.small .field, .field.small {
|
||||
.chzn-container-single .chzn-single {
|
||||
height: 32px;
|
||||
line-height: 30px; /* not relative, as then we'd had to redo most of chzn */
|
||||
font-size: $font-base-size;
|
||||
font-size: $font-size-root;
|
||||
background-image: linear-gradient(#efefef, #fff 10%, #fff 90%, #efefef);
|
||||
|
||||
&:hover, &:focus, &:active {
|
||||
@ -524,7 +524,7 @@ form.small .field, .field.small {
|
||||
|
||||
&.ss-ui-button-small {
|
||||
.ui-button-text {
|
||||
font-size: $font-base-size - 2;
|
||||
font-size: $font-size-sm;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,6 @@
|
||||
padding: $grid-y*1.5 8px;
|
||||
position: relative;
|
||||
vertical-align: middle;
|
||||
font-size: $font-base-size;
|
||||
transition: padding .2s;
|
||||
min-height: 52px;
|
||||
transition: padding .2s;
|
||||
@ -55,7 +54,6 @@
|
||||
|
||||
span {
|
||||
font-weight: bold;
|
||||
font-size: $font-base-size;
|
||||
line-height: 16px;
|
||||
padding: 6px 0;
|
||||
margin-left: 32px;
|
||||
@ -65,7 +63,7 @@
|
||||
.cms-login-status {
|
||||
padding: $grid-y*1.5 8px;
|
||||
line-height: 16px;
|
||||
font-size: $font-base-size - 1;
|
||||
font-size: $font-size-sm;
|
||||
min-height: 28px;
|
||||
transition: padding .2s;
|
||||
|
||||
@ -278,7 +276,6 @@
|
||||
display: block;
|
||||
line-height: $grid-y * 2;
|
||||
min-height: 50px;
|
||||
font-size: $font-base-size;
|
||||
color: $color-text-default;
|
||||
padding: (2 * $grid-y + 1) 5px (2 * $grid-y + 1) 8px;
|
||||
background-color: $base-menu-bg;
|
||||
|
@ -127,11 +127,11 @@ Used in side panels and action tabs
|
||||
line-height: $grid-y * 2;
|
||||
}
|
||||
h3 {
|
||||
font-size: $font-base-size + 1;
|
||||
font-size: $font-size-root;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: $font-base-size;
|
||||
font-size: $font-size-root -1;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
|
@ -69,8 +69,6 @@
|
||||
|
||||
// Reset the font and vertical alignment.
|
||||
@mixin reset-font {
|
||||
font: inherit;
|
||||
font-size: 100%;
|
||||
vertical-align: baseline; }
|
||||
|
||||
// Resets the outline when focus.
|
||||
|
@ -37,11 +37,6 @@ body.cms {
|
||||
}
|
||||
}
|
||||
|
||||
body .ui-widget {
|
||||
font-family: $font-family;
|
||||
font-size: $font-base-size;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
@ -111,7 +106,7 @@ body.cms {
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: $font-base-size + 2;
|
||||
font-size: $font-size-h4;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
margin-bottom: $grid-x;
|
||||
@ -1109,11 +1104,11 @@ body.cms {
|
||||
line-height: $grid-y * 2;
|
||||
}
|
||||
h3 {
|
||||
font-size: $font-base-size + 1;
|
||||
font-size: $font-size-h5;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: $font-base-size;
|
||||
font-size: $font-size-h5;
|
||||
margin:5px 0;
|
||||
}
|
||||
|
||||
@ -1131,7 +1126,7 @@ body.cms {
|
||||
label {
|
||||
float: none;
|
||||
width: auto;
|
||||
font-size: $font-base-size;
|
||||
font-size: $font-size-root;
|
||||
padding: 0 $grid-x 4px 0;
|
||||
}
|
||||
|
||||
@ -1441,7 +1436,7 @@ form.member-profile-form {
|
||||
font-style: normal;
|
||||
}
|
||||
.toggle {
|
||||
font-size: $font-base-size - 1;
|
||||
font-size: $font-size-sm;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1669,7 +1664,7 @@ form.member-profile-form {
|
||||
|
||||
// Titlebar for pop-up dialog.
|
||||
.ui-dialog-titlebar.ui-widget-header {
|
||||
font-size: $font-base-size+2;
|
||||
font-size: $font-size-root +1;
|
||||
padding: 0;
|
||||
border:none;
|
||||
background: transparent url(../images/textures/cms_content_header.png) repeat;
|
||||
@ -2019,7 +2014,7 @@ body.cms-dialog {
|
||||
|
||||
.flyout {
|
||||
height: 26px - 2*4px; // minus padding
|
||||
font-size: $font-base-size+2;
|
||||
font-size: $font-size-root +1;
|
||||
font-weight: bold;
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
|
@ -4,7 +4,6 @@
|
||||
* Contains the basic typography related styles for the admin interface.
|
||||
*/
|
||||
body, html {
|
||||
font-size: $font-base-size;
|
||||
line-height: $grid-y * 2;
|
||||
font-family: $font-family;
|
||||
color: $color-text;
|
||||
@ -18,22 +17,9 @@ body, html {
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: $font-base-size + 6;
|
||||
line-height: $grid-y * 3;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: $font-base-size + 4;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: $font-base-size + 2;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: $font-base-size;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: $grid-y * 2;
|
||||
margin-bottom: $grid-y * 2;
|
||||
|
@ -12,7 +12,7 @@
|
||||
.ui-widget-content,
|
||||
.ui-widget {
|
||||
color: $color-text;
|
||||
font-size: $font-base-size;
|
||||
font-size: 1em;
|
||||
font-family: $font-family;
|
||||
border: 0;
|
||||
}
|
||||
@ -32,8 +32,6 @@
|
||||
text-shadow: lighten($color-base, 10%) 1px 1px 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
& a.ui-dialog-titlebar-close {
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
@ -65,15 +63,6 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ui-widget input,
|
||||
.ui-widget select,
|
||||
.ui-widget textarea,
|
||||
.ui-widget button {
|
||||
color: $color-text;
|
||||
font-size: $font-base-size;
|
||||
font-family: $font-family;
|
||||
}
|
||||
|
||||
.ui-accordion {
|
||||
.ui-accordion-header {
|
||||
border-color: $color-button-generic-border;
|
||||
|
@ -26,17 +26,17 @@
|
||||
//
|
||||
// Grayscale and brand colors for use across Bootstrap.
|
||||
|
||||
// $gray-dark: #373a3c;
|
||||
// $gray: #55595c;
|
||||
// $gray-light: #818a91;
|
||||
$gray-dark: #4f5861;
|
||||
$gray: #55595c;
|
||||
$gray-light: #d3d9dd;
|
||||
$gray-lighter: #e8e9ea;
|
||||
// $gray-lightest: #f7f7f9;
|
||||
//
|
||||
// $brand-primary: #0275d8;
|
||||
// $brand-success: #5cb85c;
|
||||
$brand-success: #3fa142;
|
||||
// $brand-info: #5bc0de;
|
||||
// $brand-warning: #f0ad4e;
|
||||
// $brand-danger: #d9534f;
|
||||
$brand-danger: #D40404;
|
||||
|
||||
|
||||
// Options
|
||||
@ -142,26 +142,26 @@ $gray-lighter: #e8e9ea;
|
||||
//
|
||||
// Font, line-height, and color for body text, headings, and more.
|
||||
|
||||
// $font-family-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
// $font-family-serif: Georgia, "Times New Roman", Times, serif;
|
||||
// $font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
// $font-family-base: $font-family-sans-serif;
|
||||
$font-family-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
$font-family-serif: Georgia, "Times New Roman", Times, serif;
|
||||
$font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
$font-family-base: $font-family-sans-serif;
|
||||
|
||||
// Pixel value used to responsively scale all typography. Applied to the `<html>` element.
|
||||
// $font-size-root: 16px;
|
||||
//
|
||||
// $font-size-base: 1rem;
|
||||
// $font-size-lg: 1.25rem;
|
||||
// $font-size-sm: .875rem;
|
||||
// $font-size-xs: .75rem;
|
||||
//
|
||||
// $font-size-h1: 2.5rem;
|
||||
// $font-size-h2: 2rem;
|
||||
// $font-size-h3: 1.75rem;
|
||||
// $font-size-h4: 1.5rem;
|
||||
// $font-size-h5: 1.25rem;
|
||||
// $font-size-h6: 1rem;
|
||||
//
|
||||
$font-size-root: 13px;
|
||||
|
||||
$font-size-base: 1rem;
|
||||
$font-size-lg: 1.23rem; /* 16px */
|
||||
$font-size-sm: .846rem; /* 11px */
|
||||
$font-size-xs: .769rem; /* 10px */
|
||||
|
||||
$font-size-h1: 2.5rem;
|
||||
$font-size-h2: 18px; /* 2rem; */
|
||||
$font-size-h3: 16px; /* 1.75rem; */
|
||||
$font-size-h4: 14px; /* 1.5rem; */
|
||||
$font-size-h5: 13px; /* 1.25rem; */
|
||||
$font-size-h6: 1rem;
|
||||
|
||||
// $display1-size: 6rem;
|
||||
// $display2-size: 5.5rem;
|
||||
// $display3-size: 4.5rem;
|
||||
@ -171,9 +171,9 @@ $gray-lighter: #e8e9ea;
|
||||
// $display2-weight: 300;
|
||||
// $display3-weight: 300;
|
||||
// $display4-weight: 300;
|
||||
//
|
||||
// $line-height: 1.5;
|
||||
//
|
||||
|
||||
$line-height: 1.538;
|
||||
|
||||
// $headings-margin-bottom: ($spacer / 2);
|
||||
// $headings-font-family: inherit;
|
||||
// $headings-font-weight: 500;
|
||||
@ -239,34 +239,40 @@ $gray-lighter: #e8e9ea;
|
||||
//
|
||||
// For each of Bootstrap's buttons, define text, background and border color.
|
||||
|
||||
// $btn-padding-x: 1rem;
|
||||
// $btn-padding-y: .375rem;
|
||||
// $btn-font-weight: normal;
|
||||
//
|
||||
// $btn-primary-color: #fff;
|
||||
$btn-padding-x: 1.1rem;
|
||||
$btn-padding-y: .3846rem;
|
||||
$btn-font-weight: normal;
|
||||
|
||||
$btn-primary-color: #fff;
|
||||
// $btn-primary-bg: $brand-primary;
|
||||
// $btn-primary-border: $btn-primary-bg;
|
||||
//
|
||||
// $btn-secondary-color: $gray-dark;
|
||||
// $btn-secondary-bg: #fff;
|
||||
// $btn-secondary-border: #ccc;
|
||||
//
|
||||
|
||||
$btn-secondary-color: $gray-dark;
|
||||
$btn-secondary-bg: transparent;
|
||||
$btn-secondary-border: transparent;
|
||||
|
||||
// $btn-info-color: #fff;
|
||||
// $btn-info-bg: $brand-info;
|
||||
// $btn-info-border: $btn-info-bg;
|
||||
//
|
||||
// $btn-success-color: #fff;
|
||||
// $btn-success-bg: $brand-success;
|
||||
// $btn-success-border: $btn-success-bg;
|
||||
//
|
||||
|
||||
$btn-success-color: #fff;
|
||||
$btn-success-bg: $brand-success;
|
||||
$btn-success-border: $btn-success-bg;
|
||||
$btn-success-shadow: darken($btn-success-bg, 6%);
|
||||
|
||||
$btn-complete-color: #555;
|
||||
$btn-complete-bg: $brand-success;
|
||||
$btn-complete-border: $gray-light;
|
||||
$btn-complete-shadow: darken($btn-success-bg, 6%);
|
||||
|
||||
// $btn-warning-color: #fff;
|
||||
// $btn-warning-bg: $brand-warning;
|
||||
// $btn-warning-border: $btn-warning-bg;
|
||||
//
|
||||
// $btn-danger-color: #fff;
|
||||
// $btn-danger-bg: $brand-danger;
|
||||
// $btn-danger-border: $btn-danger-bg;
|
||||
//
|
||||
|
||||
$btn-danger-color: $brand-danger;
|
||||
$btn-danger-bg: transparent;
|
||||
$btn-danger-border: transparent;
|
||||
|
||||
// $btn-link-disabled-color: $gray-light;
|
||||
//
|
||||
// $btn-padding-x-sm: .75rem;
|
||||
|
@ -98,7 +98,6 @@ $tab-panel-texture-background: $tab-panel-texture-color url(../images/textures/b
|
||||
* Typography.
|
||||
* ------------------------------------------------ */
|
||||
$font-family: Arial, sans-serif !default;
|
||||
$font-base-size: 12px !default;
|
||||
|
||||
/** -----------------------------------------------
|
||||
* Grid Units (px)
|
||||
|
34
gulpfile.js
34
gulpfile.js
@ -213,10 +213,23 @@ gulp.task('bundle-lib', function bundleLib() {
|
||||
comments: false
|
||||
}))
|
||||
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/config.js', { expose: 'config' })
|
||||
.require(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/jQuery.js', { expose: 'jQuery' })
|
||||
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/action', { expose: 'action-button' })
|
||||
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/form', { expose: 'form' })
|
||||
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/form-action', { expose: 'form-action' })
|
||||
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/form-builder', { expose: 'form-builder' })
|
||||
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/sections/grid-field', { expose: 'grid-field' })
|
||||
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/grid-field-cell', { expose: 'grid-field-cell' })
|
||||
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/grid-field-header', { expose: 'grid-field-header' })
|
||||
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/grid-field-header-cell', { expose: 'grid-field-header-cell' })
|
||||
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/grid-field-row', { expose: 'grid-field-row' })
|
||||
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/grid-field-table', { expose: 'grid-field-table' })
|
||||
.require(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/i18n.js', { expose: 'i18n' })
|
||||
.require(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/router.js', { expose: 'router' })
|
||||
.require(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/jQuery.js', { expose: 'jQuery' })
|
||||
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/north-header', { expose: 'north-header' })
|
||||
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/north-header-breadcrumbs', { expose: 'north-header-breadcrumbs' })
|
||||
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/reducer-register.js', { expose: 'reducer-register' })
|
||||
.require(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/router.js', { expose: 'router' })
|
||||
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/text-field', { expose: 'text-field' })
|
||||
.bundle()
|
||||
.on('update', bundleLib)
|
||||
.on('error', notify.onError({ message: bundleFileName + ': <%= error.message %>' }))
|
||||
@ -228,14 +241,20 @@ gulp.task('bundle-lib', function bundleLib() {
|
||||
|
||||
gulp.task('bundle-react', function bundleReact() {
|
||||
return browserify(Object.assign({}, browserifyOptions))
|
||||
.require('react-addons-test-utils', { expose: 'react-addons-test-utils' })
|
||||
.transform(babelify.configure({
|
||||
presets: ['es2015'],
|
||||
ignore: /(node_modules)/
|
||||
}))
|
||||
.require('deep-freeze', { expose: 'deep-freeze' })
|
||||
.require('react', { expose: 'react' })
|
||||
.require('react-addons-css-transition-group', { expose: 'react-addons-css-transition-group' })
|
||||
.require('react-addons-test-utils', { expose: 'react-addons-test-utils' })
|
||||
.require('react-dom', { expose: 'react-dom' })
|
||||
.require('redux', { expose: 'redux' })
|
||||
.require('react-redux', { expose: 'react-redux' })
|
||||
.require('redux', { expose: 'redux' })
|
||||
.require('redux-thunk', { expose: 'redux-thunk' })
|
||||
.require('isomorphic-fetch', { expose: 'isomorphic-fetch' })
|
||||
.require(PATHS.ADMIN_JAVASCRIPT_DIST + '/SilverStripeComponent', { expose: 'silverstripe-component' })
|
||||
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/SilverStripeComponent', { expose: 'silverstripe-component' })
|
||||
.bundle()
|
||||
.on('update', bundleReact)
|
||||
.on('error', notify.onError({ message: bundleFileName + ': <%= error.message %>' }))
|
||||
@ -271,18 +290,21 @@ gulp.task('bundle-campaign-admin', function bundleCampaignAdmin() {
|
||||
presets: ['es2015', 'react'],
|
||||
ignore: /(node_modules)/
|
||||
}))
|
||||
.external('action-button')
|
||||
.external('deep-freeze')
|
||||
.external('grid-field')
|
||||
.external('i18n')
|
||||
.external('jQuery')
|
||||
.external('north-header')
|
||||
.external('page.js')
|
||||
.external('react')
|
||||
.external('react-addons-test-utils')
|
||||
.external('react-dom')
|
||||
.external('react-redux')
|
||||
.external('reducer-register')
|
||||
.external('redux')
|
||||
.external('redux-thunk')
|
||||
.external('silverstripe-component')
|
||||
.external('reducer-register')
|
||||
.bundle()
|
||||
.on('error', notify.onError({ message: bundleFileName + ': <%= error.message %>' }))
|
||||
.pipe(source(bundleFileName))
|
||||
|
@ -40,7 +40,6 @@
|
||||
"blueimp-tmpl": "^1.0.2",
|
||||
"bootstrap": "^4.0.0-alpha.2",
|
||||
"deep-freeze": "0.0.1",
|
||||
"isomorphic-fetch": "^2.2.1",
|
||||
"jquery-sizes": "^0.33.0",
|
||||
"npm-shrinkwrap": "^5.4.1",
|
||||
"page.js": "^4.13.3",
|
||||
|
@ -114,7 +114,6 @@ $gf_grid_x: 16px;
|
||||
}
|
||||
.grid-csv-button, .grid-print-button {
|
||||
margin-bottom: 0;
|
||||
font-size: $font-base-size;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user