Completed color picker, value editor, desktop layout.
This commit is contained in:
+2
-2
@@ -55,9 +55,9 @@
|
|||||||
"^react$",
|
"^react$",
|
||||||
"^react-dom(.*)$",
|
"^react-dom(.*)$",
|
||||||
"^react(.*)$",
|
"^react(.*)$",
|
||||||
"^@(?!(components|hooks|providers|/))(.*)$",
|
"^@(?!(/))(.*)$",
|
||||||
"^(?!@|[.])(.*)$",
|
"^(?!@|[.])(.*)$",
|
||||||
"^@(/|components|hooks|providers)(.*)$",
|
"^@(/)(.*)$",
|
||||||
"^[./]"
|
"^[./]"
|
||||||
],
|
],
|
||||||
"importOrderSeparation": true,
|
"importOrderSeparation": true,
|
||||||
|
|||||||
+72
-131
@@ -1,7 +1,42 @@
|
|||||||
.appWrapper {
|
.appWrapper {
|
||||||
|
background-color: white;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
max-width: 1200px;
|
||||||
overflow: hidden;
|
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,
|
.mobileContent,
|
||||||
@@ -17,146 +52,52 @@
|
|||||||
scrollbar-width: none;
|
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 */
|
/* Large */
|
||||||
@media (min-width: 992px),
|
@media (min-width: 992px),
|
||||||
(min-width: 568px) and (max-width: 991px) and (orientation: portrait) {
|
(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 */
|
/* Landscape Phone */
|
||||||
@media (min-width: 568px) and (max-width: 991px) and (orientation: landscape) {
|
@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 */
|
/* Portrait Phone */
|
||||||
@media (max-width: 567px) {
|
@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
@@ -1,14 +1,16 @@
|
|||||||
import { useReducer, useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
import { Color } from "colorlib";
|
||||||
|
|
||||||
import ColorPicker from "@components/ColorPicker/ColorPicker";
|
import ColorPicker from "@/components/ColorPicker/ColorPicker";
|
||||||
import ColorValues from "@components/ColorValues/ColorValues";
|
import ColorValues from "@/components/ColorValues/ColorValues";
|
||||||
import { LeftMenu, RightMenu } from "@components/SideMenu";
|
import { LeftMenu, RightMenu } from "@/components/SideMenu";
|
||||||
import { useMediaQuery } from "@providers/hooks";
|
import { useMediaQuery } from "@/providers/hooks";
|
||||||
import { useSelectedColor } from "@providers/hooks";
|
import { useSelectedColor } from "@/providers/hooks";
|
||||||
|
|
||||||
import styles from "./App.module.css";
|
import styles from "./App.module.css";
|
||||||
|
import { formatCssRgb } from "./util";
|
||||||
|
|
||||||
// Menu Button Components
|
// Menu Button Components
|
||||||
|
|
||||||
@@ -106,7 +108,7 @@ function MobileFirstZone() {
|
|||||||
aria-roledescription="slide"
|
aria-roledescription="slide"
|
||||||
aria-label="Color Picker"
|
aria-label="Color Picker"
|
||||||
>
|
>
|
||||||
<ColorPicker />
|
<ColorPicker color={selectedColor} actions={selectedColorActions} />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={clsx(styles.tab, styles.colorValuesWrapper)}
|
className={clsx(styles.tab, styles.colorValuesWrapper)}
|
||||||
@@ -191,13 +193,17 @@ function MobileContent({
|
|||||||
|
|
||||||
// Desktop Layout Components
|
// Desktop Layout Components
|
||||||
|
|
||||||
|
function TitleZone() {
|
||||||
|
return <section className={styles.TitleZone}></section>;
|
||||||
|
}
|
||||||
|
|
||||||
function FirstZone() {
|
function FirstZone() {
|
||||||
const { selectedColor, selectedColorActions } = useSelectedColor();
|
const { selectedColor, selectedColorActions } = useSelectedColor();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.firstZone} aria-label="Color tools">
|
<section className={styles.firstZone} aria-label="Color tools">
|
||||||
<div className={styles.colorPickerWrapper} aria-label="Color picker">
|
<div className={styles.colorPickerWrapper} aria-label="Color picker">
|
||||||
<ColorPicker />
|
<ColorPicker color={selectedColor} actions={selectedColorActions} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.colorValuesWrapper} aria-label="Color values">
|
<div className={styles.colorValuesWrapper} aria-label="Color values">
|
||||||
@@ -210,6 +216,7 @@ function FirstZone() {
|
|||||||
function SecondZone() {
|
function SecondZone() {
|
||||||
return (
|
return (
|
||||||
<section className={styles.secondZone} aria-label="Palette tools">
|
<section className={styles.secondZone} aria-label="Palette tools">
|
||||||
|
Palette Creator Coming Soon.
|
||||||
<div
|
<div
|
||||||
className={styles.paletteEditorWrapper}
|
className={styles.paletteEditorWrapper}
|
||||||
aria-label="Palette editor"
|
aria-label="Palette editor"
|
||||||
@@ -224,10 +231,16 @@ function SecondZone() {
|
|||||||
|
|
||||||
function DesktopContent() {
|
function DesktopContent() {
|
||||||
return (
|
return (
|
||||||
<main className={styles.mainContent}>
|
<>
|
||||||
<FirstZone />
|
<header className={styles.appHeader}>
|
||||||
<SecondZone />
|
<span className={styles.title}>LUMINANCE</span>
|
||||||
</main>
|
<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() {
|
function App() {
|
||||||
const [isRightMenuOpen, setIsRightMenuOpen] = useState(false);
|
const [isRightMenuOpen, setIsRightMenuOpen] = useState(false);
|
||||||
const [isLeftMenuOpen, setIsLeftMenuOpen] = 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 (
|
return (
|
||||||
<div className={styles.appWrapper} role="application">
|
<div
|
||||||
{!isDesktop && (
|
className={styles.background}
|
||||||
<MobileContent
|
style={{
|
||||||
isLeftMenuOpen={isLeftMenuOpen}
|
width: "100%",
|
||||||
setIsLeftMenuOpen={setIsLeftMenuOpen}
|
height: "100%",
|
||||||
isRightMenuOpen={isRightMenuOpen}
|
background: `linear-gradient(180deg, ${colorGradient})`,
|
||||||
setIsRightMenuOpen={setIsRightMenuOpen}
|
}}
|
||||||
/>
|
>
|
||||||
)}
|
<div className={styles.appWrapper} role="application">
|
||||||
|
{!isDesktop && (
|
||||||
|
<MobileContent
|
||||||
|
isLeftMenuOpen={isLeftMenuOpen}
|
||||||
|
setIsLeftMenuOpen={setIsLeftMenuOpen}
|
||||||
|
isRightMenuOpen={isRightMenuOpen}
|
||||||
|
setIsRightMenuOpen={setIsRightMenuOpen}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{isDesktop && <DesktopContent />}
|
{isDesktop && <DesktopContent />}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,110 @@
|
|||||||
.container {
|
.container {
|
||||||
width: 100%;
|
padding: 40px;
|
||||||
height: 100%;
|
display: grid;
|
||||||
display: flex;
|
grid-template-columns: 25px 1fr 25px;
|
||||||
align-items: center;
|
grid-template-rows: 50px 1fr 25px;
|
||||||
justify-content: center;
|
grid-template-areas:
|
||||||
flex-direction: column;
|
". preview ."
|
||||||
|
"leftGrip square rightGrip"
|
||||||
|
". bottomGrip ."
|
||||||
|
". bar .";
|
||||||
}
|
}
|
||||||
|
|
||||||
.pickerSquare {
|
.pickerSquare {
|
||||||
flex: 1;
|
grid-area: square;
|
||||||
|
position: relative;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
border: 2px solid #7a7a7a;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pickerBar {
|
.pickerBar {
|
||||||
flex: 1;
|
grid-area: bar;
|
||||||
|
position: relative;
|
||||||
|
height: 25px;
|
||||||
|
margin-top: 15px;
|
||||||
|
border: 2px solid #7a7a7a;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Large - Landscape Tablets / Desktops */
|
.verticalGripLeft {
|
||||||
/* Medium - Portrait Tablets */
|
grid-area: leftGrip;
|
||||||
/* Horizontal layout, vertically scrolling picker and palette content */
|
|
||||||
@media (min-width: 992px),
|
|
||||||
(min-width: 568px) and (max-width: 991px) and (orientation: portrait) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Medium - Landscape Phones */
|
.verticalGripRight {
|
||||||
/* Horizontal layout, side menu, vertical tabbed picker */
|
grid-area: rightGrip;
|
||||||
@media (min-width: 568px) and (max-width: 991px) and (orientation: landscape) {
|
|
||||||
.container {
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Small - Portrait Phones*/
|
.horizontalGrip {
|
||||||
/* Vertical layout, side menu, horizontal tabbed picker */
|
grid-area: bottomGrip;
|
||||||
@media (max-width: 567px) {
|
}
|
||||||
|
|
||||||
|
/* 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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container} ref={containerRef}>
|
||||||
<div className={styles.pickerSquare}>Square</div>
|
<div
|
||||||
<div className={styles.pickerBar}>Bar</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,137 @@
|
|||||||
.colorValuesWrapper {
|
.colorValuesWrapper {
|
||||||
width: 100%;
|
padding: 40px;
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { KeyboardEvent } from "react";
|
|||||||
|
|
||||||
import * as colorlib from "colorlib";
|
import * as colorlib from "colorlib";
|
||||||
|
|
||||||
import type { ColorActions } from "@hooks/color";
|
import type { ColorActions } from "@/hooks/color";
|
||||||
|
|
||||||
import styles from "./ColorValues.module.css";
|
import styles from "./ColorValues.module.css";
|
||||||
import SpaceEditor from "./SpaceEditor";
|
import SpaceEditor from "./SpaceEditor";
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useReducer } from "react";
|
|||||||
|
|
||||||
import { Color } from "colorlib";
|
import { Color } from "colorlib";
|
||||||
|
|
||||||
import { colorReducer, createColorActions } from "@hooks/color";
|
import { colorReducer, createColorActions } from "@/hooks/color";
|
||||||
|
|
||||||
import ColorValues from "./ColorValues";
|
import ColorValues from "./ColorValues";
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useReducer } from "react";
|
|||||||
|
|
||||||
import { Color } from "colorlib";
|
import { Color } from "colorlib";
|
||||||
|
|
||||||
import { colorReducer, createColorActions } from "@hooks/color";
|
import { colorReducer, createColorActions } from "@/hooks/color";
|
||||||
|
|
||||||
import { HexEditor } from "./ValueEditor";
|
import { HexEditor } from "./ValueEditor";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
.spaceWrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex: 1;
|
|
||||||
min-height: 0;
|
|
||||||
}
|
|
||||||
@@ -4,9 +4,9 @@ import type {
|
|||||||
HCLColorActions,
|
HCLColorActions,
|
||||||
HSVColorActions,
|
HSVColorActions,
|
||||||
RGBColorActions,
|
RGBColorActions,
|
||||||
} from "@hooks/color";
|
} from "@/hooks/color";
|
||||||
|
|
||||||
import styles from "./SpaceEditor.module.css";
|
import styles from "./ColorValues.module.css";
|
||||||
import { ValueEditor } from "./ValueEditor";
|
import { ValueEditor } from "./ValueEditor";
|
||||||
|
|
||||||
type ColorSpaceProps =
|
type ColorSpaceProps =
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { useReducer } from "react";
|
|||||||
|
|
||||||
import { Color } from "colorlib";
|
import { Color } from "colorlib";
|
||||||
|
|
||||||
|
import { colorReducer, createColorActions } from "@/hooks/color";
|
||||||
import { roundTo } from "@/util";
|
import { roundTo } from "@/util";
|
||||||
import { colorReducer, createColorActions } from "@hooks/color";
|
|
||||||
|
|
||||||
import SpaceEditor from "./SpaceEditor";
|
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;
|
|
||||||
}
|
|
||||||
@@ -10,15 +10,15 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import * as colorlib from "colorlib";
|
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 type { CartesianSpace, Range, Setter, Timeout } from "@/types";
|
||||||
import { Direction } from "@/types";
|
import { Direction } from "@/types";
|
||||||
import { minmax, setMeasurements, valueToPosition } from "@/util";
|
import { minmax, roundTo, 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 styles from "./ValueEditor.module.css";
|
import styles from "./ColorValues.module.css";
|
||||||
|
|
||||||
// ------------ //
|
// ------------ //
|
||||||
// Value Editor //
|
// Value Editor //
|
||||||
@@ -68,7 +68,7 @@ export function ValueEditor({
|
|||||||
setValue((prev) => {
|
setValue((prev) => {
|
||||||
const scaledStep = step / scale;
|
const scaledStep = step / scale;
|
||||||
const newValue = minmax(
|
const newValue = minmax(
|
||||||
Math.floor(prev * scale) / scale + scaledStep,
|
roundTo(Math.floor(roundTo(prev * scale, 6)) / scale + scaledStep, 6),
|
||||||
valueRange.min,
|
valueRange.min,
|
||||||
valueRange.max,
|
valueRange.max,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useReducer } from "react";
|
|||||||
|
|
||||||
import { Color } from "colorlib";
|
import { Color } from "colorlib";
|
||||||
|
|
||||||
import { colorReducer, createColorActions } from "@hooks/color";
|
import { colorReducer, createColorActions } from "@/hooks/color";
|
||||||
|
|
||||||
import { ValueEditor } from "./ValueEditor";
|
import { ValueEditor } from "./ValueEditor";
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -62,7 +62,7 @@ export function colorReducer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Setter = (valOrCallback: SetterValueOrCallback<number>) => void;
|
export type Setter = (valOrCallback: SetterValueOrCallback<number>) => void;
|
||||||
|
|
||||||
export interface CommonColorActions {
|
export interface CommonColorActions {
|
||||||
setColor: (color: colorlib.Color) => void;
|
setColor: (color: colorlib.Color) => void;
|
||||||
|
|||||||
+22
-3
@@ -9,6 +9,8 @@ import {
|
|||||||
positionToValue,
|
positionToValue,
|
||||||
} from "@/util";
|
} from "@/util";
|
||||||
|
|
||||||
|
import { useSmoothAnimation } from "./animation";
|
||||||
|
|
||||||
if (typeof TouchEvent === "undefined") {
|
if (typeof TouchEvent === "undefined") {
|
||||||
// @ts-ignore - intentionally creating global
|
// @ts-ignore - intentionally creating global
|
||||||
window.TouchEvent = window.MouseEvent;
|
window.TouchEvent = window.MouseEvent;
|
||||||
@@ -21,6 +23,8 @@ export function useCrosshair({
|
|||||||
setYValue,
|
setYValue,
|
||||||
xValueRange,
|
xValueRange,
|
||||||
yValueRange,
|
yValueRange,
|
||||||
|
invertX,
|
||||||
|
invertY,
|
||||||
}: {
|
}: {
|
||||||
origin: CartesianSpace;
|
origin: CartesianSpace;
|
||||||
dimensions: CartesianSpace;
|
dimensions: CartesianSpace;
|
||||||
@@ -28,6 +32,8 @@ export function useCrosshair({
|
|||||||
setYValue: Setter<number>;
|
setYValue: Setter<number>;
|
||||||
xValueRange: Range;
|
xValueRange: Range;
|
||||||
yValueRange: Range;
|
yValueRange: Range;
|
||||||
|
invertX?: boolean;
|
||||||
|
invertY?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
const crosshairRef = useRef<HTMLDivElement>(null);
|
const crosshairRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -42,6 +48,9 @@ export function useCrosshair({
|
|||||||
const xValueRangeRef = useRef(xValueRange);
|
const xValueRangeRef = useRef(xValueRange);
|
||||||
const yValueRangeRef = useRef(yValueRange);
|
const yValueRangeRef = useRef(yValueRange);
|
||||||
|
|
||||||
|
// Hooks
|
||||||
|
const smoothAnimation = useSmoothAnimation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
originRef.current = origin;
|
originRef.current = origin;
|
||||||
dimensionsRef.current = dimensions;
|
dimensionsRef.current = dimensions;
|
||||||
@@ -55,13 +64,23 @@ export function useCrosshair({
|
|||||||
const calculatePositions = useCallback((event: MouseEvent | TouchEvent) => {
|
const calculatePositions = useCallback((event: MouseEvent | TouchEvent) => {
|
||||||
const orig = originRef.current;
|
const orig = originRef.current;
|
||||||
const dims = dimensionsRef.current;
|
const dims = dimensionsRef.current;
|
||||||
|
const xRange = xValueRangeRef.current;
|
||||||
|
const yRange = yValueRangeRef.current;
|
||||||
|
|
||||||
const { clientX, clientY } = extractEventCoordinates(event);
|
const { clientX, clientY } = extractEventCoordinates(event);
|
||||||
|
|
||||||
const xPos = minmax(clientX - orig.x, 0, dims.x - 1);
|
const xPos = minmax(clientX - orig.x, 0, dims.x - 1);
|
||||||
const yPos = minmax(clientY - orig.y, 0, dims.y - 1);
|
const yPos = minmax(clientY - orig.y, 0, dims.y - 1);
|
||||||
const newXValue = positionToValue(xPos, dims.x - 1, xValueRangeRef.current);
|
let newXValue = positionToValue(xPos, dims.x - 1, xRange);
|
||||||
const newYValue = positionToValue(yPos, dims.y - 1, yValueRangeRef.current);
|
let newYValue = positionToValue(yPos, dims.y - 1, yRange);
|
||||||
|
|
||||||
|
if (invertX) {
|
||||||
|
newXValue = xRange.max - newXValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invertY) {
|
||||||
|
newYValue = yRange.max - newYValue;
|
||||||
|
}
|
||||||
|
|
||||||
setXValueRef.current(newXValue);
|
setXValueRef.current(newXValue);
|
||||||
setYValueRef.current(newYValue);
|
setYValueRef.current(newYValue);
|
||||||
@@ -70,7 +89,7 @@ export function useCrosshair({
|
|||||||
const handleMove = useCallback(
|
const handleMove = useCallback(
|
||||||
(event: MouseEvent | TouchEvent) => {
|
(event: MouseEvent | TouchEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
calculatePositions(event);
|
smoothAnimation(() => calculatePositions(event));
|
||||||
},
|
},
|
||||||
[calculatePositions],
|
[calculatePositions],
|
||||||
);
|
);
|
||||||
|
|||||||
+18
-2
@@ -12,6 +12,7 @@ import {
|
|||||||
valueToPosition,
|
valueToPosition,
|
||||||
} from "@/util";
|
} from "@/util";
|
||||||
|
|
||||||
|
import { useSmoothAnimation } from "./animation";
|
||||||
import { useScroll } from "./scroll";
|
import { useScroll } from "./scroll";
|
||||||
|
|
||||||
if (typeof TouchEvent === "undefined") {
|
if (typeof TouchEvent === "undefined") {
|
||||||
@@ -34,6 +35,7 @@ export function useSlider({
|
|||||||
valueRange,
|
valueRange,
|
||||||
value,
|
value,
|
||||||
setValue,
|
setValue,
|
||||||
|
invert = false,
|
||||||
}: {
|
}: {
|
||||||
direction: Direction;
|
direction: Direction;
|
||||||
origin: CartesianSpace;
|
origin: CartesianSpace;
|
||||||
@@ -41,6 +43,7 @@ export function useSlider({
|
|||||||
valueRange: Range;
|
valueRange: Range;
|
||||||
value: number;
|
value: number;
|
||||||
setValue: Setter<number>;
|
setValue: Setter<number>;
|
||||||
|
invert?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
const sliderRef = useRef<HTMLDivElement>(null);
|
const sliderRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -59,6 +62,9 @@ export function useSlider({
|
|||||||
const [position, setPosition] = useState(0);
|
const [position, setPosition] = useState(0);
|
||||||
const positionRef = useRef(position);
|
const positionRef = useRef(position);
|
||||||
|
|
||||||
|
// Hooks
|
||||||
|
const smoothAnimation = useSmoothAnimation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
directionRef.current = direction;
|
directionRef.current = direction;
|
||||||
originRef.current = origin;
|
originRef.current = origin;
|
||||||
@@ -68,6 +74,11 @@ export function useSlider({
|
|||||||
dimensions.x,
|
dimensions.x,
|
||||||
dimensions.y,
|
dimensions.y,
|
||||||
);
|
);
|
||||||
|
positionRef.current = valueToPosition(
|
||||||
|
value,
|
||||||
|
maxPosition.current,
|
||||||
|
valueRangeRef.current,
|
||||||
|
);
|
||||||
}, [direction, origin, dimensions]);
|
}, [direction, origin, dimensions]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -94,19 +105,23 @@ export function useSlider({
|
|||||||
0,
|
0,
|
||||||
chooseValueByDirection(dir, dims.x, dims.y),
|
chooseValueByDirection(dir, dims.x, dims.y),
|
||||||
);
|
);
|
||||||
const newValue = positionToValue(
|
let newValue = positionToValue(
|
||||||
newPosition,
|
newPosition,
|
||||||
maxPosition.current,
|
maxPosition.current,
|
||||||
valueRangeRef.current,
|
valueRangeRef.current,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (invert) {
|
||||||
|
newValue = valueRangeRef.current.max - newValue;
|
||||||
|
}
|
||||||
|
|
||||||
setValueRef.current(newValue);
|
setValueRef.current(newValue);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleMove = useCallback(
|
const handleMove = useCallback(
|
||||||
(event: MouseEvent | TouchEvent) => {
|
(event: MouseEvent | TouchEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
calculatePosition(event);
|
smoothAnimation(() => calculatePosition(event));
|
||||||
},
|
},
|
||||||
[calculatePosition],
|
[calculatePosition],
|
||||||
);
|
);
|
||||||
@@ -154,6 +169,7 @@ export function useSlider({
|
|||||||
maxPosition.current,
|
maxPosition.current,
|
||||||
valueRangeRef.current,
|
valueRangeRef.current,
|
||||||
);
|
);
|
||||||
|
|
||||||
setValueRef.current(newValue);
|
setValueRef.current(newValue);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { createContext, useContext, useReducer } from "react";
|
import { createContext, useReducer } from "react";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
|
|
||||||
import * as colorlib from "colorlib";
|
import * as colorlib from "colorlib";
|
||||||
|
|
||||||
import { colorReducer, createColorActions } from "@hooks/color";
|
import { colorReducer, createColorActions } from "@/hooks/color";
|
||||||
import type { ColorActions } from "@hooks/color";
|
import type { ColorActions } from "@/hooks/color";
|
||||||
|
|
||||||
interface SelectedColorContextType {
|
interface SelectedColorContextType {
|
||||||
selectedColor: colorlib.Color;
|
selectedColor: colorlib.Color;
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import type { RefObject } from "react";
|
import type { RefObject } from "react";
|
||||||
|
|
||||||
|
import { Hex } from "colorlib";
|
||||||
|
|
||||||
import type { CartesianSpace, Range } from "./types";
|
import type { CartesianSpace, Range } from "./types";
|
||||||
import { Direction } from "./types";
|
import { Direction } from "./types";
|
||||||
|
|
||||||
@@ -94,3 +96,7 @@ export function roundTo(value: number, decimals: number = 0) {
|
|||||||
const factor = Math.pow(10, decimals);
|
const factor = Math.pow(10, decimals);
|
||||||
return Math.round(value * factor) / factor;
|
return Math.round(value * factor) / factor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function formatCssRgb(hex: Hex) {
|
||||||
|
return `rgb(${hex.r},${hex.g},${hex.b})`;
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,9 +26,6 @@
|
|||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"],
|
"@/*": ["./src/*"],
|
||||||
"@components/*": ["./src/components/*"],
|
|
||||||
"@hooks/*": ["./src/hooks/*"],
|
|
||||||
"@providers/*": ["./src/providers/*"]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src", "cypress"]
|
"include": ["src", "cypress"]
|
||||||
|
|||||||
@@ -11,9 +11,6 @@ export default defineConfig({
|
|||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
"@": path.resolve(__dirname, "./src"),
|
"@": path.resolve(__dirname, "./src"),
|
||||||
"@components": path.resolve(__dirname, "./src/components"),
|
|
||||||
"@hooks": path.resolve(__dirname, "./src/hooks"),
|
|
||||||
"@providers": path.resolve(__dirname, "./src/providers"),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
|
|||||||
Reference in New Issue
Block a user