-import { useContext } from "react";
+import { useContext, useEffect, useState } from "react";
import PropTypes from "prop-types";
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`;
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;
}
<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>
);
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);
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]);