Move SubNavigation to its own component
This commit is contained in:
parent
72c9fe8c9e
commit
44e618bf9d
|
|
@ -28,7 +28,14 @@ const isExternalLink = computed(() => {
|
||||||
<slot />
|
<slot />
|
||||||
<it-icon-external-link />
|
<it-icon-external-link />
|
||||||
</a>
|
</a>
|
||||||
<router-link v-else v-slot="{ isActive, href, navigate }" v-bind="$props" custom>
|
<!-- make `:to` explicit -->
|
||||||
|
<router-link
|
||||||
|
v-else
|
||||||
|
v-slot="{ isActive, href, navigate }"
|
||||||
|
v-bind="$props"
|
||||||
|
:to="$props.to"
|
||||||
|
custom
|
||||||
|
>
|
||||||
<a
|
<a
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
:class="isActive ? activeClass : ''"
|
:class="isActive ? activeClass : ''"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import SubNavItem from "@/components/header/SubNavItem.vue";
|
||||||
|
import { Listbox, ListboxOption, ListboxOptions } from "@headlessui/vue";
|
||||||
|
import { computed, ref } from "vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
export interface EntryRoute {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EntryOrExternalRoute = EntryRoute | string;
|
||||||
|
|
||||||
|
export interface SubNavEntry {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
route: EntryOrExternalRoute;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
items: SubNavEntry[];
|
||||||
|
}
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
|
const isCurrentRoute = (route: { name: string } | string) => {
|
||||||
|
return typeof route !== "string" && route?.name === router.currentRoute.value.name;
|
||||||
|
};
|
||||||
|
const currentRouteName = computed(() => {
|
||||||
|
return props.items.find((item) => isCurrentRoute(item.route))?.name || "";
|
||||||
|
});
|
||||||
|
const open = ref<boolean>(false);
|
||||||
|
const currentRoute = ref(props.items.find((item) => isCurrentRoute(item.route)));
|
||||||
|
const selectRoute = (current: SubNavEntry) => {
|
||||||
|
// we use this to mimic VueRouter's active flag
|
||||||
|
open.value = false;
|
||||||
|
currentRoute.value = current;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<nav class="border-b bg-white px-4 lg:px-8">
|
||||||
|
<Listbox as="div" :model-value="currentRoute" by="id">
|
||||||
|
<div class="relative w-full py-2 lg:hidden">
|
||||||
|
<button
|
||||||
|
class="relative flex w-full cursor-default flex-row items-center border bg-white py-3 pl-5 pr-10 text-left"
|
||||||
|
@click="open = !open"
|
||||||
|
>
|
||||||
|
{{ currentRouteName }}
|
||||||
|
<span
|
||||||
|
class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"
|
||||||
|
>
|
||||||
|
<it-icon-arrow-down class="h-5 w-5" aria-hidden="true" />
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<ListboxOptions
|
||||||
|
v-if="open"
|
||||||
|
class="absolute top-14 z-50 flex w-full cursor-default flex-col rounded-xl border-0 bg-white text-left shadow-lg"
|
||||||
|
static
|
||||||
|
>
|
||||||
|
<ListboxOption
|
||||||
|
v-for="item in items"
|
||||||
|
:key="item.id"
|
||||||
|
v-slot="{ selected }"
|
||||||
|
:value="item"
|
||||||
|
class="relative w-full border-b py-3 pl-10 pr-10 last:border-b-0"
|
||||||
|
>
|
||||||
|
<SubNavItem
|
||||||
|
:to="item.route"
|
||||||
|
class="flex items-center gap-2"
|
||||||
|
@click="selectRoute(item)"
|
||||||
|
>
|
||||||
|
<it-icon-check
|
||||||
|
v-if="selected"
|
||||||
|
class="absolute left-2 top-1/2 w-8 -translate-y-1/2"
|
||||||
|
/>
|
||||||
|
{{ item.name }}
|
||||||
|
</SubNavItem>
|
||||||
|
</ListboxOption>
|
||||||
|
</ListboxOptions>
|
||||||
|
</div>
|
||||||
|
</Listbox>
|
||||||
|
<ul class="hidden flex-col gap-12 lg:flex lg:flex-row">
|
||||||
|
<li
|
||||||
|
v-for="item in items"
|
||||||
|
:key="item.id"
|
||||||
|
class="border-t-2 border-t-transparent"
|
||||||
|
:class="{ 'border-b-2 border-b-blue-900': isCurrentRoute(item.route) }"
|
||||||
|
>
|
||||||
|
<!-- todo: split by external / internal and align external links to the right -->
|
||||||
|
<SubNavItem :to="item.route" class="block py-3">
|
||||||
|
{{ item.name }}
|
||||||
|
</SubNavItem>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</template>
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import SubNavItem from "@/components/header/SubNavItem.vue";
|
import SubNavigation, { type SubNavEntry } from "@/components/header/SubNavigation.vue";
|
||||||
import { useCurrentCourseSession, useEvaluationWithFeedback } from "@/composables";
|
import { useCurrentCourseSession, useEvaluationWithFeedback } from "@/composables";
|
||||||
import {
|
import {
|
||||||
CERTIFICATES_ROUTE,
|
CERTIFICATES_ROUTE,
|
||||||
|
|
@ -7,16 +7,12 @@ import {
|
||||||
COMPETENCES_ROUTE,
|
COMPETENCES_ROUTE,
|
||||||
SELF_EVALUATION_ROUTE,
|
SELF_EVALUATION_ROUTE,
|
||||||
} from "@/router/names";
|
} from "@/router/names";
|
||||||
import { Listbox, ListboxOption, ListboxOptions } from "@headlessui/vue";
|
|
||||||
import { useTranslation } from "i18next-vue";
|
import { useTranslation } from "i18next-vue";
|
||||||
import * as log from "loglevel";
|
import * as log from "loglevel";
|
||||||
import { computed, onMounted, ref } from "vue";
|
import { onMounted } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
|
||||||
|
|
||||||
log.debug("CompetenceParentPage created");
|
log.debug("CompetenceParentPage created");
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const currentCourseSession = useCurrentCourseSession();
|
const currentCourseSession = useCurrentCourseSession();
|
||||||
|
|
@ -39,11 +35,7 @@ const competencesRoute = {
|
||||||
name: COMPETENCES_ROUTE,
|
name: COMPETENCES_ROUTE,
|
||||||
};
|
};
|
||||||
|
|
||||||
const isCurrentRoute = (route: { name: string } | string) => {
|
const items: SubNavEntry[] = [
|
||||||
return typeof route !== "string" && route?.name === router.currentRoute.value.name;
|
|
||||||
};
|
|
||||||
|
|
||||||
const items = [
|
|
||||||
{ id: 0, name: t("a.Übersicht"), route: competenceRoute },
|
{ id: 0, name: t("a.Übersicht"), route: competenceRoute },
|
||||||
...(currentCourseSession.value.course.configuration.enable_competence_certificates
|
...(currentCourseSession.value.course.configuration.enable_competence_certificates
|
||||||
? [
|
? [
|
||||||
|
|
@ -66,84 +58,19 @@ const items = [
|
||||||
{
|
{
|
||||||
id: 4,
|
id: 4,
|
||||||
name: "MS Teams",
|
name: "MS Teams",
|
||||||
iconName: "it-icon-external-link",
|
|
||||||
route: "https://iterativ.ch",
|
route: "https://iterativ.ch",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 5,
|
id: 5,
|
||||||
name: "Vorschau Teilnehmer",
|
name: "Vorschau Teilnehmer",
|
||||||
iconName: "it-icon-external-link",
|
|
||||||
route: "https://iterativ.ch",
|
route: "https://iterativ.ch",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const currentRouteName = computed(() => {
|
|
||||||
return items.find((item) => isCurrentRoute(item.route))?.name || "";
|
|
||||||
});
|
|
||||||
const open = ref<boolean>(false);
|
|
||||||
const currentRoute = ref(items.find((item) => isCurrentRoute(item.route)));
|
|
||||||
const selectRoute = (current) => {
|
|
||||||
// we use this to mimic VueRouter's active flag
|
|
||||||
open.value = false;
|
|
||||||
currentRoute.value = current;
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bg-gray-200">
|
<div class="bg-gray-200">
|
||||||
<nav class="border-b bg-white px-4 lg:px-8">
|
<SubNavigation :items="items" />
|
||||||
<Listbox as="div" :model-value="currentRoute" by="id">
|
|
||||||
<div class="relative w-full py-2 lg:hidden">
|
|
||||||
<button
|
|
||||||
class="relative flex w-full cursor-default flex-row items-center border bg-white py-3 pl-5 pr-10 text-left"
|
|
||||||
@click="open = !open"
|
|
||||||
>
|
|
||||||
{{ currentRouteName }}
|
|
||||||
<span
|
|
||||||
class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"
|
|
||||||
>
|
|
||||||
<it-icon-arrow-down class="h-5 w-5" aria-hidden="true" />
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<ListboxOptions
|
|
||||||
v-if="open"
|
|
||||||
class="absolute top-14 z-50 flex w-full cursor-default flex-col rounded-xl border-0 bg-white text-left shadow-lg"
|
|
||||||
static
|
|
||||||
>
|
|
||||||
<ListboxOption
|
|
||||||
v-for="item in items"
|
|
||||||
:key="item.id"
|
|
||||||
v-slot="{ selected }"
|
|
||||||
:value="item"
|
|
||||||
class="relative w-full border-b py-3 pl-10 pr-10 last:border-b-0"
|
|
||||||
>
|
|
||||||
<SubNavItem
|
|
||||||
:to="item.route"
|
|
||||||
class="flex items-center gap-2"
|
|
||||||
@click="selectRoute(item)"
|
|
||||||
>
|
|
||||||
<it-icon-check
|
|
||||||
v-if="selected"
|
|
||||||
class="absolute left-2 top-1/2 w-8 -translate-y-1/2"
|
|
||||||
/>
|
|
||||||
{{ item.name }}
|
|
||||||
</SubNavItem>
|
|
||||||
</ListboxOption>
|
|
||||||
</ListboxOptions>
|
|
||||||
</div>
|
|
||||||
</Listbox>
|
|
||||||
<ul class="hidden flex-col gap-12 lg:flex lg:flex-row">
|
|
||||||
<li
|
|
||||||
v-for="item in items"
|
|
||||||
:key="item.id"
|
|
||||||
class="border-t-2 border-t-transparent"
|
|
||||||
:class="{ 'border-b-2 border-b-blue-900': isCurrentRoute(item.route) }"
|
|
||||||
>
|
|
||||||
<SubNavItem :to="item.route" class="block py-3">
|
|
||||||
{{ item.name }}
|
|
||||||
</SubNavItem>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
<main>
|
<main>
|
||||||
<router-view></router-view>
|
<router-view></router-view>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { computed, ref } from "vue";
|
import { computed } from "vue";
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
|
|
||||||
export function useRouteLookups() {
|
export function useRouteLookups() {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue