Skip to content

Commit b5c5760

Browse files
committed
Merge branch 'main' of github.com:npmx-dev/npmx.dev into feat/preview-md-in-code-view
2 parents 4e69d98 + 94a7441 commit b5c5760

40 files changed

Lines changed: 2461 additions & 519 deletions

app/app.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ const route = useRoute()
66
const router = useRouter()
77
const { locale, locales } = useI18n()
88
9-
// Initialize accent color before hydration to prevent flash
10-
initAccentOnPrehydrate()
9+
// Initialize user preferences (accent color, package manager) before hydration to prevent flash/CLS
10+
initPreferencesOnPrehydrate()
1111
1212
const isHomepage = computed(() => route.name === 'index')
1313

app/assets/main.css

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ button {
154154

155155
/* Skip link */
156156
.skip-link {
157-
position: absolute;
157+
position: fixed;
158158
top: -100%;
159159
left: 0;
160160
padding: 0.5rem 1rem;
@@ -165,6 +165,15 @@ button {
165165
transition: top 0.2s ease;
166166
}
167167

168+
html[dir='rtl'] .skip-link {
169+
left: unset;
170+
right: 0;
171+
}
172+
173+
.skip-link:hover {
174+
color: var(--bg);
175+
text-decoration: underline;
176+
}
168177
.skip-link:focus {
169178
top: 0;
170179
}

app/components/AccentColorPicker.vue

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22
import { useAccentColor } from '~/composables/useSettings'
33
44
const { accentColors, selectedAccentColor, setAccentColor } = useAccentColor()
5+
6+
onPrehydrate(el => {
7+
const settings = JSON.parse(localStorage.getItem('npmx-settings') || '{}')
8+
const id = settings.accentColorId
9+
if (id) {
10+
const input = el.querySelector<HTMLInputElement>(`input[value="${id}"]`)
11+
if (input) {
12+
input.checked = true
13+
}
14+
}
15+
})
516
</script>
617

718
<template>
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<script setup lang="ts">
2+
import type { JsrPackageInfo } from '#shared/types/jsr'
3+
import type { PackageManagerId } from '~/utils/install-command'
4+
5+
/**
6+
* A terminal-style execute command display for binary-only packages.
7+
* Renders all package manager variants with CSS-based visibility.
8+
*/
9+
10+
const props = defineProps<{
11+
packageName: string
12+
jsrInfo?: JsrPackageInfo | null
13+
isCreatePackage?: boolean
14+
}>()
15+
16+
const selectedPM = useSelectedPackageManager()
17+
18+
// Generate execute command parts for a specific package manager
19+
function getExecutePartsForPM(pmId: PackageManagerId) {
20+
return getExecuteCommandParts({
21+
packageName: props.packageName,
22+
packageManager: pmId,
23+
jsrInfo: props.jsrInfo,
24+
isBinaryOnly: true,
25+
isCreatePackage: props.isCreatePackage,
26+
})
27+
}
28+
29+
// Full execute command for copying (uses current selected PM)
30+
function getFullExecuteCommand() {
31+
return getExecuteCommand({
32+
packageName: props.packageName,
33+
packageManager: selectedPM.value,
34+
jsrInfo: props.jsrInfo,
35+
isBinaryOnly: true,
36+
isCreatePackage: props.isCreatePackage,
37+
})
38+
}
39+
40+
// Copy handler
41+
const { copied: executeCopied, copy: copyExecute } = useClipboard({ copiedDuring: 2000 })
42+
const copyExecuteCommand = () => copyExecute(getFullExecuteCommand())
43+
</script>
44+
45+
<template>
46+
<div class="relative group">
47+
<!-- Terminal-style execute command -->
48+
<div class="bg-bg-subtle border border-border rounded-lg overflow-hidden">
49+
<div class="flex gap-1.5 px-3 pt-2 sm:px-4 sm:pt-3">
50+
<span class="w-2.5 h-2.5 rounded-full bg-fg-subtle" />
51+
<span class="w-2.5 h-2.5 rounded-full bg-fg-subtle" />
52+
<span class="w-2.5 h-2.5 rounded-full bg-fg-subtle" />
53+
</div>
54+
<div class="px-3 pt-2 pb-3 sm:px-4 sm:pt-3 sm:pb-4 space-y-1">
55+
<!-- Execute command - render all PM variants, CSS controls visibility -->
56+
<div
57+
v-for="pm in packageManagers"
58+
:key="`execute-${pm.id}`"
59+
:data-pm-cmd="pm.id"
60+
class="flex items-center gap-2 group/executecmd"
61+
>
62+
<span class="text-fg-subtle font-mono text-sm select-none">$</span>
63+
<code class="font-mono text-sm"
64+
><span
65+
v-for="(part, i) in getExecutePartsForPM(pm.id)"
66+
:key="i"
67+
:class="i === 0 ? 'text-fg' : 'text-fg-muted'"
68+
>{{ i > 0 ? ' ' : '' }}{{ part }}</span
69+
></code
70+
>
71+
<button
72+
type="button"
73+
class="px-2 py-0.5 font-mono text-xs text-fg-muted bg-bg-subtle/80 border border-border rounded transition-colors duration-200 opacity-0 group-hover/executecmd:opacity-100 hover:(text-fg border-border-hover) active:scale-95 focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
74+
:aria-label="$t('package.get_started.copy_command')"
75+
@click.stop="copyExecuteCommand"
76+
>
77+
{{ executeCopied ? $t('common.copied') : $t('common.copy') }}
78+
</button>
79+
</div>
80+
</div>
81+
</div>
82+
</div>
83+
</template>
84+
85+
<style>
86+
/* Hide all variants by default when preference is set */
87+
:root[data-pm] [data-pm-cmd] {
88+
display: none;
89+
}
90+
91+
/* Show only the matching package manager command */
92+
:root[data-pm='npm'] [data-pm-cmd='npm'],
93+
:root[data-pm='pnpm'] [data-pm-cmd='pnpm'],
94+
:root[data-pm='yarn'] [data-pm-cmd='yarn'],
95+
:root[data-pm='bun'] [data-pm-cmd='bun'],
96+
:root[data-pm='deno'] [data-pm-cmd='deno'],
97+
:root[data-pm='vlt'] [data-pm-cmd='vlt'] {
98+
display: flex;
99+
}
100+
101+
/* Fallback: when no data-pm is set (SSR initial), show npm as default */
102+
:root:not([data-pm]) [data-pm-cmd]:not([data-pm-cmd='npm']) {
103+
display: none;
104+
}
105+
</style>

0 commit comments

Comments
 (0)