Skip to content

Commit 287a4a7

Browse files
feat: add back button and fix keys
1 parent 3d244a0 commit 287a4a7

5 files changed

Lines changed: 72 additions & 62 deletions

File tree

app/components/Package/ActionBar.vue

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ onKeyStroke(
2020
<Transition name="action-bar-slide" appear>
2121
<div
2222
v-if="selectedPackages.length"
23-
class="fixed bottom-12 inset-is-0 w-full flex items-center justify-center z-36 pointer-events-none"
23+
class="fixed bottom-10 inset-is-0 w-full flex items-center justify-center z-36 pointer-events-none"
2424
>
2525
<div
2626
ref="actionBarRef"
2727
tabindex="-1"
2828
aria-keyshortcuts="b"
29-
class="pointer-events-auto bg-bg shadow-2xl border border-fg-muted/20 p-2.5 min-w-[280px] rounded-xl flex gap-2 items-center justify-between animate-in"
29+
class="pointer-events-auto bg-bg shadow-xl shadow-accent/5 border border-fg-muted/20 p-2.5 min-w-[300px] rounded-xl flex gap-2 items-center justify-between animate-in"
3030
>
3131
<div aria-live="polite" aria-atomic="true" class="sr-only">
3232
{{ $t('action_bar.selection', selectedPackages.length) }}.
@@ -38,21 +38,17 @@ onKeyStroke(
3838
{{ $t('action_bar.selection', selectedPackages.length) }}
3939
</span>
4040
<button @click="clearSelectedPackages" class="flex items-center ms-2 hover:text-fg-muted">
41-
<span class="i-lucide:x text-sm" aria-label="Close action bar" />
41+
<span class="i-lucide:x text-xs relative top-px" aria-label="Close action bar" />
4242
</button>
4343
</div>
4444

45-
<span class="w-px h-8 bg-fg-subtle/40" />
46-
47-
<div>
48-
<LinkBase
49-
:to="{ name: 'compare', query: { packages: selectedPackagesParam } }"
50-
variant="button-secondary"
51-
classicon="i-lucide:git-compare"
52-
>
53-
Compare
54-
</LinkBase>
55-
</div>
45+
<LinkBase
46+
:to="{ name: 'compare', query: { packages: selectedPackagesParam } }"
47+
variant="button-secondary"
48+
classicon="i-lucide:git-compare"
49+
>
50+
Compare
51+
</LinkBase>
5652
</div>
5753
</div>
5854
</Transition>

app/components/Package/List.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ const { isPackageSelected, togglePackageSelection } = usePackageSelection()
181181
<template #default="{ item, index }">
182182
<div class="pb-4">
183183
<PackageCard
184+
:key="item.package.name"
184185
:result="item"
185186
:heading-level="headingLevel"
186187
:show-publisher="showPublisher"
@@ -189,8 +190,8 @@ const { isPackageSelected, togglePackageSelection } = usePackageSelection()
189190
class="motion-safe:animate-fade-in motion-safe:animate-fill-both"
190191
:filters="filters"
191192
:style="{ animationDelay: `${Math.min(index * 0.02, 0.3)}s` }"
192-
:selected="isPackageSelected(item.package.name)"
193-
@update:selected="togglePackageSelection(item.package.name)"
193+
:selected="isPackageSelected(item)"
194+
@update:selected="togglePackageSelection(item)"
194195
@click-keyword="emit('clickKeyword', $event)"
195196
/>
196197
</div>

app/components/Package/SelectionView.vue

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,35 @@ defineProps<{
33
viewMode?: ViewMode
44
}>()
55
6-
const { selectedPackages } = usePackageSelection()
7-
const packages = ref<NpmSearchResult[]>([])
6+
const { selectedPackages, clearSelectedPackages, selectedPackagesParam } = usePackageSelection()
87
</script>
98

109
<template>
1110
<section>
12-
<p class="text-fg-muted text-sm mt-4 font-mono">
13-
{{
14-
$t('search.found_packages', { count: $n(selectedPackages.length) }, selectedPackages.length)
15-
}}
16-
</p>
11+
<header class="mb-6 flex items-center justify-end">
12+
<div class="flex items-center gap-2">
13+
<ButtonBase variant="secondary" @click="clearSelectedPackages" classicon="i-lucide:x"
14+
>Clear all</ButtonBase
15+
>
16+
<LinkBase
17+
:to="{ name: 'compare', query: { packages: selectedPackagesParam } }"
18+
variant="button-primary"
19+
classicon="i-lucide:git-compare"
20+
>
21+
Compare
22+
</LinkBase>
23+
</div>
24+
</header>
1725

18-
{{ selectedPackages }}
26+
<p class="text-fg-muted text-sm font-mono">
27+
{{ $t('action_bar.selection', selectedPackages.length) }}
28+
</p>
1929

2030
<div class="mt-6">
2131
<PackageList
22-
v-if="packages.length > 0"
32+
v-if="selectedPackages.length > 0"
2333
:view-mode="viewMode"
24-
:results="packages"
34+
:results="selectedPackages"
2535
search-context
2636
heading-level="h2"
2737
show-publisher

app/composables/usePackageSelection.ts

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,20 @@
11
export function usePackageSelection() {
2-
const selectedPackagesParam = useRouteQuery<string>('selection', '', {
3-
mode: 'replace',
4-
})
2+
const selectedPackages = useState<NpmSearchResult[]>('package_selection', () => [])
3+
const selectedPackagesParam = computed<string>(() =>
4+
selectedPackages.value.map(p => p.package.name).join(','),
5+
)
56

6-
const selectedPackages = computed<string[]>({
7-
get() {
8-
if (!selectedPackagesParam) {
9-
return []
10-
}
11-
12-
return selectedPackagesParam.value
13-
.split(',')
14-
.map(p => p.trim())
15-
.filter(p => p.length > 0)
16-
.slice(0, 4)
17-
},
18-
set(value) {
19-
selectedPackagesParam.value = value.length > 0 ? value.join(',') : ''
20-
},
21-
})
22-
23-
function isPackageSelected(name: string): boolean {
24-
return selectedPackages.value.includes(name)
7+
function isPackageSelected(pkg: NpmSearchResult): boolean {
8+
return selectedPackages.value.some(p => p.package.name === pkg.package.name)
259
}
2610

27-
function togglePackageSelection(name: string) {
28-
if (isPackageSelected(name)) {
29-
selectedPackages.value = selectedPackages.value.filter(selected => selected !== name)
11+
function togglePackageSelection(pkg: NpmSearchResult) {
12+
if (isPackageSelected(pkg)) {
13+
selectedPackages.value = selectedPackages.value.filter(
14+
selected => selected.package.name !== pkg.package.name,
15+
)
3016
} else {
31-
selectedPackages.value = [...selectedPackages.value, name]
17+
selectedPackages.value = [...selectedPackages.value, pkg]
3218
}
3319
}
3420

app/pages/search.vue

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,20 @@ import { normalizeSearchParam } from '#shared/utils/url'
1010
const route = useRoute()
1111
1212
const { selectedPackages } = usePackageSelection()
13-
const selectionView = useRouteQuery<string>('selection-view', '', { mode: 'push' })
13+
const isSelectioView = ref<boolean>(false)
1414
15-
function toggleSelection() {
16-
selectionView.value = selectionView.value === 'true' ? '' : 'true'
17-
}
15+
watch(selectedPackages, packages => {
16+
if (packages.length === 0) {
17+
isSelectioView.value = false
18+
}
19+
})
1820
19-
const isSelectioView = computed<boolean>(() => selectionView.value === 'true')
21+
function showSelectionView() {
22+
isSelectioView.value = true
23+
}
24+
function hideSelectionView() {
25+
isSelectioView.value = false
26+
}
2027
2128
// Preferences (persisted to localStorage)
2229
const {
@@ -552,23 +559,33 @@ defineOgImageComponent('Default', {
552559
</script>
553560

554561
<template>
555-
<PackageActionBar />
562+
<PackageActionBar v-if="!isSelectioView" />
556563

557564
<main class="flex-1 py-8 search-page" :class="{ 'overflow-x-hidden': viewMode !== 'table' }">
558565
<div class="container-sm">
559566
<div class="flex items-center justify-between gap-4 mb-4">
560567
<h1 class="font-mono text-2xl sm:text-3xl font-medium">
561568
{{ $t('search.title') }}
562569
</h1>
563-
<SearchProviderToggle />
570+
<button
571+
v-if="isSelectioView"
572+
type="button"
573+
class="cursor-pointer inline-flex items-center gap-2 font-mono text-sm text-fg-muted hover:text-fg transition-colors duration-200 rounded focus-visible:outline-accent/70 shrink-0"
574+
@click="hideSelectionView"
575+
>
576+
<span class="i-lucide:arrow-left rtl-flip w-4 h-4" aria-hidden="true" />
577+
<span class="hidden sm:inline">{{ $t('nav.back') }}</span>
578+
</button>
579+
<SearchProviderToggle v-else />
564580
</div>
565581

566582
<PackageSelectionView
567-
:view-mode="viewMode"
583+
@back="hideSelectionView"
568584
v-if="isSelectioView && selectedPackages.length"
585+
:view-mode="viewMode"
569586
/>
570587

571-
<section v-if="query" class="results-layout">
588+
<section v-else-if="query" class="results-layout">
572589
<LoadingSpinner v-if="showSearching" :text="$t('search.searching')" />
573590

574591
<div
@@ -637,7 +654,7 @@ defineOgImageComponent('Default', {
637654
:disabled-sort-keys="disabledSortKeys"
638655
search-context
639656
@toggle-column="toggleColumn"
640-
@toggle-selection="toggleSelection"
657+
@toggle-selection="showSelectionView"
641658
@reset-columns="resetColumns"
642659
@clear-filter="handleClearFilter"
643660
@clear-all-filters="clearAllFilters"

0 commit comments

Comments
 (0)