Skip to content

Commit b87bd69

Browse files
authored
Merge branch 'main' into fix-types-badge
2 parents 4acca29 + 022d207 commit b87bd69

Some content is hidden

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

61 files changed

+1083
-309
lines changed

app/components/BackButton.vue

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<script setup lang="ts">
2+
const router = useRouter()
3+
const canGoBack = useCanGoBack()
4+
</script>
5+
6+
<template>
7+
<button
8+
v-if="canGoBack"
9+
type="button"
10+
class="inline-flex items-center gap-2 p-1.5 -mx-1.5 font-mono text-sm text-fg-muted hover:text-fg transition-colors duration-200 rounded focus-visible:outline-accent/70 shrink-0"
11+
@click="router.back()"
12+
>
13+
<span class="i-lucide:arrow-left rtl-flip w-4 h-4" aria-hidden="true" />
14+
<span class="sr-only sm:not-sr-only">{{ $t('nav.back') }}</span>
15+
</button>
16+
</template>
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<script setup lang="ts">
2+
import type { SlimPackumentVersion } from '#shared/types'
3+
4+
const props = defineProps<{
5+
packageName: string
6+
version: SlimPackumentVersion
7+
}>()
8+
9+
const loading = shallowRef(false)
10+
11+
async function getDownloadUrl(tarballUrl: string) {
12+
try {
13+
const response = await fetch(tarballUrl)
14+
if (!response.ok) {
15+
throw new Error(`Failed to fetch tarball (${response.status})`)
16+
}
17+
const blob = await response.blob()
18+
return URL.createObjectURL(blob)
19+
} catch (error) {
20+
// oxlint-disable-next-line no-console -- error logging
21+
console.error('failed to fetch tarball', { cause: error })
22+
return null
23+
}
24+
}
25+
26+
async function downloadPackage() {
27+
const tarballUrl = props.version.dist.tarball
28+
if (!tarballUrl) return
29+
30+
if (loading.value) return
31+
loading.value = true
32+
33+
const downloadUrl = await getDownloadUrl(tarballUrl)
34+
35+
const link = document.createElement('a')
36+
link.href = downloadUrl ?? tarballUrl
37+
link.download = `${props.packageName.replace(/\//g, '__')}-${props.version.version}.tgz`
38+
document.body.appendChild(link)
39+
link.click()
40+
document.body.removeChild(link)
41+
42+
if (downloadUrl) {
43+
URL.revokeObjectURL(downloadUrl)
44+
}
45+
46+
loading.value = false
47+
}
48+
</script>
49+
50+
<template>
51+
<TooltipApp :text="$t('package.download.tarball')">
52+
<ButtonBase
53+
ref="triggerRef"
54+
v-bind="$attrs"
55+
type="button"
56+
@click="downloadPackage"
57+
:disabled="loading"
58+
class="border-border-subtle bg-bg-subtle! text-xs text-fg-muted hover:enabled:(text-fg border-border-hover)"
59+
>
60+
<span
61+
class="size-[1em]"
62+
aria-hidden="true"
63+
:class="loading ? 'i-lucide:loader-circle animate-spin' : 'i-lucide:download'"
64+
/>
65+
{{ $t('package.download.button') }}
66+
</ButtonBase>
67+
</TooltipApp>
68+
</template>

