---
description: Create a new Vue component following ThrillWiki patterns
---

# New Component Workflow

Create a new Vue component following ThrillWiki's design system and conventions.

## Information Gathering

1. **Component Name**: PascalCase (e.g., `ParkCard`, `RatingDisplay`)
2. **Category**: 
   - `ui/` - Base components (Button, Card, Input)
   - `entity/` - Domain-specific (ParkCard, RideCard)
   - `forms/` - Form components
   - `specialty/` - Complex/unique components
3. **Props**: What data does it receive?
4. **Emits**: What events does it emit?
5. **State**: Does it have internal state?
6. **Variants**: Does it need multiple variants/sizes?

## Component Template

### Base Component Structure

Location: `frontend/components/[category]/ComponentName.vue`

```vue
<script setup lang="ts">
// 1. Import types
import type { ComponentProps } from '~/types'

// 2. Define props with TypeScript
const props = withDefaults(defineProps<{
  // Required props
  title: string
  // Optional props with defaults
  variant?: 'default' | 'compact' | 'expanded'
  disabled?: boolean
}>(), {
  variant: 'default',
  disabled: false,
})

// 3. Define emits
const emit = defineEmits<{
  (e: 'click'): void
  (e: 'select', value: string): void
}>()

// 4. Use composables
const { formatDistance } = useUnits()

// 5. Internal state
const isOpen = ref(false)

// 6. Computed properties
const computedClass = computed(() => ({
  'opacity-50 pointer-events-none': props.disabled,
  'p-4': props.variant === 'default',
  'p-2': props.variant === 'compact',
}))

// 7. Methods
function handleClick() {
  if (!props.disabled) {
    emit('click')
  }
}
</script>

<template>
  <div :class="computedClass" @click="handleClick">
    <!-- Component content -->
    <slot />
  </div>
</template>
```

### Entity Card Component

For ParkCard, RideCard, etc.:

```vue
<script setup lang="ts">
import type { Park } from '~/types'
import { MapPin, Star } from 'lucide-vue-next'

const props = defineProps<{
  park: Park
  variant?: 'default' | 'compact'
}>()

const statusVariant = computed(() => {
  switch (props.park.status) {
    case 'operating': return 'success'
    case 'closed': return 'destructive'
    case 'construction': return 'warning'
    default: return 'default'
  }
})
</script>

<template>
  <NuxtLink :to="`/parks/${park.slug}`">
    <Card interactive class="overflow-hidden">
      <!-- Image -->
      <div class="aspect-video relative">
        <NuxtImg
          :src="park.image || '/placeholder-park.jpg'"
          :alt="park.name"
          class="object-cover w-full h-full"
          loading="lazy"
        />
        <Badge 
          :variant="statusVariant" 
          class="absolute top-2 right-2"
        >
          {{ park.status }}
        </Badge>
      </div>
      
      <!-- Content -->
      <div class="p-4">
        <h3 class="font-semibold text-lg line-clamp-1">
          {{ park.name }}
        </h3>
        
        <p class="text-sm text-muted-foreground flex items-center gap-1 mt-1">
          <MapPin class="w-4 h-4" />
          {{ park.city }}, {{ park.country }}
        </p>
        
        <div class="flex items-center justify-between mt-3">
          <span class="text-sm text-muted-foreground">
            🎢 {{ park.rideCount }} rides
          </span>
          <RatingDisplay 
            v-if="park.averageRating" 
            :rating="park.averageRating" 
            size="sm"
          />
        </div>
      </div>
    </Card>
  </NuxtLink>
</template>
```

### Skeleton Loading Component

Every entity card should have a matching skeleton:

```vue
<script setup lang="ts">
defineProps<{
  variant?: 'default' | 'compact'
}>()
</script>

<template>
  <Card>
    <Skeleton class="aspect-video rounded-t-lg" />
    <div class="p-4 space-y-2">
      <Skeleton class="h-5 w-3/4" />
      <Skeleton class="h-4 w-1/2" />
      <div class="flex justify-between mt-3">
        <Skeleton class="h-4 w-20" />
        <Skeleton class="h-4 w-16" />
      </div>
    </div>
  </Card>
</template>
```

## Variant Pattern (using CVA)

For components with many variants, use class-variance-authority:

```vue
<script setup lang="ts">
import { cva, type VariantProps } from 'class-variance-authority'

const buttonVariants = cva(
  'inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2',
  {
    variants: {
      variant: {
        default: 'bg-primary text-primary-foreground hover:bg-primary/90',
        secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
        outline: 'border border-input bg-background hover:bg-accent',
        ghost: 'hover:bg-accent hover:text-accent-foreground',
        destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
      },
      size: {
        default: 'h-10 px-4 py-2',
        sm: 'h-8 px-3 text-sm',
        lg: 'h-12 px-6 text-lg',
        icon: 'h-10 w-10',
      },
    },
    defaultVariants: {
      variant: 'default',
      size: 'default',
    },
  }
)

type Props = VariantProps<typeof buttonVariants> & {
  loading?: boolean
  disabled?: boolean
}

const props = withDefaults(defineProps<Props>(), {
  loading: false,
  disabled: false,
})
</script>

<template>
  <button
    :class="buttonVariants({ variant, size })"
    :disabled="disabled || loading"
  >
    <Loader2 v-if="loading" class="w-4 h-4 mr-2 animate-spin" />
    <slot />
  </button>
</template>
```

## Composable Integration

If component needs shared logic, create a composable:

```typescript
// composables/useUnitDisplay.ts
export function useUnitDisplay() {
  const { preferredUnits } = useUserPreferences()
  
  function formatSpeed(kmh: number): string {
    if (preferredUnits.value === 'imperial') {
      return `${Math.round(kmh * 0.621371)} mph`
    }
    return `${kmh} km/h`
  }
  
  function formatHeight(meters: number): string {
    if (preferredUnits.value === 'imperial') {
      return `${Math.round(meters * 3.28084)} ft`
    }
    return `${meters} m`
  }
  
  return { formatSpeed, formatHeight }
}
```

## Checklist

After creating the component:

- [ ] TypeScript props are properly defined
- [ ] Component follows design system (colors, spacing, typography)
- [ ] Responsive on all screen sizes
- [ ] Handles loading state (if applicable)
- [ ] Handles empty state (if applicable)
- [ ] Accessible (ARIA labels, keyboard nav)
- [ ] Has matching skeleton component (for async data)
- [ ] Works in dark mode

## Output

Report what was created:
```
Created: frontend/components/[category]/ComponentName.vue
Props: [list of props]
Emits: [list of events]
Related: [any composables or sub-components]
```
