[READ-ONLY] a fast, modern browser for the npm registry

refactor: remove class shortcuts (#604)

Co-authored-by: Daniel Roe <daniel@roe.dev>

authored by

Marcus Blättermann
Daniel Roe
and committed by
GitHub
db829ccb 8b065441

+145 -145
+23
app/components/BaseCard.vue
··· 1 + <script setup lang="ts"> 2 + defineProps<{ 3 + /** Whether this is an exact match for the query */ 4 + isExactMatch?: boolean 5 + }>() 6 + </script> 7 + 8 + <template> 9 + <article 10 + class="group bg-bg-subtle border border-border rounded-lg p-4 sm:p-6 transition-[border-color,background-color] duration-200 hover:(border-border-hover bg-bg-muted) cursor-pointer relative focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-bg focus-within:ring-offset-2 focus-within:ring-fg/50 focus-within:bg-bg-muted focus-within:border-border-hover" 11 + :class="{ 12 + 'border-accent/30 contrast-more:border-accent/90 bg-accent/5': isExactMatch, 13 + }" 14 + > 15 + <!-- Glow effect for exact matches --> 16 + <div 17 + v-if="isExactMatch" 18 + class="absolute -inset-px rounded-lg bg-gradient-to-r from-accent/0 via-accent/20 to-accent/0 opacity-100 blur-sm -z-1 pointer-events-none motion-reduce:opacity-50" 19 + aria-hidden="true" 20 + /> 21 + <slot /> 22 + </article> 23 + </template>
+1 -1
app/components/Filter/Panel.vue
··· 228 228 :value="filters.text" 229 229 :placeholder="searchPlaceholder" 230 230 autocomplete="off" 231 - class="input-base" 231 + class="w-full bg-bg-subtle border border-border rounded-md px-4 py-3 font-mono text-sm text-fg placeholder:text-fg-subtle transition-all duration-200 focus:(border-fg/40 outline-none ring-1 ring-fg/10)" 232 232 @input="handleTextInput" 233 233 /> 234 234 </div>
+2 -13
app/components/Package/Card.vue
··· 29 29 </script> 30 30 31 31 <template> 32 - <article 33 - class="group card-interactive scroll-mt-48 scroll-mb-6 relative focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-bg focus-within:ring-offset-2 focus-within:ring-fg/50 focus-within:bg-bg-muted focus-within:border-border-hover" 34 - :class="{ 35 - 'border-accent/30 contrast-more:border-accent/90 bg-accent/5': isExactMatch, 36 - }" 37 - > 38 - <!-- Glow effect for exact matches --> 39 - <div 40 - v-if="isExactMatch" 41 - class="absolute -inset-px rounded-lg bg-gradient-to-r from-accent/0 via-accent/20 to-accent/0 opacity-100 blur-sm -z-1 pointer-events-none motion-reduce:opacity-50" 42 - aria-hidden="true" 43 - /> 32 + <BaseCard :isExactMatch="isExactMatch"> 44 33 <div class="mb-2 flex items-baseline justify-start gap-2"> 45 34 <component 46 35 :is="headingLevel ?? 'h3'" ··· 169 158 {{ keyword }} 170 159 </li> 171 160 </ul> 172 - </article> 161 + </BaseCard> 173 162 </template>
+56 -54
app/components/Package/Skeleton.vue
··· 12 12 <div class="flex-1 min-w-0"> 13 13 <!-- Package name: h1 font-mono text-2xl sm:text-3xl font-medium mb-2 --> 14 14 <h1 class="font-mono text-2xl sm:text-3xl font-medium mb-2"> 15 - <span class="skeleton inline-block h-9 w-48" /> 15 + <SkeletonInline class="h-9 w-48" /> 16 16 </h1> 17 17 <!-- Description: fixed height container matching min-h-[4.5rem] (72px) to prevent CLS --> 18 18 <div class="relative max-w-2xl min-h-[4.5rem]"> 19 19 <div class="space-y-2"> 20 - <span class="skeleton block h-5 w-full" /> 21 - <span class="skeleton block h-5 w-4/5" /> 22 - <span class="skeleton block h-5 w-3/5" /> 20 + <SkeletonBlock class="h-5 w-full" /> 21 + <SkeletonBlock class="h-5 w-4/5" /> 22 + <SkeletonBlock class="h-5 w-3/5" /> 23 23 </div> 24 24 </div> 25 25 </div> 26 26 27 27 <!-- Version badge: shrink-0 px-3 py-1 font-mono text-sm bg-bg-muted border border-border rounded-md --> 28 - <span class="skeleton shrink-0 h-8 w-20 rounded-md" /> 28 + <SkeletonInline class="shrink-0 h-8 w-20 rounded-md" /> 29 29 </div> 30 30 31 31 <!-- Stats grid: grid grid-cols-2 sm:grid-cols-3 md:grid-cols-6 gap-4 mt-6 --> ··· 36 36 {{ $t('package.skeleton.license') }} 37 37 </dt> 38 38 <dd class="font-mono text-sm"> 39 - <span class="skeleton inline-block h-5 w-12" /> 39 + <SkeletonInline class="h-5 w-12" /> 40 40 </dd> 41 41 </div> 42 42 ··· 46 46 {{ $t('package.skeleton.weekly') }} 47 47 </dt> 48 48 <dd class="font-mono text-sm"> 49 - <span class="skeleton inline-block h-5 w-20" /> 49 + <SkeletonInline class="h-5 w-20" /> 50 50 </dd> 51 51 </div> 52 52 ··· 56 56 {{ $t('package.skeleton.size') }} 57 57 </dt> 58 58 <dd class="font-mono text-sm"> 59 - <span class="skeleton inline-block h-5 w-16" /> 59 + <SkeletonInline class="h-5 w-16" /> 60 60 </dd> 61 61 </div> 62 62 ··· 66 66 {{ $t('package.skeleton.deps') }} 67 67 </dt> 68 68 <dd class="font-mono text-sm"> 69 - <span class="skeleton inline-block h-5 w-8" /> 69 + <SkeletonInline class="h-5 w-8" /> 70 70 </dd> 71 71 </div> 72 72 ··· 76 76 {{ $t('package.skeleton.published') }} 77 77 </dt> 78 78 <dd class="font-mono text-sm"> 79 - <span class="skeleton inline-block h-5 w-28" /> 79 + <SkeletonInline class="h-5 w-28" /> 80 80 </dd> 81 81 </div> 82 82 </dl> ··· 85 85 <nav aria-label="Package links" class="mt-6"> 86 86 <ul class="flex flex-wrap items-center gap-4 list-none m-0 p-0"> 87 87 <li> 88 - <span class="skeleton inline-block h-5 w-14" /> 88 + <SkeletonInline class="h-5 w-14" /> 89 89 </li> 90 90 <li> 91 - <span class="skeleton inline-block h-5 w-20" /> 91 + <SkeletonInline class="h-5 w-20" /> 92 92 </li> 93 93 <li> 94 - <span class="skeleton inline-block h-5 w-16" /> 94 + <SkeletonInline class="h-5 w-16" /> 95 95 </li> 96 96 <li> 97 - <span class="skeleton inline-block h-5 w-12" /> 97 + <SkeletonInline class="h-5 w-12" /> 98 98 </li> 99 99 </ul> 100 100 </nav> ··· 110 110 </h2> 111 111 <!-- code-block with relative positioning for copy button --> 112 112 <div class="relative"> 113 - <div class="code-block pe-16"> 114 - <span class="skeleton inline-block h-5 w-52" /> 113 + <div 114 + class="bg-bg-muted border border-border rounded-md p-4 font-mono text-sm overflow-x-auto pe-16" 115 + > 116 + <SkeletonInline class="h-5 w-52" /> 115 117 </div> 116 - <span class="skeleton absolute top-3 inset-ie-3 h-6 w-12 rounded" /> 118 + <SkeletonInline class="absolute top-3 inset-ie-3 h-6 w-12 rounded" /> 117 119 </div> 118 120 </section> 119 121 ··· 131 133 <!-- Simulated README content --> 132 134 <div class="space-y-4"> 133 135 <!-- Heading --> 134 - <span class="skeleton block h-7 w-2/3" /> 136 + <SkeletonBlock class="h-7 w-2/3" /> 135 137 <!-- Paragraphs --> 136 - <span class="skeleton block h-4 w-full" /> 137 - <span class="skeleton block h-4 w-full" /> 138 - <span class="skeleton block h-4 w-4/5" /> 138 + <SkeletonBlock class="h-4 w-full" /> 139 + <SkeletonBlock class="h-4 w-full" /> 140 + <SkeletonBlock class="h-4 w-4/5" /> 139 141 <!-- Gap for section break --> 140 - <span class="skeleton block h-6 w-1/2 mt-6" /> 141 - <span class="skeleton block h-4 w-full" /> 142 - <span class="skeleton block h-4 w-full" /> 143 - <span class="skeleton block h-4 w-3/4" /> 142 + <SkeletonBlock class="h-6 w-1/2 mt-6" /> 143 + <SkeletonBlock class="h-4 w-full" /> 144 + <SkeletonBlock class="h-4 w-full" /> 145 + <SkeletonBlock class="h-4 w-3/4" /> 144 146 <!-- Code block placeholder --> 145 - <div class="skeleton h-24 w-full rounded-lg mt-4" /> 146 - <span class="skeleton block h-4 w-full" /> 147 - <span class="skeleton block h-4 w-5/6" /> 147 + <SkeletonBlock class="h-24 w-full rounded-lg mt-4" /> 148 + <SkeletonBlock class="h-4 w-full" /> 149 + <SkeletonBlock class="h-4 w-5/6" /> 148 150 </div> 149 151 </section> 150 152 </div> ··· 161 163 </h2> 162 164 <ul class="space-y-2 list-none m-0 p-0"> 163 165 <li> 164 - <span class="skeleton inline-block h-5 w-28" /> 166 + <SkeletonInline class="h-5 w-28" /> 165 167 </li> 166 168 <li> 167 - <span class="skeleton inline-block h-5 w-24" /> 169 + <SkeletonInline class="h-5 w-24" /> 168 170 </li> 169 171 </ul> 170 172 </section> ··· 179 181 </h2> 180 182 <!-- flex flex-wrap gap-1.5 --> 181 183 <ul class="flex flex-wrap gap-1.5 list-none m-0 p-0"> 182 - <li><span class="skeleton inline-block h-6 w-16 rounded" /></li> 183 - <li><span class="skeleton inline-block h-6 w-12 rounded" /></li> 184 - <li><span class="skeleton inline-block h-6 w-20 rounded" /></li> 185 - <li><span class="skeleton inline-block h-6 w-14 rounded" /></li> 186 - <li><span class="skeleton inline-block h-6 w-18 rounded" /></li> 187 - <li><span class="skeleton inline-block h-6 w-10 rounded" /></li> 184 + <li><SkeletonInline class="h-6 w-16 rounded" /></li> 185 + <li><SkeletonInline class="h-6 w-12 rounded" /></li> 186 + <li><SkeletonInline class="h-6 w-20 rounded" /></li> 187 + <li><SkeletonInline class="h-6 w-14 rounded" /></li> 188 + <li><SkeletonInline class="h-6 w-18 rounded" /></li> 189 + <li><SkeletonInline class="h-6 w-10 rounded" /></li> 188 190 </ul> 189 191 </section> 190 192 ··· 199 201 <!-- space-y-1, each row: flex items-center justify-between py-1.5 text-sm --> 200 202 <div class="space-y-1"> 201 203 <div class="flex items-center justify-between py-1.5 text-sm"> 202 - <span class="skeleton inline-block h-4 w-16" /> 203 - <span class="skeleton inline-block h-4 w-24" /> 204 + <SkeletonInline class="h-4 w-16" /> 205 + <SkeletonInline class="h-4 w-24" /> 204 206 </div> 205 207 <div class="flex items-center justify-between py-1.5 text-sm"> 206 - <span class="skeleton inline-block h-4 w-14" /> 207 - <span class="skeleton inline-block h-4 w-24" /> 208 + <SkeletonInline class="h-4 w-14" /> 209 + <SkeletonInline class="h-4 w-24" /> 208 210 </div> 209 211 <div class="flex items-center justify-between py-1.5 text-sm"> 210 - <span class="skeleton inline-block h-4 w-18" /> 211 - <span class="skeleton inline-block h-4 w-24" /> 212 + <SkeletonInline class="h-4 w-18" /> 213 + <SkeletonInline class="h-4 w-24" /> 212 214 </div> 213 215 <div class="flex items-center justify-between py-1.5 text-sm"> 214 - <span class="skeleton inline-block h-4 w-14" /> 215 - <span class="skeleton inline-block h-4 w-24" /> 216 + <SkeletonInline class="h-4 w-14" /> 217 + <SkeletonInline class="h-4 w-24" /> 216 218 </div> 217 219 <div class="flex items-center justify-between py-1.5 text-sm"> 218 - <span class="skeleton inline-block h-4 w-16" /> 219 - <span class="skeleton inline-block h-4 w-24" /> 220 + <SkeletonInline class="h-4 w-16" /> 221 + <SkeletonInline class="h-4 w-24" /> 220 222 </div> 221 223 </div> 222 224 </section> ··· 232 234 <!-- space-y-1, each: flex items-center justify-between py-1 text-sm --> 233 235 <ul class="space-y-1 list-none m-0 p-0"> 234 236 <li class="flex items-center justify-between py-1 text-sm"> 235 - <span class="skeleton inline-block h-4 w-24" /> 236 - <span class="skeleton inline-block h-4 w-12" /> 237 + <SkeletonInline class="h-4 w-24" /> 238 + <SkeletonInline class="h-4 w-12" /> 237 239 </li> 238 240 <li class="flex items-center justify-between py-1 text-sm"> 239 - <span class="skeleton inline-block h-4 w-32" /> 240 - <span class="skeleton inline-block h-4 w-10" /> 241 + <SkeletonInline class="h-4 w-32" /> 242 + <SkeletonInline class="h-4 w-10" /> 241 243 </li> 242 244 <li class="flex items-center justify-between py-1 text-sm"> 243 - <span class="skeleton inline-block h-4 w-20" /> 244 - <span class="skeleton inline-block h-4 w-14" /> 245 + <SkeletonInline class="h-4 w-20" /> 246 + <SkeletonInline class="h-4 w-14" /> 245 247 </li> 246 248 <li class="flex items-center justify-between py-1 text-sm"> 247 - <span class="skeleton inline-block h-4 w-28" /> 248 - <span class="skeleton inline-block h-4 w-12" /> 249 + <SkeletonInline class="h-4 w-28" /> 250 + <SkeletonInline class="h-4 w-12" /> 249 251 </li> 250 252 </ul> 251 253 </section>
+4 -4
app/components/Package/WeeklyDownloadStats.vue
··· 221 221 <div class="min-h-[75.195px]"> 222 222 <!-- Title row: date range (24px height) --> 223 223 <div class="h-6 flex items-center ps-3"> 224 - <span class="skeleton h-3 w-36" /> 224 + <SkeletonInline class="h-3 w-36" /> 225 225 </div> 226 226 <!-- Chart area: data label left, sparkline right --> 227 227 <div class="aspect-[500/80] flex items-center"> 228 228 <!-- Data label (covers ~42% width) --> 229 229 <div class="w-[42%] flex items-center ps-0.5"> 230 - <span class="skeleton h-7 w-24" /> 230 + <SkeletonInline class="h-7 w-24" /> 231 231 </div> 232 232 <!-- Sparkline area (~58% width) --> 233 233 <div class="flex-1 flex items-end gap-0.5 h-4/5 pe-3"> 234 - <span 234 + <SkeletonInline 235 235 v-for="i in 16" 236 236 :key="i" 237 - class="skeleton flex-1 rounded-sm" 237 + class="flex-1 rounded-sm" 238 238 :style="{ height: `${25 + ((i * 7) % 50)}%` }" 239 239 /> 240 240 </div>
+2 -13
app/components/SearchSuggestionCard.vue
··· 12 12 </script> 13 13 14 14 <template> 15 - <article 16 - class="group card-interactive relative focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-bg focus-within:ring-offset-2 focus-within:ring-fg/50 focus-within:bg-bg-muted focus-within:border-border-hover" 17 - :class="{ 18 - 'border-accent/30 contrast-more:border-accent/90 bg-accent/5': isExactMatch, 19 - }" 20 - > 21 - <!-- Glow effect for exact matches --> 22 - <div 23 - v-if="isExactMatch" 24 - class="absolute -inset-px rounded-lg bg-gradient-to-r from-accent/0 via-accent/20 to-accent/0 opacity-100 blur-sm -z-1 pointer-events-none motion-reduce:opacity-50" 25 - aria-hidden="true" 26 - /> 15 + <BaseCard :isExactMatch="isExactMatch"> 27 16 <NuxtLink 28 17 :to="type === 'user' ? `/~${name}` : `/@${name}`" 29 18 :data-suggestion-index="index" ··· 72 61 aria-hidden="true" 73 62 /> 74 63 </NuxtLink> 75 - </article> 64 + </BaseCard> 76 65 </template>
+1 -1
app/components/Settings/Toggle.server.vue
··· 10 10 <span v-if="label" class="text-sm text-fg font-medium text-start"> 11 11 {{ label }} 12 12 </span> 13 - <span class="skeleton block h-6 w-11 shrink-0 rounded-full" /> 13 + <SkeletonBlock class="h-6 w-11 shrink-0 rounded-full" /> 14 14 </div> 15 15 <p v-if="description" class="text-sm text-fg-muted"> 16 16 {{ description }}
+3
app/components/SkeletonBlock.vue
··· 1 + <template> 2 + <div class="bg-bg-elevated rounded animate-skeleton-pulse" /> 3 + </template>
+3
app/components/SkeletonInline.vue
··· 1 + <template> 2 + <span class="inline-block bg-bg-elevated rounded animate-skeleton-pulse" /> 3 + </template>
+18 -18
app/pages/package-code/[...path].vue
··· 493 493 <!-- Fake line numbers column --> 494 494 <div class="shrink-0 bg-bg-subtle border-ie border-border w-14 py-0"> 495 495 <div v-for="n in 20" :key="n" class="px-3 h-6 flex items-center justify-end"> 496 - <span class="skeleton w-4 h-3 rounded-sm" /> 496 + <SkeletonInline class="w-4 h-3 rounded-sm" /> 497 497 </div> 498 498 </div> 499 499 <!-- Fake code content --> 500 500 <div class="flex-1 p-4 space-y-1.5"> 501 - <div class="skeleton h-4 w-32 rounded-sm" /> 502 - <div class="skeleton h-4 w-48 rounded-sm" /> 503 - <div class="skeleton h-4 w-24 rounded-sm" /> 501 + <SkeletonBlock class="h-4 w-32 rounded-sm" /> 502 + <SkeletonBlock class="h-4 w-48 rounded-sm" /> 503 + <SkeletonBlock class="h-4 w-24 rounded-sm" /> 504 504 <div class="h-4" /> 505 - <div class="skeleton h-4 w-64 rounded-sm" /> 506 - <div class="skeleton h-4 w-56 rounded-sm" /> 507 - <div class="skeleton h-4 w-40 rounded-sm" /> 508 - <div class="skeleton h-4 w-72 rounded-sm" /> 505 + <SkeletonBlock class="h-4 w-64 rounded-sm" /> 506 + <SkeletonBlock class="h-4 w-56 rounded-sm" /> 507 + <SkeletonBlock class="h-4 w-40 rounded-sm" /> 508 + <SkeletonBlock class="h-4 w-72 rounded-sm" /> 509 509 <div class="h-4" /> 510 - <div class="skeleton h-4 w-36 rounded-sm" /> 511 - <div class="skeleton h-4 w-52 rounded-sm" /> 512 - <div class="skeleton h-4 w-44 rounded-sm" /> 513 - <div class="skeleton h-4 w-28 rounded-sm" /> 510 + <SkeletonBlock class="h-4 w-36 rounded-sm" /> 511 + <SkeletonBlock class="h-4 w-52 rounded-sm" /> 512 + <SkeletonBlock class="h-4 w-44 rounded-sm" /> 513 + <SkeletonBlock class="h-4 w-28 rounded-sm" /> 514 514 <div class="h-4" /> 515 - <div class="skeleton h-4 w-60 rounded-sm" /> 516 - <div class="skeleton h-4 w-48 rounded-sm" /> 517 - <div class="skeleton h-4 w-32 rounded-sm" /> 518 - <div class="skeleton h-4 w-56 rounded-sm" /> 519 - <div class="skeleton h-4 w-40 rounded-sm" /> 520 - <div class="skeleton h-4 w-24 rounded-sm" /> 515 + <SkeletonBlock class="h-4 w-60 rounded-sm" /> 516 + <SkeletonBlock class="h-4 w-48 rounded-sm" /> 517 + <SkeletonBlock class="h-4 w-32 rounded-sm" /> 518 + <SkeletonBlock class="h-4 w-56 rounded-sm" /> 519 + <SkeletonBlock class="h-4 w-40 rounded-sm" /> 520 + <SkeletonBlock class="h-4 w-24 rounded-sm" /> 521 521 </div> 522 522 </div> 523 523
+4 -4
app/pages/package-docs/[...path].vue
··· 166 166 <!-- Main content --> 167 167 <main class="flex-1 min-w-0"> 168 168 <div v-if="showLoading" class="p-6 sm:p-8 lg:p-12 space-y-4"> 169 - <div class="skeleton h-8 w-64 rounded" /> 170 - <div class="skeleton h-4 w-full max-w-2xl rounded" /> 171 - <div class="skeleton h-4 w-5/6 max-w-2xl rounded" /> 172 - <div class="skeleton h-4 w-3/4 max-w-2xl rounded" /> 169 + <SkeletonBlock class="h-8 w-64 rounded" /> 170 + <SkeletonBlock class="h-4 w-full max-w-2xl rounded" /> 171 + <SkeletonBlock class="h-4 w-5/6 max-w-2xl rounded" /> 172 + <SkeletonBlock class="h-4 w-3/4 max-w-2xl rounded" /> 173 173 </div> 174 174 175 175 <div v-else-if="showEmptyState" class="p-6 sm:p-8 lg:p-12">
+4 -4
app/pages/package/[...package].vue
··· 476 476 class="self-baseline ms-1 sm:ms-2" 477 477 /> 478 478 <template #fallback> 479 - <ul class="flex items-center gap-1.5 self-baseline ms-1 sm:ms-2"> 480 - <li class="skeleton w-8 h-5 rounded" /> 481 - <li class="skeleton w-12 h-5 rounded" /> 482 - </ul> 479 + <div class="flex items-center gap-1.5 self-baseline ms-1 sm:ms-2"> 480 + <SkeletonBlock class="w-8 h-5 rounded" /> 481 + <SkeletonBlock class="w-12 h-5 rounded" /> 482 + </div> 483 483 </template> 484 484 </ClientOnly> 485 485
+2 -5
app/pages/~[username]/orgs.vue
··· 208 208 > 209 209 {{ org.role }} 210 210 </span> 211 - <span 212 - v-else-if="org.isLoadingDetails" 213 - class="skeleton inline-block mt-1 h-5 w-16 rounded" 214 - /> 211 + <SkeletonInline v-else-if="org.isLoadingDetails" class="mt-1 h-5 w-16 rounded" /> 215 212 </div> 216 213 </div> 217 214 ··· 228 225 ) 229 226 }} 230 227 </span> 231 - <span v-else-if="org.isLoadingDetails" class="skeleton inline-block h-4 w-20" /> 228 + <SkeletonInline v-else-if="org.isLoadingDetails" class="h-4 w-20" /> 232 229 <span v-else class="text-fg-subtle">—</span> 233 230 </div> 234 231 </div>
+20
test/nuxt/a11y.spec.ts
··· 57 57 import { 58 58 AppFooter, 59 59 AppHeader, 60 + BaseCard, 60 61 UserAvatar, 61 62 BuildEnvironment, 62 63 CallToAction, ··· 207 208 describe('AppFooter', () => { 208 209 it('should have no accessibility violations', async () => { 209 210 const component = await mountSuspended(AppFooter) 211 + const results = await runAxe(component) 212 + expect(results.violations).toEqual([]) 213 + }) 214 + }) 215 + 216 + describe('BaseCard', () => { 217 + it('should have no accessibility violations', async () => { 218 + const component = await mountSuspended(BaseCard, { 219 + slots: { default: '<p>Card content</p>' }, 220 + }) 221 + const results = await runAxe(component) 222 + expect(results.violations).toEqual([]) 223 + }) 224 + 225 + it('should have no accessibility violations with exact match highlight', async () => { 226 + const component = await mountSuspended(BaseCard, { 227 + props: { isExactMatch: true }, 228 + slots: { default: '<p>Exact match content</p>' }, 229 + }) 210 230 const results = await runAxe(component) 211 231 expect(results.violations).toEqual([]) 212 232 })
+2
test/unit/a11y-component-coverage.spec.ts
··· 41 41 'Package/WeeklyDownloadStats.vue': 42 42 'Uses vue-data-ui VueUiSparkline - has DOM measurement issues in test environment', 43 43 'UserCombobox.vue': 'Unused component - intended for future admin features', 44 + 'SkeletonBlock.vue': 'Already covered indirectly via other component tests', 45 + 'SkeletonInline.vue': 'Already covered indirectly via other component tests', 44 46 } 45 47 46 48 /**
-28
uno.config.ts
··· 142 142 ], 143 143 ['link-subtle', 'text-fg-muted hover:text-fg transition-colors duration-200 focus-ring'], 144 144 145 - // Cards 146 - [ 147 - 'card', 148 - 'bg-bg-subtle border border-border rounded-lg p-4 sm:p-6 transition-[border-color,background-color] duration-200', 149 - ], 150 - ['card-interactive', 'card hover:(border-border-hover bg-bg-muted) cursor-pointer'], 151 - 152 - // Form elements 153 - [ 154 - 'input-base', 155 - 'w-full bg-bg-subtle border border-border rounded-md px-4 py-3 font-mono text-sm text-fg placeholder:text-fg-subtle transition-all duration-200 focus:(border-fg/40 outline-none ring-1 ring-fg/10)', 156 - ], 157 - 158 145 // Tags/badges 159 146 [ 160 147 'tag', ··· 169 156 ['badge-purple', 'bg-badge-purple/10 text-badge-purple'], 170 157 ['badge-pink', 'bg-badge-pink/10 text-badge-pink'], 171 158 ['badge-subtle', 'bg-bg-subtle text-fg-subtle'], 172 - 173 - // Code blocks 174 - [ 175 - 'code-block', 176 - 'bg-bg-muted border border-border rounded-md p-4 font-mono text-sm overflow-x-auto', 177 - ], 178 - 179 - // Skeleton loading 180 - ['skeleton', 'bg-bg-elevated rounded animate-skeleton-pulse'], 181 - 182 - // Subtle divider 183 - ['divider', 'border-t border-border'], 184 - 185 - // Section spacing 186 - ['section', 'py-8 sm:py-12'], 187 159 ], 188 160 rules: [ 189 161 // Custom scale for active states