Explorar o código

Merged in feature/styles (pull request #15)

Feature/styles

Approved-by: Fela Maslen <fela.maslen@mubaloo.com>
Fela Maslen %!s(int64=7) %!d(string=hai) anos
pai
achega
909776415e

+ 13 - 6
src/components/AddCrudItem/index.js

@@ -3,6 +3,8 @@ import PropTypes from 'prop-types';
 
 import Field from 'components/Field';
 
+import './style.scss';
+
 function getEmptyValue(type) {
     if (type === 'text' || type === 'textarea') {
         return '';
@@ -54,11 +56,14 @@ export default class AddCrudItem extends Component {
         } = this.props;
 
         const fields = Object.keys(docFields).map(key => (
-            <Field key={key}
-                type={docFields[key].type}
-                value={this.state[key]}
-                onChange={this.onFieldChange[key]}
-            />
+            <div key={key} className="field-wrapper">
+                <span className="title">{key}</span>
+                <Field
+                    type={docFields[key].type}
+                    value={this.state[key]}
+                    onChange={this.onFieldChange[key]}
+                />
+            </div>
         ));
 
         return (
@@ -66,7 +71,9 @@ export default class AddCrudItem extends Component {
                 <div className="fields">
                     {fields}
                 </div>
-                <button className="button-add" onClick={this.onCreate}>{'Add'}</button>
+                <div className="meta">
+                    <button className="button-add" onClick={this.onCreate}>{'Add'}</button>
+                </div>
             </div>
         );
     }

+ 42 - 0
src/components/AddCrudItem/style.scss

@@ -0,0 +1,42 @@
+.add-field-wrapper {
+    display: flex;
+    margin-top: 0.5em;
+
+    .fields {
+        flex: 4;
+
+        .field-wrapper {
+            display: flex;
+            margin-bottom: 0.5em;
+            align-items: center;
+
+            .title {
+                flex: 1;
+                text-transform: capitalize;
+                font-size: 90%;
+                font-weight: bold;
+
+                &::after {
+                    content: ':';
+                }
+            }
+
+            input,
+            textarea {
+                margin: 0 1em;
+                flex: 2;
+            }
+
+            textarea {
+                flex: 4;
+                height: 144px;
+                resize: none;
+            }
+        }
+    }
+
+    .meta {
+        flex: 1;
+    }
+}
+

+ 39 - 0
src/components/App/style.scss

@@ -1 +1,40 @@
+@import '~variables';
+
+html {
+    box-sizing: border-box;
+}
+
+*, *:before, *:after {
+    box-sizing: inherit;
+}
+
+body {
+    margin: 0;
+    font-family: Arial, Helvetica, sans-serif;
+}
+
+.gurubot2 {
+    display: flex;
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    flex-flow: column;
+
+    .main-header {
+        flex: 0 0 auto;
+        background: $color-primary;
+        padding: 1em;
+
+        h1 {
+            margin: 0;
+        }
+    }
+
+    .main-body {
+        display: flex;
+        flex: 1;
+        padding: 1em;
+        background: $color-secondary;
+    }
+}
 

+ 44 - 0
src/components/CrudDocument/style.scss

@@ -1,8 +1,52 @@
 @import '~variables';
 
 .crud-document-wrapper {
+    display: flex;
+    width: 100%;
+    overflow: hidden;
+    margin-top: 0.5em;
+    padding-bottom: 0.5em;
+    align-items: center;
+
+    border-bottom: 1px solid $color-grey-light;
+
     &.pending {
         opacity: $opacity-pending;
     }
+
+    .fields-list {
+        display: flex;
+        flex: 4;
+        min-width: 0;
+        flex-flow: column;
+        padding-right: 0.5em;
+
+        input, .crud-field {
+            line-height: 20px;
+            padding: 0 0.3em;
+            margin: 0.1em 0;
+            border: 1px solid $color-grey-light;
+            letter-spacing: 0;
+            font-size: 14px;
+            font-family: Arial, Helvetica, sans-serif;
+            outline: none;
+        }
+
+        textarea, .crud-field.multi-line {
+            flex: 1;
+            white-space: pre-wrap;
+            overflow-x: auto;
+            height: 144px;
+            resize: none;
+        }
+
+        .editing {
+            border-color: $color-secondary;
+        }
+    }
+
+    .meta {
+        flex: 1;
+    }
 }
 

+ 8 - 2
src/components/CrudField/index.js

@@ -39,7 +39,9 @@ export default class CrudField extends Component {
     };
 
     onEditEnd = () => {
-        this.props.onEditEnd(this.props.field, this.state.editValue);
+        if (this.state.editValue !== this.props.value) {
+            this.props.onEditEnd(this.props.field, this.state.editValue);
+        }
 
         this.setState({
             editing: false
@@ -74,7 +76,11 @@ export default class CrudField extends Component {
             );
         }
 
-        const className = classNames('crud-field', { pending });
+        const className = classNames('crud-field', {
+            'multi-line': type === 'textarea',
+            'single-line': ['text', 'number'].includes(type),
+            pending
+        });
 
         return (
             <FieldView

+ 9 - 0
src/components/CrudField/style.scss

@@ -0,0 +1,9 @@
+.crud-field {
+    &:nth-child(1) {
+        font-size: 95%;
+    }
+    &:nth-child(2) {
+        font-size: 90%;
+    }
+}
+

+ 30 - 16
src/components/Field/index.js

@@ -1,25 +1,39 @@
-import React from 'react';
+import React, { Component } from 'react';
 import PropTypes from 'prop-types';
 
-export default function Field({ type, ...props }) {
-    if (type === 'number') {
-        return (
-            <input type="number" {...props} />
-        );
+export default class Field extends Component {
+    static propTypes = {
+        type: PropTypes.string.isRequired
+    };
+
+    constructor(props) {
+        super(props);
+
+        this.input = React.createRef();
+    }
+
+    componentDidMount() {
+        this.input.current.focus();
     }
 
-    if (type === 'textarea') {
+    render() {
+        const { type, ...props } = this.props;
+
+        if (type === 'number') {
+            return (
+                <input ref={this.input} type="number" {...props} />
+            );
+        }
+
+        if (type === 'textarea') {
+            return (
+                <textarea ref={this.input} {...props} />
+            );
+        }
+
         return (
-            <textarea {...props} />
+            <input ref={this.input} type="text" {...props} />
         );
     }
-
-    return (
-        <input type="text" {...props} />
-    );
 }
 
-Field.propTypes = {
-    type: PropTypes.string.isRequired
-};
-

+ 75 - 0
src/containers/AnnoyButton/style.scss

@@ -0,0 +1,75 @@
+@import '~mixins';
+@import '~variables';
+
+.annoy-button-wrapper {
+    height: 100px;
+    text-align: center;
+    align-items: center;
+
+    .button-annoy {
+        display: block;
+        margin: 0 auto;
+        background: none;
+        outline: none;
+    }
+    
+    &:not(.loading) {
+        .button-annoy {
+            $grey: desaturate(#800015, 90%);
+
+            font-size: 2em;
+            outline: none;
+            color: white;
+            text-shadow: -1px -1px 1px rgba(0, 0, 0, 0.7), 1px 1px 1px rgba(255, 255, 255, 0.3);
+            display: block;
+            padding: 23px 37px 25px 35px;
+            cursor: pointer;
+            background-color: #2e050c;
+            background-image: linear-gradient(273deg, #eb4763 30%, #e6193c 40%);
+            border: none;
+            border-radius: 16px;
+            box-shadow: 
+                inset 0px 0px 1px 1px rgba(138, 15, 36, 0.9),
+                inset 0px 0px 2px 3px rgba(230, 25, 60, 0.9),
+                inset 1px 1px 1px 4px rgba(255, 255, 255, 0.8),
+                inset 0px 0px 2px 7px rgba(235, 71, 99, 0.8),
+                inset 0px 0px 4px 10px rgba(230, 25, 60, 0.9),
+                8px 10px 2px 6px rgba(92, 10, 24, 0.55),
+                0px 0px 3px 2px rgba(184, 20, 48, 0.9),
+                0px 0px 2px 6px rgba(230, 25, 60, 0.9),
+                -1px -px 1px 6px rgba(255, 255, 255, 0.9),
+                0px 0px 2px 11px rgba(230, 25, 60, 0.9),
+                0px 0px 1px 12px rgba(184, 20, 48, 0.9),
+                1px 3px 14px 14px rgba(0, 0, 0, 0.4);
+
+            &:active {
+                color: rgb(217, 217, 217);
+                padding: 26px 34px 22px 38px;
+                background-image: linear-gradient(273deg, rgb(230, 25, 60) 50%, rgb(232, 48, 79) 60%);
+                box-shadow:
+                    inset 3px 4px 3px 2px hsla(350,80%,20%,0.55),
+                    inset 0px 0px 1px 1px hsla(350,80%,30%,0.9),
+                    inset -1px -1px 2px 3px hsla(350,80%,50%,0.9),
+                    inset -2px -2px 1px 3px hsla(350,80%,100%,0.8),
+                    inset 0px 0px 2px 7px hsla(350,80%,60%,0.8),
+                    inset 0px 0px 3px 10px hsla(350,80%,50%,0.9),
+                    
+                    0px 0px 3px 2px hsla(350,80%,40%,0.9),
+                    0px 0px 2px 6px hsla(350,80%,50%,0.9),
+                    /*0px 0px 2px 3px hsla(350,80%,60%,0.9),*/
+                    -1px -1px 1px 6px hsla(350,80%,100%,0.9),
+                    /*0px 0px 2px 8px hsla(350,80%,60%,0.9),*/
+                    0px 0px 2px 11px hsla(350,80%,50%,0.9),
+                    0px 0px 1px 12px hsla(350,80%,40%,0.9),
+                    1px 3px 14px 14px hsla(350,80%,0%,0.4);
+            }
+        }
+    }
+
+    &.loading {
+        .button-annoy {
+            @include loading-spinner(86px, red, 1/6);
+        }
+    }
+}
+

+ 17 - 15
src/containers/CrudList/index.js

@@ -94,22 +94,24 @@ class CrudList extends Component {
 
         return (
             <div className={classNames('crud-list-wrapper', { loading })}>
-                <div className="head">
-                    <h3 className="title">{title}</h3>
-                    <div className="meta">
-                        <button className="button-refresh"
-                            onClick={this.onRefresh}>
-                            {'Refresh'}
-                        </button>
-                        <LoadingIndicator loading={loading} />
+                <div className="crud-list-wrapper-inner">
+                    <div className="head">
+                        <h3 className="title">{title}</h3>
+                        <div className="meta">
+                            <button className="button-refresh"
+                                onClick={this.onRefresh}>
+                                {'Refresh'}
+                            </button>
+                            <LoadingIndicator loading={loading} />
+                        </div>
+                    </div>
+                    <div className="body">
+                        {docsList}
+                        <AddCrudItem
+                            docFields={docFields}
+                            onCreate={this.onCreate}
+                        />
                     </div>
-                </div>
-                <div className="body">
-                    {docsList}
-                    <AddCrudItem
-                        docFields={docFields}
-                        onCreate={this.onCreate}
-                    />
                 </div>
             </div>
         );

+ 60 - 0
src/containers/CrudList/style.scss

@@ -1,10 +1,70 @@
 @import '~variables';
+@import '~mixins';
 
 .crud-list-wrapper {
+    flex: 0 0 50%;
+    overflow: hidden;
+
+    &-inner {
+        display: flex;
+        flex-flow: column;
+        background: white;
+        box-shadow: 0 3px 6px rgba(0, 0, 0, 0.2);
+        margin: 0 0.5em;
+        padding: 0.5em;
+        border: 1px solid $color-grey-light;
+    }
+
+    &:first-child {
+        .crud-list-wrapper-inner {
+            margin-left: 0;
+        }
+    }
+    &:last-child {
+        .crud-list-wrapper-inner {
+            margin-right: 0;
+        }
+    }
+
     &.loading {
+        .head {
+            .meta {
+                .loading-indicator {
+                    opacity: 1;
+                }
+            }
+        }
+
         .body {
             opacity: $opacity-pending;
         }
     }
+
+    .head {
+        .title {
+            margin: 0;
+        }
+
+        .meta {
+            display: flex;
+            margin-top: 0.3em;
+            padding-top: 0.3em;
+            border-top: 1px solid $color-grey-light;
+            justify-content: space-between;
+            align-items: center;
+
+            .loading-indicator {
+                @include loading-spinner(20px, black, 1/8);
+                display: block;
+                opacity: 0;
+                transition: opacity 0.3s ease;
+            }
+        }
+    }
+
+    .body {
+        margin: 0.5em 1em 0.5em 0;
+        overflow-y: auto;
+    }
 }
 

+ 1 - 0
src/index.html

@@ -2,6 +2,7 @@
 <html>
     <head>
         <meta charset="utf-8" />
+        <meta name="viewport" content="width=device-width, initial-scale=1">
         <title>GuruBot2 Administration</title>
     </head>
     <body>

+ 1 - 0
src/reducers/crud.js

@@ -77,6 +77,7 @@ function onCreateResponse(state, { route, pendingId, err, response }) {
     if (err) {
         const newRoute = {
             ...(state[route] || {}),
+            loading: false,
             error: true
         };
 

+ 31 - 0
src/sass/mixins.scss

@@ -0,0 +1,31 @@
+@keyframes spinner {
+    0% {
+        transform: rotate(0deg);
+    }
+    100% {
+        transform: rotate(360deg);
+    }
+}
+
+@mixin loading-spinner($size: 10px, $color: white, $ratio: 0.1) {
+    $border-size: $size * $ratio;
+
+    font-size: 10px;
+    position: relative;
+    text-indent: -9999px;
+    border-top: $border-size solid rgba($color, 0.2);
+    border-right: $border-size solid rgba($color, 0.2);
+    border-bottom: $border-size solid rgba($color, 0.2);
+    border-left: $border-size solid $color;
+    transform: translateZ(0);
+    animation: spinner 1.1s infinite linear;
+    overflow: hidden;
+
+    &, &::after {
+        border-radius: 50%;
+        width: $size;
+        height: $size;
+    }
+}
+
+

+ 4 - 0
src/sass/variables.scss

@@ -1,4 +1,8 @@
 $color-grey-md: #999;
+$color-grey-light: #ccc;
+
+$color-primary: #ffaf28;
+$color-secondary: #2a0072;
 
 $opacity-pending: 0.4;
 

+ 1 - 0
test/reducers/crud.spec.js

@@ -111,6 +111,7 @@ describe('CRUD reducer', () => {
             const result = crud(state, action);
 
             expect(result[route].items).to.have.length(0);
+            expect(result[route]).to.have.property('loading', false);
             expect(result[route]).to.have.property('error', true);
         });
     });

+ 1 - 0
webpack.config.js

@@ -163,6 +163,7 @@ module.exports = {
             sagas: path.resolve(__dirname, 'src/sagas'),
             selectors: path.resolve(__dirname, 'src/selectors'),
             variables: path.resolve(__dirname, 'src/sass/variables.scss'),
+            mixins: path.resolve(__dirname, 'src/sass/mixins.scss'),
             sprite: path.resolve(__dirname, 'src/images/sprite/sprite.scss')
         }
     },