floating chat

This commit is contained in:
Bastian Wagner
2026-06-15 13:58:40 +02:00
parent 86e85520a8
commit b979a3b097
6 changed files with 151 additions and 92 deletions

View File

@@ -104,12 +104,12 @@
<main class="app-main">
<router-outlet />
</main>
<app-assistant-chat class="assistant-sidebar" />
</div>
</mat-sidenav-content>
</mat-sidenav-container>
<app-assistant-chat />
<nav class="bottom-nav" aria-label="Mobile Hauptnavigation">
<a
class="bottom-nav-link"

View File

@@ -125,10 +125,6 @@
min-height: calc(100dvh - 56px);
}
.assistant-sidebar {
min-width: 0;
}
.shell .app-main {
padding-bottom: calc(76px + env(safe-area-inset-bottom));
}
@@ -232,16 +228,3 @@
display: none;
}
}
@media (min-width: 1041px) {
.shell-workspace {
grid-template-columns: minmax(0, 1fr) 360px;
align-items: start;
}
.assistant-sidebar {
position: sticky;
top: 64px;
min-height: calc(100dvh - 64px);
}
}

View File

@@ -1,10 +1,19 @@
<div class="assistant-floating">
@if (open()) {
<section class="assistant-shell" aria-label="Listify-Assistent">
<header class="assistant-header">
<div>
<h2>Assistent</h2>
<p>{{ onlineStatus.online() ? 'Listen planen und erstellen' : 'Nur online verfuegbar' }}</p>
</div>
<mat-icon aria-hidden="true">{{ onlineStatus.online() ? 'auto_awesome' : 'cloud_off' }}</mat-icon>
<button
mat-icon-button
type="button"
aria-label="Assistent schliessen"
(click)="close()"
>
<mat-icon aria-hidden="true">close</mat-icon>
</button>
</header>
<div class="message-list" aria-live="polite">
@@ -68,3 +77,19 @@
</button>
</div>
</section>
}
<button
mat-fab
type="button"
class="assistant-launcher"
[class.offline]="!onlineStatus.online()"
[attr.aria-expanded]="open()"
[attr.aria-label]="open() ? 'Assistent schliessen' : 'Assistent oeffnen'"
(click)="toggleOpen()"
>
<mat-icon aria-hidden="true">
{{ open() ? 'close' : (onlineStatus.online() ? 'auto_awesome' : 'cloud_off') }}
</mat-icon>
</button>
</div>

View File

@@ -1,18 +1,32 @@
:host {
display: block;
min-width: 0;
display: contents;
}
.assistant-floating {
position: fixed;
right: 1rem;
bottom: calc(76px + env(safe-area-inset-bottom));
z-index: 35;
display: grid;
justify-items: end;
gap: 0.75rem;
pointer-events: none;
}
.assistant-shell {
display: grid;
grid-template-rows: auto minmax(220px, 1fr) auto auto;
gap: 0.75rem;
width: 100%;
min-height: 420px;
max-height: calc(100dvh - 88px);
width: min(360px, calc(100vw - 2rem));
height: min(640px, calc(100dvh - 166px));
min-height: 360px;
padding: 0.85rem;
border-left: 1px solid color-mix(in srgb, var(--mat-sys-outline-variant) 72%, transparent);
background: color-mix(in srgb, var(--mat-sys-surface) 94%, transparent);
border: 1px solid color-mix(in srgb, var(--mat-sys-outline-variant) 72%, transparent);
border-radius: 8px;
background: color-mix(in srgb, var(--mat-sys-surface) 96%, transparent);
box-shadow: 0 18px 48px color-mix(in srgb, var(--mat-sys-shadow) 20%, transparent);
pointer-events: auto;
backdrop-filter: blur(14px);
}
.assistant-header {
@@ -44,6 +58,10 @@
color: var(--mat-sys-primary);
}
.assistant-header button {
flex: 0 0 auto;
}
.message-list {
display: flex;
flex-direction: column;
@@ -158,10 +176,34 @@
justify-self: end;
}
@media (max-width: 1040px) {
.assistant-launcher {
pointer-events: auto;
box-shadow: 0 12px 28px color-mix(in srgb, var(--mat-sys-shadow) 24%, transparent);
}
.assistant-launcher.offline {
--mdc-fab-container-color: var(--mat-sys-error-container);
--mat-fab-foreground-color: var(--mat-sys-on-error-container);
}
@media (min-width: 801px) {
.assistant-floating {
bottom: 1rem;
}
.assistant-shell {
max-height: none;
border-top: 1px solid color-mix(in srgb, var(--mat-sys-outline-variant) 72%, transparent);
border-left: 0;
height: min(640px, calc(100dvh - 6rem));
}
}
@media (max-width: 480px) {
.assistant-floating {
right: 0.75rem;
left: 0.75rem;
}
.assistant-shell {
width: 100%;
height: min(620px, calc(100dvh - 154px));
}
}

View File

@@ -47,6 +47,7 @@ export class AssistantChatComponent {
protected readonly draft = signal('');
protected readonly sending = signal(false);
protected readonly errorMessage = signal<string | null>(null);
protected readonly open = signal(false);
protected readonly canSend = computed(
() =>
this.draft().trim().length > 0 &&
@@ -109,6 +110,14 @@ export class AssistantChatComponent {
});
}
protected toggleOpen(): void {
this.open.update((open) => !open);
}
protected close(): void {
this.open.set(false);
}
private resolveContext(): Observable<AssistantPageContext> {
const route = this.router.url || '/';
const path = route.split(/[?#]/)[0] || '/';

View File

@@ -63,7 +63,7 @@ export class ListsComponent implements OnInit {
protected readonly errorMessage = signal<string | null>(null);
protected readonly searchTerm = signal('');
protected readonly kindFilter = signal<ListKindFilter>('all');
protected readonly statusFilter = signal<ListStatusFilter>('all');
protected readonly statusFilter = signal<ListStatusFilter>('open');
protected readonly sortOption = signal<ListSortOption>('updated-desc');
protected readonly hasLists = computed(() => this.lists().length > 0);
protected readonly visibleLists = computed(() => {