Skip to content

Commit 200554e

Browse files
committed
Merge remote-tracking branch 'origin/main' into feat/display-author-profile-picture
2 parents 485cc9c + 8825b0a commit 200554e

55 files changed

Lines changed: 5072 additions & 258 deletions

Some content is hidden

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

CONTRIBUTING.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ The connector will check your npm authentication, generate a connection token, a
105105

106106
## Code style
107107

108+
When committing changes, try to keep an eye out for unintended formatting updates. These can make a pull request look noisier than it really is and slow down the review process. Sometimes IDEs automatically reformat files on save, which can unintentionally introduce extra changes.
109+
110+
To help with this, the project uses `oxfmt` to handle formatting via a pre-commit hook. The hook will automatically reformat files when needed. If something can’t be fixed automatically, it will let you know what needs to be updated before you can commit.
111+
112+
If you want to get ahead of any formatting issues, you can also run `pnpm lint:fix` before committing to fix formatting across the whole project.
113+
108114
### Typescript
109115

110116
- We care about good types – never cast things to `any` 💪

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/PackageSkeleton.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@
150150
</div>
151151

152152
<!-- Sidebar: order-1 lg:order-2 space-y-8 -->
153-
<aside class="order-1 lg:order-2 space-y-8">
153+
<div class="order-1 lg:order-2 space-y-8">
154154
<!-- Maintainers -->
155155
<section>
156156
<h2
@@ -249,7 +249,7 @@
249249
</li>
250250
</ul>
251251
</section>
252-
</aside>
252+
</div>
253253
</div>
254254
</article>
255255
</template>

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)