Skip to content

Commit ab28b00

Browse files
committed
Merge branch 'main' into fix/ignore-shortcuts
2 parents 23c580e + 4801787 commit ab28b00

85 files changed

Lines changed: 2716 additions & 553 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,4 +139,4 @@ jobs:
139139
run: pnpm install
140140

141141
- name: 🔍 Check for unused code
142-
run: pnpm knip:production
142+
run: pnpm knip

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2026 Daniel Roe
3+
Copyright (c) 2026 npmx team and contributors
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

app/components/AppHeader.vue

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const isSearchExpandedManually = shallowRef(false)
2020
const searchBoxRef = shallowRef<{ focus: () => void } | null>(null)
2121
2222
// On search page, always show search expanded on mobile
23+
const isOnHomePage = computed(() => route.name === 'index')
2324
const isOnSearchPage = computed(() => route.name === 'search')
2425
const isSearchExpanded = computed(() => isOnSearchPage.value || isSearchExpandedManually.value)
2526
@@ -72,17 +73,36 @@ onKeyStroke(
7273
},
7374
{ dedupe: true },
7475
)
76+
77+
onKeyStroke(
78+
'c',
79+
e => {
80+
// Allow more specific handlers to take precedence
81+
if (e.defaultPrevented) return
82+
83+
// Don't trigger if user is typing in an input
84+
const target = e.target as HTMLElement
85+
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
86+
return
87+
}
88+
89+
e.preventDefault()
90+
navigateTo('/compare')
91+
},
92+
{ dedupe: true },
93+
)
7594
</script>
7695

