feat(web): inline chat search and global audio bar
Some checks failed
CI / test (push) Failing after 20s

- replace modal message search with header inline search controls

- add global top audio bar linked to active inline audio player

- improve chat info header variants and light theme readability
This commit is contained in:
2026-03-08 11:21:57 +03:00
parent 03bf197949
commit 14610b5699
7 changed files with 514 additions and 117 deletions

View File

@@ -0,0 +1,74 @@
import { create } from "zustand";
interface ActiveTrack {
src: string;
title: string;
}
interface AudioPlayerState {
track: ActiveTrack | null;
audioEl: HTMLAudioElement | null;
isPlaying: boolean;
volume: number;
activate: (audioEl: HTMLAudioElement, track: ActiveTrack) => void;
detach: (audioEl: HTMLAudioElement) => void;
setPlaying: (audioEl: HTMLAudioElement, isPlaying: boolean) => void;
setVolume: (audioEl: HTMLAudioElement, volume: number) => void;
togglePlay: () => Promise<void>;
stop: () => void;
}
export const useAudioPlayerStore = create<AudioPlayerState>((set, get) => ({
track: null,
audioEl: null,
isPlaying: false,
volume: 1,
activate: (audioEl, track) => {
set({
audioEl,
track,
isPlaying: !audioEl.paused,
volume: audioEl.volume ?? 1,
});
},
detach: (audioEl) => {
const current = get().audioEl;
if (current !== audioEl) {
return;
}
set({ audioEl: null, track: null, isPlaying: false });
},
setPlaying: (audioEl, isPlaying) => {
if (get().audioEl !== audioEl) {
return;
}
set({ isPlaying });
},
setVolume: (audioEl, volume) => {
if (get().audioEl !== audioEl) {
return;
}
set({ volume });
},
togglePlay: async () => {
const audio = get().audioEl;
if (!audio) {
return;
}
if (audio.paused) {
await audio.play();
set({ isPlaying: true });
return;
}
audio.pause();
set({ isPlaying: false });
},
stop: () => {
const audio = get().audioEl;
if (audio) {
audio.pause();
}
set({ audioEl: null, track: null, isPlaying: false });
},
}));