Загрузка данных
import { ChangeEvent, useId } from 'react';
import styles from './index.module.scss';
interface RangeFieldProps {
label?: string;
value: number;
min?: number;
max?: number;
step?: number;
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
}
export const RangeField = ({ label, value, min = 0, max = 100, step = 1, onChange }: RangeFieldProps) => {
const inputId = useId();
return (
<div className={styles.range}>
{label && (
<label
htmlFor={inputId}
className={styles.range_label}
>
{label}
</label>
)}
<div className={styles.range_wrapper}>
<input
id={inputId}
type="range"
min={min}
max={max}
step={step}
value={value}
className={styles.range_input}
onChange={onChange}
/>
<span className={styles.range_value}>{value}%</span>
</div>
</div>
);
};
import { InputText } from 'exchange-elements/v2';
import { ChangeEvent, useEffect, useRef, useState } from 'react';
import styles from './index.module.scss';
import { RangeField } from './RangeField';
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}
/>
<RangeField
value={opacity}
onChange={handleOpacityChange}
/>
</div>
);
};
[data-theme='mxt'],
[data-theme='mb'][data-mode='light'] {
--dd-item-text-selected: var(--neutral-2);
--dd-item-bg-selected: var(--neutral-12);
}
[data-theme='tr'],
[data-theme='mb'][data-mode='dark'] {
--dd-item-text-selected: var(--neutral-13);
--dd-item-bg-selected: var(--neutral-5);
}
$button-color-top-margin: 29px;
.input {
color: var(--neutral-12);
input {
color: var(--neutral-12);
}
}
.dropdown {
li {
&[aria-selected='true'] {
background-color: var(--dd-item-bg-selected);
div[aria-label='Label'] {
color: var(--dd-item-text-selected);
}
}
svg {
color: transparent;
}
}
}
.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;
top: $button-color-top-margin;
right: var(--space-0500);
width: var(--space-1375);
height: var(--space-1375);
border-radius: var(--space-0125);
}
.input_hidden {
position: absolute;
top: $button-color-top-margin;
right: var(--space-0500);
width: var(--space-1375);
height: var(--space-1375);
opacity: 0;
}
.range {
display: flex;
flex-direction: column;
gap: var(--space-0250);
&_label {
font-size: var(--space-0875);
color: var(--neutral-11);
}
&_wrapper {
display: flex;
align-items: center;
gap: var(--space-0500);
}
&_value {
min-width: var(--space-2000);
color: var(--neutral-11);
font-size: var(--space-0750);
text-align: right;
}
&_input {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: var(--space-0250);
background: transparent;
cursor: pointer;
outline: none;
&::-webkit-slider-runnable-track {
height: var(--space-0250);
border: none;
border-radius: 999px;
background: var(--blue-1);
}
&::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: var(--space-1000);
height: var(--space-1000);
margin-top: calc(var(--space-0375) * -1);
border: none;
border-radius: 50%;
background: var(--blue-1);
}
&::-moz-range-track {
height: var(--space-0250);
border: none;
border-radius: 999px;
background: var(--blue-1);
}
&::-moz-range-thumb {
width: var(--space-1000);
height: var(--space-1000);
border: none;
border-radius: 50%;
background: var(--blue-1);
}
}
}