Skip to content

feat(navigation): support external links in sidenav #3954

Closed

Description

@PierreBrisorgueil

Context

The sidenav currently only supports internal Vue Router links via `:to="item.path"`. Downstream projects need to add external links (e.g., API docs) that open in a new tab. The pattern already exists in the header component (`window.open`), but not in the sidenav.

Scope

1. Support `meta.href` on route definitions

Allow routes to declare an external URL:

```javascript
{
path: '/docs', // used as key, not navigated
name: 'Documentation',
meta: {
icon: 'fa-solid fa-book',
display: true,
href: 'https://api.trawl.me/api/docs', // external URL
target: '_blank', // optional, defaults to '_blank'
},
}
```

No `component` property needed — Vue Router won't render anything for this route.

2. Update navigation component

File: `src/modules/core/components/core.navigation.component.vue` (line ~48)

Current:
```vue

```

Proposed:
```vue
<v-list-item
v-for="item in nav"
:key="item.path"
v-bind="navItemBind(item)"

```

With a method:
```javascript
navItemBind(item) {
if (item.meta?.href) {
return { href: item.meta.href, target: item.meta.target || '_blank' };
}
return { to: item.path };
},
```

Apply the same pattern to `navBottom` items (line ~63).

3. Core store — no changes needed

`refreshNav()` already filters by `meta.icon` + `meta.display`. External link routes will pass through the same filter as long as they have `icon` and `display: true`.

Note: Routes with `meta.href` but no `component` won't break Vue Router — they just won't render a ``. The sidenav will use `:href` instead of `:to`, bypassing Vue Router entirely for navigation.

4. Handle edge cases

  • Active state: External links should NOT show as active in the sidenav (no route match). Vuetify's `v-list-item` with `:href` instead of `:to` won't trigger active styling — verify this.
  • CASL permissions: If `meta.action` and `meta.subject` are set, the external link respects permissions like any other nav item (already handled by `refreshNav` filter).
  • Position: `meta.position: 'bottom'` works for external links too (same filter logic).

Testing requirements

  • Unit test: `navItemBind` returns `{ href, target }` for external items and `{ to }` for internal
  • Unit test: external link items pass through `refreshNav` filter when they have icon + display
  • Unit test: external link items are excluded when `meta.display: false`
  • Unit test: CASL permissions apply to external links
  • Visual test: external link renders in sidenav with correct icon
  • Visual test: clicking external link opens new tab (not in-app navigation)
  • Visual test: external link does NOT show active state
  • Regression: all existing internal nav items still work

Files to modify

  • `src/modules/core/components/core.navigation.component.vue` — add `navItemBind` method, update template
  • `src/modules/core/tests/core.navigation.component.unit.tests.js` — add external link tests (create if doesn't exist)

Downstream usage example (trawl_vue)

After this ships, trawl_vue adds to its router:
```javascript
{
path: '/api-docs',
name: 'API Docs',
meta: {
icon: 'fa-solid fa-book',
display: true,
href: 'https://api.trawl.me/api/docs',
target: '_blank',
position: 'bottom',
},
}
```

Acceptance criteria

  • External links render in sidenav with icon and label
  • Clicking opens in new tab (`target="_blank"`)
  • No active state highlighting for external links
  • CASL permissions respected
  • Internal links unaffected (zero regression)
  • Works for both `nav` and `navBottom` positions

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions