NavLink

Work in progress - this component is not yet ready for use.

Basic

NNavLink is a component that renders a link to a page in your application. It is a wrapper around the NButton and NuxtLink component that adds some additional functionality.

refer to the NButton and NuxtLink documentation for more information on the props that can be passed to those components.

Preview
Code

Variants & Colors

nav-link-{variant}-{color} - add a variant to the navigation. The default variant is text-primary.

nav-link-active-{variant}-{color} - add a variant to the navigation when the link is active. The default variant is text-primary.

nav-link-inactive-{variant}-{color} - add a variant to the navigation when the link is inactive. The default variant is text-gray.

VariantDescription
textThe text variant (default)
solidThe solid variant.
~The unstyle or base variant
Preview
Code

Props

import type { NBadgeProps } from './badge'
import type { NButtonProps } from './button'

export interface NNavLinkProps extends Omit<NButtonProps, 'una'> {
  navLink?: string
  navLinkInactive?: string
  navLinkActive?: string
  badge?: NBadgeProps

  una?: {
    navLinkDefaultVariant?: string
    navLink?: string
    navLinkActive?: string
    navLinkInactive?: string
  } & NButtonProps['una']
}

Presets

type NavLinkPrefix = 'nav-link'

export const staticNavLink: Record<`${NavLinkPrefix}-${string}` | NavLinkPrefix, string> = {
  // config
  'nav-link-default-variant': 'nav-link-text-primary',

  // base
  'nav-link': 'transition-base leading-6 justify-start gap-x-3 rounded-md',

  // badge
  'nav-link-badge': 'min-w-max whitespace-nowrap rounded-full px-2.5 py-.5 leading-5',

  // text-variant
  'nav-link-text-gray': 'hover:bg-$c-gray-100 hover:text-$c-gray-950',
  'nav-link-active-text-gray': 'bg-$c-gray-100 text-$c-gray-950',
  'nav-link-inactive-text-gray': 'text-$c-gray-950',
}

export const dynamicNavLink: [RegExp, (params: RegExpExecArray) => string][] = [
  // text-variant
  [/^nav-link-active-text(-(\S+))?$/, ([, , c = 'primary']) => `bg-$c-gray-100 text-${c}-600 dark:text-${c}-500`],
  [/^nav-link-inactive-text(-(\S+))?$/, ([, , c = 'gray']) => `text-${c}-800 dark:text-${c}-100`],
  [/^nav-link-text(-(\S+))?$/, ([, , c = 'primary']) => `btn-focus-${c} hover:nav-link-active-text-${c}`],

  // solid-variant
  [/^nav-link-active-solid(-(\S+))?$/, ([, , c = 'primary']) => `bg-${c}-700 dark:bg-${c}-400 text-white dark:text-${c}-950`],
  [/^nav-link-inactive-solid(-(\S+))?$/, ([, , c = 'primary']) => `text-white dark:text-${c}-950`],
  [/^nav-link-solid(-(\S+))?$/, ([, , c = 'primary']) => `btn-focus-${c} hover:nav-link-active-solid-${c}`],
]

export const navLink = [
  ...dynamicNavLink,
  staticNavLink,
]

Components

<script setup lang="ts">
import type { NNavLinkProps } from '../../types'
import { computed } from 'vue'
import { omitProps } from '../../utils'
import NBadge from '../elements/Badge.vue'
import NButton from '../elements/Button.vue'
import NIcon from '../elements/Icon.vue'

defineOptions({
  inheritAttrs: false,
})

const props = withDefaults(
  defineProps<NNavLinkProps>(),
  {
    navLinkActive: 'text-primary',
    navLinkInactive: 'text-primary',
    una: () => ({
      btnDefaultVariant: '~',
      navLinkDefaultVariant: 'nav-link-default-variant',
    }),
  },
)

const btnProps = omitProps(props.una, [
  'navLinkDefaultVariant',
  'navLink',
  'navLinkActive',
  'navLinkInactive',
])

const navLinkVariants = ['text', 'solid'] as const
const hasVariant = computed(() => navLinkVariants.some(navLinkVariants => props.navLink?.includes(navLinkVariants)))
const isBaseVariant = computed(() => props.navLink?.includes('~') || props.una.navLink?.includes('~'))
</script>

<template>
  <NButton
    :nav-link="navLink"
    :nav-link-active="navLinkActive"
    :nav-link-inactive="navLinkInactive"
    :una="btnProps"
    class="nav-link"
    :class="[
      !hasVariant && !isBaseVariant ? una?.navLinkDefaultVariant : null,
      { 'btn-reverse': reverse },
    ]"
    v-bind="{
      ...omitProps(props, ['badge', 'una']),
      ...$attrs,
    }"
  >
    <template #leading>
      <NIcon
        v-if="leading"
        :name="leading"
        :class="una?.btnLeading"
        btn="leading"
      />
    </template>

    <template #default>
      <span
        btn="label"
        class="w-full text-left"
        :class="una?.btnLabel"
      >
        {{ label }}
      </span>
    </template>

    <template v-if="badge" #trailing>
      <!-- TODO: move to una preset -->
      <NBadge
        v-bind="badge"
        :una="{
          badgeDefaultVariant: 'badge-outline-white',
          ...props.badge?.una,
        }"
        class="min-w-max whitespace-nowrap rounded-full px-2.5 py-0.5 leading-5"
      />
    </template>
  </NButton>
</template>