Search by

nowo-tech / dashboard-menu-bundle

HecFranco

Symfony bundle for configurable dashboard menus with i18n (JSON translations), tree structure (parent/position), permissions, Twig and JSON API. No external ORM extensions.

Package info

github.com/nowo-tech/DashboardMenuBundle

Type:symfony-bundle

pkg:composer/nowo-tech/dashboard-menu-bundle

Fund package maintenance!

HecFranco

Statistics

Installs: 4 527

Dependents: 1

Suggesters: 2

Stars: 0

Open Issues: 0

v2.16.0 2026-11-01 11:02 UTC

README

CI Packagist Version Packagist Downloads License PHP Symfony GitHub stars Coverage

⭐ Found this useful? Install from Packagist · Give it a star on GitHub so more developers can find it.

Nowo Dashboard Menu Bundle — Configurable dashboard menus with i18n (JSON translations), tree structure (parent + position), permissions, Twig rendering and JSON API. No external ORM extensions (Gedmo/Stof). Minimum: PHP 8.2+, Symfony 7.4+ / 8 (^7.4 || ^8.0).

FrankenPHP Friendly Worker Mode

This bundle is FrankenPHP worker mode friendly.

Menus list: UiKit list scaffold with search, row actions and pagination inside the Symfony 8 demo shell (sidebar menus rendered by the bundle, Web Profiler toolbar) Menu detail: items table with route / URL, permission keys and row actions (move, add child, edit, configure, delete) Item label form in a UiKit native dialog: base label and one tab per locale (default locale first, starred)
Menus list (UiKit page/list) Menu detail + items (UiKit page/show) Item labels and translations in a dialog (locale tabs)

Demo: demo/symfony8 (Symfony 8, FrankenPHP, Bootstrap 5 host theme). UiKit page templates behind these screens: docs/TEMPLATES.md.

Table of contents

Features

  • Menu & MenuItem entities: Tree (parent/children, ordered by position), labels with optional JSON translations per locale, optional icon per item (e.g. Symfony UX Icons)
  • Context resolution: Same code can have multiple menus with different JSON context (e.g. partnerId, operatorId); pass an ordered list of context sets and the first match is used; empty context = fallback
  • Config: Doctrine connection and table prefix; cache (TTL + pool) for the menu tree; icon_library_prefix_map (e.g. bootstrap-icons → bi); locales; per-menu options (classes, permission checker, depth limit, icons, collapsible) in the database
  • Permissions: MenuPermissionCheckerInterface — implement and tag to filter items per user/context
  • Current item: MenuCurrentMatcherInterface (v2.1) to mark the active item
  • FrankenPHP worker: request-scoped memos + DashboardMenuWorkerStateSubscriber so worker mode with reset_kernel: false stays correct without depending on services_resetter (see FRANKENPHP-WORKER-AUDIT.md)
  • CLI: nowo_dashboard_menu:generate-migration, nowo_dashboard_menu:translations:google-sync
  • Twig: dashboard_menu_tree(menuCode, permissionContext?, contextSets?), dashboard_menu_href(item), dashboard_menu_config(menuCode, contextSets?); include @NowoDashboardMenuBundle/menu.html.twig
  • Menu render options (2.15.0, opt-in): dashboard.nested_group_display: link_row (nested collapsible groups as link-style toggle rows), group_children_marker: dot (dotted links inside sections) and toggle_icon (UX icon chevrons) — sidebars with collapsible groups without copying menu.html.twig — see Menu render options
  • Collapse without Bootstrap JS (2.6.0, opt-in): menu_collapse.enabled + the CSP-safe js/menu-collapse.js script make collapsible / nested menus toggle without Bootstrap's JavaScript (ARIA in sync, state remembered in localStorage, no double toggle when Bootstrap JS is present) — see Nested collapse without Bootstrap JS
  • JSON API: GET /api/menu/{code} for SPA consumption (optional _locale, _context_sets query params)
  • Dashboard: CRUD at /admin/menus (list, create, edit, copy menu, manage items); export/import menus as JSON; optional drag-and-drop tree reorder (SortableJS) on a dedicated route; forms split into definition (pencil) and configuration (gear); dashboard search/action flows use Symfony Form Types + FormKit-friendly Twig rendering (form_start / form_end); redirect to referer after successful actions; import available in a modal
  • UiKit page contract (2.7, REQ-UI-003): the dashboard pages extend the UiKit list / show / form scaffolds and render in your nowo_ui_kit.layouts.admin layout; theme them once with nowo_ui_kit.theme (per-bundle exception theme.bundles.NowoDashboardMenuBundle); forms open in native UiKit dialogs, deletes use the UiKit confirm dialog — see docs/TEMPLATES.md. The 1.x dashboard.ui chrome hooks are deprecated (bridged into the UiKit theme)
  • Performance: Two SQL queries per menu (menu + items), optional PSR-6 cache (configurable TTL); labels by locale from JSON; tree built in PHP
  • Dev: Web Profiler panel “Dashboard menus” (menus on page, query count, configuration tab: connection, cache, locales, icon map, permission checkers)

Installation

composer require nowo-tech/dashboard-menu-bundle

Install from Packagist

With Symfony Flex, the recipe (if available) registers the bundle and adds config/routes. Without Flex, see docs/INSTALLATION.md for manual steps.

Requirements

  • PHP >= 8.2, < 8.6
  • Symfony 7.4+ or 8.x (^7.4 || ^8.0) — Symfony 7 floor is 7.4 (REQ-SF-001; FormKit ^2.0)
  • Doctrine ORM ^3.7 (no Gedmo/Stof required)
  • UiKitBundle + FormKitBundle (required hard dependencies for the dashboard UI)
  • Symfony UX Autocomplete & Live Component (installed with the bundle): 2.x (from 2.32 / 2.33) or 3.x

See docs/INSTALLATION.md and docs/UPGRADING.md for compatibility notes.

Configuration

Menus are defined in the database (dashboard at /admin/menus or fixtures): code, name, context (optional JSON), CSS classes. In YAML you only need defaults (and optional project, locales). See docs/CONFIGURATION.md.

# config/packages/nowo_dashboard_menu.yaml
nowo_dashboard_menu:
  project: my_app
  doctrine:
    connection: default
    table_prefix: ''
  cache:
    ttl: 60
    pool: cache.app
  icon_library_prefix_map:
    bootstrap-icons: bi
  locales: ['es', 'en']
  api:
    enabled: true
    path_prefix: /api/menu
  menu_collapse:          # 2.6.0, opt-in: collapse without Bootstrap JS
    enabled: false
    storage_key: nowo.dashboardMenu.navCollapse

Usage

Twig:

{% set tree = dashboard_menu_tree('sidebar') %}
{% set config = dashboard_menu_config('sidebar') %}
{% include '@NowoDashboardMenuBundle/menu.html.twig' with { menuTree: tree, menuCode: 'sidebar', menuConfig: config } %}

With context sets (resolve which menu variant to show):

{% set contextSets = [{ 'partnerId': 1, 'operatorId': 1 }, { 'partnerId': 1 }, {}] %}
{% set tree = dashboard_menu_tree('sidebar', null, contextSets) %}

API: GET /api/menu/sidebar returns JSON tree with label, href, routeName, children. Optional query: _context_sets (JSON array of context objects).

Full details: docs/USAGE.md.

Demo

The in-repo demo is demo/symfony8 (FrankenPHP + Caddy; HTTP on port 80 in the container). The package still supports Symfony 7.4+ and 8 via Composer. Compose defaults to APP_ENV=dev and FRANKENPHP_MODE=worker; set FRANKENPHP_MODE=classic for hot-reload (Caddyfile.dev, no PHP worker) — docs/DEMO-FRANKENPHP.md. Quick start: docs/DEMO.md.

Default host port: 8011 via PORT in demo/symfony8/.env.

From bundle root:

make -C demo/symfony8 up
make -C demo/symfony8 install
# Open http://localhost:8011 (or PORT from demo/symfony8/.env)

Playwright e2e: make -C demo/symfony8 test-e2e; refresh the README screenshots with make -C demo/symfony8 demo-screenshots (see CONTRIBUTING).

Development

Run tests and QA with Docker: make up && make install && make test (or make test-coverage, make qa). Without Docker: composer install && composer test. Full details: docs/DEVELOPMENT.md.

Dashboard assets (TS → JS): Built with Vite (as in IconSelectorBundle). Sources under src/Resources/assets/src/ (dashboard.ts, stimulus-live.ts, menu-collapse.ts, logger.ts). Output: src/Resources/public/js/dashboard.js (IIFE), js/stimulus-live.js (ESM) and js/menu-collapse.js (IIFE, 2.6.0), installed by Symfony to public/bundles/nowodashboardmenu/js/.

pnpm install
pnpm run build    # production build (dashboard.js + stimulus-live.js + menu-collapse.js)
pnpm run dev     # watch dashboard.js
pnpm run build:dashboard    # only dashboard.js
pnpm run build:stimulus-live  # only stimulus-live.js
pnpm run build:menu-collapse  # only menu-collapse.js

Then run php bin/console assets:install in the app so public/bundles/nowodashboardmenu/js/ is updated.

Documentation

Additional documentation

Tests and coverage

  • Tests: PHPUnit (unit and integration suites) + Vitest for TypeScript (make test-ts)
  • PHP (Lines): 99.32% on the includable src/ set — see docs/COVERAGE.md. Run make coverage-check / composer coverage-check.
  • TS/JS: 100% on logger.ts (Vitest + v8); run make test-ts (prints global TS coverage via .scripts/ts-coverage-percent.sh)
  • Python: N/A

License

The MIT License (MIT). Please see LICENSE for more information.

Author

Created by Héctor Franco Aceituno at Nowo.tech