Skip to content

Commit f7699d0

Browse files
committed
Merge remote-tracking branch 'origin/main' into feat/update-all-sidebar-elements-to-collapsible
# Conflicts: # app/pages/package/[...package].vue
2 parents 2e2df55 + 6a41d10 commit f7699d0

19 files changed

Lines changed: 181 additions & 108 deletions

File tree

app/components/Filter/Chips.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const emit = defineEmits<{
1414
<template>
1515
<div v-if="chips.length > 0" class="flex flex-wrap items-center gap-2">
1616
<TransitionGroup name="chip">
17-
<span v-for="chip in chips" :key="chip.id" class="tag gap-1">
17+
<TagStatic v-for="chip in chips" :key="chip.id" class="gap-1">
1818
<span class="text-fg-subtle text-xs">{{ chip.label }}:</span>
1919
<span class="max-w-32 truncate">{{
2020
Array.isArray(chip.value) ? chip.value.join(', ') : chip.value
@@ -27,7 +27,7 @@ const emit = defineEmits<{
2727
>
2828
<span class="i-carbon-close w-3 h-3" aria-hidden="true" />
2929
</button>
30-
</span>
30+
</TagStatic>
3131
</TransitionGroup>
3232

3333
<button

app/components/Filter/Panel.vue

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -243,22 +243,17 @@ const hasActiveFilters = computed(() => !!filterSummary.value)
243243
role="radiogroup"
244244
:aria-label="$t('filters.weekly_downloads')"
245245
>
246-
<button
246+
<TagClickable
247247
v-for="range in DOWNLOAD_RANGES"
248248
:key="range.value"
249249
type="button"
250250
role="radio"
251251
:aria-checked="filters.downloadRange === range.value"
252-
class="tag transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-fg focus-visible:ring-offset-1"
253-
:class="
254-
filters.downloadRange === range.value
255-
? 'bg-fg text-bg border-fg hover:text-bg/50'
256-
: ''
257-
"
252+
:status="filters.downloadRange === range.value ? 'active' : 'default'"
258253
@click="emit('update:downloadRange', range.value)"
259254
>
260255
{{ $t(getDownloadRangeLabelKey(range.value)) }}
261-
</button>
256+
</TagClickable>
262257
</div>
263258
</fieldset>
264259

@@ -272,22 +267,17 @@ const hasActiveFilters = computed(() => !!filterSummary.value)
272267
role="radiogroup"
273268
:aria-label="$t('filters.updated_within')"
274269
>
275-
<button
270+
<TagClickable
276271
v-for="option in UPDATED_WITHIN_OPTIONS"
277272
:key="option.value"
278273
type="button"
279274
role="radio"
280275
:aria-checked="filters.updatedWithin === option.value"
281-
class="tag transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-fg focus-visible:ring-offset-1"
282-
:class="
283-
filters.updatedWithin === option.value
284-
? 'bg-fg text-bg border-fg hover:text-bg/70'
285-
: ''
286-
"
276+
:status="filters.updatedWithin === option.value ? 'active' : 'default'"
287277
@click="emit('update:updatedWithin', option.value)"
288278
>
289279
{{ $t(getUpdatedWithinLabelKey(option.value)) }}
290-
</button>
280+
</TagClickable>
291281
</div>
292282
</fieldset>
293283

@@ -300,20 +290,17 @@ const hasActiveFilters = computed(() => !!filterSummary.value)
300290
</span>
301291
</legend>
302292
<div class="flex flex-wrap gap-2" role="radiogroup" :aria-label="$t('filters.security')">
303-
<button
293+
<TagClickable
304294
v-for="security in SECURITY_FILTER_VALUES"
305295
:key="security"
306296
type="button"
307297
role="radio"
308298
disabled
309299
:aria-checked="filters.security === security"
310-
class="tag transition-colors duration-200 opacity-50 cursor-not-allowed focus-visible:ring-2 focus-visible:ring-fg focus-visible:ring-offset-1"
311-
:class="
312-
filters.security === security ? 'bg-fg text-bg border-fg hover:text-bg/70' : ''
313-
"
300+
:status="filters.security === security ? 'active' : 'default'"
314301
>
315302
{{ $t(getSecurityLabelKey(security)) }}
316-
</button>
303+
</TagClickable>
317304
</div>
318305
</fieldset>
319306

@@ -323,19 +310,16 @@ const hasActiveFilters = computed(() => !!filterSummary.value)
323310
{{ $t('filters.keywords') }}
324311
</legend>
325312
<div class="flex flex-wrap gap-1.5" role="group" :aria-label="$t('filters.keywords')">
326-
<button
313+
<TagClickable
327314
v-for="keyword in displayedKeywords"
328315
:key="keyword"
329316
type="button"
330317
:aria-pressed="filters.keywords.includes(keyword)"
331-
class="tag text-xs transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-fg focus-visible:ring-offset-1"
332-
:class="
333-
filters.keywords.includes(keyword) ? 'bg-fg text-bg border-fg hover:text-bg/70' : ''
334-
"
318+
:status="filters.keywords.includes(keyword) ? 'active' : 'default'"
335319
@click="emit('toggleKeyword', keyword)"
336320
>
337321
{{ keyword }}
338-
</button>
322+
</TagClickable>
339323
<button
340324
v-if="hasMoreKeywords"
341325
type="button"

app/components/Package/Card.vue

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -162,20 +162,20 @@ const pkgDescription = useMarkdown(() => ({
162162
:aria-label="$t('package.card.keywords')"
163163
class="relative z-10 flex flex-wrap gap-1.5 mt-3 pt-3 border-t border-border list-none m-0 p-0 pointer-events-none"
164164
>
165-
<button
165+
<TagClickable
166166
v-for="keyword in result.package.keywords.slice(0, 5)"
167167
:key="keyword"
168168
type="button"
169-
class="tag text-xs hover:bg-fg hover:text-bg hover:border-fg transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-fg focus-visible:ring-offset-1 border-solid pointer-events-auto"
170-
:class="{ 'bg-fg text-bg hover:opacity-80': props.filters?.keywords.includes(keyword) }"
169+
class="pointer-events-auto"
170+
:status="props.filters?.keywords.includes(keyword) ? 'active' : 'default'"
171171
:title="`Filter by ${keyword}`"
172172
@click.stop="emit('clickKeyword', keyword)"
173173
>
174174
{{ keyword }}
175-
</button>
175+
</TagClickable>
176176
<span
177177
v-if="result.package.keywords.length > 5"
178-
class="tag text-fg-subtle text-xs border-none bg-transparent pointer-events-auto"
178+
class="text-fg-subtle text-xs pointer-events-auto"
179179
:title="result.package.keywords.slice(5).join(', ')"
180180
>
181181
+{{ result.package.keywords.length - 5 }}

app/components/Package/DownloadAnalytics.vue

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,9 @@ const rootEl = shallowRef<HTMLElement | null>(null)
2020
2121
const { width } = useElementSize(rootEl)
2222
23-
const chartKey = ref(0)
24-
25-
let chartRemountTimeoutId: ReturnType<typeof setTimeout> | null = null
26-
2723
onMounted(() => {
2824
rootEl.value = document.documentElement
2925
resolvedMode.value = colorMode.value === 'dark' ? 'dark' : 'light'
30-
31-
// If the chart is painted too early, built-in auto-sizing does not adapt to the final container size
32-
chartRemountTimeoutId = setTimeout(() => {
33-
chartKey.value += 1
34-
chartRemountTimeoutId = null
35-
}, 1)
36-
})
37-
38-
onBeforeUnmount(() => {
39-
if (chartRemountTimeoutId !== null) {
40-
clearTimeout(chartRemountTimeoutId)
41-
chartRemountTimeoutId = null
42-
}
4326
})
4427
4528
const { colors } = useCssVariables(
@@ -718,12 +701,7 @@ const config = computed(() => {
718701
</div>
719702

720703
<ClientOnly v-if="inModal && chartData.dataset">
721-
<VueUiXy
722-
:dataset="chartData.dataset"
723-
:config="config"
724-
class="[direction:ltr]"
725-
:key="chartKey"
726-
>
704+
<VueUiXy :dataset="chartData.dataset" :config="config" class="[direction:ltr]">
727705
<template #menuIcon="{ isOpen }">
728706
<span v-if="isOpen" class="i-carbon:close w-6 h-6" aria-hidden="true" />
729707
<span v-else class="i-carbon:overflow-menu-vertical w-6 h-6" aria-hidden="true" />
@@ -836,8 +814,10 @@ const config = computed(() => {
836814
}
837815
838816
/* Override default placement of the refresh button to have it to the minimap's side */
839-
#download-analytics .vue-data-ui-refresh-button {
840-
top: -0.6rem !important;
841-
left: calc(100% + 2rem) !important;
817+
@media screen and (min-width: 767px) {
818+
#download-analytics .vue-data-ui-refresh-button {
819+
top: -0.6rem !important;
820+
left: calc(100% + 2rem) !important;
821+
}
842822
}
843823
</style>

app/components/Package/Keywords.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
<script setup lang="ts">
2-
const props = defineProps<{
2+
defineProps<{
33
keywords?: string[]
44
}>()
55
</script>
66
<template>
77
<CollapsibleSection v-if="keywords?.length" :title="$t('package.keywords_title')" id="keywords">
88
<ul class="flex flex-wrap gap-1.5 list-none m-0 p-0">
99
<li v-for="keyword in keywords.slice(0, 15)" :key="keyword">
10-
<NuxtLink :to="{ name: 'search', query: { q: `keywords:${keyword}` } }" class="tag">
10+
<TagClickable :as="NuxtLink" :to="{ name: 'search', query: { q: `keywords:${keyword}` } }">
1111
{{ keyword }}
12-
</NuxtLink>
12+
</TagClickable>
1313
</li>
1414
</ul>
1515
</CollapsibleSection>

app/components/Package/TableRow.vue

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -123,20 +123,19 @@ const allMaintainersText = computed(() => {
123123
class="flex flex-wrap gap-1"
124124
:aria-label="$t('package.card.keywords')"
125125
>
126-
<button
126+
<TagClickable
127127
v-for="keyword in pkg.keywords.slice(0, 3)"
128128
:key="keyword"
129129
type="button"
130-
class="tag text-xs hover:bg-fg hover:text-bg hover:border-fg transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-fg focus-visible:ring-offset-1 border-solid"
131-
:class="{ 'bg-fg text-bg hover:opacity-80': props.filters?.keywords.includes(keyword) }"
130+
:status="props.filters?.keywords.includes(keyword) ? 'active' : 'default'"
132131
:title="`Filter by ${keyword}`"
133132
@click.stop="emit('clickKeyword', keyword)"
134133
>
135134
{{ keyword }}
136-
</button>
135+
</TagClickable>
137136
<span
138137
v-if="pkg.keywords.length > 3"
139-
class="tag text-fg-subtle text-xs border-none bg-transparent"
138+
class="text-fg-subtle text-xs"
140139
:title="pkg.keywords.slice(3).join(', ')"
141140
>
142141
+{{ pkg.keywords.length - 3 }}

app/components/Package/WeeklyDownloadStats.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ const props = defineProps<{
1111
const chartModal = useModal('chart-modal')
1212
1313
const isChartModalOpen = shallowRef(false)
14-
function openChartModal() {
14+
async function openChartModal() {
1515
isChartModalOpen.value = true
1616
// ensure the component renders before opening the dialog
17-
nextTick(() => chartModal.open())
17+
await nextTick()
18+
await nextTick()
19+
chartModal.open()
1820
}
1921
2022
const { fetchPackageDownloadEvolution } = useCharts()

app/components/Tag/Clickable.vue

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<script setup lang="ts">
2+
const props = withDefaults(
3+
defineProps<{ as?: string | Component; status?: 'default' | 'active'; disabled?: boolean }>(),
4+
{
5+
status: 'default',
6+
as: 'button',
7+
},
8+
)
9+
</script>
10+
11+
<template>
12+
<component
13+
:is="props.as"
14+
class="inline-flex items-center px-2 py-0.5 text-xs font-mono border rounded transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-fg focus-visible:ring-offset-1"
15+
:class="{
16+
'bg-bg-muted text-fg-muted border-border hover:(text-fg border-border-hover)':
17+
status === 'default',
18+
'bg-fg text-bg border-fg hover:(text-text-bg/50)': status === 'active',
19+
'opacity-50 cursor-not-allowed': disabled,
20+
}"
21+
:disabled="disabled"
22+
>
23+
<slot />
24+
</component>
25+
</template>

app/components/Tag/Static.vue

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<script setup lang="ts">
2+
const props = withDefaults(defineProps<{ as?: string | Component }>(), { as: 'span' })
3+
</script>
4+
5+
<template>
6+
<component
7+
:is="as"
8+
class="inline-flex items-center px-2 py-0.5 text-xs font-mono text-fg-muted bg-bg-muted border border-border rounded"
9+
>
10+
<slot />
11+
</component>
12+
</template>

app/components/Tooltip/Base.vue

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<script setup lang="ts">
22
import type { HTMLAttributes } from 'vue'
3+
import type { Placement } from '@floating-ui/vue'
4+
import { autoUpdate, flip, offset, shift, useFloating } from '@floating-ui/vue'
35
46
const props = defineProps<{
57
/** Tooltip text */
@@ -12,34 +14,39 @@ const props = defineProps<{
1214
tooltipAttr?: HTMLAttributes
1315
}>()
1416
15-
const positionClasses: Record<string, string> = {
16-
top: 'bottom-full inset-is-1/2 -translate-x-1/2 mb-1',
17-
bottom: 'top-full inset-is-0 mt-1',
18-
left: 'inset-ie-full top-1/2 -translate-y-1/2 me-2',
19-
right: 'inset-is-full top-1/2 -translate-y-1/2 ms-2',
20-
}
17+
const triggerRef = useTemplateRef('triggerRef')
18+
const tooltipRef = useTemplateRef('tooltipRef')
2119
22-
const tooltipPosition = computed(() => positionClasses[props.position || 'bottom'])
20+
const placement = computed<Placement>(() => props.position || 'bottom')
21+
22+
const { floatingStyles } = useFloating(triggerRef, tooltipRef, {
23+
placement,
24+
whileElementsMounted: autoUpdate,
25+
middleware: [offset(4), flip(), shift({ padding: 8 })],
26+
})
2327
</script>
2428

2529
<template>
26-
<div class="relative inline-flex">
30+
<div ref="triggerRef" class="inline-flex">
2731
<slot />
2832

29-
<Transition
30-
enter-active-class="transition-opacity duration-150 motion-reduce:transition-none"
31-
leave-active-class="transition-opacity duration-100 motion-reduce:transition-none"
32-
enter-from-class="opacity-0"
33-
leave-to-class="opacity-0"
34-
>
35-
<div
36-
v-if="props.isVisible"
37-
class="absolute px-2 py-1 font-mono text-xs text-fg bg-bg-elevated border border-border rounded shadow-lg whitespace-nowrap z-[100] pointer-events-none"
38-
:class="tooltipPosition"
39-
v-bind="tooltipAttr"
33+
<Teleport to="body">
34+
<Transition
35+
enter-active-class="transition-opacity duration-150 motion-reduce:transition-none"
36+
leave-active-class="transition-opacity duration-100 motion-reduce:transition-none"
37+
enter-from-class="opacity-0"
38+
leave-to-class="opacity-0"
4039
>
41-
{{ text }}
42-
</div>
43-
</Transition>
40+
<div
41+
v-if="props.isVisible"
42+
ref="tooltipRef"
43+
class="px-2 py-1 font-mono text-xs text-fg bg-bg-elevated border border-border rounded shadow-lg whitespace-nowrap z-[100] pointer-events-none"
44+
:style="floatingStyles"
45+
v-bind="tooltipAttr"
46+
>
47+
{{ text }}
48+
</div>
49+
</Transition>
50+
</Teleport>
4451
</div>
4552
</template>

0 commit comments

Comments
 (0)