Skip to content

Commit dafc475

Browse files
committed
feat(Button, PackageDownloadButton): Add async download functionality
1 parent 07261cb commit dafc475

3 files changed

Lines changed: 50 additions & 14 deletions

File tree

app/components/Button/Base.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ defineExpose({
3939
'text-xs px-2 py-2': size === 'small' && variant === 'subtle',
4040
'border-border': variant !== 'subtle',
4141
'border-border-subtle': variant === 'subtle',
42-
'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))':
42+
'bg-transparent text-fg hover:enabled:(bg-fg/10) aria-pressed:(bg-fg/10 border-fg/20 hover:enabled:(bg-fg/20 text-fg/50))':
4343
variant === 'secondary',
44-
'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))':
44+
'text-bg bg-fg hover:enabled:(bg-fg/50) aria-pressed:(bg-fg text-bg border-fg hover:enabled:(text-bg/50))':
4545
variant === 'primary',
46-
'bg-bg-subtle text-fg-muted hover:enabled:(text-fg border-border-hover) focus-visible:enabled:(text-fg border-border-hover) active:enabled:scale-95':
46+
'bg-bg-subtle text-fg-muted hover:enabled:(text-fg border-border-hover) active:enabled:scale-95':
4747
variant === 'subtle',
4848
}"
4949
:type="props.type"

app/components/Package/DownloadButton.vue

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -102,16 +102,31 @@ function handleAction(id: string | undefined) {
102102
close()
103103
}
104104
105-
function downloadPackage() {
105+
async function downloadPackage() {
106106
const tarballUrl = props.version.dist.tarball
107107
if (!tarballUrl) return
108108
109-
const link = document.createElement('a')
110-
link.href = tarballUrl
111-
link.download = `${props.packageName}-${props.version.version}.tgz`
112-
document.body.appendChild(link)
113-
link.click()
114-
document.body.removeChild(link)
109+
try {
110+
const response = await fetch(tarballUrl)
111+
const blob = await response.blob()
112+
const url = URL.createObjectURL(blob)
113+
const link = document.createElement('a')
114+
link.href = url
115+
link.download = `${props.packageName.replace(/\//g, '__')}-${props.version.version}.tgz`
116+
document.body.appendChild(link)
117+
link.click()
118+
document.body.removeChild(link)
119+
URL.revokeObjectURL(url)
120+
} catch (error) {
121+
console.error('Failed to download package:', error)
122+
// Fallback to direct link for non-CORS or other issues, though download attribute may be ignored
123+
const link = document.createElement('a')
124+
link.href = tarballUrl
125+
link.download = `${props.packageName.replace(/\//g, '__')}-${props.version.version}.tgz`
126+
document.body.appendChild(link)
127+
link.click()
128+
document.body.removeChild(link)
129+
}
115130
}
116131
117132
function downloadDependenciesScript() {
@@ -125,18 +140,21 @@ function downloadDependenciesScript() {
125140
]
126141
127142
// Add root package
128-
const rootTarball = (props.version.dist as any).tarball
143+
const rootTarball = props.version.dist.tarball
129144
if (rootTarball) {
130145
lines.push(`# ${props.packageName}@${props.version.version}`)
131146
lines.push(
132-
`curl -L ${rootTarball} -o ${props.packageName.replace(/\//g, '__')}-${props.version.version}.tgz`,
147+
`curl -L "${rootTarball}" -o "${props.packageName.replace(/\//g, '__')}-${props.version.version}.tgz"`,
133148
)
134149
}
135150
136151
// Add dependencies
137-
props.installSize.dependencies.forEach((dep: any) => {
152+
props.installSize.dependencies.forEach(dep => {
153+
if (!dep.tarballUrl) return
138154
lines.push(`# ${dep.name}@${dep.version}`)
139-
lines.push(`curl -L ${dep.tarballUrl} -o ${dep.name.replace(/\//g, '__')}-${dep.version}.tgz`)
155+
lines.push(
156+
`curl -L "${dep.tarballUrl}" -o "${dep.name.replace(/\//g, '__')}-${dep.version}.tgz"`,
157+
)
140158
})
141159
142160
const blob = new Blob([lines.join('\n')], { type: 'text/x-shellscript' })

test/nuxt/a11y.spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ import {
160160
PackageListControls,
161161
PackageListToolbar,
162162
PackageMaintainers,
163+
PackageDownloadButton,
163164
PackageManagerSelect,
164165
PackageMetricsBadges,
165166
PackagePlaygrounds,
@@ -2509,6 +2510,23 @@ describe('component accessibility audits', () => {
25092510
expect(results.violations).toEqual([])
25102511
})
25112512
})
2513+
2514+
describe('PackageDownloadButton', () => {
2515+
it('should have no accessibility violations', async () => {
2516+
const component = await mountSuspended(PackageDownloadButton, {
2517+
props: {
2518+
packageName: 'vue',
2519+
version: {
2520+
version: '3.5.0',
2521+
dist: { tarball: 'https://registry.npmjs.org/vue/-/vue-3.5.0.tgz' },
2522+
} as any,
2523+
installSize: null,
2524+
},
2525+
})
2526+
const results = await runAxe(component)
2527+
expect(results.violations).toEqual([])
2528+
})
2529+
})
25122530
})
25132531

25142532
function applyTheme(colorMode: string, bgTheme: string | null) {

0 commit comments

Comments
 (0)