GrapesJS Custom Trait Manager

by artur_arseniev

HTML

<script src="https://unpkg.com/grapesjs"></script>
<link rel="stylesheet" href="https://unpkg.com/grapesjs/dist/css/grapes.min.css">
<script src="https://unpkg.com/grapesjs-blocks-basic"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/[email protected]/css/materialdesignicons.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.min.css">
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.min.js"></script>
<div id="gjs">
  <div style="padding: 25px" data-gjs-type="demo-cmp">
    Custom Trait Manager (select me and check my traits)
  </div>
</div>

<div style="display: none;">
    <!-- Vue app  -->
  <div class="vue-app">
    <v-app>
      <v-main>
        <v-expansion-panels accordion multiple v-model="panels">
          <v-expansion-panel v-for="trc in traitCategories">
            <v-expansion-panel-header v-if="trc.category">
              {{ trc.category.getLabel() }}
            </v-expansion-panel-header>
            <v-expansion-panel-header class="no-cat-header" v-else></v-expansion-panel-header>
            <v-expansion-panel-content>
              <v-row>
                <trait-field v-for="trait in trc.items" :key="trait.id" :trait="trait"/>
              </v-row>
            </v-expansion-panel-content>
          </v-expansion-panel>
        </v-expansion-panels>
      </v-main>
    </v-app>
  </div>
  
  <!-- Trait Field template -->
  <div id="trait-field" style="display: none;">
    <v-col :class="['py-0 px-1 mb-1', trait.get('full') && 'mb-3']" :cols="12">
      <v-row class="flex-nowrap" v-if="isTypeNeedLabel">
        <v-col cols="auto pr-0">{{ trait.getLabel() }}</v-col>
      </v-row>
      <div v-if="type === 'number'">
        <v-text-field :placeholder="placeholder" :value="inputValue" @change="handleChange" outlined dense/>
      </div>
      <div v-else-if="type === 'checkbox'">
        <v-checkbox...

CSS

body, html {
  margin: 0;
  height: 100%;
}

.trait-input-color {
  width: 16px !important;
  height: 15px !important;
  opacity: 0 !important;
}

/* Vuetify overrides */
.v-application {
  background: transparent !important;
}
.v-application--wrap {
  min-height: auto;
}
.v-input__slot {
  font-size: 12px;
  min-height: 10px !important;
  color-scheme: dark;
}
.v-select__selections {
  flex-wrap: nowrap;
}
.v-text-field .v-input__slot {
  padding: 0 10px !important;
}
.v-input--selection-controls {
  margin-top: 0;
}
.v-text-field__details, .v-messages {
  display: none;
}
.no-cat-header {
  opacity: 0;
  padding: 10px;
  max-height: 10px;
  pointer-events: none;
  min-height: auto !important;
}
.trait-color-prv {
  border: 1px solid rgba(255,255,255,0.5);
  border-radius: 3px;
}

Babel + JSX

const myPlugin = (editor) => {
  const category1 = { id: 'first', label: 'First category' };
  const category2 = { id: 'second', label: 'Second category' };
	
  // Demo component with some traits
  editor.Components.addType('demo-cmp', {
    model: {
      defaults: {
        traits: [
          {
            type: 'date',
            name: 'date-trait',
            label: 'Date trait',
            placeholder: 'Insert date',
          },
          {
            type: 'text',
            name: 'text-trait',
            label: false,
            placeholder: 'Insert text',
            category: category1,
          },
          {
            type: 'select',
            name: 'select-trait',
            label: 'Select 1',
            category: category1,
            default: 'opt1',
            options: [
              { id: 'opt1', name: 'Option 1'},
              { id: 'opt2', name: 'Option 2'},
              { id: 'opt3', name: 'Option 3'},
            ]
          },
          {
            type: 'select',
            name: 'select-trait2',
            label: 'Select 2',
            category: category1,
            default: 'opt2',
            options: [
              { id: 'opt1', name: 'Option 1'},
              { id: 'opt2', name: 'Option 2'},
              { id: 'opt3', name: 'Option 3'},
            ]
          },
          {
            type: 'number',
            name: 'number-trait',
            placeholder: '0-100',
            min: 0,
            max: 100,
            step: 5,
          },
          {
            type: 'color',
            label: 'Color trait',
            name: 'color-trait',
          },
          {
            type: 'checkbox',
            label: 'Checkbox trait',
            name: 'checkbox-trait',
            valueTrue: 'YES',
            valueFalse: 'NO',
          },
          {
            type: 'checkbox',
            name: 'open',
            category: category2,
          },
          {
            type: 'button',
          ...