Skip to content

Commit 53a6afd

Browse files
authored
Merge branch 'main' into i18n/ar
2 parents 94e0610 + 4b26a00 commit 53a6afd

55 files changed

Lines changed: 980 additions & 276 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/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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ npmx.dev uses [@nuxtjs/i18n](https://i18n.nuxtjs.org/) for internationalization.
242242
### Approach
243243

244244
- All user-facing strings should use translation keys via `$t()` in templates and script
245-
- 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`)
246246
- We use the `no_prefix` strategy (no `/en-US/` or `/fr-FR/` in URLs)
247247
- Locale preference is stored in cookies and respected on subsequent visits
248248

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: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,51 @@
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-start gap-2 sm:gap-4">
5-
<p class="font-mono m-0 hidden sm:block">{{ $t('tagline') }}</p>
6-
<span aria-hidden="true" class="flex-shrink-1 flex-grow-1" />
7-
<div class="flex items-center justify-start gap-3 sm:gap-6">
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>
8+
<div class="flex items-center gap-3 sm:gap-6">
89
<NuxtLink
910
to="/about"
1011
class="link-subtle font-mono text-xs min-h-8 sm:min-h-11 flex items-center"
1112
>
12-
<span>{{ $t('footer.about') }}</span>
13+
{{ $t('footer.about') }}
1314
</NuxtLink>
1415
<a
1516
href="https://docs.npmx.dev"
1617
target="_blank"
1718
rel="noopener noreferrer"
18-
class="link-subtle font-mono text-xs min-h-8 sm:min-h-11 flex items-center justify-start gap-1"
19+
class="link-subtle font-mono text-xs min-h-8 sm:min-h-11 flex items-center gap-1"
1920
>
20-
<span>{{ $t('footer.docs') }}</span>
21+
{{ $t('footer.docs') }}
2122
<span class="i-carbon:launch rtl-flip w-3 h-3" aria-hidden="true" />
2223
</a>
2324
<a
2425
href="https://repo.npmx.dev"
2526
target="_blank"
2627
rel="noopener noreferrer"
27-
class="link-subtle font-mono text-xs min-h-8 sm:min-h-11 flex justify-start items-center gap-1"
28+
class="link-subtle font-mono text-xs min-h-8 sm:min-h-11 flex items-center gap-1"
2829
>
29-
<span>{{ $t('footer.source') }}</span>
30+
{{ $t('footer.source') }}
3031
<span class="i-carbon:launch rtl-flip w-3 h-3" aria-hidden="true" />
3132
</a>
3233
<a
3334
href="https://social.npmx.dev"
3435
target="_blank"
3536
rel="noopener noreferrer"
36-
class="link-subtle font-mono text-xs min-h-8 sm:min-h-11 flex items-center justify-start gap-1"
37+
class="link-subtle font-mono text-xs min-h-8 sm:min-h-11 flex items-center gap-1"
3738
>
38-
<span>{{ $t('footer.social') }}</span>
39+
{{ $t('footer.social') }}
3940
<span class="i-carbon:launch rtl-flip w-3 h-3" aria-hidden="true" />
4041
</a>
4142
<a
4243
href="https://chat.npmx.dev"
4344
target="_blank"
4445
rel="noopener noreferrer"
45-
class="link-subtle font-mono text-xs min-h-8 sm:min-h-11 flex items-center justify-start gap-1"
46+
class="link-subtle font-mono text-xs min-h-8 sm:min-h-11 flex items-center gap-1"
4647
>
47-
<span>{{ $t('footer.chat') }}</span>
48+
{{ $t('footer.chat') }}
4849
<span class="i-carbon:launch rtl-flip w-3 h-3" aria-hidden="true" />
4950
</a>
5051
</div>

app/components/PackageDependencies.vue

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { useVulnerabilityTree } from '~/composables/useVulnerabilityTree'
2+
import { useDependencyAnalysis } from '~/composables/useDependencyAnalysis'
33
import { SEVERITY_TEXT_COLORS, getHighestSeverity } from '#shared/utils/severity'
44
55
const props = defineProps<{
@@ -15,7 +15,7 @@ const props = defineProps<{
1515
const outdatedDeps = useOutdatedDependencies(() => props.dependencies)
1616
1717
// Get vulnerability info from shared cache (already fetched by PackageVulnerabilityTree)
18-
const { data: vulnTree } = useVulnerabilityTree(
18+
const { data: vulnTree } = useDependencyAnalysis(
1919
() => props.packageName,
2020
() => props.version,
2121
)
@@ -26,6 +26,12 @@ function getVulnerableDepInfo(depName: string) {
2626
return vulnTree.value.vulnerablePackages.find(p => p.name === depName && p.depth === 'direct')
2727
}
2828
29+
// Check if a dependency is deprecated (only direct deps)
30+
function getDeprecatedDepInfo(depName: string) {
31+
if (!vulnTree.value) return null
32+
return vulnTree.value.deprecatedPackages.find(p => p.name === depName && p.depth === 'direct')
33+
}
34+
2935
// Expanded state for each section
3036
const depsExpanded = shallowRef(false)
3137
const peerDepsExpanded = shallowRef(false)
@@ -80,7 +86,7 @@ const sortedOptionalDependencies = computed(() => {
8086
>
8187
{{ $t('package.dependencies.title', { count: sortedDependencies.length }) }}
8288
<span
83-
class="i-carbon-link w-3 h-3 block opacity-0 group-hover:opacity-100 transition-opacity duration-200"
89+
class="i-carbon:link w-3 h-3 block opacity-0 group-hover:opacity-100 transition-opacity duration-200"
8490
aria-hidden="true"
8591
/>
8692
</a>
@@ -89,7 +95,7 @@ const sortedOptionalDependencies = computed(() => {
8995
<li
9096
v-for="[dep, version] in sortedDependencies.slice(0, depsExpanded ? undefined : 10)"
9197
:key="dep"
92-
class="flex items-center justify-between py-1 text-sm gap-2"
98+
class="flex items-center justify-start py-1 text-sm gap-2"
9399
>
94100
<NuxtLink
95101
:to="{ name: 'package', params: { package: dep.split('/') } }"
@@ -105,8 +111,9 @@ const sortedOptionalDependencies = computed(() => {
105111
:title="getOutdatedTooltip(outdatedDeps[dep])"
106112
aria-hidden="true"
107113
>
108-
<span class="i-carbon-warning-alt w-3 h-3 block" />
114+
<span class="i-carbon:warning-alt w-3 h-3 block" />
109115
</span>
116+
<span aria-hidden="true" class="flex-shrink-1 flex-grow-1" />
110117
<NuxtLink
111118
v-if="getVulnerableDepInfo(dep)"
112119
:to="{
@@ -117,12 +124,24 @@ const sortedOptionalDependencies = computed(() => {
117124
:class="SEVERITY_TEXT_COLORS[getHighestSeverity(getVulnerableDepInfo(dep)!.counts)]"
118125
:title="`${getVulnerableDepInfo(dep)!.counts.total} vulnerabilities`"
119126
>
120-
<span class="i-carbon-security w-3 h-3 block" aria-hidden="true" />
127+
<span class="i-carbon:security w-3 h-3 block" aria-hidden="true" />
121128
<span class="sr-only">{{ $t('package.dependencies.view_vulnerabilities') }}</span>
122129
</NuxtLink>
130+
<NuxtLink
131+
v-if="getDeprecatedDepInfo(dep)"
132+
:to="{
133+
name: 'package',
134+
params: { package: [...dep.split('/'), 'v', getDeprecatedDepInfo(dep)!.version] },
135+
}"
136+
class="shrink-0 text-purple-500"
137+
:title="getDeprecatedDepInfo(dep)!.message"
138+
>
139+
<span class="i-carbon-warning-hex w-3 h-3 block" aria-hidden="true" />
140+
<span class="sr-only">{{ $t('package.deprecated.label') }}</span>
141+
</NuxtLink>
123142
<NuxtLink
124143
:to="{ name: 'package', params: { package: [...dep.split('/'), 'v', version] } }"
125-
class="font-mono text-xs text-right truncate"
144+
class="font-mono text-xs text-end truncate"
126145
:class="getVersionClass(outdatedDeps[dep])"
127146
:title="outdatedDeps[dep] ? getOutdatedTooltip(outdatedDeps[dep]) : version"
128147
>
@@ -164,7 +183,7 @@ const sortedOptionalDependencies = computed(() => {
164183
>
165184
{{ $t('package.peer_dependencies.title', { count: sortedPeerDependencies.length }) }}
166185
<span
167-
class="i-carbon-link w-3 h-3 block opacity-0 group-hover:opacity-100 transition-opacity duration-200"
186+
class="i-carbon:link w-3 h-3 block opacity-0 group-hover:opacity-100 transition-opacity duration-200"
168187
aria-hidden="true"
169188
/>
170189
</a>
@@ -176,7 +195,7 @@ const sortedOptionalDependencies = computed(() => {
176195
<li
177196
v-for="peer in sortedPeerDependencies.slice(0, peerDepsExpanded ? undefined : 10)"
178197
:key="peer.name"
179-
class="flex items-center justify-between py-1 text-sm gap-2"
198+
class="flex items-center justify-start py-1 text-sm gap-2"
180199
>
181200
<div class="flex items-center gap-2 min-w-0">
182201
<NuxtLink
@@ -193,12 +212,13 @@ const sortedOptionalDependencies = computed(() => {
193212
{{ $t('package.dependencies.optional') }}
194213
</span>
195214
</div>
215+
<span aria-hidden="true" class="flex-shrink-1 flex-grow-1" />
196216
<NuxtLink
197217
:to="{
198218
name: 'package',
199219
params: { package: [...peer.name.split('/'), 'v', peer.version] },
200220
}"
201-
class="font-mono text-xs text-fg-subtle max-w-[40%] text-right truncate"
221+
class="font-mono text-xs text-fg-subtle max-w-[40%] text-end truncate"
202222
:title="peer.version"
203223
>
204224
{{ peer.version }}
@@ -234,7 +254,7 @@ const sortedOptionalDependencies = computed(() => {
234254
$t('package.optional_dependencies.title', { count: sortedOptionalDependencies.length })
235255
}}
236256
<span
237-
class="i-carbon-link w-3 h-3 block opacity-0 group-hover:opacity-100 transition-opacity duration-200"
257+
class="i-carbon:link w-3 h-3 block opacity-0 group-hover:opacity-100 transition-opacity duration-200"
238258
aria-hidden="true"
239259
/>
240260
</a>
@@ -249,17 +269,18 @@ const sortedOptionalDependencies = computed(() => {
249269
optionalDepsExpanded ? undefined : 10,
250270
)"
251271
:key="dep"
252-
class="flex items-center justify-between py-1 text-sm gap-2"
272+
class="flex items-center justify-start py-1 text-sm gap-2"
253273
>
254274
<NuxtLink
255275
:to="{ name: 'package', params: { package: dep.split('/') } }"
256276
class="font-mono text-fg-muted hover:text-fg transition-colors duration-200 truncate min-w-0"
257277
>
258278
{{ dep }}
259279
</NuxtLink>
280+
<span aria-hidden="true" class="flex-shrink-1 flex-grow-1" />
260281
<NuxtLink
261282
:to="{ name: 'package', params: { package: [...dep.split('/'), 'v', version] } }"
262-
class="font-mono text-xs text-fg-subtle max-w-[50%] text-right truncate"
283+
class="font-mono text-xs text-fg-subtle max-w-[50%] text-end truncate"
263284
:title="version"
264285
>
265286
{{ version }}

0 commit comments

Comments
 (0)