140 lines
4.0 KiB
Vue
140 lines
4.0 KiB
Vue
<template>
|
|
<div
|
|
:class="[
|
|
'runner-pill',
|
|
runner ? 'occupied' : 'empty',
|
|
isSelected ? 'selected' : '',
|
|
isHeld ? 'held' : ''
|
|
]"
|
|
@click="handleClick"
|
|
>
|
|
<template v-if="runner">
|
|
<!-- Occupied base -->
|
|
<div class="w-8 h-8 rounded-full flex-shrink-0 overflow-hidden border-2" :style="{ borderColor: teamColor }">
|
|
<img
|
|
v-if="runnerPlayer?.headshot || runnerPlayer?.image"
|
|
:src="runnerPlayer.headshot || runnerPlayer.image"
|
|
:alt="runnerName"
|
|
class="w-full h-full object-cover"
|
|
>
|
|
<div v-else class="w-full h-full bg-gray-300 flex items-center justify-center text-gray-600 font-bold text-xs">
|
|
{{ base }}
|
|
</div>
|
|
</div>
|
|
<div class="ml-2 flex-1 min-w-0">
|
|
<div class="text-xs font-bold text-gray-900 truncate">{{ runnerName }}</div>
|
|
<div class="text-[10px] text-gray-500">{{ base }}</div>
|
|
</div>
|
|
|
|
<!-- Hold runner icon -->
|
|
<button
|
|
v-if="holdInteractive || isHeld"
|
|
type="button"
|
|
:class="[
|
|
'hold-icon flex-shrink-0 w-10 rounded-lg flex items-center justify-center transition-all duration-200 px-1 self-stretch gap-0.5',
|
|
holdInteractive ? 'cursor-pointer' : 'cursor-default',
|
|
isHeld
|
|
? 'bg-amber-500 text-white shadow-sm ring-1 ring-amber-400'
|
|
: 'bg-gray-200 text-gray-400 hover:bg-gray-300'
|
|
]"
|
|
:title="isHeld ? 'Release runner' : 'Hold runner'"
|
|
:disabled="!holdInteractive"
|
|
@click.stop="handleToggleHold"
|
|
>
|
|
<template v-if="isHeld">
|
|
<span class="text-[9px] font-extrabold leading-[0.85] tracking-tighter" style="writing-mode: vertical-rl; text-orientation: upright;">HELD</span>
|
|
</template>
|
|
<template v-else>
|
|
<span class="text-[9px] font-extrabold leading-[0.85] tracking-tighter" style="writing-mode: vertical-rl; text-orientation: upright;">NOT</span>
|
|
<span class="text-[9px] font-extrabold leading-[0.85] tracking-tighter" style="writing-mode: vertical-rl; text-orientation: upright;">HELD</span>
|
|
</template>
|
|
</button>
|
|
</template>
|
|
<template v-else>
|
|
<!-- Empty base -->
|
|
<div class="w-8 h-8 rounded-full bg-gray-200 border-2 border-dashed border-gray-400 flex items-center justify-center flex-shrink-0">
|
|
<span class="text-gray-400 text-[10px] font-bold">{{ base }}</span>
|
|
</div>
|
|
<div class="ml-2 text-xs text-gray-400">Empty</div>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed } from 'vue'
|
|
import type { LineupPlayerState } from '~/types/game'
|
|
import { useGameStore } from '~/store/game'
|
|
|
|
interface Props {
|
|
base: '1B' | '2B' | '3B'
|
|
runner: LineupPlayerState | null
|
|
isSelected: boolean
|
|
teamColor: string
|
|
isHeld?: boolean
|
|
holdInteractive?: boolean
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
isHeld: false,
|
|
holdInteractive: false,
|
|
})
|
|
|
|
const emit = defineEmits<{
|
|
click: []
|
|
toggleHold: []
|
|
}>()
|
|
|
|
const gameStore = useGameStore()
|
|
|
|
// Resolve player data from lineup
|
|
const runnerPlayer = computed(() => {
|
|
if (!props.runner) return null
|
|
const lineupEntry = gameStore.findPlayerInLineup(props.runner.lineup_id)
|
|
return lineupEntry?.player ?? null
|
|
})
|
|
|
|
const runnerName = computed(() => {
|
|
if (!runnerPlayer.value) return 'Unknown Runner'
|
|
return runnerPlayer.value.name
|
|
})
|
|
|
|
function handleClick() {
|
|
if (props.runner) {
|
|
emit('click')
|
|
}
|
|
}
|
|
|
|
function handleToggleHold() {
|
|
if (props.holdInteractive) {
|
|
emit('toggleHold')
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.runner-pill {
|
|
@apply flex items-center p-2 rounded-lg bg-white border border-gray-200
|
|
shadow-sm transition-all duration-200 cursor-default;
|
|
}
|
|
|
|
.runner-pill.occupied {
|
|
@apply cursor-pointer hover:bg-red-50;
|
|
}
|
|
|
|
.runner-pill.selected {
|
|
@apply ring-2 ring-red-500 bg-red-50 shadow-md;
|
|
}
|
|
|
|
.runner-pill.held {
|
|
@apply border-amber-400;
|
|
}
|
|
|
|
.runner-pill.empty {
|
|
@apply bg-gray-50 opacity-60;
|
|
}
|
|
|
|
.hold-icon:disabled {
|
|
@apply opacity-70;
|
|
}
|
|
</style>
|