forked from
npmx.dev/npmx.dev
[READ-ONLY]
a fast, modern browser for the npm registry
1<script setup lang="ts">
2const {
3 isFacetSelected,
4 toggleFacet,
5 selectCategory,
6 deselectCategory,
7 facetsByCategory,
8 categoryOrder,
9 getCategoryLabel,
10} = useFacetSelection()
11
12// Check if all non-comingSoon facets in a category are selected
13function isCategoryAllSelected(category: string): boolean {
14 const facets = facetsByCategory.value[category] ?? []
15 const selectableFacets = facets.filter(f => !f.comingSoon)
16 return selectableFacets.length > 0 && selectableFacets.every(f => isFacetSelected(f.id))
17}
18
19// Check if no facets in a category are selected
20function isCategoryNoneSelected(category: string): boolean {
21 const facets = facetsByCategory.value[category] ?? []
22 const selectableFacets = facets.filter(f => !f.comingSoon)
23 return selectableFacets.length > 0 && selectableFacets.every(f => !isFacetSelected(f.id))
24}
25</script>
26
27<template>
28 <div class="space-y-3" role="group" :aria-label="$t('compare.facets.group_label')">
29 <div v-for="category in categoryOrder" :key="category">
30 <!-- Category header with all/none buttons -->
31 <div class="flex items-center gap-2 mb-2">
32 <span class="text-3xs text-fg-subtle uppercase tracking-wider">
33 {{ getCategoryLabel(category) }}
34 </span>
35 <!-- TODO: These should be radios, since they are mutually exclusive, and currently this behavior is faked with buttons -->
36 <ButtonBase
37 :aria-label="
38 $t('compare.facets.select_category', { category: getCategoryLabel(category) })
39 "
40 :aria-pressed="isCategoryAllSelected(category)"
41 :disabled="isCategoryAllSelected(category)"
42 @click="selectCategory(category)"
43 size="small"
44 >
45 {{ $t('compare.facets.all') }}
46 </ButtonBase>
47 <span class="text-2xs text-fg-muted/40">/</span>
48 <ButtonBase
49 :aria-label="
50 $t('compare.facets.deselect_category', { category: getCategoryLabel(category) })
51 "
52 :aria-pressed="isCategoryNoneSelected(category)"
53 :disabled="isCategoryNoneSelected(category)"
54 @click="deselectCategory(category)"
55 size="small"
56 >
57 {{ $t('compare.facets.none') }}
58 </ButtonBase>
59 </div>
60
61 <!-- Facet buttons -->
62 <div class="flex items-center gap-1.5 flex-wrap" role="group">
63 <!-- TODO: These should be checkboxes -->
64 <ButtonBase
65 v-for="facet in facetsByCategory[category]"
66 :key="facet.id"
67 size="small"
68 :title="facet.comingSoon ? $t('compare.facets.coming_soon') : facet.description"
69 :disabled="facet.comingSoon"
70 :aria-pressed="isFacetSelected(facet.id)"
71 :aria-label="facet.label"
72 class="gap-1 px-1.5 rounded transition-colors focus-visible:outline-accent/70"
73 :class="
74 facet.comingSoon
75 ? 'text-fg-subtle/50 bg-bg-subtle border-border-subtle cursor-not-allowed'
76 : isFacetSelected(facet.id)
77 ? 'text-fg-muted bg-bg-muted'
78 : 'text-fg-subtle bg-bg-subtle border-border-subtle hover:text-fg-muted hover:border-border'
79 "
80 @click="!facet.comingSoon && toggleFacet(facet.id)"
81 :classicon="
82 facet.comingSoon
83 ? undefined
84 : isFacetSelected(facet.id)
85 ? 'i-lucide:check'
86 : 'i-lucide:plus'
87 "
88 >
89 {{ facet.label }}
90 <span v-if="facet.comingSoon" class="text-4xs"
91 >({{ $t('compare.facets.coming_soon') }})</span
92 >
93 </ButtonBase>
94 </div>
95 </div>
96 </div>
97</template>