Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/components/Package/Card.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const props = defineProps<{
searchQuery?: string
}>()
const { selectable } = usePackageSelectionContext()
const { isPackageSelected, togglePackageSelection, canSelectMore } = usePackageSelection()
const isSelected = computed<boolean>(() => {
return isPackageSelected(props.result.package.name)
Expand Down Expand Up @@ -65,6 +66,7 @@ const numberFormatter = useNumberFormatter()
</component>

<PackageSelectionCheckbox
v-if="selectable"
:package-name="result.package.name"
:disabled="!canSelectMore && !isSelected"
:checked="isSelected"
Expand Down
4 changes: 4 additions & 0 deletions app/components/Package/List.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ const props = defineProps<{
currentPage?: number
/** When true, shows search-specific UI (relevance sort, no filters) */
searchContext?: boolean
/** Whether package cards should show selection checkboxes. */
selectable?: boolean
}>()

const emit = defineEmits<{
Expand All @@ -46,6 +48,8 @@ const emit = defineEmits<{
'clickKeyword': [keyword: string]
}>()

providePackageSelectionContext(props.selectable ?? false)

// Reference to WindowVirtualizer for infinite scroll detection
const listRef = useTemplateRef<WindowVirtualizerHandle>('listRef')

Expand Down
1 change: 1 addition & 0 deletions app/components/Package/SelectionView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const { data, pending } = useAsyncData(
:view-mode="viewMode"
:results="data"
heading-level="h2"
selectable
/>
<p v-else class="text-fg-muted text-sm">
{{ $t('filters.table.no_packages') }}
Expand Down
6 changes: 4 additions & 2 deletions app/components/Package/Table.vue
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,16 @@ const columnLabels = computed(() => ({
function getColumnLabel(id: ColumnId): string {
return columnLabels.value[id]
}
const { selectable } = usePackageSelectionContext()
</script>

<template>
<div class="overflow-x-auto">
<table class="w-full text-start">
<thead class="border-b border-border">
<tr>
<th scope="col" class="w-8">
<th scope="col" class="w-8" v-if="selectable">
<span class="sr-only">{{ getColumnLabel('selection') }}</span>
</th>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
<!-- Name (always visible) -->
Expand Down Expand Up @@ -265,7 +267,7 @@ function getColumnLabel(id: ColumnId): string {
<!-- Loading skeleton rows -->
<template v-if="isLoading && results.length === 0">
<tr v-for="i in 5" :key="`skeleton-${i}`" class="border-b border-border">
<td class="py-3 px-3 w-8">
<td v-if="selectable" class="py-3 px-3 w-8">
<div class="h-4 w-4 bg-bg-muted rounded animate-pulse ms-auto" />
</td>
<td class="py-3 px-3">
Expand Down
4 changes: 3 additions & 1 deletion app/components/Package/TableRow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ const allMaintainersText = computed(() => {
})

const compactNumberFormatter = useCompactNumberFormatter()

const { selectable } = usePackageSelectionContext()
</script>

<template>
Expand All @@ -41,7 +43,7 @@ const compactNumberFormatter = useCompactNumberFormatter()
tabindex="0"
:data-result-index="index"
>
<td class="ps-3">
<td class="ps-3" v-if="selectable">
<PackageSelectionCheckbox
:package-name="result.package.name"
:disabled="!canSelectMore && !isSelected"
Expand Down
18 changes: 18 additions & 0 deletions app/composables/usePackageSelection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,21 @@ export function usePackageSelection() {
openSelectionView,
}
}

const PACKAGE_SELECTION_CONTEXT_KEY = Symbol('packageSelectionContext')

export interface PackageSelectionContext {
selectable: Readonly<Ref<boolean>>
}

export function providePackageSelectionContext(selectable: boolean | Ref<boolean>) {
const value = computed(() => (isRef(selectable) ? selectable.value : selectable))
provide(PACKAGE_SELECTION_CONTEXT_KEY, { selectable: value })
}

export function usePackageSelectionContext(): PackageSelectionContext {
const context = inject<PackageSelectionContext>(PACKAGE_SELECTION_CONTEXT_KEY, {
selectable: computed(() => false),
})
return context
}
30 changes: 30 additions & 0 deletions app/pages/org/[org].vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { debounce } from 'perfect-debounce'

definePageMeta({
name: 'org',
preserveScrollOnQuery: true,
})

const route = useRoute('org')
Expand Down Expand Up @@ -128,6 +129,15 @@ const activeTab = shallowRef<'members' | 'teams'>('members')
// Canonical URL for this org page
const canonicalUrl = computed(() => `https://npmx.dev/@${orgName.value}`)

const { selectedPackages, showSelectionView, openSelectionView, closeSelectionView } =
usePackageSelection()

watch(selectedPackages, newSelectedPackages => {
if (newSelectedPackages.length === 0) {
closeSelectionView()
}
})

useHead({
link: [{ rel: 'canonical', href: canonicalUrl }],
})
Expand All @@ -152,6 +162,8 @@ defineOgImage(
</script>

<template>
<PackageActionBar v-if="!showSelectionView" />

<main class="container flex-1 py-8 sm:py-12 w-full">
<!-- Header -->
<header class="mb-8 pb-8 border-b border-border">
Expand Down Expand Up @@ -259,6 +271,22 @@ defineOgImage(
</p>
</div>

<section v-else-if="showSelectionView && selectedPackages.length">
<header class="flex justify-end mb-4">
<button
type="button"
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"
@click="closeSelectionView"
:aria-label="$t('nav.back')"
>
<span class="i-lucide:arrow-left rtl-flip w-4 h-4" aria-hidden="true" />
<span class="hidden sm:inline">{{ $t('nav.back') }}</span>
</button>
</header>

<PackageSelectionView :view-mode="viewMode" />
</section>

<!-- Package list -->
<section v-else-if="packages.length > 0" :aria-label="$t('org.page.packages_title')">
<h2 class="text-xs text-fg-subtle uppercase tracking-wider mb-4">
Expand All @@ -282,6 +310,7 @@ defineOgImage(
@clear-filter="handleClearFilter"
@clear-all-filters="clearAllFilters"
@update:text="setTextFilter"
@toggle-selection="openSelectionView"
@update:search-scope="setSearchScope"
@update:download-range="setDownloadRange"
@update:security="setSecurity"
Expand All @@ -306,6 +335,7 @@ defineOgImage(
:page-size="pageSize"
:current-page="currentPage"
@click-keyword="toggleKeyword"
selectable
/>

<!-- Pagination controls -->
Expand Down
1 change: 1 addition & 0 deletions app/pages/search.vue
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,7 @@ onBeforeUnmount(() => {
@load-more="loadMore"
@page-change="handlePageChange"
@click-keyword="toggleKeyword"
selectable
/>

<PaginationControls
Expand Down
Loading