]> git.ipfire.org Git - thirdparty/bulma.git/commitdiff
Add Hex prompt
authorJeremy Thomas <bbxdesign@gmail.com>
Tue, 25 Jun 2024 15:20:07 +0000 (16:20 +0100)
committerJeremy Thomas <bbxdesign@gmail.com>
Tue, 25 Jun 2024 15:20:07 +0000 (16:20 +0100)
docs/_react/bulma-customizer/src/App.jsx
docs/_react/bulma-customizer/src/components/Color.jsx
docs/_react/bulma-customizer/src/components/Color.module.css
docs/_react/bulma-customizer/src/components/Slider.jsx
docs/_react/bulma-customizer/src/components/Slider.module.css

index 198537606683fed7f4c577e3b0bb7e936eb7ed51..51444f5c6594a326e40995fa6c4e3529c6e7f96e 100644 (file)
@@ -49,15 +49,20 @@ function App() {
     getVar: (id) => {
       return context.cssvars[id];
     },
-    updateVar: (id, newValue, unit) => {
-      const computedValue = `${newValue}${unit}`;
-
-      document.documentElement.style.setProperty(
-        `--bulma-${id}`,
-        computedValue,
-      );
-
+    updateVar: (id, newValue) => {
       setContext((context) => {
+        const { start, unit } = context.cssvars[id];
+        const computedValue = `${newValue}${unit}`;
+
+        if (start === newValue) {
+          document.documentElement.style.removeProperty(`--bulma-${id}`);
+        } else {
+          document.documentElement.style.setProperty(
+            `--bulma-${id}`,
+            computedValue,
+          );
+        }
+
         return {
           ...context,
           cssvars: {
index 62951bba2108640df8315829396c4f4b906cf0e2..a209636125b24d03f9cb670252b5822e55e573eb 100644 (file)
@@ -1,4 +1,4 @@
-import { useContext } from "react";
+import { useContext, useEffect, useState } from "react";
 import PropTypes from "prop-types";
 
 import Slider from "./Slider";
@@ -6,12 +6,103 @@ import Slider from "./Slider";
 import cn from "./Color.module.css";
 import { CustomizerContext } from "../App";
 
-function Color({ color }) {
-  // const [hue, setHue] = useState(h.start);
-  // const [saturation, setSaturation] = useState(s.start);
-  // const [lightness, setLightness] = useState(l.start);
+function hslToHex(h, s, l) {
+  s /= 100;
+  l /= 100;
+
+  let c = (1 - Math.abs(2 * l - 1)) * s;
+  let x = c * (1 - Math.abs(((h / 60) % 2) - 1));
+  let m = l - c / 2;
+  let r = 0,
+    g = 0,
+    b = 0;
+
+  if (0 <= h && h < 60) {
+    r = c;
+    g = x;
+    b = 0;
+  } else if (60 <= h && h < 120) {
+    r = x;
+    g = c;
+    b = 0;
+  } else if (120 <= h && h < 180) {
+    r = 0;
+    g = c;
+    b = x;
+  } else if (180 <= h && h < 240) {
+    r = 0;
+    g = x;
+    b = c;
+  } else if (240 <= h && h < 300) {
+    r = x;
+    g = 0;
+    b = c;
+  } else if (300 <= h && h < 360) {
+    r = c;
+    g = 0;
+    b = x;
+  }
+
+  r = Math.round((r + m) * 255);
+  g = Math.round((g + m) * 255);
+  b = Math.round((b + m) * 255);
+
+  return `${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase()}`;
+}
+
+function hexToHsl(hex) {
+  // Remove the hash at the start if it's there
+  hex = hex.replace(/^#/, "");
+
+  // Parse the hex values
+  let r = parseInt(hex.slice(0, 2), 16);
+  let g = parseInt(hex.slice(2, 4), 16);
+  let b = parseInt(hex.slice(4, 6), 16);
+
+  // Convert the RGB values to the range [0, 1]
+  r /= 255;
+  g /= 255;
+  b /= 255;
+
+  // Find the maximum and minimum values to get the lightness
+  let max = Math.max(r, g, b);
+  let min = Math.min(r, g, b);
+  let h,
+    s,
+    l = (max + min) / 2;
+
+  if (max === min) {
+    h = s = 0; // Achromatic
+  } else {
+    let d = max - min;
+    s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
+
+    switch (max) {
+      case r:
+        h = (g - b) / d + (g < b ? 6 : 0);
+        break;
+      case g:
+        h = (b - r) / d + 2;
+        break;
+      case b:
+        h = (r - g) / d + 4;
+        break;
+    }
 
+    h *= 60;
+  }
+
+  return {
+    hue: Math.round(h),
+    saturation: Math.round(s * 100),
+    lightness: Math.round(l * 100),
+  };
+}
+
+function Color({ color }) {
   const { cssvars, updateVar } = useContext(CustomizerContext);
+  const [hexValue, setHexValue] = useState("");
+
   const hName = `${color}-h`;
   const sName = `${color}-s`;
   const lName = `${color}-l`;
@@ -28,14 +119,74 @@ function Color({ color }) {
 
   const handleReset = (event) => {
     event.preventDefault();
-    updateVar(h.id, h.start, h.unit);
-    updateVar(s.id, s.start, s.unit);
-    updateVar(l.id, l.start, l.unit);
-    // document.documentElement.style.removeProperty(`--bulma-${hName}`);
-    // document.documentElement.style.removeProperty(`--bulma-${sName}`);
-    // document.documentElement.style.removeProperty(`--bulma-${lName}`);
+    updateVar(h.id, h.start);
+    updateVar(s.id, s.start);
+    updateVar(l.id, l.start);
+  };
+
+  const handleHexInput = (event) => {
+    event.preventDefault();
+
+    let value = window.prompt("Enter a Hexadecimal value (e.g. 00d1b2)");
+
+    if (value.startsWith("#")) {
+      value = value.replace(/^#/, "");
+    }
+
+    const hexPattern = /^([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$/;
+
+    if (!hexPattern.test(value) || value.length > 6) {
+      window.prompt("That is not a valid Hexadecimal value. Please try again.");
+      return;
+    }
+
+    if (value.length === 3) {
+      value = value[0] + value[0] + value[1] + value[1] + value[2] + value[2];
+    }
+
+    const { hue, saturation, lightness } = hexToHsl(value);
+
+    updateVar(h.id, hue);
+    updateVar(s.id, saturation);
+    updateVar(l.id, lightness);
+  };
+
+  const handleHexChange = (event) => {
+    let value = event.target.value;
+
+    if (value.startsWith("#")) {
+      value = value.replace(/^#/, "");
+    }
+
+    setHexValue(value);
+
+    const hexPattern = /^([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$/;
+
+    if (!hexPattern.test(value) || value.length < 6) {
+      return;
+    }
+
+    const { hue, saturation, lightness } = hexToHsl(value);
+
+    updateVar(h.id, hue);
+    updateVar(s.id, saturation);
+    updateVar(l.id, lightness);
   };
 
+  const handleInputChange = (event, cssvar) => {
+    let value = event.target.value;
+    updateVar(cssvar.id, value);
+  };
+
+  useEffect(() => {
+    if (!h) {
+      return;
+    }
+
+    const hex = hslToHex(h.current, s.current, l.current);
+    setHexValue(hex);
+  }, [h, s, l]);
+
   if (!h) {
     return;
   }
@@ -51,52 +202,80 @@ function Color({ color }) {
           <p>{name}</p>
         </div>
 
-        <button
-          className="button is-small"
-          onClick={handleReset}
-          disabled={isDisabled}
-        >
-          Reset
+        <button className="button is-small" onClick={handleHexInput}>
+          Enter a Hex code
         </button>
+
+        <div className="is-hidden field has-addons">
+          <p className="control">
+            <span className="button is-static">#</span>
+          </p>
+          <p className="control">
+            <input
+              className="input"
+              type="text"
+              value={hexValue}
+              onChange={handleHexChange}
+            />
+          </p>
+          <p className="control">
+            <span className="button is-icon">Copy</span>
+          </p>
+        </div>
       </div>
 
       <div className={cn.lines}>
         <div className={cn.line}>
           <p>Hue</p>
           <Slider id={hName} kind="hue" color={color} />
-          <p>
-            <code>
-              {h.current}
-              {h.unit}
-            </code>
+          <p className={cn.form}>
+            <input
+              type="text"
+              className="input"
+              value={h.current}
+              onChange={(e) => handleInputChange(e, h)}
+              size="3"
+            />
+            <span>{h.unit}</span>
           </p>
         </div>
 
         <div className={cn.line}>
           <p>Saturation</p>
           <Slider id={sName} kind="saturation" color={color} />
-          <p>
-            <code>
-              {s.current}
-              {s.unit}
-            </code>
+          <p className={cn.form}>
+            <input
+              type="text"
+              className="input"
+              value={s.current}
+              onChange={(e) => handleInputChange(e, s)}
+              size="3"
+            />
+            <span>{s.unit}</span>
           </p>
         </div>
 
         <div className={cn.line}>
           <p>Lightness</p>
           <Slider id={lName} kind="lightness" color={color} />
-          <p>
-            <code>
-              {l.current}
-              {l.unit}
-            </code>
+          <p className={cn.form}>
+            <input
+              type="text"
+              className="input"
+              value={l.current}
+              onChange={(e) => handleInputChange(e, l)}
+              size="3"
+            />
+            <span>{l.unit}</span>
           </p>
         </div>
       </div>
 
       <div className={cn.side}>
         <button className={`button is-${color}`}>{name}</button>
+        <button className="button" onClick={handleReset} disabled={isDisabled}>
+          Reset
+        </button>
       </div>
     </div>
   );
index 48088b7332faec1b81394bf6cb1ef64ef4cf84e4..bce0b8d87a51cc5f15bce014c4ab05ec83d08a41 100644 (file)
   color: var(--bulma-text-strong);
   width: 6rem;
 }
+
+.form {
+  display: flex;
+  align-items: center;
+  font-family: var(--bulma-family-code);
+  gap: 0.25em;
+}
+
+.form input {
+  font-family: inherit;
+  font-size: inherit;
+  padding: 0.25em;
+  height: auto;
+  border-radius: 0.25em;
+  width: 3em;
+  padding: 0 0.25em;
+}
+
+.form span {
+  opacity: 0.5;
+}
index 1adee34a9c8f880adfdae7cb5ffb77838fd7a39c..c46f62b5824480dd49d55e2b0eb359d7983f87fb 100644 (file)
@@ -27,14 +27,14 @@ const valueToX = (value, width, min, max) => {
 
 function Slider({ id, color, kind }) {
   const { cssvars, updateVar } = useContext(CustomizerContext);
-  const { start, unit, current } = cssvars[id];
+  const { start, current } = cssvars[id];
   const [min, max] = RANGES[kind];
 
   const sliderRef = useRef(null);
   const handleRef = useRef(null);
 
   const [isMoving, setMoving] = useState(false);
-  const [x, setX] = useState(valueToX(start, 240, min, max));
+  const [x, setX] = useState(valueToX(start, 360, min, max));
 
   const handleMouseDown = (event) => {
     setMoving(true);
@@ -69,11 +69,11 @@ function Slider({ id, color, kind }) {
     const slider = sliderRef.current;
     const sliderRect = slider.getBoundingClientRect();
     const final = xToValue(x, sliderRect.width, min, max);
-    updateVar(id, final, unit);
-  }, [id, min, max, updateVar, unit, x]);
+    updateVar(id, final);
+  }, [id, min, max, updateVar, x]);
 
   useEffect(() => {
-    const newX = valueToX(current, 240, min, max);
+    const newX = valueToX(current, 360, min, max);
     setX(newX);
   }, [min, max, current]);
 
index 9c46d0b5daead9ba2b5c996f9a8a65323463b9ad..4e7433d5b0b410605d60d61f6139ad360cc12336 100644 (file)
@@ -1,6 +1,6 @@
 .main {
   position: relative;
-  width: 15rem;
+  width: 360px;
   padding: 0.375rem 0;
   cursor: grab;
 }