Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b1cb88d
fix(a11y): make buttons always visible | fix(ui): make borders visible
IdrisGit Feb 5, 2026
5cf068c
test: update tests for copy command
IdrisGit Feb 5, 2026
2b3481a
test: add missing run command tests
IdrisGit Feb 5, 2026
8c8cefa
fix(a11y): make copy button always visible
IdrisGit Feb 5, 2026
97c7c56
fix(test): exact match the npx command
IdrisGit Feb 5, 2026
dd5e281
style: use checkmark and copy icons to be consistent
IdrisGit Feb 8, 2026
0f2f04b
Merge branch 'main' into fix-copy-button-visibility
IdrisGit Feb 8, 2026
3a1d3d6
Merge branch 'main' into fix-copy-button-visibility
IdrisGit Feb 10, 2026
1209f19
fix: add arial lable to run locally command
IdrisGit Feb 10, 2026
47827cd
Merge branch 'main' into fix-copy-button-visibility
IdrisGit Mar 11, 2026
ea5b4db
feat:a11y: hide and show on hower only for devices that support it an…
IdrisGit Mar 11, 2026
4730c57
feat: improve command text
IdrisGit Mar 11, 2026
9831bf9
test: update tests
IdrisGit Mar 11, 2026
beee5a9
style: use lucide icons | fix: css for sr
IdrisGit Mar 12, 2026
d0ee70c
feat:style: use ButtonBase for consistent styling
IdrisGit Mar 12, 2026
5fb969f
feat: add new square size
IdrisGit Mar 12, 2026
14467a5
Merge branch 'main' into fix-copy-button-visibility
IdrisGit Apr 21, 2026
6acbee4
feat:style: add new icon size
IdrisGit Apr 21, 2026
ae83214
feat: update copy icon button
IdrisGit Apr 21, 2026
b2f1c2b
fix:style: dollar sign alignment
IdrisGit Apr 21, 2026
b267f23
feat: add copied schema in i18n
IdrisGit Apr 21, 2026
6cb2a0b
feat: use new NuxtAnnouncer
IdrisGit Apr 21, 2026
42e15a5
test: update test to check new icons and nuxt announcer
IdrisGit Apr 21, 2026
bbc6515
fix: broken i18n key
IdrisGit Apr 21, 2026
442d5e4
fix:tests: version selector doesn't render "v" prefix
IdrisGit Apr 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ defineOgImage('Page.takumi', {}, { alt: 'npmx — a fast, modern browser for the
{{ route.name === 'search' ? `${$t('search.title_packages')} - npmx` : message }}
</NuxtRouteAnnouncer>

<NuxtAnnouncer />

<div id="main-content" class="flex-1 flex flex-col" tabindex="-1">
<NuxtPage />
</div>
Expand Down
3 changes: 2 additions & 1 deletion app/components/Button/Base.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const props = withDefaults(
/** @default "secondary" */
variant?: 'primary' | 'secondary'
/** @default "md" */
size?: 'sm' | 'md'
size?: 'sm' | 'md' | 'icon'
/** Keyboard shortcut hint */
ariaKeyshortcuts?: string
/** Forces the button to occupy the entire width of its container. */
Expand Down Expand Up @@ -50,6 +50,7 @@ defineExpose({
'flex': block,
'text-sm px-4 py-2': size === 'md',
'text-xs px-2 py-0.5': size === 'sm',
'text-sm p-2': size === 'icon',
'bg-transparent text-fg hover:enabled:(bg-fg/10) focus-visible:enabled:(bg-fg/10) aria-pressed:(bg-fg/10 border-fg/20 hover:enabled:(bg-fg/20 text-fg/50))':
variant === 'secondary',
'text-bg bg-fg hover:enabled:(bg-fg/50) focus-visible:enabled:(bg-fg/50) aria-pressed:(bg-fg text-bg border-fg hover:enabled:(text-bg/50))':
Expand Down
21 changes: 13 additions & 8 deletions app/components/Package/SkillsModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,12 @@ const installCommand = computed(() => {
})

const { copied, copy } = useClipboard({ copiedDuring: 2000 })
const copyCommand = () => installCommand.value && copy(installCommand.value)
const { polite } = useAnnouncer()
const copyCommand = () => {
if (!installCommand.value) return
copy(installCommand.value)
polite($t('package.command.copied_skills'))
}

function getWarningTooltip(skill: SkillListItem): string | undefined {
if (!skill.warnings?.length) return undefined
Expand Down Expand Up @@ -123,20 +128,20 @@ function getWarningTooltip(skill: SkillListItem): string | undefined {
<span class="w-2.5 h-2.5 rounded-full bg-fg-subtle" />
</div>
<div class="px-3 pt-2 pb-3 sm:px-4 sm:pt-3 sm:pb-4 overflow-x-auto">
<div class="relative group/cmd">
<code class="font-mono text-sm whitespace-nowrap">
<div class="relative flex items-center gap-2 group/cmd">
<code class="font-mono text-sm">
<span class="text-fg-subtle select-none">$ </span>
<span class="text-fg">npx </span>
<span class="text-fg-muted">skills add {{ baseUrl }}/{{ packageName }}</span>
</code>
<button
<ButtonBase
type="button"
class="absolute top-0 inset-ie-0 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/cmd:opacity-100 hover:(text-fg border-border-hover) active:scale-95 focus-visible:opacity-100 focus-visible:outline-accent/70"
size="icon"
class="text-fg-muted bg-bg-subtle/80 border-border media-mouse:opacity-0 media-mouse:group-hover/cmd:opacity-100 media-mouse:focus-within:opacity-100 active:scale-95 focus-visible:opacity-100 select-none"
:aria-label="$t('package.get_started.copy_command')"
:classicon="copied ? 'i-lucide:check' : 'i-lucide:copy'"
@click.stop="copyCommand"
>
<span aria-live="polite">{{ copied ? $t('common.copied') : $t('common.copy') }}</span>
</button>
/>
</div>
</div>
</div>
Expand Down
18 changes: 11 additions & 7 deletions app/components/Terminal/Execute.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const props = defineProps<{
}>()

const selectedPM = useSelectedPackageManager()
const { polite } = useAnnouncer()

// Generate execute command parts for a specific package manager
function getExecutePartsForPM(pmId: PackageManagerId) {
Expand All @@ -39,7 +40,10 @@ function getFullExecuteCommand() {

// Copy handler
const { copied: executeCopied, copy: copyExecute } = useClipboard({ copiedDuring: 2000 })
const copyExecuteCommand = () => copyExecute(getFullExecuteCommand())
const copyExecuteCommand = () => {
copyExecute(getFullExecuteCommand())
polite($t('package.command.copied_execute'))
}
</script>

<template>
Expand All @@ -59,7 +63,7 @@ const copyExecuteCommand = () => copyExecute(getFullExecuteCommand())
:data-pm-cmd="pm.id"
class="flex items-center gap-2 group/executecmd"
>
<span class="text-fg-subtle font-mono text-sm select-none">$</span>
<span class="text-fg-subtle font-mono text-sm select-none shrink-0">$</span>
<code class="font-mono text-sm"
><span
v-for="(part, i) in getExecutePartsForPM(pm.id)"
Expand All @@ -68,14 +72,14 @@ const copyExecuteCommand = () => copyExecute(getFullExecuteCommand())
>{{ i > 0 ? ' ' : '' }}{{ part }}</span
></code
>
<button
<ButtonBase
type="button"
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"
size="icon"
class="text-fg-muted bg-bg-subtle/80 border-border media-mouse:opacity-0 media-mouse:group-hover/executecmd:opacity-100 media-mouse:focus-within:opacity-100 active:scale-95 focus-visible:opacity-100 select-none"
:aria-label="$t('package.get_started.copy_command')"
:classicon="executeCopied ? 'i-lucide:check' : 'i-lucide:copy'"
@click.stop="copyExecuteCommand"
>
{{ executeCopied ? $t('common.copied') : $t('common.copy') }}
</button>
/>
</div>
</div>
</div>
Expand Down
78 changes: 44 additions & 34 deletions app/components/Terminal/Install.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ const { selectedPM, showTypesInInstall, copied, copyInstallCommand } = useInstal
() => props.installVersionOverride ?? null,
)

const { announce } = useCommandPalette()
const { polite } = useAnnouncer()

async function copyInstallCommandWithAnnounce() {
const success = await copyInstallCommand()
if (success) polite($t('package.command.copied_install'))
}

// Generate install command parts for a specific package manager
function getInstallPartsForPM(pmId: PackageManagerId) {
return getInstallCommandParts({
Expand Down Expand Up @@ -105,13 +113,19 @@ function getFullCreateCommand() {

// Copy handlers
const { copied: runCopied, copy: copyRun } = useClipboard({ copiedDuring: 2000 })
const copyRunCommand = (command?: string) => copyRun(getFullRunCommand(command))
const copyRunCommand = (command?: string) => {
copyRun(getFullRunCommand(command))
polite($t('package.command.copied_run'))
}

const { copied: createCopied, copy: copyCreate } = useClipboard({ copiedDuring: 2000 })
const copyCreateCommand = () => copyCreate(getFullCreateCommand())
const copyCreateCommand = () => {
copyCreate(getFullCreateCommand())
polite($t('package.command.copied_create'))
}

const { copied: devInstallCopied, copy: copyDevInstall } = useClipboard({ copiedDuring: 2000 })
const copyDevInstallCommand = () =>
const copyDevInstallCommand = () => {
copyDevInstall(
getInstallCommand({
packageName: props.packageName,
Expand All @@ -121,8 +135,8 @@ const copyDevInstallCommand = () =>
dev: true,
}),
)

const { announce } = useCommandPalette()
polite($t('package.command.copied_dev_install'))
}

useCommandPaletteContextCommands(
computed((): CommandPaletteContextCommandInput[] => {
Expand Down Expand Up @@ -226,7 +240,7 @@ useCommandPaletteContextCommands(
:data-pm-cmd="pm.id"
class="flex items-center gap-2 group/installcmd min-w-0"
>
<span class="self-start text-fg-subtle font-mono text-sm select-none shrink-0">$</span>
<span class="text-fg-subtle font-mono text-sm select-none shrink-0">$</span>
<code class="font-mono text-sm min-w-0"
><span
v-for="(part, i) in getInstallPartsForPM(pm.id)"
Expand All @@ -235,14 +249,14 @@ useCommandPaletteContextCommands(
>{{ i > 0 ? ' ' : '' }}{{ part }}</span
></code
>
<button
<ButtonBase
type="button"
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/installcmd:opacity-100 hover:(text-fg border-border-hover) active:scale-95 focus-visible:opacity-100 focus-visible:outline-accent/70 select-none"
size="icon"
class="text-fg-muted bg-bg-subtle/80 border-border media-mouse:opacity-0 media-mouse:group-hover/installcmd:opacity-100 media-mouse:focus-within:opacity-100 active:scale-95 focus-visible:opacity-100 select-none"
:aria-label="$t('package.get_started.copy_command')"
@click.stop="copyInstallCommand"
>
<span aria-live="polite">{{ copied ? $t('common.copied') : $t('common.copy') }}</span>
</button>
:classicon="copied ? 'i-lucide:check' : 'i-lucide:copy'"
@click.stop="copyInstallCommandWithAnnounce"
/>
</div>

<!-- Suggested dev dependency install command -->
Expand All @@ -269,15 +283,12 @@ useCommandPaletteContextCommands(
>
<ButtonBase
type="button"
size="sm"
class="text-fg-muted bg-bg-subtle/80 border-border opacity-0 group-hover/devinstallcmd:opacity-100 active:scale-95 focus-visible:opacity-100 select-none"
size="icon"
class="text-fg-muted bg-bg-subtle/80 border-border media-mouse:opacity-0 media-mouse:group-hover/devinstallcmd:opacity-100 media-mouse:focus-within:opacity-100 active:scale-95 focus-visible:opacity-100 select-none"
:aria-label="$t('package.get_started.copy_dev_command')"
:classicon="devInstallCopied ? 'i-lucide:check' : 'i-lucide:copy'"
@click.stop="copyDevInstallCommand"
>
<span aria-live="polite">{{
devInstallCopied ? $t('common.copied') : $t('common.copy')
}}</span>
</ButtonBase>
/>
</div>
</template>

Expand All @@ -289,7 +300,7 @@ useCommandPaletteContextCommands(
:data-pm-cmd="pm.id"
class="flex items-center gap-2 min-w-0"
>
<span class="self-start text-fg-subtle font-mono text-sm select-none shrink-0">$</span>
<span class="text-fg-subtle font-mono text-sm select-none shrink-0">$</span>
<code class="font-mono text-sm min-w-0"
><span
v-for="(part, i) in getTypesInstallPartsForPM(pm.id)"
Expand Down Expand Up @@ -324,7 +335,7 @@ useCommandPaletteContextCommands(
:data-pm-cmd="pm.id"
class="flex items-center gap-2 group/runcmd"
>
<span class="self-start text-fg-subtle font-mono text-sm select-none">$</span>
<span class="text-fg-subtle font-mono text-sm select-none shrink-0">$</span>
<code class="font-mono text-sm"
><span
v-for="(part, i) in getRunPartsForPM(pm.id, executableInfo?.primaryCommand)"
Expand All @@ -333,13 +344,14 @@ useCommandPaletteContextCommands(
>{{ i > 0 ? ' ' : '' }}{{ part }}</span
></code
>
<button
<ButtonBase
type="button"
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/runcmd:opacity-100 hover:(text-fg border-border-hover) active:scale-95 focus-visible:opacity-100 focus-visible:outline-accent/70 select-none"
size="icon"
class="text-fg-muted bg-bg-subtle/80 border-border media-mouse:opacity-0 media-mouse:group-hover/runcmd:opacity-100 media-mouse:focus-within:opacity-100 active:scale-95 focus-visible:opacity-100 select-none"
:aria-label="$t('package.run.copy_command')"
:classicon="runCopied ? 'i-lucide:check' : 'i-lucide:copy'"
@click.stop="copyRunCommand(executableInfo?.primaryCommand)"
>
{{ runCopied ? $t('common.copied') : $t('common.copy') }}
</button>
/>
</div>
</template>

Expand Down Expand Up @@ -369,7 +381,7 @@ useCommandPaletteContextCommands(
:data-pm-cmd="pm.id"
class="flex items-center gap-2 group/createcmd"
>
<span class="self-start text-fg-subtle font-mono text-sm select-none">$</span>
<span class="text-fg-subtle font-mono text-sm select-none shrink-0">$</span>
<code class="font-mono text-sm"
><span
v-for="(part, i) in getCreatePartsForPM(pm.id)"
Expand All @@ -378,16 +390,14 @@ useCommandPaletteContextCommands(
>{{ i > 0 ? ' ' : '' }}{{ part }}</span
></code
>
<button
<ButtonBase
type="button"
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/createcmd:opacity-100 hover:(text-fg border-border-hover) active:scale-95 focus-visible:opacity-100 focus-visible:outline-accent/70 select-none"
size="icon"
class="text-fg-muted bg-bg-subtle/80 border-border media-mouse:opacity-0 media-mouse:group-hover/createcmd:opacity-100 media-mouse:focus-within:opacity-100 active:scale-95 focus-visible:opacity-100 select-none"
:aria-label="$t('package.create.copy_command')"
:classicon="createCopied ? 'i-lucide:check' : 'i-lucide:copy'"
@click.stop="copyCreateCommand"
>
<span aria-live="polite">{{
createCopied ? $t('common.copied') : $t('common.copy')
}}</span>
</button>
/>
</div>
</template>
</div>
Expand Down
3 changes: 2 additions & 1 deletion app/composables/useInstallCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,9 @@ export function useInstallCommand(
const { copied, copy } = useClipboard({ copiedDuring: 2000 })

async function copyInstallCommand() {
if (!fullInstallCommand.value) return
if (!fullInstallCommand.value) return false
await copy(fullInstallCommand.value)
return true
}

return {
Expand Down
11 changes: 10 additions & 1 deletion i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,16 @@
},
"run": {
"title": "Run",
"locally": "Run locally"
"locally": "Run locally",
"copy_command": "Copy command to run locally"
},
"command": {
"copied_install": "Install command copied",
"copied_dev_install": "Dev install command copied",
"copied_run": "Run command copied",
"copied_create": "Create command copied",
"copied_execute": "Execute command copied",
"copied_skills": "Skills command copied"
},
"readme": {
"title": "Readme",
Expand Down
27 changes: 27 additions & 0 deletions i18n/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1410,6 +1410,33 @@
},
"locally": {
"type": "string"
},
"copy_command": {
"type": "string"
}
},
"additionalProperties": false
},
"command": {
"type": "object",
"properties": {
"copied_install": {
"type": "string"
},
"copied_dev_install": {
"type": "string"
},
"copied_run": {
"type": "string"
},
"copied_create": {
"type": "string"
},
"copied_execute": {
"type": "string"
},
"copied_skills": {
"type": "string"
}
},
"additionalProperties": false
Expand Down
Loading
Loading