135 lines
2.9 KiB
Vue
135 lines
2.9 KiB
Vue
<script setup lang="ts">
|
|
import { CDRAGON_BASE, mapPath } from '~/utils/cdragon'
|
|
|
|
import type { Perk } from '~/types/cdragon'
|
|
|
|
interface Props {
|
|
perk: Perk
|
|
size?: number
|
|
isActive?: boolean
|
|
isKeystone?: boolean
|
|
class?: string
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
size: 48,
|
|
isActive: false,
|
|
isKeystone: false,
|
|
class: ''
|
|
})
|
|
|
|
// Tooltip state - encapsulated in this component
|
|
const tooltipState = reactive({
|
|
show: false,
|
|
perk: null as Perk | null,
|
|
x: 0,
|
|
y: 0
|
|
})
|
|
|
|
const handleMouseEnter = (event: MouseEvent) => {
|
|
tooltipState.perk = props.perk
|
|
|
|
// Calculate optimal position to keep tooltip within viewport
|
|
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
|
|
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.perk = null
|
|
}
|
|
|
|
const perkIconPath = computed(() => CDRAGON_BASE + mapPath(props.perk.iconPath))
|
|
</script>
|
|
|
|
<template>
|
|
<div class="rune-icon-wrapper" @mouseleave="handleMouseLeave">
|
|
<div
|
|
class="rune-icon"
|
|
:class="[
|
|
props.class,
|
|
{
|
|
'rune-activated': isActive,
|
|
'rune-keystone': isKeystone
|
|
}
|
|
]"
|
|
:style="{ width: size + 'px', height: size + 'px' }"
|
|
@mouseenter="handleMouseEnter"
|
|
>
|
|
<NuxtImg :src="perkIconPath" :alt="perk.name || 'Rune'" class="rune-img" />
|
|
</div>
|
|
|
|
<RuneTooltip
|
|
:show="tooltipState.show"
|
|
:perk="tooltipState.perk"
|
|
:x="tooltipState.x"
|
|
:y="tooltipState.y"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.rune-icon-wrapper {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
}
|
|
|
|
.rune-icon {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border-radius: 50%;
|
|
border: 1px solid var(--color-on-surface);
|
|
overflow: hidden;
|
|
cursor: help;
|
|
position: relative;
|
|
}
|
|
|
|
.rune-icon.rune-keystone {
|
|
border: none;
|
|
}
|
|
|
|
.rune-img {
|
|
width: 100%;
|
|
height: 100%;
|
|
filter: grayscale(1);
|
|
}
|
|
|
|
.rune-icon.rune-activated .rune-img {
|
|
filter: none;
|
|
}
|
|
</style>
|