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

@@ -10,6 +10,7 @@ import {
import type { Message, MessageReaction } from "../chat/types";
import { useAuthStore } from "../store/authStore";
import { useChatStore } from "../store/chatStore";
import { useAudioPlayerStore } from "../store/audioPlayerStore";
import { useUiStore } from "../store/uiStore";
import { formatTime } from "../utils/format";
import { formatMessageHtml } from "../utils/formatMessage";
@@ -796,7 +797,7 @@ function renderMessageContent(
<span className="inline-flex h-7 w-7 items-center justify-center rounded-full bg-slate-700/80">🎤</span>
<span className="font-semibold">Voice message</span>
</div>
<AudioInlinePlayer src={text} />
<AudioInlinePlayer src={text} title="Voice message" />
</div>
);
}
@@ -814,7 +815,7 @@ function renderMessageContent(
<p className="text-[11px] text-slate-400">Audio file</p>
</div>
</div>
<AudioInlinePlayer src={text} />
<AudioInlinePlayer src={text} title={extractFileName(text)} />
</div>
);
}
@@ -915,8 +916,12 @@ async function downloadFileFromUrl(url: string): Promise<void> {
window.URL.revokeObjectURL(blobUrl);
}
function AudioInlinePlayer({ src }: { src: string }) {
function AudioInlinePlayer({ src, title }: { src: string; title: string }) {
const audioRef = useRef<HTMLAudioElement | null>(null);
const activate = useAudioPlayerStore((s) => s.activate);
const detach = useAudioPlayerStore((s) => s.detach);
const setPlayingState = useAudioPlayerStore((s) => s.setPlaying);
const setVolumeState = useAudioPlayerStore((s) => s.setVolume);
const [isPlaying, setIsPlaying] = useState(false);
const [duration, setDuration] = useState(0);
const [position, setPosition] = useState(0);
@@ -934,17 +939,30 @@ function AudioInlinePlayer({ src }: { src: string }) {
};
const onEnded = () => {
setIsPlaying(false);
setPlayingState(audio, false);
};
const onPlay = () => {
activate(audio, { src, title });
setPlayingState(audio, true);
};
const onPause = () => {
setPlayingState(audio, false);
};
audio.addEventListener("loadedmetadata", onLoaded);
audio.addEventListener("timeupdate", onTime);
audio.addEventListener("ended", onEnded);
audio.addEventListener("play", onPlay);
audio.addEventListener("pause", onPause);
return () => {
audio.removeEventListener("loadedmetadata", onLoaded);
audio.removeEventListener("timeupdate", onTime);
audio.removeEventListener("ended", onEnded);
audio.removeEventListener("play", onPlay);
audio.removeEventListener("pause", onPause);
detach(audio);
};
}, []);
}, [activate, detach, setPlayingState, src, title]);
async function togglePlay() {
const audio = audioRef.current;
@@ -955,6 +973,7 @@ function AudioInlinePlayer({ src }: { src: string }) {
return;
}
try {
activate(audio, { src, title });
await audio.play();
setIsPlaying(true);
} catch {
@@ -974,6 +993,7 @@ function AudioInlinePlayer({ src }: { src: string }) {
if (!audio) return;
audio.volume = nextValue;
setVolume(nextValue);
setVolumeState(audio, nextValue);
}
return (