diff --git a/package-lock.json b/package-lock.json
index 1187d1e..035c882 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
"version": "0.0.0",
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^7.0.0",
+ "@fortawesome/free-regular-svg-icons": "^7.0.0",
"@fortawesome/free-solid-svg-icons": "^7.0.0",
"@fortawesome/react-fontawesome": "^0.2.3",
"motion": "^12.23.12",
@@ -1145,6 +1146,18 @@
"node": ">=6"
}
},
+ "node_modules/@fortawesome/free-regular-svg-icons": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-7.0.0.tgz",
+ "integrity": "sha512-qAh0mTaCY22sQzMK2lKBrtn/aR4keUu5XmtdYR7d702laMe0h+Ab4Kj2pExR9HZkKhjKoq8pbwt8Td+mjW/ipQ==",
+ "license": "(CC-BY-4.0 AND MIT)",
+ "dependencies": {
+ "@fortawesome/fontawesome-common-types": "7.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/@fortawesome/free-solid-svg-icons": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-7.0.0.tgz",
diff --git a/package.json b/package.json
index d3bafcc..f009d34 100644
--- a/package.json
+++ b/package.json
@@ -7,17 +7,18 @@
"build:wasm": "wasm-pack build colorlib -t bundler -d pkg --release",
"clean": "rm -rf dist colorlib/pkg*",
"cypress:open": "1>/dev/null 2>/dev/null cypress open -d &",
- "cypress:component": "cypress run --component",
- "cypress:e2e": "cypress run --e2e",
"dev": "vite",
"lint": "eslint .",
"preview": "vite preview",
"test:wasm": "cargo test --lib --manifest-path colorlib/Cargo.toml",
"test:wasmdoc": "cargo test --doc --manifest-path colorlib/Cargo.toml",
- "test": "vitest"
+ "test": "vitest",
+ "test:component:chrome": "cypress run --component -b chromium",
+ "test:component:fire": "cypress run --component -b firefox"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^7.0.0",
+ "@fortawesome/free-regular-svg-icons": "^7.0.0",
"@fortawesome/free-solid-svg-icons": "^7.0.0",
"@fortawesome/react-fontawesome": "^0.2.3",
"motion": "^12.23.12",
diff --git a/src/components/ColorValues/SpaceEditor.cy.tsx b/src/components/ColorValues/SpaceEditor.cy.tsx
index e69de29..c66875e 100644
--- a/src/components/ColorValues/SpaceEditor.cy.tsx
+++ b/src/components/ColorValues/SpaceEditor.cy.tsx
@@ -0,0 +1,106 @@
+import { useReducer } from "react";
+
+import { Color } from "colorlib";
+
+import { roundTo } from "@/util";
+import { colorReducer, createColorActions } from "@hooks/color";
+
+import SpaceEditor from "./SpaceEditor";
+
+const initialState = {
+ color: Color.from_hex("2edd9d"),
+};
+
+function TestWrapper() {
+ const [state, dispatch] = useReducer(colorReducer, initialState);
+ const actions = createColorActions(dispatch);
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+ HCL ({roundTo(state.color.hcl.h, 0)}, {roundTo(state.color.hcl.c, 2)},{" "}
+ {roundTo(state.color.hcl.l, 2)})
+
+
+ HSV ({roundTo(state.color.hsv.h, 0)}, {roundTo(state.color.hsv.s, 2)},{" "}
+ {roundTo(state.color.hsv.v, 2)})
+
+
+ RGB ({roundTo(state.color.rgb.r, 0)}, {roundTo(state.color.rgb.g, 0)},{" "}
+ {roundTo(state.color.rgb.b, 0)})
+
+
HEX: #{state.color.hex.to_code()}
+
+ >
+ );
+}
+
+describe("space editor tests", () => {
+ it("can edit color values", () => {
+ cy.mount();
+
+ // Confirm initial values
+ cy.dataCy("RGB-editor").within(() => {
+ cy.dataCy("R-value-input").should("have.value", 46);
+ cy.dataCy("G-value-input").should("have.value", 221);
+ cy.dataCy("B-value-input").should("have.value", 157);
+ });
+
+ cy.dataCy("HSV-editor").within(() => {
+ cy.dataCy("H-value-input").should("have.value", 158);
+ cy.dataCy("S-value-input").should("have.value", 79);
+ cy.dataCy("V-value-input").should("have.value", 87);
+ });
+
+ cy.dataCy("HCL-editor").within(() => {
+ cy.dataCy("H-value-input").should("have.value", 158);
+ cy.dataCy("C-value-input").should("have.value", 79);
+ cy.dataCy("L-value-input").should("have.value", 70);
+ });
+
+ cy.dataCy("rgb-value").should("have.text", "RGB (46, 221, 157)");
+ cy.dataCy("hsv-value").should("have.text", "HSV (158, 0.79, 0.87)");
+ cy.dataCy("hcl-value").should("have.text", "HCL (158, 0.79, 0.7)");
+ cy.dataCy("hex-value").should("have.text", "HEX: #2EDD9D");
+
+ // Update the color values
+ cy.wait(50); // ensure render
+ cy.dataCy("HCL-editor").within(() => {
+ cy.dataCy("H-slider").click();
+ cy.dataCy("C-decrement-button").click();
+ cy.dataCy("L-value-input").type("25");
+ });
+
+ cy.dataCy("rgb-value").should("have.text", "RGB (17, 76, 74)");
+ cy.dataCy("hsv-value").should("have.text", "HSV (179, 0.78, 0.3)");
+ cy.dataCy("hcl-value").should("have.text", "HCL (179, 0.78, 0.25)");
+ cy.dataCy("hex-value").should("have.text", "HEX: #104B4A");
+ });
+});
diff --git a/src/components/ColorValues/SpaceEditor.module.css b/src/components/ColorValues/SpaceEditor.module.css
index e69de29..616096e 100644
--- a/src/components/ColorValues/SpaceEditor.module.css
+++ b/src/components/ColorValues/SpaceEditor.module.css
@@ -0,0 +1,6 @@
+.spaceWrapper {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ min-height: 0;
+}
diff --git a/src/components/ColorValues/SpaceEditor.tsx b/src/components/ColorValues/SpaceEditor.tsx
index 7b0940c..500baaf 100644
--- a/src/components/ColorValues/SpaceEditor.tsx
+++ b/src/components/ColorValues/SpaceEditor.tsx
@@ -1,9 +1,165 @@
import * as colorlib from "colorlib";
-import styles from "./SpaceEditor.module.css";
+import type {
+ HCLColorActions,
+ HSVColorActions,
+ RGBColorActions,
+} from "@hooks/color";
-function SpaceEditor({}: {}) {
- return;
+import styles from "./SpaceEditor.module.css";
+import ValueEditor from "./ValueEditor";
+
+type SpaceEditorProps =
+ | { space: "RGB"; color: colorlib.RGB; actions: RGBColorActions }
+ | { space: "HSV"; color: colorlib.HSV; actions: HSVColorActions }
+ | { space: "HCL"; color: colorlib.HCL; actions: HCLColorActions };
+
+function SpaceEditor({ space, color, actions }: SpaceEditorProps) {
+ switch (space) {
+ case "RGB":
+ return ;
+
+ case "HSV":
+ return ;
+
+ case "HCL":
+ return ;
+
+ default:
+ return <>>;
+ }
+}
+
+const COLOR_SPACES = {
+ RGB: {
+ symbols: { r: "R", g: "G", b: "B" },
+ ranges: {
+ r: { min: 0, max: 255 },
+ g: { min: 0, max: 255 },
+ b: { min: 0, max: 255 },
+ },
+ },
+ HSV: {
+ symbols: { h: "H", s: "S", v: "V" },
+ ranges: {
+ h: { min: 0, max: 359 },
+ s: { min: 0, max: 1 },
+ v: { min: 0, max: 1 },
+ },
+ scales: {
+ s: 100,
+ v: 100,
+ },
+ },
+ HCL: {
+ symbols: { h: "H", c: "C", l: "L" },
+ ranges: {
+ h: { min: 0, max: 359 },
+ c: { min: 0, max: 1 },
+ l: { min: 0, max: 1 },
+ },
+ scales: {
+ c: 100,
+ l: 100,
+ },
+ },
+};
+
+function RGBSpaceEditor({
+ color,
+ actions,
+}: {
+ color: colorlib.RGB;
+ actions: RGBColorActions;
+}) {
+ return (
+
+
+
+
+
+ );
+}
+
+function HSVSpaceEditor({
+ color,
+ actions,
+}: {
+ color: colorlib.HSV;
+ actions: HSVColorActions;
+}) {
+ return (
+
+
+
+
+
+ );
+}
+
+function HCLSpaceEditor({
+ color,
+ actions,
+}: {
+ color: colorlib.HCL;
+ actions: HCLColorActions;
+}) {
+ return (
+
+
+
+
+
+ );
}
export default SpaceEditor;
diff --git a/src/components/ColorValues/ValueEditor.module.css b/src/components/ColorValues/ValueEditor.module.css
index a1d35d5..abcb415 100644
--- a/src/components/ColorValues/ValueEditor.module.css
+++ b/src/components/ColorValues/ValueEditor.module.css
@@ -2,7 +2,7 @@
display: flex;
align-items: stretch;
width: 100%;
- height: var(--height, 25px);
+ min-height: 0;
font-family: monospace;
border: 1px solid black;
border-top: none;
@@ -84,5 +84,4 @@
font-family: monospace;
font-size: 14px;
text-align: right;
- box-sizing: border-box;
}
diff --git a/src/components/ColorValues/ValueEditor.tsx b/src/components/ColorValues/ValueEditor.tsx
index 745e1fc..4ca07c5 100644
--- a/src/components/ColorValues/ValueEditor.tsx
+++ b/src/components/ColorValues/ValueEditor.tsx
@@ -1,5 +1,5 @@
import { useEffect, useRef, useState } from "react";
-import type { ChangeEvent, RefObject } from "react";
+import type { CSSProperties, ChangeEvent, RefObject } from "react";
import {
faChevronLeft,
@@ -36,17 +36,18 @@ function ValueEditor({
const direction = Direction.HORIZONTAL;
const [origin, setOrigin] = useState({ x: 0, y: 0 });
const [dimensions, setDimensions] = useState({ x: 0, y: 0 });
- const position = useRef(0);
+ const [position, setPosition] = useState(0);
useEffect(() => {
- position.current = valueToPosition(value, dimensions.x, valueRange);
+ const newPosition = valueToPosition(value, dimensions.x, valueRange);
+ setPosition(newPosition);
}, [value, dimensions, valueRange]);
// Handler functions
const handleInputChange = (e: ChangeEvent) => {
const inputValue = parseInt(e.target.value, 10);
if (!isNaN(inputValue)) {
- const actualValue = inputValue / scale;
+ const actualValue = Math.floor(inputValue) / scale;
const newValue = minmax(actualValue, valueRange.min, valueRange.max);
setValue(newValue);
@@ -59,7 +60,7 @@ function ValueEditor({
setValue((prev) => {
const scaledStep = step / scale;
const newValue = minmax(
- prev + scaledStep,
+ Math.floor(prev * scale) / scale + scaledStep,
valueRange.min,
valueRange.max,
);
@@ -96,7 +97,7 @@ function ValueEditor({
diff --git a/src/components/ColorValues/ValueEditorTest.cy.tsx b/src/components/ColorValues/ValueEditorTest.cy.tsx
index f5d9ff0..bfed38b 100644
--- a/src/components/ColorValues/ValueEditorTest.cy.tsx
+++ b/src/components/ColorValues/ValueEditorTest.cy.tsx
@@ -15,7 +15,14 @@ function TestWrapper() {
const actions = createColorActions(dispatch);
return (
-
+
{
cy.clock().then((clock) => clock.restore());
});
- it("works with mouse events", () => {
+ it.only("works with mouse events", () => {
// Check initial state
cy.dataCy("R-slider-bar")
.should("have.css", "width", "0px")
diff --git a/src/util.ts b/src/util.ts
index f85b63e..2645f39 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -89,3 +89,8 @@ export function chooseValueByDirection(
) {
return direction === Direction.HORIZONTAL ? xValue : yValue;
}
+
+export function roundTo(value: number, decimals: number = 0) {
+ const factor = Math.pow(10, decimals);
+ return Math.round(value * factor) / factor;
+}