Skip to content

Commit 48c277f

Browse files
authored
Merge branch 'main' into perf/dependency-analytics
2 parents a27e494 + 8825b0a commit 48c277f

50 files changed

Lines changed: 3552 additions & 248 deletions

Some content is hidden

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

app/components/AppHeader.vue

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,19 @@ function expandMobileSearch() {
3030
})
3131
}
3232
33+
watch(
34+
isOnSearchPage,
35+
visible => {
36+
if (!visible) return
37+
38+
searchBoxRef.value?.focus()
39+
nextTick(() => {
40+
searchBoxRef.value?.focus()
41+
})
42+
},
43+
{ flush: 'sync' },
44+
)
45+
3346
function handleSearchBlur() {
3447
showFullSearch.value = false
3548
// Collapse expanded search on mobile after blur (with delay for click handling)
@@ -140,6 +153,15 @@ onKeyStroke(
140153

141154
<!-- End: Desktop nav items + Mobile menu button -->
142155
<div class="flex-shrink-0 flex items-center gap-4 sm:gap-6">
156+
<!-- Desktop: Compare link -->
157+
<NuxtLink
158+
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"
160+
>
161+
<span class="i-carbon:compare w-4 h-4" aria-hidden="true" />
162+
{{ $t('nav.compare') }}
163+
</NuxtLink>
164+
143165
<!-- Desktop: Settings link -->
144166
<NuxtLink
145167
to="/settings"

app/components/CollapsibleSection.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { ref, computed } from 'vue'
2+
import { shallowRef, computed } from 'vue'
33
44
interface Props {
55
title: string
@@ -19,7 +19,7 @@ const buttonId = `${props.id}-collapsible-button`
1919
const contentId = `${props.id}-collapsible-content`
2020
const headingId = `${props.id}-heading`
2121
22-
const isOpen = ref(true)
22+
const isOpen = shallowRef(true)
2323
2424
onPrehydrate(() => {
2525
const settings = JSON.parse(localStorage.getItem('npmx-settings') || '{}')

app/components/ConnectorModal.vue

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ const tokenInput = shallowRef('')
88
const portInput = shallowRef('31415')
99
const { copied, copy } = useClipboard({ copiedDuring: 2000 })
1010
11+
const hasAttemptedConnect = shallowRef(false)
12+
1113
async function handleConnect() {
14+
hasAttemptedConnect.value = true
1215
const port = Number.parseInt(portInput.value, 10) || 31415
1316
const success = await connect(tokenInput.value.trim(), port)
1417
if (success) {
@@ -42,6 +45,7 @@ const executeNpmxConnectorCommand = computed(() => {
4245
watch(open, isOpen => {
4346
if (isOpen) {
4447
tokenInput.value = ''
48+
hasAttemptedConnect.value = false
4549
}
4650
})
4751
</script>
@@ -116,10 +120,58 @@ watch(open, isOpen => {
116120

117121
<!-- Disconnected state -->
118122
<form v-else class="space-y-4" @submit.prevent="handleConnect">
123+
<!-- Contributor-only notice -->
124+
<div class="p-3 bg-amber-500/10 border border-amber-500/30 rounded-lg">
125+
<div class="space-y-2">
126+
<span
127+
class="inline-block px-2 py-0.5 text-xs font-bold uppercase tracking-wider bg-amber-500/20 text-amber-400 rounded"
128+
>
129+
{{ $t('connector.modal.contributor_badge') }}
130+
</span>
131+
<p class="text-sm text-fg-muted">
132+
<i18n-t keypath="connector.modal.contributor_notice">
133+
<template #link>
134+
<a
135+
href="https://github.com/npmx-dev/npmx.dev/blob/main/CONTRIBUTING.md#local-connector-cli"
136+
target="_blank"
137+
rel="noopener noreferrer"
138+
class="text-amber-400 hover:underline"
139+
>
140+
{{ $t('connector.modal.contributor_link') }}
141+
</a>
142+
</template>
143+
</i18n-t>
144+
</p>
145+
</div>
146+
</div>
147+
119148
<p class="text-sm text-fg-muted">
120149
{{ $t('connector.modal.run_hint') }}
121150
</p>
122151

152+
<div
153+
class="flex items-center p-3 bg-bg-muted border border-border rounded-lg font-mono text-sm"
154+
>
155+
<span class="text-fg-subtle">$</span>
156+
<span class="text-fg-subtle ms-2">pnpm npmx-connector</span>
157+
<button
158+
type="button"
159+
:aria-label="
160+
copied ? $t('connector.modal.copied') : $t('connector.modal.copy_command')
161+
"
162+
class="ms-auto text-fg-subtle hover:text-fg transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 rounded"
163+
@click="copy('pnpm npmx-connector')"
164+
>
165+
<span v-if="!copied" class="i-carbon:copy block w-5 h-5" aria-hidden="true" />
166+
<span
167+
v-else
168+
class="i-carbon:checkmark block w-5 h-5 text-green-500"
169+
aria-hidden="true"
170+
/>
171+
</button>
172+
</div>
173+
174+
<!-- TODO: Uncomment when npmx-connector is published to npm
123175
<div
124176
class="flex items-center p-3 bg-bg-muted border border-border rounded-lg font-mono text-sm"
125177
>
@@ -145,6 +197,7 @@ watch(open, isOpen => {
145197
</button>
146198
</div>
147199
</div>
200+
-->
148201

149202
<p class="text-sm text-fg-muted">{{ $t('connector.modal.paste_token') }}</p>
150203

@@ -193,9 +246,9 @@ watch(open, isOpen => {
193246
</details>
194247
</div>
195248

196-
<!-- Error message -->
249+
<!-- Error message (only show after user explicitly clicks Connect) -->
197250
<div
198-
v-if="error"
251+
v-if="error && hasAttemptedConnect"
199252
role="alert"
200253
class="p-3 text-sm text-red-400 bg-red-500/10 border border-red-500/20 rounded-md"
201254
>

app/components/MobileMenu.vue

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,15 @@ watch(isOpen, open => (isLocked.value = open))
9999
{{ $t('footer.about') }}
100100
</NuxtLink>
101101

102+
<NuxtLink
103+
to="/compare"
104+
class="flex items-center gap-3 px-3 py-3 rounded-md font-mono text-sm text-fg hover:bg-bg-subtle transition-colors duration-200"
105+
@click="closeMenu"
106+
>
107+
<span class="i-carbon:compare w-5 h-5 text-fg-muted" aria-hidden="true" />
108+
{{ $t('nav.compare') }}
109+
</NuxtLink>
110+
102111
<NuxtLink
103112
to="/settings"
104113
class="flex items-center gap-3 px-3 py-3 rounded-md font-mono text-sm text-fg hover:bg-bg-subtle transition-colors duration-200"
@@ -173,7 +182,7 @@ watch(isOpen, open => (isLocked.value = open))
173182
rel="noopener noreferrer"
174183
class="flex items-center gap-3 px-3 py-3 rounded-md font-mono text-sm text-fg hover:bg-bg-subtle transition-colors duration-200"
175184
>
176-
<span class="i-carbon:logo-x w-5 h-5 text-fg-muted" aria-hidden="true" />
185+
<span class="i-simple-icons:bluesky w-5 h-5 text-fg-muted" aria-hidden="true" />
177186
{{ $t('footer.social') }}
178187
<span
179188
class="i-carbon:launch rtl-flip w-3 h-3 ms-auto text-fg-subtle"

app/components/SearchBox.vue

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
<script setup lang="ts">
22
import { debounce } from 'perfect-debounce'
33
4-
const isMobile = useIsMobile()
5-
64
withDefaults(
75
defineProps<{
86
inputClass?: string
@@ -107,7 +105,6 @@ defineExpose({ focus })
107105
<input
108106
id="header-search"
109107
ref="inputRef"
110-
:autofocus="!isMobile"
111108
v-model="searchQuery"
112109
type="search"
113110
name="q"

app/components/ToggleSkeleton.vue

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<script setup lang="ts">
2+
defineProps<{
3+
label: string
4+
}>()
5+
</script>
6+
7+
<template>
8+
<div class="w-full flex items-center justify-between gap-4">
9+
<span class="text-sm text-fg font-medium text-start">
10+
{{ label }}
11+
</span>
12+
<span class="skeleton block h-6 w-11 shrink-0 rounded-full" />
13+
</div>
14+
</template>
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<script setup lang="ts">
2+
defineProps<{
3+
/** Number of columns (2-4) */
4+
columns: number
5+
/** Column headers (package names or version numbers) */
6+
headers: string[]
7+
}>()
8+
</script>
9+
10+
<template>
11+
<div class="overflow-x-auto">
12+
<div
13+
class="comparison-grid"
14+
:class="[columns === 4 ? 'min-w-[800px]' : 'min-w-[600px]', `columns-${columns}`]"
15+
:style="{ '--columns': columns }"
16+
>
17+
<!-- Header row -->
18+
<div class="comparison-header">
19+
<div class="comparison-label" />
20+
<div
21+
v-for="(header, index) in headers"
22+
:key="index"
23+
class="comparison-cell comparison-cell-header"
24+
>
25+
<span class="font-mono text-sm font-medium text-fg truncate" :title="header">
26+
{{ header }}
27+
</span>
28+
</div>
29+
</div>
30+
31+
<!-- Facet rows -->
32+
<slot />
33+
</div>
34+
</div>
35+
</template>
36+
37+
<style scoped>
38+
.comparison-grid {
39+
display: grid;
40+
gap: 0;
41+
}
42+
43+
.comparison-grid.columns-2 {
44+
grid-template-columns: minmax(120px, 180px) repeat(2, 1fr);
45+
}
46+
47+
.comparison-grid.columns-3 {
48+
grid-template-columns: minmax(120px, 160px) repeat(3, 1fr);
49+
}
50+
51+
.comparison-grid.columns-4 {
52+
grid-template-columns: minmax(100px, 140px) repeat(4, 1fr);
53+
}
54+
55+
.comparison-header {
56+
display: contents;
57+
}
58+
59+
.comparison-header > .comparison-label {
60+
padding: 0.75rem 1rem;
61+
border-bottom: 1px solid var(--color-border);
62+
}
63+
64+
.comparison-header > .comparison-cell-header {
65+
padding: 0.75rem 1rem;
66+
background: var(--color-bg-subtle);
67+
border-bottom: 1px solid var(--color-border);
68+
text-align: center;
69+
}
70+
71+
/* First header cell rounded top-start */
72+
.comparison-header > .comparison-cell-header:first-of-type {
73+
border-start-start-radius: 0.5rem;
74+
}
75+
76+
/* Last header cell rounded top-end */
77+
.comparison-header > .comparison-cell-header:last-of-type {
78+
border-start-end-radius: 0.5rem;
79+
}
80+
</style>

0 commit comments

Comments
 (0)