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 { 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}>
|
||||||
|
|||||||
@@ -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}>
|
||||||
|
|||||||
Reference in New Issue
Block a user