From 0ca090a39106596c46c96b897c5dd5949dfdb060 Mon Sep 17 00:00:00 2001 From: Paul Clarke Date: Tue, 22 Mar 2016 12:25:23 +1300 Subject: [PATCH] Add generic React components Includes moving some components from AssetAdmin --- admin/code/CampaignAdmin.php | 32 +-- admin/javascript/lang/en.js | 3 +- admin/javascript/lang/src/en.js | 3 +- .../src/__mocks__/silverstripe-component.js | 15 -- .../src/components/action/README.md | 37 +++ .../javascript/src/components/action/index.js | 109 +++++++++ .../src/components/action/styles.scss | 123 ++++++++++ .../action/tests/action-component-test.js | 1 + .../src/components/form-action/README.md | 17 ++ .../src/components/form-action/index.js | 27 ++ .../src/components/form-builder/README.md | 23 ++ .../src/components/form-builder/index.js | 230 ++++++++++++++++++ .../form-builder/tests/form-builder-test.js | 31 +++ .../javascript/src/components/form/README.md | 37 +++ admin/javascript/src/components/form/index.js | 59 +++++ .../src/components/grid-field-cell/index.js | 2 +- .../grid-field-header-cell/index.js | 2 +- .../src/components/grid-field-header/index.js | 2 +- .../src/components/grid-field-row/index.js | 2 +- .../src/components/grid-field-table/index.js | 2 +- .../src/components/hidden-field/index.js | 48 ++++ .../src/components/hidden-field/readme.md | 21 ++ .../src/components/hidden-field/styles.scss | 3 + .../north-header-breadcrumbs/index.js | 2 +- .../src/components/north-header/index.js | 2 +- .../src/components/text-field/index.js | 55 +++++ .../src/components/text-field/readme.md | 25 ++ .../tests/text-field-component-test.js | 37 +++ .../src/sections/campaign-admin/controller.js | 27 +- .../src/sections/campaign-admin/styles.scss | 3 - .../src/sections/grid-field/index.js | 2 +- admin/javascript/src/state/schema/README.md | 5 + .../src/state/schema/action-types.js | 5 + admin/javascript/src/state/schema/actions.js | 15 ++ admin/javascript/src/state/schema/reducer.js | 33 +++ .../src/state/schema/tests/reducer-test.js | 72 ++++++ admin/javascript/src/styles/main.scss | 2 + admin/scss/_forms.scss | 4 +- admin/scss/_menu.scss | 5 +- admin/scss/_mixins.scss | 6 +- admin/scss/_reset.scss | 2 - admin/scss/_style.scss | 19 +- admin/scss/_typography.scss | 14 -- admin/scss/_uitheme.scss | 13 +- admin/scss/bootstrap/_variables.scss | 98 ++++---- admin/scss/themes/_default.scss | 1 - gulpfile.js | 50 ++-- package.json | 1 - scss/GridField.scss | 1 - 49 files changed, 1168 insertions(+), 160 deletions(-) delete mode 100644 admin/javascript/src/__mocks__/silverstripe-component.js create mode 100644 admin/javascript/src/components/action/README.md create mode 100644 admin/javascript/src/components/action/index.js create mode 100644 admin/javascript/src/components/action/styles.scss create mode 100644 admin/javascript/src/components/action/tests/action-component-test.js create mode 100644 admin/javascript/src/components/form-action/README.md create mode 100644 admin/javascript/src/components/form-action/index.js create mode 100644 admin/javascript/src/components/form-builder/README.md create mode 100644 admin/javascript/src/components/form-builder/index.js create mode 100644 admin/javascript/src/components/form-builder/tests/form-builder-test.js create mode 100644 admin/javascript/src/components/form/README.md create mode 100644 admin/javascript/src/components/form/index.js create mode 100644 admin/javascript/src/components/hidden-field/index.js create mode 100644 admin/javascript/src/components/hidden-field/readme.md create mode 100644 admin/javascript/src/components/hidden-field/styles.scss create mode 100644 admin/javascript/src/components/text-field/index.js create mode 100644 admin/javascript/src/components/text-field/readme.md create mode 100644 admin/javascript/src/components/text-field/tests/text-field-component-test.js create mode 100644 admin/javascript/src/state/schema/README.md create mode 100644 admin/javascript/src/state/schema/action-types.js create mode 100644 admin/javascript/src/state/schema/actions.js create mode 100644 admin/javascript/src/state/schema/reducer.js create mode 100644 admin/javascript/src/state/schema/tests/reducer-test.js diff --git a/admin/code/CampaignAdmin.php b/admin/code/CampaignAdmin.php index 683e1b3f1..37b7e0897 100644 --- a/admin/code/CampaignAdmin.php +++ b/admin/code/CampaignAdmin.php @@ -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", diff --git a/admin/javascript/lang/en.js b/admin/javascript/lang/en.js index e421f3fc7..e8405915b 100644 --- a/admin/javascript/lang/en.js +++ b/admin/javascript/lang/en.js @@ -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" }); } \ No newline at end of file diff --git a/admin/javascript/lang/src/en.js b/admin/javascript/lang/src/en.js index f374e7dda..85289aa4c 100644 --- a/admin/javascript/lang/src/en.js +++ b/admin/javascript/lang/src/en.js @@ -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" } \ No newline at end of file diff --git a/admin/javascript/src/__mocks__/silverstripe-component.js b/admin/javascript/src/__mocks__/silverstripe-component.js deleted file mode 100644 index 8a4dcdcc3..000000000 --- a/admin/javascript/src/__mocks__/silverstripe-component.js +++ /dev/null @@ -1,15 +0,0 @@ -import { Component } from 'react'; - -export default class SilverStripeComponent extends Component { - constructor(props) { - super(props); - } - - componentDidMount() { - - } - - componentWillUnmount() { - - } -}; diff --git a/admin/javascript/src/components/action/README.md b/admin/javascript/src/components/action/README.md new file mode 100644 index 000000000..966bf937b --- /dev/null +++ b/admin/javascript/src/components/action/README.md @@ -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. diff --git a/admin/javascript/src/components/action/index.js b/admin/javascript/src/components/action/index.js new file mode 100644 index 000000000..4350e0ac0 --- /dev/null +++ b/admin/javascript/src/components/action/index.js @@ -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 ( + + ); + } + + /** + * 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 ( +
+ + + + + +
+ ); + } + + 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; diff --git a/admin/javascript/src/components/action/styles.scss b/admin/javascript/src/components/action/styles.scss new file mode 100644 index 000000000..4c2ec2278 --- /dev/null +++ b/admin/javascript/src/components/action/styles.scss @@ -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; + } +} + diff --git a/admin/javascript/src/components/action/tests/action-component-test.js b/admin/javascript/src/components/action/tests/action-component-test.js new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/admin/javascript/src/components/action/tests/action-component-test.js @@ -0,0 +1 @@ + diff --git a/admin/javascript/src/components/form-action/README.md b/admin/javascript/src/components/form-action/README.md new file mode 100644 index 000000000..372bd86fc --- /dev/null +++ b/admin/javascript/src/components/form-action/README.md @@ -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` diff --git a/admin/javascript/src/components/form-action/index.js b/admin/javascript/src/components/form-action/index.js new file mode 100644 index 000000000..6fb2cfa03 --- /dev/null +++ b/admin/javascript/src/components/form-action/index.js @@ -0,0 +1,27 @@ +import React from 'react'; +import SilverStripeComponent from '../../SilverStripeComponent'; + +class FormActionComponent extends SilverStripeComponent { + + render() { + return ( + + ); + } + +} + +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; diff --git a/admin/javascript/src/components/form-builder/README.md b/admin/javascript/src/components/form-builder/README.md new file mode 100644 index 000000000..0d793d617 --- /dev/null +++ b/admin/javascript/src/components/form-builder/README.md @@ -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. diff --git a/admin/javascript/src/components/form-builder/index.js b/admin/javascript/src/components/form-builder/index.js new file mode 100644 index 000000000..1e15a5bfb --- /dev/null +++ b/admin/javascript/src/components/form-builder/index.js @@ -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 + }); + } + + 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 + } +} + +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); diff --git a/admin/javascript/src/components/form-builder/tests/form-builder-test.js b/admin/javascript/src/components/form-builder/tests/form-builder-test.js new file mode 100644 index 000000000..908a3155f --- /dev/null +++ b/admin/javascript/src/components/form-builder/tests/form-builder-test.js @@ -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'); + }); + }); + +}); diff --git a/admin/javascript/src/components/form/README.md b/admin/javascript/src/components/form/README.md new file mode 100644 index 000000000..51fa85322 --- /dev/null +++ b/admin/javascript/src/components/form/README.md @@ -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. diff --git a/admin/javascript/src/components/form/index.js b/admin/javascript/src/components/form/index.js new file mode 100644 index 000000000..36c5a15bc --- /dev/null +++ b/admin/javascript/src/components/form/index.js @@ -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 ; + }); + } + + render() { + const attr = this.props.attributes; + const fields = this.props.mapFieldsToComponents(this.props.fields); + const actions = this.getFormActionComponents(); + + return ( +
+ {fields && +
+ {fields} +
+ } + + {actions && +
+
+ {actions} +
+
+ } +
+ ); + } + +} + +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; diff --git a/admin/javascript/src/components/grid-field-cell/index.js b/admin/javascript/src/components/grid-field-cell/index.js index 54dcef1bc..0bde01f82 100644 --- a/admin/javascript/src/components/grid-field-cell/index.js +++ b/admin/javascript/src/components/grid-field-cell/index.js @@ -1,5 +1,5 @@ import React from 'react'; -import SilverStripeComponent from 'silverstripe-component'; +import SilverStripeComponent from '../../SilverStripeComponent'; class GridFieldCellComponent extends SilverStripeComponent { diff --git a/admin/javascript/src/components/grid-field-header-cell/index.js b/admin/javascript/src/components/grid-field-header-cell/index.js index 76ff15b7a..9e0aea134 100644 --- a/admin/javascript/src/components/grid-field-header-cell/index.js +++ b/admin/javascript/src/components/grid-field-header-cell/index.js @@ -1,5 +1,5 @@ import React from 'react'; -import SilverStripeComponent from 'silverstripe-component'; +import SilverStripeComponent from '../../SilverStripeComponent'; class GridFieldHeaderCellComponent extends SilverStripeComponent { diff --git a/admin/javascript/src/components/grid-field-header/index.js b/admin/javascript/src/components/grid-field-header/index.js index 025ed54c8..9a6547535 100644 --- a/admin/javascript/src/components/grid-field-header/index.js +++ b/admin/javascript/src/components/grid-field-header/index.js @@ -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 { diff --git a/admin/javascript/src/components/grid-field-row/index.js b/admin/javascript/src/components/grid-field-row/index.js index a2c945553..ff91b4e48 100644 --- a/admin/javascript/src/components/grid-field-row/index.js +++ b/admin/javascript/src/components/grid-field-row/index.js @@ -1,5 +1,5 @@ import React from 'react'; -import SilverStripeComponent from 'silverstripe-component'; +import SilverStripeComponent from '../../SilverStripeComponent'; class GridFieldRowComponent extends SilverStripeComponent { diff --git a/admin/javascript/src/components/grid-field-table/index.js b/admin/javascript/src/components/grid-field-table/index.js index 479acdeaa..858ade0a2 100644 --- a/admin/javascript/src/components/grid-field-table/index.js +++ b/admin/javascript/src/components/grid-field-table/index.js @@ -1,5 +1,5 @@ import React from 'react'; -import SilverStripeComponent from 'silverstripe-component'; +import SilverStripeComponent from '../../SilverStripeComponent'; class GridFieldTableComponent extends SilverStripeComponent { diff --git a/admin/javascript/src/components/hidden-field/index.js b/admin/javascript/src/components/hidden-field/index.js new file mode 100644 index 000000000..bcbe9ff13 --- /dev/null +++ b/admin/javascript/src/components/hidden-field/index.js @@ -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 ( +
+ +
+ ); + } + + 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; diff --git a/admin/javascript/src/components/hidden-field/readme.md b/admin/javascript/src/components/hidden-field/readme.md new file mode 100644 index 000000000..319ca2bce --- /dev/null +++ b/admin/javascript/src/components/hidden-field/readme.md @@ -0,0 +1,21 @@ +# Hidden Field Component + +Generates an `` + +## Props + +### extraClass + +Addition CSS classes to apply to the `` 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. diff --git a/admin/javascript/src/components/hidden-field/styles.scss b/admin/javascript/src/components/hidden-field/styles.scss new file mode 100644 index 000000000..8bfacef1a --- /dev/null +++ b/admin/javascript/src/components/hidden-field/styles.scss @@ -0,0 +1,3 @@ +.field.hidden { + display: none; +} \ No newline at end of file diff --git a/admin/javascript/src/components/north-header-breadcrumbs/index.js b/admin/javascript/src/components/north-header-breadcrumbs/index.js index 1bacb6c6e..dc8e3c532 100644 --- a/admin/javascript/src/components/north-header-breadcrumbs/index.js +++ b/admin/javascript/src/components/north-header-breadcrumbs/index.js @@ -1,5 +1,5 @@ import React from 'react'; -import SilverStripeComponent from 'silverstripe-component'; +import SilverStripeComponent from '../../SilverStripeComponent'; class NorthHeaderBreadcrumbsComponent extends SilverStripeComponent { diff --git a/admin/javascript/src/components/north-header/index.js b/admin/javascript/src/components/north-header/index.js index f6b89c697..a9d44c122 100644 --- a/admin/javascript/src/components/north-header/index.js +++ b/admin/javascript/src/components/north-header/index.js @@ -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 { diff --git a/admin/javascript/src/components/text-field/index.js b/admin/javascript/src/components/text-field/index.js new file mode 100644 index 000000000..15cd5a311 --- /dev/null +++ b/admin/javascript/src/components/text-field/index.js @@ -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 ( +
+ {this.props.label && + + } +
+ +
+
+ ); + } + + 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; diff --git a/admin/javascript/src/components/text-field/readme.md b/admin/javascript/src/components/text-field/readme.md new file mode 100644 index 000000000..616661298 --- /dev/null +++ b/admin/javascript/src/components/text-field/readme.md @@ -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 `` 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. \ No newline at end of file diff --git a/admin/javascript/src/components/text-field/tests/text-field-component-test.js b/admin/javascript/src/components/text-field/tests/text-field-component-test.js new file mode 100644 index 000000000..28adee1cb --- /dev/null +++ b/admin/javascript/src/components/text-field/tests/text-field-component-test.js @@ -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( + + ); + }); + + it('should call the onChange function on props', function () { + textField.handleChange(); + + expect(textField.props.onChange.mock.calls.length).toBe(1); + }); + }); +}); diff --git a/admin/javascript/src/sections/campaign-admin/controller.js b/admin/javascript/src/sections/campaign-admin/controller.js index c26042390..284fcf542 100644 --- a/admin/javascript/src/sections/campaign-admin/controller.js +++ b/admin/javascript/src/sections/campaign-admin/controller.js @@ -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 (
- - + + + + +
); } + addCampaign() { + //Add campaign + } + } CampaignAdminContainer.propTypes = { diff --git a/admin/javascript/src/sections/campaign-admin/styles.scss b/admin/javascript/src/sections/campaign-admin/styles.scss index 173bfabdd..e69de29bb 100644 --- a/admin/javascript/src/sections/campaign-admin/styles.scss +++ b/admin/javascript/src/sections/campaign-admin/styles.scss @@ -1,3 +0,0 @@ -.CampaignAdmin { - -} diff --git a/admin/javascript/src/sections/grid-field/index.js b/admin/javascript/src/sections/grid-field/index.js index 9a8d2b61d..d9a01f40f 100644 --- a/admin/javascript/src/sections/grid-field/index.js +++ b/admin/javascript/src/sections/grid-field/index.js @@ -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'; diff --git a/admin/javascript/src/state/schema/README.md b/admin/javascript/src/state/schema/README.md new file mode 100644 index 000000000..6c545284c --- /dev/null +++ b/admin/javascript/src/state/schema/README.md @@ -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. diff --git a/admin/javascript/src/state/schema/action-types.js b/admin/javascript/src/state/schema/action-types.js new file mode 100644 index 000000000..da4e46080 --- /dev/null +++ b/admin/javascript/src/state/schema/action-types.js @@ -0,0 +1,5 @@ +const ACTION_TYPES = { + SET_SCHEMA: 'SET_SCHEMA' +}; + +export default ACTION_TYPES; diff --git a/admin/javascript/src/state/schema/actions.js b/admin/javascript/src/state/schema/actions.js new file mode 100644 index 000000000..011fb7135 --- /dev/null +++ b/admin/javascript/src/state/schema/actions.js @@ -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 + }); + } +} diff --git a/admin/javascript/src/state/schema/reducer.js b/admin/javascript/src/state/schema/reducer.js new file mode 100644 index 000000000..6e846ded5 --- /dev/null +++ b/admin/javascript/src/state/schema/reducer.js @@ -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; + } + +} diff --git a/admin/javascript/src/state/schema/tests/reducer-test.js b/admin/javascript/src/state/schema/tests/reducer-test.js new file mode 100644 index 000000000..35aa81314 --- /dev/null +++ b/admin/javascript/src/state/schema/tests/reducer-test.js @@ -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!' } })); + }); + + }); + +}); diff --git a/admin/javascript/src/styles/main.scss b/admin/javascript/src/styles/main.scss index 6448bf5b6..ba20591f5 100644 --- a/admin/javascript/src/styles/main.scss +++ b/admin/javascript/src/styles/main.scss @@ -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"; diff --git a/admin/scss/_forms.scss b/admin/scss/_forms.scss index f8a2c3e75..67407305b 100644 --- a/admin/scss/_forms.scss +++ b/admin/scss/_forms.scss @@ -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; } } diff --git a/admin/scss/_menu.scss b/admin/scss/_menu.scss index 870015d2e..49497e04d 100644 --- a/admin/scss/_menu.scss +++ b/admin/scss/_menu.scss @@ -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; diff --git a/admin/scss/_mixins.scss b/admin/scss/_mixins.scss index 3dbae9846..83bb46071 100644 --- a/admin/scss/_mixins.scss +++ b/admin/scss/_mixins.scss @@ -127,12 +127,12 @@ 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; - margin:5px 0; + font-size: $font-size-root -1; + margin: 5px 0; } .ui-widget-content { diff --git a/admin/scss/_reset.scss b/admin/scss/_reset.scss index 434ea6d9f..8f777c8ed 100644 --- a/admin/scss/_reset.scss +++ b/admin/scss/_reset.scss @@ -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. diff --git a/admin/scss/_style.scss b/admin/scss/_style.scss index 58bb64538..60ccc38c9 100644 --- a/admin/scss/_style.scss +++ b/admin/scss/_style.scss @@ -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; diff --git a/admin/scss/_typography.scss b/admin/scss/_typography.scss index 6d322a634..d9afb7def 100644 --- a/admin/scss/_typography.scss +++ b/admin/scss/_typography.scss @@ -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; diff --git a/admin/scss/_uitheme.scss b/admin/scss/_uitheme.scss index fd1830201..351c87927 100644 --- a/admin/scss/_uitheme.scss +++ b/admin/scss/_uitheme.scss @@ -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; diff --git a/admin/scss/bootstrap/_variables.scss b/admin/scss/bootstrap/_variables.scss index 0bc6a242d..79bf69c24 100644 --- a/admin/scss/bootstrap/_variables.scss +++ b/admin/scss/bootstrap/_variables.scss @@ -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 `` 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; diff --git a/admin/scss/themes/_default.scss b/admin/scss/themes/_default.scss index 0a350be10..e602235d9 100644 --- a/admin/scss/themes/_default.scss +++ b/admin/scss/themes/_default.scss @@ -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) diff --git a/gulpfile.js b/gulpfile.js index 87c9b8eaa..d9dae5637 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -212,11 +212,24 @@ gulp.task('bundle-lib', function bundleLib() { ignore: /(thirdparty)/, comments: false })) - .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/config.js', { expose: 'config' }) - .require(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/jQuery.js', { expose: 'jQuery' }) - .require(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/i18n.js', { expose: 'i18n' }) - .require(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/router.js', { expose: 'router' }) - .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/reducer-register.js', { expose: 'reducer-register' }) + .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/config.js', { expose: 'config' }) + .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 + '/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' }) - .require('react', { expose: 'react' }) - .require('react-dom', { expose: 'react-dom' }) - .require('redux', { expose: 'redux' }) - .require('react-redux', { expose: 'react-redux' }) - .require('redux-thunk', { expose: 'redux-thunk' }) - .require('isomorphic-fetch', { expose: 'isomorphic-fetch' }) - .require(PATHS.ADMIN_JAVASCRIPT_DIST + '/SilverStripeComponent', { expose: 'silverstripe-component' }) + .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('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_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)) diff --git a/package.json b/package.json index 54c912c59..f7f0ae5b5 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/scss/GridField.scss b/scss/GridField.scss index b4131a017..d9ca52620 100644 --- a/scss/GridField.scss +++ b/scss/GridField.scss @@ -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; } }