fix: enable svelte a11y linting and fix violations
This commit is contained in:
@@ -29,5 +29,22 @@ export default defineConfig(
|
||||
svelteConfig
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'svelte:a11y:rules',
|
||||
files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'],
|
||||
rules: {
|
||||
// Compile all .svelte files and surface compiler warnings as ESLint errors.
|
||||
// The Svelte 5 compiler has built-in a11y checks (a11y_missing_attribute,
|
||||
// a11y_click_events_have_key_events, a11y_no_static_element_interactions, etc.)
|
||||
// that are enabled by default via accessibilityWarnings: true.
|
||||
'svelte/valid-compile': 'error',
|
||||
|
||||
// Require explicit type attribute on <button> elements
|
||||
'svelte/button-has-type': 'error',
|
||||
|
||||
// Require rel="noopener noreferrer" with target="_blank"
|
||||
'svelte/no-target-blank': 'error'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1075,6 +1075,7 @@
|
||||
<!-- Tabs -->
|
||||
<div class="flex gap-6 border-b border-gray-200">
|
||||
<button
|
||||
type="button"
|
||||
class="pb-2 text-sm font-medium transition-colors {userType === 'member'
|
||||
? 'border-b-2 border-primary text-primary'
|
||||
: 'text-gray-500 hover:text-gray-700'}"
|
||||
@@ -1086,6 +1087,7 @@
|
||||
Member
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="pb-2 text-sm font-medium transition-colors {userType === 'guest'
|
||||
? 'border-b-2 border-primary text-primary'
|
||||
: 'text-gray-500 hover:text-gray-700'}"
|
||||
@@ -1302,6 +1304,7 @@
|
||||
<div class="space-y-2">
|
||||
{#each customServices as cs (cs.id)}
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full items-center justify-between rounded-lg border px-3 py-2 text-sm hover:bg-gray-50"
|
||||
onclick={() => {
|
||||
if (!selectedServices.some((s) => s.id === cs.id)) {
|
||||
|
||||
@@ -528,6 +528,7 @@
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span>Week starting: {week}</span>
|
||||
<button
|
||||
type="button"
|
||||
class="text-xs text-red-500 hover:text-red-700"
|
||||
onclick={() => removeWeek(index)}
|
||||
>
|
||||
|
||||
@@ -653,6 +653,7 @@
|
||||
<!-- Tabs -->
|
||||
<div class="flex gap-6 border-b border-gray-200">
|
||||
<button
|
||||
type="button"
|
||||
class="pb-2 text-sm font-medium transition-colors {userType === 'member'
|
||||
? 'border-b-2 border-primary text-primary'
|
||||
: 'text-gray-500 hover:text-gray-700'}"
|
||||
@@ -664,6 +665,7 @@
|
||||
Member
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="pb-2 text-sm font-medium transition-colors {userType === 'guest'
|
||||
? 'border-b-2 border-primary text-primary'
|
||||
: 'text-gray-500 hover:text-gray-700'}"
|
||||
@@ -877,6 +879,7 @@
|
||||
<div class="space-y-2">
|
||||
{#each customServices as cs (cs.id)}
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full items-center justify-between rounded-lg border px-3 py-2 text-sm hover:bg-gray-50"
|
||||
onclick={() => {
|
||||
if (!selectedServices.some((s) => s.id === cs.id)) {
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
<a
|
||||
href={`https://instagram.com/${instagram}`}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
rel="noopener noreferrer"
|
||||
class="text-primary hover:underline">@{instagram}</a
|
||||
>
|
||||
</div>
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
<a
|
||||
href="https://instagram.com"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
rel="noopener noreferrer"
|
||||
class="text-gray-600 hover:text-primary"
|
||||
aria-label="Instagram"
|
||||
>
|
||||
@@ -180,6 +180,7 @@
|
||||
<!-- Mobile: Burger -->
|
||||
<div class="flex items-center md:hidden">
|
||||
<button
|
||||
type="button"
|
||||
onclick={toggleMenu}
|
||||
class="relative h-6 w-6 focus:outline-none"
|
||||
aria-label="Toggle menu"
|
||||
|
||||
@@ -1303,6 +1303,7 @@
|
||||
<div class="desktop-tab-menu">
|
||||
<div class="flex rounded-lg border bg-gray-50 p-1">
|
||||
<button
|
||||
type="button"
|
||||
class="flex-1 rounded-md px-2 py-2 text-xs font-medium transition-colors {activeTab ===
|
||||
'general'
|
||||
? 'bg-white text-gray-900 shadow-sm'
|
||||
@@ -1323,6 +1324,7 @@
|
||||
</button>
|
||||
{#if authStore.currentUser?.role !== 'admin'}
|
||||
<button
|
||||
type="button"
|
||||
class="flex-1 rounded-md px-2 py-2 text-xs font-medium transition-colors {activeTab ===
|
||||
'history'
|
||||
? 'bg-white text-gray-900 shadow-sm'
|
||||
@@ -1346,6 +1348,7 @@
|
||||
{/if}
|
||||
{#if authStore.currentUser?.role !== 'admin'}
|
||||
<button
|
||||
type="button"
|
||||
class="flex-1 rounded-md px-2 py-2 text-xs font-medium transition-colors {activeTab ===
|
||||
'referral'
|
||||
? 'bg-white text-gray-900 shadow-sm'
|
||||
@@ -1369,6 +1372,7 @@
|
||||
{/if}
|
||||
{#if canSaveCards}
|
||||
<button
|
||||
type="button"
|
||||
class="flex-1 rounded-md px-2 py-2 text-xs font-medium transition-colors {activeTab ===
|
||||
'cards'
|
||||
? 'bg-white text-gray-900 shadow-sm'
|
||||
@@ -1394,6 +1398,7 @@
|
||||
</button>
|
||||
{/if}
|
||||
<button
|
||||
type="button"
|
||||
class="flex-1 rounded-md px-2 py-2 text-xs font-medium transition-colors {activeTab ===
|
||||
'admin'
|
||||
? 'bg-white text-gray-900 shadow-sm'
|
||||
@@ -2734,6 +2739,7 @@
|
||||
<div class="mobile-tab-menu">
|
||||
<div class="flex rounded-lg border bg-gray-50 p-1">
|
||||
<button
|
||||
type="button"
|
||||
class="flex-1 rounded-md px-2 py-2 text-xs font-medium transition-colors {activeTab ===
|
||||
'general'
|
||||
? 'bg-white text-gray-900 shadow-sm'
|
||||
@@ -2755,6 +2761,7 @@
|
||||
|
||||
{#if authStore.currentUser?.role !== 'admin'}
|
||||
<button
|
||||
type="button"
|
||||
class="flex-1 rounded-md px-2 py-2 text-xs font-medium transition-colors {activeTab ===
|
||||
'history'
|
||||
? 'bg-white text-gray-900 shadow-sm'
|
||||
@@ -2779,6 +2786,7 @@
|
||||
|
||||
{#if authStore.currentUser?.role !== 'admin'}
|
||||
<button
|
||||
type="button"
|
||||
class="flex-1 rounded-md px-2 py-2 text-xs font-medium transition-colors {activeTab ===
|
||||
'referral'
|
||||
? 'bg-white text-gray-900 shadow-sm'
|
||||
@@ -2803,6 +2811,7 @@
|
||||
|
||||
{#if canSaveCards}
|
||||
<button
|
||||
type="button"
|
||||
class="flex-1 rounded-md px-2 py-2 text-xs font-medium transition-colors {activeTab ===
|
||||
'cards'
|
||||
? 'bg-white text-gray-900 shadow-sm'
|
||||
@@ -2829,6 +2838,7 @@
|
||||
{/if}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="flex-1 rounded-md px-2 py-2 text-xs font-medium transition-colors {activeTab ===
|
||||
'admin'
|
||||
? 'bg-white text-gray-900 shadow-sm'
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
<!-- 2. Customers & Bookings -->
|
||||
<div class="rounded-lg border">
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full items-center justify-between bg-gray-50 px-4 py-3 transition-colors hover:bg-gray-100 {sectionState.customers ===
|
||||
'expanded'
|
||||
? 'rounded-t-lg'
|
||||
@@ -310,6 +311,7 @@
|
||||
<!-- 3. Services & Products -->
|
||||
<div class="rounded-lg border">
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full items-center justify-between bg-gray-50 px-4 py-3 transition-colors hover:bg-gray-100 {sectionState.services ===
|
||||
'expanded'
|
||||
? 'rounded-t-lg'
|
||||
@@ -354,6 +356,7 @@
|
||||
<!-- 4. Scheduling & Hours -->
|
||||
<div class="rounded-lg border">
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full items-center justify-between bg-gray-50 px-4 py-3 transition-colors hover:bg-gray-100 {sectionState.scheduling ===
|
||||
'expanded'
|
||||
? 'rounded-t-lg'
|
||||
@@ -384,6 +387,7 @@
|
||||
<!-- 5. Promotions & Finance -->
|
||||
<div class="rounded-lg border">
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full items-center justify-between bg-gray-50 px-4 py-3 transition-colors hover:bg-gray-100 {sectionState.promotions ===
|
||||
'expanded'
|
||||
? 'rounded-t-lg'
|
||||
@@ -413,6 +417,7 @@
|
||||
<!-- 6. Business Settings -->
|
||||
<div class="rounded-lg border">
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full items-center justify-between bg-gray-50 px-4 py-3 transition-colors hover:bg-gray-100 {sectionState.settings ===
|
||||
'expanded'
|
||||
? 'rounded-t-lg'
|
||||
|
||||
@@ -542,6 +542,7 @@
|
||||
<div class="mb-6 flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center">
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
onclick={handleGotoAccount}
|
||||
class="mb-2 inline-flex items-center gap-1 text-sm text-gray-500 hover:text-gray-900"
|
||||
>
|
||||
|
||||
@@ -727,6 +727,7 @@
|
||||
{@const hasActiveFilters = Object.values(selectedFilters).some((v) => v && v !== '')}
|
||||
{#if hasActiveFilters || selectedTag || selectedTags.length > 0}
|
||||
<button
|
||||
type="button"
|
||||
class="filter-dropdown flex shrink-0 items-center justify-center"
|
||||
onclick={clearFilters}
|
||||
aria-label="Clear all filters"
|
||||
@@ -750,6 +751,7 @@
|
||||
{@const hasActiveFilter = selectedValue && selectedValue !== ''}
|
||||
<div class="filter-dropdown relative shrink-0">
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center gap-1 rounded-lg border border-gray-300 bg-white px-3 py-1.5 text-sm transition-colors hover:bg-gray-50 {hasActiveFilter
|
||||
? 'border-primary bg-primary/10'
|
||||
: ''}"
|
||||
@@ -781,6 +783,7 @@
|
||||
style="position: fixed; left: {dropdownPosition?.x}px; top: {dropdownPosition?.y}px;"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="filter-dropdown flex w-full items-center justify-between px-3 py-2 text-left text-sm transition-colors hover:bg-fuchsia-50 {selectedValue ===
|
||||
''
|
||||
? 'bg-fuchsia-100 font-medium'
|
||||
@@ -794,6 +797,7 @@
|
||||
</button>
|
||||
{#each filter.values as value (value.value)}
|
||||
<button
|
||||
type="button"
|
||||
class="filter-dropdown flex w-full items-center justify-between px-3 py-2 text-left text-sm transition-colors hover:bg-fuchsia-50 {selectedValue ===
|
||||
value.value
|
||||
? 'bg-fuchsia-100 font-medium'
|
||||
@@ -827,6 +831,7 @@
|
||||
|
||||
{#if selectedTag || selectedTags.length > 0}
|
||||
<button
|
||||
type="button"
|
||||
class="flex shrink-0 items-center justify-center"
|
||||
onclick={clearSearch}
|
||||
aria-label="Clear search tags"
|
||||
@@ -852,6 +857,7 @@
|
||||
<div class="grid grid-cols-3 gap-1 p-2 pt-0 sm:grid-cols-4 md:grid-cols-6 lg:grid-cols-8">
|
||||
{#each images as img (img.id)}
|
||||
<button
|
||||
type="button"
|
||||
class="relative aspect-square overflow-hidden rounded-xs transition-opacity hover:opacity-90 active:opacity-75"
|
||||
onclick={() => openModal(img)}
|
||||
aria-label="View full size portfolio item"
|
||||
@@ -970,6 +976,7 @@
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
bind:this={closeBtnRef}
|
||||
class="absolute right-2 z-[60] rounded-full bg-black/50 p-2 text-white transition-colors hover:bg-black/70 sm:right-4"
|
||||
onclick={closeModal}
|
||||
@@ -987,6 +994,7 @@
|
||||
|
||||
{#if currentIndex > 0}
|
||||
<button
|
||||
type="button"
|
||||
bind:this={prevButtonRef}
|
||||
class="absolute top-1/2 left-2 z-[60] -translate-y-1/2 rounded-full bg-black/50 p-3 text-white transition-colors hover:bg-black/70 sm:left-4"
|
||||
onclick={navigatePrev}
|
||||
@@ -1005,6 +1013,7 @@
|
||||
|
||||
{#if currentIndex < images.length - 1}
|
||||
<button
|
||||
type="button"
|
||||
bind:this={nextButtonRef}
|
||||
class="absolute top-1/2 right-2 z-[60] -translate-y-1/2 rounded-full bg-black/50 p-3 text-white transition-colors hover:bg-black/70 sm:right-4"
|
||||
onclick={navigateNext}
|
||||
|
||||
@@ -369,6 +369,7 @@
|
||||
{/if}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => openBooking(booking.id)}
|
||||
class="inline-flex items-center gap-1 rounded-lg px-2.5 py-1.5 text-sm font-medium text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-900"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user