Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
37 changes: 34 additions & 3 deletions app/components/CollapsibleSection.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
<script setup lang="ts">
import { shallowRef, computed } from 'vue'
import { computed, shallowRef } from 'vue'

interface Props {
title: string
isLoading?: boolean
headingLevel?: `h${number}`
id: string
order?: number
}

const props = withDefaults(defineProps<Props>(), {
Expand All @@ -20,6 +21,7 @@ const contentId = `${props.id}-collapsible-content`
const headingId = `${props.id}-heading`

const isOpen = shallowRef(true)
const isPinned = shallowRef(false)

onPrehydrate(() => {
const settings = JSON.parse(localStorage.getItem('npmx-settings') || '{}')
Expand All @@ -38,13 +40,14 @@ onPrehydrate(() => {
onMounted(() => {
if (document?.documentElement) {
isOpen.value = !(document.documentElement.dataset.collapsed?.includes(props.id) ?? false)
isPinned.value = document.documentElement.dataset.pinned?.includes(props.id) ?? false
}
})

function toggle() {
isOpen.value = !isOpen.value

const removed = appSettings.settings.value.sidebar.collapsed.filter(c => c !== props.id)
const removed = appSettings.settings.value.sidebar.collapsed?.filter(c => c !== props.id) ?? []

if (isOpen.value) {
appSettings.settings.value.sidebar.collapsed = removed
Expand All @@ -57,6 +60,21 @@ function toggle() {
appSettings.settings.value.sidebar.collapsed.join(' ')
}

function togglePin() {
isPinned.value = !isPinned.value

const removed = appSettings.settings.value.sidebar.pinned?.filter(c => c !== props.id) ?? []

if (isPinned.value) {
removed.push(props.id)
appSettings.settings.value.sidebar.pinned = removed
} else {
appSettings.settings.value.sidebar.pinned = removed
}

document.documentElement.dataset.pinned = appSettings.settings.value.sidebar.pinned.join(' ')
}

const ariaLabel = computed(() => {
const action = isOpen.value ? 'Collapse' : 'Expand'
return props.title ? `${action} ${props.title}` : action
Expand All @@ -74,7 +92,11 @@ useHead({
</script>

<template>
<section class="scroll-mt-20" :data-anchor-id="id">
<section
class="scroll-mt-20"
:class="order !== undefined ? `order-${order}` : ''"
:data-anchor-id="id"
>
<div class="flex items-center justify-between mb-3">
<component
:is="headingLevel"
Expand Down Expand Up @@ -117,6 +139,15 @@ useHead({

<!-- Actions slot for buttons or other elements -->
<slot name="actions" />
<!-- pin button -->
<button
type="button"
class="w-4 h-4 flex items-center justify-center text-fg-subtle hover:text-fg-muted transition-colors duration-200 shrink-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 rounded"
@click="togglePin"
>
<span v-if="isPinned" class="i-carbon:pin-filled w-3 h-3" aria-hidden="true" />
<span v-else class="i-carbon:pin w-3 h-3" aria-hidden="true" />
</button>
</div>

<div
Expand Down
13 changes: 3 additions & 10 deletions app/components/Package/InstallScripts.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const props = defineProps<{
content?: Record<string, string>
npxDependencies: Record<string, string>
}
order: number
}>()

const outdatedNpxDeps = useOutdatedDependencies(() => props.installScripts.npxDependencies)
Expand All @@ -18,15 +19,7 @@ const isExpanded = shallowRef(false)
</script>

<template>
<section>
<h2
id="install-scripts-heading"
class="text-xs text-fg-subtle uppercase tracking-wider mb-3 flex items-center gap-2"
>
<span class="i-carbon:warning-alt w-3 h-3 text-yellow-500" aria-hidden="true" />
{{ $t('package.install_scripts.title') }}
</h2>

<CollapsibleSection id="install-scripts" :title="$t('package.install_scripts.title')" :order>
<!-- Script list: name as label, content below -->
<dl class="space-y-2 m-0">
<div v-for="scriptName in installScripts.scripts" :key="scriptName">
Expand Down Expand Up @@ -112,5 +105,5 @@ const isExpanded = shallowRef(false)
</li>
</ul>
</div>
</section>
</CollapsibleSection>
</template>
22 changes: 8 additions & 14 deletions app/components/Package/Maintainers.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { NewOperation } from '~/composables/useConnector'
const props = defineProps<{
packageName: string
maintainers?: Array<{ name?: string; email?: string }>
order: number
}>()

const {
Expand Down Expand Up @@ -168,19 +169,12 @@ watch(
</script>

<template>
<section id="maintainers" v-if="maintainers?.length" class="scroll-mt-20">
<h2 id="maintainers-heading" class="group text-xs text-fg-subtle uppercase tracking-wider mb-3">
<a
href="#maintainers"
class="inline-flex items-center gap-1.5 text-fg-subtle hover:text-fg-muted transition-colors duration-200 no-underline"
>
{{ $t('package.maintainers.title') }}
<span
class="i-carbon-link w-3 h-3 block opacity-0 group-hover:opacity-100 transition-opacity duration-200"
aria-hidden="true"
/>
</a>
</h2>
<CollapsibleSection
v-if="maintainers?.length"
id="maintainers"
:title="$t('package.maintainers.title')"
:order
>
<ul class="space-y-2 list-none m-0 p-0" :aria-label="$t('package.maintainers.list_label')">
<li
v-for="maintainer in visibleMaintainers"
Expand Down Expand Up @@ -293,5 +287,5 @@ watch(
{{ $t('package.maintainers.add_owner') }}
</button>
</div>
</section>
</CollapsibleSection>
</template>
14 changes: 8 additions & 6 deletions app/components/Package/Playgrounds.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { decodeHtmlEntities } from '~/utils/formatters'

const props = defineProps<{
links: PlaygroundLink[]
order: number
}>()

// Map provider id to icon class
Expand Down Expand Up @@ -109,11 +110,12 @@ function focusMenuItem(index: number) {
</script>

<template>
<section v-if="links.length > 0">
<h2 id="playgrounds-heading" class="text-xs text-fg-subtle uppercase tracking-wider mb-3">
{{ $t('package.playgrounds.title') }}
</h2>

<CollapsibleSection
v-if="links.length > 0"
id="playgrounds"
:title="$t('package.playgrounds.title')"
:order
>
<div ref="dropdownRef" class="relative">
<!-- Single link: direct button -->
<TooltipApp v-if="hasSingleLink && firstLink" :text="firstLink.providerName" class="w-full">
Expand Down Expand Up @@ -189,5 +191,5 @@ function focusMenuItem(index: number) {
</div>
</Transition>
</div>
</section>
</CollapsibleSection>
</template>
2 changes: 2 additions & 0 deletions app/components/Package/Versions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const props = defineProps<{
versions: Record<string, PackumentVersion>
distTags: Record<string, string>
time: Record<string, string>
order: number
}>()

/** Maximum number of dist-tag rows to show before collapsing into "Other versions" */
Expand Down Expand Up @@ -315,6 +316,7 @@ function getTagVersions(tag: string): VersionDisplay[] {
v-if="allTagRows.length > 0"
:title="$t('package.versions.title')"
id="versions"
:order
>
<div class="space-y-0.5 min-w-0">
<!-- Dist-tag rows (limited to MAX_VISIBLE_TAGS) -->
Expand Down
83 changes: 49 additions & 34 deletions app/components/Package/WeeklyDownloadStats.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

const props = defineProps<{
packageName: string
order: number
}>()

const chartModal = useModal('chart-modal')
Expand Down Expand Up @@ -196,6 +197,20 @@
</script>

<template>
<<<<<<< HEAD:app/components/PackageWeeklyDownloadStats.vue
<CollapsibleSection id="downloads" :title="$t('package.downloads.title')" :order>
<template #actions>
<button
type="button"
@click="showModal = true"

Check failure on line 205 in app/components/Package/WeeklyDownloadStats.vue

View workflow job for this annotation

GitHub Actions / test

Property 'showModal' does not exist on type '{ openChartModal: () => void; dataset: { value: number; period: string; }[]; config: { theme: string; skeletonConfig: { style: { backgroundColor: string; dataLabel: { show: boolean; color: string; }; area: { color: string | undefined; useGradient: boolean; opacity: number; }; line: { ...; }; }; }; skeletonDataset: n...'.
class="link-subtle font-mono text-sm inline-flex items-center gap-1.5 ms-auto shrink-0 self-center focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 rounded"
:title="$t('package.downloads.analyze')"
>
<span class="i-carbon:data-analytics w-4 h-4" aria-hidden="true" />
<span class="sr-only">{{ $t('package.downloads.analyze') }}</span>
</button>
</template>
=======
<div class="space-y-8">
<CollapsibleSection id="downloads" :title="$t('package.downloads.title')">
<template #actions>
Expand All @@ -209,44 +224,44 @@
<span class="sr-only">{{ $t('package.downloads.analyze') }}</span>
</button>
</template>
>>>>>>> main:app/components/Package/WeeklyDownloadStats.vue

<div class="w-full overflow-hidden">
<ClientOnly>
<VueUiSparkline class="w-full max-w-xs" :dataset :config>
<template #skeleton>
<!-- This empty div overrides the default built-in scanning animation on load -->
<div />
</template>
</VueUiSparkline>
<template #fallback>
<!-- Skeleton matching sparkline layout: title row + chart with data label -->
<div class="min-h-[75.195px]">
<!-- Title row: date range (24px height) -->
<div class="h-6 flex items-center ps-3">
<span class="skeleton h-3 w-36" />
<div class="w-full overflow-hidden">
<ClientOnly>
<VueUiSparkline class="w-full max-w-xs" :dataset :config>
<template #skeleton>
<!-- This empty div overrides the default built-in scanning animation on load -->
<div />
</template>
</VueUiSparkline>
<template #fallback>
<!-- Skeleton matching sparkline layout: title row + chart with data label -->
<div class="min-h-[75.195px]">
<!-- Title row: date range (24px height) -->
<div class="h-6 flex items-center ps-3">
<span class="skeleton h-3 w-36" />
</div>
<!-- Chart area: data label left, sparkline right -->
<div class="aspect-[500/80] flex items-center">
<!-- Data label (covers ~42% width) -->
<div class="w-[42%] flex items-center ps-0.5">
<span class="skeleton h-7 w-24" />
</div>
<!-- Chart area: data label left, sparkline right -->
<div class="aspect-[500/80] flex items-center">
<!-- Data label (covers ~42% width) -->
<div class="w-[42%] flex items-center ps-0.5">
<span class="skeleton h-7 w-24" />
</div>
<!-- Sparkline area (~58% width) -->
<div class="flex-1 flex items-end gap-0.5 h-4/5 pe-3">
<span
v-for="i in 16"
:key="i"
class="skeleton flex-1 rounded-sm"
:style="{ height: `${25 + ((i * 7) % 50)}%` }"
/>
</div>
<!-- Sparkline area (~58% width) -->
<div class="flex-1 flex items-end gap-0.5 h-4/5 pe-3">
<span
v-for="i in 16"
:key="i"
class="skeleton flex-1 rounded-sm"
:style="{ height: `${25 + ((i * 7) % 50)}%` }"
/>
</div>
</div>
</template>
</ClientOnly>
</div>
</CollapsibleSection>
</div>
</div>
</template>
</ClientOnly>
</div>
</CollapsibleSection>

<PackageChartModal v-if="isChartModalOpen" @close="isChartModalOpen = false">
<PackageDownloadAnalytics
Expand Down
2 changes: 2 additions & 0 deletions app/composables/useSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface AppSettings {
selectedLocale: LocaleObject['code'] | null
sidebar: {
collapsed: string[]
pinned: string[]
}
}

Expand All @@ -32,6 +33,7 @@ const DEFAULT_SETTINGS: AppSettings = {
selectedLocale: null,
sidebar: {
collapsed: [],
pinned: [],
},
}

Expand Down
Loading
Loading