-
Square
-
Bar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/src/components/ColorValues/ColorValues.module.css b/src/components/ColorValues/ColorValues.module.css
index 5fbec1b..45bdf14 100644
--- a/src/components/ColorValues/ColorValues.module.css
+++ b/src/components/ColorValues/ColorValues.module.css
@@ -1,6 +1,137 @@
.colorValuesWrapper {
- width: 100%;
- height: 100%;
+ padding: 40px;
display: flex;
flex-direction: column;
+ gap: 16px;
+}
+
+.spaceWrapper {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ min-height: 0;
+ border: 2px solid #7a7a7a;
+}
+
+.componentWrapper {
+ display: flex;
+ align-items: stretch;
+ width: 100%;
+ height: 25px;
+ font-family: monospace;
+ border-top: 1px solid #7a7a7a;
+ border-bottom: 1px solid #7a7a7a;
+}
+
+.componentWrapper:first-of-type {
+ border-top: none;
+}
+
+.componentWrapper:last-of-type {
+ border-bottom: none;
+}
+
+.section {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-right: 2px solid #7a7a7a;
+}
+
+.section:last-of-type {
+ border-right: none;
+}
+
+.symbol {
+ font-family: monospace;
+ font-size: 14px;
+ aspect-ratio: 1 / 1;
+}
+
+.sliderSection {
+ position: relative;
+ flex-grow: 1;
+}
+
+.sliderWrapper {
+ width: 100%;
+ height: 100%;
+}
+
+.sliderBar {
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 100%;
+ background-color: #aaa;
+ pointer-events: none;
+}
+
+.buttonWrapper {
+ aspect-ratio: 1 / 1;
+}
+
+.button {
+ height: 100%;
+ width: 100%;
+ padding: 0;
+ cursor: pointer;
+ user-select: none;
+ background: none;
+ border: none;
+}
+
+.button:hover {
+ background-color: #ddd;
+}
+
+.button:active {
+ background-color: #bbb;
+}
+
+.valueWrapper {
+ aspect-ratio: 1.5 / 1;
+ min-width: 40px;
+}
+
+.value {
+ height: 100%;
+ width: 100%;
+ background: none;
+ border: none;
+ padding: 0 5px;
+ font-family: monospace;
+ font-size: 14px;
+ text-align: right;
+}
+
+.hexEditor {
+ display: flex;
+ align-items: stretch;
+ font-family: monospace;
+ border: 2px solid #7a7a7a;
+ height: 25px;
+ max-width: 150px;
+}
+
+.hexLabel {
+ font-family: monospace;
+ font-size: 14px;
+ padding: 0 10px;
+}
+
+.hexValueWrapper {
+ flex: 1;
+}
+
+.hexValueWrapper input {
+ height: 100%;
+ width: 100%;
+ background: none;
+ border: none;
+ padding: 0 5px;
+ font-family: monospace;
+ font-size: 14px;
+ letter-spacing: 0.1em;
+ text-align: center;
}
diff --git a/src/components/ColorValues/ColorValues.tsx b/src/components/ColorValues/ColorValues.tsx
index ae4a46f..888f5d2 100644
--- a/src/components/ColorValues/ColorValues.tsx
+++ b/src/components/ColorValues/ColorValues.tsx
@@ -3,7 +3,7 @@ import type { KeyboardEvent } from "react";
import * as colorlib from "colorlib";
-import type { ColorActions } from "@hooks/color";
+import type { ColorActions } from "@/hooks/color";
import styles from "./ColorValues.module.css";
import SpaceEditor from "./SpaceEditor";
diff --git a/src/components/ColorValues/ColorValuesTest.cy.tsx b/src/components/ColorValues/ColorValuesTest.cy.tsx
index a16214b..ebdac5a 100644
--- a/src/components/ColorValues/ColorValuesTest.cy.tsx
+++ b/src/components/ColorValues/ColorValuesTest.cy.tsx
@@ -2,7 +2,7 @@ import { useReducer } from "react";
import { Color } from "colorlib";
-import { colorReducer, createColorActions } from "@hooks/color";
+import { colorReducer, createColorActions } from "@/hooks/color";
import ColorValues from "./ColorValues";
diff --git a/src/components/ColorValues/HexEditorTest.cy.tsx b/src/components/ColorValues/HexEditorTest.cy.tsx
index 7db6f55..e0e88a1 100644
--- a/src/components/ColorValues/HexEditorTest.cy.tsx
+++ b/src/components/ColorValues/HexEditorTest.cy.tsx
@@ -2,7 +2,7 @@ import { useReducer } from "react";
import { Color } from "colorlib";
-import { colorReducer, createColorActions } from "@hooks/color";
+import { colorReducer, createColorActions } from "@/hooks/color";
import { HexEditor } from "./ValueEditor";
diff --git a/src/components/ColorValues/SpaceEditor.module.css b/src/components/ColorValues/SpaceEditor.module.css
deleted file mode 100644
index 616096e..0000000
--- a/src/components/ColorValues/SpaceEditor.module.css
+++ /dev/null
@@ -1,6 +0,0 @@
-.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 c4c4034..ac29a34 100644
--- a/src/components/ColorValues/SpaceEditor.tsx
+++ b/src/components/ColorValues/SpaceEditor.tsx
@@ -4,9 +4,9 @@ import type {
HCLColorActions,
HSVColorActions,
RGBColorActions,
-} from "@hooks/color";
+} from "@/hooks/color";
-import styles from "./SpaceEditor.module.css";
+import styles from "./ColorValues.module.css";
import { ValueEditor } from "./ValueEditor";
type ColorSpaceProps =
diff --git a/src/components/ColorValues/SpaceEditorTest.cy.tsx b/src/components/ColorValues/SpaceEditorTest.cy.tsx
index 166f62f..6c9c0c4 100644
--- a/src/components/ColorValues/SpaceEditorTest.cy.tsx
+++ b/src/components/ColorValues/SpaceEditorTest.cy.tsx
@@ -2,8 +2,8 @@ import { useReducer } from "react";
import { Color } from "colorlib";
+import { colorReducer, createColorActions } from "@/hooks/color";
import { roundTo } from "@/util";
-import { colorReducer, createColorActions } from "@hooks/color";
import SpaceEditor from "./SpaceEditor";
diff --git a/src/components/ColorValues/ValueEditor.module.css b/src/components/ColorValues/ValueEditor.module.css
deleted file mode 100644
index 78142b7..0000000
--- a/src/components/ColorValues/ValueEditor.module.css
+++ /dev/null
@@ -1,118 +0,0 @@
-.componentWrapper {
- display: flex;
- align-items: stretch;
- width: 100%;
- height: 25px;
- font-family: monospace;
- border: 1px solid black;
- border-top: none;
-}
-
-.componentWrapper:first-of-type {
- border-top: 1px solid black;
-}
-
-.section {
- display: flex;
- align-items: center;
- justify-content: center;
- border-right: 1px solid black;
-}
-
-.section:last-of-type {
- border-right: none;
-}
-
-.symbol {
- font-family: monospace;
- font-size: 14px;
- aspect-ratio: 1 / 1;
-}
-
-.sliderSection {
- position: relative;
- flex-grow: 1;
-}
-
-.sliderWrapper {
- width: 100%;
- height: 100%;
-}
-
-.sliderBar {
- position: absolute;
- top: 0;
- left: 0;
- height: 100%;
- background-color: #aaa;
- pointer-events: none;
-}
-
-.buttonWrapper {
- aspect-ratio: 1 / 1;
-}
-
-.button {
- height: 100%;
- width: 100%;
- padding: 0;
- cursor: pointer;
- user-select: none;
- background: none;
- border: none;
-}
-
-.button:hover {
- background-color: #ddd;
-}
-
-.button:active {
- background-color: #bbb;
-}
-
-.valueWrapper {
- aspect-ratio: 1.5 / 1;
- min-width: 40px;
-}
-
-.value {
- height: 100%;
- width: 100%;
- background: none;
- border: none;
- padding: 0 5px;
- font-family: monospace;
- font-size: 14px;
- text-align: right;
-}
-
-.hexEditor {
- display: flex;
- align-items: stretch;
- font-family: monospace;
- border: 1px solid black;
- height: 25px;
- max-width: 150px;
-}
-
-.hexLabel {
- font-family: monospace;
- font-size: 14px;
- padding: 0 10px;
-}
-
-.hexValueWrapper {
- flex: 1;
-}
-
-.hexValueWrapper input {
- height: 100%;
- width: 100%;
- background: none;
- border: none;
- padding: 0 5px;
- font-family: monospace;
- font-size: 14px;
- letter-spacing: 0.1em;
- text-align: center;
-}
diff --git a/src/components/ColorValues/ValueEditor.tsx b/src/components/ColorValues/ValueEditor.tsx
index cd88550..8c83bb9 100644
--- a/src/components/ColorValues/ValueEditor.tsx
+++ b/src/components/ColorValues/ValueEditor.tsx
@@ -10,15 +10,15 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import clsx from "clsx";
import * as colorlib from "colorlib";
+import type { HexColorActions } from "@/hooks/color";
+import { useScroll } from "@/hooks/scroll";
+import { useSlider } from "@/hooks/slider";
+import { useResize } from "@/hooks/window";
import type { CartesianSpace, Range, Setter, Timeout } from "@/types";
import { Direction } from "@/types";
-import { minmax, setMeasurements, valueToPosition } from "@/util";
-import type { HexColorActions } from "@hooks/color";
-import { useScroll } from "@hooks/scroll";
-import { useSlider } from "@hooks/slider";
-import { useResize } from "@hooks/window";
+import { minmax, roundTo, setMeasurements, valueToPosition } from "@/util";
-import styles from "./ValueEditor.module.css";
+import styles from "./ColorValues.module.css";
// ------------ //
// Value Editor //
@@ -68,7 +68,7 @@ export function ValueEditor({
setValue((prev) => {
const scaledStep = step / scale;
const newValue = minmax(
- Math.floor(prev * scale) / scale + scaledStep,
+ roundTo(Math.floor(roundTo(prev * scale, 6)) / scale + scaledStep, 6),
valueRange.min,
valueRange.max,
);
diff --git a/src/components/ColorValues/ValueEditorTest.cy.tsx b/src/components/ColorValues/ValueEditorTest.cy.tsx
index f5d886a..f4b7304 100644
--- a/src/components/ColorValues/ValueEditorTest.cy.tsx
+++ b/src/components/ColorValues/ValueEditorTest.cy.tsx
@@ -2,7 +2,7 @@ import { useReducer } from "react";
import { Color } from "colorlib";
-import { colorReducer, createColorActions } from "@hooks/color";
+import { colorReducer, createColorActions } from "@/hooks/color";
import { ValueEditor } from "./ValueEditor";
diff --git a/src/hooks/color.ts b/src/hooks/color.ts
index fa19ba5..767c479 100644
--- a/src/hooks/color.ts
+++ b/src/hooks/color.ts
@@ -62,7 +62,7 @@ export function colorReducer(
}
}
-type Setter = (valOrCallback: SetterValueOrCallback
) => void;
+export type Setter = (valOrCallback: SetterValueOrCallback) => void;
export interface CommonColorActions {
setColor: (color: colorlib.Color) => void;
diff --git a/src/hooks/crosshair.tsx b/src/hooks/crosshair.tsx
index 39fb975..4d602df 100644
--- a/src/hooks/crosshair.tsx
+++ b/src/hooks/crosshair.tsx
@@ -9,6 +9,8 @@ import {
positionToValue,
} from "@/util";
+import { useSmoothAnimation } from "./animation";
+
if (typeof TouchEvent === "undefined") {
// @ts-ignore - intentionally creating global
window.TouchEvent = window.MouseEvent;
@@ -21,6 +23,8 @@ export function useCrosshair({
setYValue,
xValueRange,
yValueRange,
+ invertX,
+ invertY,
}: {
origin: CartesianSpace;
dimensions: CartesianSpace;
@@ -28,6 +32,8 @@ export function useCrosshair({
setYValue: Setter;
xValueRange: Range;
yValueRange: Range;
+ invertX?: boolean;
+ invertY?: boolean;
}) {
const [isDragging, setIsDragging] = useState(false);
const crosshairRef = useRef(null);
@@ -42,6 +48,9 @@ export function useCrosshair({
const xValueRangeRef = useRef(xValueRange);
const yValueRangeRef = useRef(yValueRange);
+ // Hooks
+ const smoothAnimation = useSmoothAnimation();
+
useEffect(() => {
originRef.current = origin;
dimensionsRef.current = dimensions;
@@ -55,13 +64,23 @@ export function useCrosshair({
const calculatePositions = useCallback((event: MouseEvent | TouchEvent) => {
const orig = originRef.current;
const dims = dimensionsRef.current;
+ const xRange = xValueRangeRef.current;
+ const yRange = yValueRangeRef.current;
const { clientX, clientY } = extractEventCoordinates(event);
const xPos = minmax(clientX - orig.x, 0, dims.x - 1);
const yPos = minmax(clientY - orig.y, 0, dims.y - 1);
- const newXValue = positionToValue(xPos, dims.x - 1, xValueRangeRef.current);
- const newYValue = positionToValue(yPos, dims.y - 1, yValueRangeRef.current);
+ let newXValue = positionToValue(xPos, dims.x - 1, xRange);
+ let newYValue = positionToValue(yPos, dims.y - 1, yRange);
+
+ if (invertX) {
+ newXValue = xRange.max - newXValue;
+ }
+
+ if (invertY) {
+ newYValue = yRange.max - newYValue;
+ }
setXValueRef.current(newXValue);
setYValueRef.current(newYValue);
@@ -70,7 +89,7 @@ export function useCrosshair({
const handleMove = useCallback(
(event: MouseEvent | TouchEvent) => {
event.preventDefault();
- calculatePositions(event);
+ smoothAnimation(() => calculatePositions(event));
},
[calculatePositions],
);
diff --git a/src/hooks/slider.tsx b/src/hooks/slider.tsx
index 65a594f..203b48e 100644
--- a/src/hooks/slider.tsx
+++ b/src/hooks/slider.tsx
@@ -12,6 +12,7 @@ import {
valueToPosition,
} from "@/util";
+import { useSmoothAnimation } from "./animation";
import { useScroll } from "./scroll";
if (typeof TouchEvent === "undefined") {
@@ -34,6 +35,7 @@ export function useSlider({
valueRange,
value,
setValue,
+ invert = false,
}: {
direction: Direction;
origin: CartesianSpace;
@@ -41,6 +43,7 @@ export function useSlider({
valueRange: Range;
value: number;
setValue: Setter;
+ invert?: boolean;
}) {
const [isDragging, setIsDragging] = useState(false);
const sliderRef = useRef(null);
@@ -59,6 +62,9 @@ export function useSlider({
const [position, setPosition] = useState(0);
const positionRef = useRef(position);
+ // Hooks
+ const smoothAnimation = useSmoothAnimation();
+
useEffect(() => {
directionRef.current = direction;
originRef.current = origin;
@@ -68,6 +74,11 @@ export function useSlider({
dimensions.x,
dimensions.y,
);
+ positionRef.current = valueToPosition(
+ value,
+ maxPosition.current,
+ valueRangeRef.current,
+ );
}, [direction, origin, dimensions]);
useEffect(() => {
@@ -94,19 +105,23 @@ export function useSlider({
0,
chooseValueByDirection(dir, dims.x, dims.y),
);
- const newValue = positionToValue(
+ let newValue = positionToValue(
newPosition,
maxPosition.current,
valueRangeRef.current,
);
+ if (invert) {
+ newValue = valueRangeRef.current.max - newValue;
+ }
+
setValueRef.current(newValue);
}, []);
const handleMove = useCallback(
(event: MouseEvent | TouchEvent) => {
event.preventDefault();
- calculatePosition(event);
+ smoothAnimation(() => calculatePosition(event));
},
[calculatePosition],
);
@@ -154,6 +169,7 @@ export function useSlider({
maxPosition.current,
valueRangeRef.current,
);
+
setValueRef.current(newValue);
}, []);
diff --git a/src/providers/SelectedColorProvider.tsx b/src/providers/SelectedColorProvider.tsx
index e0b4786..bffd8e7 100644
--- a/src/providers/SelectedColorProvider.tsx
+++ b/src/providers/SelectedColorProvider.tsx
@@ -1,10 +1,10 @@
-import { createContext, useContext, useReducer } from "react";
+import { createContext, useReducer } from "react";
import type { ReactNode } from "react";
import * as colorlib from "colorlib";
-import { colorReducer, createColorActions } from "@hooks/color";
-import type { ColorActions } from "@hooks/color";
+import { colorReducer, createColorActions } from "@/hooks/color";
+import type { ColorActions } from "@/hooks/color";
interface SelectedColorContextType {
selectedColor: colorlib.Color;
diff --git a/src/util.ts b/src/util.ts
index 2645f39..43a2a8a 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -1,5 +1,7 @@
import type { RefObject } from "react";
+import { Hex } from "colorlib";
+
import type { CartesianSpace, Range } from "./types";
import { Direction } from "./types";
@@ -94,3 +96,7 @@ export function roundTo(value: number, decimals: number = 0) {
const factor = Math.pow(10, decimals);
return Math.round(value * factor) / factor;
}
+
+export function formatCssRgb(hex: Hex) {
+ return `rgb(${hex.r},${hex.g},${hex.b})`;
+}
diff --git a/tsconfig.app.json b/tsconfig.app.json
index 3b06989..c5ce3c4 100644
--- a/tsconfig.app.json
+++ b/tsconfig.app.json
@@ -26,9 +26,6 @@
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
- "@components/*": ["./src/components/*"],
- "@hooks/*": ["./src/hooks/*"],
- "@providers/*": ["./src/providers/*"]
}
},
"include": ["src", "cypress"]
diff --git a/vite.config.ts b/vite.config.ts
index decd6d1..737e56d 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -11,9 +11,6 @@ export default defineConfig({
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
- "@components": path.resolve(__dirname, "./src/components"),
- "@hooks": path.resolve(__dirname, "./src/hooks"),
- "@providers": path.resolve(__dirname, "./src/providers"),
},
},
server: {