Completed color picker, value editor, desktop layout.

This commit is contained in:
Jay
2025-08-13 18:05:29 -04:00
parent ae02e49ce2
commit 7a2e4cf2ae
22 changed files with 533 additions and 339 deletions
+72 -131
View File
@@ -1,7 +1,42 @@
.appWrapper {
background-color: white;
height: 100%;
width: 100%;
max-width: 1200px;
overflow: hidden;
margin: 0 auto;
box-shadow: 0 0 40px #7a7a7a;
border-left: 2px solid #7a7a7a;
border-right: 2px solid #7a7a7a;
}
.appHeader {
height: 40px;
display: flex;
align-items: baseline;
border-bottom: 2px solid #7a7a7a;
padding: 20px 30px 12px;
}
.appHeader .title {
/* Typography */
font-family: "Inter", "Roboto", sans-serif;
font-size: 2rem;
font-weight: 400;
letter-spacing: 0.4rem;
text-transform: uppercase;
line-height: 1;
}
.appHeader .subtitle {
margin-left: 1.5rem;
font-family: "Inter", "Roboto", sans-serif;
font-style: italic;
font-size: 1rem;
font-weight: 300;
letter-spacing: 0.08rem;
line-height: 1;
color: #7a7a7a;
}
.mobileContent,
@@ -17,146 +52,52 @@
scrollbar-width: none;
}
.appWrapper {
}
.mobileContent {
display: none;
}
.mainContent {
display: grid;
grid-template-columns: 1fr 2fr;
}
.firstZone {
display: flex;
flex-direction: column;
border-right: 2px solid #7a7a7a;
}
.secondZone {
padding: 40px;
color: #555;
font-style: italic;
}
.colorPickerWrapper {
border-bottom: 2px solid #7a7a7a;
}
.colorValuesWrapper {
}
.paletteEditorWrapper {
}
.paletteLibraryWrapper {
}
/* Large */
@media (min-width: 992px),
(min-width: 568px) and (max-width: 991px) and (orientation: portrait) {
.appWrapper {
max-width: 1200px;
margin: 0 auto;
}
.mobileContent {
display: none;
}
.mainContent {
display: flex;
}
.firstZone {
flex: 1;
}
.secondZone {
flex: 1;
}
.colorPickerWrapper {
}
.colorValuesWrapper {
}
.paletteEditorWrapper {
}
.paletteLibraryWrapper {
}
}
/* Landscape Phone */
@media (min-width: 568px) and (max-width: 991px) and (orientation: landscape) {
.appWrapper {
}
.mainContent,
.mobileTopNav {
display: none;
}
.mobileContent {
display: flex;
}
.mobileLeftNav {
}
.mobileRightNav {
}
.mobileFirstZone {
flex: 1;
}
.mobileSecondZone {
flex: 1;
}
.tabWrapper {
max-height: 100%;
overflow-y: scroll;
scroll-snap-type: y mandatory;
}
.tabWrapper .tab {
scroll-snap-align: start;
}
.colorPickerWrapper {
}
.colorValuesWrapper {
}
.paletteEditorWrapper {
}
.paletteLibraryWrapper {
}
}
/* Portrait Phone */
@media (max-width: 567px) {
.appWrapper {
}
.mainContent,
.mobileRightNav,
.mobileLeftNav {
display: none;
}
.mobileContent {
}
.mobileTopNav {
display: flex;
}
.leftMenuButton {
margin-right: auto;
}
.rightMenuButton {
margin-left: auto;
}
.mobileFirstZone {
}
.mobileSecondZone {
}
.tabWrapper {
display: flex;
overflow-x: scroll;
scroll-snap-type: x mandatory;
}
.tabWrapper .tab {
min-width: 100vw;
scroll-snap-align: start;
}
.colorPickerWrapper {
}
.colorValuesWrapper {
}
.paletteEditorWrapper {
}
.paletteLibraryWrapper {
}
}
+63 -23
View File
@@ -1,14 +1,16 @@
import { useReducer, useState } from "react";
import { useState } from "react";
import clsx from "clsx";
import { Color } from "colorlib";
import ColorPicker from "@components/ColorPicker/ColorPicker";
import ColorValues from "@components/ColorValues/ColorValues";
import { LeftMenu, RightMenu } from "@components/SideMenu";
import { useMediaQuery } from "@providers/hooks";
import { useSelectedColor } from "@providers/hooks";
import ColorPicker from "@/components/ColorPicker/ColorPicker";
import ColorValues from "@/components/ColorValues/ColorValues";
import { LeftMenu, RightMenu } from "@/components/SideMenu";
import { useMediaQuery } from "@/providers/hooks";
import { useSelectedColor } from "@/providers/hooks";
import styles from "./App.module.css";
import { formatCssRgb } from "./util";
// Menu Button Components
@@ -106,7 +108,7 @@ function MobileFirstZone() {
aria-roledescription="slide"
aria-label="Color Picker"
>
<ColorPicker />
<ColorPicker color={selectedColor} actions={selectedColorActions} />
</div>
<div
className={clsx(styles.tab, styles.colorValuesWrapper)}
@@ -191,13 +193,17 @@ function MobileContent({
// Desktop Layout Components
function TitleZone() {
return <section className={styles.TitleZone}></section>;
}
function FirstZone() {
const { selectedColor, selectedColorActions } = useSelectedColor();
return (
<section className={styles.firstZone} aria-label="Color tools">
<div className={styles.colorPickerWrapper} aria-label="Color picker">
<ColorPicker />
<ColorPicker color={selectedColor} actions={selectedColorActions} />
</div>
<div className={styles.colorValuesWrapper} aria-label="Color values">
@@ -210,6 +216,7 @@ function FirstZone() {
function SecondZone() {
return (
<section className={styles.secondZone} aria-label="Palette tools">
Palette Creator Coming Soon.
<div
className={styles.paletteEditorWrapper}
aria-label="Palette editor"
@@ -224,10 +231,16 @@ function SecondZone() {
function DesktopContent() {
return (
<main className={styles.mainContent}>
<FirstZone />
<SecondZone />
</main>
<>
<header className={styles.appHeader}>
<span className={styles.title}>LUMINANCE</span>
<span className={styles.subtitle}>A color picker for humans.</span>
</header>
<main className={styles.mainContent}>
<FirstZone />
<SecondZone />
</main>
</>
);
}
@@ -236,20 +249,47 @@ function DesktopContent() {
function App() {
const [isRightMenuOpen, setIsRightMenuOpen] = useState(false);
const [isLeftMenuOpen, setIsLeftMenuOpen] = useState(false);
const { isDesktop } = useMediaQuery();
// const { isDesktop } = useMediaQuery();
const isDesktop = true;
const lum = 0.75;
const chr = 0.8;
const steps = 8;
const colors = Array.from({ length: steps }, (_, index) => {
const hue = (index * 360) / (steps - 1);
return Color.from_hcl(hue, chr, lum);
});
const colorGradient = colors
.map((color, index) => {
const colorString = formatCssRgb(color.hex);
const percentage = (index / (colors.length - 1)) * 100;
return `${colorString} ${percentage}%`;
})
.join(", ");
return (
<div className={styles.appWrapper} role="application">
{!isDesktop && (
<MobileContent
isLeftMenuOpen={isLeftMenuOpen}
setIsLeftMenuOpen={setIsLeftMenuOpen}
isRightMenuOpen={isRightMenuOpen}
setIsRightMenuOpen={setIsRightMenuOpen}
/>
)}
<div
className={styles.background}
style={{
width: "100%",
height: "100%",
background: `linear-gradient(180deg, ${colorGradient})`,
}}
>
<div className={styles.appWrapper} role="application">
{!isDesktop && (
<MobileContent
isLeftMenuOpen={isLeftMenuOpen}
setIsLeftMenuOpen={setIsLeftMenuOpen}
isRightMenuOpen={isRightMenuOpen}
setIsRightMenuOpen={setIsRightMenuOpen}
/>
)}
{isDesktop && <DesktopContent />}
{isDesktop && <DesktopContent />}
</div>
</div>
);
}
@@ -1,36 +1,110 @@
.container {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
padding: 40px;
display: grid;
grid-template-columns: 25px 1fr 25px;
grid-template-rows: 50px 1fr 25px;
grid-template-areas:
". preview ."
"leftGrip square rightGrip"
". bottomGrip ."
". bar .";
}
.pickerSquare {
flex: 1;
grid-area: square;
position: relative;
aspect-ratio: 1/1;
border: 2px solid #7a7a7a;
}
.pickerBar {
flex: 1;
grid-area: bar;
position: relative;
height: 25px;
margin-top: 15px;
border: 2px solid #7a7a7a;
}
/* Large - Landscape Tablets / Desktops */
/* Medium - Portrait Tablets */
/* Horizontal layout, vertically scrolling picker and palette content */
@media (min-width: 992px),
(min-width: 568px) and (max-width: 991px) and (orientation: portrait) {
.verticalGripLeft {
grid-area: leftGrip;
}
/* Medium - Landscape Phones */
/* Horizontal layout, side menu, vertical tabbed picker */
@media (min-width: 568px) and (max-width: 991px) and (orientation: landscape) {
.container {
flex-direction: row;
}
.verticalGripRight {
grid-area: rightGrip;
}
/* Small - Portrait Phones*/
/* Vertical layout, side menu, horizontal tabbed picker */
@media (max-width: 567px) {
.horizontalGrip {
grid-area: bottomGrip;
}
/* Color Square */
.colorSquareWrapper {
height: 100%;
width: 100%;
}
.colorSquare {
cursor: crosshair;
}
/* Color Bar */
.colorBarWrapper {
height: 100%;
width: 100%;
}
.colorBar {
cursor: crosshair;
}
/* Crosshairs */
.crosshairWrapper {
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
pointer-events: none;
}
.crosshair {
position: absolute;
transition:
background-color 400ms,
box-shadow 400ms;
pointer-events: none;
}
.crossEye {
position: absolute;
width: 9px;
height: 9px;
z-index: 2;
border-radius: 9px;
border-width: 2px;
border-style: solid;
transition:
border-color 350ms,
box-shadow 400ms;
pointer-events: none;
}
/* Square Grips */
.gripSlider {
position: relative;
width: 100%;
height: 100%;
}
.grip {
position: absolute;
width: 0;
height: 0;
}
/* Preview */
.preview {
grid-area: preview;
margin-bottom: 15px;
border: 2px solid #7a7a7a;
}
+102 -5
View File
@@ -1,10 +1,107 @@
import styles from "./ColorPicker.module.css";
import { useEffect, useRef, useState } from "react";
import * as colorlib from "colorlib";
import type { ColorActions } from "@/hooks/color";
import { useResize } from "@/hooks/window";
import { Direction } from "@/types";
import type { CartesianSpace } from "@/types";
import { formatCssRgb, setMeasurements } from "@/util";
import ColorBar from "./ColorBar";
import styles from "./ColorPicker.module.css";
import ColorSquare from "./ColorSquare";
import { BarCrosshair, SquareCrosshair } from "./Crosshair";
import GripSlider from "./GripSlider";
function ColorPicker({
color,
actions,
}: {
color: colorlib.Color;
actions: ColorActions;
}) {
const containerRef = useRef<HTMLDivElement>(null);
const hueRange = { min: 0, max: 359 };
const lumRange = { min: 0, max: 1 };
const [_origin, setOrigin] = useState<CartesianSpace>({ x: 0, y: 0 });
const [dimensions, setDimensions] = useState<CartesianSpace>({ x: 0, y: 0 });
// Get measurements
useEffect(() => {
if (containerRef.current) {
setMeasurements(containerRef, setOrigin, setDimensions);
}
return useResize(() => {
setMeasurements(containerRef, setOrigin, setDimensions);
});
}, [containerRef.current]);
function ColorPicker() {
return (
<div className={styles.container}>
<div className={styles.pickerSquare}>Square</div>
<div className={styles.pickerBar}>Bar</div>
<div className={styles.container} ref={containerRef}>
<div
className={styles.preview}
style={{
backgroundColor: formatCssRgb(color.hex),
}}
></div>
<div className={styles.verticalGripLeft}>
<GripSlider
direction={Direction.VERTICAL}
value={color.hcl.l}
setValue={actions.hcl.setL}
valueRange={lumRange}
arrowDirection="right"
invert={true}
/>
</div>
<div className={styles.pickerSquare}>
<SquareCrosshair
hue={color.hcl.h}
luminance={color.hcl.l}
hex={color.hex}
/>
<ColorSquare
chroma={color.hcl.c}
actions={actions.hcl}
parentDimensions={dimensions}
/>
</div>
<div className={styles.verticalGripRight}>
<GripSlider
direction={Direction.VERTICAL}
value={color.hcl.l}
setValue={actions.hcl.setL}
valueRange={lumRange}
arrowDirection="left"
invert={true}
/>
</div>
<div className={styles.horizontalGrip}>
<GripSlider
direction={Direction.HORIZONTAL}
value={color.hcl.h}
setValue={actions.hcl.setH}
valueRange={hueRange}
arrowDirection="up"
/>
</div>
<div className={styles.pickerBar}>
<ColorBar
hue={color.hcl.h}
chroma={color.hcl.c}
luminance={color.hcl.l}
setChroma={actions.hcl.setC}
parentDimensions={dimensions}
/>
<BarCrosshair
chroma={color.hcl.c}
luminance={color.hcl.l}
hex={color.hex}
/>
</div>
</div>
);
}
@@ -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;
}
+1 -1
View File
@@ -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";
@@ -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";
@@ -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";
@@ -1,6 +0,0 @@
.spaceWrapper {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
}
+2 -2
View File
@@ -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 =
@@ -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";
@@ -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;
}
+7 -7
View File
@@ -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,
);
@@ -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";
+1 -1
View File
@@ -62,7 +62,7 @@ export function colorReducer(
}
}
type Setter = (valOrCallback: SetterValueOrCallback<number>) => void;
export type Setter = (valOrCallback: SetterValueOrCallback<number>) => void;
export interface CommonColorActions {
setColor: (color: colorlib.Color) => void;
+22 -3
View File
@@ -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<number>;
xValueRange: Range;
yValueRange: Range;
invertX?: boolean;
invertY?: boolean;
}) {
const [isDragging, setIsDragging] = useState(false);
const crosshairRef = useRef<HTMLDivElement>(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],
);
+18 -2
View File
@@ -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<number>;
invert?: boolean;
}) {
const [isDragging, setIsDragging] = useState(false);
const sliderRef = useRef<HTMLDivElement>(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);
}, []);
+3 -3
View File
@@ -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;
+6
View File
@@ -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})`;
}