[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:
parent
b8a41ad5a0
commit
105ac5ece5
4 changed files with 33 additions and 7 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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} />
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue