docbrown/app/javascript/admin/controllers/creator_settings_controller.js
Ridhwana 25dd42704e
Validate the color contrast ratio before submitting the Creator Settings Form (#15444)
* feat: add a color contrast utility

* feat: add an error when the color contrast is low

* feat: add form validations

* refactor: treat WCAGColorContrast as a library that can be intercanged at any time

* fix: styling

* test: add a test for the contrast

* feat: add test for WCAGColorContrast

* feat: update cypress tests for brand color and color contrast ratios

* feat: update the message to read better

* chore: update the styling

* refactor: address all feedback/suggestions

* Update cypress/integration/creatorOnboardingFlows/creatorSettings.spec.js

Co-authored-by: Nick Taylor <nick@iamdeveloper.com>

* Replaced other .trigger('change')s with .blur()

Co-authored-by: Nick Taylor <nick@iamdeveloper.com>
Co-authored-by: Nick Taylor <nick@dev.to>
2021-12-01 18:49:11 +02:00

129 lines
3.5 KiB
JavaScript

import { Controller } from '@hotwired/stimulus';
import { isLowContrast } from '@utilities/color/contrastValidator';
import { brightness } from '@utilities/color/accentCalculator';
const MAX_LOGO_PREVIEW_HEIGHT = 80;
const MAX_LOGO_PREVIEW_WIDTH = 220;
/**
* Manages interactions on the Creator Settings page.
*/
export class CreatorSettingsController extends Controller {
static targets = ['previewLogo', 'colorContrastError', 'brandColor'];
/**
* Displays a preview of the image selected by the user.
*
* @param {Event} event
*/
previewLogo(event) {
const {
target: {
files: [firstFile],
},
} = event;
if (!firstFile) {
// Most likely the user cancelled the file selection.
return;
}
const reader = new FileReader();
reader.onload = () => {
const imageURL = reader.result;
const image = document.createElement('img');
image.src = imageURL;
// The logo preview image is purely visual so no need to communicate this to assistive technology.
image.alt = 'preview of logo selected';
image.addEventListener(
'load',
(event) => {
let {
target: { width, height },
} = event;
if (height > MAX_LOGO_PREVIEW_HEIGHT) {
width = (width / height) * MAX_LOGO_PREVIEW_HEIGHT;
height = MAX_LOGO_PREVIEW_HEIGHT;
}
if (width > MAX_LOGO_PREVIEW_WIDTH) {
width = MAX_LOGO_PREVIEW_WIDTH;
height = (height / width) * MAX_LOGO_PREVIEW_WIDTH;
}
image.style.width = `${width}px`;
image.style.height = `${height}px`;
this.previewLogoTarget.replaceChild(
image,
this.previewLogoTarget.firstChild,
);
},
{ once: true },
);
};
reader.readAsDataURL(firstFile);
}
/**
* Validates the color contrast for accessibility,
* if the contrast is okay, it updates the branding,
* else it displays the error.
* @param {Event} event
*/
handleValidationsAndUpdates(event) {
const { value: color } = event.target;
if (isLowContrast(color)) {
this.colorContrastErrorTarget.innerText =
'The selected color must be darker for accessibility purposes.';
} else {
this.updateBranding(color);
this.colorContrastErrorTarget.innerText = '';
}
}
/**
* Updates ths branding/colors on the Creator Settings Page.
* by overridding the accent-color in the :root object
*
* @param {String} color
*/
updateBranding(color) {
if (!new RegExp(event.target.getAttribute('pattern')).test(color)) {
return;
}
document.documentElement.style.setProperty('--accent-brand', color);
// We need to recalculate '--accent-brand-darker' in javascript as it's
// currently being calculated in ruby. It is used for the hover effect
// over the button.
// 0.85 represents the brightness value set in Ruby to calculate
// '--accent-brand-darker'
document.documentElement.style.setProperty(
'--accent-brand-darker',
brightness(color, 0.85),
);
}
/**
* Prevents a submission of the form if the
* color contrast is low.
*
* @param {Event} event
*/
formValidations(event) {
const { value: color } = this.brandColorTarget;
if (isLowContrast(color)) {
event.preventDefault();
this.colorContrastErrorTarget.classList.remove('hidden');
// we don't want the form to submit if the contrast is low
}
}
}