Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(HorizontalNavigation): new component #1279

Merged
merged 2 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading