forked from
npmx.dev/npmx.dev
[READ-ONLY]
a fast, modern browser for the npm registry
1<script setup lang="ts">
2export type SortOption = 'downloads' | 'updated' | 'name-asc' | 'name-desc'
3
4const props = defineProps<{
5 /** Current search/filter text */
6 filter: string
7 /** Current sort option */
8 sort: SortOption
9 /** Placeholder text for the search input */
10 placeholder?: string
11 /** Total count of packages (before filtering) */
12 totalCount?: number
13 /** Filtered count of packages */
14 filteredCount?: number
15}>()
16
17const emit = defineEmits<{
18 'update:filter': [value: string]
19 'update:sort': [value: SortOption]
20}>()
21
22const filterValue = computed({
23 get: () => props.filter,
24 set: value => emit('update:filter', value),
25})
26
27const sortValue = computed({
28 get: () => props.sort,
29 set: value => emit('update:sort', value),
30})
31
32const sortOptions = computed(
33 () =>
34 [
35 { value: 'downloads', label: $t('package.sort.downloads') },
36 { value: 'updated', label: $t('package.sort.published') },
37 { value: 'name-asc', label: $t('package.sort.name_asc') },
38 { value: 'name-desc', label: $t('package.sort.name_desc') },
39 ] as const,
40)
41
42// Show filter count when filtering is active
43const showFilteredCount = computed(() => {
44 return (
45 props.filter &&
46 props.filteredCount !== undefined &&
47 props.totalCount !== undefined &&
48 props.filteredCount !== props.totalCount
49 )
50})
51</script>
52
53<template>
54 <div class="flex flex-col sm:flex-row gap-3 mb-6">
55 <!-- Filter input -->
56 <div class="flex-1 relative">
57 <label for="package-filter" class="sr-only">{{ $t('package.list.filter_label') }}</label>
58 <div
59 class="absolute h-full w-10 flex items-center justify-center text-fg-subtle pointer-events-none"
60 aria-hidden="true"
61 >
62 <div class="i-lucide:search w-4 h-4" />
63 </div>
64 <InputBase
65 id="package-filter"
66 v-model="filterValue"
67 type="search"
68 :placeholder="placeholder ?? $t('package.list.filter_placeholder')"
69 no-correct
70 class="w-full min-w-25 ps-10"
71 size="medium"
72 />
73 </div>
74
75 <!-- Sort select -->
76 <SelectField
77 :label="$t('package.list.sort_label')"
78 hidden-label
79 id="package-sort"
80 class="relative shrink-0"
81 v-model="sortValue"
82 :items="sortOptions.map(option => ({ label: option.label, value: option.value }))"
83 />
84 </div>
85
86 <!-- Filtered count indicator -->
87 <p v-if="showFilteredCount" class="text-fg-subtle text-xs font-mono mb-4">
88 {{ $t('package.list.showing_count', { filtered: filteredCount, total: totalCount }) }}
89 </p>
90</template>