diff --git a/src/components/ColorComponentInput.module.css b/src/components/ColorComponentInput.module.css new file mode 100644 index 0000000..1ed827f --- /dev/null +++ b/src/components/ColorComponentInput.module.css @@ -0,0 +1,21 @@ +@layer components { + .rgb_input { + display: flex; + flex-direction: column; + align-items: stretch; + gap: var(--spacing-m); + .rgb_input { + text-align: right; + text-transform: uppercase; + } + .component_row { + display: flex; + flex-direction: row; + align-items: center; + gap: var(--spacing-xs); + .component_input { + flex: 1 0; + } + } + } +} diff --git a/src/components/ColorComponentInput.tsx b/src/components/ColorComponentInput.tsx new file mode 100644 index 0000000..7b196de --- /dev/null +++ b/src/components/ColorComponentInput.tsx @@ -0,0 +1,234 @@ +import { Icon } from '@iconify/react/dist/iconify.js'; +import cx from 'clsx'; +import { clamp, divide, floor, times, trim } from 'lodash-es'; +import { ChangeEvent, useEffect, useState } from 'react'; +import { useColorFunction } from '../ColorFunctionContext'; +import styles from './ColorComponentInput.module.css'; + +type ColorComponentInputProps = { + color: string | null; + onChange: (color: string) => void; +}; + +export function ColorComponentInput({ color, onChange }: ColorComponentInputProps) { + const { colorFn } = useColorFunction(); + const [hex, setHex] = useState(color ?? '000000'); + const [r, setR] = useState(0); + const [g, setG] = useState(0); + const [b, setB] = useState(0); + const [h, setH] = useState(0); + const [s, setS] = useState(0); + const [l, setL] = useState(0); + const updateHex = (evt: ChangeEvent) => { + try { + const hexValue = trim(evt.target.value, '#'); + const [r, g, b] = colorFn?.represent_rgb(hexValue) ?? [0, 0, 0]; + setR(r); + setG(g); + setB(b); + const [h, s, l] = colorFn?.represent_hsl(hexValue) ?? [0, 0, 0]; + setH(h); + setS(s); + setL(l); + setHex(hexValue); + onChange(hexValue); + } catch (e) { + console.error('[Convert HEX]', e); + } + }; + const updateRGB = (colorValue: string) => { + try { + const [r, g, b] = colorFn?.represent_rgb(colorValue) ?? [0, 0, 0]; + setR(r); + setG(g); + setB(b); + } catch (e) { + console.error('[Convert RGB]', e); + } + }; + const updateHSL = (colorValue: string) => { + try { + const [h, s, l] = colorFn?.represent_hsl(colorValue) ?? [0, 0, 0]; + setH(floor(h, 1)); + setS(floor(times(s, 100), 2)); + setL(floor(times(l, 100), 2)); + } catch (e) { + console.error('[Convert HSL]', e); + } + }; + const updateR = (evt: ChangeEvent) => { + const value = clamp(parseInt(evt.target.value, 10), 0, 255); + try { + const cHex = colorFn?.rgb_to_hex(value, g, b); + setR(value); + setHex(cHex); + updateHSL(cHex); + onChange(cHex); + } catch (e) { + console.error('[Update R]', e, value); + } + }; + const updateG = (evt: ChangeEvent) => { + const value = clamp(parseInt(evt.target.value, 10), 0, 255); + try { + const cHex = colorFn?.rgb_to_hex(r, value, b); + setG(value); + setHex(cHex); + updateHSL(cHex); + onChange(cHex); + } catch (e) { + console.error('[Update G]', e, value); + } + }; + const updateB = (evt: ChangeEvent) => { + const value = clamp(parseInt(evt.target.value, 10), 0, 255); + try { + const cHex = colorFn?.rgb_to_hex(r, g, value); + setB(value); + setHex(cHex); + updateHSL(cHex); + onChange(cHex); + } catch (e) { + console.error('[Update B]', e, value); + } + }; + const updateH = (evt: ChangeEvent) => { + const value = parseInt(evt.target.value, 10); + if (value > 360) { + value -= 360; + } + if (value < 0) { + value += 360; + } + try { + const cHex = colorFn.hsl_to_hex(value, divide(s, 100), divide(l, 100)); + setH(value); + setHex(cHex); + updateRGB(cHex); + onChange(cHex); + } catch (e) { + console.error('[Update Hue]', e, value); + } + }; + const updateS = (evt: ChangeEvent) => { + const value = clamp(parseInt(evt.target.value, 10), 0, 100); + try { + const cHex = colorFn.hsl_to_hex(h, divide(value, 100), divide(l, 100)); + setS(value); + setHex(cHex); + updateRGB(cHex); + onChange(cHex); + } catch (e) { + console.error('[Update Saturation]', e, value); + } + }; + const updateL = (evt: ChangeEvent) => { + const value = clamp(parseInt(evt.target.value, 10), 0, 100); + try { + const cHex = colorFn.hsl_to_hex(h, divide(s, 100), divide(value, 100)); + setL(value); + setHex(cHex); + updateRGB(cHex); + onChange(cHex); + } catch (e) { + console.error('[Update Lightness]', e, value); + } + }; + + useEffect(() => { + try { + const [r, g, b] = colorFn?.represent_rgb(color) ?? [0, 0, 0]; + setR(r); + setG(g); + setB(b); + const [h, s, l] = colorFn?.represent_hsl(color) ?? [0, 0, 0]; + setH(floor(h, 1)); + setS(floor(times(s, 100), 2)); + setL(floor(times(l, 100), 2)); + } catch (e) { + console.error('[Convert RGB]', e); + } + }, []); + + return ( +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + + ° +
+
+ + + +
+
+ + + +
+
+
+ ); +}