Merged in bugfix/PMCORE-3833 (pull request #8504)

PMCORE-3833

Approved-by: Fabio Guachalla
This commit is contained in:
Martin Laguna
2022-08-10 19:54:11 +00:00
committed by Julio Cesar Laura Avendaño
16 changed files with 1247 additions and 20 deletions

View File

@@ -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,

View File

@@ -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>

View 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>

View 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>

View 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>

View 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>

View 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>

View File

@@ -0,0 +1,9 @@
import SidebarMenu from './components/SidebarMenu.vue'
export default {
install (Vue) {
Vue.component('sidebar-menu', SidebarMenu)
}
}
export { SidebarMenu }

View 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;
}
}

View 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;

View 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% );
}
}

View 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% );
}
}

View File

@@ -0,0 +1,7 @@
// base styles
@import './variables';
@import './base';
// themes
@import './themes/default-theme';
@import './themes/white-theme';

View File

@@ -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);
},

View File

@@ -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);

View File

@@ -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;
}