//PEN HEADER
header.header
  h1.header__title Password Strength Validator UI
  .header__btns.btns
    a.header__btn.btn(href="https://github.com/nat-davydova/password-strength" title="Check on Github" target="_blank") Check on Github
    
//PEN CONTENT     
.content
  
  //content inner
  .content__inner
    
    .container
    
      .row
      
        .col-lg-6.m-auto
          
          form.password-strength.form.p-4
          
            h3.form__title.text-center.mb-4 Enter the Password
            
            .form-group
            
              label(for="password-input") Password
              
              .input-group
                input.password-strength__input.form-control(type="password" id="password-input" aria-describedby="passwordHelp" placeholder="Enter password")
                .input-group-append
                  button.password-strength__visibility.btn.btn-outline-secondary(type="button")
                    span.password-strength__visibility-icon(data-visible='hidden')
                      i.fas.fa-eye-slash
                    span.password-strength__visibility-icon(data-visible='visible').js-hidden
                      i.fas.fa-eye
              
              small.password-strength__error.text-danger.js-hidden This symbol is not allowed!
              small#passwordHelp.form-text.text-muted.mt-2 Add 9 charachters or more, lowercase letters, uppercase letters, numbers and symbols to make the password really strong!
              
            .password-strength__bar-block.progress.mb-4
              .password-strength__bar.progress-bar.bg-danger(role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100")
            
            button.password-strength__submit.btn.btn-success.d-flex.m-auto(type="button" disabled) Submit
            
              
View Compiled
//mixins
@mixin transition-mix ($property: all, $duration: 0.2s, $timing: linear, $delay: 0s) {
  transition-property: $property;
  transition-duration: $duration;
  transition-timing-function: $timing;
  transition-delay: $delay;
}

@mixin position-absolute ($top: null, $left: null, $right: null, $bottom: null) {
  position: absolute;
  top: $top;
  left: $left;
  right: $right;
  bottom: $bottom;
}

//basic variables
$theme-font-color: #2c2c2c;

//common styles
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font: {
    family: 'Muli', sans-serif;
    size: 16px;
  }
  color: $theme-font-color;
  
  a {
    color: inherit;
    text-decoration: none;
  }
}

.header__btn {
  @include transition-mix;
  
  padding: 10px 20px;
  display: inline-block;
  margin-right: 10px;
  
  background-color: #fff;
  border: 1px solid $theme-font-color;
  border-radius: 3px;
  
  cursor: pointer;
  
  outline: none;
  
  &:last-child {
    margin-right: 0;
  }
  
  &:hover,
  &.js-active{
    color: #fff;
    
    background-color: $theme-font-color;
  }
}

//header styles
.header {
  max-width: 600px;
  margin: 50px auto;
  
  text-align: center;
}

.header__title {
  margin-bottom: 30px;
  
   font: {
    size: 2.1rem;
  }
}

//content styles
.content {
  width: 95%;
  margin: 0 auto 50px;
}

//PASSWORD STRENTH ELEMS STYLES
.password-strength {
  box-shadow: 0 4px 5px 0 rgba(0,0,0,0.14), 
    0 1px 10px 0 rgba(0,0,0,0.12), 
    0 2px 4px -1px rgba(0,0,0,0.3);
}

.js-hidden {
  display: none;
}
View Compiled
DOM = {
  passwForm: '.password-strength',
  passwErrorMsg: '.password-strength__error',
  passwInput: document.querySelector('.password-strength__input'),
  passwVisibilityBtn: '.password-strength__visibility',
  passwVisibility_icon: '.password-strength__visibility-icon',
  strengthBar: document.querySelector('.password-strength__bar'),
  submitBtn: document.querySelector('.password-strength__submit'),
}

//*** HELPERS

//need to append classname with '.' symbol
const findParentNode = (elem, parentClass) => {
  
  parentClass = parentClass.slice(1, parentClass.length);
  
  while (true) {
    
    if(!elem.classList.contains(parentClass)) {
      elem = elem.parentNode;
    } else {
      return elem;
    }
    
  }
  
};

//*** MAIN CODE

const getPasswordVal = input => {
  return input.value;
};

const testPasswRegexp = (passw, regexp) => {
  
  return regexp.test(passw);
  
};

