Reoodle - Text count demo

by joplomacedo

HTML

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/css/all.min.css">
<div id="app">
  <div class="sms_edit__box l-stack">
    <div class="sms_edit__info_row l-stack">
      <div class="sms_edit__character_count_info">
        <span>Character count</span> <span>{{ smsBodyCharCount }}/{{ CHARS_COUNT_PER_SEGMENT * smsSegmentCount}}</span>
      </div>

      <accordion-item v-if="smsSegmentCount > 1">
        <template v-slot:header>
          <i class="sms_edit__info_warning_icon fas fa-exclamation-triangle"></i>
           Counts as {{smsSegmentCount}} texts because your text exceeds {{CHARS_COUNT_PER_SEGMENT}} characters.
        </template>
        <template v-slot:body>
          Clear detailed explanation goes here...
        </template>
      </accordion-item>

      <accordion-item v-if="smsSegmentCount > 1 && smsContainsCustomVars">
        <template v-slot:header>
          <i class="sms_edit__info_warning_icon fas fa-exclamation-triangle"></i>
          You are using dynamic variables in your text
          (<span>
            <span class="sms_edit__info_custom_var" v-for="(smsCustomVar, i) in smsCustomVars" :key="i">#{{smsCustomVar}}</span>
          </span>). The real cost can be different than the one presented above.
        </template>
        <template v-slot:body>
          Because the text's length will depend on the actual value of the variables you're using (likely different for each appointment) Reoodle cannot determine the actual size of the text and the corresponding text count. The displayed text count is just an estimation (each dynamic variable is counted as 10 characters)
        </template>
      </accordion-item>
      
      <accordion-item v-if="smsSegmentCount === 1 && smsContainsCustomVars">
        <template v-slot:header>
          <i class="sms_edit__info_warning_icon fas fa-exclamation-triangle"></i>
          Because you are using dynamic variables in your text, the delivery of this text can...

CSS

*,
*:before,
*:after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  border: 0;
  font: inherit;
  color: inherit;
}

html {
  font-family: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI',
    Roboto, 'Helvetica Neue', Arial, sans-serif;
  font-size: 16px;
  word-spacing: 1px;
  -ms-text-size-adjust: 100%;
  -webkit-text-size-adjust: 100%;
  -moz-osx-font-smoothing: grayscale;
  -webkit-font-smoothing: antialiased;
  line-height: 1.5;
}


ol,
ul {
  list-style: none;
}

a,
button,
input[type="button"],
input[type="submit"] {
  cursor: pointer;
}

a {
  text-decoration: none;
}

textarea {
  display: block;
  width: 100%;
}


body {
  padding: 2em;
}

.l-stack > * + *  {
  margin-top: 1em;
}


.accordion__item {
  --border-radius: 4px;
  background-color: #eee;
  font-size: .9em;
  margin-bottom: .4em;
  border-radius: var(--border-radius);
}


.accordion__header {
  padding: .7em 1em .8em;
  cursor: pointer;
  background: #ebebeb;
  border-radius: var(--border-radius) var(--border-radius) 0 0;
}

.accordion__header_content {
  margin-right: .5em;
}

.accordion__toggle_indicator {
  text-decoration: underline;
  color: blue;
}

.accordion__body {
  padding: 1em 1em 1.2em;
  background: #f7f7f7;
  color: #6d6d6d;
  border-radius: 0 0 var(--border-radius) var(--border-radius);
}

.accordion__item.is-open .accordion__body {
  display: block;
}

.accordion__item.is-closed .accordion__body {
  display: none;
}

.field {
  border: 1px solid #ccc;
  border-radius: 2px;
  padding: 1em;
}

.field--texarea {
  min-height: 17em;
}

.sms_edit__info_warning_icon {
  color: #e8b030;
}

.sms_edit__info_custom_var {
  display: inline-block;
  line-height: 1.2;
  padding: .1em .3em;
  border-radius: 2px;
  background-color: #fff;
  margin-right: .3em;
}

.sms_edit__info_custom_var:last-child {
  margin-right: 0;
}

Vue

//utils
function removeWhitespace(str) {
	return str.trim().replace(/^ +| +$/gm, "");
}

function removeWhitespaceAndNewLines(str) {
	return str.replace(/(\r\n|\n|\r)/gm, " ").replace(/\s+/g, " ");
}

function isAlphaNumeric(str) {
	var code, i, len;

	for (i = 0, len = str.length; i < len; i++) {
		code = str.charCodeAt(i);
		if (
			!(code > 47 && code < 58) && // numeric (0-9)
			!(code > 64 && code < 91) && // upper alpha (A-Z)
			!(code > 96 && code < 123)
		) {
			// lower alpha (a-z)
			return false;
		}
	}
	return true;
}


Vue.component('accordion-item', {
	template: `
  <div class="accordion__item" :class="isOpen ? 'is-open' : 'is-closed'">
  	<div class="accordion__header" @click="handleHeaderClick">
    	<span class="accordion__header_content">
        <slot name="header">
        </slot>
      </span>
      
      <span class="accordion__toggle_indicator">
      	Learn more.
      </span>
    </div>
    <div class="accordion__body">
    	<slot name="body">
      </slot>
    </div>
  </div>
  `,
  data() {
  	return {
    	isOpen: false
    }
  },
  
  methods: {
  	handleHeaderClick() {
    	this.isOpen = !this.isOpen;
    }
  }
});


new Vue({
	el: "#app",

	data: {
				REOODLE_VARS: ["dt", "datetime", "date", "time"],
        CHARS_COUNT_PER_CUSTOM_VAR_ESTIMATATION: 10,
        CHARS_COUNT_PER_SEGMENT: 160,

        smsBody: removeWhitespace(`
            Hello Rita,

            This is just a reminder of your hairstyling appointment at 15:00 at 31/12/2020.

            If you cannot attend please call us today at 888-808-505.

            Thank you,
            Sandra's Hair Magic
        `),

		
	},

	computed: {
		cleanSmsBody() {
			return removeWhitespace(this.smsBody);
		},

		smsBodyCharCount() {
			const smsCustomVarsKeysCharCount = this.smsCustomVars.reduce(
				(acc, smsCustomVars) => {
					//1 is to count with the '#'
					return acc + 1 + smsCustomVars.length;
				},
				0
			);

			const smsCustomVarsCharCountEstimation...