refactor(ui): allow routerLink/routerView in tabs, enabling browser navigation
This commit is contained in:
parent
0b4d5b3acd
commit
abf29eeffe
|
@ -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>
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue