438 lines
16 KiB
Vue
438 lines
16 KiB
Vue
<template>
|
||
<div class="min-h-screen bg-gradient-to-br from-gray-50 to-blue-50 dark:from-gray-900 dark:to-gray-800 py-8">
|
||
<div class="container mx-auto px-4 max-w-6xl">
|
||
<!-- Header -->
|
||
<div class="text-center mb-8">
|
||
<h1 class="text-4xl font-bold text-gray-900 dark:text-white mb-2">
|
||
⚾ Phase F3 Decision Components Demo
|
||
</h1>
|
||
<p class="text-gray-600 dark:text-gray-400">
|
||
Interactive preview of all decision input components
|
||
</p>
|
||
</div>
|
||
|
||
<!-- Demo Controls -->
|
||
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 mb-8">
|
||
<h2 class="text-xl font-bold text-gray-900 dark:text-white mb-4">Demo Controls</h2>
|
||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||
Active State
|
||
</label>
|
||
<ToggleSwitch
|
||
v-model="demoControls.isActive"
|
||
label="Components Active"
|
||
size="md"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||
Runners on Base
|
||
</label>
|
||
<ToggleSwitch
|
||
v-model="demoControls.hasRunners"
|
||
label="Runners Present"
|
||
size="md"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||
Decision Phase
|
||
</label>
|
||
<select
|
||
v-model="demoControls.phase"
|
||
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
||
>
|
||
<option value="idle">Idle</option>
|
||
<option value="defensive">Defensive</option>
|
||
<option value="offensive">Offensive</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tab Navigation -->
|
||
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg mb-8 overflow-hidden">
|
||
<div class="flex border-b border-gray-200 dark:border-gray-700">
|
||
<button
|
||
v-for="tab in tabs"
|
||
:key="tab.id"
|
||
:class="[
|
||
'flex-1 px-6 py-4 text-sm font-medium transition-colors',
|
||
activeTab === tab.id
|
||
? 'bg-primary text-white border-b-2 border-primary'
|
||
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-50 dark:hover:bg-gray-700'
|
||
]"
|
||
@click="activeTab = tab.id"
|
||
>
|
||
{{ tab.icon }} {{ tab.label }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tab Content -->
|
||
<div class="space-y-8">
|
||
<!-- UI Components Tab -->
|
||
<div v-if="activeTab === 'ui'">
|
||
<div class="space-y-8">
|
||
<!-- ActionButton Demo -->
|
||
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
|
||
<h3 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">ActionButton</h3>
|
||
<div class="space-y-6">
|
||
<!-- Variants -->
|
||
<div>
|
||
<h4 class="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-3">Variants</h4>
|
||
<div class="flex flex-wrap gap-3">
|
||
<ActionButton variant="primary" @click="showToast('Primary clicked!')">
|
||
Primary
|
||
</ActionButton>
|
||
<ActionButton variant="secondary" @click="showToast('Secondary clicked!')">
|
||
Secondary
|
||
</ActionButton>
|
||
<ActionButton variant="success" @click="showToast('Success clicked!')">
|
||
Success
|
||
</ActionButton>
|
||
<ActionButton variant="danger" @click="showToast('Danger clicked!')">
|
||
Danger
|
||
</ActionButton>
|
||
<ActionButton variant="warning" @click="showToast('Warning clicked!')">
|
||
Warning
|
||
</ActionButton>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Sizes -->
|
||
<div>
|
||
<h4 class="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-3">Sizes</h4>
|
||
<div class="flex flex-wrap items-center gap-3">
|
||
<ActionButton size="sm">Small</ActionButton>
|
||
<ActionButton size="md">Medium</ActionButton>
|
||
<ActionButton size="lg">Large</ActionButton>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- States -->
|
||
<div>
|
||
<h4 class="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-3">States</h4>
|
||
<div class="flex flex-wrap gap-3">
|
||
<ActionButton :loading="true">Loading</ActionButton>
|
||
<ActionButton :disabled="true">Disabled</ActionButton>
|
||
<ActionButton full-width>Full Width</ActionButton>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ButtonGroup Demo -->
|
||
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
|
||
<h3 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">ButtonGroup</h3>
|
||
<div class="space-y-6">
|
||
<!-- Horizontal -->
|
||
<div>
|
||
<h4 class="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-3">Horizontal</h4>
|
||
<ButtonGroup
|
||
v-model="demoState.selectedOption"
|
||
:options="[
|
||
{ value: 'opt1', label: 'Option 1', icon: '🎯' },
|
||
{ value: 'opt2', label: 'Option 2', icon: '⚡' },
|
||
{ value: 'opt3', label: 'Option 3', icon: '🚀' },
|
||
]"
|
||
/>
|
||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
||
Selected: {{ demoState.selectedOption }}
|
||
</p>
|
||
</div>
|
||
|
||
<!-- Vertical -->
|
||
<div>
|
||
<h4 class="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-3">Vertical</h4>
|
||
<ButtonGroup
|
||
v-model="demoState.selectedVertical"
|
||
:options="[
|
||
{ value: 'a', label: 'Option A' },
|
||
{ value: 'b', label: 'Option B' },
|
||
{ value: 'c', label: 'Option C' },
|
||
]"
|
||
vertical
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ToggleSwitch Demo -->
|
||
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
|
||
<h3 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">ToggleSwitch</h3>
|
||
<div class="space-y-4">
|
||
<ToggleSwitch
|
||
v-model="demoState.toggle1"
|
||
label="Enable feature 1"
|
||
size="sm"
|
||
/>
|
||
<ToggleSwitch
|
||
v-model="demoState.toggle2"
|
||
label="Enable feature 2 (medium)"
|
||
size="md"
|
||
/>
|
||
<ToggleSwitch
|
||
v-model="demoState.toggle3"
|
||
label="Enable feature 3 (large)"
|
||
size="lg"
|
||
/>
|
||
<ToggleSwitch
|
||
v-model="demoState.toggle4"
|
||
label="Disabled toggle"
|
||
:disabled="true"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Defensive Setup Tab -->
|
||
<div v-if="activeTab === 'defensive'">
|
||
<DefensiveSetup
|
||
game-id="demo-game-123"
|
||
:is-active="demoControls.isActive"
|
||
:current-setup="demoState.defensiveSetup"
|
||
@submit="handleDefensiveSubmit"
|
||
/>
|
||
</div>
|
||
|
||
<!-- Stolen Base Tab -->
|
||
<div v-if="activeTab === 'steals'">
|
||
<StolenBaseInputs
|
||
:runners="demoRunners"
|
||
:is-active="demoControls.isActive"
|
||
:current-attempts="demoState.stealAttempts"
|
||
@submit="handleStealSubmit"
|
||
@cancel="handleStealCancel"
|
||
/>
|
||
</div>
|
||
|
||
<!-- Offensive Approach Tab -->
|
||
<div v-if="activeTab === 'offensive'">
|
||
<OffensiveApproach
|
||
game-id="demo-game-123"
|
||
:is-active="demoControls.isActive"
|
||
:current-decision="demoState.offensiveDecision"
|
||
:has-runners-on-base="demoControls.hasRunners"
|
||
:runner-on-first="!!demoRunners.first"
|
||
:runner-on-second="!!demoRunners.second"
|
||
:runner-on-third="!!demoRunners.third"
|
||
:outs="0"
|
||
@submit="handleOffensiveSubmit"
|
||
/>
|
||
</div>
|
||
|
||
<!-- Decision Panel Tab -->
|
||
<div v-if="activeTab === 'panel'">
|
||
<DecisionPanel
|
||
game-id="demo-game-123"
|
||
current-team="home"
|
||
:is-my-turn="demoControls.isActive"
|
||
:phase="demoControls.phase"
|
||
:runners="demoRunners"
|
||
:current-defensive-setup="demoState.defensiveSetup"
|
||
:current-offensive-decision="demoState.offensiveDecision"
|
||
:current-steal-attempts="demoState.stealAttempts"
|
||
:decision-history="demoState.decisionHistory"
|
||
@defensive-submit="handleDefensiveSubmit"
|
||
@offensive-submit="handleOffensiveSubmit"
|
||
@steal-attempts-submit="handleStealSubmit"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Toast Notification -->
|
||
<Transition
|
||
enter-active-class="transition ease-out duration-300"
|
||
enter-from-class="opacity-0 translate-y-4"
|
||
enter-to-class="opacity-100 translate-y-0"
|
||
leave-active-class="transition ease-in duration-200"
|
||
leave-from-class="opacity-100 translate-y-0"
|
||
leave-to-class="opacity-0 translate-y-4"
|
||
>
|
||
<div
|
||
v-if="toastMessage"
|
||
class="fixed bottom-8 right-8 bg-green-600 text-white px-6 py-4 rounded-lg shadow-2xl z-50 max-w-md"
|
||
>
|
||
<div class="flex items-center gap-3">
|
||
<span class="text-2xl">✅</span>
|
||
<div>
|
||
<p class="font-semibold">{{ toastMessage }}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</Transition>
|
||
|
||
<!-- Footer with Demo Links -->
|
||
<div class="mt-12 border-t border-gray-200 dark:border-gray-700 pt-8">
|
||
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
|
||
<h3 class="text-lg font-bold text-gray-900 dark:text-white mb-4">
|
||
📚 Other Demo Pages
|
||
</h3>
|
||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||
<NuxtLink
|
||
to="/demo"
|
||
class="p-4 rounded-lg border-2 border-gray-300 dark:border-gray-600 hover:border-blue-500 dark:hover:border-blue-400 transition-colors"
|
||
>
|
||
<div class="text-2xl mb-2">🎮</div>
|
||
<div class="font-semibold text-gray-900 dark:text-white">Game State Demo</div>
|
||
<div class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||
Live game state visualization
|
||
</div>
|
||
</NuxtLink>
|
||
<NuxtLink
|
||
to="/demo-gameplay"
|
||
class="p-4 rounded-lg border-2 border-gray-300 dark:border-gray-600 hover:border-blue-500 dark:hover:border-blue-400 transition-colors"
|
||
>
|
||
<div class="text-2xl mb-2">🎲</div>
|
||
<div class="font-semibold text-gray-900 dark:text-white">Gameplay Demo</div>
|
||
<div class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||
Dice rolling & manual outcomes
|
||
</div>
|
||
</NuxtLink>
|
||
<NuxtLink
|
||
to="/demo-substitutions"
|
||
class="p-4 rounded-lg border-2 border-gray-300 dark:border-gray-600 hover:border-blue-500 dark:hover:border-blue-400 transition-colors"
|
||
>
|
||
<div class="text-2xl mb-2">🔄</div>
|
||
<div class="font-semibold text-gray-900 dark:text-white">Substitutions Demo</div>
|
||
<div class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||
Player substitution workflow
|
||
</div>
|
||
</NuxtLink>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, computed } from 'vue'
|
||
import type { DefensiveDecision, OffensiveDecision } from '~/types/game'
|
||
import ActionButton from '~/components/UI/ActionButton.vue'
|
||
import ButtonGroup from '~/components/UI/ButtonGroup.vue'
|
||
import ToggleSwitch from '~/components/UI/ToggleSwitch.vue'
|
||
import DefensiveSetup from '~/components/Decisions/DefensiveSetup.vue'
|
||
import StolenBaseInputs from '~/components/Decisions/StolenBaseInputs.vue'
|
||
import OffensiveApproach from '~/components/Decisions/OffensiveApproach.vue'
|
||
import DecisionPanel from '~/components/Decisions/DecisionPanel.vue'
|
||
|
||
// Tab navigation
|
||
const activeTab = ref('ui')
|
||
const tabs = [
|
||
{ id: 'ui', label: 'UI Components', icon: '🎨' },
|
||
{ id: 'defensive', label: 'Defensive Setup', icon: '🛡️' },
|
||
{ id: 'steals', label: 'Stolen Bases', icon: '🏃' },
|
||
{ id: 'offensive', label: 'Offensive Approach', icon: '⚔️' },
|
||
{ id: 'panel', label: 'Decision Panel', icon: '🎯' },
|
||
]
|
||
|
||
// Demo controls
|
||
const demoControls = ref({
|
||
isActive: true,
|
||
hasRunners: true,
|
||
phase: 'offensive' as 'idle' | 'defensive' | 'offensive',
|
||
})
|
||
|
||
// Demo state
|
||
const demoState = ref({
|
||
selectedOption: 'opt1',
|
||
selectedVertical: 'a',
|
||
toggle1: false,
|
||
toggle2: true,
|
||
toggle3: false,
|
||
toggle4: true,
|
||
defensiveSetup: {
|
||
infield_depth: 'normal',
|
||
outfield_depth: 'normal',
|
||
hold_runners: [],
|
||
} as DefensiveDecision,
|
||
offensiveDecision: {
|
||
action: 'swing_away',
|
||
} as Omit<OffensiveDecision, 'steal_attempts'>,
|
||
stealAttempts: [] as number[],
|
||
decisionHistory: [
|
||
{
|
||
type: 'Defensive' as const,
|
||
summary: 'normal infield, normal outfield',
|
||
timestamp: '10:45:23',
|
||
},
|
||
{
|
||
type: 'Offensive' as const,
|
||
summary: 'Swing Away',
|
||
timestamp: '10:45:18',
|
||
},
|
||
],
|
||
})
|
||
|
||
// Demo runners
|
||
const demoRunners = computed(() => {
|
||
if (!demoControls.value.hasRunners) {
|
||
return { first: null, second: null, third: null }
|
||
}
|
||
return {
|
||
first: 101,
|
||
second: 102,
|
||
third: 103,
|
||
}
|
||
})
|
||
|
||
// Toast notification
|
||
const toastMessage = ref('')
|
||
const showToast = (message: string) => {
|
||
toastMessage.value = message
|
||
setTimeout(() => {
|
||
toastMessage.value = ''
|
||
}, 3000)
|
||
}
|
||
|
||
// Event handlers
|
||
const handleDefensiveSubmit = (decision: DefensiveDecision) => {
|
||
demoState.value.defensiveSetup = decision
|
||
const summary = `${decision.infield_depth} infield, ${decision.outfield_depth} outfield`
|
||
demoState.value.decisionHistory.unshift({
|
||
type: 'Defensive',
|
||
summary,
|
||
timestamp: new Date().toLocaleTimeString(),
|
||
})
|
||
showToast(`Defensive setup submitted: ${summary}`)
|
||
}
|
||
|
||
const handleOffensiveSubmit = (decision: Omit<OffensiveDecision, 'steal_attempts'>) => {
|
||
demoState.value.offensiveDecision = decision
|
||
const actionLabels: Record<string, string> = {
|
||
swing_away: 'Swing Away',
|
||
steal: 'Steal',
|
||
check_jump: 'Check Jump',
|
||
hit_and_run: 'Hit and Run',
|
||
sac_bunt: 'Sacrifice Bunt',
|
||
squeeze_bunt: 'Squeeze Bunt',
|
||
}
|
||
const summary = actionLabels[decision.action] || decision.action
|
||
demoState.value.decisionHistory.unshift({
|
||
type: 'Offensive',
|
||
summary,
|
||
timestamp: new Date().toLocaleTimeString(),
|
||
})
|
||
showToast(`Offensive action selected: ${summary}`)
|
||
}
|
||
|
||
const handleStealSubmit = (attempts: number[]) => {
|
||
demoState.value.stealAttempts = attempts
|
||
const bases = attempts.map(a => {
|
||
if (a === 2) return '2nd'
|
||
if (a === 3) return '3rd'
|
||
if (a === 4) return 'Home'
|
||
return a
|
||
}).join(', ')
|
||
showToast(`Steal attempts: ${bases || 'None'}`)
|
||
}
|
||
|
||
const handleStealCancel = () => {
|
||
showToast('Steal attempts canceled')
|
||
}
|
||
</script>
|