add nova
This commit is contained in:
48
nova/resources/js/components/Menu/Breadcrumbs.vue
Normal file
48
nova/resources/js/components/Menu/Breadcrumbs.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<nav
|
||||
v-if="hasItems"
|
||||
class="text-gray-500 font-semibold"
|
||||
aria-label="breadcrumb"
|
||||
dusk="breadcrumbs"
|
||||
>
|
||||
<ol>
|
||||
<li
|
||||
v-for="(item, index) in breadcrumbs"
|
||||
class="inline-block"
|
||||
v-bind="{
|
||||
'aria-current': index === breadcrumbs.length - 1 ? 'page' : null,
|
||||
}"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<Link
|
||||
:href="$url(item.path)"
|
||||
v-if="item.path !== null && index < breadcrumbs.length - 1"
|
||||
class="link-default"
|
||||
>
|
||||
{{ item.name }}
|
||||
</Link>
|
||||
<span v-else>{{ item.name }}</span>
|
||||
<Icon
|
||||
type="chevron-right"
|
||||
v-if="index < breadcrumbs.length - 1"
|
||||
class="w-4 h-4 mx-2 text-gray-300 dark:text-gray-700"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
...mapGetters(['breadcrumbs']),
|
||||
|
||||
hasItems() {
|
||||
return this.breadcrumbs.length > 0
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
31
nova/resources/js/components/Menu/MainMenu.vue
Normal file
31
nova/resources/js/components/Menu/MainMenu.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="hasItems"
|
||||
class="sidebar-menu space-y-6"
|
||||
dusk="sidebar-menu"
|
||||
role="navigation"
|
||||
>
|
||||
<component
|
||||
:key="item.key"
|
||||
:is="item.component"
|
||||
v-for="(item, index) in mainMenu"
|
||||
:item="item"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'MainMenu',
|
||||
|
||||
computed: {
|
||||
...mapGetters(['mainMenu']),
|
||||
|
||||
hasItems() {
|
||||
return this.mainMenu.length > 0
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
73
nova/resources/js/components/Menu/MenuGroup.vue
Normal file
73
nova/resources/js/components/Menu/MenuGroup.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<div v-if="item.items.length > 0">
|
||||
<h4
|
||||
@click.prevent="handleClick"
|
||||
class="flex items-center px-1 py-1 rounded text-left text-gray-500"
|
||||
:class="{
|
||||
'cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-800':
|
||||
displayAsButton,
|
||||
'font-bold text-primary-500 dark:text-primary-500': item.active,
|
||||
}"
|
||||
>
|
||||
<span class="inline-block shrink-0 w-6 h-6" />
|
||||
|
||||
<span
|
||||
class="flex-1 flex items-center w-full tracking-wide uppercase font-bold text-left text-xs px-3 py-1"
|
||||
>
|
||||
{{ item.name }}
|
||||
</span>
|
||||
|
||||
<span
|
||||
v-if="item.collapsable"
|
||||
class="inline-flex items-center justify-center shrink-0 w-6 h-6"
|
||||
>
|
||||
<CollapseButton :collapsed="collapsed" :to="item.path" />
|
||||
</span>
|
||||
</h4>
|
||||
|
||||
<div v-if="!collapsed">
|
||||
<component
|
||||
:key="item.name"
|
||||
v-for="item in item.items"
|
||||
:is="item.component"
|
||||
:item="item"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Collapsable } from '@/mixins'
|
||||
|
||||
export default {
|
||||
mixins: [Collapsable],
|
||||
|
||||
props: ['item'],
|
||||
|
||||
methods: {
|
||||
handleClick() {
|
||||
if (this.item.collapsable) {
|
||||
this.toggleCollapse()
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
component() {
|
||||
if (this.item.items.length > 0) {
|
||||
return 'div'
|
||||
}
|
||||
|
||||
return 'h3'
|
||||
},
|
||||
|
||||
displayAsButton() {
|
||||
return this.item.items.length > 0 && this.item.collapsable
|
||||
},
|
||||
|
||||
collapsedByDefault() {
|
||||
return this.item?.collapsedByDefault ?? false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
89
nova/resources/js/components/Menu/MenuItem.vue
Normal file
89
nova/resources/js/components/Menu/MenuItem.vue
Normal file
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<div>
|
||||
<component
|
||||
:is="component"
|
||||
v-bind="linkAttributes"
|
||||
class="w-full flex min-h-8 px-1 py-1 rounded text-left text-gray-500 dark:text-gray-500 focus:outline-none focus:ring focus:ring-primary-200 dark:focus:ring-gray-600 cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-800"
|
||||
:data-active-link="item.active"
|
||||
:class="{
|
||||
'font-bold text-primary-500 dark:text-primary-500': item.active,
|
||||
}"
|
||||
@click="handleClick"
|
||||
>
|
||||
<span class="inline-block shrink-0 w-6 h-6" />
|
||||
<span class="flex-1 flex items-center w-full px-3 text-sm">
|
||||
{{ item.name }}
|
||||
</span>
|
||||
|
||||
<span class="inline-block h-6 shrink-0">
|
||||
<Badge v-if="item.badge" :extra-classes="item.badge.typeClass">
|
||||
{{ item.badge.value }}
|
||||
</Badge>
|
||||
</span>
|
||||
</component>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import identity from 'lodash/identity'
|
||||
import isNull from 'lodash/isNull'
|
||||
import omitBy from 'lodash/omitBy'
|
||||
import pickBy from 'lodash/pickBy'
|
||||
import { mapGetters, mapMutations } from 'vuex'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapMutations(['toggleMainMenu']),
|
||||
|
||||
handleClick() {
|
||||
if (this.mainMenuShown) {
|
||||
this.toggleMainMenu()
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(['mainMenuShown']),
|
||||
|
||||
requestMethod() {
|
||||
return this.item.method || 'GET'
|
||||
},
|
||||
|
||||
component() {
|
||||
if (this.requestMethod !== 'GET') {
|
||||
return 'FormButton'
|
||||
} else if (this.item.external !== true) {
|
||||
return 'Link'
|
||||
}
|
||||
|
||||
return 'a'
|
||||
},
|
||||
|
||||
linkAttributes() {
|
||||
let method = this.requestMethod
|
||||
|
||||
return pickBy(
|
||||
omitBy(
|
||||
{
|
||||
href: this.item.path,
|
||||
method: method !== 'GET' ? method : null,
|
||||
headers: this.item.headers || null,
|
||||
data: this.item.data || null,
|
||||
rel: this.component === 'a' ? 'noreferrer noopener' : null,
|
||||
target: this.item.target || null,
|
||||
},
|
||||
isNull
|
||||
),
|
||||
identity
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
11
nova/resources/js/components/Menu/MenuList.vue
Normal file
11
nova/resources/js/components/Menu/MenuList.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div class="sidebar-list">
|
||||
<menu-item :key="item.key" v-for="item in item.items" :item="item" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['item'],
|
||||
}
|
||||
</script>
|
||||
99
nova/resources/js/components/Menu/MenuSection.vue
Normal file
99
nova/resources/js/components/Menu/MenuSection.vue
Normal file
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<div class="relative" v-if="item.path || item.items.length > 0">
|
||||
<component
|
||||
:is="component"
|
||||
:href="item.path ?? null"
|
||||
@click.prevent="handleClick"
|
||||
:tabindex="displayAsButton ? 0 : null"
|
||||
class="w-full flex items-start px-1 py-1 rounded text-left text-gray-500 dark:text-gray-500 focus:outline-none focus:ring focus:ring-primary-200 dark:focus:ring-gray-600"
|
||||
:class="{
|
||||
'cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-800':
|
||||
displayAsButton,
|
||||
'font-bold text-primary-500 dark:text-primary-500': item.active,
|
||||
}"
|
||||
>
|
||||
<span class="inline-block shrink-0 w-6 h-6">
|
||||
<component
|
||||
:is="`heroicons-outline-${item.icon}`"
|
||||
height="24"
|
||||
width="24"
|
||||
/>
|
||||
</span>
|
||||
|
||||
<span class="flex-1 flex items-center w-full px-3 text-base">
|
||||
{{ item.name }}
|
||||
</span>
|
||||
|
||||
<span class="inline-block h-6 shrink-0">
|
||||
<Badge v-if="item.badge" :extra-classes="item.badge.typeClass">
|
||||
{{ item.badge.value }}
|
||||
</Badge>
|
||||
</span>
|
||||
|
||||
<span
|
||||
v-if="item.collapsable"
|
||||
class="inline-flex items-center justify-center shrink-0 w-6 h-6"
|
||||
>
|
||||
<CollapseButton :collapsed="collapsed" :to="item.path" />
|
||||
</span>
|
||||
</component>
|
||||
|
||||
<div v-if="item.items.length > 0 && !collapsed" class="mt-1 flex flex-col">
|
||||
<component
|
||||
:is="item.component"
|
||||
v-for="item in item.items"
|
||||
:key="item.name"
|
||||
:item="item"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Collapsable } from '@/mixins'
|
||||
import { mapGetters, mapMutations } from 'vuex'
|
||||
|
||||
export default {
|
||||
mixins: [Collapsable],
|
||||
|
||||
props: ['item'],
|
||||
|
||||
methods: {
|
||||
...mapMutations(['toggleMainMenu']),
|
||||
|
||||
handleClick() {
|
||||
if (this.item.collapsable) {
|
||||
this.toggleCollapse()
|
||||
}
|
||||
|
||||
if (this.mainMenuShown && this.component !== 'button') {
|
||||
this.toggleMainMenu()
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(['mainMenuShown']),
|
||||
|
||||
component() {
|
||||
if (this.item.path) {
|
||||
return 'Link'
|
||||
}
|
||||
|
||||
if (this.item.items.length > 0 && this.item.collapsable) {
|
||||
return 'button'
|
||||
}
|
||||
|
||||
return 'h3'
|
||||
},
|
||||
|
||||
displayAsButton() {
|
||||
return ['Link', 'button'].includes(this.component)
|
||||
},
|
||||
|
||||
collapsedByDefault() {
|
||||
return this.item?.collapsedByDefault ?? false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user