Fix the search bar

This commit is contained in:
2025-11-18 17:31:08 +03:00
Unverified
parent 370b57f2c9
commit fd5cc7457c
6 changed files with 78 additions and 24 deletions
+51 -11
View File
@@ -1,6 +1,6 @@
import { useState, useEffect, useRef } from "react"; import { useState, useEffect, useRef } from "react";
import styles from "./css/searchBar.module.scss"; import styles from "./css/searchBar.module.scss";
import { MaterialIcon } from "@/utils/material"; import { MaterialIcon, type MDUIBottomAppBar } from "@/utils/material";
interface SearchBarProps { interface SearchBarProps {
placeholder: string; placeholder: string;
@@ -11,6 +11,9 @@ interface SearchBarProps {
onToggleExpanded: () => void; onToggleExpanded: () => void;
leftIcon?: string | React.ReactNode; leftIcon?: string | React.ReactNode;
rightIcon?: string | React.ReactNode; rightIcon?: string | React.ReactNode;
containerRef: React.RefObject<HTMLElement | null>;
headerRef?: React.RefObject<HTMLElement | null>;
bottomAppBarRef?: React.RefObject<MDUIBottomAppBar | null>;
} }
export default function SearchBar({ export default function SearchBar({
@@ -21,9 +24,14 @@ export default function SearchBar({
isExpanded, isExpanded,
onToggleExpanded, onToggleExpanded,
leftIcon = "search--outlined", leftIcon = "search--outlined",
rightIcon = null rightIcon = null,
containerRef,
headerRef,
bottomAppBarRef
}: SearchBarProps) { }: SearchBarProps) {
const [dynamicHeight, setDynamicHeight] = useState<string>("48px"); const [dynamicHeight, setDynamicHeight] = useState<string>("48px");
const [isTransitioning, setIsTransitioning] = useState(false);
const [showResults, setShowResults] = useState(false);
const searchContainerRef = useRef<HTMLDivElement>(null); const searchContainerRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const parentContainerRef = useRef<HTMLDivElement>(null); const parentContainerRef = useRef<HTMLDivElement>(null);
@@ -33,17 +41,46 @@ export default function SearchBar({
useEffect(() => { useEffect(() => {
if (isExpanded && inputRef.current) { if (isExpanded && inputRef.current) {
inputRef.current.focus(); inputRef.current.focus();
// Set expanded height // Set expanded height, subtracting both header and bottom app bar heights
const leftPanel = document.getElementById('chat-list'); if (containerRef.current) {
if (leftPanel) { const panelHeight = containerRef.current.offsetHeight;
const panelHeight = leftPanel.offsetHeight; let headerHeight = 0;
setDynamicHeight(`${panelHeight}px`); let bottomBarHeight = 0;
// Get header height
if (headerRef?.current) {
headerHeight = headerRef.current.offsetHeight;
}
// Get bottom app bar height
if (bottomAppBarRef?.current) {
bottomBarHeight = bottomAppBarRef.current.offsetHeight;
}
// Calculate height by subtracting both header and bottom bar heights
const availableHeight = panelHeight - headerHeight - bottomBarHeight;
setDynamicHeight(`${availableHeight}px`);
} }
} else { } else {
// Set collapsed height // Set collapsed height
setDynamicHeight("48px"); setDynamicHeight("48px");
} }
}, [isExpanded]);
// Show/hide results and disable overflow during transition
if (isExpanded) {
setShowResults(true);
}
setIsTransitioning(true);
const timeout = setTimeout(() => {
setIsTransitioning(false);
if (!isExpanded) {
setShowResults(false);
}
}, 400); // Match transition duration (0.4s)
return () => clearTimeout(timeout);
}, [isExpanded, containerRef, headerRef, bottomAppBarRef]);
function handleToggle() { function handleToggle() {
onToggleExpanded(); onToggleExpanded();
@@ -107,9 +144,12 @@ export default function SearchBar({
</div> </div>
</div> </div>
{/* Results Section - Only visible when expanded */} {/* Results Section - Visible during expansion and collapse transition */}
{isExpanded && ( {showResults && (
<div className={styles.searchResults}> <div
className={styles.searchResults}
style={{ overflowY: isTransitioning ? "hidden" : "auto" }}
>
{children} {children}
</div> </div>
)} )}
@@ -42,7 +42,7 @@ $font-size: 16px;
top: 0; top: 0;
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; // bottom will be set dynamically by React to account for bottom app bar
border-radius: 0; border-radius: 0;
background-color: $color-dark-surface-container; background-color: $color-dark-surface-container;
// Height will be set dynamically by React // Height will be set dynamically by React
@@ -108,6 +108,7 @@
padding: 32px; padding: 32px;
color: $color-dark-on-surface-variant; color: $color-dark-on-surface-variant;
text-align: center; text-align: center;
overflow: hidden;
} }
// Custom styling for search result images // Custom styling for search result images
@@ -7,7 +7,7 @@ import { MinimizedCallBar } from "@/pages/chat/ui/right/calls/MinimizedCallBar";
import styles from "@/pages/chat/css/left-panel.module.scss"; import styles from "@/pages/chat/css/left-panel.module.scss";
import logoIcon from "@/images/logo.svg"; import logoIcon from "@/images/logo.svg";
export function ChatHeader() { export function ChatHeader({ headerRef }: { headerRef?: React.RefObject<HTMLElement | null> }) {
const { profileData } = useProfile(); const { profileData } = useProfile();
const { setProfileDialog, user } = useAppState(); const { setProfileDialog, user } = useAppState();
const [profilePictureUrl, setProfilePictureUrl] = useState(profileData?.profile_picture || defaultAvatar); const [profilePictureUrl, setProfilePictureUrl] = useState(profileData?.profile_picture || defaultAvatar);
@@ -27,7 +27,7 @@ export function ChatHeader() {
return ( return (
<> <>
<header className={styles.chatHeaderLeft}> <header className={styles.chatHeaderLeft} ref={headerRef}>
<img src={logoIcon} alt="Logo" className={styles.logo} /> <img src={logoIcon} alt="Logo" className={styles.logo} />
<div className={styles.productName}>{PRODUCT_NAME}</div> <div className={styles.productName}>{PRODUCT_NAME}</div>
<div className={styles.profile}> <div className={styles.profile}>
+12 -8
View File
@@ -1,19 +1,19 @@
import { useAppState } from "@/pages/chat/state"; import { useAppState } from "@/pages/chat/state";
import { useState } from "react"; import { useRef, useState } from "react";
import { SettingsDialog } from "./settings/SettingsDialog"; import { SettingsDialog } from "./settings/SettingsDialog";
import { UsernameSearch } from "./UsernameSearch"; import { UsernameSearch } from "./UsernameSearch";
import { UnifiedChatsList } from "./UnifiedChatsList"; import { UnifiedChatsList } from "./UnifiedChatsList";
import { ChatHeader } from "./ChatHeader"; import { ChatHeader } from "./ChatHeader";
import { MaterialBottomAppBar, MaterialFab, MaterialIconButton } from "@/utils/material"; import { MaterialBottomAppBar, MaterialFab, MaterialIconButton, type MDUIBottomAppBar } from "@/utils/material";
import styles from "@/pages/chat/css/left-panel.module.scss"; import styles from "@/pages/chat/css/left-panel.module.scss";
function BottomAppBar() { function BottomAppBar({ bottomAppBarRef }: { bottomAppBarRef?: React.RefObject<MDUIBottomAppBar | null> }) {
const [settingsOpen, onSettingsOpenChange] = useState(false); const [settingsOpen, onSettingsOpenChange] = useState(false);
const { logout } = useAppState(); const { logout } = useAppState();
return ( return (
<> <>
<MaterialBottomAppBar> <MaterialBottomAppBar ref={bottomAppBarRef}>
<MaterialIconButton icon="settings--filled" id="settings-open" onClick={() => onSettingsOpenChange(true)} /> <MaterialIconButton icon="settings--filled" id="settings-open" onClick={() => onSettingsOpenChange(true)} />
<div style={{ flexGrow: 1 }} /> <div style={{ flexGrow: 1 }} />
<MaterialIconButton <MaterialIconButton
@@ -29,14 +29,18 @@ function BottomAppBar() {
} }
export function LeftPanel() { export function LeftPanel() {
const containerRef = useRef<HTMLDivElement>(null);
const headerRef = useRef<HTMLElement>(null);
const bottomAppBarRef = useRef<MDUIBottomAppBar>(null);
return ( return (
<div className={styles.chatList}> <div className={styles.chatList} ref={containerRef}>
<ChatHeader /> <ChatHeader headerRef={headerRef} />
<div className={styles.searchContainer}> <div className={styles.searchContainer}>
<UsernameSearch /> <UsernameSearch containerRef={containerRef} headerRef={headerRef} bottomAppBarRef={bottomAppBarRef} />
</div> </div>
<UnifiedChatsList /> <UnifiedChatsList />
<BottomAppBar /> <BottomAppBar bottomAppBarRef={bottomAppBarRef} />
</div> </div>
); );
} }
@@ -8,7 +8,7 @@ import { OnlineIndicator } from "@/pages/chat/ui/right/OnlineIndicator";
import { OnlineStatus } from "@/pages/chat/ui/right/OnlineStatus"; import { OnlineStatus } from "@/pages/chat/ui/right/OnlineStatus";
import defaultAvatar from "@/images/default-avatar.png"; import defaultAvatar from "@/images/default-avatar.png";
import SearchBar from "@/core/components/SearchBar"; import SearchBar from "@/core/components/SearchBar";
import { MaterialCircularProgress, MaterialIconButton, MaterialList, MaterialListItem } from "@/utils/material"; import { MaterialCircularProgress, MaterialIconButton, MaterialList, MaterialListItem, type MDUIBottomAppBar } from "@/utils/material";
import styles from "@/pages/chat/css/left-panel.module.scss"; import styles from "@/pages/chat/css/left-panel.module.scss";
interface SearchUser extends User { interface SearchUser extends User {
@@ -16,7 +16,13 @@ interface SearchUser extends User {
verified?: boolean; verified?: boolean;
} }
export function UsernameSearch() { export interface UsernameSearchProps {
containerRef: React.RefObject<HTMLElement | null>;
headerRef?: React.RefObject<HTMLElement | null>;
bottomAppBarRef?: React.RefObject<MDUIBottomAppBar | null>;
}
export function UsernameSearch({ containerRef, headerRef, bottomAppBarRef }: UsernameSearchProps) {
const { user, switchToDM, chat } = useAppState(); const { user, switchToDM, chat } = useAppState();
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
const [searchResults, setSearchResults] = useState<SearchUser[]>([]); const [searchResults, setSearchResults] = useState<SearchUser[]>([]);
@@ -165,6 +171,9 @@ export function UsernameSearch() {
icon="arrow_back--outlined" icon="arrow_back--outlined"
/> />
) : "search--outlined"} ) : "search--outlined"}
containerRef={containerRef}
headerRef={headerRef}
bottomAppBarRef={bottomAppBarRef}
> >
{isSearching && ( {isSearching && (
<div className={styles.searchLoading}> <div className={styles.searchLoading}>