Skip to content

Commit

Permalink
feat(HorizontalNavigation): new component (#1279)
Browse files Browse the repository at this point in the history
  • Loading branch information
benjamincanac committed Jan 25, 2024
1 parent b76e761 commit b8007ba
Show file tree
Hide file tree
Showing 12 changed files with 321 additions and 34 deletions.
27 changes: 27 additions & 0 deletions docs/components/content/examples/HorizontalNavigationExample.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<script setup>
const route = useRoute()
const links = [{
label: 'Profile',
avatar: {
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
},
badge: 100
}, {
label: 'Installation',
icon: 'i-heroicons-home',
to: '/getting-started/installation'
}, {
label: 'Horizontal Navigation',
icon: 'i-heroicons-chart-bar',
to: `${route.path.startsWith('/dev') ? '/dev' : ''}/navigation/horizontal-navigation`
}, {
label: 'Command Palette',
icon: 'i-heroicons-command-line',
to: '/navigation/command-palette'
}]
</script>

<template>
<UHorizontalNavigation :links="links" class="border-b border-gray-200 dark:border-gray-800" />
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<script setup>
const route = useRoute()
const links = [{
label: 'Horizontal Navigation',
to: `${route.path.startsWith('/dev') ? '/dev' : ''}/navigation/horizontal-navigation`
}, {
label: 'Command Palette',
to: '/navigation/command-palette'
}, {
label: 'Table',
to: '/data/table'
}]
</script>

<template>
<UHorizontalNavigation :links="links">
<template #default="{ link }">
<span class="group-hover:text-primary relative">{{ link.label }}</span>
</template>
</UHorizontalNavigation>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<script setup>
const route = useRoute()
const links = [
[{
label: 'Installation',
icon: 'i-heroicons-home',
to: '/getting-started/installation'
}, {
label: 'Horizontal Navigation',
icon: 'i-heroicons-chart-bar',
to: `${route.path.startsWith('/dev') ? '/dev' : ''}/navigation/horizontal-navigation`
}, {
label: 'Command Palette',
icon: 'i-heroicons-command-line',
to: '/navigation/command-palette'
}], [{
label: 'Examples',
icon: 'i-heroicons-light-bulb'
}, {
label: 'Help',
icon: 'i-heroicons-question-mark-circle'
}]
]
</script>

<template>
<UHorizontalNavigation :links="links" class="border-b border-gray-200 dark:border-gray-800" />
</template>
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<script setup>
const route = useRoute()
const links = [{
label: 'Profile',
avatar: {
Expand All @@ -12,7 +14,7 @@ const links = [{
}, {
label: 'Vertical Navigation',
icon: 'i-heroicons-chart-bar',
to: '/navigation/vertical-navigation'
to: `${route.path.startsWith('/dev') ? '/dev' : ''}/navigation/vertical-navigation`
}, {
label: 'Command Palette',
icon: 'i-heroicons-command-line',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,32 @@
<script setup>
const route = useRoute()
const links = [
[
{
label: 'Profile',
avatar: {
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
},
badge: 100
}, {
label: 'Installation',
icon: 'i-heroicons-home',
to: '/getting-started/installation'
}, {
label: 'Vertical Navigation',
icon: 'i-heroicons-chart-bar',
to: '/navigation/vertical-navigation'
}, {
label: 'Command Palette',
icon: 'i-heroicons-command-line',
to: '/navigation/command-palette'
}
],
[
{
label: 'Examples',
icon: 'i-heroicons-light-bulb',
to: '/getting-started/examples#verticalnavigation'
[{
label: 'Profile',
avatar: {
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
},
{
label: 'Help',
icon: 'i-heroicons-question-mark-circle',
to: '/getting-started/examples'
}
]
badge: 100
}, {
label: 'Installation',
icon: 'i-heroicons-home',
to: '/getting-started/installation'
}, {
label: 'Vertical Navigation',
icon: 'i-heroicons-chart-bar',
to: `${route.path.startsWith('/dev') ? '/dev' : ''}/navigation/vertical-navigation`
}, {
label: 'Command Palette',
icon: 'i-heroicons-command-line',
to: '/navigation/command-palette'
}], [{
label: 'Examples',
icon: 'i-heroicons-light-bulb'
}, {
label: 'Help',
icon: 'i-heroicons-question-mark-circle'
}]
]
</script>

Expand Down
2 changes: 1 addition & 1 deletion docs/content/5.navigation/1.vertical-navigation.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: VerticalNavigation
description: Display a vertical navigation.
description: Display a list of vertical links.
links:
- label: GitHub
icon: i-simple-icons-github
Expand Down
64 changes: 64 additions & 0 deletions docs/content/5.navigation/6.horizontal-navigation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
title: HorizontalNavigation
description: Display a list of horizontal links.
links:
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/navigation/HorizontalNavigation.vue
navigation:
badge: New
---

## Usage

Pass an array to the `links` prop of the HorizontalNavigation component. Each link can have the following properties:

- `label` - The label of the link.
- `labelClass` - The class of the link label.
- `icon` - The icon of the link.
- `iconClass` - The class of the link icon.
- `avatar` - The avatar of the link. You can pass all the props of the [Avatar](/elements/avatar) component.
- `badge` - A badge to display next to the label. You can pass all the props of the [Badge](/elements/badge) component.
- `click` - The click handler of the link.

You can also pass any property from the [NuxtLink](https://nuxt.com/docs/api/components/nuxt-link#props) component such as `to`, `exact`, etc.

:component-example{component="horizontal-navigation-example"}

## Sections

Group your navigation links into distinct sections, they will be displayed apart thanks to a `justify-between` flexbox layout.

You can do this by passing an array of arrays to the `links` prop of the HorizontalNavigation component.

:component-example{component="horizontal-navigation-example-sections"}

## Slots

You can use slots to customize links display.

### `default`

Use the `#default` slot to customize the link label. You will have access to the `link` and `isActive` properties in the slot scope.

:component-example{component="horizontal-navigation-example-default-slot"}

### `avatar`

Use the `#avatar` slot to customize the link avatar. You will have access to the `link` and `isActive` properties in the slot scope.

### `icon`

Use the `#icon` slot to customize the link icon. You will have access to the `link` and `isActive` properties in the slot scope.

### `badge`

Use the `#badge` slot to customize the link badge. You will have access to the `link` and `isActive` properties in the slot scope.

## Props

:component-props

## Config

:component-preset
109 changes: 109 additions & 0 deletions src/runtime/components/navigation/HorizontalNavigation.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<template>
<nav :class="ui.wrapper" v-bind="attrs">
<ul v-for="(section, sectionIndex) of sections" :key="`section${sectionIndex}`" :class="ui.container">
<li v-for="(link, index) of section" :key="`section${sectionIndex}-${index}`">
<ULink
v-slot="{ isActive }"
v-bind="getULinkProps(link)"
:class="[ui.base, ui.before, ui.after]"
:active-class="ui.active"
:inactive-class="ui.inactive"
@click="link.click"
@keyup.enter="$event.target.blur()"
>
<slot name="avatar" :link="link" :is-active="isActive">
<UAvatar
v-if="link.avatar"
v-bind="{ size: ui.avatar.size, ...link.avatar }"
:class="[ui.avatar.base]"
/>
</slot>
<slot name="icon" :link="link" :is-active="isActive">
<UIcon
v-if="link.icon"
:name="link.icon"
:class="twMerge(twJoin(ui.icon.base, isActive ? ui.icon.active : ui.icon.inactive), link.iconClass)"
/>
</slot>
<slot :link="link" :is-active="isActive">
<span v-if="link.label" :class="twMerge(ui.label, link.labelClass)">
<span v-if="isActive" class="sr-only">
Current page:
</span>
{{ link.label }}
</span>
</slot>
<slot name="badge" :link="link" :is-active="isActive">
<UBadge
v-if="link.badge"
v-bind="{
size: ui.badge.size,
color: ui.badge.color,
variant: ui.badge.variant,
...((typeof link.badge === 'string' || typeof link.badge === 'number') ? { label: link.badge } : link.badge)
}"
:class="ui.badge.base"
/>
</slot>
</ULink>
</li>
</ul>
</nav>
</template>

<script lang="ts">
import { toRef, defineComponent, computed } from 'vue'
import type { PropType } from 'vue'
import { twMerge, twJoin } from 'tailwind-merge'
import UIcon from '../elements/Icon.vue'
import UAvatar from '../elements/Avatar.vue'
import UBadge from '../elements/Badge.vue'
import ULink from '../elements/Link.vue'
import { useUI } from '../../composables/useUI'
import { mergeConfig, getULinkProps } from '../../utils'
import type { HorizontalNavigationLink, Strategy } from '../../types'
// @ts-expect-error
import appConfig from '#build/app.config'
import { horizontalNavigation } from '#ui/ui.config'
const config = mergeConfig<typeof horizontalNavigation>(appConfig.ui.strategy, appConfig.ui.horizontalNavigation, horizontalNavigation)
export default defineComponent({
components: {
UIcon,
UAvatar,
UBadge,
ULink
},
inheritAttrs: false,
props: {
links: {
type: Array as PropType<HorizontalNavigationLink[][] | HorizontalNavigationLink[]>,
default: () => []
},
class: {
type: [String, Object, Array] as PropType<any>,
default: () => ''
},
ui: {
type: Object as PropType<Partial<typeof config> & { strategy?: Strategy }>,
default: () => ({})
}
},
setup (props) {
const { ui, attrs } = useUI('horizontalNavigation', toRef(props, 'ui'), config, toRef(props, 'class'))
const sections = computed(() => (Array.isArray(props.links[0]) ? props.links : [props.links]) as HorizontalNavigationLink[][])
return {
// eslint-disable-next-line vue/no-dupe-keys
ui,
attrs,
sections,
getULinkProps,
twMerge,
twJoin
}
}
})
</script>
13 changes: 13 additions & 0 deletions src/runtime/types/horizontal-navigation.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Link } from './link'
import type { Avatar } from './avatar'
import type { Badge } from './badge'

export interface HorizontalNavigationLink extends Link {
label: string
labelClass?: string
icon?: string
iconClass?: string
avatar?: Avatar
click?: Function
badge?: string | number | Badge
}
1 change: 1 addition & 0 deletions src/runtime/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export * from './command-palette'
export * from './dropdown'
export * from './form-group'
export * from './form'
export * from './horizontal-navigation'
export * from './input'
export * from './kbd'
export * from './link'
Expand Down
1 change: 1 addition & 0 deletions src/runtime/ui.config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export { default as divider } from './layout/divider'

// Navigation
export { default as verticalNavigation } from './navigation/verticalNavigation'
export { default as horizontalNavigation } from './navigation/horizontalNavigation'
export { default as commandPalette } from './navigation/commandPalette'
export { default as pagination } from './navigation/pagination'
export { default as tabs } from './navigation/tabs'
Expand Down
Loading

1 comment on commit b8007ba

@vercel
Copy link

@vercel vercel bot commented on b8007ba Jan 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

ui – ./

ui-nuxt-js.vercel.app
ui.nuxt.com
ui-git-dev-nuxt-js.vercel.app

Please sign in to comment.