feat(calendar): Wiederholungen, Tagesansicht, Terminfarben, Farbpunkt als Schalter
Fünf Wünsche aus dem Durchtesten: 1. Wiederholungen einstellbar (täglich/wöchentlich/monatlich/jährlich, alle N, Ende nie/nach Anzahl/an einem Datum). Der eigentliche Aufwand lag nicht im Formular, sondern im Aufklappen: eine wöchentliche Reihe erschien sonst als *ein* Eintrag. Der Server klappt sie jetzt im angefragten Zeitfenster auf (?from=&to=, rrule-go über go-icals RecurrenceSet), die Ansichten fragen ihren sichtbaren Ausschnitt an. Geprüft: 5 Termine wöchentlich, nach Umstellung auf 14-tägig korrekt neu gerechnet, Fenster einer Woche liefert genau einen. Regeln, die das Formular nicht abbildet (etwa jeder zweite Dienstag im Monat), werden weiterhin korrekt *angezeigt* -- geprüft, die vier Termine stimmen -- aber nicht zum Bearbeiten angeboten (409). Ein halb verstandenes Speichern würde die Regel sonst still beschneiden. 2. Tag wird erst geöffnet, statt sofort einen Termin anzulegen: Klick auf einen Tag zeigt dessen Termine als Liste, dort wählt man einen aus oder legt bewusst einen neuen an. 3. Eigene Farbe je Termin (COLOR nach RFC 7986), leer = Kalenderfarbe. 4. Kalenderliste ohne Kästchen: der Farbpunkt selbst ist der Schalter -- gefüllt heißt sichtbar, leerer Ring ausgeblendet. 5. Ganztägig als Schiebeschalter statt Kästchen -- es stellt das Formular um, das ist keine Nebenangabe. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
105ce8de4a
commit
79a7cc6a3d
8 files changed
+795
-218
No files matched your search
@@ -0,0 +1,95 @@
|
||||
import type { CSSProperties } from 'react'
|
||||
|
||||
/** Eine Palette für Kalender **und** Termine – zwei getrennte Listen liefen
|
||||
* sonst mit der Zeit auseinander, und ein Termin in einer Farbe, die es als
|
||||
* Kalenderfarbe nicht gibt, sieht aus wie ein Fehler. */
|
||||
export const palette = [
|
||||
'#4285f4', '#ea4335', '#fbbc04', '#34a853',
|
||||
'#a142f4', '#ff6d00', '#00acc1', '#607d8b',
|
||||
]
|
||||
|
||||
/**
|
||||
* Schiebeschalter statt Kästchen.
|
||||
*
|
||||
* Nicht nur Zierde: „Ganztägig" schaltet das Formular um (die Uhrzeitfelder
|
||||
* verschwinden). Ein Kästchen liest sich wie eine Angabe, die man nebenbei
|
||||
* ankreuzt – ein Schalter zeigt, dass hier etwas umgestellt wird.
|
||||
*/
|
||||
export function Toggle({ checked, onChange, disabled, label }: {
|
||||
checked: boolean
|
||||
onChange: (v: boolean) => void
|
||||
disabled?: boolean
|
||||
label: string
|
||||
}) {
|
||||
return (
|
||||
<label style={{
|
||||
display: 'flex', alignItems: 'center', gap: 10,
|
||||
cursor: disabled ? 'default' : 'pointer', opacity: disabled ? 0.6 : 1,
|
||||
fontSize: 13, color: 'var(--color-text)',
|
||||
}}>
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={checked}
|
||||
aria-label={label}
|
||||
disabled={disabled}
|
||||
onClick={() => !disabled && onChange(!checked)}
|
||||
style={{
|
||||
width: 38, height: 22, borderRadius: 11, flexShrink: 0, padding: 0,
|
||||
border: '1px solid var(--color-border)',
|
||||
background: checked ? 'var(--color-primary)' : 'var(--color-surface-2)',
|
||||
cursor: disabled ? 'default' : 'pointer',
|
||||
transition: 'background 0.15s',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<span style={{
|
||||
position: 'absolute', top: 2, left: checked ? 18 : 2,
|
||||
width: 16, height: 16, borderRadius: '50%',
|
||||
background: checked ? '#fff' : 'var(--color-text-muted)',
|
||||
transition: 'left 0.15s',
|
||||
}} />
|
||||
</button>
|
||||
{label}
|
||||
</label>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Farbwahl als Kreise. Der gewählte Kreis ist gefüllt, die anderen sind
|
||||
* Umrisse – dieselbe Sprache wie in der Kalenderliste, wo ein gefüllter
|
||||
* Punkt „wird angezeigt" heißt.
|
||||
*/
|
||||
export function ColorSwatches({ value, onChange, inherit, disabled }: {
|
||||
value: string
|
||||
onChange: (c: string) => void
|
||||
/** Ist sie gesetzt, gibt es zusätzlich die Wahl „Farbe des Kalenders"
|
||||
* (leerer Wert) und dieser Kreis zeigt sie an. */
|
||||
inherit?: string
|
||||
disabled?: boolean
|
||||
}) {
|
||||
const dot = (color: string, selected: boolean, onClick: () => void, key: string, title: string) => (
|
||||
<button
|
||||
key={key} type="button" title={title} aria-label={title} disabled={disabled}
|
||||
onClick={onClick}
|
||||
style={{
|
||||
width: 26, height: 26, borderRadius: '50%', padding: 0, flexShrink: 0,
|
||||
background: selected ? color : 'transparent',
|
||||
border: `2px solid ${color}`,
|
||||
cursor: disabled ? 'default' : 'pointer',
|
||||
boxShadow: selected ? `0 0 0 2px var(--color-surface), 0 0 0 3.5px ${color}` : 'none',
|
||||
transition: 'background 0.12s, box-shadow 0.12s',
|
||||
} as CSSProperties}
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', gap: 9, flexWrap: 'wrap', alignItems: 'center' }}>
|
||||
{inherit !== undefined && dot(inherit, value === '', () => onChange(''), 'inherit', 'Farbe des Kalenders')}
|
||||
{inherit !== undefined && (
|
||||
<span style={{ width: 1, height: 20, background: 'var(--color-border)', margin: '0 2px' }} />
|
||||
)}
|
||||
{palette.map(c => dot(c, value.toLowerCase() === c.toLowerCase(), () => onChange(c), c, c))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user