138 lines
3.1 KiB
Vue
138 lines
3.1 KiB
Vue
<script setup lang="ts">
|
|
import { CDRAGON_BASE, mapPath } from '~/utils/cdragon'
|
|
|
|
interface Props {
|
|
item: Item
|
|
size?: number
|
|
showPickrate?: boolean
|
|
pickrate?: number
|
|
class?: string
|
|
}
|
|
|
|
// Expose the icon element for external use (e.g., arrow drawing)
|
|
const iconElement = ref<HTMLElement | null>(null)
|
|
|
|
defineExpose({
|
|
iconElement
|
|
})
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
size: 48,
|
|
showPickrate: false,
|
|
pickrate: 0,
|
|
class: ''
|
|
})
|
|
|
|
// Tooltip state - encapsulated in this component
|
|
const tooltipState = reactive({
|
|
show: false,
|
|
item: null as Item | null,
|
|
x: 0,
|
|
y: 0
|
|
})
|
|
|
|
const handleMouseEnter = (event: MouseEvent) => {
|
|
tooltipState.item = props.item
|
|
|
|
// Calculate optimal position to keep tooltip within viewport
|
|
// Don't estimate height - position based on cursor location
|
|
const tooltipWidth = 300 // Maximum width from CSS
|
|
const padding = 10 // Minimum padding from edges
|
|
const offset = 15 // Distance from cursor
|
|
|
|
// Get viewport dimensions
|
|
const viewportWidth = window.innerWidth
|
|
const viewportHeight = window.innerHeight
|
|
|
|
// Right edge detection: if we're in the right half, position to the left
|
|
let x = event.clientX + offset
|
|
if (event.clientX + tooltipWidth + offset > viewportWidth - padding) {
|
|
x = event.clientX - tooltipWidth - offset
|
|
// Clamp if still off-screen
|
|
if (x < padding) {
|
|
x = padding
|
|
}
|
|
}
|
|
|
|
// Bottom edge detection: if we're in the bottom half, position above
|
|
// Use a smaller offset for vertical to keep it close
|
|
let y = event.clientY + offset
|
|
if (event.clientY > viewportHeight * 0.7) {
|
|
y = event.clientY - offset - 200 // Position ~200px above
|
|
// Clamp if too high
|
|
if (y < padding) {
|
|
y = padding
|
|
}
|
|
}
|
|
|
|
// Ensure Y is within reasonable bounds
|
|
y = Math.min(y, viewportHeight - padding)
|
|
|
|
tooltipState.x = x
|
|
tooltipState.y = y
|
|
tooltipState.show = true
|
|
}
|
|
|
|
const handleMouseLeave = () => {
|
|
tooltipState.show = false
|
|
tooltipState.item = null
|
|
}
|
|
|
|
const itemIconPath = computed(() => CDRAGON_BASE + mapPath(props.item.iconPath))
|
|
</script>
|
|
|
|
<template>
|
|
<div class="item-icon-wrapper" @mouseleave="handleMouseLeave">
|
|
<div
|
|
ref="iconElement"
|
|
class="item-icon"
|
|
:class="props.class"
|
|
:style="{ width: size + 'px', height: size + 'px' }"
|
|
@mouseenter="handleMouseEnter"
|
|
>
|
|
<NuxtImg :src="itemIconPath" :alt="item.name || 'Item'" class="item-img" />
|
|
</div>
|
|
|
|
<span v-if="showPickrate" class="item-pickrate"> {{ (pickrate * 100).toFixed(0) }}% </span>
|
|
|
|
<ItemTooltip
|
|
:show="tooltipState.show"
|
|
:item="tooltipState.item"
|
|
:x="tooltipState.x"
|
|
:y="tooltipState.y"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.item-icon-wrapper {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
}
|
|
|
|
.item-icon {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border-radius: 4px;
|
|
border: 1px solid var(--color-on-surface);
|
|
overflow: hidden;
|
|
cursor: help;
|
|
position: relative;
|
|
}
|
|
|
|
.item-img {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
.item-pickrate {
|
|
font-size: 0.65rem;
|
|
color: var(--color-on-surface);
|
|
opacity: 0.6;
|
|
margin-top: 2px;
|
|
white-space: nowrap;
|
|
}
|
|
</style>
|