anotherath's picture
feat(ui): space icons, chat improvements, StudyBot mentions
880ab03
import { useState } from "react";
import { FiSidebar, FiSettings } from "react-icons/fi";
import { getUserColor } from "../../utils/userColor";
function getInitials(name) {
if (!name) return "?";
return name
.split(" ")
.map((n) => n[0])
.join("")
.slice(0, 2)
.toUpperCase();
}
function isEmoji(str) {
if (!str) return false;
return /\p{Emoji}/u.test(str) && str.length <= 2;
}
function UserAvatar({ name, avatarUrl, isOnline, isDark, isBot, color }) {
const userColor = isBot ? "var(--tertiary-active)" : getUserColor(name, color);
const textColor = isBot
? "var(--tertiary)"
: isDark
? "var(--bg-surface)"
: "#fff";
const avatarEmoji = isEmoji(avatarUrl) ? avatarUrl : null;
const imageUrl = avatarUrl && !avatarEmoji ? avatarUrl : null;
const [imgError, setImgError] = useState(false);
return (
<div className="relative flex-shrink-0">
<div
className="w-9 h-9 rounded-lg flex items-center justify-center text-sm font-semibold overflow-hidden"
style={{
background: userColor,
color: textColor,
}}
>
{avatarEmoji ? (
<span className="text-base">{avatarEmoji}</span>
) : imageUrl && !imgError ? (
<img
src={imageUrl}
alt={name}
className="w-full h-full object-cover"
onError={() => setImgError(true)}
/>
) : (
getInitials(name)
)}
</div>
{!isBot && (
<div
className="absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 rounded-full border-2"
style={{
borderColor: isDark ? "var(--bg-surface-secondary)" : "#fff",
background: isOnline ? "var(--online)" : "var(--offline)",
}}
/>
)}
</div>
);
}
function ChatHeader({
isDark,
activeRoom,
isBotRoom,
isDM,
dmUser,
roomName,
roomDescription,
onToggleRoomList,
onToggleMemberList,
roomListCollapsed,
memberListCollapsed,
onOpenRoomSettings,
spaceWelcome,
}) {
const hasNoSelection = (isDM && !dmUser) || spaceWelcome;
const headerTitle = hasNoSelection
? "Tin nhắn"
: isBotRoom
? "Trợ lý AI"
: isDM && dmUser
? dmUser.name
: roomName || (activeRoom ? `# ${activeRoom}` : "");
const headerSubtitle = hasNoSelection
? "Chọn một cuộc trò chuyện để bắt đầu"
: isBotRoom
? "Hỏi đáp với trợ lý AI"
: isDM && dmUser
? dmUser.isOnline
? "Đang hoạt động"
: "Ngoại tuyến"
: roomDescription || "";
return (
<div
className="px-4 py-3 border-b flex-shrink-0 flex items-center gap-3"
style={{
borderColor: "var(--border-primary)",
background: "var(--bg-surface-secondary)",
}}
>
{(isBotRoom || (isDM && dmUser)) && (
<UserAvatar
name={isBotRoom ? "Trợ AI" : dmUser.name}
avatarUrl={isBotRoom ? "🤖" : dmUser.avatar}
color={isBotRoom ? null : dmUser?.color}
isOnline={isBotRoom ? true : dmUser?.isOnline}
isDark={isDark}
isBot={isBotRoom}
/>
)}
<div className="min-w-0 flex-1">
<div
className="text-[15px] font-semibold flex items-center gap-1.5 truncate"
style={{ color: "var(--text-primary)" }}
>
{headerTitle}
</div>
<div
className="text-xs mt-0.5"
style={{ color: "var(--text-secondary)" }}
>
{headerSubtitle}
</div>
</div>
<div className="flex items-center gap-0.5">
{/* Toggle RoomList */}
<button
onClick={onToggleRoomList}
className="p-1.5 rounded hover:opacity-70 transition-opacity cursor-pointer"
style={{
color: roomListCollapsed ? "var(--primary)" : "var(--text-muted)",
}}
title={roomListCollapsed ? "Hiện danh sách room" : "Ẩn danh sách room"}
>
<FiSidebar size={18} />
</button>
{/* Toggle MemberList */}
<button
onClick={onToggleMemberList}
className="p-1.5 rounded hover:opacity-70 transition-opacity cursor-pointer"
style={{
color: memberListCollapsed ? "var(--primary)" : "var(--text-muted)",
}}
title={memberListCollapsed ? "Hiện danh sách thành viên" : "Ẩn danh sách thành viên"}
>
<FiSidebar size={18} style={{ transform: "scaleX(-1)" }} />
</button>
{/* Room Settings — chỉ hiển thị khi ở room (không phải DM) */}
{!isDM && (
<button
onClick={onOpenRoomSettings}
className="p-1.5 rounded hover:opacity-70 transition-opacity cursor-pointer"
style={{ color: "var(--text-muted)" }}
title="Thiết lập room"
>
<FiSettings size={18} />
</button>
)}
</div>
</div>
);
}
export default ChatHeader;