feat(spa): sakai-style topbar + sidebar layout
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { RouterView } from "vue-router";
|
||||
import AppTopbar from "./AppTopbar.vue";
|
||||
import AppSidebar from "./AppSidebar.vue";
|
||||
import { useUiStore } from "../stores/uiStore";
|
||||
|
||||
const ui = useUiStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col min-h-screen">
|
||||
<AppTopbar />
|
||||
<div class="flex flex-1 min-h-0">
|
||||
<AppSidebar v-show="!ui.sidebarCollapsed" />
|
||||
<main class="flex-1 overflow-auto bg-wp-bg">
|
||||
<RouterView />
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,42 @@
|
||||
<script setup lang="ts">
|
||||
import AppMenuItem from "./AppMenuItem.vue";
|
||||
|
||||
interface MenuItem { label: string; icon: string; to: string; }
|
||||
interface MenuSection { label: string; items: MenuItem[]; }
|
||||
|
||||
const sections: MenuSection[] = [
|
||||
{
|
||||
label: "Modules",
|
||||
items: [
|
||||
{ label: "All modules", icon: "pi pi-list", to: "/modules" },
|
||||
{ label: "+ New wildcard", icon: "pi pi-plus", to: "/modules/wildcard/new" },
|
||||
{ label: "+ New fixed values", icon: "pi pi-plus", to: "/modules/fixed-values/new" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Library",
|
||||
items: [
|
||||
{ label: "Categories", icon: "pi pi-tag", to: "/categories" },
|
||||
{ label: "Import / Export", icon: "pi pi-arrow-right-arrow-left", to: "/import-export" },
|
||||
{ label: "Test runner", icon: "pi pi-bolt", to: "/test" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "App",
|
||||
items: [{ label: "Settings", icon: "pi pi-cog", to: "/settings" }],
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav>
|
||||
<div v-for="section in sections" :key="section.label" class="mb-3">
|
||||
<div class="px-4 py-1 text-[10px] uppercase tracking-wider text-wp-text3 font-semibold">
|
||||
{{ section.label }}
|
||||
</div>
|
||||
<ul class="list-none p-0 m-0">
|
||||
<AppMenuItem v-for="item in section.items" :key="item.to" :item="item" />
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
@@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { RouterLink } from "vue-router";
|
||||
|
||||
defineProps<{
|
||||
item: { label: string; icon: string; to: string };
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<li>
|
||||
<RouterLink
|
||||
:to="item.to"
|
||||
class="flex items-center gap-2 px-4 py-2 text-wp-text hover:bg-wp-bg2 border-l-2 border-transparent"
|
||||
active-class="!bg-wp-accent/15 !border-l-[color:var(--wp-accent)] !text-white"
|
||||
>
|
||||
<i :class="['pi', item.icon, 'text-wp-text2']" aria-hidden="true" />
|
||||
<span class="text-sm">{{ item.label }}</span>
|
||||
</RouterLink>
|
||||
</li>
|
||||
</template>
|
||||
@@ -0,0 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import AppMenu from "./AppMenu.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<aside class="w-56 bg-wp-bg border-r border-wp-border overflow-y-auto py-3">
|
||||
<AppMenu />
|
||||
</aside>
|
||||
</template>
|
||||
@@ -0,0 +1,39 @@
|
||||
<script setup lang="ts">
|
||||
import Button from "primevue/button";
|
||||
import { useUiStore } from "../stores/uiStore";
|
||||
|
||||
const ui = useUiStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header class="h-12 px-4 flex items-center justify-between bg-wp-bg border-b border-wp-border sticky top-0 z-10">
|
||||
<div class="flex items-center gap-3">
|
||||
<Button
|
||||
icon="pi pi-bars"
|
||||
text
|
||||
rounded
|
||||
severity="secondary"
|
||||
aria-label="Toggle menu"
|
||||
@click="ui.toggleSidebar"
|
||||
/>
|
||||
<div class="flex items-center gap-2 font-semibold">
|
||||
<div
|
||||
class="w-5 h-5 rounded"
|
||||
style="background: var(--wp-brand-gradient)"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>Wildcard Pipeline</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<Button
|
||||
icon="pi pi-moon"
|
||||
text
|
||||
rounded
|
||||
severity="secondary"
|
||||
aria-label="Toggle theme"
|
||||
@click="ui.toggleTheme"
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
+17
-10
@@ -1,16 +1,23 @@
|
||||
import { createRouter, createWebHistory, type RouteRecordRaw } from "vue-router";
|
||||
import AppLayout from "../layout/AppLayout.vue";
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{ path: "/", redirect: "/modules" },
|
||||
{ path: "/modules", name: "modules", component: () => import("../views/Modules.vue") },
|
||||
{ path: "/modules/wildcard/new", name: "wildcard-new", component: () => import("../views/WildcardForm.vue") },
|
||||
{ path: "/modules/wildcard/:id", name: "wildcard-edit", component: () => import("../views/WildcardForm.vue"), props: true },
|
||||
{ path: "/modules/fixed-values/new", name: "fixed-values-new", component: () => import("../views/FixedValueForm.vue") },
|
||||
{ path: "/modules/fixed-values/:id", name: "fixed-values-edit", component: () => import("../views/FixedValueForm.vue"), props: true },
|
||||
{ path: "/categories", name: "categories", component: () => import("../views/Categories.vue") },
|
||||
{ path: "/import-export", name: "import-export", component: () => import("../views/ImportExport.vue") },
|
||||
{ path: "/test", name: "test", component: () => import("../views/TestRunner.vue") },
|
||||
{ path: "/settings", name: "settings", component: () => import("../views/Settings.vue") },
|
||||
{
|
||||
path: "/",
|
||||
component: AppLayout,
|
||||
children: [
|
||||
{ path: "", redirect: "/modules" },
|
||||
{ path: "modules", name: "modules", component: () => import("../views/Modules.vue") },
|
||||
{ path: "modules/wildcard/new", name: "wildcard-new", component: () => import("../views/WildcardForm.vue") },
|
||||
{ path: "modules/wildcard/:id", name: "wildcard-edit", component: () => import("../views/WildcardForm.vue"), props: true },
|
||||
{ path: "modules/fixed-values/new", name: "fixed-values-new", component: () => import("../views/FixedValueForm.vue") },
|
||||
{ path: "modules/fixed-values/:id", name: "fixed-values-edit", component: () => import("../views/FixedValueForm.vue"), props: true },
|
||||
{ path: "categories", name: "categories", component: () => import("../views/Categories.vue") },
|
||||
{ path: "import-export", name: "import-export", component: () => import("../views/ImportExport.vue") },
|
||||
{ path: "test", name: "test", component: () => import("../views/TestRunner.vue") },
|
||||
{ path: "settings", name: "settings", component: () => import("../views/Settings.vue") },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default createRouter({
|
||||
|
||||
Reference in New Issue
Block a user