mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-18 14:08:06 +01:00
feat(Link)!: rename from LinkCustom and add exact-query / exact-hash props
This commit is contained in:
24
docs/content/2.elements/9.link.md
Normal file
24
docs/content/2.elements/9.link.md
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: 'Link'
|
||||
description: Render a NuxtLink but with superpowers.
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxtlabs/ui/blob/dev/src/runtime/components/elements/Link.vue
|
||||
navigation:
|
||||
badge: Edge
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
The Link component is a wrapper around [`<NuxtLink>`](https://nuxt.com/docs/api/components/nuxt-link) through the [custom](https://router.vuejs.org/api/interfaces/RouterLinkProps.html#Properties-custom) prop that provides a few extra props:
|
||||
|
||||
- `inactive-class` prop to set a class when the link is inactive, `active-class` is used when active.
|
||||
- `exact` prop to style with `active-class` when the link is active and the route is exactly the same as the current route.
|
||||
- `exact-query` and `exact-hash` props to style with `active-class` when the link is active and the query or hash is exactly the same as the current query or hash.
|
||||
|
||||
The incentive behind this is to provide the same API as NuxtLink back in Nuxt 2 / Vue 2. You can read more about it in the Vue Router [migration from Vue 2](https://router.vuejs.org/guide/migration/#removal-of-the-exact-prop-in-router-link) guide.
|
||||
|
||||
It also renders an `<a>` tag when a `to` prop is provided, otherwise it renders a `<button>` tag.
|
||||
|
||||
It is used underneath by the [Button](/elements/button), [Dropdown](/elements/dropdown) and [VerticalNavigation](/navigation/vertical-navigation) components.
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<ULinkCustom :type="type" :disabled="disabled || loading" :class="buttonClass">
|
||||
<ULink :type="type" :disabled="disabled || loading" :class="buttonClass">
|
||||
<slot name="leading" :disabled="disabled" :loading="loading">
|
||||
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="leadingIconClass" aria-hidden="true" />
|
||||
</slot>
|
||||
@@ -13,7 +13,7 @@
|
||||
<slot name="trailing" :disabled="disabled" :loading="loading">
|
||||
<UIcon v-if="isTrailing && trailingIconName" :name="trailingIconName" :class="trailingIconClass" aria-hidden="true" />
|
||||
</slot>
|
||||
</ULinkCustom>
|
||||
</ULink>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -21,7 +21,7 @@ import { computed, defineComponent, useSlots } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import ULinkCustom from '../elements/LinkCustom.vue'
|
||||
import ULink from '../elements/Link.vue'
|
||||
import { classNames } from '../../utils'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
@@ -33,7 +33,7 @@ import appConfig from '#build/app.config'
|
||||
export default defineComponent({
|
||||
components: {
|
||||
UIcon,
|
||||
ULinkCustom
|
||||
ULink
|
||||
},
|
||||
props: {
|
||||
type: {
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<HMenuItems :class="[ui.base, ui.divide, ui.ring, ui.rounded, ui.shadow, ui.background, ui.height]" static>
|
||||
<div v-for="(subItems, index) of items" :key="index" :class="ui.padding">
|
||||
<HMenuItem v-for="(item, subIndex) of subItems" :key="subIndex" v-slot="{ active, disabled: itemDisabled }" :disabled="item.disabled">
|
||||
<ULinkCustom
|
||||
<ULink
|
||||
v-bind="omit(item, ['label', 'slot', 'icon', 'iconClass', 'avatar', 'shortcuts', 'disabled', 'click'])"
|
||||
:class="[ui.item.base, ui.item.padding, ui.item.size, ui.item.rounded, active ? ui.item.active : ui.item.inactive, itemDisabled && ui.item.disabled]"
|
||||
@click="item.click"
|
||||
@@ -35,7 +35,7 @@
|
||||
<UKbd v-for="shortcut of item.shortcuts" :key="shortcut">{{ shortcut }}</UKbd>
|
||||
</span>
|
||||
</slot>
|
||||
</ULinkCustom>
|
||||
</ULink>
|
||||
</HMenuItem>
|
||||
</div>
|
||||
</HMenuItems>
|
||||
@@ -53,7 +53,7 @@ import { omit } from 'lodash-es'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UAvatar from '../elements/Avatar.vue'
|
||||
import UKbd from '../elements/Kbd.vue'
|
||||
import ULinkCustom from '../elements/LinkCustom.vue'
|
||||
import ULink from '../elements/Link.vue'
|
||||
import { usePopper } from '../../composables/usePopper'
|
||||
import type { DropdownItem } from '../../types/dropdown'
|
||||
import type { PopperOptions } from '../../types'
|
||||
@@ -73,7 +73,7 @@ export default defineComponent({
|
||||
UIcon,
|
||||
UAvatar,
|
||||
UKbd,
|
||||
ULinkCustom
|
||||
ULink
|
||||
},
|
||||
props: {
|
||||
items: {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<button v-if="!to" :type="type" :disabled="disabled" v-bind="$attrs" :class="inactiveClass">
|
||||
<button v-if="!to" v-bind="$attrs" :class="inactiveClass">
|
||||
<slot />
|
||||
</button>
|
||||
<NuxtLink
|
||||
v-else
|
||||
v-slot="{ href, target, rel, navigate, isActive, isExactActive, isExternal }"
|
||||
v-slot="{ route, href, target, rel, navigate, isActive, isExactActive, isExternal }"
|
||||
v-bind="$props"
|
||||
custom
|
||||
>
|
||||
@@ -13,7 +13,7 @@
|
||||
:href="href"
|
||||
:rel="rel"
|
||||
:target="target"
|
||||
:class="resolveLinkClass({ isActive, isExactActive })"
|
||||
:class="resolveLinkClass(route, { isActive, isExactActive })"
|
||||
@click="(e) => !isExternal && navigate(e)"
|
||||
>
|
||||
<slot v-bind="{ isActive: exact ? isExactActive : isActive }" />
|
||||
@@ -22,6 +22,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { isEqual } from 'lodash-es'
|
||||
import { defineComponent } from 'vue'
|
||||
import { NuxtLink } from '#components'
|
||||
|
||||
@@ -29,25 +30,32 @@ export default defineComponent({
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
...NuxtLink.props,
|
||||
type: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: null
|
||||
},
|
||||
exact: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
exactQuery: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
exactHash: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
inactiveClass: {
|
||||
type: String,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
setup (props) {
|
||||
function resolveLinkClass ({ isActive, isExactActive }: { isActive: boolean, isExactActive: boolean }) {
|
||||
function resolveLinkClass (route, { isActive, isExactActive }: { isActive: boolean, isExactActive: boolean }) {
|
||||
if (props.exactQuery && !isEqual(route.query, useRoute().query)) {
|
||||
return props.inactiveClass
|
||||
}
|
||||
if (props.exactHash && route.hash !== useRoute().hash) {
|
||||
return props.inactiveClass
|
||||
}
|
||||
|
||||
if (props.exact && isExactActive) {
|
||||
return props.activeClass
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<nav :class="ui.wrapper">
|
||||
<ULinkCustom
|
||||
<ULink
|
||||
v-for="(link, index) of links"
|
||||
v-slot="{ isActive }"
|
||||
:key="index"
|
||||
@@ -33,7 +33,7 @@
|
||||
{{ link.badge }}
|
||||
</span>
|
||||
</slot>
|
||||
</ULinkCustom>
|
||||
</ULink>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
@@ -44,7 +44,7 @@ import { defu } from 'defu'
|
||||
import { omit } from 'lodash-es'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UAvatar from '../elements/Avatar.vue'
|
||||
import ULinkCustom from '../elements/LinkCustom.vue'
|
||||
import ULink from '../elements/Link.vue'
|
||||
import type { VerticalNavigationLink } from '../../types/vertical-navigation'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
@@ -57,7 +57,7 @@ export default defineComponent({
|
||||
components: {
|
||||
UIcon,
|
||||
UAvatar,
|
||||
ULinkCustom
|
||||
ULink
|
||||
},
|
||||
props: {
|
||||
links: {
|
||||
|
||||
4
src/runtime/types/button.d.ts
vendored
4
src/runtime/types/button.d.ts
vendored
@@ -1,4 +1,6 @@
|
||||
export interface Button {
|
||||
import type { Link } from './link'
|
||||
|
||||
export interface Button extends Link {
|
||||
type?: string
|
||||
block?: boolean
|
||||
label?: string
|
||||
|
||||
4
src/runtime/types/dropdown.d.ts
vendored
4
src/runtime/types/dropdown.d.ts
vendored
@@ -1,7 +1,7 @@
|
||||
import type { NuxtLinkProps } from '#app'
|
||||
import type { Link } from './link'
|
||||
import type { Avatar } from './avatar'
|
||||
|
||||
export interface DropdownItem extends NuxtLinkProps {
|
||||
export interface DropdownItem extends Link {
|
||||
label: string
|
||||
slot?: string
|
||||
icon?: string
|
||||
|
||||
1
src/runtime/types/index.d.ts
vendored
1
src/runtime/types/index.d.ts
vendored
@@ -4,6 +4,7 @@ export * from './button'
|
||||
export * from './clipboard'
|
||||
export * from './command-palette'
|
||||
export * from './dropdown'
|
||||
export * from './link'
|
||||
export * from './notification'
|
||||
export * from './popper'
|
||||
export * from './tabs'
|
||||
|
||||
8
src/runtime/types/link.d.ts
vendored
Normal file
8
src/runtime/types/link.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { NuxtLinkProps } from '#app'
|
||||
|
||||
export interface Link extends NuxtLinkProps {
|
||||
exact?: boolean
|
||||
exactQuery?: boolean
|
||||
exactMatch?: boolean
|
||||
inactiveClass?: string
|
||||
}
|
||||
4
src/runtime/types/vertical-navigation.d.ts
vendored
4
src/runtime/types/vertical-navigation.d.ts
vendored
@@ -1,7 +1,7 @@
|
||||
import type { NuxtLinkProps } from '#app'
|
||||
import type { Link } from './link'
|
||||
import type { Avatar } from './avatar'
|
||||
|
||||
export interface VerticalNavigationLink extends NuxtLinkProps {
|
||||
export interface VerticalNavigationLink extends Link {
|
||||
label: string
|
||||
icon?: string
|
||||
iconClass?: string
|
||||
|
||||
Reference in New Issue
Block a user