Let library prompt previews escape the video tile.

The info popover was clipped to the card, so the prompt sat inside the thumbnail frame instead of over the page.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-09-01 21:51:34 -05:00
co-authored by Cursor
parent 6b93ee951c
commit 03687b0bd6
+81 -4
View File
@@ -1,5 +1,13 @@
<template> <template>
<div class="group relative shrink-0" @wheel.prevent="onIconWheel"> <div
ref="root"
class="relative shrink-0"
@mouseenter="open"
@mouseleave="scheduleClose"
@focusin="open"
@focusout="onFocusOut"
@wheel.prevent="onIconWheel"
>
<button <button
type="button" type="button"
class="relative z-10 flex h-6 w-6 items-center justify-center rounded-full text-zinc-500 hover:text-amber-200" class="relative z-10 flex h-6 w-6 items-center justify-center rounded-full text-zinc-500 hover:text-amber-200"
@@ -12,14 +20,19 @@
<path d="M12 16v-4M12 8h.01" stroke-linecap="round" /> <path d="M12 16v-4M12 8h.01" stroke-linecap="round" />
</svg> </svg>
</button> </button>
<div class="invisible absolute right-0 top-full z-20 -mt-1 pt-2 group-hover:visible group-focus-within:visible"> <Teleport to="body">
<div <div
v-if="show"
ref="panel" ref="panel"
class="pointer-events-auto max-h-48 w-64 max-w-[calc(100vw-2rem)] overflow-y-auto overscroll-contain whitespace-pre-wrap break-words rounded-xl border border-white/10 bg-zinc-950 p-3 text-xs leading-relaxed text-zinc-300 shadow-2xl" class="fixed z-[80] max-h-[min(20rem,50vh)] w-[min(28rem,calc(100vw-1.5rem))] overflow-y-auto overscroll-contain whitespace-pre-wrap break-words rounded-xl border border-white/10 bg-zinc-950 p-3 text-xs leading-relaxed text-zinc-300 shadow-2xl"
:style="panelStyle"
@mouseenter="open"
@mouseleave="scheduleClose"
@click.stop
> >
{{ text }} {{ text }}
</div> </div>
</div> </Teleport>
</div> </div>
</template> </template>
@@ -33,11 +46,75 @@ const emit = defineEmits<{
copy: [text: string] copy: [text: string]
}>() }>()
const root = ref<HTMLElement | null>(null)
const panel = ref<HTMLElement | null>(null) const panel = ref<HTMLElement | null>(null)
const show = ref(false)
const panelStyle = ref<Record<string, string>>({})
let closeTimer: ReturnType<typeof setTimeout> | null = null
function place() {
const el = root.value
if (!el) return
const rect = el.getBoundingClientRect()
const width = Math.min(448, window.innerWidth - 24)
const maxHeight = Math.min(320, window.innerHeight * 0.5)
let left = rect.right - width
left = Math.min(Math.max(12, left), window.innerWidth - width - 12)
const below = rect.bottom + 8
const above = rect.top - 8 - maxHeight
const top = below + maxHeight <= window.innerHeight - 12 ? below : Math.max(12, above)
panelStyle.value = {
top: `${Math.round(top)}px`,
left: `${Math.round(left)}px`,
width: `${Math.round(width)}px`
}
}
function open() {
if (closeTimer) {
clearTimeout(closeTimer)
closeTimer = null
}
show.value = true
nextTick(place)
}
function close() {
show.value = false
}
function scheduleClose() {
if (closeTimer) clearTimeout(closeTimer)
closeTimer = setTimeout(close, 120)
}
function onFocusOut(event: FocusEvent) {
const next = event.relatedTarget as Node | null
if (root.value?.contains(next) || panel.value?.contains(next)) return
scheduleClose()
}
function onIconWheel(event: WheelEvent) { function onIconWheel(event: WheelEvent) {
const el = panel.value const el = panel.value
if (!el) return if (!el) return
el.scrollTop += event.deltaY el.scrollTop += event.deltaY
} }
watch(show, (value) => {
if (typeof window === 'undefined') return
if (value) {
window.addEventListener('resize', place)
window.addEventListener('scroll', place, true)
return
}
window.removeEventListener('resize', place)
window.removeEventListener('scroll', place, true)
})
onBeforeUnmount(() => {
if (closeTimer) clearTimeout(closeTimer)
if (typeof window === 'undefined') return
window.removeEventListener('resize', place)
window.removeEventListener('scroll', place, true)
})
</script> </script>