mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-02-03 13:47:55 +01:00
feat(HorizontalNavigation): new component (#1279)
This commit is contained in:
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
const links = [{
|
const links = [{
|
||||||
label: 'Profile',
|
label: 'Profile',
|
||||||
avatar: {
|
avatar: {
|
||||||
@@ -12,7 +14,7 @@ const links = [{
|
|||||||
}, {
|
}, {
|
||||||
label: 'Vertical Navigation',
|
label: 'Vertical Navigation',
|
||||||
icon: 'i-heroicons-chart-bar',
|
icon: 'i-heroicons-chart-bar',
|
||||||
to: '/navigation/vertical-navigation'
|
to: `${route.path.startsWith('/dev') ? '/dev' : ''}/navigation/vertical-navigation`
|
||||||
}, {
|
}, {
|
||||||
label: 'Command Palette',
|
label: 'Command Palette',
|
||||||
icon: 'i-heroicons-command-line',
|
icon: 'i-heroicons-command-line',
|
||||||
|
|||||||
@@ -1,38 +1,32 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
const links = [
|
const links = [
|
||||||
[
|
[{
|
||||||
{
|
label: 'Profile',
|
||||||
label: 'Profile',
|
avatar: {
|
||||||
avatar: {
|
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||||
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'
|
|
||||||
},
|
},
|
||||||
{
|
badge: 100
|
||||||
label: 'Help',
|
}, {
|
||||||
icon: 'i-heroicons-question-mark-circle',
|
label: 'Installation',
|
||||||
to: '/getting-started/examples'
|
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>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: VerticalNavigation
|
title: VerticalNavigation
|
||||||
description: Display a vertical navigation.
|
description: Display a list of vertical links.
|
||||||
links:
|
links:
|
||||||
- label: GitHub
|
- label: GitHub
|
||||||
icon: i-simple-icons-github
|
icon: i-simple-icons-github
|
||||||
|
|||||||
64
docs/content/5.navigation/6.horizontal-navigation.md
Normal file
64
docs/content/5.navigation/6.horizontal-navigation.md
Normal file
@@ -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
src/runtime/components/navigation/HorizontalNavigation.vue
Normal file
109
src/runtime/components/navigation/HorizontalNavigation.vue
Normal file
@@ -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
src/runtime/types/horizontal-navigation.d.ts
vendored
Normal file
13
src/runtime/types/horizontal-navigation.d.ts
vendored
Normal file
@@ -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
src/runtime/types/index.d.ts
vendored
1
src/runtime/types/index.d.ts
vendored
@@ -10,6 +10,7 @@ export * from './command-palette'
|
|||||||
export * from './dropdown'
|
export * from './dropdown'
|
||||||
export * from './form-group'
|
export * from './form-group'
|
||||||
export * from './form'
|
export * from './form'
|
||||||
|
export * from './horizontal-navigation'
|
||||||
export * from './input'
|
export * from './input'
|
||||||
export * from './kbd'
|
export * from './kbd'
|
||||||
export * from './link'
|
export * from './link'
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export { default as divider } from './layout/divider'
|
|||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
export { default as verticalNavigation } from './navigation/verticalNavigation'
|
export { default as verticalNavigation } from './navigation/verticalNavigation'
|
||||||
|
export { default as horizontalNavigation } from './navigation/horizontalNavigation'
|
||||||
export { default as commandPalette } from './navigation/commandPalette'
|
export { default as commandPalette } from './navigation/commandPalette'
|
||||||
export { default as pagination } from './navigation/pagination'
|
export { default as pagination } from './navigation/pagination'
|
||||||
export { default as tabs } from './navigation/tabs'
|
export { default as tabs } from './navigation/tabs'
|
||||||
|
|||||||
25
src/runtime/ui.config/navigation/horizontalNavigation.ts
Normal file
25
src/runtime/ui.config/navigation/horizontalNavigation.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
export default {
|
||||||
|
wrapper: 'relative w-full flex items-center justify-between',
|
||||||
|
container: 'flex items-center',
|
||||||
|
base: 'group relative w-full flex items-center gap-1.5 px-2.5 py-3.5 rounded-md font-medium text-sm focus:outline-none focus-visible:outline-none dark:focus-visible:outline-none focus-visible:ring-inset focus-visible:ring-2 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400 disabled:cursor-not-allowed disabled:opacity-75',
|
||||||
|
before: 'before:absolute before:inset-x-0 before:inset-y-2 before:inset-px before:rounded-md hover:before:bg-gray-50 dark:hover:before:bg-gray-800/50',
|
||||||
|
after: 'after:absolute after:bottom-0 after:inset-x-2.5 after:block after:h-[2px] after:mt-2',
|
||||||
|
active: 'text-gray-900 dark:text-white after:bg-primary-500 dark:after:bg-primary-400 after:rounded-full',
|
||||||
|
inactive: 'text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white',
|
||||||
|
label: 'truncate relative',
|
||||||
|
icon: {
|
||||||
|
base: 'flex-shrink-0 w-5 h-5',
|
||||||
|
active: 'text-gray-700 dark:text-gray-200',
|
||||||
|
inactive: 'text-gray-400 dark:text-gray-500 group-hover:text-gray-700 dark:group-hover:text-gray-200'
|
||||||
|
},
|
||||||
|
avatar: {
|
||||||
|
base: 'flex-shrink-0',
|
||||||
|
size: '2xs' as const
|
||||||
|
},
|
||||||
|
badge: {
|
||||||
|
base: 'flex-shrink-0 ml-auto relative rounded',
|
||||||
|
color: 'gray' as const,
|
||||||
|
variant: 'solid' as const,
|
||||||
|
size: 'xs' as const
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user