app/components/Package/ExternalLinks.vue

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,6 @@ const homepageUrl = computed(() => {
2323
return homepage
2424
})
2525
26-
const fundingUrl = computed(() => {
27-
let funding = displayVersion.value?.funding
28-
if (Array.isArray(funding)) funding = funding[0]
29-
30-
if (!funding) return null
31-
32-
return typeof funding === 'string' ? funding : funding.url
33-
})
34-
3526
const PROVIDER_ICONS: Record<string, IconClass> = {
3627
github: 'i-simple-icons:github',
3728
gitlab: 'i-simple-icons:gitlab',
@@ -97,10 +88,5 @@ const repoProviderIcon = computed((): IconClass => {
9788
{{ $t('package.links.jsr') }}
9889
</LinkBase>
9990
</li>
100-
<li v-if="fundingUrl">
101-
<LinkBase :to="fundingUrl" classicon="i-lucide:heart">
102-
{{ $t('package.links.fund') }}
103-
</LinkBase>
104-
</li>
10591
</ul>
10692
</template>

app/components/Package/Header.vue

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,15 @@ const likeAction = async () => {
241241
isLikeActionPending.value = false
242242
}
243243
}
244+
245+
const fundingUrl = computed(() => {
246+
let funding = props.displayVersion?.funding
247+
if (Array.isArray(funding)) funding = funding[0]
248+
249+
if (!funding) return null
250+
251+
return typeof funding === 'string' ? funding : funding.url
252+
})
244253
</script>
245254

246255
<template>
@@ -276,7 +285,7 @@ const likeAction = async () => {
276285
aria-keyshortcuts="c"
277286
classicon="i-lucide:git-compare"
278287
>
279-
<span class="max-sm:sr-only">{{ $t('package.links.compare') }}</span>
288+
<span class="max-sm:sr-only">{{ $t('package.links.compare_this_package') }}</span>
280289
</LinkBase>
281290
<!-- Package likes -->
282291
<TooltipApp
@@ -312,6 +321,15 @@ const likeAction = async () => {
312321
</span>
313322
</ButtonBase>
314323
</TooltipApp>
324+
325+
<LinkBase
326+
variant="button-secondary"
327+
v-if="fundingUrl"
328+
:to="fundingUrl"
329+
classicon="i-lucide:handshake text-accent"
330+
>
331+
<span class="max-sm:sr-only">{{ $t('package.links.fund') }}</span>
332+
</LinkBase>
315333
</div>
316334
</div>
317335
</header>

app/components/Package/Skeleton.vue

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,24 @@
22

33
<template>
44
<!-- Package header — matches area-header in [...name].vue -->
5-
<header class="bg-[--bg] pt-5 pb-1 w-full container">
6-
<div class="min-w-0">
7-
<!-- Package name -->
8-
<div class="min-w-0 flex items-center gap-2 justify-between">
9-
<h1 class="font-mono text-2xl sm:text-3xl font-medium">
10-
<SkeletonInline class="block! h-9 w-48" />
11-
</h1>
12-
<div class="flex gap-1">
13-
<!-- Compare placeholder -->
14-
<SkeletonBlock class="w-9 md:w-40 h-9 rounded self-baseline" />
15-
<!-- Likes button placeholder -->
16-
<SkeletonBlock class="w-14 h-9 rounded self-baseline" />
17-
</div>
5+
<header class="bg-bg pt-6 pb-2 pb-1 w-full container">
6+
<!-- Package name -->
7+
<div class="flex items-baseline justify-between gap-x-2 gap-y-1 flex-wrap min-w-0">
8+
<h1 class="font-mono text-2xl sm:text-3xl font-medium">
9+
<SkeletonInline class="block! h-9 w-48" />
10+
</h1>
11+
<div class="flex gap-2 flex-wrap items-stretch">
12+
<!-- Compare placeholder -->
13+
<SkeletonBlock class="w-9 md:w-36 h-9 rounded" />
14+
<!-- Likes button placeholder -->
15+
<SkeletonBlock class="w-14 h-9 rounded" />
16+
<!-- Fund link placeholder -->
17+
<SkeletonBlock class="w-22 h-9 rounded self-baseline" />
1818
</div>
1919
</div>
2020
</header>
2121

22-
<div class="sticky top-14 z-1 pt-3 bg-bg border-b border-border w-full">
22+
<div class="w-full bg-bg sticky top-14 z-10 border-b border-border pt-2">
2323
<div class="w-full container flex flex-col md:flex-row-reverse flex-wrap gap-2 justify-between">
2424
<!-- Version -->
2525
<span class="inline-flex items-baseline font-mono text-base sm:text-lg shrink-0">
@@ -132,8 +132,11 @@
132132
<h2 class="text-xs font-mono text-fg-subtle uppercase tracking-wider">
133133
{{ $t('package.get_started.title') }}
134134
</h2>
135-
<!-- Package manager select placeholder -->
136-
<SkeletonInline class="h-7 w-24 rounded" />
135+
<!-- Download button + Package manager select placeholder -->
136+
<div class="flex items-center gap-2">
137+
<SkeletonInline class="h-7 w-24 rounded" />
138+
<SkeletonInline class="h-7 w-24 rounded" />
139+
</div>
137140
</div>
138141
<!-- Terminal-style install command — matches TerminalInstall.vue -->
139142
<div class="bg-bg-subtle border border-border rounded-lg overflow-hidden">
@@ -176,7 +179,7 @@
176179
<!-- Sidebar — matches area-sidebar in [...name].vue -->
177180
<div class="area-sidebar">
178181
<div
179-
class="sticky top-30 xl:top-14 space-y-6 sm:space-y-8 min-w-0 overflow-y-auto pe-2.5 lg:(max-h-[calc(100dvh-8.5rem)] overscroll-contain) xl:(max-h-[calc(100dvh-6rem)])"
182+
class="space-y-6 sm:space-y-8 min-w-0 lg:(max-h-[calc(100dvh-8.5rem)] overscroll-contain) xl:(max-h-[calc(100dvh-6rem)])"
180183
>
181184
<div class="flex flex-col gap-4 sm:gap-6 xl:pt-4">
182185
<!-- Download stats — matches CollapsibleSection + sparkline skeleton -->

app/components/Settings/AccentColorPicker.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ onPrehydrate(el => {
2929
<fieldset
3030
class="flex items-center gap-4 has-[input:focus-visible]:(outline-solid outline-accent/70 outline-offset-4) rounded-xl w-fit"
3131
>
32-
<legend class="sr-only">{{ $t('settings.accent_colors') }}</legend>
32+
<legend class="sr-only">{{ $t('settings.accent_colors.label') }}</legend>
3333
<label
3434
v-for="color in accentColors"
3535
:key="color.id"
@@ -43,7 +43,7 @@ onPrehydrate(el => {
4343
class="sr-only"
4444
:value="color.id"
4545
:checked="selectedAccentColor === color.id || (!selectedAccentColor && color.id === 'sky')"
46-
:aria-label="color.id === 'neutral' ? $t('settings.clear_accent') : color.name"
46+
:aria-label="color.label"
4747
@change="setAccentColor(color.id)"
4848
/>
4949
<span v-if="color.id === 'neutral'" class="i-lucide:ban size-4 text-bg" aria-hidden="true" />

app/components/Settings/BgThemePicker.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ onPrehydrate(el => {
2727
<fieldset
2828
class="flex items-center gap-4 has-[input:focus-visible]:(outline-solid outline-accent/70 outline-offset-4) rounded-xl w-fit"
2929
>
30-
<legend class="sr-only">{{ $t('settings.background_themes') }}</legend>
30+
<legend class="sr-only">{{ $t('settings.background_themes.label') }}</legend>
3131
<label
3232
v-for="theme in backgroundThemes"
3333
:key="theme.id"
@@ -43,7 +43,7 @@ onPrehydrate(el => {
4343
selectedBackgroundTheme === theme.id ||
4444
(!selectedBackgroundTheme && theme.id === 'neutral')
4545
"
46-
:aria-label="theme.name"
46+
:aria-label="theme.label"
4747
@change="setBackgroundTheme(theme.id)"
4848
/>
4949
</label>

app/composables/useSettings.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -135,14 +135,25 @@ export const useKeyboardShortcuts = createSharedComposable(function useKeyboardS
135135
export function useAccentColor() {
136136
const { settings } = useSettings()
137137
const colorMode = useColorMode()
138+
const { t } = useI18n()
139+
140+
const accentColorLabels = computed<Record<AccentColorId, string>>(() => ({
141+
sky: t('settings.accent_colors.sky'),
142+
coral: t('settings.accent_colors.coral'),
143+
amber: t('settings.accent_colors.amber'),
144+
emerald: t('settings.accent_colors.emerald'),
145+
violet: t('settings.accent_colors.violet'),
146+
magenta: t('settings.accent_colors.magenta'),
147+
neutral: t('settings.clear_accent'),
148+
}))
138149

139150
const accentColors = computed(() => {
140151
const isDark = colorMode.value === 'dark'
141152
const colors = isDark ? ACCENT_COLORS.dark : ACCENT_COLORS.light
142153

143154
return Object.entries(colors).map(([id, value]) => ({
144155
id: id as AccentColorId,
145-
name: id,
156+
label: accentColorLabels.value[id as AccentColorId],
146157
value,
147158
}))
148159
})
@@ -190,12 +201,24 @@ export function useSearchProvider() {
190201
}
191202

192203
export function useBackgroundTheme() {
193-
const backgroundThemes = Object.entries(BACKGROUND_THEMES).map(([id, value]) => ({
194-
id: id as BackgroundThemeId,
195-
name: id,
196-
value,
204+
const { t } = useI18n()
205+
206+
const bgThemeLabels = computed<Record<BackgroundThemeId, string>>(() => ({
207+
neutral: t('settings.background_themes.neutral'),
208+
stone: t('settings.background_themes.stone'),
209+
zinc: t('settings.background_themes.zinc'),
210+
slate: t('settings.background_themes.slate'),
211+
black: t('settings.background_themes.black'),
197212
}))
198213

214+
const backgroundThemes = computed(() =>
215+
Object.entries(BACKGROUND_THEMES).map(([id, value]) => ({
216+
id: id as BackgroundThemeId,
217+
label: bgThemeLabels.value[id as BackgroundThemeId],
218+
value,
219+
})),
220+
)
221+
199222
const { settings } = useSettings()
200223

201224
function setBackgroundTheme(id: BackgroundThemeId | null) {

app/pages/about.vue

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@ import type { Role } from '#server/api/contributors.get'
33
import { SPONSORS } from '~/assets/logos/sponsors'
44
import { OSS_PARTNERS } from '~/assets/logos/oss-partners'
55
6-
const router = useRouter()
7-
const canGoBack = useCanGoBack()
8-
96
useSeoMeta({
107
title: () => `${$t('about.title')} - npmx`,
118
ogTitle: () => `${$t('about.title')} - npmx`,
@@ -57,15 +54,7 @@ const roleLabels = computed(
5754
<h1 class="font-mono text-3xl sm:text-4xl font-medium">
5855
{{ $t('about.heading') }}
5956
</h1>
60-
<button
61-
type="button"
62-
class="cursor-pointer inline-flex items-center gap-2 p-1.5 -mx-1.5 font-mono text-sm text-fg-muted hover:text-fg transition-colors duration-200 rounded focus-visible:outline-accent/70 shrink-0"
63-
@click="router.back()"
64-
v-if="canGoBack"
65-
>
66-
<span class="i-lucide:arrow-left rtl-flip w-4 h-4" aria-hidden="true" />
67-
<span class="hidden sm:inline">{{ $t('nav.back') }}</span>
68-
</button>
57+
<BackButton />
6958
</div>
7059
<p class="text-fg-muted text-lg">
7160
{{ $t('tagline') }}

app/pages/accessibility.vue

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@ defineOgImageComponent('Default', {
1212
title: () => $t('a11y.title'),
1313
description: () => $t('a11y.welcome', { app: 'npmx' }),
1414
})
15-
16-
const router = useRouter()
17-
const canGoBack = useCanGoBack()
1815
</script>
1916

2017
<template>
@@ -25,15 +22,7 @@ const canGoBack = useCanGoBack()
2522
<h1 class="font-mono text-3xl sm:text-4xl font-medium">
2623
{{ $t('a11y.title') }}
2724
</h1>
28-
<button
29-
type="button"
30-
class="cursor-pointer inline-flex items-center gap-2 p-1.5 -mx-1.5 font-mono text-sm text-fg-muted hover:text-fg transition-colors duration-200 rounded shrink-0"
31-
@click="router.back()"
32-
v-if="canGoBack"
33-
>
34-
<span class="i-lucide:arrow-left rtl-flip w-4 h-4" aria-hidden="true" />
35-
<span class="sr-only sm:not-sr-only">{{ $t('nav.back') }}</span>
36-
</button>
25+
<BackButton />
3726
</div>
3827
</header>
3928

0 commit comments

Comments
 (0)