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

Configure Feed

Select the types of activity you want to include in your feed.

feat: update all sidebar sections to be collapsible sections (#639)

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

authored by

Garth de Wet
Daniel Roe
and committed by
GitHub
61c5b853 792223c0

+121 -103
+2
app/components/CollapsibleSection.vue
··· 6 6 isLoading?: boolean 7 7 headingLevel?: `h${number}` 8 8 id: string 9 + icon?: string 9 10 } 10 11 11 12 const props = withDefaults(defineProps<Props>(), { ··· 107 108 :href="`#${id}`" 108 109 class="inline-flex items-center gap-1.5 text-fg-subtle hover:text-fg-muted transition-colors duration-200 no-underline" 109 110 > 111 + <span v-if="icon" :class="icon" aria-hidden="true" /> 110 112 {{ title }} 111 113 <span 112 114 class="i-carbon:link w-3 h-3 opacity-0 group-hover:opacity-100 transition-opacity duration-200"
+30
app/components/Package/Compatibility.vue
··· 1 + <script setup lang="ts"> 2 + const props = defineProps<{ 3 + engines?: { 4 + node?: string 5 + npm?: string 6 + } 7 + }>() 8 + </script> 9 + <template> 10 + <CollapsibleSection 11 + v-if="engines && (engines.node || engines.npm)" 12 + :title="$t('package.compatibility')" 13 + id="compatibility" 14 + > 15 + <dl class="space-y-2"> 16 + <div v-if="engines.node" class="flex justify-between gap-4 py-1"> 17 + <dt class="text-fg-muted text-sm shrink-0">node</dt> 18 + <dd class="font-mono text-sm text-fg text-end" :title="engines.node"> 19 + {{ engines.node }} 20 + </dd> 21 + </div> 22 + <div v-if="engines.npm" class="flex justify-between gap-4 py-1"> 23 + <dt class="text-fg-muted text-sm shrink-0">npm</dt> 24 + <dd class="font-mono text-sm text-fg text-end" :title="engines.npm"> 25 + {{ engines.npm }} 26 + </dd> 27 + </div> 28 + </dl> 29 + </CollapsibleSection> 30 + </template>
+6 -10
app/components/Package/InstallScripts.vue
··· 18 18 </script> 19 19 20 20 <template> 21 - <section> 22 - <h2 23 - id="install-scripts-heading" 24 - class="text-xs text-fg-subtle uppercase tracking-wider mb-3 flex items-center gap-2" 25 - > 26 - <span class="i-carbon:warning-alt w-3 h-3 text-yellow-500" aria-hidden="true" /> 27 - {{ $t('package.install_scripts.title') }} 28 - </h2> 29 - 21 + <CollapsibleSection 22 + :title="$t('package.install_scripts.title')" 23 + id="installScripts" 24 + icon="i-carbon:warning-alt w-3 h-3 text-yellow-500" 25 + > 30 26 <!-- Script list: name as label, content below --> 31 27 <dl class="space-y-2 m-0"> 32 28 <div v-for="scriptName in installScripts.scripts" :key="scriptName"> ··· 112 108 </li> 113 109 </ul> 114 110 </div> 115 - </section> 111 + </CollapsibleSection> 116 112 </template>
+18
app/components/Package/Keywords.vue
··· 1 + <script setup lang="ts"> 2 + import { NuxtLink } from '#components' 3 + 4 + defineProps<{ 5 + keywords?: string[] 6 + }>() 7 + </script> 8 + <template> 9 + <CollapsibleSection v-if="keywords?.length" :title="$t('package.keywords_title')" id="keywords"> 10 + <ul class="flex flex-wrap gap-1.5 list-none m-0 p-0"> 11 + <li v-for="keyword in keywords.slice(0, 15)" :key="keyword"> 12 + <TagClickable :as="NuxtLink" :to="{ name: 'search', query: { q: `keywords:${keyword}` } }"> 13 + {{ keyword }} 14 + </TagClickable> 15 + </li> 16 + </ul> 17 + </CollapsibleSection> 18 + </template>
+6 -14
app/components/Package/Maintainers.vue
··· 168 168 </script> 169 169 170 170 <template> 171 - <section id="maintainers" v-if="maintainers?.length" class="scroll-mt-20"> 172 - <h2 id="maintainers-heading" class="group text-xs text-fg-subtle uppercase tracking-wider mb-3"> 173 - <a 174 - href="#maintainers" 175 - class="inline-flex items-center gap-1.5 text-fg-subtle hover:text-fg-muted transition-colors duration-200 no-underline" 176 - > 177 - {{ $t('package.maintainers.title') }} 178 - <span 179 - class="i-carbon-link w-3 h-3 opacity-0 group-hover:opacity-100 transition-opacity duration-200" 180 - aria-hidden="true" 181 - /> 182 - </a> 183 - </h2> 171 + <CollapsibleSection 172 + v-if="maintainers?.length" 173 + id="maintainers" 174 + :title="$t('package.maintainers.title')" 175 + > 184 176 <ul class="space-y-2 list-none m-0 p-0" :aria-label="$t('package.maintainers.list_label')"> 185 177 <li 186 178 v-for="maintainer in visibleMaintainers" ··· 293 285 {{ $t('package.maintainers.add_owner') }} 294 286 </button> 295 287 </div> 296 - </section> 288 + </CollapsibleSection> 297 289 </template>
+2 -5
app/components/Package/SkillsCard.vue
··· 11 11 </script> 12 12 13 13 <template> 14 - <section v-if="skills.length" id="skills" class="scroll-mt-20"> 15 - <h2 class="text-xs text-fg-subtle uppercase tracking-wider mb-3"> 16 - {{ $t('package.skills.title') }} 17 - </h2> 14 + <CollapsibleSection v-if="skills.length" :title="$t('package.skills.title')" id="skills"> 18 15 <button 19 16 type="button" 20 17 class="w-full flex items-center gap-2 px-3 py-2 text-sm font-mono bg-bg-muted border border-border rounded-md hover:border-border-hover hover:bg-bg-elevated focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-hover transition-colors duration-200" ··· 25 22 $t('package.skills.skills_available', { count: skills.length }, skills.length) 26 23 }}</span> 27 24 </button> 28 - </section> 25 + </CollapsibleSection> 29 26 </template>
+2 -64
app/pages/package/[...package].vue
··· 1005 1005 </ClientOnly> 1006 1006 1007 1007 <!-- Keywords --> 1008 - <section id="keywords" v-if="displayVersion?.keywords?.length" class="scroll-mt-20"> 1009 - <h2 1010 - id="keywords-heading" 1011 - class="group text-xs text-fg-subtle uppercase tracking-wider mb-3" 1012 - > 1013 - <a 1014 - href="#keywords" 1015 - class="inline-flex items-center gap-1.5 text-fg-subtle hover:text-fg-muted transition-colors duration-200 no-underline" 1016 - > 1017 - {{ $t('package.keywords_title') }} 1018 - <span 1019 - class="i-carbon:link w-3 h-3 opacity-0 group-hover:opacity-100 transition-opacity duration-200" 1020 - aria-hidden="true" 1021 - /> 1022 - </a> 1023 - </h2> 1024 - <ul class="flex flex-wrap gap-1.5 list-none m-0 p-0"> 1025 - <li v-for="keyword in displayVersion.keywords.slice(0, 15)" :key="keyword"> 1026 - <TagClickable 1027 - :as="NuxtLink" 1028 - :to="{ name: 'search', query: { q: `keywords:${keyword}` } }" 1029 - > 1030 - {{ keyword }} 1031 - </TagClickable> 1032 - </li> 1033 - </ul> 1034 - </section> 1008 + <PackageKeywords :keywords="displayVersion?.keywords" /> 1035 1009 1036 1010 <!-- Agent Skills --> 1037 1011 <ClientOnly> ··· 1052 1026 :links="readmeData.playgroundLinks" 1053 1027 /> 1054 1028 1055 - <section 1056 - id="compatibility" 1057 - v-if=" 1058 - displayVersion?.engines && (displayVersion.engines.node || displayVersion.engines.npm) 1059 - " 1060 - class="scroll-mt-20" 1061 - > 1062 - <h2 1063 - id="compatibility-heading" 1064 - class="group text-xs text-fg-subtle uppercase tracking-wider mb-3" 1065 - > 1066 - <a 1067 - href="#compatibility" 1068 - class="inline-flex items-center gap-1.5 text-fg-subtle hover:text-fg-muted transition-colors duration-200 no-underline" 1069 - > 1070 - {{ $t('package.compatibility') }} 1071 - <span 1072 - class="i-carbon:link w-3 h-3 opacity-0 group-hover:opacity-100 transition-opacity duration-200" 1073 - aria-hidden="true" 1074 - /> 1075 - </a> 1076 - </h2> 1077 - <dl class="space-y-2"> 1078 - <div v-if="displayVersion.engines.node" class="flex justify-between gap-4 py-1"> 1079 - <dt class="text-fg-muted text-sm shrink-0">node</dt> 1080 - <dd class="font-mono text-sm text-fg text-end" :title="displayVersion.engines.node"> 1081 - {{ displayVersion.engines.node }} 1082 - </dd> 1083 - </div> 1084 - <div v-if="displayVersion.engines.npm" class="flex justify-between gap-4 py-1"> 1085 - <dt class="text-fg-muted text-sm shrink-0">npm</dt> 1086 - <dd class="font-mono text-sm text-fg text-end" :title="displayVersion.engines.npm"> 1087 - {{ displayVersion.engines.npm }} 1088 - </dd> 1089 - </div> 1090 - </dl> 1091 - </section> 1029 + <PackageCompatibility :engines="displayVersion?.engines" /> 1092 1030 1093 1031 <!-- Versions (grouped by release channel) --> 1094 1032 <PackageVersions
+55 -10
test/nuxt/a11y.spec.ts
··· 1 - import type { AxeResults, RunOptions } from 'axe-core' 2 - import type { VueWrapper } from '@vue/test-utils' 3 1 import type { ColumnConfig, FilterChip } from '#shared/types/preferences' 2 + import { mountSuspended } from '@nuxt/test-utils/runtime' 3 + import type { VueWrapper } from '@vue/test-utils' 4 4 import 'axe-core' 5 + import type { AxeResults, RunOptions } from 'axe-core' 5 6 import { afterEach, describe, expect, it } from 'vitest' 6 - import { mountSuspended } from '@nuxt/test-utils/runtime' 7 7 8 8 // axe-core is a UMD module that exposes itself as window.axe in the browser 9 9 declare const axe: { ··· 58 58 AppFooter, 59 59 AppHeader, 60 60 BaseCard, 61 - UserAvatar, 62 61 BuildEnvironment, 63 62 CallToAction, 64 63 CodeDirectoryListing, ··· 67 66 CodeViewer, 68 67 CollapsibleSection, 69 68 ColumnPicker, 69 + CompareComparisonGrid, 70 70 CompareFacetCard, 71 71 CompareFacetRow, 72 72 CompareFacetSelector, 73 - CompareComparisonGrid, 74 73 ComparePackageSelector, 75 74 DateTime, 76 75 DependencyPathPopup, 77 76 FilterChips, 78 77 FilterPanel, 79 78 HeaderAccountMenu, 79 + HeaderConnectorModal, 80 + HeaderSearchBox, 80 81 LicenseDisplay, 81 82 LoadingSpinner, 82 - PackageChartModal, 83 - PackageClaimPackageModal, 84 - HeaderConnectorModal, 85 83 OrgMembersPanel, 86 84 OrgOperationsQueue, 87 85 OrgTeamsPanel, 88 86 PackageAccessControls, 89 87 PackageCard, 88 + PackageChartModal, 89 + PackageClaimPackageModal, 90 + PackageCompatibility, 90 91 PackageDependencies, 91 92 PackageDeprecatedTree, 92 93 PackageDownloadAnalytics, 93 94 PackageInstallScripts, 95 + PackageKeywords, 94 96 PackageList, 95 97 PackageListControls, 96 98 PackageListToolbar, ··· 108 110 PaginationControls, 109 111 ProvenanceBadge, 110 112 Readme, 113 + SearchSuggestionCard, 111 114 SettingsAccentColorPicker, 112 115 SettingsBgThemePicker, 113 116 SettingsToggle, ··· 118 121 TooltipAnnounce, 119 122 TooltipApp, 120 123 TooltipBase, 121 - HeaderSearchBox, 122 - SearchSuggestionCard, 124 + UserAvatar, 123 125 VersionSelector, 124 126 ViewModeToggle, 125 127 } from '#components' ··· 643 645 { name: 'yyx990803', email: 'evan@vuejs.org' }, 644 646 { name: 'posva', email: 'posva@example.com' }, 645 647 ], 648 + }, 649 + }) 650 + const results = await runAxe(component) 651 + expect(results.violations).toEqual([]) 652 + }) 653 + }) 654 + 655 + describe('PackageCompatibility', () => { 656 + it('should have no accessibility violations without engines', async () => { 657 + const component = await mountSuspended(PackageCompatibility, { 658 + props: {}, 659 + }) 660 + const results = await runAxe(component) 661 + expect(results.violations).toEqual([]) 662 + }) 663 + 664 + it('should have no accessibility violations with engines', async () => { 665 + const component = await mountSuspended(PackageCompatibility, { 666 + props: { 667 + engines: { 668 + node: '>=14', 669 + npm: '>=10', 670 + }, 671 + }, 672 + }) 673 + const results = await runAxe(component) 674 + expect(results.violations).toEqual([]) 675 + }) 676 + }) 677 + 678 + describe('PackageKeywords', () => { 679 + it('should have no accessibility violations without keywords', async () => { 680 + const component = await mountSuspended(PackageKeywords, { 681 + props: {}, 682 + }) 683 + const results = await runAxe(component) 684 + expect(results.violations).toEqual([]) 685 + }) 686 + 687 + it('should have no accessibility violations with keywords', async () => { 688 + const component = await mountSuspended(PackageKeywords, { 689 + props: { 690 + keywords: ['keyword1', 'keyword2'], 646 691 }, 647 692 }) 648 693 const results = await runAxe(component)