feat: align Beam settings controls
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
const isHexColor = (value) => /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(String(value || "").trim());
|
||||
|
||||
export function createColorControl({
|
||||
value = "#ffffff",
|
||||
previewValue = value,
|
||||
ariaLabel = "Выбрать цвет",
|
||||
valueLabel = "Значение цвета",
|
||||
onChange,
|
||||
}) {
|
||||
const root = document.createElement("div");
|
||||
root.className = "env-color-field";
|
||||
|
||||
const swatch = document.createElement("label");
|
||||
swatch.className = "env-color-field__swatch";
|
||||
|
||||
const preview = document.createElement("span");
|
||||
preview.className = "env-color-field__preview";
|
||||
|
||||
const picker = document.createElement("input");
|
||||
picker.type = "color";
|
||||
picker.setAttribute("aria-label", ariaLabel);
|
||||
|
||||
const valueWrap = document.createElement("div");
|
||||
valueWrap.className = "env-color-field__value";
|
||||
|
||||
const valueInput = document.createElement("input");
|
||||
valueInput.type = "text";
|
||||
valueInput.setAttribute("aria-label", valueLabel);
|
||||
valueWrap.appendChild(valueInput);
|
||||
|
||||
const update = (nextValue, { emit = false } = {}) => {
|
||||
const safeValue = String(nextValue ?? "");
|
||||
const safePreview = isHexColor(safeValue)
|
||||
? safeValue
|
||||
: (isHexColor(previewValue) ? previewValue : "#000000");
|
||||
|
||||
valueInput.value = safeValue;
|
||||
picker.value = isHexColor(safeValue) ? safeValue : safePreview;
|
||||
preview.style.background = safePreview;
|
||||
|
||||
if (emit && typeof onChange === "function") {
|
||||
onChange(safeValue);
|
||||
}
|
||||
};
|
||||
|
||||
picker.addEventListener("input", () => update(picker.value, { emit: true }));
|
||||
valueInput.addEventListener("change", () => update(valueInput.value, { emit: true }));
|
||||
|
||||
swatch.appendChild(preview);
|
||||
swatch.appendChild(picker);
|
||||
root.appendChild(swatch);
|
||||
root.appendChild(valueWrap);
|
||||
|
||||
update(value);
|
||||
|
||||
root.setValue = (nextValue) => update(nextValue);
|
||||
root.getValue = () => valueInput.value;
|
||||
|
||||
return root;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createSliderControl } from "./controls/slider.js";
|
||||
import { createColorControl } from "./controls/color.js";
|
||||
|
||||
const defaultSettings = {
|
||||
themeVersion: "v4-beam-lime",
|
||||
@@ -186,7 +187,12 @@ export function createSettingsMenu({ panel, controls = {}, onChange = null, onSh
|
||||
header.innerHTML = `<span class="settings-title">Настройки</span>`;
|
||||
const closeBtn = document.createElement("button");
|
||||
closeBtn.className = "settings-close";
|
||||
closeBtn.textContent = "—";
|
||||
closeBtn.setAttribute("aria-label", "Закрыть настройки");
|
||||
closeBtn.innerHTML = `
|
||||
<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true">
|
||||
<path d="M4.25 4.25 11.75 11.75M11.75 4.25 4.25 11.75" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path>
|
||||
</svg>
|
||||
`;
|
||||
closeBtn.addEventListener("click", hide);
|
||||
header.appendChild(closeBtn);
|
||||
panel.appendChild(header);
|
||||
@@ -202,31 +208,53 @@ export function createSettingsMenu({ panel, controls = {}, onChange = null, onSh
|
||||
};
|
||||
|
||||
const addColor = (parent, label, key) => {
|
||||
const isHex = (val) => /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test((val || "").trim());
|
||||
const row = document.createElement("div");
|
||||
row.className = "color-row";
|
||||
const lbl = document.createElement("label");
|
||||
const lbl = document.createElement("div");
|
||||
lbl.textContent = label;
|
||||
const input = document.createElement("input");
|
||||
input.type = "color";
|
||||
input.value = isHex(state[key]) ? state[key] : "#ffffff";
|
||||
const hex = document.createElement("input");
|
||||
hex.type = "text";
|
||||
hex.value = state[key];
|
||||
input.addEventListener("change", (e) => {
|
||||
hex.value = e.target.value;
|
||||
setVal(key, e.target.value);
|
||||
});
|
||||
hex.addEventListener("change", (e) => {
|
||||
input.value = e.target.value;
|
||||
setVal(key, e.target.value);
|
||||
const control = createColorControl({
|
||||
value: state[key],
|
||||
onChange: (nextValue) => {
|
||||
setVal(key, nextValue);
|
||||
},
|
||||
});
|
||||
row.appendChild(lbl);
|
||||
row.appendChild(input);
|
||||
row.appendChild(hex);
|
||||
row.appendChild(control);
|
||||
parent.appendChild(row);
|
||||
};
|
||||
|
||||
const addSettingsSlider = ({
|
||||
parent,
|
||||
label,
|
||||
key,
|
||||
value,
|
||||
min = 0,
|
||||
max = 100,
|
||||
step = 1,
|
||||
formatValue = (nextValue) => String(nextValue),
|
||||
onChange,
|
||||
}) => {
|
||||
const slider = createSliderControl({
|
||||
label,
|
||||
value,
|
||||
min,
|
||||
max,
|
||||
step,
|
||||
className: "env-range-control",
|
||||
showValue: true,
|
||||
formatValue,
|
||||
onChange: (nextValue) => {
|
||||
if (typeof onChange === "function") {
|
||||
onChange(nextValue);
|
||||
return;
|
||||
}
|
||||
setVal(key, nextValue);
|
||||
},
|
||||
});
|
||||
parent.appendChild(slider);
|
||||
return slider;
|
||||
};
|
||||
|
||||
const font = section("Фон");
|
||||
addColor(font, "Фон внешний", "bgOuter");
|
||||
addColor(font, "Фон холста верх", "canvasTop");
|
||||
@@ -235,65 +263,59 @@ export function createSettingsMenu({ panel, controls = {}, onChange = null, onSh
|
||||
addColor(font, "Фон загрузки низ", "loadingBottom");
|
||||
panel.appendChild(font);
|
||||
|
||||
const buttons = section("Кнопки");
|
||||
addColor(buttons, "Плюс", "plus");
|
||||
addColor(buttons, "Кнопки (заливка)", "templateFill");
|
||||
addColor(buttons, "Кнопки (бордер)", "templateOutline");
|
||||
addColor(buttons, "Кнопки (outline hover)", "templateOutlineHover");
|
||||
const brightnessSlider = createSliderControl({
|
||||
label: "Яркость подсветки",
|
||||
value: Number(state.templateGlow ?? 80),
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
onChange: (val) => setVal("templateGlow", val),
|
||||
});
|
||||
buttons.appendChild(brightnessSlider);
|
||||
panel.appendChild(buttons);
|
||||
const buttons = section("Кнопки");
|
||||
addColor(buttons, "Плюс", "plus");
|
||||
addColor(buttons, "Кнопки (заливка)", "templateFill");
|
||||
addColor(buttons, "Кнопки (бордер)", "templateOutline");
|
||||
addColor(buttons, "Кнопки (outline hover)", "templateOutlineHover");
|
||||
addSettingsSlider({
|
||||
parent: buttons,
|
||||
label: "Яркость подсветки",
|
||||
key: "templateGlow",
|
||||
value: Number(state.templateGlow ?? 80),
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
formatValue: (val) => `${Math.round(val)}%`,
|
||||
});
|
||||
panel.appendChild(buttons);
|
||||
|
||||
const projectButtons = section("Кнопки проектов");
|
||||
addColor(projectButtons, "Проекты (заливка)", "projectBtnFill");
|
||||
addColor(projectButtons, "Проекты (бордер)", "projectBtnBorder");
|
||||
addColor(projectButtons, "Проекты (hover)", "projectBtnHover");
|
||||
panel.appendChild(projectButtons);
|
||||
const projectButtons = section("Кнопки проектов");
|
||||
addColor(projectButtons, "Проекты (заливка)", "projectBtnFill");
|
||||
addColor(projectButtons, "Проекты (бордер)", "projectBtnBorder");
|
||||
addColor(projectButtons, "Проекты (hover)", "projectBtnHover");
|
||||
panel.appendChild(projectButtons);
|
||||
|
||||
const modalButtons = section("Кнопки модалок");
|
||||
addColor(modalButtons, "Модальная primary", "modalBtnPrimary");
|
||||
addColor(modalButtons, "Модальная secondary", "modalBtnSecondary");
|
||||
addColor(modalButtons, "Лоудер", "loaderColor");
|
||||
panel.appendChild(modalButtons);
|
||||
const modalButtons = section("Кнопки модалок");
|
||||
addColor(modalButtons, "Модальная primary", "modalBtnPrimary");
|
||||
addColor(modalButtons, "Модальная secondary", "modalBtnSecondary");
|
||||
addColor(modalButtons, "Лоудер", "loaderColor");
|
||||
panel.appendChild(modalButtons);
|
||||
|
||||
const toolbar = section("Toolbar");
|
||||
addColor(toolbar, "Фон", "toolbarBg");
|
||||
addColor(toolbar, "Актив", "toolbarBgActive");
|
||||
addColor(toolbar, "Бордер", "toolbarBorder");
|
||||
addColor(toolbar, "Outline", "toolbarOutline");
|
||||
panel.appendChild(toolbar);
|
||||
const toolbar = section("Toolbar");
|
||||
addColor(toolbar, "Фон", "toolbarBg");
|
||||
addColor(toolbar, "Актив", "toolbarBgActive");
|
||||
addColor(toolbar, "Бордер", "toolbarBorder");
|
||||
addColor(toolbar, "Outline", "toolbarOutline");
|
||||
panel.appendChild(toolbar);
|
||||
|
||||
const navCube = section("NavCube");
|
||||
addColor(navCube, "Грани", "navCubeBase");
|
||||
addColor(navCube, "Hover", "navCubeHover");
|
||||
addColor(navCube, "Текст", "navCubeText");
|
||||
addColor(navCube, "Рёбра", "navCubeEdge");
|
||||
const edgeAlphaRow = document.createElement("div");
|
||||
edgeAlphaRow.className = "color-row";
|
||||
const edgeAlphaLbl = document.createElement("label");
|
||||
edgeAlphaLbl.textContent = "Прозрачность рёбер (0-1)";
|
||||
const edgeAlphaInput = document.createElement("input");
|
||||
edgeAlphaInput.type = "number";
|
||||
edgeAlphaInput.step = "0.05";
|
||||
edgeAlphaInput.min = "0";
|
||||
edgeAlphaInput.max = "1";
|
||||
edgeAlphaInput.value = state.navCubeEdgeAlpha;
|
||||
edgeAlphaInput.addEventListener("change", () => {
|
||||
const v = Math.min(1, Math.max(0, parseFloat(edgeAlphaInput.value) || 0));
|
||||
edgeAlphaInput.value = v;
|
||||
setVal("navCubeEdgeAlpha", v);
|
||||
addSettingsSlider({
|
||||
parent: navCube,
|
||||
label: "Прозрачность рёбер",
|
||||
value: Math.round(Math.max(0, Math.min(1, Number(state.navCubeEdgeAlpha ?? 0.6))) * 100),
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 5,
|
||||
formatValue: (val) => `${Math.round(val)}%`,
|
||||
onChange: (val) => {
|
||||
setVal("navCubeEdgeAlpha", Math.max(0, Math.min(1, Number(val) / 100)));
|
||||
},
|
||||
});
|
||||
edgeAlphaRow.appendChild(edgeAlphaLbl);
|
||||
edgeAlphaRow.appendChild(edgeAlphaInput);
|
||||
edgeAlphaRow.appendChild(document.createElement("div"));
|
||||
navCube.appendChild(edgeAlphaRow);
|
||||
panel.appendChild(navCube);
|
||||
|
||||
const measure = section("Линейка");
|
||||
@@ -325,33 +347,35 @@ export function createSettingsMenu({ panel, controls = {}, onChange = null, onSh
|
||||
const modals = section("Модальные окна");
|
||||
addColor(modals, "Основной фон", "modalBg");
|
||||
addColor(modals, "Фокусный фон", "modalFocus");
|
||||
const modalBgAlphaRow = createSliderControl({
|
||||
addSettingsSlider({
|
||||
parent: modals,
|
||||
label: "Прозрачность основного фона",
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.05,
|
||||
value: parseFloat(state.modalBgAlpha ?? 1),
|
||||
formatValue: (val) => `${Math.round(Number(val) * 100)}%`,
|
||||
onChange: (val) => {
|
||||
const num = Math.max(0, Math.min(1, parseFloat(val)));
|
||||
setVal("modalBgAlpha", num);
|
||||
document.documentElement.style.setProperty("--modal-bg-alpha", `${num * 100}%`);
|
||||
},
|
||||
});
|
||||
modals.appendChild(modalBgAlphaRow);
|
||||
|
||||
const modalFocusAlphaRow = createSliderControl({
|
||||
addSettingsSlider({
|
||||
parent: modals,
|
||||
label: "Прозрачность фокусного фона",
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.05,
|
||||
value: parseFloat(state.modalFocusAlpha ?? 1),
|
||||
formatValue: (val) => `${Math.round(Number(val) * 100)}%`,
|
||||
onChange: (val) => {
|
||||
const num = Math.max(0, Math.min(1, parseFloat(val)));
|
||||
setVal("modalFocusAlpha", num);
|
||||
document.documentElement.style.setProperty("--modal-focus-alpha", `${num * 100}%`);
|
||||
},
|
||||
});
|
||||
modals.appendChild(modalFocusAlphaRow);
|
||||
|
||||
addColor(modals, "Аутлайн", "modalBorder");
|
||||
addColor(modals, "Фон элемента", "modalElement");
|
||||
|
||||
Reference in New Issue
Block a user