fix(front): simplify tags component (tags are just links)
This commit is contained in:
parent
70c19a70b2
commit
0b2e457052
|
@ -0,0 +1,93 @@
|
|||
<script setup lang="ts">
|
||||
import { type RouterLinkProps } from 'vue-router'
|
||||
|
||||
import Link from '~/components/ui/Link.vue'
|
||||
import Layout from '~/components/ui/Layout.vue'
|
||||
|
||||
type Tab = {
|
||||
title: string,
|
||||
to: RouterLinkProps['to'],
|
||||
icon?: string,
|
||||
badge?: string | number
|
||||
}
|
||||
|
||||
const tabs = defineModel<Tab[]>({ required: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Layout
|
||||
nav
|
||||
flex
|
||||
>
|
||||
<Link
|
||||
v-for="tab in tabs"
|
||||
:key="tab.title"
|
||||
v-bind="tab"
|
||||
ghost
|
||||
min-content
|
||||
:class="$style.tab"
|
||||
>
|
||||
<Layout
|
||||
stack
|
||||
no-gap
|
||||
>
|
||||
<span :class="$style.fakeTitle">{{ tab.title }}</span>
|
||||
<span :class="$style.realTitle">{{ tab.title }}</span>
|
||||
<span
|
||||
v-if="tab.badge"
|
||||
:class="$style.badge"
|
||||
>
|
||||
{{ tab.badge }}
|
||||
</span>
|
||||
</Layout>
|
||||
</Link>
|
||||
</Layout>
|
||||
</template>
|
||||
|
||||
<style module>
|
||||
.fakeTitle {
|
||||
font-size: 16px;
|
||||
font-weight: 900;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.realTitle {
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
}
|
||||
.tab {
|
||||
--hover-background-color: transparent;
|
||||
--exact-active-background-color: transparent;
|
||||
}
|
||||
.tab:global(.router-link-exact-active) .realTitle {
|
||||
font-weight: 900;
|
||||
}
|
||||
.badge {
|
||||
display: block;
|
||||
height: 16px;
|
||||
background-color: var(--fw-secondary);
|
||||
width: 16px;
|
||||
position: absolute;
|
||||
inset: -10px -14px auto auto;
|
||||
border-radius: 100vh;
|
||||
font-size: 10px;
|
||||
font-weight: 900;
|
||||
padding: 5px;
|
||||
line-height: 5px;
|
||||
color: black;
|
||||
}
|
||||
:is(.tab:global(.router-link-exact-active), .tab:hover) .realTitle:after {
|
||||
content: '';
|
||||
display: block;
|
||||
height: 4px;
|
||||
background-color: var(--fw-secondary);
|
||||
margin: 0 auto;
|
||||
width: calc(10% + 2rem);
|
||||
position: absolute;
|
||||
inset: auto 0 -14px 0;
|
||||
border-radius: 100vh;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -1,11 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
import Layout from '~/components/ui/Layout.vue'
|
||||
import Tabs from '~/components/ui/Tabs.vue'
|
||||
import Tab from '~/components/ui/Tab.vue'
|
||||
import Nav from '~/components/ui/Nav.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
|
@ -13,6 +12,34 @@ const labels = computed(() => ({
|
|||
secondaryMenu: t('views.admin.library.Base.menu.secondary')
|
||||
}))
|
||||
|
||||
const tabs = ref([
|
||||
{
|
||||
title: t('views.admin.library.Base.link.edits'),
|
||||
to: {name: 'manage.library.edits'}
|
||||
}, {
|
||||
title: t('views.admin.library.Base.link.channels'),
|
||||
to: {name: 'manage.channels'}
|
||||
}, {
|
||||
title: t('views.admin.library.Base.link.artists'),
|
||||
to: {name: 'manage.library.artists'}
|
||||
}, {
|
||||
title: t('views.admin.library.Base.link.albums'),
|
||||
to: {name: 'manage.library.albums'}
|
||||
}, {
|
||||
title: t('views.admin.library.Base.link.tracks'),
|
||||
to: {name: 'manage.library.tracks'}
|
||||
}, {
|
||||
title: t('views.admin.library.Base.link.libraries'),
|
||||
to: {name: 'manage.library.libraries'}
|
||||
}, {
|
||||
title: t('views.admin.library.Base.link.uploads'),
|
||||
to: {name: 'manage.library.uploads'}
|
||||
}, {
|
||||
title: t('views.admin.library.Base.link.tags'),
|
||||
to: {name: 'manage.library.tags'}
|
||||
}
|
||||
])
|
||||
|
||||
const route = useRoute()
|
||||
</script>
|
||||
|
||||
|
@ -23,40 +50,8 @@ const route = useRoute()
|
|||
stack
|
||||
class="page-admin-library"
|
||||
>
|
||||
<Tabs>
|
||||
<Tab
|
||||
:title="t('views.admin.library.Base.link.edits')"
|
||||
:to="{name: 'manage.library.edits'}"
|
||||
/>
|
||||
<Tab
|
||||
:title="t('views.admin.library.Base.link.channels')"
|
||||
:to="{name: 'manage.channels'}"
|
||||
/>
|
||||
<Tab
|
||||
:title="t('views.admin.library.Base.link.artists')"
|
||||
:to="{name: 'manage.library.artists'}"
|
||||
/>
|
||||
<Tab
|
||||
:title="t('views.admin.library.Base.link.albums')"
|
||||
:to="{name: 'manage.library.albums'}"
|
||||
/>
|
||||
<Tab
|
||||
:title="t('views.admin.library.Base.link.tracks')"
|
||||
:to="{name: 'manage.library.tracks'}"
|
||||
/>
|
||||
<Tab
|
||||
:title="t('views.admin.library.Base.link.libraries')"
|
||||
:to="{name: 'manage.library.libraries'}"
|
||||
/>
|
||||
<Tab
|
||||
:title="t('views.admin.library.Base.link.uploads')"
|
||||
:to="{name: 'manage.library.uploads'}"
|
||||
/>
|
||||
<Tab
|
||||
:title="t('views.admin.library.Base.link.tags')"
|
||||
:to="{name: 'manage.library.tags'}"
|
||||
/>
|
||||
</Tabs>
|
||||
<Nav v-model="tabs" />
|
||||
|
||||
<router-view :key="route.fullPath" />
|
||||
</Layout>
|
||||
</template>
|
||||
|
|
|
@ -8,8 +8,7 @@ import { useRoute } from 'vue-router'
|
|||
import axios from 'axios'
|
||||
|
||||
import Layout from '~/components/ui/Layout.vue'
|
||||
import Tabs from '~/components/ui/Tabs.vue'
|
||||
import Tab from '~/components/ui/Tab.vue'
|
||||
import Nav from '~/components/ui/Nav.vue'
|
||||
|
||||
|
||||
const store = useStore()
|
||||
|
@ -22,6 +21,24 @@ const labels = computed(() => ({
|
|||
secondaryMenu: t('views.admin.moderation.Base.menu.secondary')
|
||||
}))
|
||||
|
||||
const tabs = ref([{
|
||||
title: t('views.admin.moderation.Base.link.reports'),
|
||||
to: { name: 'manage.moderation.reports.list', query: { q: 'resolved:no' } },
|
||||
badge: store.state.ui.notifications.pendingReviewReports > 0 ? store.state.ui.notifications.pendingReviewReports : undefined
|
||||
|
||||
}, {
|
||||
title: t('views.admin.moderation.Base.link.userRequests'),
|
||||
to: { name: 'manage.moderation.requests.list', query: { q: 'status:pending' } },
|
||||
badge: store.state.ui.notifications.pendingReviewRequests > 0 ? store.state.ui.notifications.pendingReviewRequests : undefined
|
||||
}, {
|
||||
title: t('views.admin.moderation.Base.link.domains'),
|
||||
to: { name: 'manage.moderation.domains.list' }
|
||||
}, {
|
||||
title: t('views.admin.moderation.Base.link.accounts'),
|
||||
to: { name: 'manage.moderation.accounts.list' }
|
||||
}
|
||||
])
|
||||
|
||||
const fetchNodeInfo = async () => {
|
||||
const response = await axios.get('instance/nodeinfo/2.1/')
|
||||
allowListEnabled.value = get(response.data, 'metadata.allowList.enabled', false)
|
||||
|
@ -37,41 +54,8 @@ fetchNodeInfo()
|
|||
main
|
||||
no-gap
|
||||
>
|
||||
<Tabs
|
||||
role="navigation"
|
||||
:aria-label="labels.secondaryMenu"
|
||||
>
|
||||
<Tab
|
||||
:title="t('views.admin.moderation.Base.link.reports')"
|
||||
:to="{name: 'manage.moderation.reports.list', query: {q: 'resolved:no'}}"
|
||||
>
|
||||
<div
|
||||
v-if="store.state.ui.notifications.pendingReviewReports > 0"
|
||||
:class="['ui', 'circular', 'mini', 'right floated', 'accent', 'label']"
|
||||
>
|
||||
{{ store.state.ui.notifications.pendingReviewReports }}
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab
|
||||
:title="t('views.admin.moderation.Base.link.userRequests')"
|
||||
:to="{name: 'manage.moderation.requests.list', query: {q: 'status:pending'}}"
|
||||
>
|
||||
<div
|
||||
v-if="store.state.ui.notifications.pendingReviewRequests > 0"
|
||||
:class="['ui', 'circular', 'mini', 'right floated', 'accent', 'label']"
|
||||
>
|
||||
{{ store.state.ui.notifications.pendingReviewRequests }}
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab
|
||||
:title="t('views.admin.moderation.Base.link.domains')"
|
||||
:to="{name: 'manage.moderation.domains.list'}"
|
||||
/>
|
||||
<Tab
|
||||
:title="t('views.admin.moderation.Base.link.accounts')"
|
||||
:to="{name: 'manage.moderation.accounts.list'}"
|
||||
/>
|
||||
</Tabs>
|
||||
<Nav v-model="tabs" />
|
||||
|
||||
<router-view
|
||||
:key="route.fullPath"
|
||||
:allow-list-enabled="allowListEnabled"
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { computed } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import Layout from '~/components/ui/Layout.vue'
|
||||
import Tabs from '~/components/ui/Tabs.vue'
|
||||
import Tab from '~/components/ui/Tab.vue'
|
||||
import Nav from '~/components/ui/Nav.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
|
@ -12,6 +11,18 @@ const labels = computed(() => ({
|
|||
manageUsers: t('views.admin.users.Base.title'),
|
||||
secondaryMenu: t('views.admin.users.Base.menu.secondary')
|
||||
}))
|
||||
|
||||
const tabs = ref([
|
||||
{
|
||||
title: t('views.admin.users.Base.link.users'),
|
||||
to: { name: 'manage.users.users.list' }
|
||||
},
|
||||
|
||||
{
|
||||
title: t('views.admin.users.Base.link.invitations'),
|
||||
to: { name: 'manage.users.invitations.list' }
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -20,17 +31,8 @@ const labels = computed(() => ({
|
|||
main
|
||||
stack
|
||||
>
|
||||
<Tabs>
|
||||
<Tab
|
||||
:title="t('views.admin.users.Base.link.users')"
|
||||
:to="{name: 'manage.users.users.list'}"
|
||||
/>
|
||||
<Nav v-model="tabs" />
|
||||
|
||||
<Tab
|
||||
:title="t('views.admin.users.Base.link.invitations')"
|
||||
:to="{name: 'manage.users.invitations.list'}"
|
||||
/>
|
||||
</Tabs>
|
||||
<router-view :key="$route.fullPath" />
|
||||
</Layout>
|
||||
</template>
|
||||
|
|
|
@ -16,8 +16,7 @@ import RenderedDescription from '~/components/common/RenderedDescription.vue'
|
|||
|
||||
import Layout from '~/components/ui/Layout.vue'
|
||||
import Section from '~/components/ui/Section.vue'
|
||||
import Tabs from '~/components/ui/Tabs.vue'
|
||||
import Tab from '~/components/ui/Tab.vue'
|
||||
import Nav from '~/components/ui/Nav.vue'
|
||||
|
||||
interface Events {
|
||||
(e: 'updated', value: Actor): void
|
||||
|
@ -79,6 +78,21 @@ const fetchData = async () => {
|
|||
|
||||
watch(props, fetchData, { immediate: true })
|
||||
const recentActivity = ref(0)
|
||||
|
||||
const tabs = ref([{
|
||||
title: t('views.auth.ProfileBase.link.overview') ,
|
||||
to: { name: 'profile.overview', params: routerParams }
|
||||
}, {
|
||||
title: t('views.auth.ProfileBase.link.activity') ,
|
||||
to: { name: 'profile.activity', params: routerParams }
|
||||
}, ...(
|
||||
store.state.auth.authenticated && object.value && object.value.full_username === store.state.auth.fullUsername
|
||||
? [{
|
||||
title: t('views.auth.ProfileBase.link.manageUploads') ,
|
||||
to: { name: 'profile.manageUploads', params: routerParams }
|
||||
}]
|
||||
: []
|
||||
)])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -135,38 +149,12 @@ const recentActivity = ref(0)
|
|||
/>
|
||||
</Section>
|
||||
</Layout>
|
||||
<Tabs>
|
||||
<Tab
|
||||
:title="t('views.auth.ProfileBase.link.overview')"
|
||||
:to="{name: 'profile.overview', params: routerParams}"
|
||||
>
|
||||
<router-view
|
||||
:object="object"
|
||||
@updated="fetchData"
|
||||
/>
|
||||
</Tab>
|
||||
<Nav v-model="tabs" />
|
||||
|
||||
<Tab
|
||||
:title="t('views.auth.ProfileBase.link.activity')"
|
||||
:to="{name: 'profile.activity', params: routerParams}"
|
||||
>
|
||||
<router-view
|
||||
:object="object"
|
||||
@updated="fetchData"
|
||||
/>
|
||||
</Tab>
|
||||
|
||||
<Tab
|
||||
v-if="store.state.auth.authenticated && object && object.full_username === store.state.auth.fullUsername"
|
||||
:title="t('views.auth.ProfileBase.link.manageUploads')"
|
||||
:to="{ name: 'profile.manageUploads', params: routerParams }"
|
||||
>
|
||||
<router-view
|
||||
:object="object"
|
||||
@updated="fetchData"
|
||||
/>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
<router-view
|
||||
:object="object"
|
||||
@updated="fetchData"
|
||||
/>
|
||||
</Layout>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -24,8 +24,7 @@ import RadioButton from '~/components/radios/Button.vue'
|
|||
import Loader from '~/components/ui/Loader.vue'
|
||||
import Button from '~/components/ui/Button.vue'
|
||||
import Link from '~/components/ui/Link.vue'
|
||||
import Tabs from '~/components/ui/Tabs.vue'
|
||||
import Tab from '~/components/ui/Tab.vue'
|
||||
import Nav from '~/components/ui/Nav.vue'
|
||||
import OptionsButton from '~/components/ui/button/Options.vue'
|
||||
import Popover from '~/components/ui/Popover.vue'
|
||||
import PopoverItem from '~/components/ui/popover/PopoverItem.vue'
|
||||
|
@ -60,7 +59,6 @@ const showEditModal = ref(false)
|
|||
const showSubscribeModal = ref(false)
|
||||
|
||||
const isOwner = computed(() => store.state.auth.authenticated && object.value?.attributed_to.full_username === store.state.auth.fullUsername)
|
||||
const isPodcast = computed(() => object.value?.artist?.content_category === 'podcast')
|
||||
const isPlayable = computed(() => totalTracks.value > 0)
|
||||
const externalDomain = computed(() => {
|
||||
const parser = document.createElement('a')
|
||||
|
@ -147,6 +145,18 @@ const updateSubscriptionCount = (delta: number) => {
|
|||
object.value.subscriptions_count += delta
|
||||
}
|
||||
}
|
||||
|
||||
const tabs = ref([
|
||||
{
|
||||
title: t('views.channels.DetailBase.link.channelOverview'),
|
||||
to: {name: 'channels.detail', params: { id: props.id }}
|
||||
|
||||
},
|
||||
{
|
||||
title: t('views.channels.DetailBase.link.channelEpisodes'),
|
||||
to: {name: 'channels.detail.episodes', params: { id: props.id }}
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -353,7 +363,7 @@ const updateSubscriptionCount = (delta: number) => {
|
|||
<template v-if="store.state.auth.availablePermissions['library']">
|
||||
<hr>
|
||||
<PopoverItem
|
||||
:to="{name: 'manage.channels.detail', params: {id: object.uuid}}"
|
||||
:to="{ name: 'manage.channels.detail', params: { id: object.uuid } }"
|
||||
icon="bi-wrench"
|
||||
>
|
||||
{{ t('views.channels.DetailBase.link.moderation') }}
|
||||
|
@ -482,28 +492,13 @@ const updateSubscriptionCount = (delta: number) => {
|
|||
:limit="5"
|
||||
:show-more="true"
|
||||
/>
|
||||
<Tabs>
|
||||
<Tab
|
||||
:title="t('views.channels.DetailBase.link.channelOverview')"
|
||||
:to="{name: 'channels.detail', params: {id: id}}"
|
||||
>
|
||||
<router-view
|
||||
v-if="object"
|
||||
:object="object"
|
||||
@tracks-loaded="totalTracks = $event"
|
||||
/>
|
||||
</Tab>
|
||||
<Tab
|
||||
:title="t('views.channels.DetailBase.link.channelEpisodes')"
|
||||
:to="{name: 'channels.detail.episodes', params: {id: id}}"
|
||||
>
|
||||
<router-view
|
||||
v-if="object"
|
||||
:object="object"
|
||||
@tracks-loaded="totalTracks = $event"
|
||||
/>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
<Nav v-model="tabs" />
|
||||
|
||||
<router-view
|
||||
v-if="object"
|
||||
:object="object"
|
||||
@tracks-loaded="totalTracks = $event"
|
||||
/>
|
||||
</section>
|
||||
</template>
|
||||
</Layout>
|
||||
|
|
|
@ -17,9 +17,7 @@ import OptionsButton from '~/components/ui/button/Options.vue'
|
|||
import Popover from '~/components/ui/Popover.vue'
|
||||
import PopoverItem from '~/components/ui/popover/PopoverItem.vue'
|
||||
import Header from '~/components/ui/Header.vue'
|
||||
import Tabs from '~/components/ui/Tabs.vue'
|
||||
import Tab from '~/components/ui/Tab.vue'
|
||||
import Button from '~/components/ui/Button.vue'
|
||||
import Nav from '~/components/ui/Nav.vue'
|
||||
|
||||
import useErrorHandler from '~/composables/useErrorHandler'
|
||||
import useReport from '~/composables/moderation/useReport'
|
||||
|
@ -86,10 +84,22 @@ watchEffect(() => {
|
|||
})
|
||||
|
||||
const updateUploads = (count: number) => {
|
||||
if (object.value) {
|
||||
object.value.uploads_count += count
|
||||
}
|
||||
object.value = object.value
|
||||
? { ...object.value, uploads_count: object.value.uploads_count + count }
|
||||
: null
|
||||
}
|
||||
|
||||
const tabs = ref([{
|
||||
title: t('views.library.LibraryBase.link.artists'),
|
||||
to: { name: 'library.detail' }
|
||||
}, {
|
||||
title: t('views.library.LibraryBase.link.albums'),
|
||||
to: { name: 'library.detail.albums' }
|
||||
}, {
|
||||
title: t('views.library.LibraryBase.link.tracks'),
|
||||
to: { name: 'library.detail.tracks' }
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -177,13 +187,16 @@ const updateUploads = (count: number) => {
|
|||
<i class="bi bi-globe" />
|
||||
{{ labels.visibility.everyone }}
|
||||
</span>
|
||||
<span class="middledot icon">
|
||||
<span
|
||||
v-if="object"
|
||||
class="middledot icon"
|
||||
>
|
||||
<i class="bi bi-music-note-list" />
|
||||
{{ t('views.library.LibraryBase.meta.tracks', object.uploads_count) }}
|
||||
</span>
|
||||
<span v-if="object?.size">
|
||||
<span v-if="object && 'size' in object && object.size">
|
||||
<i class="bi bi-database-fill" />
|
||||
{{ humanSize(object?.size) }}
|
||||
{{ humanSize(object.size) }}
|
||||
</span>
|
||||
</Layout>
|
||||
|
||||
|
@ -222,40 +235,13 @@ const updateUploads = (count: number) => {
|
|||
</p>
|
||||
</div>
|
||||
</Layout>
|
||||
<Tabs>
|
||||
<Tab
|
||||
:title="t('views.library.LibraryBase.link.artists')"
|
||||
:to="{name: 'library.detail'}"
|
||||
>
|
||||
<router-view
|
||||
:is-owner="isOwner"
|
||||
:object="object"
|
||||
@updated="fetchData"
|
||||
@uploads-finished="updateUploads"
|
||||
/>
|
||||
</Tab>
|
||||
<Tab
|
||||
:title="t('views.library.LibraryBase.link.albums')"
|
||||
:to="{name: 'library.detail.albums'}"
|
||||
>
|
||||
<router-view
|
||||
:is-owner="isOwner"
|
||||
:object="object"
|
||||
@updated="fetchData"
|
||||
@uploads-finished="updateUploads"
|
||||
/>
|
||||
</Tab>
|
||||
<Tab
|
||||
:title="t('views.library.LibraryBase.link.tracks')"
|
||||
:to="{name: 'library.detail.tracks'}"
|
||||
>
|
||||
<router-view
|
||||
:is-owner="isOwner"
|
||||
:object="object"
|
||||
@updated="fetchData"
|
||||
@uploads-finished="updateUploads"
|
||||
/>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
<Nav v-model="tabs" />
|
||||
|
||||
<router-view
|
||||
:is-owner="isOwner"
|
||||
:object="object"
|
||||
@updated="fetchData"
|
||||
@uploads-finished="updateUploads"
|
||||
/>
|
||||
</Layout>
|
||||
</template>
|
||||
|
|
|
@ -29,6 +29,7 @@ export default defineConfig({
|
|||
{ text: 'Pagination', link: '/components/ui/pagination' },
|
||||
{ text: 'Table of Contents', link: '/components/ui/toc' },
|
||||
{ text: 'Tabs', link: '/components/ui/tabs' },
|
||||
{ text: 'Nav', link: '/components/ui/nav' },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
import Nav from '~/components/ui/Nav.vue'
|
||||
|
||||
const nav = ref([{ title: 'Go up', to: '../' }, { title: 'Home', to: './', badge: "2" }])
|
||||
</script>
|
||||
|
||||
```ts
|
||||
import Nav from "~/components/ui/Nav.vue"
|
||||
```
|
||||
|
||||
# Nav
|
||||
|
||||
This is just a list of links, styled like tabs.
|
||||
|
||||
You can add a `badge` or an `icon` to each tab link.
|
||||
|
||||
<Link to="/">Hello</Link>
|
||||
|
||||
```ts
|
||||
import { ref } from 'vue'
|
||||
|
||||
const nav = ref([{ title: 'Go up', to: '../' }, { title: 'Home', to: './', badge: "2" }])
|
||||
```
|
||||
|
||||
```vue-html
|
||||
<Nav v-model="nav" />
|
||||
```
|
||||
|
||||
<Nav v-model="nav" />
|
Loading…
Reference in New Issue