refactor(frontend): extract SideNavShell for admin and user settings
This commit is contained in:
parent
4e805d182a
commit
7e4bf83fa0
|
|
@ -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>
|
||||||
|
|
@ -1,63 +1,24 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="content-widescreen">
|
<SideNavShell
|
||||||
<div class="user-settings">
|
:navigation-items="navigationItems"
|
||||||
<nav class="navigation">
|
:extra-links="extraSettingsLinks"
|
||||||
<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>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {computed} from 'vue'
|
import {computed} from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import {useI18n} from 'vue-i18n'
|
||||||
import { useTitle } from '@/composables/useTitle'
|
import {useTitle} from '@/composables/useTitle'
|
||||||
import { useConfigStore } from '@/stores/config'
|
import {useConfigStore} from '@/stores/config'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import {useAuthStore} from '@/stores/auth'
|
||||||
import {useRoute} from 'vue-router'
|
|
||||||
|
|
||||||
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'))
|
useTitle(() => t('user.settings.title'))
|
||||||
|
|
||||||
const configStore = useConfigStore()
|
const configStore = useConfigStore()
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const route = useRoute()
|
|
||||||
|
|
||||||
const totpEnabled = computed(() => configStore.totpEnabled)
|
const totpEnabled = computed(() => configStore.totpEnabled)
|
||||||
const caldavEnabled = computed(() => configStore.caldavEnabled)
|
const caldavEnabled = computed(() => configStore.caldavEnabled)
|
||||||
|
|
@ -98,6 +59,7 @@ const navigationItems = computed(() => {
|
||||||
{
|
{
|
||||||
title: t('migrate.title'),
|
title: t('migrate.title'),
|
||||||
routeName: 'migrate.start',
|
routeName: 'migrate.start',
|
||||||
|
activeRouteNames: ['migrate.service'],
|
||||||
condition: migratorsEnabled.value,
|
condition: migratorsEnabled.value,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -124,53 +86,9 @@ const navigationItems = computed(() => {
|
||||||
condition: userDeletionEnabled.value,
|
condition: userDeletionEnabled.value,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
return items.filter(({condition}) => condition !== false)
|
return items.filter(({condition}) => condition !== false)
|
||||||
})
|
})
|
||||||
|
|
||||||
const extraSettingsLinks = computed(() => authStore.settings.extraSettingsLinks)
|
const extraSettingsLinks = computed(() => Object.values(authStore.settings.extraSettingsLinks ?? {}))
|
||||||
</script>
|
</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>
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue