feat: restructure sidebar to two-panel layout with icon rail and content panel; add tooltip functionality and remove header
This commit is contained in:
parent
ca32004d82
commit
d8edede37a
@ -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>
|
||||||
|
|||||||
105
docs/history/2026-07-25-sidebar-dua-panel.md
Normal file
105
docs/history/2026-07-25-sidebar-dua-panel.md
Normal 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
BIN
public/assets/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
Loading…
Reference in New Issue
Block a user