Skip to content

Commit 7aa525a

Browse files
authored
Merge branch 'main' into package-card-rtl
2 parents 290736f + d168778 commit 7aa525a

101 files changed

Lines changed: 8090 additions & 774 deletions

File tree

Some content is hidden

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

.github/workflows/autofix.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ jobs:
4242
- name: 🏃 Update component test snapshots
4343
run: pnpm test:nuxt -u
4444

45-
- name: 🖥️ Update browser test snapshots
46-
run: pnpm test:browser --update-snapshots
45+
# TODO: re-enable when we have snapshots in browser tests
46+
# - name: 🖥️ Update browser test snapshots
47+
# run: pnpm test:browser --update-snapshots
4748

4849
- uses: autofix-ci/action@635ffb0c9798bd160680f18fd73371e355b85f27

CONTRIBUTING.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,18 @@ import { hasProtocol } from 'ufo'
189189
| Constants | SCREAMING_SNAKE_CASE | `NPM_REGISTRY`, `ALLOWED_TAGS` |
190190
| Types/Interfaces | PascalCase | `NpmSearchResponse` |
191191

192+
> [!TIP]
193+
> Exports in `app/composables/`, `app/utils/`, and `server/utils/` are auto-imported by Nuxt. To prevent [knip](https://knip.dev/) from flagging them as unused, add a `@public` JSDoc annotation:
194+
>
195+
> ```typescript
196+
> /**
197+
> * @public
198+
> */
199+
> export function myAutoImportedFunction() {
200+
> // ...
201+
> }
202+
> ```
203+
192204
### Vue components
193205
194206
- Use Composition API with `<script setup lang="ts">`
@@ -230,7 +242,7 @@ npmx.dev uses [@nuxtjs/i18n](https://i18n.nuxtjs.org/) for internationalization.
230242
### Approach
231243

232244
- All user-facing strings should use translation keys via `$t()` in templates and script
233-
- Translation files live in `i18n/locales/` (e.g., `en-US.json`)
245+
- Translation files live in [`i18n/locales/`](i18n/locales) (e.g., `en-US.json`)
234246
- We use the `no_prefix` strategy (no `/en-US/` or `/fr-FR/` in URLs)
235247
- Locale preference is stored in cookies and respected on subsequent visits
236248

app/app.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ useHead({
1515
},
1616
})
1717
18+
if (import.meta.server) {
19+
setJsonLd(createWebSiteSchema())
20+
}
21+
1822
// Global keyboard shortcut: "/" focuses search or navigates to search page
1923
function handleGlobalKeydown(e: KeyboardEvent) {
2024
const target = e.target as HTMLElement

app/assets/main.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,9 @@ html.light .shiki span {
281281
white-space: pre;
282282
word-break: normal;
283283
overflow-wrap: normal;
284+
/* Makes unicode and ascii art work properly */
285+
line-height: 1.25;
286+
display: inline-block;
284287
}
285288

286289
.readme-content ul,

app/components/AccentColorPicker.vue

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,37 @@ const { accentColors, selectedAccentColor, setAccentColor } = useAccentColor()
55
</script>
66

77
<template>
8-
<div role="listbox" :aria-label="$t('settings.accent_colors')" class="flex items-center gap-4">
9-
<button
8+
<fieldset class="flex items-center gap-4">
9+
<legend class="sr-only">{{ $t('settings.accent_colors') }}</legend>
10+
<label
1011
v-for="color in accentColors"
1112
:key="color.id"
12-
type="button"
13-
role="option"
14-
:aria-selected="selectedAccentColor === color.id"
15-
:aria-label="color.name"
16-
class="size-6 rounded-full transition-transform duration-150 motion-safe:hover:scale-110 focus-ring aria-selected:(ring-2 ring-fg ring-offset-2 ring-offset-bg-subtle)"
13+
class="size-6 rounded-full transition-transform duration-150 motion-safe:hover:scale-110 cursor-pointer has-[:checked]:(ring-2 ring-fg ring-offset-2 ring-offset-bg-subtle) has-[:focus-visible]:(ring-2 ring-fg ring-offset-2 ring-offset-bg-subtle)"
1714
:style="{ backgroundColor: color.value }"
18-
@click="setAccentColor(color.id)"
19-
/>
20-
<button
21-
type="button"
22-
:aria-label="$t('settings.clear_accent')"
23-
class="size-6 rounded-full transition-transform duration-150 motion-safe:hover:scale-110 focus-ring flex items-center justify-center bg-accent-fallback"
24-
@click="setAccentColor(null)"
2515
>
16+
<input
17+
type="radio"
18+
name="accent-color"
19+
class="sr-only"
20+
:value="color.id"
21+
:checked="selectedAccentColor === color.id"
22+
:aria-label="color.name"
23+
@change="setAccentColor(color.id)"
24+
/>
25+
</label>
26+
<label
27+
class="size-6 rounded-full transition-transform duration-150 motion-safe:hover:scale-110 cursor-pointer has-[:checked]:(ring-2 ring-fg ring-offset-2 ring-offset-bg-subtle) has-[:focus-visible]:(ring-2 ring-fg ring-offset-2 ring-offset-bg-subtle) flex items-center justify-center bg-accent-fallback"
28+
>
29+
<input
30+
type="radio"
31+
name="accent-color"
32+
class="sr-only"
33+
value=""
34+
:checked="selectedAccentColor === null"
35+
:aria-label="$t('settings.clear_accent')"
36+
@change="setAccentColor(null)"
37+
/>
2638
<span class="i-carbon-error size-4 text-bg" aria-hidden="true" />
27-
</button>
28-
</div>
39+
</label>
40+
</fieldset>
2941
</template>

app/components/AppFooter.vue

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
<template>
22
<footer class="border-t border-border mt-auto">
33
<div class="container py-3 sm:py-8 flex flex-col gap-2 sm:gap-4 text-fg-subtle text-sm">
4-
<div class="flex flex-col sm:flex-row items-center justify-between gap-2 sm:gap-4">
5-
<p class="font-mono m-0 hidden sm:block">{{ $t('tagline') }}</p>
4+
<div
5+
class="flex flex-col sm:flex-row items-center sm:items-baseline justify-between gap-2 sm:gap-4"
6+
>
7+
<p class="font-mono text-balance m-0 hidden sm:block">{{ $t('tagline') }}</p>
68
<div class="flex items-center gap-3 sm:gap-6">
79
<NuxtLink
810
to="/about"
@@ -17,7 +19,7 @@
1719
class="link-subtle font-mono text-xs min-h-8 sm:min-h-11 flex items-center gap-1"
1820
>
1921
{{ $t('footer.docs') }}
20-
<span class="i-carbon-launch w-3 h-3" aria-hidden="true" />
22+
<span class="i-carbon:launch rtl-flip w-3 h-3" aria-hidden="true" />
2123
</a>
2224
<a
2325
href="https://repo.npmx.dev"
@@ -26,7 +28,7 @@
2628
class="link-subtle font-mono text-xs min-h-8 sm:min-h-11 flex items-center gap-1"
2729
>
2830
{{ $t('footer.source') }}
29-
<span class="i-carbon-launch w-3 h-3" aria-hidden="true" />
31+
<span class="i-carbon:launch rtl-flip w-3 h-3" aria-hidden="true" />
3032
</a>
3133
<a
3234
href="https://social.npmx.dev"
@@ -35,7 +37,7 @@
3537
class="link-subtle font-mono text-xs min-h-8 sm:min-h-11 flex items-center gap-1"
3638
>
3739
{{ $t('footer.social') }}
38-
<span class="i-carbon-launch w-3 h-3" aria-hidden="true" />
40+
<span class="i-carbon:launch rtl-flip w-3 h-3" aria-hidden="true" />
3941
</a>
4042
<a
4143
href="https://chat.npmx.dev"
@@ -44,11 +46,11 @@
4446
class="link-subtle font-mono text-xs min-h-8 sm:min-h-11 flex items-center gap-1"
4547
>
4648
{{ $t('footer.chat') }}
47-
<span class="i-carbon-launch w-3 h-3" aria-hidden="true" />
49+
<span class="i-carbon:launch rtl-flip w-3 h-3" aria-hidden="true" />
4850
</a>
4951
</div>
5052
</div>
51-
<p class="text-xs text-fg-muted text-center sm:text-left m-0">
53+
<p class="text-xs text-fg-muted text-center sm:text-start m-0">
5254
<span class="sm:hidden">{{ $t('non_affiliation_disclaimer') }}</span>
5355
<span class="hidden sm:inline">{{ $t('trademark_disclaimer') }}</span>
5456
</p>

app/components/AppHeader.vue

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,11 @@ onKeyStroke(',', e => {
5252

5353
<template>
5454
<header class="sticky top-0 z-50 bg-bg/80 backdrop-blur-md border-b border-border">
55-
<nav :aria-label="$t('nav.main_navigation')" class="container h-14 flex items-center">
56-
<!-- Left: Logo -->
55+
<nav
56+
:aria-label="$t('nav.main_navigation')"
57+
class="container h-14 flex items-center justify-start"
58+
>
59+
<!-- Start: Logo -->
5760
<div class="flex-shrink-0">
5861
<NuxtLink
5962
v-if="showLogo"
@@ -79,7 +82,7 @@ onKeyStroke(',', e => {
7982
<div class="relative group" :class="{ 'is-focused': isSearchFocused }">
8083
<div class="search-box relative flex items-center">
8184
<span
82-
class="absolute left-3 text-fg-subtle font-mono text-sm pointer-events-none transition-colors duration-200 motion-reduce:transition-none group-focus-within:text-accent z-1"
85+
class="absolute inset-is-3 text-fg-subtle font-mono text-sm pointer-events-none transition-colors duration-200 motion-reduce:transition-none group-focus-within:text-accent z-1"
8386
>
8487
/
8588
</span>
@@ -91,7 +94,7 @@ onKeyStroke(',', e => {
9194
name="q"
9295
:placeholder="$t('search.placeholder')"
9396
v-bind="noCorrect"
94-
class="w-full bg-bg-subtle border border-border rounded-md pl-7 pr-3 py-1.5 font-mono text-sm text-fg placeholder:text-fg-subtle transition-border-color duration-300 motion-reduce:transition-none focus:border-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/50"
97+
class="w-full bg-bg-subtle border border-border rounded-md ps-7 pe-3 py-1.5 font-mono text-sm text-fg placeholder:text-fg-subtle transition-border-color duration-300 motion-reduce:transition-none focus:border-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/50"
9598
@input="handleSearchInput"
9699
@focus="isSearchFocused = true"
97100
@blur="isSearchFocused = false"
@@ -115,8 +118,8 @@ onKeyStroke(',', e => {
115118
</ul>
116119
</div>
117120

118-
<!-- Right: User status + GitHub -->
119-
<div class="flex-shrink-0 flex items-center gap-4 sm:gap-6 ml-auto sm:ml-0">
121+
<!-- End: User status + GitHub -->
122+
<div class="flex-shrink-0 flex items-center gap-4 sm:gap-6 ms-auto sm:ms-0">
120123
<NuxtLink
121124
to="/about"
122125
class="sm:hidden link-subtle font-mono text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/50 rounded"

app/components/CodeDirectoryListing.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ function formatBytes(bytes: number): string {
7070
:to="parentPath ? `${baseUrl}/${parentPath}` : baseUrl"
7171
class="flex items-center gap-2 font-mono text-sm text-fg-muted hover:text-fg transition-colors"
7272
>
73-
<span class="i-carbon-folder w-4 h-4 text-yellow-600" />
73+
<span class="i-carbon:folder w-4 h-4 text-yellow-600" />
7474
<span>..</span>
7575
</NuxtLink>
7676
</td>
@@ -91,13 +91,13 @@ function formatBytes(bytes: number): string {
9191
>
9292
<span
9393
v-if="node.type === 'directory'"
94-
class="i-carbon-folder w-4 h-4 text-yellow-600"
94+
class="i-carbon:folder w-4 h-4 text-yellow-600"
9595
/>
9696
<span v-else class="w-4 h-4" :class="getFileIcon(node.name)" />
9797
<span>{{ node.name }}</span>
9898
</NuxtLink>
9999
</td>
100-
<td class="py-2 px-4 text-right font-mono text-xs text-fg-subtle">
100+
<td class="py-2 px-4 text-end font-mono text-xs text-fg-subtle">
101101
<span v-if="node.type === 'file' && node.size">
102102
{{ formatBytes(node.size) }}
103103
</span>

app/components/CodeFileTree.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,21 +39,21 @@ watch(
3939
<template v-if="node.type === 'directory'">
4040
<button
4141
type="button"
42-
class="w-full flex items-center gap-1.5 py-1.5 px-3 text-left font-mono text-sm transition-colors hover:bg-bg-muted"
42+
class="w-full flex items-center gap-1.5 py-1.5 px-3 text-start font-mono text-sm transition-colors hover:bg-bg-muted"
4343
:class="isNodeActive(node) ? 'text-fg' : 'text-fg-muted'"
4444
:style="{ paddingLeft: `${depth * 12 + 12}px` }"
4545
@click="toggleDir(node.path)"
4646
>
4747
<span
4848
class="w-4 h-4 shrink-0 transition-transform"
49-
:class="[isExpanded(node.path) ? 'i-carbon-chevron-down' : 'i-carbon-chevron-right']"
49+
:class="[isExpanded(node.path) ? 'i-carbon:chevron-down' : 'i-carbon:chevron-right']"
5050
/>
5151
<span
5252
class="w-4 h-4 shrink-0"
5353
:class="
5454
isExpanded(node.path)
55-
? 'i-carbon-folder-open text-yellow-500'
56-
: 'i-carbon-folder text-yellow-600'
55+
? 'i-carbon:folder-open text-yellow-500'
56+
: 'i-carbon:folder text-yellow-600'
5757
"
5858
/>
5959
<span class="truncate">{{ node.name }}</span>

app/components/CodeMobileTreeDrawer.vue

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ watch(isOpen, open => (isLocked.value = open))
2727
<!-- Toggle button (mobile only) -->
2828
<button
2929
type="button"
30-
class="md:hidden fixed bottom-4 right-4 z-40 w-12 h-12 bg-bg-elevated border border-border rounded-full shadow-lg flex items-center justify-center text-fg-muted hover:text-fg transition-colors"
30+
class="md:hidden fixed bottom-4 inset-ie-4 z-40 w-12 h-12 bg-bg-elevated border border-border rounded-full shadow-lg flex items-center justify-center text-fg-muted hover:text-fg transition-colors"
3131
:aria-label="$t('code.toggle_tree')"
3232
@click="isOpen = !isOpen"
3333
>
34-
<span class="w-5 h-5" :class="isOpen ? 'i-carbon-close' : 'i-carbon-folder'" />
34+
<span class="w-5 h-5" :class="isOpen ? 'i-carbon:close' : 'i-carbon:folder'" />
3535
</button>
3636

3737
<!-- Backdrop -->
@@ -57,19 +57,20 @@ watch(isOpen, open => (isLocked.value = open))
5757
>
5858
<aside
5959
v-if="isOpen"
60-
class="md:hidden fixed inset-y-0 left-0 z-50 w-72 bg-bg-subtle border-r border-border overflow-y-auto"
60+
class="md:hidden fixed inset-y-0 inset-is-0 z-50 w-72 bg-bg-subtle border-ie border-border overflow-y-auto"
6161
>
6262
<div
63-
class="sticky top-0 bg-bg-subtle border-b border-border px-4 py-3 flex items-center justify-between"
63+
class="sticky top-0 bg-bg-subtle border-b border-border px-4 py-3 flex items-center justify-start"
6464
>
6565
<span class="font-mono text-sm text-fg-muted">{{ $t('code.files_label') }}</span>
66+
<span aria-hidden="true" class="flex-shrink-1 flex-grow-1" />
6667
<button
6768
type="button"
6869
class="text-fg-muted hover:text-fg transition-colors"
6970
:aria-label="$t('code.close_tree')"
7071
@click="isOpen = false"
7172
>
72-
<span class="i-carbon-close w-5 h-5" />
73+
<span class="i-carbon:close w-5 h-5" />
7374
</button>
7475
</div>
7576
<CodeFileTree :tree="tree" :current-path="currentPath" :base-url="baseUrl" />

0 commit comments

Comments
 (0)