Skip to content

Commit d2f893a

Browse files
authored
Merge pull request #374 from devforth/sidebarAndHeader
feat: enhance sidebar and header visibility control in custom layouts
2 parents 73d12ef + d133ac2 commit d2f893a

File tree

5 files changed

+64
-11
lines changed

5 files changed

+64
-11
lines changed

adminforth/modules/configValidator.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,22 @@ export default class ConfigValidator implements IConfigValidator {
159159
if (!customization.customPages) {
160160
customization.customPages = [];
161161
}
162+
const normalizeComponent = (comp: any) => {
163+
if (typeof comp === 'string') {
164+
return { file: comp, meta: {} };
165+
}
166+
const meta = comp.meta || {};
167+
if (meta.sidebarAndHeader === undefined) {
168+
meta.sidebarAndHeader = meta.customLayout === true ? 'none' : 'default';
169+
}
170+
delete meta.customLayout;
171+
return { ...comp, meta };
172+
};
173+
162174
customization.customPages.forEach((page, i) => {
163-
this.validateComponent(page.component, errors);
175+
const normalizedComponent = normalizeComponent(page.component);
176+
this.validateComponent(normalizedComponent, errors);
177+
customization.customPages[i].component = normalizedComponent;
164178
});
165179

166180
if (!customization.brandName) { //} === undefined) {

adminforth/spa/src/App.vue

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,15 +75,16 @@
7575
<Sidebar
7676
v-if="loggedIn && routerIsReady && loginRedirectCheckIsReady && defaultLayout"
7777
:sideBarOpen="sideBarOpen"
78+
:forceIconOnly="route.meta?.sidebarAndHeader === 'preferIconOnly'"
7879
@hideSidebar="hideSidebar"
7980
@loadMenu="loadMenu"
8081
@sidebarStateChange="handleSidebarStateChange"
8182
/>
8283

8384
<div class="transition-all duration-300 ease-in-out max-w-[100vw]"
8485
:class="{
85-
'sm:ml-18': isSidebarIconOnly,
86-
'sm:ml-64': !isSidebarIconOnly,
86+
'sm:ml-20': isSidebarIconOnly,
87+
'sm:ml-[264px]': !isSidebarIconOnly,
8788
'sm:max-w-[calc(100%-4.5rem)]': isSidebarIconOnly,
8889
'sm:max-w-[calc(100%-16rem)]': !isSidebarIconOnly
8990
}"
@@ -229,16 +230,19 @@ async function initRouter() {
229230
230231
async function loadMenu() {
231232
await initRouter();
232-
if (!route.meta.customLayout) {
233+
if (route.meta.sidebarAndHeader !== 'none') {
233234
// for custom layouts we don't need to fetch menu
234235
await coreStore.fetchMenuAndResource();
235236
}
236237
loginRedirectCheckIsReady.value = true;
237238
}
238239
239240
function handleCustomLayout() {
240-
if (route.meta?.customLayout) {
241+
if (route.meta?.sidebarAndHeader === 'none') {
241242
defaultLayout.value = false;
243+
} else if (route.meta?.sidebarAndHeader === 'preferIconOnly') {
244+
defaultLayout.value = true;
245+
isSidebarIconOnly.value = true;
242246
} else {
243247
defaultLayout.value = true;
244248
}

adminforth/spa/src/components/Sidebar.vue

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
:adminUser="coreStore.adminUser"
3232
/>
3333
</div>
34-
<div class="absolute top-1.5 -right-4 z-10 hidden sm:block" v-if="iconOnlySidebarEnabled && (!isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering))">
34+
<div class="absolute top-1.5 -right-4 z-10 hidden sm:block" v-if="!forceIconOnly && iconOnlySidebarEnabled && (!isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering))">
3535
<button class="text-sm text-lightSidebarIcons group-hover:text-lightSidebarIconsHover dark:group-hover:text-darkSidebarIconsHover dark:text-darkSidebarIcons" @click="toggleSidebar">
3636
<IconCloseSidebarSolid v-if="!isSidebarIconOnly" class="w-5 h-5 active:scale-95 transition-all duration-200 hover:text-lightSidebarIconsHover dark:hover:text-darkSidebarIconsHover" />
3737
<IconOpenSidebarSolid v-else class="w-5 h-5 active:scale-95 transition-all duration-200 hover:text-lightSidebarIconsHover dark:hover:text-darkSidebarIconsHover" />
@@ -286,6 +286,7 @@ import adminforth from '@/adminforth';
286286
287287
interface Props {
288288
sideBarOpen: boolean;
289+
forceIconOnly?: boolean;
289290
}
290291
291292
const props = defineProps<Props>();
@@ -304,15 +305,21 @@ const sidebarAside = ref(null);
304305
305306
const smQuery = window.matchMedia('(min-width: 640px)');
306307
const isMobile = ref(!smQuery.matches);
307-
const iconOnlySidebarEnabled = computed(() => coreStore.config?.iconOnlySidebar?.enabled !== false);
308-
const isSidebarIconOnly = ref(!isMobile.value && localStorage.getItem('afIconOnlySidebar') === 'true');
308+
const iconOnlySidebarEnabled = computed(() => props.forceIconOnly === true || coreStore.config?.iconOnlySidebar?.enabled !== false);
309+
const isSidebarIconOnly = ref(false);
309310
310311
function handleBreakpointChange(e: MediaQueryListEvent) {
311312
isMobile.value = !e.matches;
312313
if (isMobile.value) {
313314
isSidebarIconOnly.value = false;
314315
} else {
315-
isSidebarIconOnly.value = iconOnlySidebarEnabled.value && localStorage.getItem('afIconOnlySidebar') === 'true';
316+
if (props.forceIconOnly === true) {
317+
isSidebarIconOnly.value = true;
318+
} else if (iconOnlySidebarEnabled.value && localStorage.getItem('afIconOnlySidebar') === 'true') {
319+
isSidebarIconOnly.value = true;
320+
} else {
321+
isSidebarIconOnly.value = false;
322+
}
316323
}
317324
}
318325
@@ -323,6 +330,9 @@ const isSidebarHovering = ref(false);
323330
const isTogglingSidebar = ref(false);
324331
325332
function toggleSidebar() {
333+
if (props.forceIconOnly) {
334+
return;
335+
}
326336
if (!iconOnlySidebarEnabled.value) {
327337
return;
328338
}
@@ -358,7 +368,7 @@ watch(()=>coreStore.menu, () => {
358368
359369
360370
watch(isSidebarIconOnly, (isIconOnly) => {
361-
if (!isMobile.value && iconOnlySidebarEnabled.value) {
371+
if (!isMobile.value && iconOnlySidebarEnabled.value && !props.forceIconOnly) {
362372
localStorage.setItem('afIconOnlySidebar', isIconOnly.toString());
363373
}
364374
emit('sidebarStateChange', { isSidebarIconOnly: isIconOnly, isSidebarHovering: isSidebarHovering.value });
@@ -416,4 +426,18 @@ onMounted(() => {
416426
onUnmounted(() => {
417427
smQuery.removeEventListener('change', handleBreakpointChange);
418428
})
429+
430+
watch(() => props.forceIconOnly, (force) => {
431+
if (isMobile.value) {
432+
isSidebarIconOnly.value = false;
433+
return;
434+
}
435+
if (props.forceIconOnly === true) {
436+
isSidebarIconOnly.value = true;
437+
} else if (iconOnlySidebarEnabled.value && localStorage.getItem('afIconOnlySidebar') === 'true') {
438+
isSidebarIconOnly.value = true;
439+
} else {
440+
isSidebarIconOnly.value = false;
441+
}
442+
}, { immediate: true })
419443
</script>

adminforth/spa/src/router/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ const router = createRouter({
6868
component: () => import('@/views/SettingsView.vue'),
6969
meta: {
7070
title: 'Settings',
71+
sidebarAndHeader: 'preferIconOnly',
7172
},
7273
},
7374
/* IMPORTANT:ADMINFORTH ROUTES */

adminforth/types/Common.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,17 @@ export interface AdminForthComponentDeclarationFull {
261261
* </script>
262262
*
263263
*/
264-
meta?: any,
264+
meta?: {
265+
/**
266+
* Controls sidebar and header visibility for custom pages
267+
* - 'default': Show both sidebar and header (default behavior)
268+
* - 'none': Hide both sidebar and header (full custom layout)
269+
* - 'preferIconOnly': Show header but prefer icon-only sidebar
270+
*/
271+
sidebarAndHeader?: 'default' | 'none' | 'preferIconOnly',
272+
273+
[key: string]: any,
274+
}
265275
}
266276
import { type AdminForthActionInput } from './Back.js'
267277
export { type AdminForthActionInput } from './Back.js'

0 commit comments

Comments
 (0)