diff --git a/frontend/src/core/components/SearchBar.tsx b/frontend/src/core/components/SearchBar.tsx index 8616475..5c59b09 100644 --- a/frontend/src/core/components/SearchBar.tsx +++ b/frontend/src/core/components/SearchBar.tsx @@ -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; + headerRef?: React.RefObject; + bottomAppBarRef?: React.RefObject; } 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("48px"); + const [isTransitioning, setIsTransitioning] = useState(false); + const [showResults, setShowResults] = useState(false); const searchContainerRef = useRef(null); const inputRef = useRef(null); const parentContainerRef = useRef(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({ - {/* Results Section - Only visible when expanded */} - {isExpanded && ( -
+ {/* Results Section - Visible during expansion and collapse transition */} + {showResults && ( +
{children}
)} diff --git a/frontend/src/core/components/css/searchBar.module.scss b/frontend/src/core/components/css/searchBar.module.scss index a63d859..7f6f049 100644 --- a/frontend/src/core/components/css/searchBar.module.scss +++ b/frontend/src/core/components/css/searchBar.module.scss @@ -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 diff --git a/frontend/src/pages/chat/css/left-panel.module.scss b/frontend/src/pages/chat/css/left-panel.module.scss index 0cf9e9c..375f340 100644 --- a/frontend/src/pages/chat/css/left-panel.module.scss +++ b/frontend/src/pages/chat/css/left-panel.module.scss @@ -108,6 +108,7 @@ padding: 32px; color: $color-dark-on-surface-variant; text-align: center; + overflow: hidden; } // Custom styling for search result images diff --git a/frontend/src/pages/chat/ui/left/ChatHeader.tsx b/frontend/src/pages/chat/ui/left/ChatHeader.tsx index cfb4f14..67c476d 100644 --- a/frontend/src/pages/chat/ui/left/ChatHeader.tsx +++ b/frontend/src/pages/chat/ui/left/ChatHeader.tsx @@ -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 }) { const { profileData } = useProfile(); const { setProfileDialog, user } = useAppState(); const [profilePictureUrl, setProfilePictureUrl] = useState(profileData?.profile_picture || defaultAvatar); @@ -27,7 +27,7 @@ export function ChatHeader() { return ( <> -
+
Logo
{PRODUCT_NAME}
diff --git a/frontend/src/pages/chat/ui/left/LeftPanel.tsx b/frontend/src/pages/chat/ui/left/LeftPanel.tsx index 7fdce4b..3287784 100644 --- a/frontend/src/pages/chat/ui/left/LeftPanel.tsx +++ b/frontend/src/pages/chat/ui/left/LeftPanel.tsx @@ -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 }) { const [settingsOpen, onSettingsOpenChange] = useState(false); const { logout } = useAppState(); return ( <> - + onSettingsOpenChange(true)} />
(null); + const headerRef = useRef(null); + const bottomAppBarRef = useRef(null); + return ( -
- +
+
- +
- +
); } diff --git a/frontend/src/pages/chat/ui/left/UsernameSearch.tsx b/frontend/src/pages/chat/ui/left/UsernameSearch.tsx index b27c861..9ed8dae 100644 --- a/frontend/src/pages/chat/ui/left/UsernameSearch.tsx +++ b/frontend/src/pages/chat/ui/left/UsernameSearch.tsx @@ -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; + headerRef?: React.RefObject; + bottomAppBarRef?: React.RefObject; +} + +export function UsernameSearch({ containerRef, headerRef, bottomAppBarRef }: UsernameSearchProps) { const { user, switchToDM, chat } = useAppState(); const [searchQuery, setSearchQuery] = useState(""); const [searchResults, setSearchResults] = useState([]); @@ -165,6 +171,9 @@ export function UsernameSearch() { icon="arrow_back--outlined" /> ) : "search--outlined"} + containerRef={containerRef} + headerRef={headerRef} + bottomAppBarRef={bottomAppBarRef} > {isSearching && (