refactor(frontend): extract SideNavShell for admin and user settings

This commit is contained in:
kolaente
2026-04-20 18:59:20 +02:00
committed by kolaente
parent 4e805d182a
commit 7e4bf83fa0
2 changed files with 139 additions and 95 deletions

View File

@@ -0,0 +1,126 @@
<template>
<div class="content-widescreen">
<div class="side-nav-shell">
<nav class="navigation">
<ul>
<li
v-for="(item, index) in navigationItems"
:key="`nav-${index}`"
>
<RouterLink
v-slot="{href, navigate, isActive, isExactActive}"
:to="{name: item.routeName}"
custom
>
<a
:href="href"
class="navigation-link"
:class="{'is-active': (exact ? isExactActive : isActive) || isAliasActive(item)}"
@click="navigate"
>
{{ item.title }}
</a>
</RouterLink>
</li>
<li
v-for="({url, text}, index) in extraLinks"
:key="`extra-${index}`"
>
<BaseButton
class="navigation-link is-flex is-align-items-center"
:href="url"
>
<span>
{{ text }}
</span>
<span class="ml-1 has-text-grey-light is-size-7">
<Icon
icon="arrow-up-right-from-square"
/>
</span>
</BaseButton>
</li>
</ul>
</nav>
<section class="view">
<RouterView />
</section>
</div>
</div>
</template>
<script setup lang="ts">
import {useRoute} from 'vue-router'
import BaseButton from '@/components/base/BaseButton.vue'
export interface SideNavItem {
title: string
routeName: string
activeRouteNames?: string[]
}
export interface SideNavExtraLink {
url: string
text: string
}
withDefaults(defineProps<{
navigationItems: SideNavItem[]
extraLinks?: SideNavExtraLink[]
exact?: boolean
}>(), {
extraLinks: () => [],
exact: false,
})
const route = useRoute()
function isAliasActive(item: SideNavItem) {
return item.activeRouteNames?.includes(route.name as string) ?? false
}
</script>
<style lang="scss" scoped>
.side-nav-shell {
display: flex;
@media screen and (max-width: $tablet) {
flex-direction: column;
}
}
.navigation {
inline-size: 25%;
padding-inline-end: 1rem;
@media screen and (max-width: $tablet) {
inline-size: 100%;
padding-inline-start: 0;
}
}
.navigation-link {
display: block;
padding: .5rem;
color: var(--text);
inline-size: 100%;
border-inline-start: 3px solid transparent;
&:hover,
&.is-active {
background: var(--white);
border-color: var(--primary);
}
}
.view {
inline-size: 75%;
@media screen and (max-width: $tablet) {
inline-size: 100%;
padding-inline-start: 0;
padding-block-start: 1rem;
}
}
</style>

View File

@@ -1,63 +1,24 @@
<template>
<div class="content-widescreen">
<div class="user-settings">
<nav class="navigation">
<ul>
<li
v-for="({routeName, title }, index) in navigationItems"
:key="index"
>
<RouterLink
class="navigation-link"
:class="{'router-link-active': routeName === 'migrate.start' && route.name === 'migrate.service'}"
:to="{name: routeName}"
>
{{ title }}
</RouterLink>
</li>
<li
v-for="({url, text}, index) in extraSettingsLinks"
:key="index"
>
<BaseButton
class="navigation-link is-flex is-align-items-center"
:href="url"
>
<span>
{{ text }}
</span>
<span class="ml-1 has-text-grey-light is-size-7">
<Icon
icon="arrow-up-right-from-square"
/>
</span>
</BaseButton>
</li>
</ul>
</nav>
<section class="view">
<RouterView />
</section>
</div>
</div>
<SideNavShell
:navigation-items="navigationItems"
:extra-links="extraSettingsLinks"
/>
</template>
<script setup lang="ts">
import {computed} from 'vue'
import { useI18n } from 'vue-i18n'
import { useTitle } from '@/composables/useTitle'
import { useConfigStore } from '@/stores/config'
import { useAuthStore } from '@/stores/auth'
import {useRoute} from 'vue-router'
import {useI18n} from 'vue-i18n'
import {useTitle} from '@/composables/useTitle'
import {useConfigStore} from '@/stores/config'
import {useAuthStore} from '@/stores/auth'
import BaseButton from '@/components/base/BaseButton.vue'
import SideNavShell from '@/components/misc/SideNavShell.vue'
const { t } = useI18n({useScope: 'global'})
const {t} = useI18n({useScope: 'global'})
useTitle(() => t('user.settings.title'))
const configStore = useConfigStore()
const authStore = useAuthStore()
const route = useRoute()
const totpEnabled = computed(() => configStore.totpEnabled)
const caldavEnabled = computed(() => configStore.caldavEnabled)
@@ -98,6 +59,7 @@ const navigationItems = computed(() => {
{
title: t('migrate.title'),
routeName: 'migrate.start',
activeRouteNames: ['migrate.service'],
condition: migratorsEnabled.value,
},
{
@@ -124,53 +86,9 @@ const navigationItems = computed(() => {
condition: userDeletionEnabled.value,
},
]
return items.filter(({condition}) => condition !== false)
})
const extraSettingsLinks = computed(() => authStore.settings.extraSettingsLinks)
const extraSettingsLinks = computed(() => Object.values(authStore.settings.extraSettingsLinks ?? {}))
</script>
<style lang="scss" scoped>
.user-settings {
display: flex;
@media screen and (max-width: $tablet) {
flex-direction: column;
}
}
.navigation {
inline-size: 25%;
padding-inline-end: 1rem;
@media screen and (max-width: $tablet) {
inline-size: 100%;
padding-inline-start: 0;
}
}
.navigation-link {
display: block;
padding: .5rem;
color: var(--text);
inline-size: 100%;
border-inline-start: 3px solid transparent;
&:hover,
&.router-link-active {
background: var(--white);
border-color: var(--primary);
}
}
.view {
inline-size: 75%;
@media screen and (max-width: $tablet) {
inline-size: 100%;
padding-inline-start: 0;
padding-block-start: 1rem;
}
}
</style>