chore: add components

This commit is contained in:
Jesús Pérez Lorenzo 2021-10-19 21:52:07 +01:00
parent f52da5044e
commit 324a14e055
12 changed files with 1058 additions and 0 deletions

View File

@ -0,0 +1,251 @@
<template>
<nav class="flex items-center justify-between pt-1 dark:bg-cool-gray-800 dark:text-white">
<!-- Navbar left -->
<div class="flex items-center space-x-3">
<span class="cursor-pointer p-2 text-xl font-semibold tracking-wider uppercase lg:hidden" @click="on_logo_app">
<img class="-mt-1 h-11 w-auto rounded" src="/assets/images/app_w.svg" alt="App">
</span>
<!-- Toggle sidebar button -->
<button class="p-2 rounded-md focus:outline-none focus:ring dark:bg-cool-gray-600" @click="toggleSidebar">
<span class="sr-only">Toggle sidebar</span>
<ChevronDoubleRight
aria-hidden="true"
class="w-4 h-4 text-gray-600 dark:text-white"
:class="{ 'transform transition-transform -rotate-180': isSidebarOpen }"
/>
</button>
<Breadcrump :bc-path="bcPath" @on-book-selec="bookSelec" />
<span class="lg:ml-3 flex">
<span
class="hidden lg:flex flex-grow"
:class="`${navTitle && navTitle.cmpnt === 'boxmenu' ? 'mt-2': ''} ${navTitle.textclick ? 'cursor-pointer': ''}`"
@click="onNavTitleClick"
>{{ navTitle && navTitle.text || '' }}
</span>
<BoxMenu
v-if="navTitle && navTitle.text !== '' && navTitle.cmpnt === 'boxmenu'"
class="-ml-4 lg:ml-0 flex-grow-0"
:menu-options="navTitle.ops"
:title="navTitle.title"
:btn-type="navTitle.btntype"
@on-menu-option="onNavTitleMenuOption"
/>
</span>
</div>
<!-- Navbar right -->
<nav aria-label="Secondary" class="flex items-center space-x-3">
<span class="cursor-pointer p-2 text-xl font-semibold tracking-wider uppercase" @click="on_logo_app">
<img class="-mt-1 h-11 w-auto rounded" src="/assets/images/logo.svg" alt="App">
</span>
<NavbarIconButton
v-show="authData.auth === ''"
label="Login"
@click="router.push('/login')"
>
<carbon-login /> {{ t('button.login','Login') }}
</NavbarIconButton>
<NavbarIconButton
v-show="useSettings"
label="Open setting panel"
@click="isSettingsPanelOpen = !isSettingsPanelOpen"
>
<carbon-settings />
</NavbarIconButton>
<!-- Search button -->
<NavbarIconButton v-if="navbar_right.search" label="Open search panel" @click="isSearchPanelOpen = true">
<!-- SearchIcon aria-hidden="true" class="w-6 h-6" -->
<carbon-search />
</NavbarIconButton>
<!-- Notification Button -->
<NavbarIconButton v-if="navbar_right.notify" label="Open notifications panel" @click="isNotificationsPanelOpen = true">
<!--BellIcon aria-hidden="true" class="w-6 h-6" /-->
<carbon-notification />
</NavbarIconButton>
<!-- User menu -->
<Menu v-slot="{ open }" as="div" class="relative pr-2">
<MenuButton
id="user-menu"
aria-haspopup="true"
:aria-expanded="open ? 'true' : 'false'"
class="relative flex items-center overflow-hidden rounded-full group focus:outline-none focus:ring"
v-if="authData.auth !== ''"
>
<span class="sr-only">{{ `${navbar_right.title || 'Open menu'}` }}</span>
<img
class="object-cover w-8 h-8 rounded-full group-hover:opacity-90"
src="/assets/images/jesus.jpg"
alt="User image"
>
<!-- <div class="absolute right-0 p-1 bg-green-400 rounded-full top-1 animate-ping"></div>
<div class="absolute right-0 p-1 bg-green-400 border border-white rounded-full top-1"></div> -->
</MenuButton>
<transition
enter-active-class="transition duration-100 ease-out"
enter-from-class="transform scale-95 opacity-0"
enter-to-class="transform scale-100 opacity-100"
leave-active-class="transition duration-75 ease-out"
leave-from-class="transform scale-100 opacity-100"
leave-to-class="transform scale-95 opacity-0"
>
<MenuItems
class="absolute right-0 z-999 w-48 py-1 mt-2 origin-top-righti dark:bg-gray-600 bg-white rounded-md shadow-lg focus:outline-none"
role="menu"
aria-orientation="vertical"
aria-labelledby="user-menu"
>
<MenuItem
v-for="(itm,index) in navbar_right.items"
v-slot="{ active }"
:key="`${String(index)}-${itm.title}`"
>
<span class="block text-sm dark:text-gray-300 text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-500">
<a
v-if="itm && itm.type === 'app_link'"
:class="{ 'bg-gray-100': active }"
class="px-4 py-2 flex items-center"
role="menuitem"
@click.prevent="onMenuItemClick(itm)"
>
{{ t(itm.title) }}
</a>
<router-link
v-if="itm && itm.type === 'router_link'"
:title="t(itm.title)"
:to="{ name: itm.name_to }"
class="px-4 py-2 flex items-center"
>
{{ t(itm.title) }}
</router-link>
</span>
</MenuItem>
</MenuItems>
</transition>
</Menu>
</nav>
</nav>
</template>
<script setup lang="ts">
/*
// https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
<MenuItem v-slot="{ active }">
<a
href="#"
class="block px-4 py-2 text-sm text-gray-700"
:class="{ 'bg-gray-100': active }"
role="menuitem"
>
{{ t('menu.your-profile') }}
</a>
</MenuItem>
<MenuItem v-slot="{ active }">
<a
href="#"
class="block px-4 py-2 text-sm text-gray-700"
:class="{ 'bg-gray-100': active }"
role="menuitem"
>
{{ t('menu.settings') }}
</a>
</MenuItem>
<MenuItem v-slot="{ active }">
<a
href="#"
class="block px-4 py-2 text-sm text-gray-700"
:class="{ 'bg-gray-100': active }"
role="menuitem"
>
{{ t('menu.sign-out') }}
</a>
</MenuItem>
*/
import {
computed,
} from 'vue'
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue'
import ChevronDoubleRight from '@/icons/ChevronDoubleRight.vue'
// import SearchIcon from '@/icons/SearchIcon.vue'
// import BellIcon from '@/icons/BellIcon.vue'
import Breadcrump from '@/Breadcrump.vue'
import { useStore } from 'vuex'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import NavbarIconButton from './NavbarIconButton.vue'
import BoxMenu from '~/views/BoxMenu.vue'
import useState from '~/hooks/useState'
import useComponent from '~/hooks/useComponent'
// import { NavItemType } from '~/typs'
// import { SideMenuItemType } from '~/typs/cmpnts'
import { auth_data } from '~/hooks/utils'
import { SideMenuItemType } from '~/typs/cmpnts'
const router = useRouter()
const store = useStore()
const open_menu = ref(false)
const { isSidebarOpen, isSearchPanelOpen, isSettingsPanelOpen, toggleSidebar, isNotificationsPanelOpen, bcPath, navTitle } = useState()
const { t } = useI18n()
const authData = computed(() => auth_data())
const map_key = router.currentRoute.value.meta.uiMapkey || 'ui'
const defs = computed(() => store.state.app_defs.main.get(map_key) || {})
const user_image = computed(() => {
const image = defs.value.profile && defs.value.profile.image ? defs.value.profile.image : ''
if (image === '') {
return '/assets/images/user.jpg'
}
if (image.charAt(0) === '/' || image.includes('http')) {
return image
} else {
return `/assets/images/${image}`
}
})
const useSettings = computed(() => Object.entries(useComponent().settingsComponent.value).length !== 0)
const navbar_right = computed(() => {
return defs.value && defs.value.header && defs.value.header.navbar && defs.value.header.navbar.menuright
? defs.value.header.navbar.menuright
: { title: '', notify: false, search: false, items: [] }
})
const bookSelec = (target: string) => {
switch (target) {
case 'home':
useState().bcPath.value = ''
useState().dfltNavTitle()
router.push('/')
break
default:
useState().bookSelec(target)
}
}
const on_logo_app = () => {
useState().bcPath.value = ''
useState().dfltNavTitle()
useState().isSidebarOpen.value = false
if (useState().app_home_click.value) {
useState().app_home_click.value()
}
router.push('/')
}
const onMenuItemClick = (itm: SideMenuItemType ) => {
const ustate: any = useState()
const ky=`show_${itm.click}`
if (ustate[ky])
ustate[ky].value = !ustate[ky].value
}
const onNavTitleMenuOption = (data: any) => {
if (navTitle.value.cllbck)
navTitle.value.cllbck(data)
}
const onNavTitleClick = () => {
if (navTitle.value.textclick)
navTitle.value.textclick()
}
</script>

