feat: restructure sidebar to two-panel layout with icon rail and content panel; add tooltip functionality and remove header

This commit is contained in:
Yoga Pangestu 2026-07-25 11:41:03 +07:00
parent ca32004d82
commit d8edede37a
3 changed files with 185 additions and 53 deletions

View File

@ -1,69 +1,93 @@
<script setup lang="ts"> <script setup lang="ts">
import { import {
BriefcaseBusiness,
CreditCard, CreditCard,
Database, Database,
Grid, FolderKanban,
HelpCircle, LayoutDashboard
Settings
} from "@lucide/vue" } from "@lucide/vue"
import { computed, ref, watch } from 'vue'
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip'
import { import {
Sidebar, Sidebar,
SidebarContent, SidebarContent,
SidebarGroup,
SidebarGroupContent, SidebarGroupContent,
SidebarGroupLabel, SidebarGroupLabel,
SidebarHeader,
SidebarMenu, SidebarMenu,
SidebarMenuButton, SidebarMenuButton,
SidebarMenuItem, SidebarMenuItem,
} from '@/components/ui/sidebar' } from '@/components/ui/sidebar'
const data = { const route = useRoute()
user: {
name: "shadcn", const groups = [
email: "m@example.com",
avatar: "/avatars/shadcn.jpg",
},
navMain: [
{ {
title: "Dashboard", title: 'Master',
url: "/admin/dashboard", icon: FolderKanban,
icon: Grid, items: [
}, { title: 'Dashboard', url: '/admin/dashboard', icon: LayoutDashboard },
{ { title: 'Jenis Bisnis', url: '/admin/business-types', icon: BriefcaseBusiness },
title: "Jenis Bisnis", { title: 'Plans', url: '/admin/plans', icon: CreditCard },
url: "/admin/business-types",
icon: Grid,
},
{
title: "Plans",
url: "/admin/plans",
icon: CreditCard,
},
], ],
} },
]
const activeGroup = ref(
groups.find(g => g.items.some(it => route.path === it.url))?.title ?? groups[0].title
)
watch(() => route.path, (path) => {
const found = groups.find(g => g.items.some(it => it.url === path))
if (found) activeGroup.value = found.title
})
const currentGroup = computed(() => groups.find(g => g.title === activeGroup.value))
</script> </script>
<template> <template>
<Sidebar collapsible="icon"> <Sidebar collapsible="icon">
<SidebarHeader> <div class="flex flex-row h-full w-full">
<SidebarMenu> <!-- Left: Icon Rail -->
<SidebarMenuItem> <div class="flex w-(--sidebar-width-icon) shrink-0 flex-col items-center border-r py-2 gap-1">
<SidebarMenuButton as-child class="data-[slot=sidebar-menu-button]:p-1.5!"> <SidebarMenuButton as-child class="p-1.5! mb-2">
<a href="#"> <a href="#">
<Database class="size-6!" /> <img src="/assets/icon.png" class="size-6!" />
<span class="text-lg font-semibold">Profitra</span>
</a> </a>
</SidebarMenuButton> </SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu> <Tooltip v-for="g in groups" :key="g.title">
</SidebarHeader> <TooltipTrigger as-child>
<SidebarMenuButton as-child size="lg" :data-active="activeGroup === g.title || undefined"
@click="activeGroup = g.title">
<button>
<component :is="g.icon" />
</button>
</SidebarMenuButton>
</TooltipTrigger>
<TooltipContent side="right">
{{ g.title }}
</TooltipContent>
</Tooltip>
</div>
<!-- Right: Content Panel -->
<div class="flex flex-1 flex-col min-w-0">
<SidebarContent> <SidebarContent>
<SidebarGroupContent class="flex flex-col gap-2"> <SidebarGroup>
<SidebarGroupLabel>Master</SidebarGroupLabel> <SidebarGroupLabel class="text-lg font-bold mb-5">
{{ currentGroup?.title }}
</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu> <SidebarMenu>
<SidebarMenuItem v-for="item in data.navMain" :key="item.title"> <SidebarMenuItem v-for="item in currentGroup?.items" :key="item.title">
<SidebarMenuButton as-child :tooltip="item.title"> <SidebarMenuButton as-child :is-active="route.path === item.url">
<NuxtLink :to="item.url"> <NuxtLink :to="item.url">
<component :is="item.icon" v-if="item.icon" /> <component :is="item.icon" v-if="item.icon" />
<span>{{ item.title }}</span> <span>{{ item.title }}</span>
@ -72,6 +96,9 @@ const data = {
</SidebarMenuItem> </SidebarMenuItem>
</SidebarMenu> </SidebarMenu>
</SidebarGroupContent> </SidebarGroupContent>
</SidebarGroup>
</SidebarContent> </SidebarContent>
</div>
</div>
</Sidebar> </Sidebar>
</template> </template>

View File

@ -0,0 +1,105 @@
# Sidebar Dua Panel (Icon Rail + Content Panel)
**Tanggal:** 2026-07-25
**Status:** Selesai
## Tujuan
Merubah struktur sidebar dari satu panel menjadi dua panel: Icon Rail (kiri) berisi icon group menu, dan Content Panel (kanan) berisi sub-menu dari group yang dipilih.
## Yang Dikerjakan
### 1. Restrukturisasi Data Menu
Data menu diubah dari flat `navMain` menjadi grouped `groups[]` agar mendukung multiple group.
**Sebelum:**
```ts
navMain: [
{ title: "Dashboard", url: "/admin/dashboard", icon: Grid },
{ title: "Jenis Bisnis", url: "/admin/business-types", icon: Grid },
{ title: "Plans", url: "/admin/plans", icon: CreditCard },
]
```
**Sesudah:**
```ts
const activeGroup = ref('Master')
const groups = [
{
title: 'Master',
icon: Database,
items: [
{ title: 'Dashboard', url: '/admin/dashboard', icon: Grid },
{ title: 'Jenis Bisnis', url: '/admin/business-types', icon: Grid },
{ title: 'Plans', url: '/admin/plans', icon: CreditCard },
],
},
]
```
### 2. Perubahan Layout Sidebar
Sidebar diubah dari single panel menjadi dua panel berdampingan (flex-row).
**Layout Baru:**
```
┌─────────────────────────────────────────────┐
│ [Icon Rail] │ [Content Panel] │
│ ~3rem fixed │ flex-1 │
├─────────────────────┼───────────────────────┤
│ ◉ [Database] │ Master │
│ (Logo) │ ────────────── │
│ │ • Dashboard │
│ ◉ [Database] ◀aktif │ • Jenis Bisnis │
│ (Master) │ • Plans │
│ ↑ hover → "Master"│ │
└─────────────────────────────────────────────┘
```
### 3. Tooltip pada Icon Rail
- Sebelumnya menggunakan `:tooltip` prop dari `SidebarMenuButton` yang hanya muncul saat sidebar collapsed
- Sekarang menggunakan `Tooltip` + `TooltipContent` dari `@/components/ui/tooltip` agar selalu muncul di hover
### 4. Hapus Header "Profitra"
- `SidebarHeader` yang berisi tulisan "Profitra" dihapus
- Sub-menu langsung berada di bagian atas panel kanan (tanpa spasi kosong)
### 5. Group Label Styling
- `SidebarGroupLabel` diberi `class="text-lg font-bold"` agar lebih besar dan tebal
### 6. Sistem Filter Group
- Klik icon group di panel kiri → `activeGroup` berubah → panel kanan menampilkan sub-menu dari group yang dipilih
- Icon yang aktif mendapat highlight via CSS `data-active="true"`
## File yang Diubah
| File | Aksi | Detail |
|------|------|--------|
| `app/components/AppSidebar.vue` | Diubah | Restrukturisasi dari flat menu ke grouped menu dengan two-panel layout, tooltip custom, hapus header, styling label |
### 7. Active State Menu
- Menu item yang sedang aktif (sesuai URL route) mendapat `is-active` → muncul highlight background
- `activeGroup` otomatis terdeteksi dari route path saat halaman dimuat
- Ketika navigasi ke halaman lain, `activeGroup` otomatis berubah mengikuti route baru
- User tetap bisa klik icon group secara manual untuk melihat group lain
### 8. Route Detection
```ts
const route = useRoute()
// Auto-detect active group from current URL
const activeGroup = ref(
groups.find(g => g.items.some(it => route.path === it.url))?.title ?? groups[0].title
)
// Watch route changes
watch(() => route.path, (path) => {
const found = groups.find(g => g.items.some(it => it.url === path))
if (found) activeGroup.value = found.title
})
```
## Notes
- Menggunakan `collapsible="icon"` agar spacing mechanism (peer) tetap berfungsi dengan `SidebarInset`
- Tooltip menggunakan `Tooltip` dari reka-ui via `@/components/ui/tooltip` agar muncul di hover meski sidebar expanded
- `useRoute()` auto-import dari Nuxt
- Siap untuk penambahan group baru (Manaje, Konten, dll) cukup dengan menambah entry di array `groups`

BIN
public/assets/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB