From 3c3746d529aba2215cce966296c8aef9b48f8945 Mon Sep 17 00:00:00 2001 From: Karim Date: Sat, 4 Jul 2026 05:15:42 +0200 Subject: [PATCH] =?UTF-8?q?Farbfelder:=20Hex-Code=20separat=20editierbar,?= =?UTF-8?q?=20Swatch=20=C3=B6ffnet=20die=20Palette?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src/panels/AttributesPanel.tsx | 20 ++--------- src/styles.css | 28 +++++++++++++++ src/ui/ColorHexField.tsx | 64 ++++++++++++++++++++++++++++++++++ src/ui/SettingsDialog.tsx | 7 ++-- 4 files changed, 97 insertions(+), 22 deletions(-) create mode 100644 src/ui/ColorHexField.tsx diff --git a/src/panels/AttributesPanel.tsx b/src/panels/AttributesPanel.tsx index ff71513..97d9867 100644 --- a/src/panels/AttributesPanel.tsx +++ b/src/panels/AttributesPanel.tsx @@ -29,6 +29,7 @@ import { PEN_WEIGHTS } from "../model/types"; import type { AttributeSource } from "../model/types"; import { usePanelHost } from "./host"; import { Dropdown } from "../ui/Dropdown"; +import { ColorHexField } from "../ui/ColorHexField"; /** UI-Zustand des 3-Optionen-Quellen-Dropdowns (nicht 1:1 das Modell-Feld: * „custom" ist abgeleitet aus „ist ein expliziter Wert gesetzt?", nicht @@ -95,11 +96,7 @@ export function AttributesPanel() { // nur, wenn die Quelle „custom" ist — sonst zeigt allein der Dropdown den // Zustand. const colorValue = (value: string | undefined, onSet: (color: string) => void) => ( - + ); // Eine Attribut-Zeile: Label + Quellen-Dropdown + (nur bei „eigener Wert") @@ -170,18 +167,7 @@ export function AttributesPanel() { {/* Farbe — gilt für Wand UND Drawing2D (Kontrakt setzt beide). */} {t("attr.color")} - + {/* Strichstärke (mm) — Wand/Decke/Drawing2D, mit Nach-Ebene/Nach-Bauteil/ diff --git a/src/styles.css b/src/styles.css index 62891c1..bb10fbe 100644 --- a/src/styles.css +++ b/src/styles.css @@ -4018,6 +4018,14 @@ body { display: inline-flex; align-items: center; gap: 6px; +} +/* Swatch + natives Farb-Input — EIGENER Klick-Bereich (öffnet die Palette). + Der Hex-Text daneben ist jetzt ein separates (ColorHexField), + damit er unabhängig editierbar ist statt nur die Palette zu öffnen. */ +.attr-color-swatch-wrap { + display: inline-flex; + align-items: center; + gap: 4px; cursor: pointer; } .attr-color-swatch { @@ -4041,6 +4049,26 @@ body { color: var(--muted); font-size: 11px; } +/* Editierbarer Hex-Code: sieht wie reiner Text aus, bis man klickt/fokussiert + — dann Rahmen + helle Schrift als Editier-Signal. */ +.attr-color-hex-input { + width: 6.5ch; + border: 1px solid transparent; + background: transparent; + border-radius: 3px; + padding: 1px 3px; + font-family: var(--font-mono); + cursor: text; +} +.attr-color-hex-input:hover:not(:disabled) { + border-color: var(--border); +} +.attr-color-hex-input:focus { + outline: none; + border-color: var(--accent); + background: var(--input); + color: var(--ink); +} .attr-color input[type="color"]:disabled { opacity: 0.4; cursor: not-allowed; diff --git a/src/ui/ColorHexField.tsx b/src/ui/ColorHexField.tsx new file mode 100644 index 0000000..df90c6b --- /dev/null +++ b/src/ui/ColorHexField.tsx @@ -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 ( + + + 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(); + } + }} + /> + + ); +} diff --git a/src/ui/SettingsDialog.tsx b/src/ui/SettingsDialog.tsx index 121c1e0..d9ff785 100644 --- a/src/ui/SettingsDialog.tsx +++ b/src/ui/SettingsDialog.tsx @@ -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({
{label} - +
{ACCENT_SWATCHES.map((a) => (