const testPassw = passw => {
  
  let strength = 'none';
  
  const moderate = /(?=.*[A-Z])(?=.*[a-z]).{5,}|(?=.*[\d])(?=.*[a-z]).{5,}|(?=.*[\d])(?=.*[A-Z])(?=.*[a-z]).{5,}/g; 
  const strong = /(?=.*[A-Z])(?=.*[a-z])(?=.*[\d]).{7,}|(?=.*[\!@#$%^&*()\\[\]{}\-_+=~`|:;"'<>,./?])(?=.*[a-z])(?=.*[\d]).{7,}/g;
  const extraStrong = /(?=.*[A-Z])(?=.*[a-z])(?=.*[\d])(?=.*[\!@#$%^&*()\\[\]{}\-_+=~`|:;"'<>,./?]).{9,}/g;
  
  if(testPasswRegexp(passw, extraStrong)) {
    strength = 'extra';
  } else if(testPasswRegexp(passw, strong)) {
    strength = 'strong';
  } else if(testPasswRegexp(passw, moderate)) {
    strength = 'moderate';
  } else if(passw.length > 0) {
    strength = 'weak';  
  }
  
  return strength;
  
};

const testPasswError = passw => {
  
  const errorSymbols = /\s/g;
  
  return testPasswRegexp(passw, errorSymbols);
  
};

const setStrengthBarValue = (bar, strength) => {
  
  let strengthValue;
  
  switch(strength) {
    case 'weak':
      strengthValue = 25;
      bar.setAttribute('aria-valuenow', strengthValue);
      break;
      
    case 'moderate':
      strengthValue = 50;
      bar.setAttribute('aria-valuenow', strengthValue);
      break;
      
    case 'strong':
      strengthValue = 75;
      bar.setAttribute('aria-valuenow', strengthValue);
      break;
      
    case 'extra':
      strengthValue = 100;
      bar.setAttribute('aria-valuenow', strengthValue);
      break;
      
    default:
      strengthValue = 0;
      bar.setAttribute('aria-valuenow', 0);
  }
  
  return strengthValue;
  
};

//also adds a text label based on styles
const setStrengthBarStyles = (bar, strengthValue) => {
  
  bar.style.width = `${strengthValue}%`;
  
  bar.classList.remove('bg-success', 'bg-info', 'bg-warning');
  
  switch(strengthValue) {
    case 25:
      bar.classList.add('bg-danger');
      bar.textContent = 'Weak';
      break;
      
    case 50:
      bar.classList.remove('bg-danger');
      bar.classList.add('bg-warning');
      bar.textContent = 'Moderate';
      break;
      
    case 75:
      bar.classList.remove('bg-danger');
      bar.classList.add('bg-info');
      bar.textContent = 'Strong';
      break;
      
    case 100:
      bar.classList.remove('bg-danger');
      bar.classList.add('bg-success');
      bar.textContent = 'Extra Strong';
      break;
      
    default:
      bar.classList.add('bg-danger');
      bar.textContent = '';
      bar.style.width = `0`;
  }
  
};

const setStrengthBar = (bar, strength) => {
  
  //setting value
  const strengthValue = setStrengthBarValue(bar, strength);
  
  //setting styles
  setStrengthBarStyles(bar, strengthValue);
};

const unblockSubmitBtn = (btn, strength) => {
  
  if(strength === 'none' || strength === 'weak') {
    btn.disabled = true;
  } else {
    btn.disabled = false;
  }
  
};

const findErrorMsg = input => {
   const passwForm = findParentNode(input, DOM.passwForm);
   return passwForm.querySelector(DOM.passwErrorMsg);
};

const showErrorMsg = input => {
  const errorMsg = findErrorMsg(input);
  errorMsg.classList.remove('js-hidden');
};

const hideErrorMsg = input => {
  const errorMsg = findErrorMsg(input);
  errorMsg.classList.add('js-hidden');
};

const passwordStrength = (input, strengthBar, btn) => {
  
  //getting password
  const passw = getPasswordVal(input);
  
  //check if there is an error
  const error = testPasswError(passw);
  
  if(error) {
   
    showErrorMsg(input);
     
  } else {
    
    //hide error messages
    hideErrorMsg(input);
    
    //finding strength
    const strength = testPassw(passw);
  
    //setting strength bar (value and styles)
    setStrengthBar(strengthBar, strength);
  
    //unblock submit btn only if password is moderate or stronger
    unblockSubmitBtn(btn,strength);
  }
  
};

const passwordVisible = passwField => {
  
  const passwType = passwField.getAttribute('type');
  
  let visibilityStatus;
  
  if (passwType === 'text') {
    
      passwField.setAttribute('type', 'password');
      
      visibilityStatus = 'hidden';
    
  } else {
    
    passwField.setAttribute('type', 'text');
    
    visibilityStatus = 'visible';
    
  }
  
  return visibilityStatus;
  
};

const changeVisibiltyBtnIcon = (btn, status) => {
  
  const hiddenPasswIcon = btn.querySelector(`${DOM.passwVisibility_icon}[data-visible="hidden"]`);
  
  const visibilePasswIcon = btn.querySelector(`${DOM.passwVisibility_icon}[data-visible="visible"]`);
  
  if (status === 'visible') {
    visibilePasswIcon.classList.remove('js-hidden');
    hiddenPasswIcon.classList.add('js-hidden');
  } else if(status === 'hidden') {
    visibilePasswIcon.classList.add('js-hidden');
    hiddenPasswIcon.classList.remove('js-hidden');       
  }
  
};

const passwVisibilitySwitcher = (passwField, visibilityToggler) => {
  
  const visibilityStatus = passwordVisible(passwField);
  
  changeVisibiltyBtnIcon(visibilityToggler, visibilityStatus);
}


//*** EVENT LISTENERS
DOM.passwInput.addEventListener('input', () => {
  passwordStrength(DOM.passwInput, DOM.strengthBar, DOM.submitBtn);
});

const passwVisibilityBtn = document.querySelector(DOM.passwVisibilityBtn);

passwVisibilityBtn.addEventListener('click', e => {
  
  let toggler = findParentNode(e.target,  DOM.passwVisibilityBtn);
  
  passwVisibilitySwitcher(DOM.passwInput, toggler);
  
});
View Compiled

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css
  2. https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.min.css

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/js/bootstrap.min.js