diff --git a/app/javascript/admin/controllers/creator_settings_controller.js b/app/javascript/admin/controllers/creator_settings_controller.js
deleted file mode 100644
index 5f5928636..000000000
--- a/app/javascript/admin/controllers/creator_settings_controller.js
+++ /dev/null
@@ -1,67 +0,0 @@
-import { Controller } from '@hotwired/stimulus';
-import { isLowContrast } from '@utilities/color/contrastValidator';
-import { brightness } from '@utilities/color/accentCalculator';
-
-/**
- * Manages interactions on the Creator Settings page.
- */
-export class CreatorSettingsController extends Controller {
- static targets = ['colorContrastError', 'brandColor'];
-
- /**
- * 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 overriding 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
- }
- }
-}
diff --git a/app/javascript/packs/colorPicker.jsx b/app/javascript/colorPickers/replaceTextInputWithColorPicker.jsx
similarity index 65%
rename from app/javascript/packs/colorPicker.jsx
rename to app/javascript/colorPickers/replaceTextInputWithColorPicker.jsx
index 03bd8ae10..37cbc7675 100644
--- a/app/javascript/packs/colorPicker.jsx
+++ b/app/javascript/colorPickers/replaceTextInputWithColorPicker.jsx
@@ -1,11 +1,18 @@
import { h, render } from 'preact';
import { ColorPicker } from '@crayons';
-// Find any color picker inputs on the page and replace them with the Preact enhanced component
-const colorInputs = document.querySelectorAll('[data-color-picker]');
-
-for (const input of colorInputs) {
- const { labelText } = input.dataset;
+/**
+ * Takes a text input, and replaces it with the richer Preact component
+ *
+ * @param {HTMLElement} input The input to replace
+ * @param {string} labelText The label to apply to the new form controls
+ * @param {function} onChange Any onChange callback
+ */
+export function replaceTextInputWithColorPicker({
+ input,
+ labelText,
+ onChange,
+}) {
const inputProps = {};
// Copy any specific attributes to the new input
@@ -15,13 +22,14 @@ for (const input of colorInputs) {
}
// The third `replaceNode` argument here makes sure that the new picker is rendered in the correct position within the parentElement
- // However, Preact is unable to do a VDOM diff that allows a straight replacement of one input for the other, so we also need to remove it manually on line 29
+ // However, Preact is unable to do a VDOM diff that allows a straight replacement of one input for the other, so we also need to remove it manually below
render(
,
input.parentElement,
input,
diff --git a/app/javascript/crayons/formElements/ColorPicker/ColorPicker.jsx b/app/javascript/crayons/formElements/ColorPicker/ColorPicker.jsx
index dba6d2ebb..33056598d 100644
--- a/app/javascript/crayons/formElements/ColorPicker/ColorPicker.jsx
+++ b/app/javascript/crayons/formElements/ColorPicker/ColorPicker.jsx
@@ -6,11 +6,21 @@ import { HexColorPicker, HexColorInput } from 'react-colorful';
import { initializeDropdown } from '@utilities/dropdownUtils';
import { ButtonNew as Button } from '@crayons';
+const convertThreeCharHexToSix = (hex) => {
+ const r = hex.charAt(1);
+ const g = hex.charAt(2);
+ const b = hex.charAt(3);
+
+ return `#${r}${r}${g}${g}${b}${b}`;
+};
+
export const ColorPicker = ({
id,
buttonLabelText,
defaultValue,
inputProps,
+ onChange,
+ onBlur,
}) => {
// Ternary has been used here to guard against an empty string being passed as default value
const [color, setColor] = useState(defaultValue ? defaultValue : '#000');
@@ -25,9 +35,21 @@ export const ColorPicker = ({
});
}, [buttonId, popoverId]);
+ // Hex codes may validly be represented by three characters, where r, g, b are all repeated,
+ // e.g. #0D6 === #00DD66. To make sure that all color codes can be handled consistently through our app,
+ // we convert any shorthand hex codes to their full 6 char representation.
+ const handleBlur = () => {
+ // Color always includes a leading '#', hence a length of 4
+ if (color.length === 4) {
+ const fullHexCode = convertThreeCharHexToSix(color);
+ setColor(fullHexCode);
+ onChange?.(fullHexCode);
+ }
+ };
+
return (
-