|
| 1 | +<script setup lang="ts"> |
| 2 | + import { Separator } from '~/components/ui/separator'; |
| 3 | +
|
| 4 | + const route = useRoute(); |
| 5 | + const { categories } = useExamplesData(); |
| 6 | +
|
| 7 | + // Flatten all examples in order |
| 8 | + const allExamples = computed(() => categories.flatMap((cat) => cat.examples)); |
| 9 | +
|
| 10 | + const currentIndex = computed(() => |
| 11 | + allExamples.value.findIndex((ex) => ex.href === route.path), |
| 12 | + ); |
| 13 | +
|
| 14 | + const prevExample = computed(() => |
| 15 | + currentIndex.value > 0 ? allExamples.value[currentIndex.value - 1] : null, |
| 16 | + ); |
| 17 | +
|
| 18 | + const nextExample = computed(() => |
| 19 | + currentIndex.value < allExamples.value.length - 1 |
| 20 | + ? allExamples.value[currentIndex.value + 1] |
| 21 | + : null, |
| 22 | + ); |
| 23 | +
|
| 24 | + const editUrl = computed(() => { |
| 25 | + const filename = route.path.split('/').pop(); |
| 26 | + return `https://github.com/geoql/v-maplibre/edit/main/apps/mapcn-vue/app/pages/examples/${filename}.vue`; |
| 27 | + }); |
| 28 | +
|
| 29 | + const issueUrl = |
| 30 | + 'https://github.com/geoql/v-maplibre/issues/new?labels=examples'; |
| 31 | +</script> |
| 32 | + |
| 33 | +<template> |
| 34 | + <div class="mt-10"> |
| 35 | + <!-- Edit & Report Links --> |
| 36 | + <div class="flex items-center justify-center gap-2 text-sm"> |
| 37 | + <Separator class="flex-1 bg-border/50" /> |
| 38 | + <a |
| 39 | + :href="editUrl" |
| 40 | + target="_blank" |
| 41 | + rel="noopener noreferrer" |
| 42 | + class="inline-flex items-center gap-1.5 rounded-full border border-border/50 bg-card/50 px-3 py-1.5 text-muted-foreground backdrop-blur-sm transition-all hover:border-primary/30 hover:bg-primary/5 hover:text-primary" |
| 43 | + > |
| 44 | + <Icon name="lucide:pencil" class="size-3.5" /> |
| 45 | + Edit |
| 46 | + </a> |
| 47 | + <a |
| 48 | + :href="issueUrl" |
| 49 | + target="_blank" |
| 50 | + rel="noopener noreferrer" |
| 51 | + class="inline-flex items-center gap-1.5 rounded-full border border-border/50 bg-card/50 px-3 py-1.5 text-muted-foreground backdrop-blur-sm transition-all hover:border-primary/30 hover:bg-primary/5 hover:text-primary" |
| 52 | + > |
| 53 | + <Icon name="lucide:circle-dot" class="size-3.5" /> |
| 54 | + Report |
| 55 | + </a> |
| 56 | + <Separator class="flex-1 bg-border/50" /> |
| 57 | + </div> |
| 58 | + |
| 59 | + <!-- Prev/Next Navigation --> |
| 60 | + <div class="mt-8 grid grid-cols-2 gap-4"> |
| 61 | + <NuxtLink |
| 62 | + v-if="prevExample" |
| 63 | + :to="prevExample.href" |
| 64 | + class="group relative flex flex-col overflow-hidden rounded-xl border border-border/50 bg-card/50 p-5 backdrop-blur-md transition-all duration-300 hover:border-primary/30 hover:bg-card/80 hover:shadow-lg hover:shadow-primary/5" |
| 65 | + > |
| 66 | + <div |
| 67 | + class="pointer-events-none absolute inset-0 opacity-0 transition-opacity duration-300 group-hover:opacity-100" |
| 68 | + > |
| 69 | + <div |
| 70 | + class="absolute -inset-px rounded-xl bg-linear-to-b from-primary/10 via-transparent to-transparent" |
| 71 | + ></div> |
| 72 | + </div> |
| 73 | + <div class="relative mb-3 flex items-center gap-2"> |
| 74 | + <Icon |
| 75 | + name="lucide:arrow-left" |
| 76 | + class="size-4 text-muted-foreground transition-all duration-300 group-hover:-translate-x-1 group-hover:text-primary" |
| 77 | + /> |
| 78 | + <span class="text-xs text-muted-foreground">Previous</span> |
| 79 | + </div> |
| 80 | + <div class="relative flex items-center gap-3"> |
| 81 | + <div |
| 82 | + class="flex size-9 shrink-0 items-center justify-center rounded-lg border border-border/50 bg-muted/50 transition-all duration-300 group-hover:border-primary/30 group-hover:bg-primary/10" |
| 83 | + > |
| 84 | + <Icon |
| 85 | + :name="prevExample.icon" |
| 86 | + class="size-4 text-muted-foreground transition-colors duration-300 group-hover:text-primary" |
| 87 | + /> |
| 88 | + </div> |
| 89 | + <span |
| 90 | + class="font-medium transition-colors duration-300 group-hover:text-primary" |
| 91 | + >{{ prevExample.title }}</span |
| 92 | + > |
| 93 | + </div> |
| 94 | + </NuxtLink> |
| 95 | + <div v-else></div> |
| 96 | + |
| 97 | + <NuxtLink |
| 98 | + v-if="nextExample" |
| 99 | + :to="nextExample.href" |
| 100 | + class="group relative flex flex-col items-end overflow-hidden rounded-xl border border-border/50 bg-card/50 p-5 text-right backdrop-blur-md transition-all duration-300 hover:border-primary/30 hover:bg-card/80 hover:shadow-lg hover:shadow-primary/5" |
| 101 | + > |
| 102 | + <div |
| 103 | + class="pointer-events-none absolute inset-0 opacity-0 transition-opacity duration-300 group-hover:opacity-100" |
| 104 | + > |
| 105 | + <div |
| 106 | + class="absolute -inset-px rounded-xl bg-linear-to-b from-primary/10 via-transparent to-transparent" |
| 107 | + ></div> |
| 108 | + </div> |
| 109 | + <div class="relative mb-3 flex items-center gap-2"> |
| 110 | + <span class="text-xs text-muted-foreground">Next</span> |
| 111 | + <Icon |
| 112 | + name="lucide:arrow-right" |
| 113 | + class="size-4 text-muted-foreground transition-all duration-300 group-hover:translate-x-1 group-hover:text-primary" |
| 114 | + /> |
| 115 | + </div> |
| 116 | + <div class="relative flex items-center gap-3"> |
| 117 | + <span |
| 118 | + class="font-medium transition-colors duration-300 group-hover:text-primary" |
| 119 | + >{{ nextExample.title }}</span |
| 120 | + > |
| 121 | + <div |
| 122 | + class="flex size-9 shrink-0 items-center justify-center rounded-lg border border-border/50 bg-muted/50 transition-all duration-300 group-hover:border-primary/30 group-hover:bg-primary/10" |
| 123 | + > |
| 124 | + <Icon |
| 125 | + :name="nextExample.icon" |
| 126 | + class="size-4 text-muted-foreground transition-colors duration-300 group-hover:text-primary" |
| 127 | + /> |
| 128 | + </div> |
| 129 | + </div> |
| 130 | + </NuxtLink> |
| 131 | + </div> |
| 132 | + </div> |
| 133 | +</template> |
0 commit comments