feat(settings): Akzentfarbe zusätzlich als Hex-Code eingebbar

- normalizeHexColor() (apps/web/src/lib/color.ts) normalisiert Hex-Eingaben (fuehrendes # optional, 3-stellige Kurzform, Gross-/Kleinschreibung, Leerraum)
- Kontoformular: Hex-Textfeld neben dem Farbwaehler, beide bidirektional synchron, ersetzt den reinen Anzeige-Span
- Ungueltiger Text: aria-invalid + roter Rand + Fehlertext, "Farbe speichern" gesperrt; Zuruecksetzen setzt beides auf #ffed00
- i18n: settings.account.accentColorHex/accentColorHexInvalid in de.json und en.json
- Unit-Tests: color.test.ts (9 Faelle), account-settings-form.test.tsx (7 Faelle)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-17 12:14:43 +02:00
parent f6eda20fcb
commit 795c6a492c
6 changed files with 341 additions and 5 deletions
+44
View File
@@ -0,0 +1,44 @@
import { describe, expect, it } from 'vitest';
import { normalizeHexColor } from './color';
/**
* color.test — Normalisierung der Hex-Eingabe der Akzentfarbe
* (quick-260917-gsh).
*/
describe('normalizeHexColor', () => {
it('accepts a canonical six-digit hex value with leading #', () => {
expect(normalizeHexColor('#ffed00')).toBe('#ffed00');
});
it('accepts a six-digit value without # and uppercase letters', () => {
expect(normalizeHexColor('FFED00')).toBe('#ffed00');
});
it('expands a three-digit shorthand', () => {
expect(normalizeHexColor('#fe0')).toBe('#ffee00');
});
it('trims surrounding whitespace and lowercases mixed case', () => {
expect(normalizeHexColor(' #FfEd00 ')).toBe('#ffed00');
});
it('rejects non-hex characters', () => {
expect(normalizeHexColor('#ggg')).toBeNull();
});
it('rejects the wrong digit count (5)', () => {
expect(normalizeHexColor('#12345')).toBeNull();
});
it('rejects an empty string', () => {
expect(normalizeHexColor('')).toBeNull();
});
it('rejects a lone hash', () => {
expect(normalizeHexColor('#')).toBeNull();
});
it('rejects the wrong digit count (7)', () => {
expect(normalizeHexColor('#1234567')).toBeNull();
});
});
+30
View File
@@ -0,0 +1,30 @@
/**
* Normalisierung der Hex-Eingabe der persoenlichen Akzentfarbe
* (Einstellungen → Konto, quick-260917-gsh).
*
* `PATCH /users/me/accent-color` verlangt serverseitig `/^#[0-9a-fA-F]{6}$/` —
* diese Funktion bringt Roheingaben (mit/ohne `#`, Kurzform, Gross-/
* Kleinschreibung, umgebender Leerraum) in genau diese kanonische Form
* oder liefert `null`, wenn das nicht moeglich ist.
*/
const HEX_PATTERN = /^[0-9a-f]{3}$|^[0-9a-f]{6}$/i;
export function normalizeHexColor(input: string): string | null {
const trimmed = input.trim();
const withoutHash = trimmed.startsWith('#') ? trimmed.slice(1) : trimmed;
if (!HEX_PATTERN.test(withoutHash)) {
return null;
}
const expanded =
withoutHash.length === 3
? withoutHash
.split('')
.map((ch) => ch + ch)
.join('')
: withoutHash;
return `#${expanded.toLowerCase()}`;
}