7796
<template>
7897
<header class="sticky top-0 z-50 bg-bg/80 backdrop-blur-md border-b border-border">
7998
<nav
8099
:aria-label="$t('nav.main_navigation')"
81-
class="container min-h-14 flex items-center justify-between gap-2"
100+
class="container min-h-14 flex items-center gap-2"
101+
:class="isOnHomePage ? 'justify-end' : 'justify-between'"
82102
>
83103
<!-- Mobile: Logo + search button (expands search, doesn't navigate) -->
84104
<button
85-
v-if="!isSearchExpanded"
105+
v-if="!isSearchExpanded && !isOnHomePage"
86106
type="button"
87107
class="sm:hidden flex-shrink-0 inline-flex items-center gap-2 font-mono text-lg font-medium text-fg hover:text-fg transition-colors duration-200 focus-ring rounded"
88108
:aria-label="$t('nav.tap_to_search')"
@@ -156,10 +176,16 @@ onKeyStroke(
156176
<!-- Desktop: Compare link -->
157177
<NuxtLink
158178
to="/compare"
159-
class="hidden sm:inline-flex link-subtle font-mono text-sm items-center gap-1.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/50 rounded"
179+
class="hidden sm:inline-flex link-subtle font-mono text-sm items-center gap-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/50 rounded"
180+
aria-keyshortcuts="c"
160181
>
161-
<span class="i-carbon:compare w-4 h-4" aria-hidden="true" />
162182
{{ $t('nav.compare') }}
183+
<kbd
184+
class="inline-flex items-center justify-center w-5 h-5 text-xs bg-bg-muted border border-border rounded"
185+
aria-hidden="true"
186+
>
187+
c
188+
</kbd>
163189
</NuxtLink>
164190

165191
<!-- Desktop: Settings link -->
@@ -185,7 +211,7 @@ onKeyStroke(
185211
<!-- Mobile: Menu button (always visible, toggles menu) -->
186212
<button
187213
type="button"
188-
class="sm:hidden p-2 -m-2 text-fg-subtle hover:text-fg transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/50 rounded"
214+
class="sm:hidden flex items-center p-2 -m-2 text-fg-subtle hover:text-fg transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/50 rounded"
189215
:aria-label="showMobileMenu ? $t('common.close') : $t('nav.open_menu')"
190216
:aria-expanded="showMobileMenu"
191217
@click="showMobileMenu = !showMobileMenu"

app/components/PackageTableRow.vue

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ const emit = defineEmits<{
1717
const pkg = computed(() => props.result.package)
1818
const score = computed(() => props.result.score)
1919
20-
// Get the best available date: prefer result.updated (from packument), fall back to package.date
21-
const updatedDate = computed(() => props.result.updated ?? props.result.package.date)
20+
const updatedDate = computed(() => props.result.package.date)
2221
2322
function formatDownloads(count?: number): string {
2423
if (count === undefined) return '-'

app/components/PackageVulnerabilityTree.vue

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,11 @@ const props = defineProps<{
77
version: string
88
}>()
99
10-
const {
11-
data: vulnTree,
12-
status,
13-
fetch: fetchVulnTree,
14-
} = useDependencyAnalysis(
10+
const { data: vulnTree, status } = useDependencyAnalysis(
1511
() => props.packageName,
1612
() => props.version,
1713
)
1814
19-
onMounted(() => fetchVulnTree())
20-
2115
const isExpanded = shallowRef(false)
2216
const showAllPackages = shallowRef(false)
2317
const showAllVulnerabilities = shallowRef(false)

app/components/Toggle.client.vue

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<script setup lang="ts">
2+
defineProps<{
3+
label?: string
4+
description?: string
5+
}>()
6+
7+
const checked = defineModel<boolean>({
8+
default: false,
9+
})
10+
</script>
11+
12+
<template>
13+
<button
14+
type="button"
15+
class="w-full flex items-center justify-between gap-4 group"
16+
role="switch"
17+
:aria-checked="checked"
18+
@click="checked = !checked"
19+
>
20+
<span v-if="label" class="text-sm text-fg font-medium text-start">
21+
{{ label }}
22+
</span>
23+
<span
24+
class="relative inline-flex h-6 w-11 shrink-0 items-center rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out motion-reduce:transition-none shadow-sm cursor-pointer"
25+
:class="checked ? 'bg-accent' : 'bg-bg border border-border'"
26+
aria-hidden="true"
27+
>
28+
<span
29+
class="pointer-events-none inline-block h-5 w-5 rounded-full shadow-sm ring-0 transition-transform duration-200 ease-in-out motion-reduce:transition-none"
30+
:class="checked ? 'bg-bg' : 'bg-fg-muted'"
31+
/>
32+
</span>
33+
</button>
34+
<p v-if="description" class="text-sm text-fg-muted">
35+
{{ description }}
36+
</p>
37+
</template>
38+
39+
<style scoped>
40+
button[aria-checked='false'] > span:last-of-type > span {
41+
translate: 0;
42+
}
43+
button[aria-checked='true'] > span:last-of-type > span {
44+
translate: calc(100%);
45+
}
46+
html[dir='rtl'] button[aria-checked='true'] > span:last-of-type > span {
47+
translate: calc(-100%);
48+
}
49+
</style>
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
<script setup lang="ts">
22
defineProps<{
3-
label: string
3+
label?: string
4+
description?: string
45
}>()
56
</script>
67

78
<template>
89
<div class="w-full flex items-center justify-between gap-4">
9-
<span class="text-sm text-fg font-medium text-start">
10+
<span v-if="label" class="text-sm text-fg font-medium text-start">
1011
{{ label }}
1112
</span>
1213
<span class="skeleton block h-6 w-11 shrink-0 rounded-full" />
1314
</div>
15+
<p v-if="description" class="text-sm text-fg-muted">
16+
{{ description }}
17+
</p>
1418
</template>

app/components/ViewModeToggle.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const viewMode = defineModel<ViewMode>({ default: 'cards' })
1818
:aria-label="$t('filters.view_mode.cards')"
1919
@click="viewMode = 'cards'"
2020
>
21-
<span class="i-carbon-grid w-4 h-4" aria-hidden="true" />
21+
<span class="i-carbon-horizontal-view w-4 h-4" aria-hidden="true" />
2222
<span class="sr-only">{{ $t('filters.view_mode.cards') }}</span>
2323
</button>
2424
<button
@@ -29,7 +29,7 @@ const viewMode = defineModel<ViewMode>({ default: 'cards' })
2929
:aria-label="$t('filters.view_mode.table')"
3030
@click="viewMode = 'table'"
3131
>
32-
<span class="i-carbon-list w-4 h-4" aria-hidden="true" />
32+
<span class="i-carbon-table-split w-4 h-4" aria-hidden="true" />
3333
<span class="sr-only">{{ $t('filters.view_mode.table') }}</span>
3434
</button>
3535
</div>

app/composables/useAtproto.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { UserSession } from '#shared/schemas/userSession'
22

3-
/** @public */
43
export function useAtproto() {
54
const { data: user, pending, clear } = useFetch<UserSession | null>('/api/auth/session')
65

app/composables/useCachedFetch.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import type { CachedFetchResult } from '#shared/utils/fetch-cache-config'
2727
* )
2828
* }
2929
* ```
30-
* @public
3130
*/
3231
export function useCachedFetch(): CachedFetchFunction {
3332
// On client, return a function that just uses $fetch (no caching, not stale)

0 commit comments

Comments
 (0)