mirror of
https://github.com/fromchat-messenger/web.git
synced 2026-09-22 19:15:08 +03:00
Fix the search bar
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import styles from "./css/searchBar.module.scss";
|
||||
import { MaterialIcon } from "@/utils/material";
|
||||
import { MaterialIcon, type MDUIBottomAppBar } from "@/utils/material";
|
||||
|
||||
interface SearchBarProps {
|
||||
placeholder: string;
|
||||
@@ -11,6 +11,9 @@ interface SearchBarProps {
|
||||
onToggleExpanded: () => void;
|
||||
leftIcon?: 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({
|
||||
@@ -21,9 +24,14 @@ export default function SearchBar({
|
||||
isExpanded,
|
||||
onToggleExpanded,
|
||||
leftIcon = "search--outlined",
|
||||
rightIcon = null
|
||||
rightIcon = null,
|
||||
containerRef,
|
||||
headerRef,
|
||||
bottomAppBarRef
|
||||
}: SearchBarProps) {
|
||||
const [dynamicHeight, setDynamicHeight] = useState<string>("48px");
|
||||
const [isTransitioning, setIsTransitioning] = useState(false);
|
||||
const [showResults, setShowResults] = useState(false);
|
||||
const searchContainerRef = useRef<HTMLDivElement>(null);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const parentContainerRef = useRef<HTMLDivElement>(null);
|
||||
@@ -33,17 +41,46 @@ export default function SearchBar({
|
||||
useEffect(() => {
|
||||
if (isExpanded && inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
// Set expanded height
|
||||
const leftPanel = document.getElementById('chat-list');
|
||||
if (leftPanel) {
|
||||
const panelHeight = leftPanel.offsetHeight;
|
||||
setDynamicHeight(`${panelHeight}px`);
|
||||
// Set expanded height, subtracting both header and bottom app bar heights
|
||||
if (containerRef.current) {
|
||||
const panelHeight = containerRef.current.offsetHeight;
|
||||
let headerHeight = 0;
|
||||
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 {
|
||||
// Set collapsed height
|
||||
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() {
|
||||
onToggleExpanded();
|
||||
@@ -107,9 +144,12 @@ export default function SearchBar({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Results Section - Only visible when expanded */}
|
||||
{isExpanded && (
|
||||
<div className={styles.searchResults}>
|
||||
{/* Results Section - Visible during expansion and collapse transition */}
|
||||
{showResults && (
|
||||
<div
|
||||
className={styles.searchResults}
|
||||
style={{ overflowY: isTransitioning ? "hidden" : "auto" }}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -42,7 +42,7 @@ $font-size: 16px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
// bottom will be set dynamically by React to account for bottom app bar
|
||||
border-radius: 0;
|
||||
background-color: $color-dark-surface-container;
|
||||
// Height will be set dynamically by React
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
padding: 32px;
|
||||
color: $color-dark-on-surface-variant;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// 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 logoIcon from "@/images/logo.svg";
|
||||
|
||||
export function ChatHeader() {
|
||||
export function ChatHeader({ headerRef }: { headerRef?: React.RefObject<HTMLElement | null> }) {
|
||||
const { profileData } = useProfile();
|
||||
const { setProfileDialog, user } = useAppState();
|
||||
const [profilePictureUrl, setProfilePictureUrl] = useState(profileData?.profile_picture || defaultAvatar);
|
||||
@@ -27,7 +27,7 @@ export function ChatHeader() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className={styles.chatHeaderLeft}>
|
||||
<header className={styles.chatHeaderLeft} ref={headerRef}>
|
||||
<img src={logoIcon} alt="Logo" className={styles.logo} />
|
||||
<div className={styles.productName}>{PRODUCT_NAME}</div>
|
||||
<div className={styles.profile}>
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { useAppState } from "@/pages/chat/state";
|
||||
import { useState } from "react";
|
||||
import { useRef, useState } from "react";
|
||||
import { SettingsDialog } from "./settings/SettingsDialog";
|
||||
import { UsernameSearch } from "./UsernameSearch";
|
||||
import { UnifiedChatsList } from "./UnifiedChatsList";
|
||||
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";
|
||||
|
||||
function BottomAppBar() {
|
||||
function BottomAppBar({ bottomAppBarRef }: { bottomAppBarRef?: React.RefObject<MDUIBottomAppBar | null> }) {
|
||||
const [settingsOpen, onSettingsOpenChange] = useState(false);
|
||||
const { logout } = useAppState();
|
||||
|
||||
return (
|
||||
<>
|
||||
<MaterialBottomAppBar>
|
||||
<MaterialBottomAppBar ref={bottomAppBarRef}>
|
||||
<MaterialIconButton icon="settings--filled" id="settings-open" onClick={() => onSettingsOpenChange(true)} />
|
||||
<div style={{ flexGrow: 1 }} />
|
||||
<MaterialIconButton
|
||||
@@ -29,14 +29,18 @@ function BottomAppBar() {
|
||||
}
|
||||
|
||||
export function LeftPanel() {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const headerRef = useRef<HTMLElement>(null);
|
||||
const bottomAppBarRef = useRef<MDUIBottomAppBar>(null);
|
||||
|
||||
return (
|
||||
<div className={styles.chatList}>
|
||||
<ChatHeader />
|
||||
<div className={styles.chatList} ref={containerRef}>
|
||||
<ChatHeader headerRef={headerRef} />
|
||||
<div className={styles.searchContainer}>
|
||||
<UsernameSearch />
|
||||
<UsernameSearch containerRef={containerRef} headerRef={headerRef} bottomAppBarRef={bottomAppBarRef} />
|
||||
</div>
|
||||
<UnifiedChatsList />
|
||||
<BottomAppBar />
|
||||
<BottomAppBar bottomAppBarRef={bottomAppBarRef} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { OnlineIndicator } from "@/pages/chat/ui/right/OnlineIndicator";
|
||||
import { OnlineStatus } from "@/pages/chat/ui/right/OnlineStatus";
|
||||
import defaultAvatar from "@/images/default-avatar.png";
|
||||
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";
|
||||
|
||||
interface SearchUser extends User {
|
||||
@@ -16,7 +16,13 @@ interface SearchUser extends User {
|
||||
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 [searchQuery, setSearchQuery] = useState("");
|
||||
const [searchResults, setSearchResults] = useState<SearchUser[]>([]);
|
||||
@@ -165,6 +171,9 @@ export function UsernameSearch() {
|
||||
icon="arrow_back--outlined"
|
||||
/>
|
||||
) : "search--outlined"}
|
||||
containerRef={containerRef}
|
||||
headerRef={headerRef}
|
||||
bottomAppBarRef={bottomAppBarRef}
|
||||
>
|
||||
{isSearching && (
|
||||
<div className={styles.searchLoading}>
|
||||
|
||||
Reference in New Issue
Block a user