View File

@ -0,0 +1,18 @@
<template>
<button
type="button"
class="flex items-center justify-center p-2 text-gray-400 transition-colors bg-gray-100 rounded-full focus:outline-none focus:ring hover:bg-gray-200 hover:text-gray-500 dark:bg-cool-gray-600 dark:text-white"
>
<span class="sr-only">{{ label }}</span>
<slot />
</button>
</template>
<script setup>
defineProps({
label: {
type: String,
required: true,
},
})
</script>

236
src/components/navbar/v.vue Normal file
View File

@ -0,0 +1,236 @@
<template>
<nav class="flex items-center justify-between pt-1 dark:bg-cool-gray-800 dark:text-white">
<!-- Navbar left -->
<div class="flex items-center space-x-3">
<span class="cursor-pointer p-2 text-xl font-semibold tracking-wider uppercase lg:hidden" @click="on_logo_app">
<img class="-mt-1 h-11 w-auto rounded" src="/assets/images/app_w.svg" alt="App">
</span>
<!-- Toggle sidebar button -->
<button class="p-2 rounded-md focus:outline-none focus:ring dark:bg-cool-gray-600" @click="toggleSidebar">
<span class="sr-only">Toggle sidebar</span>
<ChevronDoubleRight
aria-hidden="true"
class="w-4 h-4 text-gray-600 dark:text-white"
:class="{ 'transform transition-transform -rotate-180': isSidebarOpen }"
/>
</button>
<Breadcrump :bc-path="bcPath" @on-book-selec="bookSelec" />
<span class="lg:ml-3 flex">
<span
class="hidden lg:flex flex-grow"
:class="`${navTitle && navTitle.cmpnt === 'boxmenu' ? 'mt-2': ''} ${navTitle.textclick ? 'cursor-pointer': ''}`"
@click="onNavTitleClick"
>{{ navTitle && navTitle.text || '' }}
</span>
<BoxMenu
v-if="navTitle && navTitle.text !== '' && navTitle.cmpnt === 'boxmenu'"
class="-ml-4 lg:ml-0 flex-grow-0"
:menu-options="navTitle.ops"
:title="navTitle.title"
:btn-type="navTitle.btntype"
@on-menu-option="onNavTitleMenuOption"
/>
</span>
</div>
<!-- Navbar right -->
<nav aria-label="Secondary" class="flex items-center space-x-3">
<span class="cursor-pointer p-2 text-xl font-semibold tracking-wider uppercase" @click="on_logo_app">
<img class="-mt-1 h-11 w-auto rounded" src="/assets/images/logo.svg" alt="App">
</span>
<NavbarIconButton
v-show="useSettings"
label="Open setting panel"
@click="isSettingsPanelOpen = !isSettingsPanelOpen"
>
<carbon-settings />
</NavbarIconButton>
<!-- Search button -->
<NavbarIconButton v-if="navbar_right.search" label="Open search panel" @click="isSearchPanelOpen = true">
<!-- SearchIcon aria-hidden="true" class="w-6 h-6" -->
<carbon-search />
</NavbarIconButton>
<!-- Notification Button -->
<NavbarIconButton v-if="navbar_right.notify" label="Open notifications panel" @click="isNotificationsPanelOpen = true">
<!--BellIcon aria-hidden="true" class="w-6 h-6" /-->
<carbon-notification />
</NavbarIconButton>
<!-- User menu -->
<Menu v-slot="{ open }" as="div" class="relative pr-2">
<span> {{ navbar_right.items}} </span>
<MenuButton
id="user-menu"
aria-haspopup="true"
:aria-expanded="open ? 'true' : 'false'"
class="relative flex items-center overflow-hidden rounded-full group focus:outline-none focus:ring"
>
<span class="sr-only">{{ `${navbar_right.title || 'Open menu'}` }}</span>
<img
class="object-cover w-8 h-8 rounded-full group-hover:opacity-90"
src="/assets/images/jesus.jpg"
alt="User image"
>
<!-- <div class="absolute right-0 p-1 bg-green-400 rounded-full top-1 animate-ping"></div>
<div class="absolute right-0 p-1 bg-green-400 border border-white rounded-full top-1"></div> -->
</MenuButton>
<transition
enter-active-class="transition duration-100 ease-out"
enter-from-class="transform scale-95 opacity-0"
enter-to-class="transform scale-100 opacity-100"
leave-active-class="transition duration-75 ease-out"
leave-from-class="transform scale-100 opacity-100"
leave-to-class="transform scale-95 opacity-0"
>
<MenuItems
class="absolute right-0 z-999 w-48 py-1 mt-2 origin-top-righti dark:bg-gray-600 bg-white rounded-md shadow-lg focus:outline-none"
role="menu"
aria-orientation="vertical"
aria-labelledby="user-menu"
>
<MenuItem
v-for="(itm,index) in navbar_right.items"
v-slot="{ active }"
:key="`${String(index)}-${itm.title}`"
>
<span class="block text-sm dark:text-gray-300 text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-500">
<a
v-if="itm && itm.type === 'app_link'"
:href="itm.link"
:class="{ 'bg-gray-100': active }"
class="px-4 py-2"
role="menuitem"
>
{{ t(itm.title) }}
</a>
<router-link
v-if="itm && itm.type === 'router_link'"
:title="t(itm.title)"
:to="{ name: itm.name_to }"
class="px-4 py-2 flex items-center"
>
{{ t(itm.title) }}
</router-link>
</span>
</MenuItem>
</MenuItems>
</transition>
</Menu>
</nav>
</nav>
</template>
<script setup lang="ts">
/*
// https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
<MenuItem v-slot="{ active }">
<a
href="#"
class="block px-4 py-2 text-sm text-gray-700"
:class="{ 'bg-gray-100': active }"
role="menuitem"
>
{{ t('menu.your-profile') }}
</a>
</MenuItem>
<MenuItem v-slot="{ active }">
<a
href="#"
class="block px-4 py-2 text-sm text-gray-700"
:class="{ 'bg-gray-100': active }"
role="menuitem"
>
{{ t('menu.settings') }}
</a>
</MenuItem>
<MenuItem v-slot="{ active }">
<a
href="#"
class="block px-4 py-2 text-sm text-gray-700"
:class="{ 'bg-gray-100': active }"
role="menuitem"
>
{{ t('menu.sign-out') }}
</a>
</MenuItem>
*/
import {
computed,
} from 'vue'
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue'
import ChevronDoubleRight from '@/icons/ChevronDoubleRight.vue'
// import SearchIcon from '@/icons/SearchIcon.vue'
// import BellIcon from '@/icons/BellIcon.vue'
import Breadcrump from '@/Breadcrump.vue'
import { useStore } from 'vuex'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import NavbarIconButton from './NavbarIconButton.vue'
import BoxMenu from '~/views/BoxMenu.vue'
import useState from '~/hooks/useState'
import useComponent from '~/hooks/useComponent'
// import { NavItemType } from '~/typs'
// import { SideMenuItemType } from '~/typs/cmpnts'
import { auth_data } from '~/hooks/utils'
const router = useRouter()
const store = useStore()
const { isSidebarOpen, isSearchPanelOpen, isSettingsPanelOpen, toggleSidebar, isNotificationsPanelOpen, bcPath, navTitle } = useState()
const { t } = useI18n()
const authData = computed(() => auth_data())
const map_key = router.currentRoute.value.meta.uiMapkey || 'ui'
const defs = computed(() => store.state.app_defs.main.get(map_key) || {})
const user_image = computed(() => {
const image = defs.value.profile && defs.value.profile.image ? defs.value.profile.image : ''
if (image === '') {
return '/assets/images/user.jpg'
}
if (image.charAt(0) === '/' || image.includes('http')) {
return image
} else {
return `/assets/images/${image}`
}
})
const useSettings = computed(() => Object.entries(useComponent().settingsComponent.value).length !== 0)
const navbar_right = computed(() => {
return defs.value && defs.value.header && defs.value.header.navbar && defs.value.header.navbar.menuright
? defs.value.header.navbar.menuright
: { title: '', notify: false, search: false, items: [] }
})
const bookSelec = (target: string) => {
switch (target) {
case 'home':
useState().bcPath.value = ''
useState().dfltNavTitle()
router.push('/')
break
default:
useState().bookSelec(target)
}
}
const on_logo_app = () => {
useState().bcPath.value = ''
useState().dfltNavTitle()
useState().isSidebarOpen.value = false
if (useState().app_home_click.value) {
useState().app_home_click.value()
}
router.push('/')
}
const onNavTitleMenuOption = (data: any) => {
if (navTitle.value.cllbck)
navTitle.value.cllbck(data)
}
const onNavTitleClick = () => {
if (navTitle.value.textclick)
navTitle.value.textclick()
}
</script>

View File

@ -0,0 +1,45 @@
<template>
<Panel :show="show" :title="title" id="aside" @close="close">
<div class="flex-1 max-h-full p-4 overflow-hidden hover:overflow-y-auto">
<keep-alive>
<component :is="asideComponent" />
</keep-alive>
<slot />
</div>
</Panel>
</template>
<script setup>
import { computed, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import Panel from './Panel.vue'
import useComponent, { DynComponent } from '~/hooks/useComponent'
defineProps({
show: {
type: Boolean,
required: true,
},
title: {
type: String,
required: false,
default: () => '',
},
})
const emit = defineEmits(['close'])
const cmpnt = useComponent()
const close = () => {
emit('close')
}
onMounted(() => {
document.addEventListener('keydown', (event) => {
switch (event.key) {
case 'Escape':
emit('close')
break
}
})
})
const { t, locale } = useI18n()
const asideComponent = computed(() => cmpnt.asideComponent.value)
</script>

View File

@ -0,0 +1,40 @@
<template>
<Panel :show="show" :title="t(title || 'notifications.notifications')" id="notifycation" @close="close">
<div class="flex-1 max-h-full p-4 overflow-hidden hover:overflow-y-auto">
<span>{{ t('notifications.content') }}</span>
<!-- Notifications Panel Content ... -->
</div>
</Panel>
</template>
<script setup>
import { onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import Panel from './Panel.vue'
defineProps({
show: {
type: Boolean,
required: true,
},
title: {
type: String,
required: false,
default: () => '',
},
})
const emit = defineEmits(['close'])
const close = () => {
emit('close')
}
const { t, locale } = useI18n()
onMounted(() => {
document.addEventListener('keydown', (event) => {
switch (event.key) {
case 'Escape':
emit('close')
break
}
})
})
</script>

View File

@ -0,0 +1,73 @@
<template>
<!-- Backdrop -->
<Backdrop :show="show" :blur="backdrop_blur" @close="close" />
<!-- Panel -->
<transition
enter-active-class="transition duration-300 ease-in-out transform"
:enter-from-class="left ? '-translate-x-full' : 'translate-x-full'"
:enter-to-class="left ? '-translate-x-0' : 'translate-x-0'"
leave-active-class="transition duration-300 ease-in-out transform"
:leave-from-class="left ? '-translate-x-0' : 'translate-x-0'"
:leave-to-class="left ? '-translate-x-full' : 'translate-x-full'"
>
<section
v-if="show"
:aria-labelledby="title"
class="fixed z-999 border-left-2 border-gray-400 max-w-xs bg-white sm:max-w-md ring-1 ring-black ring-opacity-5 focus:outline-none dark:bg-cool-gray-800 dark:text-white"
:class="`${left ? 'left-0' : 'right-0'} bg-opacity-${back_opacity} ${panel_style}`"
>
<div class="flex items-center justify-between flex-shrink-0 p-2">
<h6 class="p-2 text-lg">
{{ title }}
</h6>
<!-- Close button -->
<button class="p-2 rounded-md focus:outline-none focus:ring dark:bg-cool-gray-600" @click="close">
<CloseIcon class="w-6 h-6 text-gray-600 dark:text-white" />
</button>
</div>
<!-- Panel content -->
<slot />
</section>
</transition>
</template>
<script setup lang="ts">
import Backdrop from '../global/Backdrop.vue'
import CloseIcon from '../icons/CloseIcon.vue'
import useState from '~/hooks/useState'
const props = defineProps<{
show: {
type: boolean,
required: true,
},
left: {
type: boolean,
required: false,
default: false,
},
title: {
type: string,
required: true,
},
id: {
type: string,
required: true,
},
}>()
const backdrop_blur = useState().backdrop_blur
const back_opacity = useState().back_opacity
const emit = defineEmits(['close'])
const panel_style = computed(() => {
const key: any = props.id || ''
return useState().panels.value[key] && useState().panels.value[key].style ? useState().panels.value[key].style : 'inset-y-0 w-full'
})
const close = () => {
emit('close')
}
</script>

View File

@ -0,0 +1,51 @@
<template>
<Panel :show="show" :title="t(title || 'search.search')" id="search" @close="close">
<div class="flex-1 max-h-full p-4 space-y-4 overflow-hidden hover:overflow-y-auto dark:text-gray-600 dark:text-white">
<form @submit.prevent="close" >
<input
type="text"
v-model="search"
:placeholder="`${t('search.search')}...`"
class="w-full px-4 py-2 border rounded-md focus:outline-none focus:ring dark:text-gray-600 dark:text-white"
>
</form>
<div>
</div>
</div>
</Panel>
</template>
<script setup>
import { onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import Panel from './Panel.vue'
import useState from '~/hooks/useState'
const search = useState().search
defineProps({
show: {
type: Boolean,
required: true,
},
title: {
type: String,
required: false,
default: () => '',
},
})
const emit = defineEmits(['close'])
const close = () => {
emit('close')
}
const { t, locale } = useI18n()
onMounted(() => {
document.addEventListener('keydown', (event) => {
switch (event.key) {
case 'Escape':
emit('close')
break
}
})
})
</script>

View File

@ -0,0 +1,47 @@
<template>
<Panel :show="show" :title="title || t('settings.settings')" id="setting" @close="close">
<div class="flex-1 max-h-full p-4 overflow-hidden hover:overflow-y-auto">
<keep-alive>
<component :is="currentComponent" />
</keep-alive>
<slot />
<!-- Settings Panel Content ... -->
</div>
</Panel>
</template>
<script setup>
import { computed, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import Panel from './Panel.vue'
import useComponent, { DynComponent } from '~/hooks/useComponent'
defineProps({
show: {
type: Boolean,
required: true,
},
title: {
type: String,
required: false,
default: () => '',
},
})
const emit = defineEmits(['close'])
const cmpnt = useComponent()
const close = () => {
emit('close')
}
const { t, locale } = useI18n()
const currentComponent = computed(() => cmpnt.settingsComponent.value)
onMounted(() => {
document.addEventListener('keydown', (event) => {
switch (event.key) {
case 'Escape':
emit('close')
break
}
})
})
</script>

View File

@ -0,0 +1,170 @@
<template>
<Backdrop :show="isSidebarOpen" :blur="backdrop_blur" class="lg:hidden" @close="isSidebarOpen = false" />
<aside
class="fixed inset-y-0 z-999 flex flex-col flex-shrink-0 w-64 max-h-screen transition-all transform border-r dark:border-gray-700 border-gray-200 bg-white shadow-lg lg:z-auto lg:static lg:shadow-none"
:class="{ '-translate-x-full lg:translate-x-0 lg:w-20': !isSidebarOpen }"
>
<!-- sidebar header -->
<SidebarHeader />
<!-- Sidebar links -->
<nav aria-label="Main" class="dark:bg-cool-gray-800 dark:text-white bg-white flex-1 overflow-hidden hover:overflow-y-auto">
<!-- Sidebar Links... -->
<ul class="p-2">
<li
v-for="(itm,index) in sidebarMenuItems"
:key="`${String(index)}-${itm.title}`"
dark:border-gray-700
border-gray-200
>
<hr v-if="itm.type === NavItemType.separator" class=" dark:border-gray-600 border-gray-300">
<router-link
v-if="itm.type === NavItemType.router_link"
:title="t(itm.title)"
:to="{ name: itm.name_to }"
class="flex items-center p-2 overflow-hidden rounded-md hover:bg-gray-100 dark:hover:bg-gray-500"
:class="{ 'justify-center': !isSidebarOpen }"
>
<IconLink :typ="itm.type" :icon="itm.icon_on" :mode="itm.mode||''" :open="isSidebarOpen" :pfx="itm.pfx" :title="itm.title" :show_to="itm.show_to" :name="itm.name_to" />
</router-link>
<span
v-if="itm.type === NavItemType.module_label"
class="border-bottom-1 dark:border-gray-700 border-gray-200 flex text-xs color-gray-100"
:class="isSidebarOpen ? 'justify-end' : 'justify-start'"
>
<span class="float-right text-gray-400" :class="{ 'lg:hidden': !isSidebarOpen }"> {{ t(`menu.${itm.label}`,'') }}</span>
</span>
<a
v-if="itm.type == NavItemType.a_blank"
class="flex items-center p-2 overflow-hidden rounded-md hover:bg-gray-100 dark:hover:bg-gray-500"
:class="{ 'justify-center': !isSidebarOpen }"
@click="itemClick(itm.click || '')"
>
<IconLink :typ="itm.type" :icon="itm.icon_on" :mode="itm.mode || ''" :open="isSidebarOpen" :pfx="itm.pfx" :title="itm.title" :show_to="itm.show_to" :name="itm.name_to" />
</a>
<a
v-if="itm.type == NavItemType.a_link"
rel="noreferrer"
:href="itm.href || '#'"
target="_blank"
:title="t(itm.title)"
>
<span v-if="itm.icon_on === 'carbon-launch'"> <carbon-launch /> </span>
<span class="ml-2" :class="{ 'lg:hidden': !isSidebarOpen }"> {{ t(itm.title) }}</span>
</a>
<a
v-if="itm.type == NavItemType.app_link"
:title="t(itm.title)"
class="flex items-center p-2 overflow-hidden rounded-md hover:bg-gray-100 dark:hover:bg-gray-500"
:class="{ 'justify-center': !isSidebarOpen, 'ml-5': itm.name_to && itm.name_to === 'items' }"
@click.prevent="appItemClick(itm)"
>
<IconLink :typ="itm.type" :icon="itm.icon_on" :mode="itm.mode || ''" :open="isSidebarOpen" :pfx="itm.pfx" :title="itm.title" :show_to="itm.show_to" :name="itm.name_to" />
</a>
<a
v-if="itm.type == NavItemType.cloud_link && !isSidebarOpen && itm.name_to && itm.name_to !== 'items'"
:title="t(itm.title)"
class="flex items-center justify-center p-2 overflow-hidden rounded-md hover:bg-gray-300 dark:hover:text-gray-100 dark:hover:bg-gray-500"
@click.prevent="appItemClick(itm)"
>
<span v-if="itm.name_to && itm.name_to !== 'cloud'" class="flex -ml-2 text-gray-500 dark:text-gray-500 dark:hover:text-gray-100 dark:hover:bg-gray-500">
<span v-if="isSidebarOpen && itm.pfx" class="-ml-2 mr-2 font-light">
<small class="text-gray-400">{{ itm.pfx }}</small>
</span>
<span class="ml-2 text-xs font-light"> {{ itm.name || '' }}</span>
</span>
<span v-else class="pt-1 pb-1 border-b border-t border-gray-200 dark:border-gray-500">
<span v-if="itm.icon_on === 'carbon-cloud'" class="flex justify-center items-center"> <carbon-cloud /> </span>
<span class="text-xs flex justify-center items-center "> {{ itm.name || '' }}</span>
</span>
</a>
</li>
</ul>
</nav>
<span @click="on_logo_app" class="cursor-pointer text-xl font-semibold tracking-wider uppercase dark:bg-cool-gray-800 dark:text-white bg-white">
<div class="flex">
<img class="-mt-1 h-11 w-auto rounded" src="/assets/images/logo.svg" alt="App">
<span :class="{ 'lg:hidden': !isSidebarOpen }" class="ml-4 mt-2 text-gray-400">{{ t('app.title','') }}</span>
</div>
</span>
<SidebarFooter />
</aside>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useStore } from 'vuex'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import Backdrop from '@/global/Backdrop.vue'
import IconLink from '@/IconLink.vue'
import SidebarFooter from './SidebarFooter.vue'
import SidebarHeader from './SidebarHeader.vue'
import useState from '~/hooks/useState'
import { SideMenuItemType } from '~/typs/cmpnts'
import { NavItemType } from '~/typs'
const router = useRouter()
const store = useStore()
const { isSidebarOpen } = useState()
const { t } = useI18n()
const map_key = router.currentRoute.value.meta.uiMapkey || 'ui'
const defs = computed(() => store.state.app_defs.main.get(map_key) || {})
const backdrop_blur = useState().backdrop_blur
const sidebarMenuItems = computed((): SideMenuItemType[] => {
const ctx = router.currentRoute.value.meta.ctx || ''
const defsMenuItems = defs.value && defs.value.sidebar && defs.value.sidebar.menu_items ? defs.value.sidebar.menu_items : [] as SideMenuItemType[]
const all_items = [].concat(defsMenuItems || []) // .concat(useState().sidebarMenuItems.value as SideMenuItemType[] | any)
useState().sidebarMenuItems.value = all_items.filter((itm: SideMenuItemType) => {
if (itm.ctx && itm.ctx === ctx) {
return true
} else if (itm.ctx && itm.ctx !== ctx) {
return false
} else {
return true
}
})
return useState().sidebarMenuItems.value
})
const itemClick = (target: string) => {
switch (target) {
case 'home':
useState().bcPath.value = ''
// useState().navTitle.value = ''
router.push('/')
break
default:
}
}
const appItemClick = (itm: SideMenuItemType) => {
if (itm.cllbck) {
itm.cllbck(itm)
isSidebarOpen.value = false
} else {
if (useState().side_menu_click.value) {
try {
useState().side_menu_click.value(itm)
isSidebarOpen.value.value = false
} catch(e) {
console.log(e)
}
} else if (itm.click === 'tophome') {
router.push('/')
}
}
}
const on_logo_app = () => {
useState().bcPath.value = ''
useState().dfltNavTitle()
useState().isSidebarOpen.value = false
if (useState().app_home_click.value) {
useState().app_home_click.value()
}
router.push('/')
}
</script>

View File

@ -0,0 +1,62 @@
<template>
<div class="flex-shrink-0 p-4 border-t dark:border-gray-700 border-gray-200 max-h-14 dark:bg-cool-gray-800 dark:text-white">
<Button
v-if="sidebarFooter.itm.type === NavItemType.a_link"
class="flex w-full text-gray-700 bg-gray-100 hover:bg-gray-700 dark:bg-cool-gray-800 dark:text-gray-100 dark:hover:bg-gray-700"
:class="{'justify-start': isSidebarOpen}"
@click="itemClick(sidebarFooter.itm.click || '')"
>
<!-- LogoutIcon aria-hidden="true" class="w-6 h-6" -->
<span v-if="sidebarFooter.itm.icon_on === 'carbon-logout'"> <carbon-logout /> </span>
<span :class="{ 'lg:hidden': !isSidebarOpen }" class="ml-2"> {{ t(sidebarFooter.itm.title) }} </span>
</Button>
<router-link
v-if="sidebarFooter.itm.type === NavItemType.router_link"
:to="{ name: `${sidebarFooter.itm.name_to === 'Logout' && authData.auth === '' ? 'Login' : 'Logout'}` }"
class="flex w-full text-gray-700 bg-gray-100 dark:bg-cool-gray-800 dark:text-gray-100 dark:hover:bg-gray-700"
:class="{'justify-start': isSidebarOpen}"
>
<span v-if="sidebarFooter.itm.icon_on === 'carbon-logout' && authData.auth !== ''" class="mt-1"> <carbon-logout /> </span>
<span v-else class="mt-1"> <carbon-login /> </span>
<span :class="{ 'lg:hidden': !isSidebarOpen }" class="ml-2"> {{ t(sidebarFooter.itm.title) }} </span>
</router-link>
</div>
</template>
<script setup lang="ts">
import Button from '@/global/Button.vue'
// import LogoutIcon from '@/icons/LogoutIcon.vue'
import { computed } from 'vue'
import { useStore } from 'vuex'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import useState from '~/hooks/useState'
import { NavItemType } from '~/typs'
import { auth_data } from '~/hooks/utils'
const router = useRouter()
const store = useStore()
const { isSidebarOpen } = useState()
const { t } = useI18n()
const map_key = router.currentRoute.value.meta.uiMapkey || 'ui'
const defs = computed(() => store.state.app_defs.main.get(map_key) || {})
const authData = computed(() => auth_data())
const sidebarFooter = computed(() => {
return defs.value && defs.value.sidebar && defs.value.sidebar.footer ? defs.value.sidebar.footer : { itm: {} }
})
const itemClick = (target: string) => {
switch (target) {
case 'home':
useState().bcPath.value = ''
useState().navTitle.value = {} as any
router.push('/')
break
}
}
</script>

View File

@ -0,0 +1,58 @@
<template>
<div class="flex items-center justify-between flex-shrink-0 p-2 dark:bg-cool-gray-800 dark:text-white" :class="{ 'lg:justify-center': !isSidebarOpen }">
<span
class="cursor-pointer text-lg lg:text-xl font-semibold leading-8 tracking-wider uppercase whitespace-nowrap dark:bg-cool-gray-800 dark:text-white"
@click="on_logo_app"
>
<div class="flex">
<img class="h-11 w-auto rounded" :src="`${header.logo && header.logo !== '' ? header.logo : '/assets/images/app_w.svg'}`" alt="App">
<span :class="{ 'lg:hidden': !isSidebarOpen }" class="ml-4 mt-2 text-gray-400">{{ header.title || '' }}</span>
</div>
</span>
<button class="rounded-md lg:hidden" @click="isSidebarOpen = false">
<svg
class="w-6 h-6 text-gray-600"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { useStore } from 'vuex'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import useState from '~/hooks/useState'
const router = useRouter()
const store = useStore()
const { isSidebarOpen } = useState()
const { t, locale } = useI18n()
const map_key = router.currentRoute.value.meta.uiMapkey || 'ui'
const defs = computed(() => store.state.app_defs.main.get(map_key) || {})
const header = computed(() => {
return defs.value && defs.value.header ? defs.value.header : {}
})
const on_logo_app = () => {
useState().bcPath.value = ''
useState().dfltNavTitle()
useState().isSidebarOpen.value = false
if (useState().app_home_click.value) {
useState().app_home_click.value()
}
router.push('/')
}
</script>

View File

@ -0,0 +1,7 @@
<template>
<div class="antialiased text-gray-900 bg-white">
<slot />
</div>
</template>
<script setup></script>