Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
2 changes: 2 additions & 0 deletions app/components/CopyToClipboardButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const props = defineProps<{
ariaLabelCopy?: string
ariaLabelCopied?: string
buttonAttrs?: HTMLAttributes
hideCopyText?: boolean
}>()

const buttonCopyText = computed(() => props.copyText || $t('common.copy'))
Expand All @@ -32,6 +33,7 @@ function handleClick() {
<div class="group relative" v-bind="$attrs">
<slot />
<button
v-if="!hideCopyText"
type="button"
@click="handleClick"
class="absolute z-20 inset-is-0 top-full inline-flex items-center gap-1 px-2 py-1 rounded border text-xs font-mono whitespace-nowrap transition-all duration-150 opacity-0 -translate-y-1 pointer-events-none group-hover:opacity-100 group-hover:translate-y-0 group-hover:pointer-events-auto focus-visible:opacity-100 focus-visible:translate-y-0 focus-visible:pointer-events-auto"
Expand Down
72 changes: 59 additions & 13 deletions app/components/Package/Header.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,21 @@ const packageHeaderHeight = usePackageHeaderHeight()
const header = useTemplateRef('header')
const isHeaderPinned = shallowRef(false)
const { height: headerHeight } = useElementBounding(header)
const versionSelectorRef = useTemplateRef('versionSelectorRef')
const versionSelectorOpen = computed(() => versionSelectorRef.value?.isOpen ?? false)
const shouldHideCopyText = ref(false)
let hideTextTimeout: ReturnType<typeof setTimeout> | null = null

watch(versionSelectorOpen, isOpen => {
if (isOpen) {
shouldHideCopyText.value = true
if (hideTextTimeout) clearTimeout(hideTextTimeout)
} else {
hideTextTimeout = setTimeout(() => {
shouldHideCopyText.value = false
}, 300)
}
})

function isStickyPinned(el: HTMLElement | null): boolean {
if (!el) return false
Expand Down Expand Up @@ -61,6 +76,7 @@ const { y: scrollY } = useScroll(window)
const showScrollToTop = computed(() => scrollY.value > SCROLL_TO_TOP_THRESHOLD)

const packageName = computed(() => props.pkg?.name ?? '')
const resolvedVersionDisplay = computed(() => props.resolvedVersion ?? '')
const fundingUrl = computed(() => {
let funding = props.displayVersion?.funding
if (Array.isArray(funding)) funding = funding[0]
Expand All @@ -75,6 +91,11 @@ const { copied: copiedPkgName, copy: copyPkgName } = useClipboard({
copiedDuring: 2000,
})

const { copied: copiedPkgVersion, copy: copyPkgVersion } = useClipboard({
source: resolvedVersionDisplay,
copiedDuring: 2000,
})

function hasProvenance(version: PackumentVersion | null): boolean {
if (!version?.dist) return false
return !!(version.dist as { attestations?: unknown }).attestations
Expand All @@ -86,8 +107,10 @@ useCommandPaletteContextCommands(
computed((): CommandPaletteContextCommandInput[] => {
if (!packageName.value) return []

const commands: CommandPaletteContextCommandInput[] = [
{
const commands: CommandPaletteContextCommandInput[] = []

if (packageName.value) {
commands.push({
id: 'package-copy-name',
group: 'package',
label: $t('package.copy_name'),
Expand All @@ -97,8 +120,22 @@ useCommandPaletteContextCommands(
copyPkgName()
announce($t('command_palette.announcements.copied_to_clipboard'))
},
},
]
})
}

if (props.resolvedVersion) {
commands.push({
id: 'package-copy-version',
group: 'package',
label: $t('package.versions.copy_version'),
keywords: [props.resolvedVersion ?? ''],
iconClass: 'i-lucide:copy',
action: () => {
copyPkgVersion()
announce($t('command_palette.announcements.copied_to_clipboard'))
},
})
}

if (fundingUrl.value) {
commands.push({
Expand Down Expand Up @@ -287,16 +324,25 @@ useShortcuts({
<span class="i-lucide:cable rtl-flip min-w-3 w-3 h-3 mx-1" aria-hidden="true" />
</TooltipApp>
</template>
<!-- Version selector -->
<VersionSelector
<CopyToClipboardButton
:copied="copiedPkgVersion"
v-if="resolvedVersion && pkg?.versions && pkg?.['dist-tags']"
:package-name="packageName"
:current-version="resolvedVersion"
:versions="pkg.versions"
:dist-tags="pkg['dist-tags']"
:url-pattern="versionUrlPattern"
position-class="max-md:inset-is-0 md:inset-ie-0"
/>
:copy-text="$t('package.versions.copy_version')"
:hide-copy-text="shouldHideCopyText"
class="inline-flex items-center min-w-0"
@click="copyPkgVersion()"
>
<!-- Version selector -->
<VersionSelector
ref="versionSelectorRef"
:package-name="packageName"
:current-version="resolvedVersion"
:versions="pkg.versions"
:dist-tags="pkg['dist-tags']"
:url-pattern="versionUrlPattern"
position-class="max-md:inset-is-0 md:inset-ie-0"
/>
</CopyToClipboardButton>
</div>
</div>
<!-- Docs + Code — inline on desktop, floating bottom bar on mobile -->
Expand Down
5 changes: 5 additions & 0 deletions app/components/VersionSelector.vue
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,11 @@ watch(
}
},
)

// Expose isOpen state to parent components
defineExpose({
isOpen,
})
</script>

<template>
Expand Down
3 changes: 2 additions & 1 deletion i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,8 @@
},
"page_title": "Version History",
"current_tags": "Current Tags",
"no_match_filter": "No versions match {filter}"
"no_match_filter": "No versions match {filter}",
"copy_version": "Copy version number"
},
"timeline": {
"load_more": "Load more",
Expand Down
3 changes: 3 additions & 0 deletions i18n/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1716,6 +1716,9 @@
},
"no_match_filter": {
"type": "string"
},
"copy_version": {
"type": "string"
}
},
"additionalProperties": false
Expand Down
Loading