mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:41: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:on={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>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@
|
|||
|
||||
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 isAdmin = $derived(authStore.user?.role === 'admin');
|
||||
|
||||
|
|
|
|||
|
|
@ -88,8 +88,8 @@
|
|||
placeholder="Stadt oder Region suchen..."
|
||||
/>
|
||||
<div class="radius-row">
|
||||
<label class="radius-label">Radius: {radiusKm} km</label>
|
||||
<input type="range" min="5" max="100" step="5" bind:value={radiusKm} />
|
||||
<label class="radius-label" for="region-radius">Radius: {radiusKm} km</label>
|
||||
<input id="region-radius" type="range" min="5" max="100" step="5" bind:value={radiusKm} />
|
||||
</div>
|
||||
{#if suggestions.length > 0}
|
||||
<ul class="suggestions">
|
||||
|
|
|
|||
|
|
@ -314,9 +314,6 @@
|
|||
.source-item.error {
|
||||
border-color: rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
.source-item.inactive {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.source-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
|
@ -342,14 +339,6 @@
|
|||
background: rgba(239, 68, 68, 0.15);
|
||||
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 {
|
||||
font-size: 0.6875rem;
|
||||
color: rgb(220, 38, 38);
|
||||
|
|
|
|||
|
|
@ -562,6 +562,8 @@
|
|||
class:active={i === currentQuestionIdx}
|
||||
class:answered={answeredSet.has(categoryQuestions[i].id)}
|
||||
onclick={() => (currentQuestionIdx = i)}
|
||||
aria-label="Frage {i + 1}"
|
||||
aria-current={i === currentQuestionIdx ? 'step' : undefined}
|
||||
></button>
|
||||
{/each}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -65,6 +65,18 @@
|
|||
5: 'Fr',
|
||||
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>
|
||||
|
||||
<div class="overview">
|
||||
|
|
@ -108,9 +120,15 @@
|
|||
<button class="edit-btn primary" onclick={() => saveEdit('about.bio')}>Speichern</button
|
||||
>
|
||||
</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}
|
||||
</p>{/if}
|
||||
</div>{/if}
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
|
|
@ -144,7 +162,13 @@
|
|||
</div>
|
||||
</div>
|
||||
{: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}
|
||||
</div>
|
||||
{:else}
|
||||
|
|
@ -162,7 +186,13 @@
|
|||
<section class="section-card">
|
||||
<h3 class="section-title">Tagesablauf</h3>
|
||||
{#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">
|
||||
<span class="routine-label">Aufstehen</span><span class="routine-value"
|
||||
>{ctx.routine.wakeUp}</span
|
||||
|
|
@ -196,9 +226,15 @@
|
|||
<h3 class="section-title">Ernährung</h3>
|
||||
{#if ctx?.nutrition && (ctx.nutrition.diet || ctx.nutrition.allergies?.length)}
|
||||
<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}
|
||||
</p>{/if}
|
||||
</div>{/if}
|
||||
{#if ctx.nutrition.allergies?.length}
|
||||
{#if editingField === 'nutrition.allergies'}
|
||||
<div class="tags-edit">
|
||||
|
|
@ -232,7 +268,12 @@
|
|||
{:else}
|
||||
<div
|
||||
class="tags-list"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
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}
|
||||
</div>
|
||||
|
|
@ -258,7 +299,10 @@
|
|||
<span class="routine-label">Sport</span>
|
||||
<div
|
||||
class="tags-list"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
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}
|
||||
</div>
|
||||
|
|
@ -269,7 +313,10 @@
|
|||
<span class="routine-label">Medien</span>
|
||||
<div
|
||||
class="tags-list"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
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}
|
||||
</div>
|
||||
|
|
@ -280,7 +327,10 @@
|
|||
<span class="routine-label">Haustiere</span>
|
||||
<span
|
||||
class="section-text"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onclick={() => startEdit('leisure.pets', ctx?.leisure?.pets ?? '')}
|
||||
onkeydown={onActivate(() => startEdit('leisure.pets', ctx?.leisure?.pets ?? ''))}
|
||||
>{ctx.leisure.pets}</span
|
||||
>
|
||||
</div>
|
||||
|
|
@ -318,7 +368,13 @@
|
|||
</div>
|
||||
</div>
|
||||
{: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}
|
||||
</div>
|
||||
{:else}
|
||||
|
|
@ -336,7 +392,13 @@
|
|||
<section class="section-card">
|
||||
<h3 class="section-title">Arbeitsstil</h3>
|
||||
{#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">
|
||||
<span class="routine-label">Arbeitsweise</span><span class="routine-value"
|
||||
>{ctx.social.workStyle}</span
|
||||
|
|
@ -394,7 +456,10 @@
|
|||
{:else if ctx?.about?.languages?.length}
|
||||
<div
|
||||
class="tags-list"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
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}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -20,9 +20,17 @@
|
|||
|
||||
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 ratingError = $state<string | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
rating = entry.userRating ?? 0;
|
||||
});
|
||||
|
||||
async function setRating(value: number) {
|
||||
if (!runId || !entry.resultId) return;
|
||||
ratingError = null;
|
||||
|
|
|
|||
|
|
@ -294,9 +294,6 @@
|
|||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
.animate-fade-in {
|
||||
animation: fade-in 0.2s ease-out;
|
||||
}
|
||||
@keyframes bounce-once {
|
||||
0%,
|
||||
100% {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue