[WEB-5569] chore: top nav search enhancements (#8226)

* chore: top nav power k search menu enhancements

* chore: expandable search panel refactor
This commit is contained in:
Anmol Singh Bhatia 2025-12-03 16:09:12 +05:30 committed by GitHub
parent b8a41ad5a0
commit 105ac5ece5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 33 additions and 7 deletions

View file

@ -272,6 +272,7 @@ export const TopNavPowerK = observer(() => {
isWorkspaceLevel={isWorkspaceLevel} isWorkspaceLevel={isWorkspaceLevel}
searchTerm={searchTerm} searchTerm={searchTerm}
setSearchTerm={setSearchTerm} setSearchTerm={setSearchTerm}
handleSearchMenuClose={() => closePanel()}
/> />
</Command.List> </Command.List>
<PowerKModalFooter <PowerKModalFooter

View file

@ -11,6 +11,7 @@ export type TPowerKCommandsListProps = {
isWorkspaceLevel: boolean; isWorkspaceLevel: boolean;
searchTerm: string; searchTerm: string;
setSearchTerm: (value: string) => void; setSearchTerm: (value: string) => void;
handleSearchMenuClose?: () => void;
}; };
export function ProjectsAppPowerKCommandsList(props: TPowerKCommandsListProps) { export function ProjectsAppPowerKCommandsList(props: TPowerKCommandsListProps) {
@ -22,6 +23,7 @@ export function ProjectsAppPowerKCommandsList(props: TPowerKCommandsListProps) {
isWorkspaceLevel, isWorkspaceLevel,
searchTerm, searchTerm,
setSearchTerm, setSearchTerm,
handleSearchMenuClose,
} = props; } = props;
return ( return (
@ -32,6 +34,7 @@ export function ProjectsAppPowerKCommandsList(props: TPowerKCommandsListProps) {
isWorkspaceLevel={!context.params.projectId || isWorkspaceLevel} isWorkspaceLevel={!context.params.projectId || isWorkspaceLevel}
searchTerm={searchTerm} searchTerm={searchTerm}
updateSearchTerm={setSearchTerm} updateSearchTerm={setSearchTerm}
handleSearchMenuClose={handleSearchMenuClose}
/> />
<PowerKContextBasedPagesList <PowerKContextBasedPagesList
activeContext={context.activeContext} activeContext={context.activeContext}

View file

@ -22,10 +22,11 @@ type Props = {
isWorkspaceLevel: boolean; isWorkspaceLevel: boolean;
searchTerm: string; searchTerm: string;
updateSearchTerm: (value: string) => void; updateSearchTerm: (value: string) => void;
handleSearchMenuClose?: () => void;
}; };
export function PowerKModalSearchMenu(props: Props) { export function PowerKModalSearchMenu(props: Props) {
const { activePage, context, isWorkspaceLevel, searchTerm, updateSearchTerm } = props; const { activePage, context, isWorkspaceLevel, searchTerm, updateSearchTerm, handleSearchMenuClose } = props;
// states // states
const [resultsCount, setResultsCount] = useState(0); const [resultsCount, setResultsCount] = useState(0);
const [isSearching, setIsSearching] = useState(false); const [isSearching, setIsSearching] = useState(false);
@ -68,6 +69,11 @@ export function PowerKModalSearchMenu(props: Props) {
if (activePage) return null; if (activePage) return null;
const handleClosePalette = () => {
handleSearchMenuClose?.();
togglePowerKModal(false);
};
return ( return (
<> <>
{searchTerm.trim() !== "" && ( {searchTerm.trim() !== "" && (
@ -97,9 +103,7 @@ export function PowerKModalSearchMenu(props: Props) {
/> />
)} )}
{searchTerm.trim() !== "" && ( {searchTerm.trim() !== "" && <PowerKModalSearchResults closePalette={handleClosePalette} results={results} />}
<PowerKModalSearchResults closePalette={() => togglePowerKModal(false)} results={results} />
)}
</> </>
); );
} }

View file

@ -1,4 +1,4 @@
import { useCallback, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import { useOutsideClickDetector } from "@plane/hooks"; import { useOutsideClickDetector } from "@plane/hooks";
type UseExpandableSearchOptions = { type UseExpandableSearchOptions = {
@ -8,6 +8,7 @@ type UseExpandableSearchOptions = {
/** /**
* Custom hook for expandable search input behavior * Custom hook for expandable search input behavior
* Handles focus management to prevent unwanted opening on programmatic focus restoration * Handles focus management to prevent unwanted opening on programmatic focus restoration
* Opens on click, typing, or keyboard shortcut (via PowerK Cmd+F)
*/ */
export const useExpandableSearch = (options?: UseExpandableSearchOptions) => { export const useExpandableSearch = (options?: UseExpandableSearchOptions) => {
const { onClose } = options || {}; const { onClose } = options || {};
@ -19,6 +20,7 @@ export const useExpandableSearch = (options?: UseExpandableSearchOptions) => {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const wasClickedRef = useRef<boolean>(false); const wasClickedRef = useRef<boolean>(false);
const wasKeyboardTriggeredRef = useRef<boolean>(false);
// Handle close // Handle close
const handleClose = useCallback(() => { const handleClose = useCallback(() => {
@ -37,16 +39,32 @@ export const useExpandableSearch = (options?: UseExpandableSearchOptions) => {
// Outside click detection // Outside click detection
useOutsideClickDetector(containerRef, handleOutsideClick); useOutsideClickDetector(containerRef, handleOutsideClick);
// Track keyboard shortcuts that trigger focus (Cmd+F / Ctrl+F)
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === "f") {
// Mark as keyboard triggered so handleFocus knows to open
wasKeyboardTriggeredRef.current = true;
}
};
document.addEventListener("keydown", handleKeyDown);
return () => {
document.removeEventListener("keydown", handleKeyDown);
};
}, []);
// Track explicit clicks // Track explicit clicks
const handleMouseDown = useCallback(() => { const handleMouseDown = useCallback(() => {
wasClickedRef.current = true; wasClickedRef.current = true;
}, []); }, []);
// Only open on explicit clicks, not programmatic focus // Open on explicit clicks or keyboard shortcut, not programmatic focus restoration
const handleFocus = useCallback(() => { const handleFocus = useCallback(() => {
if (wasClickedRef.current) { if (wasClickedRef.current || wasKeyboardTriggeredRef.current) {
setIsOpen(true); setIsOpen(true);
wasClickedRef.current = false; wasClickedRef.current = false;
wasKeyboardTriggeredRef.current = false;
} }
}, []); }, []);