feat: accessibility follow-up — heading hierarchy, keyboard resize, contrast fix
- Fix heading hierarchy: modal titles h3→h2 (RecurrenceModal, two WeeklyView dialogs), SettingsSidebar h4→h3 for Localisation, Data Export, Backup & Restore sections - Add keyboard resize handle: role=slider, tabIndex, aria-valuemin/max/now/text, ArrowUp/Down keys to adjust task duration in 15-min increments - Add aria-label to CalendarEventModal notes textarea fallback - Color contrast: raise --weekly-text-light from #a6a6a7 (~2.4:1) to #767676 (~4.5:1) on white background to meet WCAG AA v1.86.0
This commit is contained in:
parent
5c01869444
commit
8ffb6a0690
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "my-weekly-todo-list",
|
||||
"version": "1.85.0",
|
||||
"version": "1.86.0",
|
||||
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
@ -455,7 +455,7 @@ h3 {
|
||||
--weekly-teal: #009a9a;
|
||||
--weekly-bg: #ffffff;
|
||||
--weekly-text: #000000;
|
||||
--weekly-text-light: #a6a6a7;
|
||||
--weekly-text-light: #767676;
|
||||
--weekly-border: #cccccc;
|
||||
--weekly-completed: #cccccc;
|
||||
--weekly-font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
|
||||
@ -415,7 +415,7 @@ export default function CalendarEventModal({
|
||||
<div style={{ flex: 1, overflowY: 'auto', minHeight: 0 }}>
|
||||
|
||||
<div style={{ padding: '8px 14px 6px', display: 'flex', flexDirection: 'column', gap: '2px' }}>
|
||||
{error && <div style={{ color: '#ef4444', fontSize: '0.75rem', textAlign: 'center', padding: '4px', background: 'rgba(239,68,68,0.08)', borderRadius: '6px', marginBottom: '4px' }}>{error}</div>}
|
||||
{error && <div id="cal-event-error" role="alert" style={{ color: '#ef4444', fontSize: '0.75rem', textAlign: 'center', padding: '4px', background: 'rgba(239,68,68,0.08)', borderRadius: '6px', marginBottom: '4px' }}>{error}</div>}
|
||||
|
||||
{/* Title */}
|
||||
<input
|
||||
@ -424,6 +424,7 @@ export default function CalendarEventModal({
|
||||
onChange={e => setTitle(e.target.value)}
|
||||
placeholder={language === 'de' ? 'Titel hinzufügen' : 'Add title'}
|
||||
aria-label={language === 'de' ? 'Ereignistitel' : 'Event title'}
|
||||
aria-describedby={error ? 'cal-event-error' : undefined}
|
||||
autoFocus
|
||||
style={{
|
||||
width: '100%', padding: '3px 0', fontSize: '1rem', fontWeight: 600,
|
||||
@ -444,6 +445,9 @@ export default function CalendarEventModal({
|
||||
type="button"
|
||||
onClick={() => !event && setIsCalendarSelectorOpen(!isCalendarSelectorOpen)}
|
||||
disabled={!!event}
|
||||
aria-label={language === 'de' ? 'Kalender auswählen' : 'Select calendar'}
|
||||
aria-expanded={isCalendarSelectorOpen}
|
||||
aria-haspopup="listbox"
|
||||
style={{
|
||||
display: 'flex', alignItems: 'center', gap: '4px',
|
||||
background: 'transparent', border: 'none', fontWeight: 500,
|
||||
@ -502,14 +506,15 @@ export default function CalendarEventModal({
|
||||
|
||||
{/* All Day toggle */}
|
||||
<div style={iconRow}>
|
||||
<div style={iconCol}><Clock size={14} /></div>
|
||||
<div style={iconCol} aria-hidden="true"><Clock size={14} /></div>
|
||||
<div style={{ ...fieldCol, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<span style={{ fontSize: '0.8rem', color: 'var(--weekly-text)' }}>{language === 'de' ? 'Ganztägig' : 'All day'}</span>
|
||||
<label style={{ position: 'relative', width: '36px', height: '20px', cursor: 'pointer' }}>
|
||||
<label style={{ position: 'relative', width: '36px', height: '20px', cursor: 'pointer' }} aria-label={language === 'de' ? 'Ganztägig' : 'All day event'}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={allDay}
|
||||
onChange={e => setAllDay(e.target.checked)}
|
||||
aria-label={language === 'de' ? 'Ganztägig' : 'All day event'}
|
||||
style={{ opacity: 0, width: 0, height: 0 }}
|
||||
/>
|
||||
<span style={{
|
||||
@ -537,6 +542,7 @@ export default function CalendarEventModal({
|
||||
type={allDay ? "date" : "datetime-local"}
|
||||
value={allDay ? startDate.toISOString().split('T')[0] : toLocalISOString(startDate)}
|
||||
onChange={e => handleStartDateChange(e.target.value)}
|
||||
aria-label={language === 'de' ? 'Startdatum und -uhrzeit' : 'Start date and time'}
|
||||
style={{
|
||||
flex: 1, padding: '3px 6px', border: 'none', borderRadius: '5px',
|
||||
background: 'var(--weekly-bg-secondary, #f3f4f6)',
|
||||
@ -551,6 +557,7 @@ export default function CalendarEventModal({
|
||||
type={allDay ? "date" : "datetime-local"}
|
||||
value={allDay ? endDate.toISOString().split('T')[0] : toLocalISOString(endDate)}
|
||||
onChange={e => setEndDate(new Date(e.target.value))}
|
||||
aria-label={language === 'de' ? 'Enddatum und -uhrzeit' : 'End date and time'}
|
||||
style={{
|
||||
flex: 1, padding: '3px 6px', border: 'none', borderRadius: '5px',
|
||||
background: 'var(--weekly-bg-secondary, #f3f4f6)',
|
||||
@ -564,9 +571,9 @@ export default function CalendarEventModal({
|
||||
|
||||
{/* Repeat */}
|
||||
<div style={iconRow}>
|
||||
<div style={iconCol}><Repeat size={14} /></div>
|
||||
<div style={iconCol} aria-hidden="true"><Repeat size={14} /></div>
|
||||
<div style={{ ...fieldCol, display: 'flex', alignItems: 'center' }}>
|
||||
<select value={recurrence} onChange={e => setRecurrence(e.target.value)} style={{ ...inlineSelect, flex: 1 }}>
|
||||
<select value={recurrence} onChange={e => setRecurrence(e.target.value)} aria-label={language === 'de' ? 'Wiederholung' : 'Recurrence'} style={{ ...inlineSelect, flex: 1 }}>
|
||||
<option value="none">{language === 'de' ? 'Nie' : 'Never'}</option>
|
||||
<option value="daily">{language === 'de' ? 'Täglich' : 'Every Day'}</option>
|
||||
<option value="weekly">{language === 'de' ? 'Wöchentlich' : 'Every Week'}</option>
|
||||
@ -585,13 +592,14 @@ export default function CalendarEventModal({
|
||||
<input
|
||||
type="number" min={1} max={99} value={customInterval}
|
||||
onChange={e => setCustomInterval(Math.max(1, parseInt(e.target.value) || 1))}
|
||||
aria-label={language === 'de' ? 'Wiederholungsintervall' : 'Recurrence interval'}
|
||||
style={{
|
||||
width: '40px', textAlign: 'center', fontSize: '0.8rem',
|
||||
background: 'var(--weekly-bg-secondary, #f5f5f5)', borderRadius: '6px',
|
||||
padding: '3px 4px', border: 'none', outline: 'none', color: 'var(--weekly-text)',
|
||||
}}
|
||||
/>
|
||||
<select value={customUnit} onChange={e => setCustomUnit(e.target.value as any)} style={inlineSelect}>
|
||||
<select value={customUnit} onChange={e => setCustomUnit(e.target.value as any)} aria-label={language === 'de' ? 'Wiederholungseinheit' : 'Recurrence unit'} style={inlineSelect}>
|
||||
<option value="days">{customInterval === 1 ? (language === 'de' ? 'Tag' : 'Day') : (language === 'de' ? 'Tage' : 'Days')}</option>
|
||||
<option value="weeks">{customInterval === 1 ? (language === 'de' ? 'Woche' : 'Week') : (language === 'de' ? 'Wochen' : 'Weeks')}</option>
|
||||
<option value="months">{customInterval === 1 ? (language === 'de' ? 'Monat' : 'Month') : (language === 'de' ? 'Monate' : 'Months')}</option>
|
||||
@ -602,6 +610,7 @@ export default function CalendarEventModal({
|
||||
<div style={{ display: 'flex', gap: '3px', flexWrap: 'wrap' }}>
|
||||
{(() => {
|
||||
const allDays = language === 'de' ? ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'] : ['S', 'M', 'T', 'W', 'T', 'F', 'S'];
|
||||
const allDaysFull = language === 'de' ? ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'] : ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
||||
const dayIndices = Array.from({ length: 7 }, (_, i) => (i + weekStartDay) % 7);
|
||||
return dayIndices.map(dow => (
|
||||
<button
|
||||
@ -611,6 +620,8 @@ export default function CalendarEventModal({
|
||||
prev.includes(dow) ? (prev.length > 1 ? prev.filter(d => d !== dow) : prev) : [...prev, dow]
|
||||
);
|
||||
}}
|
||||
aria-label={allDaysFull[dow]}
|
||||
aria-pressed={customDays.includes(dow)}
|
||||
style={{
|
||||
width: '28px', height: '28px', borderRadius: '50%',
|
||||
border: customDays.includes(dow) ? '2px solid #3b82f6' : '1px solid var(--weekly-border, #ddd)',
|
||||
@ -636,6 +647,7 @@ export default function CalendarEventModal({
|
||||
<select
|
||||
value={recurrenceEndType}
|
||||
onChange={e => setRecurrenceEndType(e.target.value as 'never' | 'date' | 'count')}
|
||||
aria-label={language === 'de' ? 'Wiederholung Ende' : 'Recurrence end'}
|
||||
style={inlineSelect}
|
||||
>
|
||||
<option value="never">{language === 'de' ? 'Nie' : 'Never'}</option>
|
||||
@ -647,6 +659,7 @@ export default function CalendarEventModal({
|
||||
type="date"
|
||||
value={recurrenceEndDateValue}
|
||||
onChange={e => setRecurrenceEndDateValue(e.target.value)}
|
||||
aria-label={language === 'de' ? 'Enddatum der Wiederholung' : 'Recurrence end date'}
|
||||
style={{
|
||||
fontSize: '0.8rem', border: 'none', borderRadius: '6px',
|
||||
background: 'var(--weekly-bg-secondary, #f5f5f5)',
|
||||
@ -659,6 +672,7 @@ export default function CalendarEventModal({
|
||||
<input
|
||||
type="number" min={1} max={999} value={recurrenceCount}
|
||||
onChange={e => setRecurrenceCount(Math.max(1, parseInt(e.target.value) || 1))}
|
||||
aria-label={language === 'de' ? 'Anzahl der Wiederholungen' : 'Number of occurrences'}
|
||||
style={{
|
||||
width: '44px', textAlign: 'center', fontSize: '0.8rem',
|
||||
background: 'var(--weekly-bg-secondary, #f5f5f5)', borderRadius: '6px',
|
||||
@ -675,12 +689,13 @@ export default function CalendarEventModal({
|
||||
|
||||
{/* Location */}
|
||||
<div style={iconRow}>
|
||||
<div style={iconCol}><MapPin size={14} /></div>
|
||||
<div style={iconCol} aria-hidden="true"><MapPin size={14} /></div>
|
||||
<input
|
||||
type="text"
|
||||
value={location}
|
||||
onChange={e => setLocation(e.target.value)}
|
||||
placeholder={language === 'de' ? 'Ort hinzufügen' : 'Add location'}
|
||||
aria-label={language === 'de' ? 'Ort' : 'Location'}
|
||||
style={{
|
||||
...fieldCol, padding: '2px 0', border: 'none',
|
||||
background: 'transparent', outline: 'none', fontSize: '0.8rem',
|
||||
@ -691,7 +706,7 @@ export default function CalendarEventModal({
|
||||
|
||||
{/* Alert */}
|
||||
<div style={iconRow}>
|
||||
<div style={iconCol}><Bell size={14} /></div>
|
||||
<div style={iconCol} aria-hidden="true"><Bell size={14} /></div>
|
||||
<div style={{ ...fieldCol, display: 'flex', flexDirection: 'column', gap: '2px' }}>
|
||||
{reminders.length === 0 ? (
|
||||
<button onClick={addReminder} style={{ ...inlineSelect, color: 'var(--weekly-text-light)', fontSize: '0.8rem', textAlign: 'left', padding: '2px 0' }}>
|
||||
@ -703,15 +718,18 @@ export default function CalendarEventModal({
|
||||
<select
|
||||
value={reminder.minutes}
|
||||
onChange={e => updateReminder(idx, parseInt(e.target.value))}
|
||||
aria-label={`${language === 'de' ? 'Erinnerung' : 'Reminder'} ${idx + 1}`}
|
||||
style={{ ...inlineSelect, flex: 1 }}
|
||||
>
|
||||
{REMINDER_OPTIONS.map(opt => (
|
||||
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
||||
))}
|
||||
</select>
|
||||
<button onClick={() => setReminders(reminders.filter((_, i) => i !== idx))}
|
||||
<button
|
||||
onClick={() => setReminders(reminders.filter((_, i) => i !== idx))}
|
||||
aria-label={language === 'de' ? `Erinnerung ${idx + 1} entfernen` : `Remove reminder ${idx + 1}`}
|
||||
style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '2px', color: 'var(--weekly-text-light)', opacity: 0.5 }}>
|
||||
<X size={12} />
|
||||
<X size={12} aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
))
|
||||
@ -726,15 +744,15 @@ export default function CalendarEventModal({
|
||||
|
||||
{/* Status & Visibility chips */}
|
||||
<div style={iconRow}>
|
||||
<div style={iconCol}><Activity size={14} /></div>
|
||||
<div style={iconCol} aria-hidden="true"><Activity size={14} /></div>
|
||||
<div style={{ ...fieldCol, display: 'flex', alignItems: 'center', gap: '6px', flexWrap: 'wrap' }}>
|
||||
<select value={busyStatus} onChange={e => setBusyStatus(e.target.value)} style={{ ...inlineSelect, fontSize: '0.75rem' }}>
|
||||
<select value={busyStatus} onChange={e => setBusyStatus(e.target.value)} aria-label={language === 'de' ? 'Status' : 'Busy status'} style={{ ...inlineSelect, fontSize: '0.75rem' }}>
|
||||
{BUSY_STATUS_OPTIONS.map(opt => (
|
||||
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
||||
))}
|
||||
</select>
|
||||
<span style={{ color: 'var(--weekly-border)', fontSize: '0.7rem' }}>|</span>
|
||||
<select value={visibility} onChange={e => setVisibility(e.target.value)} style={{ ...inlineSelect, fontSize: '0.75rem' }}>
|
||||
<span style={{ color: 'var(--weekly-border)', fontSize: '0.7rem' }} aria-hidden="true">|</span>
|
||||
<select value={visibility} onChange={e => setVisibility(e.target.value)} aria-label={language === 'de' ? 'Sichtbarkeit' : 'Visibility'} style={{ ...inlineSelect, fontSize: '0.75rem' }}>
|
||||
{VISIBILITY_OPTIONS.map(opt => (
|
||||
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
||||
))}
|
||||
@ -745,12 +763,13 @@ export default function CalendarEventModal({
|
||||
{/* URL (Conditional) */}
|
||||
{supportsURL && (
|
||||
<div style={iconRow}>
|
||||
<div style={iconCol}><Link2 size={14} /></div>
|
||||
<div style={iconCol} aria-hidden="true"><Link2 size={14} /></div>
|
||||
<input
|
||||
type="url"
|
||||
value={url}
|
||||
onChange={e => setUrl(e.target.value)}
|
||||
placeholder="URL"
|
||||
aria-label="URL"
|
||||
style={{
|
||||
...fieldCol, padding: '2px 0', border: 'none',
|
||||
background: 'transparent', fontSize: '0.8rem', outline: 'none',
|
||||
@ -762,9 +781,11 @@ export default function CalendarEventModal({
|
||||
|
||||
{/* Invitees & Attachments toggle */}
|
||||
<div style={iconRow}>
|
||||
<div style={iconCol}><Users size={14} /></div>
|
||||
<div style={iconCol} aria-hidden="true"><Users size={14} /></div>
|
||||
<button
|
||||
onClick={() => setShowMoreOptions(!showMoreOptions)}
|
||||
aria-expanded={showMoreOptions}
|
||||
aria-label={language === 'de' ? 'Teilnehmer & Anhänge anzeigen' : 'Show invitees & attachments'}
|
||||
style={{
|
||||
display: 'flex', alignItems: 'center', gap: '4px',
|
||||
background: 'none', border: 'none', cursor: 'pointer',
|
||||
@ -772,7 +793,7 @@ export default function CalendarEventModal({
|
||||
}}
|
||||
>
|
||||
{language === 'de' ? 'Teilnehmer & Anhänge' : 'Invitees & Attachments'}
|
||||
{showMoreOptions ? <ChevronUp size={12} /> : <ChevronDown size={12} />}
|
||||
{showMoreOptions ? <ChevronUp size={12} aria-hidden="true" /> : <ChevronDown size={12} aria-hidden="true" />}
|
||||
{attendees.length > 0 && <span style={{ fontSize: '0.7rem', background: 'rgba(59,130,246,0.1)', color: '#3b82f6', padding: '1px 5px', borderRadius: '8px' }}>{attendees.length}</span>}
|
||||
</button>
|
||||
</div>
|
||||
@ -791,9 +812,11 @@ export default function CalendarEventModal({
|
||||
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||
{att.displayName ? `${att.displayName} (${att.email})` : att.email}
|
||||
</span>
|
||||
<button onClick={() => removeAttendee(att.email)}
|
||||
<button
|
||||
onClick={() => removeAttendee(att.email)}
|
||||
aria-label={`${language === 'de' ? 'Teilnehmer entfernen' : 'Remove attendee'}: ${att.displayName || att.email}`}
|
||||
style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '1px', color: 'var(--weekly-text-light)', flexShrink: 0 }}>
|
||||
<X size={12} />
|
||||
<X size={12} aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
@ -803,6 +826,7 @@ export default function CalendarEventModal({
|
||||
value={newAttendeeEmail}
|
||||
onChange={e => setNewAttendeeEmail(e.target.value)}
|
||||
placeholder={language === 'de' ? 'E-Mail-Adresse' : 'Email address'}
|
||||
aria-label={language === 'de' ? 'Teilnehmer E-Mail' : 'Attendee email'}
|
||||
onKeyDown={e => e.key === 'Enter' && addAttendee()}
|
||||
style={{
|
||||
flex: 1, padding: '3px 6px', border: 'none',
|
||||
@ -811,11 +835,11 @@ export default function CalendarEventModal({
|
||||
color: 'var(--weekly-text)',
|
||||
}}
|
||||
/>
|
||||
<button onClick={addAttendee} style={{
|
||||
background: 'none', border: 'none', cursor: 'pointer',
|
||||
color: '#3b82f6', padding: '2px',
|
||||
}}>
|
||||
<Plus size={14} />
|
||||
<button
|
||||
onClick={addAttendee}
|
||||
aria-label={language === 'de' ? 'Teilnehmer hinzufügen' : 'Add attendee'}
|
||||
style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#3b82f6', padding: '2px' }}>
|
||||
<Plus size={14} aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -836,9 +860,11 @@ export default function CalendarEventModal({
|
||||
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||
{att.title || att.url}
|
||||
</span>
|
||||
<button onClick={() => removeAttachment(att.url)}
|
||||
<button
|
||||
onClick={() => removeAttachment(att.url)}
|
||||
aria-label={`${language === 'de' ? 'Anhang entfernen' : 'Remove attachment'}: ${att.title || att.url}`}
|
||||
style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '1px', color: 'var(--weekly-text-light)', flexShrink: 0 }}>
|
||||
<X size={12} />
|
||||
<X size={12} aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
@ -848,6 +874,7 @@ export default function CalendarEventModal({
|
||||
value={newAttachmentUrl}
|
||||
onChange={e => setNewAttachmentUrl(e.target.value)}
|
||||
placeholder="URL"
|
||||
aria-label={language === 'de' ? 'Anhang URL' : 'Attachment URL'}
|
||||
onKeyDown={e => e.key === 'Enter' && addAttachment()}
|
||||
style={{
|
||||
flex: 1, padding: '3px 6px', border: 'none',
|
||||
@ -856,11 +883,11 @@ export default function CalendarEventModal({
|
||||
color: 'var(--weekly-text)',
|
||||
}}
|
||||
/>
|
||||
<button onClick={addAttachment} style={{
|
||||
background: 'none', border: 'none', cursor: 'pointer',
|
||||
color: '#3b82f6', padding: '2px',
|
||||
}}>
|
||||
<Plus size={14} />
|
||||
<button
|
||||
onClick={addAttachment}
|
||||
aria-label={language === 'de' ? 'Anhang hinzufügen' : 'Add attachment'}
|
||||
style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#3b82f6', padding: '2px' }}>
|
||||
<Plus size={14} aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -879,6 +906,7 @@ export default function CalendarEventModal({
|
||||
value={description}
|
||||
onChange={e => setDescription(e.target.value)}
|
||||
placeholder={language === 'de' ? 'Notizen' : 'Notes'}
|
||||
aria-label={language === 'de' ? 'Notizen' : 'Notes'}
|
||||
rows={2}
|
||||
style={{ width: '100%', padding: '3px 0', border: 'none', background: 'transparent', resize: 'none', fontSize: '0.78rem', outline: 'none', color: 'var(--weekly-text)' }}
|
||||
/>
|
||||
|
||||
@ -232,6 +232,19 @@ export function GridTaskBlock({
|
||||
document.body.style.cursor = "ns-resize";
|
||||
};
|
||||
|
||||
const onResizeKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key !== 'ArrowUp' && e.key !== 'ArrowDown') return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const step = 15;
|
||||
const newDuration = e.key === 'ArrowUp'
|
||||
? Math.min(duration + step, 480)
|
||||
: Math.max(duration - step, 15);
|
||||
if (newDuration !== duration) {
|
||||
updateTaskDuration(task.id, newDuration);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={blockRef}
|
||||
@ -899,7 +912,18 @@ export function GridTaskBlock({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="resize-handle" onMouseDown={onResizeStart} />
|
||||
<div
|
||||
className="resize-handle"
|
||||
onMouseDown={onResizeStart}
|
||||
onKeyDown={onResizeKeyDown}
|
||||
tabIndex={0}
|
||||
role="slider"
|
||||
aria-label={`Resize task duration, currently ${duration} minutes`}
|
||||
aria-valuemin={15}
|
||||
aria-valuemax={480}
|
||||
aria-valuenow={duration}
|
||||
aria-valuetext={`${duration} minutes`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -75,7 +75,7 @@ export default function RecurrenceModal({ task, onClose, onSave, language = 'en'
|
||||
return (
|
||||
<div className="weekly-modal-overlay" onClick={onClose}>
|
||||
<div className="weekly-modal-content" onClick={e => e.stopPropagation()} style={{ maxWidth: '400px' }}>
|
||||
<h3 style={{ marginBottom: '1.5rem' }}>{isDE ? 'Wiederkehrende Aufgabe' : 'Recurring Task'}</h3>
|
||||
<h2 style={{ marginBottom: '1.5rem', fontSize: '1.1rem' }}>{isDE ? 'Wiederkehrende Aufgabe' : 'Recurring Task'}</h2>
|
||||
|
||||
<div style={{ marginBottom: '1.5rem' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '1rem' }}>
|
||||
|
||||
@ -1389,9 +1389,9 @@ function SettingsSidebar({
|
||||
<div
|
||||
style={{ display: "flex", flexDirection: "column", gap: "20px" }}
|
||||
>
|
||||
<h4 style={{ fontSize: "1rem", fontWeight: 600, margin: 0 }}>
|
||||
<h3 style={{ fontSize: "1rem", fontWeight: 600, margin: 0 }}>
|
||||
{t.localisation || "Localisation"}
|
||||
</h4>
|
||||
</h3>
|
||||
|
||||
{/* Start Week Setting + Start View On */}
|
||||
<div style={{ display: "flex", gap: "24px", flexWrap: "wrap" }}>
|
||||
@ -4226,7 +4226,7 @@ function SettingsSidebar({
|
||||
borderTop: "1px solid var(--weekly-border)",
|
||||
}}
|
||||
>
|
||||
<h4
|
||||
<h3
|
||||
style={{
|
||||
marginBottom: "10px",
|
||||
fontSize: "1rem",
|
||||
@ -4235,7 +4235,7 @@ function SettingsSidebar({
|
||||
}}
|
||||
>
|
||||
{profile.language === "de" ? "Datenexport" : "Data Export"}
|
||||
</h4>
|
||||
</h3>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "0.9rem",
|
||||
@ -4335,7 +4335,7 @@ function SettingsSidebar({
|
||||
borderTop: "1px solid var(--weekly-border)",
|
||||
}}
|
||||
>
|
||||
<h4
|
||||
<h3
|
||||
style={{
|
||||
marginBottom: "10px",
|
||||
fontSize: "1rem",
|
||||
@ -4344,7 +4344,7 @@ function SettingsSidebar({
|
||||
}}
|
||||
>
|
||||
{t.backupRestore}
|
||||
</h4>
|
||||
</h3>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "0.9rem",
|
||||
|
||||
@ -5737,7 +5737,7 @@ export default function WeeklyView() {
|
||||
{/* Sync status indicator — fixed width so date never shifts */}
|
||||
<div style={{ width: 24, display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0 }}>
|
||||
{syncError
|
||||
? <AlertCircle size={14} className="text-red-500" title={syncError} />
|
||||
? <span role="img" aria-label={syncError ?? 'Sync error'} style={{ display: 'inline-flex' }}><AlertCircle size={14} className="text-red-500" aria-hidden="true" /></span>
|
||||
: (isLoading || isSyncing || syncStatus === "syncing")
|
||||
? <div className="weekly-spinner" role="status" aria-label="Syncing..." style={{ width: 14, height: 14 }}></div>
|
||||
: null}
|
||||
@ -8208,10 +8208,10 @@ export default function WeeklyView() {
|
||||
className="bg-white dark:bg-zinc-900 p-6 rounded-xl shadow-2xl w-full max-w-md"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<h3 className="text-xl font-black mb-1 flex items-center gap-2">
|
||||
<h2 className="text-xl font-black mb-1 flex items-center gap-2">
|
||||
<Plus className="w-6 h-6" />
|
||||
NEW LIST
|
||||
</h3>
|
||||
</h2>
|
||||
<p className="text-xs text-zinc-500 dark:text-zinc-400 mb-6 uppercase tracking-widest font-bold">
|
||||
Create a new section for your tasks
|
||||
</p>
|
||||
@ -8426,9 +8426,9 @@ export default function WeeklyView() {
|
||||
<Repeat size={24} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-xl font-black uppercase tracking-tight">
|
||||
<h2 className="text-xl font-black uppercase tracking-tight">
|
||||
Recurring Task
|
||||
</h3>
|
||||
</h2>
|
||||
<p className="text-xs text-zinc-500 dark:text-zinc-400 font-bold uppercase tracking-widest">
|
||||
Deletion Options
|
||||
</p>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user