fix transcendence star positioning and update uncap styles
This commit is contained in:
parent
6762c2dab4
commit
d13412dfb9
2 changed files with 180 additions and 14 deletions
|
|
@ -2,10 +2,12 @@
|
|||
|
||||
<script lang="ts">
|
||||
import TranscendenceFragment from './TranscendenceFragment.svelte'
|
||||
import { Portal } from 'bits-ui'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
stage?: number
|
||||
type?: 'character' | 'weapon' | 'summon'
|
||||
editable?: boolean
|
||||
interactive?: boolean
|
||||
tabIndex?: number
|
||||
|
|
@ -17,6 +19,7 @@
|
|||
let {
|
||||
className,
|
||||
stage = 0,
|
||||
type = 'character',
|
||||
editable = false,
|
||||
interactive = false,
|
||||
tabIndex,
|
||||
|
|
@ -27,19 +30,100 @@
|
|||
|
||||
const NUM_FRAGMENTS = 5
|
||||
|
||||
interface PopoverPosition {
|
||||
top: number
|
||||
left: number
|
||||
placement: 'above' | 'below'
|
||||
}
|
||||
|
||||
let visibleStage = $state(stage)
|
||||
let currentStage = $state(stage)
|
||||
let immutable = $state(false)
|
||||
let isPopoverOpen = $state(false)
|
||||
let popoverPosition = $state<PopoverPosition | null>(null)
|
||||
let starElement: HTMLDivElement
|
||||
let popoverElement: HTMLDivElement
|
||||
|
||||
const baseLevel = $derived(type === 'character' ? 100 : 200)
|
||||
const displayLevel = $derived(baseLevel + 10 * visibleStage)
|
||||
|
||||
function calculatePopoverPosition(): PopoverPosition | null {
|
||||
if (!starElement) return null
|
||||
|
||||
const rect = starElement.getBoundingClientRect()
|
||||
const popoverWidth = 100 // Approximate width
|
||||
const popoverHeight = 120 // Approximate height
|
||||
const gap = 8 // Gap between star and popover
|
||||
|
||||
// Calculate available space
|
||||
const spaceBelow = window.innerHeight - rect.bottom
|
||||
const spaceAbove = rect.top
|
||||
|
||||
// Determine vertical placement
|
||||
const placement: 'above' | 'below' =
|
||||
spaceBelow < popoverHeight && spaceAbove > spaceBelow ? 'above' : 'below'
|
||||
|
||||
// Calculate vertical position
|
||||
let top = placement === 'below' ? rect.bottom + gap : rect.top - popoverHeight - gap
|
||||
|
||||
// Center horizontally on star
|
||||
let left = rect.left + rect.width / 2 - popoverWidth / 2
|
||||
|
||||
// Adjust horizontal position if too close to edges
|
||||
const edgeMargin = 8
|
||||
if (left < edgeMargin) {
|
||||
left = edgeMargin
|
||||
} else if (left + popoverWidth > window.innerWidth - edgeMargin) {
|
||||
left = window.innerWidth - popoverWidth - edgeMargin
|
||||
}
|
||||
|
||||
return { top, left, placement }
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
visibleStage = stage
|
||||
currentStage = stage
|
||||
})
|
||||
|
||||
$effect(() => {
|
||||
if (isPopoverOpen) {
|
||||
// Update position when popover opens
|
||||
popoverPosition = calculatePopoverPosition()
|
||||
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
starElement &&
|
||||
!starElement.contains(event.target as Node) &&
|
||||
popoverElement &&
|
||||
!popoverElement.contains(event.target as Node)
|
||||
) {
|
||||
isPopoverOpen = false
|
||||
popoverPosition = null
|
||||
}
|
||||
}
|
||||
|
||||
const updatePosition = () => {
|
||||
popoverPosition = calculatePopoverPosition()
|
||||
}
|
||||
|
||||
// Add listeners
|
||||
document.addEventListener('click', handleClickOutside)
|
||||
window.addEventListener('scroll', updatePosition, true)
|
||||
window.addEventListener('resize', updatePosition)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('click', handleClickOutside)
|
||||
window.removeEventListener('scroll', updatePosition, true)
|
||||
window.removeEventListener('resize', updatePosition)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function handleClick() {
|
||||
if (editable && onStarClick) {
|
||||
onStarClick()
|
||||
} else if (interactive) {
|
||||
isPopoverOpen = !isPopoverOpen
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -54,6 +138,7 @@
|
|||
if (onFragmentClick) {
|
||||
onFragmentClick(newStage)
|
||||
}
|
||||
isPopoverOpen = false
|
||||
}
|
||||
|
||||
function handleFragmentHover(index: number) {
|
||||
|
|
@ -87,25 +172,39 @@
|
|||
role={editable ? 'button' : undefined}
|
||||
aria-label={editable ? 'Transcendence star' : undefined}
|
||||
>
|
||||
<div class="fragments">
|
||||
{#if interactive}
|
||||
{#each Array(NUM_FRAGMENTS) as _, i}
|
||||
{@const loopStage = i + 1}
|
||||
<TranscendenceFragment
|
||||
stage={loopStage}
|
||||
visible={loopStage <= visibleStage}
|
||||
{interactive}
|
||||
onClick={handleFragmentClick}
|
||||
onHover={handleFragmentHover}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
{#if interactive && isPopoverOpen && popoverPosition}
|
||||
<Portal>
|
||||
<div
|
||||
class="popover"
|
||||
class:above={popoverPosition.placement === 'above'}
|
||||
style="top: {popoverPosition.top}px; left: {popoverPosition.left}px"
|
||||
bind:this={popoverElement}
|
||||
>
|
||||
<div class="fragments">
|
||||
{#each Array(NUM_FRAGMENTS) as _, i}
|
||||
{@const loopStage = i + 1}
|
||||
<TranscendenceFragment
|
||||
stage={loopStage}
|
||||
visible={loopStage <= visibleStage}
|
||||
{interactive}
|
||||
onClick={handleFragmentClick}
|
||||
onHover={handleFragmentHover}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="level">
|
||||
<span>Level</span>
|
||||
<span class="level-value" class:pending={visibleStage !== currentStage}>{displayLevel}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Portal>
|
||||
{/if}
|
||||
<i class="figure {className || ''}" class:interactive class:base={className?.includes('base')} />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@use '$src/themes/spacing' as spacing;
|
||||
@use '$src/themes/typography';
|
||||
|
||||
.star {
|
||||
--size: 18px;
|
||||
|
|
@ -234,4 +333,70 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popover {
|
||||
position: fixed;
|
||||
z-index: 1001;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
padding: 12px;
|
||||
width: auto;
|
||||
min-width: 80px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
animation: popover-appear 0.2s ease-out;
|
||||
|
||||
&.above {
|
||||
animation: popover-appear-above 0.2s ease-out;
|
||||
}
|
||||
|
||||
.fragments {
|
||||
position: relative;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.level {
|
||||
font-size: typography.$font-small;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
color: #333;
|
||||
|
||||
.level-value {
|
||||
font-weight: 500;
|
||||
|
||||
&.pending {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes popover-appear {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes popover-appear-above {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(4px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -80,6 +80,7 @@
|
|||
type: 'transcendence',
|
||||
props: {
|
||||
stage: transcendenceStage,
|
||||
type,
|
||||
editable,
|
||||
interactive: editable,
|
||||
onFragmentClick: editable ? handleTranscendenceUpdate : undefined
|
||||
|
|
|
|||
Loading…
Reference in a new issue