From 4d2e309967cbed46ce5b4caa486c562fd3423cbc Mon Sep 17 00:00:00 2001 From: elpatron Date: Fri, 29 May 2026 17:35:57 +0200 Subject: [PATCH] fix: Einstellungs-Dropdowns durch ThemedSelect mit lesbarem Kontrast ersetzen. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Native Select-Optionen waren in Light/Dark Mode schlecht lesbar; ein eigenes Dropdown steuert Hintergrund und Textfarbe zuverlässig. Co-authored-by: Cursor --- client/src/App.css | 100 ++++++++++++++++++++++++- client/src/components/SettingsForm.tsx | 35 ++++----- client/src/components/ThemedSelect.tsx | 86 +++++++++++++++++++++ 3 files changed, 201 insertions(+), 20 deletions(-) create mode 100644 client/src/components/ThemedSelect.tsx diff --git a/client/src/App.css b/client/src/App.css index 02e608c..2fde769 100644 --- a/client/src/App.css +++ b/client/src/App.css @@ -119,9 +119,103 @@ select.input-text { cursor: pointer; } -select.input-text option { - background-color: var(--app-input-bg); - color: var(--app-input-text); +.themed-select { + position: relative; + width: 100%; +} + +.themed-select-trigger { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + text-align: left; + width: 100%; +} + +.themed-select-chevron { + flex-shrink: 0; + opacity: 0.75; + transition: transform 0.2s ease; +} + +.themed-select.is-open .themed-select-chevron { + transform: rotate(180deg); +} + +.themed-select-menu { + position: absolute; + z-index: 120; + top: calc(100% + 4px); + left: 0; + right: 0; + margin: 0; + padding: 4px; + list-style: none; + border: 1px solid var(--app-input-border); + border-radius: var(--app-radius-input); + box-shadow: var(--app-card-shadow); + max-height: 240px; + overflow-y: auto; + isolation: isolate; +} + +html.scheme-light .themed-select-menu { + background: #ffffff; + color: #0f172a; +} + +html.scheme-dark .themed-select-menu { + background: #1c1c1e; + color: #f8fafc; +} + +.themed-select-option { + padding: 10px 12px; + border-radius: calc(var(--app-radius-input) - 2px); + cursor: pointer; + font-size: 16px; + font-weight: 500; + line-height: 1.4; +} + +html.scheme-light .themed-select-option { + color: #0f172a; + -webkit-text-fill-color: #0f172a; +} + +html.scheme-dark .themed-select-option { + color: #f8fafc; + -webkit-text-fill-color: #f8fafc; +} + +.themed-select-option:hover { + background: var(--app-accent-bg); +} + +html.scheme-light .themed-select-option:hover { + color: var(--app-accent); + -webkit-text-fill-color: var(--app-accent); +} + +html.scheme-dark .themed-select-option:hover { + color: var(--app-accent-light); + -webkit-text-fill-color: var(--app-accent-light); +} + +.themed-select-option.is-selected { + background: var(--app-accent-bg); + font-weight: 600; +} + +html.scheme-light .themed-select-option.is-selected { + color: var(--app-accent); + -webkit-text-fill-color: var(--app-accent); +} + +html.scheme-dark .themed-select-option.is-selected { + color: var(--app-accent-light); + -webkit-text-fill-color: var(--app-accent-light); } .input-textarea { diff --git a/client/src/components/SettingsForm.tsx b/client/src/components/SettingsForm.tsx index 38d9e3d..aec40a1 100644 --- a/client/src/components/SettingsForm.tsx +++ b/client/src/components/SettingsForm.tsx @@ -6,6 +6,7 @@ import AccountDangerZone from './AccountDangerZone.tsx' import PwaInstallPrompt from './PwaInstallPrompt.tsx' import { useDialog } from './ModalDialog.tsx' import { notifyAppearanceChanged } from '../services/appearance.js' +import ThemedSelect from './ThemedSelect.tsx' interface SettingsFormProps { logbookId?: string | null @@ -326,18 +327,18 @@ export default function SettingsForm({ logbookId }: SettingsFormProps) {

- + onChange={handleThemeChange} + options={[ + { value: 'auto', label: t('settings.theme_auto') }, + { value: 'ocean', label: t('settings.theme_ocean') }, + { value: 'material', label: t('settings.theme_material') }, + { value: 'cupertino', label: t('settings.theme_cupertino') } + ]} + />
@@ -350,17 +351,17 @@ export default function SettingsForm({ logbookId }: SettingsFormProps) {

- + onChange={handleColorSchemeChange} + options={[ + { value: 'auto', label: t('settings.color_scheme_auto') }, + { value: 'light', label: t('settings.color_scheme_light') }, + { value: 'dark', label: t('settings.color_scheme_dark') } + ]} + />
diff --git a/client/src/components/ThemedSelect.tsx b/client/src/components/ThemedSelect.tsx new file mode 100644 index 0000000..a63d875 --- /dev/null +++ b/client/src/components/ThemedSelect.tsx @@ -0,0 +1,86 @@ +import { useEffect, useRef, useState } from 'react' +import { ChevronDown } from 'lucide-react' + +export interface ThemedSelectOption { + value: string + label: string +} + +interface ThemedSelectProps { + id?: string + value: string + options: ThemedSelectOption[] + onChange: (value: string) => void + disabled?: boolean +} + +export default function ThemedSelect({ + id, + value, + options, + onChange, + disabled = false +}: ThemedSelectProps) { + const [open, setOpen] = useState(false) + const rootRef = useRef(null) + const selected = options.find((option) => option.value === value) + + useEffect(() => { + if (!open) return + + const closeOnOutsideClick = (event: MouseEvent) => { + if (rootRef.current && !rootRef.current.contains(event.target as Node)) { + setOpen(false) + } + } + + const closeOnEscape = (event: KeyboardEvent) => { + if (event.key === 'Escape') setOpen(false) + } + + document.addEventListener('mousedown', closeOnOutsideClick) + document.addEventListener('keydown', closeOnEscape) + return () => { + document.removeEventListener('mousedown', closeOnOutsideClick) + document.removeEventListener('keydown', closeOnEscape) + } + }, [open]) + + const selectOption = (nextValue: string) => { + onChange(nextValue) + setOpen(false) + } + + return ( +
+ + + {open && ( +
    + {options.map((option) => ( +
  • selectOption(option.value)} + > + {option.label} +
  • + ))} +
+ )} +
+ ) +}