mirror of
https://github.com/go-vikunja/vikunja.git
synced 2026-05-22 02:30:56 -05:00
refactor(frontend): extract SideNavShell for admin and user settings
This commit is contained in:
126
frontend/src/components/misc/SideNavShell.vue
Normal file
126
frontend/src/components/misc/SideNavShell.vue
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user