feat(calendar): Kalenderwahl im Termin mit Farbe statt nur Text

Die Farbe ist das, woran man einen Kalender im Raster wiedererkennt --
im Termindialog stand aber nur der Name in einem Auswahlfeld. Wer
"Arbeit" waehlt, soll dabei schon das Blau sehen, in dem der Termin
gleich erscheint.

Deshalb Plaketten statt <select>: Farbpunkt plus Name, alle nebeneinander
sichtbar, kein Aufklappen noetig. Der Punkt spricht dieselbe Sprache wie
ueberall sonst im Kalender -- gefuellt heisst gewaehlt, ein leerer Ring
heisst nicht gewaehlt, genau wie in der Kalenderliste und bei der
Terminfarbe.

Die gewaehlte Plakette bekommt nur einen Hauch der Kalenderfarbe als
Grund. Acht kraeftig ausgefuellte Toene nebeneinander erschluegen sonst
das ganze Formular, in dem die Farbe eigentlich der Nebendarsteller ist.

Als CalendarPicker in controls.tsx, wo schon Toggle und ColorSwatches
liegen -- damit die naechste Stelle, die eine Kalenderwahl braucht,
nicht wieder bei null anfaengt.

Selbsttest weiter 36 gruen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-09-05 00:04:30 +02:00
1 parent 77bf16b27f
commit 7a1f0837f5
2 files changed
+56 -4

No files matched your search

@@ -5,7 +5,7 @@ import {
addDays, allDayISO, dateFromInput, eventDay, nthOfMonth, timedISO,
toDateInput, toTimeInput, weekdayCodeOf, weekdayCodes,
} from './dates'
import { ColorSwatches, Toggle } from './controls'
import { CalendarPicker, ColorSwatches, Toggle } from './controls'
interface Props {
/** Fehlt er, entsteht ein neuer Termin. */
@@ -215,9 +215,7 @@ export default function EventDialog({ event, defaultStart, calendars, calendarId
Speichern die Sammlung, behält aber seine Kennung – für
CalDAV-Clients derselbe Termin an neuer Adresse, nicht ein
neuer. */}
<select value={calId} disabled={readOnly} onChange={e => setCalId(e.target.value)} style={inputStyle}>
{calendars.map(c => <option key={c.id} value={c.id}>{c.name}</option>)}
</select>
<CalendarPicker calendars={calendars} value={calId} onChange={setCalId} disabled={readOnly} />
{!!event && calId !== calendarId && (
<p style={{ margin: '6px 2px 0', fontSize: 12, color: 'var(--color-text-muted)', lineHeight: 1.4 }}>
Wird beim Speichern nach „{calendars.find(c => c.id === calId)?.name}" verschoben
@@ -93,3 +93,57 @@ export function ColorSwatches({ value, onChange, inherit, disabled }: {
</div>
)
}
/**
* Kalenderwahl als farbige Plaketten statt als Auswahlfeld.
*
* Ein Auswahlfeld zeigt nur den Namen – die Farbe aber ist das, woran man
* einen Kalender im Raster wiedererkennt. Wer „Arbeit" wählt, soll dabei
* schon das Blau sehen, in dem der Termin gleich erscheint.
*
* Der Punkt spricht dieselbe Sprache wie überall sonst im Kalender: gefüllt
* heißt gewählt, ein leerer Ring heißt nicht gewählt.
*/
export function CalendarPicker({ calendars, value, onChange, disabled }: {
calendars: { id: string; name: string; color: string }[]
value: string
onChange: (id: string) => void
disabled?: boolean
}) {
return (
<div style={{ display: 'flex', gap: 7, flexWrap: 'wrap' }}>
{calendars.map(cal => {
const on = cal.id === value
return (
<button
key={cal.id} type="button" disabled={disabled}
onClick={() => onChange(cal.id)}
aria-pressed={on}
style={{
display: 'flex', alignItems: 'center', gap: 7, maxWidth: '100%',
padding: '7px 12px 7px 10px', borderRadius: 999, fontSize: 13,
border: `1.5px solid ${on ? cal.color : 'var(--color-border)'}`,
// Nur ein Hauch der Kalenderfarbe: Ausgefüllte Plaketten in acht
// kräftigen Tönen nebeneinander erschlagen das ganze Formular.
background: on ? cal.color + '22' : 'transparent',
color: on ? 'var(--color-text)' : 'var(--color-text-muted)',
cursor: disabled ? 'default' : 'pointer',
opacity: disabled ? 0.6 : 1,
transition: 'background 0.12s, border-color 0.12s, color 0.12s',
} as CSSProperties}
>
<span style={{
width: 12, height: 12, borderRadius: '50%', flexShrink: 0,
border: `2px solid ${cal.color}`,
background: on ? cal.color : 'transparent',
transition: 'background 0.12s',
}} />
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{cal.name}
</span>
</button>
)
})}
</div>
)
}