Skip to content

Commit 0ed5d0c

Browse files
committed
Merge remote-tracking branch 'origin/main' into feat/teardown
# Conflicts: # app/pages/[...package].vue # i18n/locales/en.json # lunaria/files/en-US.json # package.json # pnpm-lock.yaml
2 parents 5033ec9 + 592982d commit 0ed5d0c

254 files changed

Lines changed: 16728 additions & 4114 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/ci.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ on:
1111
branches:
1212
- main
1313

14+
# cancel in-progress runs on new commits to same PR (gitub.event.number)
15+
concurrency:
16+
group: ${{ github.workflow }}-${{ github.event.number || github.sha }}
17+
cancel-in-progress: true
18+
1419
permissions:
1520
contents: read
1621

@@ -134,4 +139,4 @@ jobs:
134139
run: pnpm install
135140

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

.oxfmtrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"$schema": "./node_modules/oxfmt/configuration_schema.json",
2+
"$schema": "https://unpkg.com/oxfmt/configuration_schema.json",
33
"semi": false,
44
"singleQuote": true,
55
"arrowParens": "avoid",

.oxlintrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"$schema": "./node_modules/oxlint/configuration_schema.json",
2+
"$schema": "https://unpkg.com/oxlint/configuration_schema.json",
33
"plugins": ["unicorn", "typescript", "oxc", "vue", "vitest"],
44
"categories": {
55
"correctness": "error",

.vscode/extensions.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"recommendations": ["oxc.oxc-vscode", "Vue.volar", "lokalise.i18n-ally"]
2+
"recommendations": ["oxc.oxc-vscode", "Vue.volar", "lokalise.i18n-ally", "antfu.unocss"]
33
}

CONTRIBUTING.md

Lines changed: 28 additions & 8 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` 💪
@@ -181,7 +187,7 @@ import { hasProtocol } from 'ufo'
181187

182188
| Type | Convention | Example |
183189
| ---------------- | ------------------------ | ------------------------------ |
184-
| Vue components | PascalCase | `MarkdownText.vue` |
190+
| Vue components | PascalCase | `DateTime.vue` |
185191
| Pages | kebab-case | `search.vue`, `[...name].vue` |
186192
| Composables | camelCase + `use` prefix | `useNpmRegistry.ts` |
187193
| Server routes | kebab-case + method | `search.get.ts` |
@@ -301,6 +307,14 @@ For example to check if all Japanese translation keys are up-to-date, run:
301307
pnpm i18n:check ja-JP
302308
```
303309

310+
To automatically add missing keys with English placeholders, use `--fix`:
311+
312+
```bash
313+
pnpm i18n:check:fix fr-FR
314+
```
315+
316+
This will add missing keys with `"EN TEXT TO REPLACE: {english text}"` as placeholder values, making it easier to see what needs translation.
317+
304318
#### Country variants (advanced)
305319

