Skip to content

Commit ddcc20d

Browse files
committed
refactor: simplify button and css
1 parent 1b7487a commit ddcc20d

File tree

4 files changed

+32
-184
lines changed

4 files changed

+32
-184
lines changed

app/components/Button/Base.vue

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const props = withDefaults(
1414
/** @default "button" */
1515
type?: 'button' | 'submit'
1616
/** @default "secondary" */
17-
variant?: 'primary' | 'secondary' | 'subtle'
17+
variant?: 'primary' | 'secondary'
1818
/** @default "medium" */
1919
size?: 'small' | 'medium'
2020
/** Keyboard shortcut hint */
@@ -44,21 +44,16 @@ defineExpose({
4444
<template>
4545
<button
4646
ref="el"
47-
class="group gap-x-1 items-center justify-center font-mono border rounded-md transition-all duration-200 disabled:(opacity-40 cursor-not-allowed border-transparent)"
47+
class="group gap-x-1 items-center justify-center font-mono border border-border rounded-md transition-all duration-200 disabled:(opacity-40 cursor-not-allowed border-transparent)"
4848
:class="{
4949
'inline-flex': !block,
5050
'flex': block,
5151
'text-sm px-4 py-2': size === 'medium',
52-
'text-xs px-2 py-0.5': size === 'small' && variant !== 'subtle',
53-
'text-xs px-2 py-2': size === 'small' && variant === 'subtle',
54-
'border-border': variant !== 'subtle',
55-
'border-border-subtle': variant === 'subtle',
56-
'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))':
52+
'text-xs px-2 py-0.5': size === 'small',
53+
'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))':
5754
variant === 'secondary',
58-
'text-bg bg-fg hover:enabled:(bg-fg/50) aria-pressed:(bg-fg text-bg border-fg hover:enabled:(text-bg/50))':
55+
'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))':
5956
variant === 'primary',
60-
'bg-bg-subtle text-fg-muted hover:enabled:(text-fg border-border-hover) active:enabled:scale-95':
61-
variant === 'subtle',
6257
}"
6358
:type="props.type"
6459
:disabled="
Lines changed: 26 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -1,115 +1,24 @@
11
<script setup lang="ts">
22
import type { SlimPackumentVersion } from '#shared/types'
3-
import { onClickOutside, useEventListener, useMediaQuery } from '@vueuse/core'
43
5-
const props = withDefaults(
6-
defineProps<{
7-
packageName: string
8-
version: SlimPackumentVersion
9-
size?: 'small' | 'medium'
10-
}>(),
11-
{
12-
size: 'medium',
13-
},
14-
)
4+
const props = defineProps<{
5+
packageName: string
6+
version: SlimPackumentVersion
7+
}>()
158
16-
const triggerRef = useTemplateRef('triggerRef')
17-
const listRef = useTemplateRef('listRef')
18-
const isOpen = shallowRef(false)
19-
const highlightedIndex = shallowRef(-1)
20-
const dropdownPosition = shallowRef<{ top: number; left: number } | null>(null)
21-
22-
const menuId = 'download-menu'
23-
const menuItems = computed(() => {
24-
const items: { id: string; icon: string; disabled: boolean }[] = [
25-
{ id: 'package', icon: 'i-lucide:package', disabled: false },
26-
]
27-
return items
28-
})
29-
30-
const prefersReducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)')
31-
32-
function getDropdownStyle(): Record<string, string> {
33-
if (!dropdownPosition.value) return {}
34-
return {
35-
top: `${dropdownPosition.value.top}px`,
36-
left: `${dropdownPosition.value.left}px`,
37-
}
38-
}
39-
40-
function toggle() {
41-
if (isOpen.value) {
42-
close()
43-
} else {
44-
const rect = triggerRef.value?.$el?.getBoundingClientRect()
45-
if (rect) {
46-
dropdownPosition.value = {
47-
top: rect.bottom + 4,
48-
left: rect.left,
49-
}
50-
}
51-
isOpen.value = true
52-
highlightedIndex.value = 0
53-
}
54-
}
55-
56-
function close() {
57-
isOpen.value = false
58-
highlightedIndex.value = -1
59-
}
60-
61-
onClickOutside(listRef, close, { ignore: [triggerRef] })
62-
63-
function handleKeydown(event: KeyboardEvent) {
64-
if (!isOpen.value) {
65-
if (event.key === 'ArrowDown' || event.key === 'Enter' || event.key === ' ') {
66-
event.preventDefault()
67-
toggle()
68-
}
69-
return
70-
}
71-
72-
switch (event.key) {
73-
case 'ArrowDown':
74-
event.preventDefault()
75-
highlightedIndex.value = (highlightedIndex.value + 1) % menuItems.value.length
76-
break
77-
case 'ArrowUp':
78-
event.preventDefault()
79-
highlightedIndex.value =
80-
highlightedIndex.value <= 0 ? menuItems.value.length - 1 : highlightedIndex.value - 1
81-
break
82-
case 'Enter':
83-
case ' ':
84-
event.preventDefault()
85-
handleAction(menuItems.value[highlightedIndex.value])
86-
break
87-
case 'Escape':
88-
event.preventDefault()
89-
close()
90-
triggerRef.value?.$el?.focus()
91-
break
92-
case 'Tab':
93-
close()
94-
break
95-
}
96-
}
97-
98-
function handleAction(item: (typeof menuItems.value)[number] | undefined) {
99-
if (!item || item.disabled) return
100-
if (item.id === 'package') {
101-
downloadPackage()
102-
}
103-
close()
104-
}
9+
const loading = shallowRef(false)
10510
10611
async function downloadPackage() {
10712
const tarballUrl = props.version.dist.tarball
10813
if (!tarballUrl) return
10914
15+
if (loading.value) return
16+
loading.value = true
17+
11018
try {
11119
const response = await fetch(tarballUrl)
11220
if (!response.ok) {
21+
loading.value = false
11322
throw new Error(`Failed to fetch tarball (${response.status})`)
11423
}
11524
const blob = await response.blob()
@@ -130,80 +39,26 @@ async function downloadPackage() {
13039
link.click()
13140
document.body.removeChild(link)
13241
}
133-
}
13442
135-
useEventListener('scroll', () => isOpen.value && close(), { passive: true })
136-
137-
defineOptions({
138-
inheritAttrs: false,
139-
})
43+
loading.value = false
44+
}
14045
</script>
14146

14247
<template>
143-
<ButtonBase
144-
ref="triggerRef"
145-
v-bind="$attrs"
146-
type="button"
147-
:variant="size === 'small' ? 'subtle' : 'secondary'"
148-
:size
149-
classicon="i-lucide:download"
150-
:aria-expanded="isOpen"
151-
aria-haspopup="menu"
152-
:aria-controls="menuId"
153-
@click="toggle"
154-
@keydown="handleKeydown"
155-
>
156-
{{ $t('package.download.button') }}
157-
<span
158-
class="i-lucide:chevron-down ms-1"
159-
:class="[
160-
size === 'small' ? 'w-3 h-3' : 'w-3.5 h-3.5',
161-
{ 'rotate-180': isOpen },
162-
prefersReducedMotion ? '' : 'transition-transform duration-200',
163-
]"
164-
aria-hidden="true"
165-
/>
166-
</ButtonBase>
167-
168-
<Teleport to="body">
169-
<Transition
170-
:enter-active-class="prefersReducedMotion ? '' : 'transition-opacity duration-150'"
171-
:enter-from-class="prefersReducedMotion ? '' : 'opacity-0'"
172-
enter-to-class="opacity-100"
173-
:leave-active-class="prefersReducedMotion ? '' : 'transition-opacity duration-100'"
174-
leave-from-class="opacity-100"
175-
:leave-to-class="prefersReducedMotion ? '' : 'opacity-0'"
48+
<TooltipApp :text="$t('package.download.tarball')">
49+
<ButtonBase
50+
ref="triggerRef"
51+
v-bind="$attrs"
52+
type="button"
53+
@click="downloadPackage"
54+
:disabled="loading"
55+
class="border-border-subtle bg-bg-subtle! text-fg-muted hover:enabled:(text-fg border-border-hover)"
17656
>
177-
<ul
178-
v-if="isOpen"
179-
:id="menuId"
180-
ref="listRef"
181-
role="menu"
182-
:aria-label="$t('package.download.button')"
183-
:style="getDropdownStyle()"
184-
class="fixed bg-bg-subtle border border-border rounded-md shadow-lg z-50"
185-
@keydown="handleKeydown"
186-
>
187-
<li
188-
v-for="(item, index) in menuItems"
189-
:key="item.id"
190-
role="menuitem"
191-
:aria-disabled="item.disabled || undefined"
192-
class="flex items-center gap-2 px-3 py-1.5 text-sm transition-colors duration-150"
193-
:class="[
194-
item.disabled
195-
? 'cursor-default text-fg-muted/50'
196-
: highlightedIndex === index
197-
? 'cursor-pointer bg-bg-elevated text-fg'
198-
: 'cursor-pointer text-fg-muted hover:bg-bg-elevated hover:text-fg',
199-
]"
200-
@click="handleAction(item)"
201-
@mouseenter="highlightedIndex = index"
202-
>
203-
<span :class="item.icon" class="w-4 h-4" aria-hidden="true" />
204-
{{ $t('package.download.package') }}
205-
</li>
206-
</ul>
207-
</Transition>
208-
</Teleport>
57+
<span
58+
class="size-[1em]"
59+
aria-hidden="true"
60+
:class="loading ? 'i-lucide:loader-circle animate-spin' : 'i-lucide:download'"
61+
/>
62+
</ButtonBase>
63+
</TooltipApp>
20964
</template>

app/pages/package/[[org]]/[name].vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -777,7 +777,6 @@ const showSkeleton = shallowRef(false)
777777
v-if="displayVersion"
778778
:package-name="pkg.name"
779779
:version="displayVersion"
780-
size="small"
781780
/>
782781
<PackageManagerSelect />
783782
</div>

i18n/locales/en.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -631,8 +631,7 @@
631631
"mb": "{size} MB"
632632
},
633633
"download": {
634-
"button": "Download",
635-
"package": "Download Package (.tgz)"
634+
"tarball": "Download Package (.tgz)"
636635
}
637636
},
638637
"connector": {

0 commit comments

Comments
 (0)