docs: add sidebar icon-rail collapse design spec

This commit is contained in:
Nut.ไปเรื่อย
2026-07-07 00:05:34 +07:00
parent 5bc4f8333f
commit d627a121d8
@@ -0,0 +1,64 @@
# Sidebar collapse-to-icon-rail — Design Spec
## Problem
The main sidebar (`app-menu-bar`) is a fixed 260px-wide column with icons + Thai text labels for every menu item. The only existing toggle (hamburger button in the toolbar) hides the entire sidebar off-screen via a negative `margin-left` — there is no "shrink to icon-only rail" mode like the reference screenshots the user provided (a narrow ~64-72px column showing just icons, with a small `>>` affordance to re-expand).
## Goal
Repurpose the existing hamburger toggle so it switches the sidebar between:
- **Full mode** (current behavior): 260px wide, icons + text, current accordion sub-menus.
- **Rail mode** (new): ~64-72px wide, icons only. Leaf items still navigate on click. Items with children (accordion groups) show a floating flyout popup (triggered by hover/click) listing the children as full-text links.
The hamburger no longer hides the sidebar entirely — it only toggles between these two width states. State persists across reloads via `localStorage`.
## Current architecture (relevant files)
- `src/app/layout/main-layout/main-layout.component.{ts,html}` — owns `isExpanedMenu: boolean`, applies `margin-left: -280px` to hide the sidebar, listens to `(expanedMenu)` from the toolbar.
- `src/app/layout/components/tool-bar/tool-bar.component.ts` — hamburger `<button class="menu-toggle-btn" (click)="toggleMenu()">`, emits `expanedMenu` event.
- `src/app/layout/components/menu-bar/menu-bar.component.{ts,html,scss}` — sidebar shell. `menu-bar.component.html:1` hardcodes `style="min-width: 260px; max-width: 260px; ..."`. Iterates `menus` (= `routes` from `navigator.ts`) and renders `app-head-menu` / `app-basic-menu` / `app-collapsable` / divider per `item.type`.
- `src/app/layout/components/menu/head-menu/*` — flat top-level leaf link (icon + text).
- `src/app/layout/components/menu/basic-menu/*` — leaf link, used at any nesting depth.
- `src/app/layout/components/menu/collapsable/*` — expandable parent; recurses the same `*ngFor` type-switch over `item.children`. Existing accordion expand/collapse state (`isCollapsed`) is per-group and unrelated to the new rail-mode toggle.
- `src/app/core/data/navigator.ts` — the `SeItem[]` data array driving the whole menu (`id, title, type, icon, children, link, ischildActive, ...`). Child items conventionally get `icon: 'fiber_manual_record'`, styled down to a 7px dot via `collapsable.component.scss` — i.e. children have no meaningful icon of their own, which is why rail mode must present them via a text flyout rather than shrinking them to an icon.
## Design
### 1. State: `isRailMode`
- Add `isRailMode: boolean` to `MainLayoutComponent`, replacing the current use of `isExpanedMenu` for the hide/show margin trick. Initialize from `localStorage.getItem('sidebar_rail_mode') === 'true'` on construction; write back on every toggle.
- `ToolBarComponent`'s existing hamburger button keeps emitting the same `expanedMenu` (or a renamed but equivalent) event; `MainLayoutComponent` flips `isRailMode` and persists it.
- Pass `isRailMode` into `MenuBarComponent` as an `@Input()`. `MenuBarComponent` passes it straight through to `app-head-menu` / `app-basic-menu` / `app-collapsable` as an `@Input()` on each, since the same three components recurse at every nesting depth.
### 2. Sidebar shell width
- `menu-bar.component.html`: replace the hardcoded inline `min-width/max-width: 260px` with an `[ngClass]`/`[ngStyle]` binding driven by `isRailMode` (260px vs. ~68px), plus a CSS `transition: width 0.2s ease` (or reuse `SEAnimations` conventions already used for the accordion expand) so the resize animates smoothly.
- Header block (logo + "มทร.รัตนโกสินทร์ / ระบบแผนงานและงบประมาณ"): wrap the text block in `*ngIf="!isRailMode"`; keep the logo icon visible and centered in both modes.
- Divider items: hide entirely in rail mode (`*ngIf="!isRailMode"` on the divider `<div>`), since a horizontal rule with no label doesn't carry meaning at that width.
### 3. Leaf items (`head-menu`, `basic-menu`)
- Title `<span>` wrapped in `*ngIf="!isRailMode"`.
- When `isRailMode` is true, add a native `title="{{item.title}}"` attribute on the icon button/row so hovering shows the label as a browser tooltip — cheap, no new UI component needed.
- Click behavior unchanged (still navigates via existing `link`/`routerLink` handling).
### 4. Parent items with children (`collapsable`)
- When `isRailMode` is true, the component no longer renders its children inline/expanded in the document flow. Instead:
- The parent icon becomes the trigger for a flyout popup.
- Flyout opens on `mouseenter` of the icon (with a short close delay on `mouseleave` from both the icon and the popup, so the user can move the cursor into the popup) and also toggles on click, for touch/keyboard accessibility.
- Flyout is a small absolutely-positioned panel anchored to the right of the icon (use Angular CDK Overlay, already a project dependency, with a `connectedPosition` anchored to the trigger element — avoids manual z-index/positioning math and handles viewport-edge flipping for free).
- Flyout content: the same recursive item list (title text + click-through), reusing `app-basic-menu`/nested `app-collapsable` in their normal (non-rail) rendering, since inside the flyout there's room for full labels.
- Closes on: selecting an item (navigation), clicking outside, or `Escape`.
- Active-group indicator: if any descendant matches the existing `ischildActive` flag, render a small colored dot/border accent on the parent's icon in rail mode (reusing the existing active-state accent color already used for the expanded active row) so the user can tell which group the current page belongs to without opening the flyout.
### 5. Out of scope
- No new/second toggle button — the existing hamburger is the only control, per the approved design.
- No responsive/mobile breakpoint behavior (none exists today; not part of this change).
- No changes to `navigator.ts` data (icons, children, structure) — this is purely a rendering-mode change layered on the existing data-driven menu.
## Testing
- Manual verification via the `run`/browser flow: toggle rail mode, confirm width animates, confirm leaf items navigate, confirm a `collapsable` item's flyout opens/closes correctly (hover and click), confirm the active-group indicator shows on the right icon when a child route is active, confirm state survives a full page reload (localStorage).
- No existing automated test suite covers the layout/menu components (none found during exploration) — this change ships without new automated tests, consistent with the rest of the layout code.