306320
Most languages only need a single locale file. Country variants are only needed when you want to support regional differences (e.g., `es-ES` for Spain vs `es-419` for Latin America).
@@ -361,13 +375,17 @@ We recommend the [i18n-ally](https://marketplace.visualstudio.com/items?itemName
361375

362376
The extension is included in our workspace recommendations, so VSCode should prompt you to install it.
363377

364-
### Formatting with locale
378+
### Formatting numbers and dates
365379

366-
When formatting numbers or dates that should respect the user's locale, pass the locale:
380+
Use vue-i18n's built-in formatters for locale-aware formatting:
367381

368-
```typescript
369-
const { locale } = useI18n()
370-
const formatted = formatNumber(12345, locale.value) // "12,345" in en-US
382+
```vue
383+
<template>
384+
<p>{{ $n(12345) }}</p>
385+
<!-- "12,345" in en-US, "12 345" in fr-FR -->
386+
<p>{{ $d(new Date()) }}</p>
387+
<!-- locale-aware date -->
388+
</template>
371389
```
372390

373391
## Testing
@@ -391,10 +409,10 @@ describe('featureName', () => {
391409
392410
### Component accessibility tests
393411

394-
All new components should have a basic accessibility test in `test/nuxt/components.spec.ts`. These tests use [axe-core](https://github.com/dequelabs/axe-core) to catch common accessibility violations.
412+
All Vue components should have accessibility tests in `test/nuxt/a11y.spec.ts`. These tests use [axe-core](https://github.com/dequelabs/axe-core) to catch common accessibility violations and run in a real browser environment via Playwright.
395413

396414
```typescript
397-
import MyComponent from '~/components/MyComponent.vue'
415+
import { MyComponent } from '#components'
398416

399417
describe('MyComponent', () => {
400418
it('should have no accessibility violations', async () => {
@@ -411,6 +429,8 @@ describe('MyComponent', () => {
411429

412430
The `runAxe` helper handles DOM isolation and disables page-level rules that don't apply to isolated component testing.
413431

432+
A coverage test in `test/unit/a11y-component-coverage.spec.ts` ensures all components are either tested or explicitly skipped with justification. When you add a new component, this test will fail until you add accessibility tests for it.
433+
414434
> [!IMPORTANT]
415435
> Just because axe-core doesn't find any obvious issues, it does not mean a component is accessible. Please do additional checks and use best practices.
416436

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

Lines changed: 76 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script setup lang="ts">
22
import type { Directions } from '@nuxtjs/i18n'
33
import { useEventListener } from '@vueuse/core'
4+
import { isEditableElement } from '~/utils/input'
45
56
const route = useRoute()
67
const router = useRouter()
@@ -10,6 +11,7 @@ const { locale, locales } = useI18n()
1011
initPreferencesOnPrehydrate()
1112
1213
const isHomepage = computed(() => route.name === 'index')
14+
const showKbdHints = shallowRef(false)
1315
1416
const localeMap = locales.value.reduce(
1517
(acc, l) => {
@@ -21,8 +23,9 @@ const localeMap = locales.value.reduce(
2123
2224
useHead({
2325
htmlAttrs: {
24-
lang: () => locale.value,
25-
dir: () => localeMap[locale.value] ?? 'ltr',
26+
'lang': () => locale.value,
27+
'dir': () => localeMap[locale.value] ?? 'ltr',
28+
'data-kbd-hints': () => showKbdHints.value,
2629
},
2730
titleTemplate: titleChunk => {
2831
return titleChunk ? titleChunk : 'npmx - Better npm Package Browser'
@@ -33,18 +36,13 @@ if (import.meta.server) {
3336
setJsonLd(createWebSiteSchema())
3437
}
3538
36-
// Global keyboard shortcut: "/" focuses search or navigates to search page
39+
// Global keyboard shortcut:
40+
// "/" focuses search or navigates to search page
41+
// "?" highlights all keyboard shortcut elements
3742
function handleGlobalKeydown(e: KeyboardEvent) {
38-
const target = e.target as HTMLElement
39-
40-
const isEditableTarget =
41-
target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable
42-
43-
if (isEditableTarget) {
44-
return
45-
}
43+
if (isEditableElement(e.target)) return
4644
47-
if (e.key === '/') {
45+
if (isKeyWithoutModifiers(e, '/')) {
4846
e.preventDefault()
4947
5048
// Try to find and focus search input on current page
@@ -59,10 +57,31 @@ function handleGlobalKeydown(e: KeyboardEvent) {
5957
6058
router.push('/search')
6159
}
60+
61+
if (isKeyWithoutModifiers(e, '?')) {
62+
e.preventDefault()
63+
showKbdHints.value = true
64+
}
65+
}
66+
67+
function handleGlobalKeyup() {
68+
showKbdHints.value = false
69+
}
70+
71+
/* A hack to get light dismiss to work in safari because it does not support closedby="any" yet */
72+
// https://codepen.io/paramagicdev/pen/gbYompq
73+
// see: https://github.com/npmx-dev/npmx.dev/pull/522#discussion_r2749978022
74+
function handleModalLightDismiss(e: MouseEvent) {
75+
const target = e.target as HTMLElement
76+
if (target.tagName === 'DIALOG' && target.hasAttribute('open')) {
77+
;(target as HTMLDialogElement).close()
78+
}
6279
}
6380
6481
if (import.meta.client) {
6582
useEventListener(document, 'keydown', handleGlobalKeydown)
83+
useEventListener(document, 'keyup', handleGlobalKeyup)
84+
useEventListener(document, 'click', handleModalLightDismiss)
6685
}
6786
</script>
6887

@@ -82,3 +101,48 @@ if (import.meta.client) {
82101
<ScrollToTop />
83102
</div>
84103
</template>
104+
105+
<style scoped>
106+
/* Skip link */
107+
.skip-link {
108+
position: fixed;
109+
top: -100%;
110+
inset-inline-start: 0;
111+
padding: 0.5rem 1rem;
112+
background: var(--fg);
113+
color: var(--bg);
114+
font-size: 0.875rem;
115+
z-index: 100;
116+
transition: top 0.2s ease;
117+
}
118+
119+
.skip-link:hover {
120+
color: var(--bg);
121+
text-decoration: underline;
122+
}
123+
.skip-link:focus {
124+
top: 0;
125+
}
126+
</style>
127+
128+
<style>
129+
/* Keyboard shortcut highlight on "?" key press */
130+
kbd {
131+
position: relative;
132+
}
133+
134+
kbd::before {
135+
content: '';
136+
position: absolute;
137+
inset: 0;
138+
border-radius: inherit;
139+
box-shadow: 0 0 4px 2px var(--accent);
140+
opacity: 0;
141+
transition: opacity 200ms ease-out;
142+
pointer-events: none;
143+
}
144+
145+
html[data-kbd-hints='true'] kbd::before {
146+
opacity: 1;
147+
}
148+
</style>

0 commit comments

Comments
 (0)