mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 09:01:09 +02:00
fix(a11y): resolve 30 svelte-check warnings across 8 files
svelte-check now completes clean (0 errors, 0 warnings, 0 files with problems). - profile/ContextOverview: 11 click-on-div sites made keyboard- accessible with role="button", tabindex="0", and an onActivate helper that fires the same handler on Enter/Space. Two <p> wrappers became <div> since <p> cannot carry role="button" per ARIA. - profile/ContextInterview: paginate dots got aria-label + aria-current. - settings/GeneralSection: toggle button got aria-label + aria-pressed. - events/RegionPicker: radius label associated with range input via for/id. - events/SourceManager: drop unused .source-item.inactive + .inactive- badge CSS selectors (dead code). - research-lab/CompareColumn: local `rating` seed from entry.userRating now uses svelte-ignore comment + $effect sync (intentional seed-only read, plus prop-update mirror). - admin/ListView: initialTab prop is deliberately read only at mount; svelte-ignore comment documents the intent. - gifts/redeem: drop unused .animate-fade-in CSS selector. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9db044178c
commit
3e09ff66d1
8 changed files with 90 additions and 24 deletions
|
|
@ -135,6 +135,8 @@
|
||||||
class="toggle"
|
class="toggle"
|
||||||
class:on={userSettings.general?.soundsEnabled ?? true}
|
class:on={userSettings.general?.soundsEnabled ?? true}
|
||||||
onclick={() => setSounds(!(userSettings.general?.soundsEnabled ?? true))}
|
onclick={() => setSounds(!(userSettings.general?.soundsEnabled ?? true))}
|
||||||
|
aria-label="Sounds ein- oder ausschalten"
|
||||||
|
aria-pressed={userSettings.general?.soundsEnabled ?? true}
|
||||||
>
|
>
|
||||||
<span class="toggle-knob"></span>
|
<span class="toggle-knob"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,9 @@
|
||||||
|
|
||||||
let { initialTab = 'overview' }: Props = $props();
|
let { initialTab = 'overview' }: Props = $props();
|
||||||
|
|
||||||
|
// initialTab is an entry-point default only — we deliberately ignore later prop
|
||||||
|
// changes because the user may have switched tabs since mount.
|
||||||
|
// svelte-ignore state_referenced_locally
|
||||||
let activeTab = $state<TabId>(initialTab);
|
let activeTab = $state<TabId>(initialTab);
|
||||||
let isAdmin = $derived(authStore.user?.role === 'admin');
|
let isAdmin = $derived(authStore.user?.role === 'admin');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -88,8 +88,8 @@
|
||||||
placeholder="Stadt oder Region suchen..."
|
placeholder="Stadt oder Region suchen..."
|
||||||
/>
|
/>
|
||||||
<div class="radius-row">
|
<div class="radius-row">
|
||||||
<label class="radius-label">Radius: {radiusKm} km</label>
|
<label class="radius-label" for="region-radius">Radius: {radiusKm} km</label>
|
||||||
<input type="range" min="5" max="100" step="5" bind:value={radiusKm} />
|
<input id="region-radius" type="range" min="5" max="100" step="5" bind:value={radiusKm} />
|
||||||
</div>
|
</div>
|
||||||
{#if suggestions.length > 0}
|
{#if suggestions.length > 0}
|
||||||
<ul class="suggestions">
|
<ul class="suggestions">
|
||||||
|
|
|
||||||
|
|
@ -314,9 +314,6 @@
|
||||||
.source-item.error {
|
.source-item.error {
|
||||||
border-color: rgba(239, 68, 68, 0.3);
|
border-color: rgba(239, 68, 68, 0.3);
|
||||||
}
|
}
|
||||||
.source-item.inactive {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
.source-info {
|
.source-info {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|
@ -342,14 +339,6 @@
|
||||||
background: rgba(239, 68, 68, 0.15);
|
background: rgba(239, 68, 68, 0.15);
|
||||||
color: rgb(220, 38, 38);
|
color: rgb(220, 38, 38);
|
||||||
}
|
}
|
||||||
.inactive-badge {
|
|
||||||
font-size: 0.625rem;
|
|
||||||
font-weight: 600;
|
|
||||||
padding: 0.0625rem 0.375rem;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
background: hsl(var(--color-muted));
|
|
||||||
color: hsl(var(--color-muted-foreground));
|
|
||||||
}
|
|
||||||
.source-error {
|
.source-error {
|
||||||
font-size: 0.6875rem;
|
font-size: 0.6875rem;
|
||||||
color: rgb(220, 38, 38);
|
color: rgb(220, 38, 38);
|
||||||
|
|
|
||||||
|
|
@ -562,6 +562,8 @@
|
||||||
class:active={i === currentQuestionIdx}
|
class:active={i === currentQuestionIdx}
|
||||||
class:answered={answeredSet.has(categoryQuestions[i].id)}
|
class:answered={answeredSet.has(categoryQuestions[i].id)}
|
||||||
onclick={() => (currentQuestionIdx = i)}
|
onclick={() => (currentQuestionIdx = i)}
|
||||||
|
aria-label="Frage {i + 1}"
|
||||||
|
aria-current={i === currentQuestionIdx ? 'step' : undefined}
|
||||||
></button>
|
></button>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,18 @@
|
||||||
5: 'Fr',
|
5: 'Fr',
|
||||||
6: 'Sa',
|
6: 'Sa',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Enter / Space on a non-button click-target counts as activation, same
|
||||||
|
// as a button would. Used to make the "tap a section to edit" surfaces
|
||||||
|
// keyboard-accessible without rewriting the whole card layout.
|
||||||
|
function onActivate(handler: () => void) {
|
||||||
|
return (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
handler();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="overview">
|
<div class="overview">
|
||||||
|
|
@ -108,9 +120,15 @@
|
||||||
<button class="edit-btn primary" onclick={() => saveEdit('about.bio')}>Speichern</button
|
<button class="edit-btn primary" onclick={() => saveEdit('about.bio')}>Speichern</button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
{:else}<p class="section-text" onclick={() => startEdit('about.bio', ctx?.about?.bio)}>
|
{:else}<div
|
||||||
|
class="section-text"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
onclick={() => startEdit('about.bio', ctx?.about?.bio)}
|
||||||
|
onkeydown={onActivate(() => startEdit('about.bio', ctx?.about?.bio))}
|
||||||
|
>
|
||||||
{ctx?.about?.bio}
|
{ctx?.about?.bio}
|
||||||
</p>{/if}
|
</div>{/if}
|
||||||
</section>
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|
@ -144,7 +162,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else if ctx?.interests?.length}
|
{:else if ctx?.interests?.length}
|
||||||
<div class="tags-list" onclick={() => startEdit('interests', ctx?.interests ?? [])}>
|
<div
|
||||||
|
class="tags-list"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
onclick={() => startEdit('interests', ctx?.interests ?? [])}
|
||||||
|
onkeydown={onActivate(() => startEdit('interests', ctx?.interests ?? []))}
|
||||||
|
>
|
||||||
{#each ctx.interests as tag (tag)}<span class="tag">{tag}</span>{/each}
|
{#each ctx.interests as tag (tag)}<span class="tag">{tag}</span>{/each}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
|
@ -162,7 +186,13 @@
|
||||||
<section class="section-card">
|
<section class="section-card">
|
||||||
<h3 class="section-title">Tagesablauf</h3>
|
<h3 class="section-title">Tagesablauf</h3>
|
||||||
{#if ctx?.routine && (ctx.routine.wakeUp || ctx.routine.workStart || ctx.routine.bedtime)}
|
{#if ctx?.routine && (ctx.routine.wakeUp || ctx.routine.workStart || ctx.routine.bedtime)}
|
||||||
<div class="routine-grid" onclick={() => onStartInterview()}>
|
<div
|
||||||
|
class="routine-grid"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
onclick={() => onStartInterview()}
|
||||||
|
onkeydown={onActivate(() => onStartInterview())}
|
||||||
|
>
|
||||||
{#if ctx.routine.wakeUp}<div class="routine-item">
|
{#if ctx.routine.wakeUp}<div class="routine-item">
|
||||||
<span class="routine-label">Aufstehen</span><span class="routine-value"
|
<span class="routine-label">Aufstehen</span><span class="routine-value"
|
||||||
>{ctx.routine.wakeUp}</span
|
>{ctx.routine.wakeUp}</span
|
||||||
|
|
@ -196,9 +226,15 @@
|
||||||
<h3 class="section-title">Ernährung</h3>
|
<h3 class="section-title">Ernährung</h3>
|
||||||
{#if ctx?.nutrition && (ctx.nutrition.diet || ctx.nutrition.allergies?.length)}
|
{#if ctx?.nutrition && (ctx.nutrition.diet || ctx.nutrition.allergies?.length)}
|
||||||
<div>
|
<div>
|
||||||
{#if ctx.nutrition.diet}<p class="section-text" onclick={() => onStartInterview()}>
|
{#if ctx.nutrition.diet}<div
|
||||||
|
class="section-text"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
onclick={() => onStartInterview()}
|
||||||
|
onkeydown={onActivate(() => onStartInterview())}
|
||||||
|
>
|
||||||
{ctx.nutrition.diet}
|
{ctx.nutrition.diet}
|
||||||
</p>{/if}
|
</div>{/if}
|
||||||
{#if ctx.nutrition.allergies?.length}
|
{#if ctx.nutrition.allergies?.length}
|
||||||
{#if editingField === 'nutrition.allergies'}
|
{#if editingField === 'nutrition.allergies'}
|
||||||
<div class="tags-edit">
|
<div class="tags-edit">
|
||||||
|
|
@ -232,7 +268,12 @@
|
||||||
{:else}
|
{:else}
|
||||||
<div
|
<div
|
||||||
class="tags-list"
|
class="tags-list"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
onclick={() => startEdit('nutrition.allergies', ctx?.nutrition?.allergies ?? [])}
|
onclick={() => startEdit('nutrition.allergies', ctx?.nutrition?.allergies ?? [])}
|
||||||
|
onkeydown={onActivate(() =>
|
||||||
|
startEdit('nutrition.allergies', ctx?.nutrition?.allergies ?? [])
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{#each ctx.nutrition.allergies as a (a)}<span class="tag warning">{a}</span>{/each}
|
{#each ctx.nutrition.allergies as a (a)}<span class="tag warning">{a}</span>{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -258,7 +299,10 @@
|
||||||
<span class="routine-label">Sport</span>
|
<span class="routine-label">Sport</span>
|
||||||
<div
|
<div
|
||||||
class="tags-list"
|
class="tags-list"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
onclick={() => startEdit('leisure.sports', ctx?.leisure?.sports ?? [])}
|
onclick={() => startEdit('leisure.sports', ctx?.leisure?.sports ?? [])}
|
||||||
|
onkeydown={onActivate(() => startEdit('leisure.sports', ctx?.leisure?.sports ?? []))}
|
||||||
>
|
>
|
||||||
{#each ctx.leisure.sports as s (s)}<span class="tag">{s}</span>{/each}
|
{#each ctx.leisure.sports as s (s)}<span class="tag">{s}</span>{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -269,7 +313,10 @@
|
||||||
<span class="routine-label">Medien</span>
|
<span class="routine-label">Medien</span>
|
||||||
<div
|
<div
|
||||||
class="tags-list"
|
class="tags-list"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
onclick={() => startEdit('leisure.media', ctx?.leisure?.media ?? [])}
|
onclick={() => startEdit('leisure.media', ctx?.leisure?.media ?? [])}
|
||||||
|
onkeydown={onActivate(() => startEdit('leisure.media', ctx?.leisure?.media ?? []))}
|
||||||
>
|
>
|
||||||
{#each ctx.leisure.media as m (m)}<span class="tag">{m}</span>{/each}
|
{#each ctx.leisure.media as m (m)}<span class="tag">{m}</span>{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -280,7 +327,10 @@
|
||||||
<span class="routine-label">Haustiere</span>
|
<span class="routine-label">Haustiere</span>
|
||||||
<span
|
<span
|
||||||
class="section-text"
|
class="section-text"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
onclick={() => startEdit('leisure.pets', ctx?.leisure?.pets ?? '')}
|
onclick={() => startEdit('leisure.pets', ctx?.leisure?.pets ?? '')}
|
||||||
|
onkeydown={onActivate(() => startEdit('leisure.pets', ctx?.leisure?.pets ?? ''))}
|
||||||
>{ctx.leisure.pets}</span
|
>{ctx.leisure.pets}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -318,7 +368,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else if ctx?.goals?.length}
|
{:else if ctx?.goals?.length}
|
||||||
<div class="tags-list" onclick={() => startEdit('goals', ctx?.goals ?? [])}>
|
<div
|
||||||
|
class="tags-list"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
onclick={() => startEdit('goals', ctx?.goals ?? [])}
|
||||||
|
onkeydown={onActivate(() => startEdit('goals', ctx?.goals ?? []))}
|
||||||
|
>
|
||||||
{#each ctx.goals as goal (goal)}<span class="tag accent">{goal}</span>{/each}
|
{#each ctx.goals as goal (goal)}<span class="tag accent">{goal}</span>{/each}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
|
@ -336,7 +392,13 @@
|
||||||
<section class="section-card">
|
<section class="section-card">
|
||||||
<h3 class="section-title">Arbeitsstil</h3>
|
<h3 class="section-title">Arbeitsstil</h3>
|
||||||
{#if ctx?.social && (ctx.social.workStyle || ctx.social.communication || ctx.social.livingSetup)}
|
{#if ctx?.social && (ctx.social.workStyle || ctx.social.communication || ctx.social.livingSetup)}
|
||||||
<div class="routine-grid" onclick={() => onStartInterview()}>
|
<div
|
||||||
|
class="routine-grid"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
onclick={() => onStartInterview()}
|
||||||
|
onkeydown={onActivate(() => onStartInterview())}
|
||||||
|
>
|
||||||
{#if ctx.social.workStyle}<div class="routine-item">
|
{#if ctx.social.workStyle}<div class="routine-item">
|
||||||
<span class="routine-label">Arbeitsweise</span><span class="routine-value"
|
<span class="routine-label">Arbeitsweise</span><span class="routine-value"
|
||||||
>{ctx.social.workStyle}</span
|
>{ctx.social.workStyle}</span
|
||||||
|
|
@ -394,7 +456,10 @@
|
||||||
{:else if ctx?.about?.languages?.length}
|
{:else if ctx?.about?.languages?.length}
|
||||||
<div
|
<div
|
||||||
class="tags-list"
|
class="tags-list"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
onclick={() => startEdit('about.languages', ctx?.about?.languages ?? [])}
|
onclick={() => startEdit('about.languages', ctx?.about?.languages ?? [])}
|
||||||
|
onkeydown={onActivate(() => startEdit('about.languages', ctx?.about?.languages ?? []))}
|
||||||
>
|
>
|
||||||
{#each ctx.about.languages as lang (lang)}<span class="tag">{lang}</span>{/each}
|
{#each ctx.about.languages as lang (lang)}<span class="tag">{lang}</span>{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,17 @@
|
||||||
|
|
||||||
const { category, entry, runId }: Props = $props();
|
const { category, entry, runId }: Props = $props();
|
||||||
|
|
||||||
|
// Local optimistic rating — seed from the current entry, then keep in sync
|
||||||
|
// via $effect when the parent swaps the prop. The seed-only read of
|
||||||
|
// entry.userRating is intentional; the $effect covers prop updates.
|
||||||
|
// svelte-ignore state_referenced_locally
|
||||||
let rating = $state(entry.userRating ?? 0);
|
let rating = $state(entry.userRating ?? 0);
|
||||||
let ratingError = $state<string | null>(null);
|
let ratingError = $state<string | null>(null);
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
rating = entry.userRating ?? 0;
|
||||||
|
});
|
||||||
|
|
||||||
async function setRating(value: number) {
|
async function setRating(value: number) {
|
||||||
if (!runId || !entry.resultId) return;
|
if (!runId || !entry.resultId) return;
|
||||||
ratingError = null;
|
ratingError = null;
|
||||||
|
|
|
||||||
|
|
@ -294,9 +294,6 @@
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.animate-fade-in {
|
|
||||||
animation: fade-in 0.2s ease-out;
|
|
||||||
}
|
|
||||||
@keyframes bounce-once {
|
@keyframes bounce-once {
|
||||||
0%,
|
0%,
|
||||||
100% {
|
100% {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue