chore: layout config updates

This commit is contained in:
tugcekucukoglu
2025-12-25 10:03:32 +03:00
parent a4b2c96b0d
commit 410c08d693
10 changed files with 530 additions and 413 deletions

View File

@@ -2,7 +2,7 @@
import { useLayout } from '@/layout/composables/layout';
import { onMounted, ref, watch } from 'vue';
const { getPrimary, getSurface, isDarkTheme } = useLayout();
const { layoutConfig, isDarkTheme } = useLayout();
const chartData = ref(null);
const chartOptions = ref(null);
@@ -77,7 +77,7 @@ function setChartOptions() {
};
}
watch([getPrimary, getSurface, isDarkTheme], () => {
watch([() => layoutConfig.primary, () => layoutConfig.surface, isDarkTheme], () => {
chartData.value = setChartData();
chartOptions.value = setChartOptions();
});

View File

@@ -6,7 +6,7 @@ import Lara from '@primeuix/themes/lara';
import Nora from '@primeuix/themes/nora';
import { ref } from 'vue';
const { layoutConfig, isDarkTheme } = useLayout();
const { layoutConfig, isDarkTheme, changeMenuMode } = useLayout();
const presets = {
Aura,
@@ -192,10 +192,6 @@ function onPresetChange() {
$t().preset(presetValue).preset(getPresetExt()).surfacePalette(surfacePalette).use({ useDefaultOptions: true });
}
function onMenuModeChange() {
layoutConfig.menuMode = menuMode.value;
}
</script>
<template>
@@ -240,7 +236,7 @@ function onMenuModeChange() {
</div>
<div class="flex flex-col gap-2">
<span class="text-sm text-muted-color font-semibold">Menu Mode</span>
<SelectButton v-model="menuMode" @change="onMenuModeChange" :options="menuModeOptions" :allowEmpty="false" optionLabel="label" optionValue="value" />
<SelectButton v-model="menuMode" @change="changeMenuMode" :options="menuModeOptions" :allowEmpty="false" optionLabel="label" optionValue="value" />
</div>
</div>
</div>

View File

@@ -1,71 +1,34 @@
<script setup>
import { useLayout } from '@/layout/composables/layout';
import { computed, ref, watch } from 'vue';
import { computed } from 'vue';
import AppFooter from './AppFooter.vue';
import AppSidebar from './AppSidebar.vue';
import AppTopbar from './AppTopbar.vue';
const { layoutConfig, layoutState, isSidebarActive } = useLayout();
const outsideClickListener = ref(null);
watch(isSidebarActive, (newVal) => {
if (newVal) {
bindOutsideClickListener();
} else {
unbindOutsideClickListener();
}
});
const { layoutConfig, layoutState, hideMobileMenu } = useLayout();
const containerClass = computed(() => {
return {
'layout-overlay': layoutConfig.menuMode === 'overlay',
'layout-static': layoutConfig.menuMode === 'static',
'layout-static-inactive': layoutState.staticMenuDesktopInactive && layoutConfig.menuMode === 'static',
'layout-overlay-active': layoutState.overlayMenuActive,
'layout-mobile-active': layoutState.staticMenuMobileActive
'layout-mobile-active': layoutState.mobileMenuActive,
'layout-static-inactive': layoutState.staticMenuInactive
};
});
function bindOutsideClickListener() {
if (!outsideClickListener.value) {
outsideClickListener.value = (event) => {
if (isOutsideClicked(event)) {
layoutState.overlayMenuActive = false;
layoutState.staticMenuMobileActive = false;
layoutState.menuHoverActive = false;
}
};
document.addEventListener('click', outsideClickListener.value);
}
}
function unbindOutsideClickListener() {
if (outsideClickListener.value) {
document.removeEventListener('click', outsideClickListener);
outsideClickListener.value = null;
}
}
function isOutsideClicked(event) {
const sidebarEl = document.querySelector('.layout-sidebar');
const topbarEl = document.querySelector('.layout-menu-button');
return !(sidebarEl.isSameNode(event.target) || sidebarEl.contains(event.target) || topbarEl.isSameNode(event.target) || topbarEl.contains(event.target));
}
</script>
<template>
<div class="layout-wrapper" :class="containerClass">
<app-topbar></app-topbar>
<app-sidebar></app-sidebar>
<AppTopbar />
<AppSidebar />
<div class="layout-main-container">
<div class="layout-main">
<router-view></router-view>
<router-view />
</div>
<app-footer></app-footer>
<AppFooter />
</div>
<div class="layout-mask animate-fadein"></div>
<div class="layout-mask animate-fadein" @click="hideMobileMenu" />
</div>
<Toast />
</template>

View File

@@ -1,41 +1,109 @@
<script setup>
import { ref } from 'vue';
import AppMenuItem from './AppMenuItem.vue';
const model = ref([
{
label: 'Home',
items: [{ label: 'Dashboard', icon: 'pi pi-fw pi-home', to: '/' }]
items: [
{
label: 'Dashboard',
icon: 'pi pi-fw pi-home',
to: '/'
}
]
},
{
label: 'UI Components',
path: '/uikit',
items: [
{ label: 'Form Layout', icon: 'pi pi-fw pi-id-card', to: '/uikit/formlayout' },
{ label: 'Input', icon: 'pi pi-fw pi-check-square', to: '/uikit/input' },
{ label: 'Button', icon: 'pi pi-fw pi-mobile', to: '/uikit/button', class: 'rotated-icon' },
{ label: 'Table', icon: 'pi pi-fw pi-table', to: '/uikit/table' },
{ label: 'List', icon: 'pi pi-fw pi-list', to: '/uikit/list' },
{ label: 'Tree', icon: 'pi pi-fw pi-share-alt', to: '/uikit/tree' },
{ label: 'Panel', icon: 'pi pi-fw pi-tablet', to: '/uikit/panel' },
{ label: 'Overlay', icon: 'pi pi-fw pi-clone', to: '/uikit/overlay' },
{ label: 'Media', icon: 'pi pi-fw pi-image', to: '/uikit/media' },
{ label: 'Menu', icon: 'pi pi-fw pi-bars', to: '/uikit/menu' },
{ label: 'Message', icon: 'pi pi-fw pi-comment', to: '/uikit/message' },
{ label: 'File', icon: 'pi pi-fw pi-file', to: '/uikit/file' },
{ label: 'Chart', icon: 'pi pi-fw pi-chart-bar', to: '/uikit/charts' },
{ label: 'Timeline', icon: 'pi pi-fw pi-calendar', to: '/uikit/timeline' },
{ label: 'Misc', icon: 'pi pi-fw pi-circle', to: '/uikit/misc' }
{
label: 'Form Layout',
icon: 'pi pi-fw pi-id-card',
to: '/uikit/formlayout'
},
{
label: 'Input',
icon: 'pi pi-fw pi-check-square',
to: '/uikit/input'
},
{
label: 'Button',
icon: 'pi pi-fw pi-mobile',
to: '/uikit/button',
class: 'rotated-icon'
},
{
label: 'Table',
icon: 'pi pi-fw pi-table',
to: '/uikit/table'
},
{
label: 'List',
icon: 'pi pi-fw pi-list',
to: '/uikit/list'
},
{
label: 'Tree',
icon: 'pi pi-fw pi-share-alt',
to: '/uikit/tree'
},
{
label: 'Panel',
icon: 'pi pi-fw pi-tablet',
to: '/uikit/panel'
},
{
label: 'Overlay',
icon: 'pi pi-fw pi-clone',
to: '/uikit/overlay'
},
{
label: 'Media',
icon: 'pi pi-fw pi-image',
to: '/uikit/media'
},
{
label: 'Menu',
icon: 'pi pi-fw pi-bars',
to: '/uikit/menu'
},
{
label: 'Message',
icon: 'pi pi-fw pi-comment',
to: '/uikit/message'
},
{
label: 'File',
icon: 'pi pi-fw pi-file',
to: '/uikit/file'
},
{
label: 'Chart',
icon: 'pi pi-fw pi-chart-bar',
to: '/uikit/charts'
},
{
label: 'Timeline',
icon: 'pi pi-fw pi-calendar',
to: '/uikit/timeline'
},
{
label: 'Misc',
icon: 'pi pi-fw pi-circle',
to: '/uikit/misc'
}
]
},
{
label: 'Prime Blocks',
icon: 'pi pi-fw pi-prime',
path: '/blocks',
items: [
{
label: 'Free Blocks',
icon: 'pi pi-fw pi-eye',
to: '/blocks'
to: '/blocks/free'
},
{
label: 'All Blocks',
@@ -48,7 +116,7 @@ const model = ref([
{
label: 'Pages',
icon: 'pi pi-fw pi-briefcase',
to: '/pages',
path: '/pages',
items: [
{
label: 'Landing',
@@ -58,6 +126,7 @@ const model = ref([
{
label: 'Auth',
icon: 'pi pi-fw pi-user',
path: '/auth',
items: [
{
label: 'Login',
@@ -95,43 +164,76 @@ const model = ref([
},
{
label: 'Hierarchy',
icon: 'pi pi-fw pi-align-left',
path: '/hierarchy',
items: [
{
label: 'Submenu 1',
icon: 'pi pi-fw pi-bookmark',
icon: 'pi pi-fw pi-align-left',
path: '/submenu_1',
items: [
{
label: 'Submenu 1.1',
icon: 'pi pi-fw pi-bookmark',
icon: 'pi pi-fw pi-align-left',
path: '/submenu_1_1',
items: [
{ label: 'Submenu 1.1.1', icon: 'pi pi-fw pi-bookmark' },
{ label: 'Submenu 1.1.2', icon: 'pi pi-fw pi-bookmark' },
{ label: 'Submenu 1.1.3', icon: 'pi pi-fw pi-bookmark' }
{
label: 'Submenu 1.1.1',
icon: 'pi pi-fw pi-align-left'
},
{
label: 'Submenu 1.1.2',
icon: 'pi pi-fw pi-align-left'
},
{
label: 'Submenu 1.1.3',
icon: 'pi pi-fw pi-align-left'
}
]
},
{
label: 'Submenu 1.2',
icon: 'pi pi-fw pi-bookmark',
items: [{ label: 'Submenu 1.2.1', icon: 'pi pi-fw pi-bookmark' }]
icon: 'pi pi-fw pi-align-left',
path: '/submenu_1_2',
items: [
{
label: 'Submenu 1.2.1',
icon: 'pi pi-fw pi-align-left'
}
]
}
]
},
{
label: 'Submenu 2',
icon: 'pi pi-fw pi-bookmark',
icon: 'pi pi-fw pi-align-left',
path: '/submenu_2',
items: [
{
label: 'Submenu 2.1',
icon: 'pi pi-fw pi-bookmark',
icon: 'pi pi-fw pi-align-left',
path: '/submenu_2_1',
items: [
{ label: 'Submenu 2.1.1', icon: 'pi pi-fw pi-bookmark' },
{ label: 'Submenu 2.1.2', icon: 'pi pi-fw pi-bookmark' }
{
label: 'Submenu 2.1.1',
icon: 'pi pi-fw pi-align-left'
},
{
label: 'Submenu 2.1.2',
icon: 'pi pi-fw pi-align-left'
}
]
},
{
label: 'Submenu 2.2',
icon: 'pi pi-fw pi-bookmark',
items: [{ label: 'Submenu 2.2.1', icon: 'pi pi-fw pi-bookmark' }]
icon: 'pi pi-fw pi-align-left',
path: '/submenu_2_2',
items: [
{
label: 'Submenu 2.2.1',
icon: 'pi pi-fw pi-align-left'
}
]
}
]
}
@@ -139,11 +241,12 @@ const model = ref([
},
{
label: 'Get Started',
path: '/start',
items: [
{
label: 'Documentation',
icon: 'pi pi-fw pi-book',
to: '/documentation'
to: '/start/documentation'
},
{
label: 'View Source',

View File

@@ -1,92 +1,78 @@
<script setup>
import { useLayout } from '@/layout/composables/layout';
import { onBeforeMount, ref, watch } from 'vue';
import { useRoute } from 'vue-router';
import { computed } from 'vue';
const route = useRoute();
const { layoutState, setActiveMenuItem, toggleMenu } = useLayout();
const { layoutState, isDesktop } = useLayout();
const props = defineProps({
item: {
type: Object,
default: () => ({})
},
index: {
type: Number,
default: 0
},
root: {
type: Boolean,
default: true
},
parentItemKey: {
parentPath: {
type: String,
default: null
}
});
const isActiveMenu = ref(false);
const itemKey = ref(null);
const fullPath = computed(() => (props.item.path ? (props.parentPath ? props.parentPath + props.item.path : props.item.path) : null));
onBeforeMount(() => {
itemKey.value = props.parentItemKey ? props.parentItemKey + '-' + props.index : String(props.index);
const activeItem = layoutState.activeMenuItem;
isActiveMenu.value = activeItem === itemKey.value || activeItem ? activeItem.startsWith(itemKey.value + '-') : false;
const isActive = computed(() => {
return props.item.path ? layoutState.activePath?.startsWith(fullPath.value) : layoutState.activePath === props.item.to;
});
watch(
() => layoutState.activeMenuItem,
(newVal) => {
isActiveMenu.value = newVal === itemKey.value || newVal.startsWith(itemKey.value + '-');
}
);
function itemClick(event, item) {
const itemClick = (event, item) => {
if (item.disabled) {
event.preventDefault();
return;
}
if ((item.to || item.url) && (layoutState.staticMenuMobileActive || layoutState.overlayMenuActive)) {
toggleMenu();
}
if (item.command) {
item.command({ originalEvent: event, item: item });
}
const foundItemKey = item.items ? (isActiveMenu.value ? props.parentItemKey : itemKey) : itemKey.value;
if (item.items) {
if (isActive.value) {
layoutState.activePath = layoutState.activePath.replace(item.path, '');
} else {
layoutState.activePath = fullPath.value;
layoutState.menuHoverActive = true;
}
} else {
layoutState.overlayMenuActive = false;
layoutState.mobileMenuActive = false;
layoutState.menuHoverActive = false;
}
};
setActiveMenuItem(foundItemKey);
}
function checkActiveRoute(item) {
return route.path === item.to;
}
const onMouseEnter = () => {
if (isDesktop() && props.root && props.item.items && layoutState.menuHoverActive) {
layoutState.activePath = fullPath.value;
}
};
</script>
<template>
<li :class="{ 'layout-root-menuitem': root, 'active-menuitem': isActiveMenu }">
<li :class="{ 'layout-root-menuitem': root, 'active-menuitem': isActive }">
<div v-if="root && item.visible !== false" class="layout-menuitem-root-text">{{ item.label }}</div>
<a v-if="(!item.to || item.items) && item.visible !== false" :href="item.url" @click="itemClick($event, item, index)" :class="item.class" :target="item.target" tabindex="0">
<i :class="item.icon" class="layout-menuitem-icon"></i>
<a v-if="(!item.to || item.items) && item.visible !== false" :href="item.url" @click="itemClick($event, item)" :class="item.class" :target="item.target" tabindex="0" @mouseenter="onMouseEnter">
<i :class="item.icon" class="layout-menuitem-icon" />
<span class="layout-menuitem-text">{{ item.label }}</span>
<i class="pi pi-fw pi-angle-down layout-submenu-toggler" v-if="item.items"></i>
<i class="pi pi-fw pi-angle-down layout-submenu-toggler" v-if="item.items" />
</a>
<router-link v-if="item.to && !item.items && item.visible !== false" @click="itemClick($event, item, index)" :class="[item.class, { 'active-route': checkActiveRoute(item) }]" tabindex="0" :to="item.to">
<i :class="item.icon" class="layout-menuitem-icon"></i>
<router-link v-if="item.to && !item.items && item.visible !== false" @click="itemClick($event, item)" exactActiveClass="active-route" :class="item.class" tabindex="0" :to="item.to" @mouseenter="onMouseEnter">
<i :class="item.icon" class="layout-menuitem-icon" />
<span class="layout-menuitem-text">{{ item.label }}</span>
<i class="pi pi-fw pi-angle-down layout-submenu-toggler" v-if="item.items"></i>
<i class="pi pi-fw pi-angle-down layout-submenu-toggler" v-if="item.items" />
</router-link>
<Transition v-if="item.items && item.visible !== false" name="layout-submenu">
<ul v-show="root ? true : isActiveMenu" class="layout-submenu">
<app-menu-item v-for="(child, i) in item.items" :key="child" :index="i" :item="child" :parentItemKey="itemKey" :root="false"></app-menu-item>
<ul v-show="root ? true : isActive" class="layout-submenu">
<app-menu-item v-for="child in item.items" :key="child.label + '_' + (child.to || child.path)" :item="child" :root="false" :parentPath="fullPath" />
</ul>
</Transition>
</li>
</template>
<style lang="scss" scoped></style>

View File

@@ -1,11 +1,66 @@
<script setup>
import { useLayout } from '@/layout/composables/layout';
import { onBeforeUnmount, ref, watch } from 'vue';
import { useRoute } from 'vue-router';
import AppMenu from './AppMenu.vue';
const { layoutState, isDesktop, hasOpenOverlay } = useLayout();
const route = useRoute();
const sidebarRef = ref(null);
let outsideClickListener = null;
watch(
() => route.path,
(newPath) => {
if (isDesktop()) layoutState.activePath = null;
else layoutState.activePath = newPath;
layoutState.overlayMenuActive = false;
layoutState.mobileMenuActive = false;
layoutState.menuHoverActive = false;
},
{ immediate: true }
);
watch(hasOpenOverlay, (newVal) => {
if (isDesktop()) {
if (newVal) bindOutsideClickListener();
else unbindOutsideClickListener();
}
});
const bindOutsideClickListener = () => {
if (!outsideClickListener) {
outsideClickListener = (event) => {
if (isOutsideClicked(event)) {
layoutState.overlayMenuActive = false;
}
};
document.addEventListener('click', outsideClickListener);
}
};
const unbindOutsideClickListener = () => {
if (outsideClickListener) {
document.removeEventListener('click', outsideClickListener);
outsideClickListener = null;
}
};
const isOutsideClicked = (event) => {
const topbarButtonEl = document.querySelector('.layout-menu-button');
return !(sidebarRef.value.isSameNode(event.target) || sidebarRef.value.contains(event.target) || topbarButtonEl?.isSameNode(event.target) || topbarButtonEl?.contains(event.target));
};
onBeforeUnmount(() => {
unbindOutsideClickListener();
});
</script>
<template>
<div class="layout-sidebar">
<app-menu></app-menu>
<div ref="sidebarRef" class="layout-sidebar">
<AppMenu />
</div>
</template>
<style lang="scss" scoped></style>

View File

@@ -9,20 +9,17 @@ const layoutConfig = reactive({
});
const layoutState = reactive({
staticMenuDesktopInactive: false,
staticMenuInactive: false,
overlayMenuActive: false,
profileSidebarVisible: false,
configSidebarVisible: false,
staticMenuMobileActive: false,
sidebarExpanded: false,
menuHoverActive: false,
activeMenuItem: null
activeMenuItem: null,
activePath: null
});
export function useLayout() {
const setActiveMenuItem = (item) => {
layoutState.activeMenuItem = item.value || item;
};
const toggleDarkMode = () => {
if (!document.startViewTransition) {
executeDarkModeToggle();
@@ -39,34 +36,51 @@ export function useLayout() {
};
const toggleMenu = () => {
if (layoutConfig.menuMode === 'overlay') {
layoutState.overlayMenuActive = !layoutState.overlayMenuActive;
}
if (isDesktop()) {
if (layoutConfig.menuMode === 'static') {
layoutState.staticMenuInactive = !layoutState.staticMenuInactive;
}
if (window.innerWidth > 991) {
layoutState.staticMenuDesktopInactive = !layoutState.staticMenuDesktopInactive;
if (layoutConfig.menuMode === 'overlay') {
layoutState.overlayMenuActive = !layoutState.overlayMenuActive;
}
} else {
layoutState.staticMenuMobileActive = !layoutState.staticMenuMobileActive;
layoutState.mobileMenuActive = !layoutState.mobileMenuActive;
}
};
const isSidebarActive = computed(() => layoutState.overlayMenuActive || layoutState.staticMenuMobileActive);
const toggleConfigSidebar = () => {
layoutState.configSidebarVisible = !layoutState.configSidebarVisible;
};
const hideMobileMenu = () => {
layoutState.mobileMenuActive = false;
};
const changeMenuMode = (event) => {
layoutConfig.menuMode = event.value;
layoutState.staticMenuInactive = false;
layoutState.mobileMenuActive = false;
layoutState.sidebarExpanded = false;
layoutState.menuHoverActive = false;
layoutState.anchored = false;
};
const isDarkTheme = computed(() => layoutConfig.darkTheme);
const isDesktop = () => window.innerWidth > 991;
const getPrimary = computed(() => layoutConfig.primary);
const getSurface = computed(() => layoutConfig.surface);
const hasOpenOverlay = computed(() => layoutState.overlayMenuActive);
return {
layoutConfig,
layoutState,
toggleMenu,
isSidebarActive,
isDarkTheme,
getPrimary,
getSurface,
setActiveMenuItem,
toggleDarkMode
toggleDarkMode,
toggleConfigSidebar,
toggleMenu,
hideMobileMenu,
changeMenuMode,
isDesktop,
hasOpenOverlay
};
}

View File

@@ -90,7 +90,7 @@ const router = createRouter({
component: () => import('@/views/uikit/TimelineDoc.vue')
},
{
path: '/blocks',
path: '/blocks/free',
name: 'blocks',
meta: {
breadcrumb: ['Prime Blocks', 'Free Blocks']
@@ -108,7 +108,7 @@ const router = createRouter({
component: () => import('@/views/pages/Crud.vue')
},
{
path: '/documentation',
path: '/start/documentation',
name: 'documentation',
component: () => import('@/views/pages/Documentation.vue')
}

View File

@@ -2,7 +2,7 @@
import { useLayout } from '@/layout/composables/layout';
import { onMounted, ref, watch } from 'vue';
const { getPrimary, getSurface, isDarkTheme } = useLayout();
const { layoutConfig, isDarkTheme } = useLayout();
const lineData = ref(null);
const pieData = ref(null);
const polarData = ref(null);
@@ -219,7 +219,7 @@ function setColorOptions() {
}
watch(
[getPrimary, getSurface, isDarkTheme],
[() => layoutConfig.primary, () => layoutConfig.surface, isDarkTheme],
() => {
setColorOptions();
},