Repository navigation
feat(navigation): support external links in sidenav #3954
Description
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
Activity
feat(navigation): support external links in sidenav (#3954)
chore(release): 2.0.0