Farbfelder: Hex-Code separat editierbar, Swatch öffnet die Palette

ColorHexField (neu, geteilt) trennt die zwei Klickziele: Swatch/
natives Farb-Input öffnet weiter den Picker, der Hex-Text daneben ist
jetzt ein eigenes Eingabefeld (Enter übernimmt, Esc verwirft, ungültige
Eingabe fällt auf den letzten gültigen Wert zurück). Ersetzt die
bisherige reine Text-Anzeige in SettingsDialog + AttributesPanel.
This commit is contained in:
2026-07-04 05:15:42 +02:00
parent d9937e5afa
commit 3c3746d529
4 changed files with 97 additions and 22 deletions
+64
View File
@@ -0,0 +1,64 @@
// Farbfeld: Swatch (öffnet die native Farbpalette) + separat editierbarer
// Hex-Code (Tippen/Enter übernimmt, Esc verwirft). Ersetzt die frühere reine
// Text-Anzeige des Hex-Werts, die nicht anklickbar/editierbar war.
import { useEffect, useState } from "react";
export function ColorHexField({
value,
onChange,
disabled,
}: {
value: string;
onChange: (hex: string) => void;
disabled?: boolean;
}) {
const [text, setText] = useState(value.toUpperCase());
// Extern geänderter Wert (z. B. Preset-Klick) synchron halten, solange das
// Feld nicht gerade selbst bearbeitet wird (kein Cursor-Sprung beim Tippen).
useEffect(() => setText(value.toUpperCase()), [value]);
const commit = (raw: string) => {
const trimmed = raw.trim();
const withHash = trimmed.startsWith("#") ? trimmed : `#${trimmed}`;
if (/^#[0-9a-fA-F]{6}$/.test(withHash)) {
onChange(withHash.toLowerCase());
setText(withHash.toUpperCase());
} else {
setText(value.toUpperCase()); // ungültig → letzten gültigen Wert zeigen
}
};
return (
<span className="attr-color">
<label className="attr-color-swatch-wrap" title="Farbe wählen">
<span className="attr-color-swatch" style={{ background: value }} />
<input
type="color"
value={value}
disabled={disabled}
onChange={(e) => onChange(e.target.value)}
/>
</label>
<input
type="text"
className="attr-color-hex attr-color-hex-input"
value={text}
disabled={disabled}
spellCheck={false}
onChange={(e) => setText(e.target.value)}
onBlur={(e) => commit(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
commit(e.currentTarget.value);
e.currentTarget.blur();
} else if (e.key === "Escape") {
setText(value.toUpperCase());
e.currentTarget.blur();
}
}}
/>
</span>
);
}
+2 -5
View File
@@ -13,6 +13,7 @@ import { useEffect, useState } from "react";
import { t } from "../i18n";
import { ACCENT_SWATCHES } from "../theme/accents";
import type { Project } from "../model/types";
import { ColorHexField } from "./ColorHexField";
export interface SettingsDialogProps {
marqueeColor: string;
@@ -145,11 +146,7 @@ function AccentColorField({
<div className="settings-color-field">
<div className="settings-color-head">
<span className="settings-field-label">{label}</span>
<label className="attr-color">
<span className="attr-color-swatch" style={{ background: value }} />
<input type="color" value={value} onChange={(e) => onChange(e.target.value)} />
<span className="attr-color-hex">{value.toUpperCase()}</span>
</label>
<ColorHexField value={value} onChange={onChange} />
</div>
<div className="settings-accent-presets" role="group" aria-label={label}>
{ACCENT_SWATCHES.map((a) => (