npm package: @mostajs/menu
Description: Configurable dashboard sidebar with permission-based menu filtering
Author: Dr Hamid MADANI
Latest version: 1.0.4

# @mostajs/menu

Configurable dashboard sidebar with permission-based menu filtering. Provides a collapsible sidebar driven by a declarative `MenuConfig` object, with built-in RBAC support.

## Installation

```bash
npm install @mostajs/menu lucide-react
```

## Quick Start

```tsx
import { DashboardSidebar } from '@mostajs/menu'
import type { MenuConfig } from '@mostajs/menu'
import {
  LayoutDashboard, Users, Ticket, ScanLine,
  Settings, BarChart3, Shield, UserCog,
} from 'lucide-react'

// 1. Define your menu config
const menuConfig: MenuConfig = {
  // Top-level direct links
  items: [
    { label: 'Dashboard', href: '/dashboard', icon: LayoutDashboard },
    { label: 'Clients', href: '/dashboard/clients', icon: Users, permission: 'client:view' },
    { label: 'Tickets', href: '/dashboard/tickets', icon: Ticket, permission: 'ticket:view' },
    { label: 'Scanner', href: '/dashboard/scan', icon: ScanLine, permission: 'scan:validate' },
  ],
  // Collapsible groups
  groups: [
    {
      label: 'Administration',
      icon: Settings,
      items: [
        { label: 'Users', href: '/dashboard/users', icon: UserCog, permission: 'user:manage' },
        { label: 'Roles', href: '/dashboard/roles', icon: Shield, permission: 'role:manage' },
        { label: 'Statistics', href: '/dashboard/stats', icon: BarChart3, permission: 'stats:view' },
      ],
    },
  ],
}

// 2. Use in your layout
export default function DashboardLayout({ children }) {
  return (
    <div style={{ display: 'flex' }}>
      <DashboardSidebar
        config={menuConfig}
        user={{
          name: 'Dr Hamid',
          role: 'admin',
          permissions: ['client:view', 'ticket:view', 'scan:validate', 'user:manage'],
        }}
        appName="SecuAccessPro"
        appIcon={Shield}
        t={(key) => translations[key] || key}
        hasPermission={(perms, required) => perms.includes(required)}
        onLogout={() => signOut()}
      />
      <main style={{ flex: 1 }}>{children}</main>
    </div>
  )
}
```

## Component

### `<DashboardSidebar />`

A full-height sidebar with header (app icon + name), collapsible navigation groups, and a user footer with logout.

**Props:**

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `config` | `MenuConfig` | **required** | Menu structure (items + groups) |
| `user` | `SidebarUser` | **required** | Current user info |
| `appName` | `string` | `'App'` | App name in sidebar header |
| `appIcon` | `ElementType` | `Shield` | Icon component for sidebar header |
| `t` | `(key: string) => string` | identity | Translation function for labels |
| `hasPermission` | `(permissions: string[], required: string) => boolean` | `() => true` | Permission check function |
| `onLogout` | `() => void` | — | Logout handler (shows logout button when provided) |

## Types

### `MenuConfig`

```typescript
interface MenuConfig {
  /** Top-level direct links (always visible, no collapsing) */
  items: MenuItem[]
  /** Collapsible groups with sub-items */
  groups: MenuGroup[]
}
```

### `MenuItem`

```typescript
interface MenuItem {
  label: string        // Display label (or i18n key if using t())
  href: string         // Route path
  icon: ElementType    // Lucide icon or any React component
  permission?: string  // Required permission (hidden if user lacks it)
}
```

### `MenuGroup`

```typescript
interface MenuGroup {
  label: string        // Group header label
  icon: ElementType    // Group header icon
  items: MenuItem[]    // Group children
}
```

### `SidebarUser`

```typescript
interface SidebarUser {
  name?: string | null   // Displayed in footer
  role?: string          // Displayed under name
  permissions?: string[] // Used by hasPermission()
}
```

## Features

### Permission-based filtering

Menu items with a `permission` property are only visible when `hasPermission(user.permissions, item.permission)` returns `true`. Groups with no visible children are automatically hidden.

```tsx
// Only users with 'admin:access' see this item
{ label: 'Admin Panel', href: '/admin', icon: Shield, permission: 'admin:access' }
```

### Collapsible sidebar

Click the chevron in the header to collapse the sidebar to icon-only mode (64px). In collapsed mode, labels are hidden and groups show only their icon.

### Auto-expand active group

Groups containing the currently active route are automatically expanded on mount.

### Internationalization

Pass a `t()` function to translate menu labels and user role:

```tsx
<DashboardSidebar
  config={menuConfig}
  user={user}
  t={(key) => {
    // Menu labels are passed as-is
    // User role uses format: auth.roles.{role}
    // Logout button uses: auth.logout.button
    return i18n.t(key)
  }}
/>
```

**Translation keys used:**
- Each `MenuItem.label` is passed through `t()`
- `auth.roles.{user.role}` for the role display
- `auth.logout.button` for the logout button title

## Styling

The sidebar uses Tailwind CSS classes. It requires:
- A Tailwind-configured project
- The `sky` and `gray` color palette

The sidebar takes full viewport height (`h-screen`) and is positioned as a flex child. Wrap it in a flex container with your main content:

```tsx
<div className="flex">
  <DashboardSidebar ... />
  <main className="flex-1 overflow-y-auto">{children}</main>
</div>
```

## License

MIT