PMCORE-3833: Tooltip now showing in collapsed menu icons
Fixed observations Dropdown tooltip now showing Solved issues with multiple tooltips
This commit is contained in:
@@ -22,10 +22,13 @@
|
||||
<script>
|
||||
import api from "./../../api/index";
|
||||
import CustomSidebarMenuItem from "./CustomSidebarMenuItem";
|
||||
import SidebarMenu from '../menu/sidebar/components/SidebarMenu.vue'
|
||||
export default {
|
||||
name: "CustomSidebar",
|
||||
props: ["menu"],
|
||||
|
||||
components: {
|
||||
SidebarMenu
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
collapsed: false,
|
||||
|
||||
@@ -25,11 +25,14 @@
|
||||
:attributes="item.attributes"
|
||||
@click.native="clickEvent"
|
||||
>
|
||||
<custom-sidebar-menu-icon
|
||||
<custom-tooltip
|
||||
v-if="item.icon && !isMobileItem && item.specialType !='header'"
|
||||
:icon="item.icon"
|
||||
v-bind:style="setIconColor"
|
||||
/>
|
||||
:data="item"
|
||||
:collapsed="isCollapsed"
|
||||
:level="level"
|
||||
:customStyle="setIconColor"
|
||||
ref="tooltip"
|
||||
></custom-tooltip>
|
||||
<transition name="fade-animation" :appear="isMobileItem">
|
||||
<template
|
||||
v-if="
|
||||
@@ -39,19 +42,14 @@
|
||||
"
|
||||
>
|
||||
<span :class="item.specialType != 'header'?'vsm--title': 'vsm--header vsm--title--header'">
|
||||
<template v-if="!verifyTaskMetrics">
|
||||
<custom-tooltip
|
||||
:data="item"
|
||||
ref="tooltip"
|
||||
></custom-tooltip>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-if="verifyTaskMetrics">
|
||||
<span>
|
||||
{{ item.title }}
|
||||
</span>
|
||||
</template>
|
||||
<span v-if="item.sortable">
|
||||
<span v-if="item.sortable" :style="item.specialType != 'header'? 'clear: right' : 'clear: none'">
|
||||
<b-icon
|
||||
class="vp-icon"
|
||||
:id="`gear-${item.id}`"
|
||||
:icon="item.sortIcon"
|
||||
@click="onClickSortSettings"
|
||||
@@ -608,4 +606,8 @@ export default {
|
||||
margin-right: 10px;
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.vp-icon {
|
||||
margin-left: 5%;
|
||||
}
|
||||
</style>
|
||||
272
resources/assets/js/components/menu/sidebar/components/SidebarMenu.vue
Executable file
272
resources/assets/js/components/menu/sidebar/components/SidebarMenu.vue
Executable file
@@ -0,0 +1,272 @@
|
||||
<template>
|
||||
<div
|
||||
class="v-sidebar-menu"
|
||||
:class="sidebarClass"
|
||||
:style="[{'max-width': sidebarWidth}]"
|
||||
@mouseleave="onMouseLeave"
|
||||
@mouseenter="onMouseEnter"
|
||||
>
|
||||
<slot name="header" />
|
||||
<div
|
||||
class="vsm--scroll-wrapper"
|
||||
:style="isCollapsed && [rtl ? {'margin-left': '-17px'} : {'margin-right': '-17px'}]"
|
||||
>
|
||||
<div
|
||||
class="vsm--list"
|
||||
:style="isCollapsed && {'width': widthCollapsed}"
|
||||
>
|
||||
<sidebar-menu-item
|
||||
v-for="(item, index) in menu"
|
||||
:key="index"
|
||||
:item="item"
|
||||
:is-collapsed="isCollapsed"
|
||||
:active-show="activeShow"
|
||||
:show-one-child="showOneChild"
|
||||
:show-child="showChild"
|
||||
:rtl="rtl"
|
||||
:mobile-item="mobileItem"
|
||||
:disable-hover="disableHover"
|
||||
@set-mobile-item="setMobileItem"
|
||||
@unset-mobile-item="unsetMobileItem"
|
||||
>
|
||||
<slot
|
||||
slot="dropdown-icon"
|
||||
name="dropdown-icon"
|
||||
/>
|
||||
</sidebar-menu-item>
|
||||
</div>
|
||||
<div
|
||||
v-if="isCollapsed"
|
||||
class="vsm--mobile-item"
|
||||
:style="mobileItemStyle.item"
|
||||
>
|
||||
<sidebar-menu-item
|
||||
v-if="mobileItem"
|
||||
:item="mobileItem"
|
||||
:is-mobile-item="true"
|
||||
:mobile-item-style="mobileItemStyle"
|
||||
:is-collapsed="isCollapsed"
|
||||
:show-child="showChild"
|
||||
:rtl="rtl"
|
||||
:disable-hover="disableHover"
|
||||
>
|
||||
<slot
|
||||
slot="dropdown-icon"
|
||||
name="dropdown-icon"
|
||||
/>
|
||||
</sidebar-menu-item>
|
||||
<transition name="slide-animation">
|
||||
<div
|
||||
v-if="mobileItem"
|
||||
class="vsm--mobile-bg"
|
||||
:style="mobileItemStyle.background"
|
||||
/>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
<slot name="footer" />
|
||||
<button
|
||||
v-if="!hideToggle"
|
||||
class="vsm--toggle-btn"
|
||||
:class="{'vsm--toggle-btn_slot' : $slots['toggle-icon']}"
|
||||
@click="onToggleClick"
|
||||
>
|
||||
<slot name="toggle-icon" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SidebarMenuItem from './SidebarMenuItem.vue'
|
||||
|
||||
export default {
|
||||
name: 'SidebarMenu',
|
||||
components: {
|
||||
SidebarMenuItem
|
||||
},
|
||||
props: {
|
||||
menu: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
collapsed: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '350px'
|
||||
},
|
||||
widthCollapsed: {
|
||||
type: String,
|
||||
default: '50px'
|
||||
},
|
||||
showChild: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
theme: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
showOneChild: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
rtl: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
relative: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
hideToggle: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
disableHover: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isCollapsed: this.collapsed,
|
||||
mobileItem: null,
|
||||
mobileItemPos: 0,
|
||||
mobileItemHeight: 0,
|
||||
mobileItemTimeout: null,
|
||||
activeShow: null,
|
||||
parentHeight: 0,
|
||||
parentWidth: 0,
|
||||
parentOffsetTop: 0,
|
||||
parentOffsetLeft: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
sidebarWidth () {
|
||||
return this.isCollapsed ? this.widthCollapsed : this.width
|
||||
},
|
||||
sidebarClass () {
|
||||
return [
|
||||
!this.isCollapsed ? 'vsm_expanded' : 'vsm_collapsed',
|
||||
this.theme ? `vsm_${this.theme}` : '',
|
||||
this.rtl ? 'vsm_rtl' : '',
|
||||
this.relative ? 'vsm_relative' : ''
|
||||
]
|
||||
},
|
||||
mobileItemStyle () {
|
||||
return {
|
||||
item: [
|
||||
{ 'position': 'absolute' },
|
||||
{ 'top': `${this.mobileItemPos}px` },
|
||||
this.rtl ? { 'right': '0px' } : { 'left': '0px' },
|
||||
this.rtl ? { 'padding-right': this.sidebarWidth } : { 'padding-left': this.sidebarWidth },
|
||||
this.rtl && { 'direction': 'rtl' },
|
||||
{ 'z-index': 0 },
|
||||
{ 'width': `${this.parentWidth - this.parentOffsetLeft}px` },
|
||||
{ 'max-width': this.width }
|
||||
],
|
||||
dropdown: [
|
||||
{ 'position': 'absolute' },
|
||||
{ 'top': `${this.mobileItemHeight}px` },
|
||||
{ 'width': '100%' },
|
||||
{ 'max-height': `${this.parentHeight - (this.mobileItemPos + this.mobileItemHeight) - this.parentOffsetTop}px` },
|
||||
{ 'overflow-y': 'auto' }
|
||||
],
|
||||
background: [
|
||||
{ 'position': 'absolute' },
|
||||
{ 'top': '0px' },
|
||||
{ 'left': '0px' },
|
||||
{ 'right': '0px' },
|
||||
{ 'width': '100%' },
|
||||
{ 'height': `${this.mobileItemHeight}px` },
|
||||
{ 'z-index': -1 }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
collapsed (val) {
|
||||
if (this.isCollapsed === this.collapsed) return
|
||||
this.isCollapsed = val
|
||||
this.mobileItem = null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onMouseLeave () {
|
||||
this.unsetMobileItem(false, 300)
|
||||
},
|
||||
onMouseEnter () {
|
||||
if (this.isCollapsed) {
|
||||
if (this.mobileItemTimeout) clearTimeout(this.mobileItemTimeout)
|
||||
}
|
||||
},
|
||||
onToggleClick () {
|
||||
this.isCollapsed = !this.isCollapsed
|
||||
this.mobileItem = null
|
||||
this.$emit('toggle-collapse', this.isCollapsed)
|
||||
},
|
||||
onActiveShow (item) {
|
||||
this.activeShow = item
|
||||
},
|
||||
onItemClick (event, item, node) {
|
||||
this.$emit('item-click', event, item, node)
|
||||
},
|
||||
setMobileItem ({ item, itemEl }) {
|
||||
if (this.mobileItem === item) return
|
||||
const sidebarTop = this.$el.getBoundingClientRect().top
|
||||
const itemLinkEl = itemEl.children[0]
|
||||
const { top, height } = itemLinkEl.getBoundingClientRect()
|
||||
|
||||
let positionTop = top - sidebarTop
|
||||
this.initParentOffsets()
|
||||
this.mobileItem = item
|
||||
this.mobileItemPos = positionTop
|
||||
this.mobileItemHeight = height
|
||||
},
|
||||
unsetMobileItem (immediate, delay = 800) {
|
||||
if (!this.mobileItem) return
|
||||
if (this.mobileItemTimeout) clearTimeout(this.mobileItemTimeout)
|
||||
if (immediate) {
|
||||
this.mobileItem = null
|
||||
return
|
||||
}
|
||||
this.mobileItemTimeout = setTimeout(() => {
|
||||
this.mobileItem = null
|
||||
}, delay)
|
||||
},
|
||||
initParentOffsets () {
|
||||
let { top: sidebarTop, left: sidebarLeft, right: sidebarRight } = this.$el.getBoundingClientRect()
|
||||
let parent = this.relative ? this.$el.parentElement : document.documentElement
|
||||
this.parentHeight = parent.clientHeight
|
||||
this.parentWidth = parent.clientWidth
|
||||
if (this.relative) {
|
||||
let { top: parentTop, left: parentLeft } = parent.getBoundingClientRect()
|
||||
this.parentOffsetTop = sidebarTop - (parentTop + parent.clientTop)
|
||||
this.parentOffsetLeft = this.rtl ? this.parentWidth - sidebarRight + (parentLeft + parent.clientLeft) : sidebarLeft - (parentLeft + parent.clientLeft)
|
||||
} else {
|
||||
this.parentOffsetTop = sidebarTop
|
||||
this.parentOffsetLeft = this.rtl ? this.parentWidth - sidebarRight : sidebarLeft
|
||||
}
|
||||
},
|
||||
onItemUpdate (newItem, item) {
|
||||
if (item === this.mobileItem) {
|
||||
this.mobileItem = newItem
|
||||
}
|
||||
if (item === this.activeShow) {
|
||||
this.activeShow = newItem
|
||||
}
|
||||
}
|
||||
},
|
||||
provide () {
|
||||
return {
|
||||
emitActiveShow: this.onActiveShow,
|
||||
emitItemClick: this.onItemClick,
|
||||
emitItemUpdate: this.onItemUpdate
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
22
resources/assets/js/components/menu/sidebar/components/SidebarMenuBadge.vue
Executable file
22
resources/assets/js/components/menu/sidebar/components/SidebarMenuBadge.vue
Executable file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<component
|
||||
:is="badge.element ? badge.element : 'span'"
|
||||
class="vsm--badge"
|
||||
:class="badge.class"
|
||||
v-bind="badge.attributes"
|
||||
>
|
||||
{{ badge.text }}
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SidebarMenuBadge',
|
||||
props: {
|
||||
badge: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
22
resources/assets/js/components/menu/sidebar/components/SidebarMenuIcon.vue
Executable file
22
resources/assets/js/components/menu/sidebar/components/SidebarMenuIcon.vue
Executable file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<component
|
||||
:is="icon.element ? icon.element : 'i'"
|
||||
class="vsm--icon"
|
||||
:class="typeof icon === 'string' || (icon instanceof String) ? icon : icon.class"
|
||||
v-bind="icon.attributes"
|
||||
>
|
||||
{{ icon.text }}
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SidebarMenuIcon',
|
||||
props: {
|
||||
icon: {
|
||||
type: [String, Object],
|
||||
default: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
375
resources/assets/js/components/menu/sidebar/components/SidebarMenuItem.vue
Executable file
375
resources/assets/js/components/menu/sidebar/components/SidebarMenuItem.vue
Executable file
@@ -0,0 +1,375 @@
|
||||
<template>
|
||||
<component
|
||||
:is="item.component"
|
||||
v-if="item.component && !isItemHidden"
|
||||
v-bind="item.props"
|
||||
/>
|
||||
<div
|
||||
v-else-if="item.header && !isItemHidden"
|
||||
class="vsm--header"
|
||||
:class="item.class"
|
||||
v-bind="item.attributes"
|
||||
>
|
||||
{{ item.title }}
|
||||
</div>
|
||||
<div
|
||||
v-else-if="!isItemHidden"
|
||||
class="vsm--item"
|
||||
:class="[{'vsm--item_open' : show}]"
|
||||
@mouseover="mouseOverEvent"
|
||||
@mouseout="mouseOutEvent"
|
||||
>
|
||||
<sidebar-menu-link
|
||||
:item="item"
|
||||
:class="itemLinkClass"
|
||||
v-bind="itemLinkAttributes"
|
||||
@click.native="clickEvent"
|
||||
>
|
||||
<sidebar-menu-icon
|
||||
v-if="item.icon && !isMobileItem && level < 2"
|
||||
:icon="item.icon"
|
||||
/>
|
||||
<transition
|
||||
v-if="level < 2"
|
||||
name="fade-animation"
|
||||
:appear="isMobileItem"
|
||||
>
|
||||
<template v-if="(isCollapsed && !isFirstLevel) || !isCollapsed || isMobileItem">
|
||||
<span class="vsm--title">{{ item.title }}</span>
|
||||
</template>
|
||||
</transition>
|
||||
<custom-tooltip
|
||||
v-else-if="level >= 2"
|
||||
class="collapseDrop"
|
||||
:data="item"
|
||||
:collapsed="isCollapsed"
|
||||
:level="level"
|
||||
:mobile="isMobileItem"
|
||||
:customStyle="{color: item.color}"
|
||||
ref="tooltip"
|
||||
></custom-tooltip>
|
||||
<template v-if="(isCollapsed && !isFirstLevel) || !isCollapsed || isMobileItem">
|
||||
<sidebar-menu-badge
|
||||
v-if="item.badge"
|
||||
:badge="item.badge"
|
||||
/>
|
||||
<div
|
||||
v-if="itemHasChild"
|
||||
class="vsm--arrow"
|
||||
:class="[{'vsm--arrow_open' : show}, {'vsm--arrow_slot' : $slots['dropdown-icon']}]"
|
||||
>
|
||||
<slot name="dropdown-icon" />
|
||||
</div>
|
||||
</template>
|
||||
</sidebar-menu-link>
|
||||
<template v-if="itemHasChild">
|
||||
<template v-if="(isCollapsed && !isFirstLevel) || !isCollapsed || isMobileItem">
|
||||
<transition
|
||||
:appear="isMobileItem"
|
||||
name="expand"
|
||||
@enter="expandEnter"
|
||||
@afterEnter="expandAfterEnter"
|
||||
@beforeLeave="expandBeforeLeave"
|
||||
>
|
||||
<div
|
||||
v-if="show"
|
||||
class="vsm--dropdown"
|
||||
:class="isMobileItem && 'vsm--dropdown_mobile-item'"
|
||||
:style="isMobileItem && mobileItemStyle.dropdown"
|
||||
>
|
||||
<div class="vsm--list">
|
||||
<sidebar-menu-item
|
||||
v-for="(subItem, index) in item.child"
|
||||
:key="index"
|
||||
:item="subItem"
|
||||
:level="level+1"
|
||||
:show-child="showChild"
|
||||
:rtl="rtl"
|
||||
:is-collapsed="isCollapsed"
|
||||
>
|
||||
<slot
|
||||
slot="dropdown-icon"
|
||||
name="dropdown-icon"
|
||||
/>
|
||||
</sidebar-menu-item>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import pathToRegexp from 'path-to-regexp'
|
||||
import SidebarMenuLink from './SidebarMenuLink.vue'
|
||||
import SidebarMenuIcon from './SidebarMenuIcon.vue'
|
||||
import SidebarMenuBadge from './SidebarMenuBadge.vue'
|
||||
import CustomTooltip from '../../../utils/CustomTooltip.vue'
|
||||
|
||||
export default {
|
||||
name: 'SidebarMenuItem',
|
||||
components: {
|
||||
SidebarMenuLink,
|
||||
SidebarMenuIcon,
|
||||
SidebarMenuBadge,
|
||||
CustomTooltip
|
||||
},
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
level: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
isCollapsed: {
|
||||
type: Boolean
|
||||
},
|
||||
isMobileItem: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
mobileItem: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
activeShow: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
showChild: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showOneChild: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
rtl: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
disableHover: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
mobileItemStyle: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
active: false,
|
||||
exactActive: false,
|
||||
itemShow: false,
|
||||
itemHover: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isFirstLevel () {
|
||||
return this.level === 1
|
||||
},
|
||||
show: {
|
||||
get () {
|
||||
if (!this.itemHasChild) return false
|
||||
if (this.showChild || this.isMobileItem) return true
|
||||
return this.itemShow
|
||||
},
|
||||
set (show) {
|
||||
if (this.showOneChild) {
|
||||
show ? this.emitActiveShow(this.item) : this.emitActiveShow(null)
|
||||
}
|
||||
this.itemShow = show
|
||||
}
|
||||
},
|
||||
itemLinkClass () {
|
||||
return [
|
||||
'vsm--link',
|
||||
!this.isMobileItem ? `vsm--link_level-${this.level}` : '',
|
||||
{ 'vsm--link_mobile-item': this.isMobileItem },
|
||||
{ 'vsm--link_hover': this.hover },
|
||||
{ 'vsm--link_active': this.active },
|
||||
{ 'vsm--link_exact-active': this.exactActive },
|
||||
{ 'vsm--link_disabled': this.item.disabled },
|
||||
this.item.class
|
||||
]
|
||||
},
|
||||
itemLinkAttributes () {
|
||||
const target = this.item.external ? '_blank' : '_self'
|
||||
const tabindex = this.item.disabled ? -1 : null
|
||||
|
||||
return {
|
||||
target,
|
||||
tabindex,
|
||||
...this.item.attributes
|
||||
}
|
||||
},
|
||||
isItemHidden () {
|
||||
if (this.isCollapsed) {
|
||||
if (this.item.hidden && this.item.hiddenOnCollapse === undefined) {
|
||||
return true
|
||||
} else {
|
||||
return this.item.hiddenOnCollapse === true
|
||||
}
|
||||
} else {
|
||||
return this.item.hidden === true
|
||||
}
|
||||
},
|
||||
hover () {
|
||||
if (this.isCollapsed && this.isFirstLevel) {
|
||||
return this.item === this.mobileItem
|
||||
}
|
||||
return this.itemHover
|
||||
},
|
||||
itemHasChild () {
|
||||
return !!(this.item.child && this.item.child.length > 0)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$route () {
|
||||
setTimeout(() => {
|
||||
if (this.item.header || this.item.component) return
|
||||
this.initState()
|
||||
}, 1)
|
||||
},
|
||||
item (newItem, item) {
|
||||
this.emitItemUpdate(newItem, item)
|
||||
},
|
||||
activeShow () {
|
||||
this.itemShow = this.item === this.activeShow
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if (this.item.header || this.item.component) return
|
||||
this.initState()
|
||||
},
|
||||
mounted () {
|
||||
if (!this.$router) {
|
||||
window.addEventListener('hashchange', this.initState)
|
||||
}
|
||||
},
|
||||
destroyed () {
|
||||
if (!this.$router) {
|
||||
window.removeEventListener('hashchange', this.initState)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isLinkActive (item) {
|
||||
return this.matchRoute(item) || this.isChildActive(item.child) || this.isAliasActive(item)
|
||||
},
|
||||
isLinkExactActive (item) {
|
||||
return this.matchExactRoute(item.href)
|
||||
},
|
||||
isChildActive (child) {
|
||||
if (!child) return false
|
||||
return child.some(item => {
|
||||
return this.isLinkActive(item)
|
||||
})
|
||||
},
|
||||
isAliasActive (item) {
|
||||
if (item.alias) {
|
||||
const current = this.$router ? this.$route.fullPath : window.location.pathname + window.location.search + window.location.hash
|
||||
if (Array.isArray(item.alias)) {
|
||||
return item.alias.some(alias => {
|
||||
return pathToRegexp(alias).test(current)
|
||||
})
|
||||
} else {
|
||||
return pathToRegexp(item.alias).test(current)
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
matchRoute ({ href, exactPath }) {
|
||||
if (!href) return false
|
||||
if (this.$router) {
|
||||
const { route } = this.$router.resolve(href)
|
||||
return exactPath ? route.path === this.$route.path : this.matchExactRoute(href)
|
||||
} else {
|
||||
return exactPath ? href === window.location.pathname : this.matchExactRoute(href)
|
||||
}
|
||||
},
|
||||
matchExactRoute (href) {
|
||||
if (!href) return false
|
||||
if (this.$router) {
|
||||
const { route } = this.$router.resolve(href)
|
||||
return route.fullPath === this.$route.fullPath
|
||||
} else {
|
||||
return href === window.location.pathname + window.location.search + window.location.hash
|
||||
}
|
||||
},
|
||||
clickEvent (event) {
|
||||
if (this.item.disabled) return
|
||||
if (!this.item.href) {
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
this.emitItemClick(event, this.item, this)
|
||||
|
||||
this.emitMobileItem(event, event.currentTarget.offsetParent)
|
||||
|
||||
if (!this.itemHasChild || this.showChild || this.isMobileItem) return
|
||||
if (!this.item.href || this.exactActive) {
|
||||
this.show = !this.show
|
||||
}
|
||||
},
|
||||
emitMobileItem (event, itemEl) {
|
||||
if (this.hover) return
|
||||
if (!this.isCollapsed || !this.isFirstLevel || this.isMobileItem) return
|
||||
this.$emit('unset-mobile-item', true)
|
||||
setTimeout(() => {
|
||||
if (this.mobileItem !== this.item) {
|
||||
this.$emit('set-mobile-item', { item: this.item, itemEl })
|
||||
}
|
||||
if (event.type === 'click' && !this.itemHasChild) {
|
||||
this.$emit('unset-mobile-item', false)
|
||||
}
|
||||
}, 0)
|
||||
},
|
||||
initState () {
|
||||
this.initActiveState()
|
||||
this.initShowState()
|
||||
},
|
||||
initActiveState () {
|
||||
this.active = this.isLinkActive(this.item)
|
||||
this.exactActive = this.isLinkExactActive(this.item)
|
||||
},
|
||||
initShowState () {
|
||||
if (!this.itemHasChild || this.showChild) return
|
||||
if ((this.showOneChild && this.active && !this.show) || (this.active && !this.show)) {
|
||||
this.show = true
|
||||
} else if (this.showOneChild && !this.active && this.show) {
|
||||
this.show = false
|
||||
}
|
||||
},
|
||||
mouseOverEvent (event) {
|
||||
if (this.item.disabled) return
|
||||
event.stopPropagation()
|
||||
this.itemHover = true
|
||||
if (!this.disableHover) {
|
||||
this.emitMobileItem(event, event.currentTarget)
|
||||
}
|
||||
},
|
||||
mouseOutEvent (event) {
|
||||
event.stopPropagation()
|
||||
this.itemHover = false
|
||||
},
|
||||
expandEnter (el) {
|
||||
el.style.height = el.scrollHeight + 'px'
|
||||
},
|
||||
expandAfterEnter (el) {
|
||||
el.style.height = 'auto'
|
||||
},
|
||||
expandBeforeLeave (el) {
|
||||
if (this.isCollapsed && this.isFirstLevel) {
|
||||
el.style.display = 'none'
|
||||
return
|
||||
}
|
||||
el.style.height = el.scrollHeight + 'px'
|
||||
}
|
||||
},
|
||||
inject: ['emitActiveShow', 'emitItemClick', 'emitItemUpdate']
|
||||
}
|
||||
</script>
|
||||
33
resources/assets/js/components/menu/sidebar/components/SidebarMenuLink.vue
Executable file
33
resources/assets/js/components/menu/sidebar/components/SidebarMenuLink.vue
Executable file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<component
|
||||
:is="tag"
|
||||
v-bind="[isRouterLink ? { to: href } : { href: href }, ...$attrs]"
|
||||
>
|
||||
<slot />
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SidebarMenuLink',
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isRouterLink () {
|
||||
return !!this.$router && this.item.href && !this.item.external
|
||||
},
|
||||
tag () {
|
||||
return this.isRouterLink ? this.$nuxt ? 'nuxt-link' : 'router-link' : 'a'
|
||||
},
|
||||
href () {
|
||||
if (!this.item.href) return '#'
|
||||
return this.item.href
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
9
resources/assets/js/components/menu/sidebar/index.js
Executable file
9
resources/assets/js/components/menu/sidebar/index.js
Executable file
@@ -0,0 +1,9 @@
|
||||
import SidebarMenu from './components/SidebarMenu.vue'
|
||||
|
||||
export default {
|
||||
install (Vue) {
|
||||
Vue.component('sidebar-menu', SidebarMenu)
|
||||
}
|
||||
}
|
||||
|
||||
export { SidebarMenu }
|
||||
201
resources/assets/js/components/menu/sidebar/scss/_base.scss
Executable file
201
resources/assets/js/components/menu/sidebar/scss/_base.scss
Executable file
@@ -0,0 +1,201 @@
|
||||
.v-sidebar-menu {
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 999;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
transition: 0.3s max-width ease;
|
||||
|
||||
.vsm--scroll-wrapper {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.vsm--dropdown > .vsm--list {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.vsm--item {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.vsm--link {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: $item-font-size;
|
||||
font-weight: 400;
|
||||
padding: $item-padding;
|
||||
line-height: $item-line-height;
|
||||
text-decoration: none;
|
||||
user-select: none;
|
||||
z-index: 20;
|
||||
transition: 0.3s all ease;
|
||||
&_exact-active,
|
||||
&_active {
|
||||
font-weight: 600;
|
||||
}
|
||||
&_disabled {
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
}
|
||||
&_level-1 {
|
||||
.vsm--icon {
|
||||
height: $icon-height;
|
||||
line-height: $icon-height;
|
||||
width: $icon-width;
|
||||
flex-shrink: 0;
|
||||
text-align: center;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vsm--icon {
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.vsm--title {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.vsm--arrow {
|
||||
width: 30px;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
transition: 0.3s transform ease;
|
||||
&:after {
|
||||
content: '\f105';
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
}
|
||||
&_open {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
&_slot:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.vsm--header {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
padding: 10px;
|
||||
white-space: nowrap;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.vsm--badge {
|
||||
&_default {
|
||||
padding: 0px 6px;
|
||||
font-size: 12px;
|
||||
border-radius: 3px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
.vsm--toggle-btn {
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
height: 50px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
width: 100%;
|
||||
&:after {
|
||||
content: '\f337';
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
}
|
||||
&_slot:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.vsm_collapsed {
|
||||
& .vsm--link_level-1 {
|
||||
&.vsm--link_hover,
|
||||
&:hover {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.vsm_rtl {
|
||||
right: 0;
|
||||
left: inherit;
|
||||
text-align: right;
|
||||
direction: rtl;
|
||||
|
||||
& .vsm--icon {
|
||||
margin-left: 10px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
&.vsm_relative {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.expand-enter-active,
|
||||
.expand-leave-active {
|
||||
transition: height 0.3s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
.expand-enter,
|
||||
.expand-leave-to {
|
||||
height: 0 !important;
|
||||
}
|
||||
|
||||
.slide-animation-enter-active {
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
.slide-animation-leave-active {
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
.slide-animation-enter,
|
||||
.slide-animation-leave-to {
|
||||
width: 0 !important;
|
||||
}
|
||||
|
||||
.fade-animation-enter-active {
|
||||
transition: opacity 0.3s ease, visibility 0.3s ease;
|
||||
}
|
||||
.fade-animation-leave-active {
|
||||
transition: opacity 0.3s ease, visibility 0.3s ease;
|
||||
}
|
||||
.fade-animation-enter,
|
||||
.fade-animation-leave-to {
|
||||
opacity: 0 !important;
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.vsm--mobile-item>.vsm--item {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
.vsm--mobile-item>.vsm--item>.vsm--link {
|
||||
margin: 0 !important;
|
||||
background-color: transparent !important;
|
||||
line-height: $icon-height !important;
|
||||
}
|
||||
}
|
||||
36
resources/assets/js/components/menu/sidebar/scss/_variables.scss
Executable file
36
resources/assets/js/components/menu/sidebar/scss/_variables.scss
Executable file
@@ -0,0 +1,36 @@
|
||||
$primary-color: #4285f4 !default;
|
||||
$base-bg: #2a2a2e !default;
|
||||
|
||||
$item-color: #fff !default;
|
||||
|
||||
$item-active-color: null !default;
|
||||
$item-active-bg: null !default;
|
||||
|
||||
$item-open-color: #fff !default;
|
||||
$item-open-bg: $primary-color !default;
|
||||
|
||||
$item-hover-color: null !default;
|
||||
$item-hover-bg: rgba(darken($base-bg, 5%), 0.5) !default;
|
||||
|
||||
$icon-color: null !default;
|
||||
$icon-bg: darken( $base-bg, 5% ) !default;
|
||||
|
||||
$icon-active-color: null !default;
|
||||
$icon-active-bg: null !default;
|
||||
|
||||
$icon-open-color: null !default;
|
||||
$icon-open-bg: $item-open-bg !default;
|
||||
|
||||
$mobile-item-color: #fff !default;
|
||||
$mobile-item-bg: $primary-color !default;
|
||||
$mobile-icon-color: $mobile-item-color !default;
|
||||
$mobile-icon-bg: $mobile-item-bg !default;
|
||||
|
||||
$dropdown-bg: lighten( $base-bg, 5% ) !default;
|
||||
$dropdown-color: null !default;
|
||||
|
||||
$item-font-size: 16px !default;
|
||||
$item-line-height: 30px !default;
|
||||
$item-padding: 10px !default;
|
||||
$icon-height: 30px !default;
|
||||
$icon-width: 30px !default;
|
||||
100
resources/assets/js/components/menu/sidebar/scss/themes/default-theme.scss
Executable file
100
resources/assets/js/components/menu/sidebar/scss/themes/default-theme.scss
Executable file
@@ -0,0 +1,100 @@
|
||||
.v-sidebar-menu {
|
||||
background-color: $base-bg;
|
||||
.vsm--link {
|
||||
color: $item-color;
|
||||
&_exact-active,
|
||||
&_active {
|
||||
color: $item-active-color;
|
||||
background-color: $item-active-bg;
|
||||
}
|
||||
&_level-1 {
|
||||
&.vsm--link_exact-active,
|
||||
&.vsm--link_active {
|
||||
box-shadow: 3px 0px 0px 0px $primary-color inset;
|
||||
& .vsm--icon {
|
||||
color: $icon-active-color;
|
||||
background-color: $icon-active-bg;
|
||||
}
|
||||
}
|
||||
& .vsm--icon {
|
||||
background-color: $icon-bg;
|
||||
}
|
||||
}
|
||||
&_hover,
|
||||
&:hover {
|
||||
color: $item-hover-color;
|
||||
background-color: $item-hover-bg;
|
||||
}
|
||||
&_mobile-item {
|
||||
color: $mobile-item-color;
|
||||
&.vsm--link_hover,
|
||||
&:hover {
|
||||
color: $mobile-item-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.vsm_collapsed {
|
||||
.vsm--link_level-1.vsm--link_hover,
|
||||
.vsm--link_level-1:hover {
|
||||
.vsm--icon {
|
||||
color: $mobile-icon-color;
|
||||
background-color: $mobile-icon-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vsm--icon {
|
||||
color: $icon-color;
|
||||
}
|
||||
|
||||
.vsm--dropdown {
|
||||
& .vsm--list {
|
||||
background-color: $dropdown-bg;
|
||||
}
|
||||
& .vsm--link {
|
||||
color: $dropdown-color;
|
||||
}
|
||||
& .vsm--icon {
|
||||
color: $dropdown-color;
|
||||
}
|
||||
}
|
||||
|
||||
.vsm--mobile-bg {
|
||||
background-color: $mobile-item-bg;
|
||||
}
|
||||
|
||||
&.vsm_expanded {
|
||||
.vsm--item_open {
|
||||
.vsm--link {
|
||||
&_level-1 {
|
||||
color: $item-open-color;
|
||||
background-color: $item-open-bg;
|
||||
& .vsm--icon {
|
||||
color: $icon-open-color;
|
||||
background-color: $icon-open-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.vsm_rtl {
|
||||
.vsm--link_level-1.vsm--link_active,
|
||||
.vsm--link_level-1.vsm--link_exact-active {
|
||||
box-shadow: -3px 0px 0px 0px $primary-color inset;
|
||||
}
|
||||
}
|
||||
|
||||
.vsm--header {
|
||||
color: rgba($item-color, 0.7);
|
||||
}
|
||||
.vsm--badge_default {
|
||||
color: $item-color;
|
||||
background-color: darken( $base-bg, 5% );
|
||||
}
|
||||
.vsm--toggle-btn {
|
||||
color: $item-color;
|
||||
background-color: darken( $base-bg, 5% );
|
||||
}
|
||||
}
|
||||
108
resources/assets/js/components/menu/sidebar/scss/themes/white-theme.scss
Executable file
108
resources/assets/js/components/menu/sidebar/scss/themes/white-theme.scss
Executable file
@@ -0,0 +1,108 @@
|
||||
$base-bg: #fff;
|
||||
$item-color: #262626;
|
||||
$icon-bg: #bbc5d6;
|
||||
$icon-active-color: #fff;
|
||||
$icon-active-bg: $item-color;
|
||||
$item-hover-bg: rgba(darken($base-bg, 5%), 0.5);
|
||||
$dropdown-bg: #e3e3e3;
|
||||
|
||||
.v-sidebar-menu.vsm_white-theme {
|
||||
background-color: $base-bg;
|
||||
.vsm--link {
|
||||
color: $item-color;
|
||||
&_exact-active,
|
||||
&_active {
|
||||
color: $item-active-color;
|
||||
background-color: $item-active-bg;
|
||||
}
|
||||
&_level-1 {
|
||||
&.vsm--link_exact-active,
|
||||
&.vsm--link_active {
|
||||
box-shadow: 3px 0px 0px 0px $primary-color inset;
|
||||
& .vsm--icon {
|
||||
color: $icon-active-color;
|
||||
background-color: $icon-active-bg;
|
||||
}
|
||||
}
|
||||
& .vsm--icon {
|
||||
background-color: $icon-bg;
|
||||
}
|
||||
}
|
||||
&_hover,
|
||||
&:hover {
|
||||
color: $item-hover-color;
|
||||
background-color: $item-hover-bg;
|
||||
}
|
||||
&_mobile-item {
|
||||
color: $mobile-item-color;
|
||||
&.vsm--link_hover,
|
||||
&:hover {
|
||||
color: $mobile-item-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.vsm_collapsed {
|
||||
.vsm--link_level-1.vsm--link_hover,
|
||||
.vsm--link_level-1:hover {
|
||||
.vsm--icon {
|
||||
color: $mobile-icon-color;
|
||||
background-color: $mobile-icon-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vsm--icon {
|
||||
color: $icon-color;
|
||||
}
|
||||
|
||||
.vsm--dropdown {
|
||||
& .vsm--list {
|
||||
background-color: $dropdown-bg;
|
||||
}
|
||||
& .vsm--link {
|
||||
color: $dropdown-color;
|
||||
}
|
||||
& .vsm--icon {
|
||||
color: $dropdown-color;
|
||||
}
|
||||
}
|
||||
|
||||
.vsm--mobile-bg {
|
||||
background-color: $mobile-item-bg;
|
||||
}
|
||||
|
||||
&.vsm_expanded {
|
||||
.vsm--item_open {
|
||||
.vsm--link {
|
||||
&_level-1 {
|
||||
color: $item-open-color;
|
||||
background-color: $item-open-bg;
|
||||
& .vsm--icon {
|
||||
color: $icon-open-color;
|
||||
background-color: $icon-open-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.vsm_rtl {
|
||||
.vsm--link_level-1.vsm--link_active,
|
||||
.vsm--link_level-1.vsm--link_exact-active {
|
||||
box-shadow: -3px 0px 0px 0px $primary-color inset;
|
||||
}
|
||||
}
|
||||
|
||||
.vsm--header {
|
||||
color: rgba($item-color, 0.7);
|
||||
}
|
||||
.vsm--badge_default {
|
||||
color: $item-color;
|
||||
background-color: darken( $base-bg, 5% );
|
||||
}
|
||||
.vsm--toggle-btn {
|
||||
color: $item-color;
|
||||
background-color: darken( $base-bg, 5% );
|
||||
}
|
||||
}
|
||||
7
resources/assets/js/components/menu/sidebar/scss/vue-sidebar-menu.scss
Executable file
7
resources/assets/js/components/menu/sidebar/scss/vue-sidebar-menu.scss
Executable file
@@ -0,0 +1,7 @@
|
||||
// base styles
|
||||
@import './variables';
|
||||
@import './base';
|
||||
|
||||
// themes
|
||||
@import './themes/default-theme';
|
||||
@import './themes/white-theme';
|
||||
@@ -1,14 +1,39 @@
|
||||
<template>
|
||||
<span
|
||||
:id="`label-${data.id}`"
|
||||
@mouseover="hoverHandler"
|
||||
@mouseleave="unhoverHandler"
|
||||
v-bind:class="{highlightText: isHighlight, loadingTooltip: isLoading}"
|
||||
>
|
||||
{{ data.title }}
|
||||
<div
|
||||
:id="`label-${data.id}-mobile`"
|
||||
v-if="collapsed && mobile && level == 1"
|
||||
class="float-left"
|
||||
>
|
||||
{{ data.title }}
|
||||
</div>
|
||||
<div
|
||||
:id="`label-${data.id}`"
|
||||
v-else-if="!collapsed || mobile || level > 1"
|
||||
class="float-left"
|
||||
>
|
||||
<custom-sidebar-menu-icon
|
||||
:icon="data.icon"
|
||||
:style="customStyle"
|
||||
/>
|
||||
{{ data.title }}
|
||||
</div>
|
||||
<div v-else-if="collapsed">
|
||||
<custom-sidebar-menu-icon
|
||||
:id="`label-${data.id}`"
|
||||
:icon="data.icon"
|
||||
:style="customStyle"
|
||||
/>
|
||||
</div>
|
||||
<b-tooltip
|
||||
:target="`label-${data.id}`"
|
||||
:target="mobile ? `label-${data.id}-mobile` : `label-${data.id}`"
|
||||
:boundary="mobile ? `label-${data.id}-mobile` : `label-${data.id}`"
|
||||
:show.sync="showTooltip"
|
||||
:placement="collapsed ? 'auto' : 'topright'"
|
||||
v-if="showTooltip"
|
||||
>
|
||||
{{ labelTooltip }}
|
||||
@@ -23,11 +48,19 @@
|
||||
|
||||
<script>
|
||||
import api from "./../../api/index";
|
||||
import CustomSidebarMenuIcon from "../menu/CustomSidebarMenuIcon.vue";
|
||||
|
||||
export default {
|
||||
name: "CustomTooltip",
|
||||
components: {
|
||||
CustomSidebarMenuIcon,
|
||||
},
|
||||
props: {
|
||||
data: Object
|
||||
data: Object,
|
||||
collapsed: Boolean,
|
||||
customStyle: Object,
|
||||
level: Number,
|
||||
mobile: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -53,6 +86,12 @@ export default {
|
||||
* Delay the hover event
|
||||
*/
|
||||
hoverHandler() {
|
||||
if (this.loading) {
|
||||
clearTimeout(this.loading);
|
||||
}
|
||||
if (this.hovering) {
|
||||
clearTimeout(this.hovering);
|
||||
}
|
||||
this.loading = setTimeout(() => { this.isLoading = true }, 1000) ;
|
||||
this.hovering = setTimeout(() => { this.setTooltip() }, 3000);
|
||||
},
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import Vue from "vue";
|
||||
import VueRouter from "vue-router";
|
||||
import VueSidebarMenu from "vue-sidebar-menu";
|
||||
import VueI18n from 'vue-i18n';
|
||||
import { BootstrapVue, BootstrapVueIcons } from 'bootstrap-vue';
|
||||
import { ServerTable, Event, ClientTable } from 'vue-tables-2';
|
||||
@@ -22,7 +21,6 @@ import Home from "./Home";
|
||||
|
||||
Vue.use(VueApexCharts);
|
||||
Vue.use(VueRouter);
|
||||
Vue.use(VueSidebarMenu);
|
||||
Vue.use(BootstrapVue);
|
||||
Vue.use(BootstrapVueIcons);
|
||||
Vue.use(VueI18n);
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
margin-right: 10px
|
||||
}
|
||||
|
||||
.vsm--link_level-2>.vsm--icon {
|
||||
.vsm--link_level-2 > :not(.collapseDrop) .vsm--icon {
|
||||
margin-top: 0px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user