JSFiddle - React, Tailwind, and code Playground

by FANTAzeRus

HTML

<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css">
<div id="propertyBox">
  <div class="propertyBoxWrapper">
    <propbox :model="propData"></propbox>
  </div>
  
  <div class="debug">{{ propData }}</div>
</div>

CSS

body {
    font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif;
    font-size: 14px;
}

.propertyBoxWrapper {
    display: block;
    position: relative;
    max-width: 400px;
    border: 1px solid #999;
    border-radius: 4px;
}


.propLine {
    border-bottom: 1px solid #999;
}

#propertyBox > div > span > div:last-child > div > div.propertyGroupBody > div:last-child {
    border-bottom: none;
}

.open-group {
    color: #97cd76;
}

.close-group {
    color: #ed6c63;
}


.propertyGroupTitle i {
    margin-right: 1px;
    cursor: pointer;
    line-height: 20px;
}

.propertyGroupTitle {
    font-weight: bold;
    padding-left: 5px;
    border-left: 5px solid tomato;
    border-bottom: 1px solid #999;
}

.propertyGroupBody {

}

.propNameWrapper {
    display: inline-block;
    width: 150px;
    border-right: 1px solid #999;
    height: 20px;
}
.propName {
    padding: 0 4px;
    line-height: 20px;
}

.propValueWrapper {
    display: inline-block;
    height: 20px;
}

.propValue {
    padding: 0;
    line-height: 20px;
}

.propValue span input {
    border: none;
    font-size: 14px;
}

.propValue span input:focus {
    outline:none;
}

.propValue span select {
    font-size: 14px;
    border: none;
}

.propValue span select:focus {
    outline:none;
}

.debug {
  margin-top: 20px;
  border: 1px solid #999;
  border-radius: 4px;
  padding: 5px;
}

JavaScript

Vue.directive('focus', {
    inserted: function (el) {
        el.focus();
        el.select();
    }
});

//Текстовые поля
Vue.component('stringfield', {
    template: '\
        <span>\
            <strong v-if="!edit"\
                @click="editField"\
            >{{ value }}</strong>\
            <input v-if="edit"\
                type="text"\
                v-focus\
                :value="value"\
                @input="onInput"\
                @blur="saveField"\
                @keyup.enter.esc="saveField"\
            >\
        </span>\
    ',

    data: function() {
        return {
            edit: false
        }
    },

    props: {
        'value': String
    },

    methods: {
        onInput: function (event) {
            this.$emit('input', event.target.value);
        },

        editField: function () {
            this.edit = true;
        },

        saveField: function () {
            this.edit = false;
        }
    }
});


//Числа
Vue.component('numberfield', {
    template: '\
        <span>\
            <strong v-if="!edit"\
                @click="editField"\
            >{{ value }}</strong>\
            <input v-if="edit"\
                type="text"\
                v-focus\
                :value="value"\
                @input="onInput"\
                @blur="saveField"\
                @keyup.enter.esc="saveField"\
            >\
        </span>\
    ',

    data: function() {
        return {
            edit: false
        }
    },

    props: {
        'value': Number
    },

    methods: {
        onInput: function (event) {
            this.$emit('input', event.target.value);
        },

        editField: function () {
            this.edit = true;
        },

        saveField: function () {
            this.edit = false;
        }
    }
});

//Логика
Vue.component('checkbox', {
    template: '\
        <input type="checkbox" :checked="value" :value="value" @change="onChange">\
    ',

    props: {
        value:...