feat(nuxt3): support `custom` prop for `<nuxt-link>` (#4249)

Co-authored-by: Daniel Roe <daniel@roe.dev>
Co-authored-by: pooya parsa <pyapar@gmail.com>
This commit is contained in:
Francisco Buceta 2022-07-07 19:28:23 +02:00 committed by GitHub
parent c88e1716ab
commit 38e0fa6e1e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 72 additions and 20 deletions

View File

@ -81,6 +81,7 @@ In this example, we use `<NuxtLink>` with `target`, `rel`, and `noRel` props.
- **replace**: Works the same as [Vue Router's `replace` prop](https://router.vuejs.org/api/#replace) on internal links
- **ariaCurrentValue**: An `aria-current` attribute value to apply on exact active links. Works the same as [Vue Router's `aria-current-value` prop](https://router.vuejs.org/api/#aria-current-value) on internal links
- **external**: Forces the link to be considered as external (`true`) or internal (`false`). This is helpful to handle edge-cases
- **custom**: Whether `<NuxtLink>` should wrap its content in an `<a>` element. It allows taking full control of how a link is rendered and how navigation works when it is clicked. Works the same as [Vue Router's `custom` prop](https://router.vuejs.org/api/#custom)
::alert{icon=👉}
Defaults can be overwritten, see [overwriting defaults](#overwriting-defaults) if you want to change them.

View File

@ -3,9 +3,17 @@
<NuxtLink to="/about">
About page
</NuxtLink>
<NuxtLink v-slot="{ navigate }" to="/about" custom>
<button @click="navigate">
Custom about page
</button>
</NuxtLink>
<NuxtLink to="https://nuxtjs.org">
Nuxt website
</NuxtLink>
<NuxtLink v-slot="{ href, target }" to="https://nuxtjs.org" custom>
<a :href="href" :target="target">Go to {{ href }}</a>
</NuxtLink>
<NuxtLink to="https://twitter.com/nuxt_js" target="_blank">
Nuxt Twitter with a blank target
</NuxtLink>

View File

@ -23,6 +23,13 @@ const timer = useState('timer', () => 0)
<NuxtLink to="/redirect" class="n-link-base">
Redirect
</NuxtLink>
<NuxtLink custom to="/redirect">
<template #default="{ href, navigate }">
<button @click="navigate">
Custom: {{ href }}
</button>
</template>
</NuxtLink>
</nav>
</template>

View File

@ -2,37 +2,38 @@ import { defineComponent, h, resolveComponent, PropType, computed, DefineCompone
import { RouteLocationRaw, Router } from 'vue-router'
import { hasProtocol } from 'ufo'
import { useRouter } from '#app'
import { navigateTo, useRouter } from '#app'
const firstNonUndefined = <T>(...args: T[]): T => args.find(arg => arg !== undefined)
const DEFAULT_EXTERNAL_REL_ATTRIBUTE = 'noopener noreferrer'
export type NuxtLinkOptions = {
componentName?: string;
externalRelAttribute?: string | null;
activeClass?: string;
exactActiveClass?: string;
componentName?: string
externalRelAttribute?: string | null
activeClass?: string
exactActiveClass?: string
}
export type NuxtLinkProps = {
// Routing
to?: string | RouteLocationRaw;
href?: string | RouteLocationRaw;
external?: boolean;
to?: string | RouteLocationRaw
href?: string | RouteLocationRaw
external?: boolean
replace?: boolean
custom?: boolean
// Attributes
target?: string;
rel?: string;
noRel?: boolean;
target?: string
rel?: string
noRel?: boolean
// Styling
activeClass?: string;
exactActiveClass?: string;
activeClass?: string
exactActiveClass?: string
// Vue Router's `<RouterLink>` additional props
replace?: boolean;
ariaCurrentValue?: string;
ariaCurrentValue?: string
};
export function defineNuxtLink (options: NuxtLinkOptions) {
@ -154,9 +155,9 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
activeClass: props.activeClass || options.activeClass,
exactActiveClass: props.exactActiveClass || options.exactActiveClass,
replace: props.replace,
ariaCurrentValue: props.ariaCurrentValue
ariaCurrentValue: props.ariaCurrentValue,
custom: props.custom
},
// TODO: Slot API
slots.default
)
}
@ -175,6 +176,22 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
// converts `""` to `null` to prevent the attribute from being added as empty (`rel=""`)
: firstNonUndefined<string | null>(props.rel, options.externalRelAttribute, href ? DEFAULT_EXTERNAL_REL_ATTRIBUTE : '') || null
const navigate = () => navigateTo(href, { replace: props.replace })
// https://router.vuejs.org/api/#custom
if (props.custom) {
if (!slots.default) { return null }
return slots.default({
href,
navigate,
route: router.resolve(href),
rel,
target,
isActive: false,
isExactActive: false
})
}
return h('a', { href, rel, target }, slots.default?.())
}
}

View File

@ -179,8 +179,24 @@ export default defineNuxtPlugin<{ route: Route, router: Router }>((nuxtApp) => {
nuxtApp.vueApp.component('RouterLink', {
functional: true,
props: { to: String },
setup: (props, { slots }) => () => h('a', { href: props.to, onClick: (e) => { e.preventDefault(); router.push(props.to) } }, slots)
props: {
to: String,
custom: Boolean,
replace: Boolean,
// Not implemented
activeClass: String,
exactActiveClass: String,
ariaCurrentValue: String
},
setup: (props, { slots }) => {
const navigate = () => handleNavigation(props.to, props.replace)
return () => {
const route = router.resolve(props.to)
return props.custom
? slots.default?.({ href: props.to, navigate, route })
: h('a', { href: props.to, onClick: (e) => { e.preventDefault(); return navigate() } }, slots)
}
}
})
if (process.client) {

View File

@ -81,7 +81,10 @@ export default defineNuxtModule({
}
nuxt.hook('autoImports:extend', (autoImports) => {
autoImports.push({ name: 'definePageMeta', as: 'definePageMeta', from: resolve(runtimeDir, 'composables') })
autoImports.push(
{ name: 'definePageMeta', as: 'definePageMeta', from: resolve(runtimeDir, 'composables') },
{ name: 'useLink', as: 'useLink', from: 'vue-router' }
)
})
// Extract macros from pages