Skip to content

Commit 11bcd36

Browse files
committed
Merge origin/main into dwells-feat-docs-wasm-attempt
2 parents 58dbc53 + 4232ae2 commit 11bcd36

49 files changed

Lines changed: 2037 additions & 491 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: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ npmx.dev uses [@nuxtjs/i18n](https://i18n.nuxtjs.org/) for internationalization.
214214

215215
### Approach
216216

217-
- All user-facing strings should use translation keys via `$t()` in templates or `t()` in script
217+
- All user-facing strings should use translation keys via `$t()` in templates and script
218218
- Translation files live in `i18n/locales/` (e.g., `en.json`)
219219
- We use the `no_prefix` strategy (no `/en/` or `/fr/` in URLs)
220220
- Locale preference is stored in cookies and respected on subsequent visits
@@ -233,8 +233,9 @@ npmx.dev uses [@nuxtjs/i18n](https://i18n.nuxtjs.org/) for internationalization.
233233
Or in script:
234234

235235
```typescript
236-
const { t } = useI18n()
237-
const message = t('my.translation.key')
236+
<script setup lang="ts">
237+
const message = computed(() => $t('my.translation.key'))
238+
</script>
238239
```
239240

240241
3. For dynamic values, use interpolation:

app/app.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import { useEventListener } from '@vueuse/core'
44
const route = useRoute()
55
const router = useRouter()
66
7+
// Initialize accent color before hydration to prevent flash
8+
initAccentOnPrehydrate()
9+
710
const isHomepage = computed(() => route.path === '/')
811
912
useHead({
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<script setup lang="ts">
2+
import { useAccentColor } from '~/composables/useSettings'
3+
4+
const { accentColors, selectedAccentColor, setAccentColor } = useAccentColor()
5+
</script>
6+
7+
<template>
8+
<div role="listbox" aria-label="Accent colors" class="flex items-center justify-between">
9+
<button
10+
v-for="color in accentColors"
11+
: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)"
17+
:style="{ backgroundColor: color.value }"
18+
@click="setAccentColor(color.id)"
19+
/>
20+
<button
21+
type="button"
22+
aria-label="Clear accent color"
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)"
25+
>
26+
<span class="i-carbon-error size-4 text-bg" aria-hidden="true" />
27+
</button>
28+
</div>
29+
</template>

app/components/AppFooter.vue

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,12 @@ onMounted(() => {
137137
z-index: 40;
138138
/* Hidden by default (translated off-screen) */
139139
transform: translateY(100%);
140-
transition: transform 0.3s ease-out;
140+
}
141+
142+
@media (prefers-reduced-motion: no-preference) {
143+
.footer-scroll-state {
144+
transition: transform 0.3s ease-out;
145+
}
141146
}
142147
143148
/* Show footer when user can scroll up (meaning they've scrolled down) */

app/components/AppHeader.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const { isConnected, npmUser } = useConnector()
2727
:aria-label="$t('header.home')"
2828
class="header-logo font-mono text-lg font-medium text-fg hover:text-fg transition-colors duration-200 focus-ring rounded"
2929
>
30-
<span class="text-fg-subtle"><span style="letter-spacing: -0.2em">.</span>/</span>npmx
30+
<span class="text-accent"><span class="-tracking-0.2em">.</span>/</span>npmx
3131
</NuxtLink>
3232
<!-- Spacer when logo is hidden -->
3333
<span v-else class="w-1" />

app/components/ClaimPackageModal.vue

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ const props = defineProps<{
88
99
const open = defineModel<boolean>('open', { default: false })
1010
11-
const { t } = useI18n()
12-
1311
const {
1412
isConnected,
1513
state,
@@ -34,7 +32,7 @@ async function checkAvailability() {
3432
try {
3533
checkResult.value = await checkPackageName(props.packageName)
3634
} catch (err) {
37-
publishError.value = err instanceof Error ? err.message : t('claim.modal.failed_to_check')
35+
publishError.value = err instanceof Error ? err.message : $t('claim.modal.failed_to_check')
3836
} finally {
3937
isChecking.value = false
4038
}
@@ -84,7 +82,7 @@ async function handleClaim() {
8482
connectorModalOpen.value = true
8583
}
8684
} catch (err) {
87-
publishError.value = err instanceof Error ? err.message : t('claim.modal.failed_to_claim')
85+
publishError.value = err instanceof Error ? err.message : $t('claim.modal.failed_to_claim')
8886
} finally {
8987
isPublishing.value = false
9088
}
@@ -171,7 +169,7 @@ const connectorModalOpen = shallowRef(false)
171169

172170
<!-- Loading state -->
173171
<div v-if="isChecking" class="py-8 text-center">
174-
<LoadingSpinner :text="t('claim.modal.checking')" />
172+
<LoadingSpinner :text="$t('claim.modal.checking')" />
175173
</div>
176174

177175
<!-- Success state -->

app/components/ConnectorStatus.client.vue

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,10 @@ const {
1212
const showModal = shallowRef(false)
1313
const showTooltip = shallowRef(false)
1414
15-
const { t } = useI18n()
16-
1715
const tooltipText = computed(() => {
18-
if (isConnecting.value) return t('connector.status.connecting')
19-
if (isConnected.value) return t('connector.status.connected')
20-
return t('connector.status.connect_cli')
16+
if (isConnecting.value) return $t('connector.status.connecting')
17+
if (isConnected.value) return $t('connector.status.connected')
18+
return $t('connector.status.connect_cli')
2119
})
2220
2321
const statusColor = computed(() => {
@@ -31,9 +29,9 @@ const operationCount = computed(() => activeOperations.value.length)
3129
3230
const ariaLabel = computed(() => {
3331
if (error.value) return error.value
34-
if (isConnecting.value) return t('connector.status.aria_connecting')
35-
if (isConnected.value) return t('connector.status.aria_connected')
36-
return t('connector.status.aria_click_to_connect')
32+
if (isConnecting.value) return $t('connector.status.aria_connecting')
33+
if (isConnected.value) return $t('connector.status.aria_connected')
34+
return $t('connector.status.aria_click_to_connect')
3735
})
3836
</script>
3937

@@ -62,7 +60,7 @@ const ariaLabel = computed(() => {
6260
<img
6361
v-if="isConnected && avatar"
6462
:src="avatar"
65-
:alt="t('connector.status.avatar_alt', { user: npmUser })"
63+
:alt="$t('connector.status.avatar_alt', { user: npmUser })"
6664
width="24"
6765
height="24"
6866
class="w-6 h-6 rounded-full"

app/components/HeaderOrgsDropdown.vue

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ const props = defineProps<{
33
username: string
44
}>()
55
6-
const { t } = useI18n()
76
const { listUserOrgs } = useConnector()
87
98
const isOpen = ref(false)
@@ -23,11 +22,11 @@ async function loadOrgs() {
2322
// Already sorted alphabetically by server, take top 10
2423
orgs.value = orgList.slice(0, 10)
2524
} else {
26-
error.value = t('header.orgs_dropdown.error')
25+
error.value = $t('header.orgs_dropdown.error')
2726
}
2827
hasLoaded.value = true
2928
} catch {
30-
error.value = t('header.orgs_dropdown.error')
29+
error.value = $t('header.orgs_dropdown.error')
3130
} finally {
3231
isLoading.value = false
3332
}
@@ -62,7 +61,7 @@ function handleKeydown(event: KeyboardEvent) {
6261
:to="`/~${username}/orgs`"
6362
class="link-subtle font-mono text-sm inline-flex items-center gap-1"
6463
>
65-
{{ t('header.orgs') }}
64+
{{ $t('header.orgs') }}
6665
<span
6766
class="i-carbon-chevron-down w-3 h-3 transition-transform duration-200"
6867
:class="{ 'rotate-180': isOpen }"
@@ -80,16 +79,16 @@ function handleKeydown(event: KeyboardEvent) {
8079
<div class="bg-bg-elevated border border-border rounded-lg shadow-lg overflow-hidden">
8180
<div class="px-3 py-2 border-b border-border">
8281
<span class="font-mono text-xs text-fg-subtle">{{
83-
t('header.orgs_dropdown.title')
82+
$t('header.orgs_dropdown.title')
8483
}}</span>
8584
</div>
8685

8786
<div v-if="isLoading" class="px-3 py-4 text-center">
88-
<span class="text-fg-muted text-sm">{{ t('header.orgs_dropdown.loading') }}</span>
87+
<span class="text-fg-muted text-sm">{{ $t('header.orgs_dropdown.loading') }}</span>
8988
</div>
9089

9190
<div v-else-if="error" class="px-3 py-4 text-center">
92-
<span class="text-fg-muted text-sm">{{ t('header.orgs_dropdown.error') }}</span>
91+
<span class="text-fg-muted text-sm">{{ $t('header.orgs_dropdown.error') }}</span>
9392
</div>
9493

9594
<ul v-else-if="orgs.length > 0" class="py-1 max-h-80 overflow-y-auto">
@@ -104,15 +103,15 @@ function handleKeydown(event: KeyboardEvent) {
104103
</ul>
105104

106105
<div v-else class="px-3 py-4 text-center">
107-
<span class="text-fg-muted text-sm">{{ t('header.orgs_dropdown.empty') }}</span>
106+
<span class="text-fg-muted text-sm">{{ $t('header.orgs_dropdown.empty') }}</span>
108107
</div>
109108

110109
<div class="px-3 py-2 border-t border-border">
111110
<NuxtLink
112111
:to="`/~${username}/orgs`"
113112
class="link-subtle font-mono text-xs inline-flex items-center gap-1"
114113
>
115-
{{ t('header.orgs_dropdown.view_all') }}
114+
{{ $t('header.orgs_dropdown.view_all') }}
116115
<span class="i-carbon-arrow-right w-3 h-3" aria-hidden="true" />
117116
</NuxtLink>
118117
</div>

app/components/HeaderPackagesDropdown.vue

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ const props = defineProps<{
33
username: string
44
}>()
55
6-
const { t } = useI18n()
76
const { listUserPackages } = useConnector()
87
98
const isOpen = ref(false)
@@ -23,11 +22,11 @@ async function loadPackages() {
2322
// Sort alphabetically and take top 10
2423
packages.value = Object.keys(pkgMap).sort().slice(0, 10)
2524
} else {
26-
error.value = t('header.packages_dropdown.error')
25+
error.value = $t('header.packages_dropdown.error')
2726
}
2827
hasLoaded.value = true
2928
} catch {
30-
error.value = t('header.packages_dropdown.error')
29+
error.value = $t('header.packages_dropdown.error')
3130
} finally {
3231
isLoading.value = false
3332
}
@@ -62,7 +61,7 @@ function handleKeydown(event: KeyboardEvent) {
6261
:to="`/~${username}`"
6362
class="link-subtle font-mono text-sm inline-flex items-center gap-1"
6463
>
65-
{{ t('header.packages') }}
64+
{{ $t('header.packages') }}
6665
<span
6766
class="i-carbon-chevron-down w-3 h-3 transition-transform duration-200"
6867
:class="{ 'rotate-180': isOpen }"
@@ -80,16 +79,16 @@ function handleKeydown(event: KeyboardEvent) {
8079
<div class="bg-bg-elevated border border-border rounded-lg shadow-lg overflow-hidden">
8180
<div class="px-3 py-2 border-b border-border">
8281
<span class="font-mono text-xs text-fg-subtle">{{
83-
t('header.packages_dropdown.title')
82+
$t('header.packages_dropdown.title')
8483
}}</span>
8584
</div>
8685

8786
<div v-if="isLoading" class="px-3 py-4 text-center">
88-
<span class="text-fg-muted text-sm">{{ t('header.packages_dropdown.loading') }}</span>
87+
<span class="text-fg-muted text-sm">{{ $t('header.packages_dropdown.loading') }}</span>
8988
</div>
9089

9190
<div v-else-if="error" class="px-3 py-4 text-center">
92-
<span class="text-fg-muted text-sm">{{ t('header.packages_dropdown.error') }}</span>
91+
<span class="text-fg-muted text-sm">{{ $t('header.packages_dropdown.error') }}</span>
9392
</div>
9493

9594
<ul v-else-if="packages.length > 0" class="py-1 max-h-80 overflow-y-auto">
@@ -104,15 +103,15 @@ function handleKeydown(event: KeyboardEvent) {
104103
</ul>
105104

106105
<div v-else class="px-3 py-4 text-center">
107-
<span class="text-fg-muted text-sm">{{ t('header.packages_dropdown.empty') }}</span>
106+
<span class="text-fg-muted text-sm">{{ $t('header.packages_dropdown.empty') }}</span>
108107
</div>
109108

110109
<div class="px-3 py-2 border-t border-border">
111110
<NuxtLink
112111
:to="`/~${username}`"
113112
class="link-subtle font-mono text-xs inline-flex items-center gap-1"
114113
>
115-
{{ t('header.packages_dropdown.view_all') }}
114+
{{ $t('header.packages_dropdown.view_all') }}
116115
<span class="i-carbon-arrow-right w-3 h-3" aria-hidden="true" />
117116
</NuxtLink>
118117
</div>

app/components/LoadingSpinner.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ defineProps<{
33
/** Text to display next to the spinner */
44
text?: string
55
}>()
6-
7-
const { t } = useI18n()
86
</script>
97

108
<template>
119
<div aria-busy="true" class="flex items-center gap-3 text-fg-muted font-mono text-sm py-8">
12-
<span class="w-4 h-4 border-2 border-fg-subtle border-t-fg rounded-full animate-spin" />
13-
{{ text ?? t('common.loading') }}
10+
<span
11+
class="w-4 h-4 border-2 border-fg-subtle border-t-fg rounded-full motion-safe:animate-spin"
12+
/>
13+
{{ text ?? $t('common.loading') }}
1414
</div>
1515
</template>

0 commit comments

Comments
 (0)