Загрузка данных
import { InputText } from 'exchange-elements/v2';
import { ChangeEvent, useEffect, useRef, useState } from 'react';
import styles from './index.module.scss';
interface ColorFieldProps {
label: string;
value: string;
onValueChange: (value: string) => void;
}
function normalizeHex(value: string): string {
const next = value.trim().startsWith('#') ? value.trim().toLowerCase() : `#${value.trim().toLowerCase()}`;
if (/^#[0-9a-f]{6}$/.test(next)) {
return `${next}ff`;
}
if (/^#[0-9a-f]{8}$/.test(next)) {
return next;
}
return '#000000ff';
}
function isValidHex(value: string): boolean {
return /^#([0-9a-f]{6}|[0-9a-f]{8})$/i.test(value) || /^([0-9a-f]{6}|[0-9a-f]{8})$/i.test(value);
}
export const ColorField = ({ label, value, onValueChange }: ColorFieldProps) => {
const colorInputRef = useRef<HTMLInputElement | null>(null);
const [draft, setDraft] = useState(normalizeHex(value));
useEffect(() => {
setDraft(normalizeHex(value));
}, [value]);
const current = isValidHex(draft) ? normalizeHex(draft) : normalizeHex(value);
const opacity = Math.round((parseInt(current.slice(7, 9), 16) / 255) * 100);
const handleTextChange = (event: ChangeEvent<HTMLInputElement>) => {
setDraft(event.target.value);
};
const handleTextBlur = () => {
if (!isValidHex(draft)) {
setDraft(normalizeHex(value));
return;
}
const next = normalizeHex(draft);
setDraft(next);
onValueChange(next);
};
const handleColorChange = (event: ChangeEvent<HTMLInputElement>) => {
const next = `${event.target.value}${current.slice(7, 9)}`;
setDraft(next);
onValueChange(next);
};
const handleOpacityChange = (event: ChangeEvent<HTMLInputElement>) => {
const alpha = Math.round((Number(event.target.value) / 100) * 255)
.toString(16)
.padStart(2, '0');
const next = `${current.slice(0, 7)}${alpha}`;
setDraft(next);
onValueChange(next);
};
return (
<div className={styles.inputWrapper}>
<InputText
value={draft}
label={label}
labelPos="top"
size="sm"
onChange={handleTextChange}
onBlur={handleTextBlur}
/>
<button
type="button"
className={styles.button_color}
style={{ backgroundColor: current }}
onClick={() => colorInputRef.current?.click()}
/>
<input
ref={colorInputRef}
type="color"
className={styles.input_hidden}
value={current.slice(0, 7)}
onChange={handleColorChange}
tabIndex={-1}
/>
<div className={styles.opacity}>
<input
type="range"
min="0"
max="100"
value={opacity}
className={styles.opacity_range}
onChange={handleOpacityChange}
/>
<span className={styles.opacity_value}>{opacity}%</span>
</div>
</div>
);
};
.inputWrapper {
position: relative;
&:focus-within {
background-color: transparent !important;
}
div {
background-color: transparent;
&:focus-within {
background-color: transparent !important;
}
.input {
background-color: transparent;
}
}
}
.button_color {
position: absolute;
bottom: calc(var(--space-0375) + 40px);
right: var(--space-0500);
width: var(--space-1250);
height: var(--space-1250);
border: 1px solid var(--neutral-8);
border-radius: var(--space-0125);
cursor: pointer;
}
.input_hidden {
position: absolute;
bottom: calc(var(--space-0375) + 40px);
right: var(--space-0500);
width: var(--space-1250);
height: var(--space-1250);
opacity: 0;
}
.opacity {
display: flex;
align-items: center;
gap: var(--space-0500);
margin-top: var(--space-0500);
}
.opacity_range {
flex: 1;
}
.opacity_value {
min-width: 40px;
text-align: right;
color: var(--neutral-13);
font-size: 12px;
}