Skip to content

Commit 4c4fab9

Browse files
authored
Merge branch 'main' into feat/issue-40
2 parents 4dc15c6 + 219ac17 commit 4c4fab9

70 files changed

Lines changed: 3073 additions & 843 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/provenance.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ permissions:
1414
contents: read
1515
jobs:
1616
check-provenance:
17-
runs-on: ubuntu-latest
17+
runs-on: ubuntu-slim
1818
steps:
1919
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
2020
with:

.github/workflows/semantic-pull-requests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
pull-requests: read # for amannn/action-semantic-pull-request to analyze PRs
1717
statuses: write # for amannn/action-semantic-pull-request to mark status of analyzed PR
1818
if: github.repository == 'npmx-dev/npmx.dev'
19-
runs-on: ubuntu-latest
19+
runs-on: ubuntu-slim
2020
name: semantic-pr
2121
steps:
2222
- name: Validate PR title

CONTRIBUTING.md

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,43 @@ We want to create 'a fast, modern browser for the npm registry.' This means, amo
1414
- Layout shift, flakiness, slowness is The Worst. We need to continually iterate to create the most performant, best DX possible.
1515
- We want to provide information in the best way. We don't want noise, cluttered display, or confusing UI. If in doubt: choose simplicity.
1616

17+
## Table of Contents
18+
19+
- [Getting started](#getting-started)
20+
- [Prerequisites](#prerequisites)
21+
- [Setup](#setup)
22+
- [Development workflow](#development-workflow)
23+
- [Available commands](#available-commands)
24+
- [Project structure](#project-structure)
25+
- [Local connector CLI](#local-connector-cli)
26+
- [Code style](#code-style)
27+
- [TypeScript](#typescript)
28+
- [Server API patterns](#server-api-patterns)
29+
- [Import order](#import-order)
30+
- [Naming conventions](#naming-conventions)
31+
- [Vue components](#vue-components)
32+
- [RTL Support](#rtl-support)
33+
- [Localization (i18n)](#localization-i18n)
34+
- [Approach](#approach)
35+
- [Adding a new locale](#adding-a-new-locale)
36+
- [Update translation](#update-translation)
37+
- [Adding translations](#adding-translations)
38+
- [Translation key conventions](#translation-key-conventions)
39+
- [Using i18n-ally (recommended)](#using-i18n-ally-recommended)
40+
- [Formatting numbers and dates](#formatting-numbers-and-dates)
41+
- [Testing](#testing)
42+
- [Unit tests](#unit-tests)
43+
- [Component accessibility tests](#component-accessibility-tests)
44+
- [End to end tests](#end-to-end-tests)
45+
- [Submitting changes](#submitting-changes)
46+
- [Before submitting](#before-submitting)
47+
- [Pull request process](#pull-request-process)
48+
- [Commit messages and PR titles](#commit-messages-and-pr-titles)
49+
- [Pre-commit hooks](#pre-commit-hooks)
50+
- [Using AI](#using-ai)
51+
- [Questions](#questions)
52+
- [License](#license)
53+
1754
## Getting started
1855

1956
### Prerequisites
@@ -111,7 +148,7 @@ To help with this, the project uses `oxfmt` to handle formatting via a pre-commi
111148

112149
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.
113150

114-
### Typescript
151+
### TypeScript
115152

116153
- We care about good types – never cast things to `any` 💪
117154
- Validate rather than just assert

app/components/AppHeader.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const showMobileMenu = shallowRef(false)
1919
const route = useRoute()
2020
const isMobile = useIsMobile()
2121
const isSearchExpandedManually = shallowRef(false)
22-
const searchBoxRef = shallowRef<{ focus: () => void } | null>(null)
22+
const searchBoxRef = useTemplateRef('searchBoxRef')
2323
2424
// On search page, always show search expanded on mobile
2525
const isOnHomePage = computed(() => route.name === 'index')
@@ -146,7 +146,7 @@ onKeyStroke(
146146
@blur="handleSearchBlur"
147147
/>
148148
<ul
149-
v-if="!isSearchExpanded"
149+
v-if="!isSearchExpanded && isConnected && npmUser"
150150
:class="{ hidden: showFullSearch }"
151151
class="hidden sm:flex items-center gap-4 sm:gap-6 list-none m-0 p-0"
152152
>

app/components/BaseCard.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ defineProps<{
1515
<!-- Glow effect for exact matches -->
1616
<div
1717
v-if="isExactMatch"
18-
class="absolute -inset-px rounded-lg bg-gradient-to-r from-accent/0 via-accent/20 to-accent/0 opacity-100 blur-sm -z-1 pointer-events-none motion-reduce:opacity-50"
18+
class="absolute -inset-px rounded-lg bg-gradient-to-r from-accent/0 via-accent/0 to-accent/10 opacity-100 blur-sm -z-1 pointer-events-none motion-reduce:opacity-50"
1919
aria-hidden="true"
2020
/>
2121
<slot />

app/components/CallToAction.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ const socialLinks = {
1212
{{ $t('about.get_involved.title') }}
1313
</h2>
1414

15-
<div class="grid gap-4 sm:grid-cols-3">
15+
<div class="grid gap-4 sm:grid-cols-3 sm:items-stretch sm:grid-rows-[auto,1fr,auto]">
1616
<a
1717
:href="socialLinks.github"
1818
target="_blank"
1919
rel="noopener noreferrer"
20-
class="group flex flex-col gap-3 p-4 rounded-lg bg-bg-subtle hover:bg-bg-elevated border border-border hover:border-border-hover transition-all duration-200"
20+
class="group grid gap-3 p-4 rounded-lg bg-bg-subtle hover:bg-bg-elevated border border-border hover:border-border-hover transition-all duration-200 sm:grid-rows-subgrid sm:row-span-3"
2121
>
2222
<div class="flex gap-2">
2323
<span class="i-carbon:logo-github shrink-0 mt-1 w-5 h-5 text-fg" aria-hidden="true" />
@@ -40,7 +40,7 @@ const socialLinks = {
4040
:href="socialLinks.discord"
4141
target="_blank"
4242
rel="noopener noreferrer"
43-
class="group flex flex-col gap-3 p-4 rounded-lg bg-bg-subtle hover:bg-bg-elevated border border-border hover:border-border-hover transition-all duration-200"
43+
class="group grid gap-3 p-4 rounded-lg bg-bg-subtle hover:bg-bg-elevated border border-border hover:border-border-hover transition-all duration-200 sm:grid-rows-subgrid sm:row-span-3"
4444
>
4545
<div class="flex gap-2">
4646
<span class="i-carbon:chat shrink-0 mt-1 w-5 h-5 text-fg" aria-hidden="true" />
@@ -63,7 +63,7 @@ const socialLinks = {
6363
:href="socialLinks.bluesky"
6464
target="_blank"
6565
rel="noopener noreferrer"
66-
class="group flex flex-col gap-3 p-4 rounded-lg bg-bg-subtle hover:bg-bg-elevated border border-border hover:border-border-hover transition-all duration-200"
66+
class="group grid gap-3 p-4 rounded-lg bg-bg-subtle hover:bg-bg-elevated border border-border hover:border-border-hover transition-all duration-200 sm:grid-rows-subgrid sm:row-span-3"
6767
>
6868
<div class="flex gap-2">
6969
<span class="i-simple-icons:bluesky shrink-0 mt-1 w-5 h-5 text-fg" aria-hidden="true" />

app/components/CollapsibleSection.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ interface Props {
66
isLoading?: boolean
77
headingLevel?: `h${number}`
88
id: string
9+
icon?: string
910
}
1011
1112
const props = withDefaults(defineProps<Props>(), {
@@ -107,6 +108,7 @@ useHead({
107108
:href="`#${id}`"
108109
class="inline-flex items-center gap-1.5 text-fg-subtle hover:text-fg-muted transition-colors duration-200 no-underline"
109110
>
111+
<span v-if="icon" :class="icon" aria-hidden="true" />
110112
{{ title }}
111113
<span
112114
class="i-carbon:link w-3 h-3 opacity-0 group-hover:opacity-100 transition-opacity duration-200"

app/components/Filter/Chips.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const emit = defineEmits<{
1414
<template>
1515
<div v-if="chips.length > 0" class="flex flex-wrap items-center gap-2">
1616
<TransitionGroup name="chip">
17-
<span v-for="chip in chips" :key="chip.id" class="tag gap-1">
17+
<TagStatic v-for="chip in chips" :key="chip.id" class="gap-1">
1818
<span class="text-fg-subtle text-xs">{{ chip.label }}:</span>
1919
<span class="max-w-32 truncate">{{
2020
Array.isArray(chip.value) ? chip.value.join(', ') : chip.value
@@ -27,7 +27,7 @@ const emit = defineEmits<{
2727
>
2828
<span class="i-carbon-close w-3 h-3" aria-hidden="true" />
2929
</button>
30-
</span>
30+
</TagStatic>
3131
</TransitionGroup>
3232

3333
<button

app/components/Filter/Panel.vue

Lines changed: 25 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import type {
88
} from '#shared/types/preferences'
99
import {
1010
DOWNLOAD_RANGES,
11-
SEARCH_SCOPE_OPTIONS,
12-
SECURITY_FILTER_OPTIONS,
11+
SEARCH_SCOPE_VALUES,
12+
SECURITY_FILTER_VALUES,
1313
UPDATED_WITHIN_OPTIONS,
1414
} from '#shared/types/preferences'
1515
@@ -205,20 +205,20 @@ const hasActiveFilters = computed(() => !!filterSummary.value)
205205
:aria-label="$t('filters.search_scope')"
206206
>
207207
<button
208-
v-for="option in SEARCH_SCOPE_OPTIONS"
209-
:key="option.value"
208+
v-for="scope in SEARCH_SCOPE_VALUES"
209+
:key="scope"
210210
type="button"
211211
class="px-2 py-0.5 text-xs font-mono rounded-sm transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-fg focus-visible:ring-offset-1"
212212
:class="
213-
filters.searchScope === option.value
213+
filters.searchScope === scope
214214
? 'bg-bg-muted text-fg'
215215
: 'text-fg-muted hover:text-fg'
216216
"
217-
:aria-pressed="filters.searchScope === option.value"
218-
:title="$t(getScopeDescriptionKey(option.value))"
219-
@click="emit('update:searchScope', option.value)"
217+
:aria-pressed="filters.searchScope === scope"
218+
:title="$t(getScopeDescriptionKey(scope))"
219+
@click="emit('update:searchScope', scope)"
220220
>
221-
{{ $t(getScopeLabelKey(option.value)) }}
221+
{{ $t(getScopeLabelKey(scope)) }}
222222
</button>
223223
</div>
224224
</div>
@@ -243,22 +243,17 @@ const hasActiveFilters = computed(() => !!filterSummary.value)
243243
role="radiogroup"
244244
:aria-label="$t('filters.weekly_downloads')"
245245
>
246-
<button
246+
<TagClickable
247247
v-for="range in DOWNLOAD_RANGES"
248248
:key="range.value"
249249
type="button"
250250
role="radio"
251251
:aria-checked="filters.downloadRange === range.value"
252-
class="tag transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-fg focus-visible:ring-offset-1"
253-
:class="
254-
filters.downloadRange === range.value
255-
? 'bg-fg text-bg border-fg hover:text-bg/50'
256-
: ''
257-
"
252+
:status="filters.downloadRange === range.value ? 'active' : 'default'"
258253
@click="emit('update:downloadRange', range.value)"
259254
>
260255
{{ $t(getDownloadRangeLabelKey(range.value)) }}
261-
</button>
256+
</TagClickable>
262257
</div>
263258
</fieldset>
264259

@@ -272,22 +267,17 @@ const hasActiveFilters = computed(() => !!filterSummary.value)
272267
role="radiogroup"
273268
:aria-label="$t('filters.updated_within')"
274269
>
275-
<button
270+
<TagClickable
276271
v-for="option in UPDATED_WITHIN_OPTIONS"
277272
:key="option.value"
278273
type="button"
279274
role="radio"
280275
:aria-checked="filters.updatedWithin === option.value"
281-
class="tag transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-fg focus-visible:ring-offset-1"
282-
:class="
283-
filters.updatedWithin === option.value
284-
? 'bg-fg text-bg border-fg hover:text-bg/70'
285-
: ''
286-
"
276+
:status="filters.updatedWithin === option.value ? 'active' : 'default'"
287277
@click="emit('update:updatedWithin', option.value)"
288278
>
289279
{{ $t(getUpdatedWithinLabelKey(option.value)) }}
290-
</button>
280+
</TagClickable>
291281
</div>
292282
</fieldset>
293283

@@ -300,20 +290,17 @@ const hasActiveFilters = computed(() => !!filterSummary.value)
300290
</span>
301291
</legend>
302292
<div class="flex flex-wrap gap-2" role="radiogroup" :aria-label="$t('filters.security')">
303-
<button
304-
v-for="option in SECURITY_FILTER_OPTIONS"
305-
:key="option.value"
293+
<TagClickable
294+
v-for="security in SECURITY_FILTER_VALUES"
295+
:key="security"
306296
type="button"
307297
role="radio"
308298
disabled
309-
:aria-checked="filters.security === option.value"
310-
class="tag transition-colors duration-200 opacity-50 cursor-not-allowed focus-visible:ring-2 focus-visible:ring-fg focus-visible:ring-offset-1"
311-
:class="
312-
filters.security === option.value ? 'bg-fg text-bg border-fg hover:text-bg/70' : ''
313-
"
299+
:aria-checked="filters.security === security"
300+
:status="filters.security === security ? 'active' : 'default'"
314301
>
315-
{{ $t(getSecurityLabelKey(option.value)) }}
316-
</button>
302+
{{ $t(getSecurityLabelKey(security)) }}
303+
</TagClickable>
317304
</div>
318305
</fieldset>
319306

@@ -323,19 +310,16 @@ const hasActiveFilters = computed(() => !!filterSummary.value)
323310
{{ $t('filters.keywords') }}
324311
</legend>
325312
<div class="flex flex-wrap gap-1.5" role="group" :aria-label="$t('filters.keywords')">
326-
<button
313+
<TagClickable
327314
v-for="keyword in displayedKeywords"
328315
:key="keyword"
329316
type="button"
330317
:aria-pressed="filters.keywords.includes(keyword)"
331-
class="tag text-xs transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-fg focus-visible:ring-offset-1"
332-
:class="
333-
filters.keywords.includes(keyword) ? 'bg-fg text-bg border-fg hover:text-bg/70' : ''
334-
"
318+
:status="filters.keywords.includes(keyword) ? 'active' : 'default'"
335319
@click="emit('toggleKeyword', keyword)"
336320
>
337321
{{ keyword }}
338-
</button>
322+
</TagClickable>
339323
<button
340324
v-if="hasMoreKeywords"
341325
type="button"

app/components/Header/AccountMenu.client.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@ function openAuthModal() {
5555
</script>
5656

5757
<template>
58-
<div ref="accountMenuRef" class="relative">
58+
<div ref="accountMenuRef" class="relative flex min-w-24 justify-end">
5959
<button
6060
type="button"
61-
class="relative flex items-center justify-end gap-2 px-2 py-1.5 min-w-24 rounded-md transition-colors duration-200 hover:bg-bg-subtle focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
61+
class="relative flex items-center gap-2 px-2 py-1.5 rounded-md transition-colors duration-200 hover:bg-bg-subtle focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
6262
:aria-expanded="isOpen"
6363
aria-haspopup="true"
6464
@click="isOpen = !isOpen"

0 commit comments

Comments
 (0)