forked from
npmx.dev/npmx.dev
[READ-ONLY]
a fast, modern browser for the npm registry
1<script setup lang="ts">
2import type { JsrPackageInfo } from '#shared/types/jsr'
3import 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
10const props = defineProps<{
11 packageName: string
12 jsrInfo?: JsrPackageInfo | null
13 isCreatePackage?: boolean
14}>()
15
16const selectedPM = useSelectedPackageManager()
17
18// Generate execute command parts for a specific package manager
19function 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)
30function 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
41const { copied: executeCopied, copy: copyExecute } = useClipboard({ copiedDuring: 2000 })
42const 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-accent/70"
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>