feat: Theme engine

big changes
This commit is contained in:
2026-04-02 00:08:38 +02:00
parent 65b9419869
commit bbb6deb0a2
48 changed files with 6150 additions and 235 deletions

View File

@@ -1,17 +1,38 @@
<div
class="fixed left-16 right-0 top-0 z-50 flex h-10 items-center justify-between border-b border-border bg-card px-4 select-none"
appThemeNode="titleBar"
class="relative z-50 flex h-10 w-full items-center justify-between border-b border-border bg-card px-4 select-none"
style="-webkit-app-region: drag"
>
<div
class="flex items-center gap-2 min-w-0 relative"
style="-webkit-app-region: no-drag"
>
<span
data-theme-slot="icon"
class="theme-icon-slot h-6 w-6 shrink-0 items-center justify-center rounded-xl border border-border/70 bg-secondary/70 bg-center bg-cover bg-no-repeat text-[10px] font-semibold uppercase tracking-[0.16em] text-foreground"
></span>
@if (inRoom()) {
<ng-icon
name="lucideHash"
class="w-5 h-5 text-muted-foreground"
/>
<span class="truncate text-sm font-semibold text-foreground">{{ roomContextTitle() }}</span>
<span
data-theme-slot="text"
class="flex min-w-0 items-center gap-2 text-sm font-semibold text-foreground"
>
<span class="truncate">{{ roomName() }}</span>
@if (isVoiceWorkspaceExpanded()) {
<span class="shrink-0 text-muted-foreground">/</span>
<span class="truncate">{{ connectedVoiceChannelName() }}</span>
} @else if (textChannels().length > 0) {
<span class="shrink-0 text-muted-foreground">/</span>
<span class="flex min-w-0 items-center gap-1 text-foreground">
<ng-icon
name="lucideHash"
class="h-4 w-4 shrink-0 text-muted-foreground"
/>
<span class="truncate">{{ activeTextChannelName() }}</span>
</span>
}
</span>
@if (showRoomCompatibilityNotice()) {
<span class="inline-flex items-center gap-1 rounded bg-destructive/15 px-2 py-0.5 text-xs text-destructive">
@@ -36,7 +57,11 @@
}
} @else {
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm text-muted-foreground truncate">{{ username() }} | {{ serverName() }}</span>
<span
data-theme-slot="text"
class="text-sm text-muted-foreground truncate"
>{{ username() }} | {{ serverName() }}</span
>
<span
class="text-xs px-2 py-0.5 rounded bg-destructive/15 text-destructive"
[class.hidden]="!isReconnecting()"
@@ -59,57 +84,62 @@
Login
</button>
<button
type="button"
(click)="toggleMenu()"
class="ml-2 rounded-md p-2 transition-colors hover:bg-secondary"
title="Menu"
>
<ng-icon
name="lucideMenu"
class="w-5 h-5 text-muted-foreground"
/>
</button>
<!-- Anchored dropdown under the menu button -->
@if (showMenu()) {
<div class="absolute right-0 top-full z-50 mt-2 w-64 rounded-md border border-border bg-popover p-1 shadow-lg">
@if (inRoom()) {
<button
type="button"
(click)="createInviteLink()"
[disabled]="creatingInvite()"
class="w-full rounded-md px-3 py-2 text-left text-sm text-foreground transition-colors hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60"
<div class="relative">
<button
type="button"
(click)="toggleMenu()"
class="grid h-8 w-8 place-items-center rounded-md transition-colors hover:bg-secondary"
title="Menu"
>
<ng-icon
name="lucideMenu"
class="h-4 w-4 text-muted-foreground"
/>
</button>
@if (showMenu()) {
<div
class="absolute right-0 top-full z-50 mt-2 w-64 rounded-md border border-border bg-popover p-1 shadow-lg"
style="-webkit-app-region: no-drag"
>
@if (inRoom()) {
<button
type="button"
(click)="createInviteLink()"
[disabled]="creatingInvite()"
class="w-full rounded-md px-3 py-2 text-left text-sm text-foreground transition-colors hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60"
>
@if (creatingInvite()) {
Creating Invite Link…
} @else {
Create Invite Link
}
</button>
<button
type="button"
(click)="leaveServer()"
class="w-full rounded-md px-3 py-2 text-left text-sm text-foreground transition-colors hover:bg-secondary"
>
Leave Server
</button>
}
<div
class="px-3 py-2 text-xs leading-5 text-muted-foreground"
[class.hidden]="!inviteStatus()"
>
@if (creatingInvite()) {
Creating Invite Link…
} @else {
Create Invite Link
}
</button>
{{ inviteStatus() }}
</div>
<div class="mx-2 my-1 h-px bg-border"></div>
<button
type="button"
(click)="leaveServer()"
(click)="logout()"
class="w-full rounded-md px-3 py-2 text-left text-sm text-foreground transition-colors hover:bg-secondary"
>
Leave Server
Logout
</button>
}
<div
class="px-3 py-2 text-xs leading-5 text-muted-foreground"
[class.hidden]="!inviteStatus()"
>
{{ inviteStatus() }}
</div>
<div class="mx-2 my-1 h-px bg-border"></div>
<button
type="button"
(click)="logout()"
class="w-full rounded-md px-3 py-2 text-left text-sm text-foreground transition-colors hover:bg-secondary"
>
Logout
</button>
</div>
}
}
</div>
@if (isElectron()) {
<button
type="button"

View File

@@ -37,6 +37,7 @@ import { STORAGE_KEY_CURRENT_USER_ID } from '../../core/constants';
import { LeaveServerDialogComponent } from '../../shared';
import { Room } from '../../shared-kernel';
import { VoiceWorkspaceService } from '../../domains/voice-session';
import { ThemeNodeDirective } from '../../domains/theme';
@Component({
selector: 'app-title-bar',
@@ -44,7 +45,8 @@ import { VoiceWorkspaceService } from '../../domains/voice-session';
imports: [
CommonModule,
NgIcon,
LeaveServerDialogComponent
LeaveServerDialogComponent,
ThemeNodeDirective
],
viewProviders: [
provideIcons({ lucideMinus,
@@ -109,23 +111,6 @@ export class TitleBarComponent {
return voiceChannel?.name || 'Voice Lounge';
});
roomContextTitle = computed(() => {
const room = this.currentRoom();
if (!room) {
return '';
}
if (this.isVoiceWorkspaceExpanded()) {
return `${room.name} / ${this.connectedVoiceChannelName()}`;
}
if (this.textChannels().length === 0) {
return room.name;
}
return `${room.name} / #${this.activeTextChannelName()}`;
});
roomContextMeta = computed(() => {
if (!this.currentRoom()) {
return '';