Initial commit from monorepo
This commit is contained in:
114
app/components/catalog/MapHero.vue
Normal file
114
app/components/catalog/MapHero.vue
Normal file
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<div class="relative h-80 sm:h-96 overflow-hidden rounded-2xl border border-base-300 bg-base-200">
|
||||
<!-- Map -->
|
||||
<ClientOnly>
|
||||
<MapboxGlobe
|
||||
v-if="hasCoordinates"
|
||||
:key="mapKey"
|
||||
:map-id="`hero-${mapKey}`"
|
||||
:locations="[mapLocation]"
|
||||
height="100%"
|
||||
:initial-center="mapCenter"
|
||||
:initial-zoom="initialZoom"
|
||||
/>
|
||||
<div v-else class="w-full h-full bg-gradient-to-br from-primary/20 via-primary/10 to-base-200" />
|
||||
</ClientOnly>
|
||||
|
||||
<!-- Overlay -->
|
||||
<div class="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/80 to-transparent p-6 sm:p-8">
|
||||
<Stack gap="3">
|
||||
<!-- Title -->
|
||||
<h1 class="text-2xl sm:text-3xl font-bold text-white">{{ title }}</h1>
|
||||
<p v-if="location?.country" class="text-white/80 flex items-center gap-2">
|
||||
<span class="text-xl leading-none">{{ countryFlag }}</span>
|
||||
<span>{{ location.country }}</span>
|
||||
</p>
|
||||
|
||||
<!-- Badges -->
|
||||
<div v-if="badges.length > 0" class="flex flex-wrap items-center gap-2">
|
||||
<span
|
||||
v-for="(badge, index) in badges"
|
||||
:key="index"
|
||||
class="badge badge-dash"
|
||||
:class="badgeTone(badge, index)"
|
||||
>
|
||||
<Icon v-if="badge.icon" :name="badge.icon" size="14" />
|
||||
{{ badge.text }}
|
||||
</span>
|
||||
</div>
|
||||
</Stack>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Badge {
|
||||
icon?: string
|
||||
text: string
|
||||
tone?: string
|
||||
}
|
||||
|
||||
interface Location {
|
||||
uuid?: string | null
|
||||
name?: string | null
|
||||
latitude?: number | null
|
||||
longitude?: number | null
|
||||
country?: string | null
|
||||
countryCode?: string | null
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
title: string
|
||||
badges?: Badge[]
|
||||
location?: Location | null
|
||||
initialZoom?: number
|
||||
}>(), {
|
||||
badges: () => [],
|
||||
location: null,
|
||||
initialZoom: 6
|
||||
})
|
||||
|
||||
const hasCoordinates = computed(() =>
|
||||
props.location?.latitude != null && props.location?.longitude != null
|
||||
)
|
||||
|
||||
// Key to force map recreation when location changes
|
||||
const mapKey = computed(() =>
|
||||
`${props.location?.uuid}-${props.location?.latitude}-${props.location?.longitude}`
|
||||
)
|
||||
|
||||
const mapCenter = computed<[number, number]>(() => {
|
||||
if (hasCoordinates.value) {
|
||||
return [props.location!.longitude!, props.location!.latitude!]
|
||||
}
|
||||
return [37.64, 55.76]
|
||||
})
|
||||
|
||||
const mapLocation = computed(() => ({
|
||||
uuid: props.location?.uuid,
|
||||
name: props.location?.name,
|
||||
latitude: props.location?.latitude,
|
||||
longitude: props.location?.longitude,
|
||||
country: props.location?.country
|
||||
}))
|
||||
|
||||
const badgeTone = (badge: Badge, index: number) => {
|
||||
if (badge.tone) return `badge-${badge.tone}`
|
||||
const palette = ['badge-primary', 'badge-secondary', 'badge-accent', 'badge-info', 'badge-success']
|
||||
return palette[index % palette.length]
|
||||
}
|
||||
|
||||
const isoToEmoji = (code: string): string => {
|
||||
return code.toUpperCase().split('').map(char => String.fromCodePoint(0x1F1E6 - 65 + char.charCodeAt(0))).join('')
|
||||
}
|
||||
|
||||
const countryFlag = computed(() => {
|
||||
if (props.location?.countryCode) {
|
||||
return isoToEmoji(props.location.countryCode)
|
||||
}
|
||||
if (props.location?.country) {
|
||||
return '🌍'
|
||||
}
|
||||
return ''
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user