feat(mana/web): clickable page titles open route in new tab

PageShell gains an optional titleHref prop — when set, the header title
renders as an <a target="_blank"> with hover underline. Also wires this
into the homepage app gallery (shared-ui/AppsPage): the grid card title
is now an anchor to /{app.id}, while the rest of the card still opens
the existing detail modal. Card converted from <button> to role=button
so the nested anchor is valid HTML.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-09 14:27:51 +02:00
parent b8bfc4d775
commit a130f8e4c0
2 changed files with 59 additions and 4 deletions

View file

@ -28,6 +28,7 @@
onMoveRight?: () => void;
// Default header
title?: string;
titleHref?: string;
color?: string;
icon?: Component;
onContextMenu?: (e: MouseEvent) => void;
@ -50,6 +51,7 @@
onMoveRight,
onContextMenu,
title = '',
titleHref,
color = '#6B7280',
icon: IconComponent,
header_left,
@ -211,7 +213,22 @@
{:else}
<span class="color-dot" style="background-color: {color}"></span>
{/if}
<span class="page-title">{title}</span>
{#if titleHref}
<a
class="page-title page-title-link"
href={titleHref}
target="_blank"
rel="noopener noreferrer"
onclick={(e) => e.stopPropagation()}
ondragstart={(e) => e.preventDefault()}
draggable="false"
title={`${title} in neuem Tab öffnen`}
>
{title}
</a>
{:else}
<span class="page-title">{title}</span>
{/if}
{/if}
{#if badge}
{@render badge()}
@ -380,6 +397,15 @@
font-weight: 600;
color: hsl(var(--color-foreground));
}
a.page-title-link {
text-decoration: none;
cursor: pointer;
transition: color 0.15s;
}
a.page-title-link:hover {
color: hsl(var(--color-primary));
text-decoration: underline;
}
.window-actions {
position: absolute;

View file

@ -125,11 +125,20 @@
<div class="apps-grid">
{#each apps as app, index}
<button
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div
class="app-card"
class:current={app.id === currentAppId}
style="--app-color: {app.color};"
role="button"
tabindex="0"
onclick={() => openModal(index)}
onkeydown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
openModal(index);
}
}}
>
<div
class="status-indicator"
@ -147,9 +156,18 @@
{/if}
</div>
<h3 class="app-name">{app.name}</h3>
<a
class="app-name app-name-link"
href={`/${app.id}`}
target="_blank"
rel="noopener noreferrer"
onclick={(e) => e.stopPropagation()}
title={`${app.name} in neuem Tab öffnen`}
>
{app.name}
</a>
<p class="app-description">{app.description[locale]}</p>
</button>
</div>
{/each}
</div>
</div>
@ -377,6 +395,17 @@
color: #f3f4f6;
}
.app-name-link {
display: inline-block;
text-decoration: none;
cursor: pointer;
transition: color 0.15s;
}
.app-name-link:hover {
color: var(--app-color);
text-decoration: underline;
}
.app-description {
font-size: 0.75rem;
line-height: 1.4;