refactor(ui): allow routerLink/routerView in tabs, enabling browser navigation

This commit is contained in:
upsiflu 2025-02-04 15:06:48 +01:00
parent 0b4d5b3acd
commit abf29eeffe
4 changed files with 58 additions and 33 deletions

View File

@ -1,24 +1,29 @@
<script setup lang="ts">
import { type RouterLinkProps } from 'vue-router'
import { TABS_INJECTION_KEY } from '~/injection-keys'
import { whenever } from '@vueuse/core'
import { inject, ref } from 'vue'
const { title, icon } = defineProps<{ title:string, icon?:string }>()
export type Props = {
title: string,
to?: RouterLinkProps['to']
icon?: string
}
const { currentTab, tabs, icons } = inject(TABS_INJECTION_KEY, {
currentTab: ref(title),
const props = defineProps<Props>()
const { currentTitle, tabs } = inject(TABS_INJECTION_KEY, {
currentTitle: ref(props.title),
tabs: [],
icons: [],
})
whenever(() => !tabs.includes(title), () => {
tabs.push(title)
icons.push(icon)
whenever(() => !tabs.includes(props), () => {
tabs.push(props)
}, { immediate: true })
</script>
<template>
<div v-if="currentTab === title" class="tab-content">
<div v-if="currentTitle === title" class="tab-content">
<slot />
</div>
</template>

View File

@ -1,22 +1,31 @@
<script setup lang="ts">
import { TABS_INJECTION_KEY } from '~/injection-keys'
import { provide, reactive, ref, watch } from 'vue'
import { computed, provide, reactive, ref, watch } from 'vue'
import { type Props as TabProps } from '~/components/ui/Tab.vue'
import Button from "~/components/ui/Button.vue";
import Link from "~/components/ui/Link.vue";
const currentTab = ref()
const tabs = reactive([] as string[])
const icons = reactive([] as string[])
const currentTitle = ref<TabProps['title']>('')
const tabs = reactive<TabProps[]>([])
provide(TABS_INJECTION_KEY, {
currentTab,
tabs,
icons
currentTitle: currentTitle,
tabs: tabs,
})
watch(() => tabs.length, (to, from) => {
const currentTab = computed(() =>
tabs.find(({title}) => title === currentTitle.value)
)
const currentIndex = computed(() =>
tabs.findIndex(({title}) => title === currentTitle.value)
)
watch(() => tabs.length, (_, from) => {
if (from === 0) {
currentTab.value = tabs[0]
currentTitle.value = tabs[0].title
}
})
</script>
@ -24,16 +33,17 @@ watch(() => tabs.length, (to, from) => {
<template>
<div class="funkwhale tabs">
<div class="tabs-header">
<Button
v-for="(tab, index) in tabs" :key="tab" :class="{ 'is-active': currentTab === tab }"
ghost
:icon="icons[index]"
@click="currentTab = tab"
@keydown.left="currentTab = tabs[(tabs.findIndex(t => t === currentTab) - 1 + tabs.length) % tabs.length]"
@keydown.right="currentTab = tabs[(tabs.findIndex(t => t === currentTab) + 1) % tabs.length]" class="tabs-item">
<div class="is-spacing">{{ tab }}</div>
<label>{{ tab }}</label>
</Button>
<component ghost :is="tab.to ? Link : Button"
v-for="(tab, _) in tabs" :class="{ 'is-active': currentTitle === tab.title }"
v-bind="tab"
:onClick="() => { currentTitle = tab.title }"
@keydown.left="currentTitle = tabs[(currentIndex - 1 + tabs.length) % tabs.length]"
@keydown.right="currentTitle = tabs[(currentIndex + 1) % tabs.length]"
class="tabs-item"
>
<div class="is-spacing">{{ tab.title }}</div>
<label>{{ tab.title }}</label>
</component>
<div class="tabs-right">
<slot name="tabs-right" />

View File

@ -1,9 +1,9 @@
import type { InjectionKey, Ref } from "vue"
import { type InjectionKey, type Ref } from "vue"
import { type Props as TabProps } from '~/components/ui/Tab.vue'
export const TABS_INJECTION_KEY = Symbol('tabs') as InjectionKey<{
tabs: string[]
icons: (string|undefined)[]
currentTab: Ref<string>
tabs: TabProps[]
currentTitle: Ref<TabProps['title']>
}>
export interface PopoverContext {

View File

@ -1,11 +1,15 @@
<script setup lang="ts">
import { ref } from 'vue'
import Input from '~/components/ui/Input.vue'
import Tabs from '~/components/ui/Tabs.vue'
import Tab from '~/components/ui/Tab.vue'
const search = ref('')
</script>
```ts
import Tabs from "~/components/ui/Tabs.vue"
import Tabs from "~/components/ui/Tabs.vue";
```
# Tabs
@ -72,6 +76,12 @@ You can add a template to the right side of the tabs using the `#tabs-right` dir
<Tab title="Activity">Activity content</Tab>
<template #tabs-right>
<Input icon="bi-search" placeholder="Search" />
<Input v-model="search" icon="bi-search" placeholder="Search" />
</template>
</Tabs>
## Tabs and routes
If the tab content covers most of the page, users will expect that they can navigate to the previous tab with the "back" button of the browser ([See Navigation](./navigation.md)).
In this case, add the `:to` prop to each tab, and place a `RouterView` with the intended props of the route's component into its default slot.