feat(search): hide searchbar instantly via window.history state when task modal is open
All checks were successful
Deploy Flowstate to VPS / deploy (push) Successful in 53s

This commit is contained in:
Chneemann 2026-08-26 09:11:26 +02:00
parent 6f42285405
commit 093944e8c4
No known key found for this signature in database
2 changed files with 49 additions and 8 deletions

View file

@ -36,7 +36,7 @@ interface TaskModalProps {
/** /**
* Renders a full task detail modal with status indicators, priority details, description, assignees, and creator-only action buttons. * Renders a full task detail modal with status indicators, priority details, description, assignees, and creator-only action buttons.
* Integrates keydown listeners to dismiss the modal on pressing the Escape key. * Supports ESC key navigation and updates URL query parameters dynamically.
* *
* @param {TaskModalProps} props - The component props. * @param {TaskModalProps} props - The component props.
* @returns {JSX.Element | null} The rendered modal component or null when no task is selected. * @returns {JSX.Element | null} The rendered modal component or null when no task is selected.
@ -46,13 +46,13 @@ export default function TaskModal({ task, onClose, onDelete }: TaskModalProps) {
useEffect(() => { useEffect(() => {
/** /**
* Keyboard event handler closing the modal dialog when pressing the Escape key. * Attaches a global keydown event listener to close the modal when the Escape key is pressed.
* *
* @param {KeyboardEvent} e - The global window keydown event. * @param {KeyboardEvent} e - The keyboard event object.
*/ */
const handleKeyDown = (e: KeyboardEvent) => { const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") { if (e.key === "Escape") {
onClose(); handleClose();
} }
}; };
window.addEventListener("keydown", handleKeyDown); window.addEventListener("keydown", handleKeyDown);
@ -61,6 +61,43 @@ export default function TaskModal({ task, onClose, onDelete }: TaskModalProps) {
}; };
}, [onClose]); }, [onClose]);
useEffect(() => {
/**
* Updates the URL search parameters to include `modal=task-detail` when a task is selected,
* maintaining modal state in the browser history without triggering a full page re-render.
*/
if (task) {
const params = new URLSearchParams(window.location.search);
if (params.get("modal") !== "task-detail") {
params.set("modal", "task-detail");
window.history.replaceState(
{},
"",
`${window.location.pathname}?${params.toString()}`,
);
}
}
}, [task]);
/**
* Removes modal query parameters from the browser location history without causing a Next.js soft navigation, then triggers onClose.
*/
const handleClose = () => {
const params = new URLSearchParams(window.location.search);
params.delete("modal");
const newQuery = params.toString();
window.history.replaceState(
{},
"",
newQuery
? `${window.location.pathname}?${newQuery}`
: window.location.pathname,
);
onClose();
};
if (!task) return null; if (!task) return null;
const priorityConfig = task.priority && PRIORITY_CONFIG[task.priority]; const priorityConfig = task.priority && PRIORITY_CONFIG[task.priority];
@ -72,7 +109,7 @@ export default function TaskModal({ task, onClose, onDelete }: TaskModalProps) {
return ( return (
<div <div
className="fixed inset-0 z-49 flex items-center justify-center bg-black/80 backdrop-blur-md p-4 animate-in fade-in duration-300" className="fixed inset-0 z-49 flex items-center justify-center bg-black/80 backdrop-blur-md p-4 animate-in fade-in duration-300"
onClick={onClose} onClick={handleClose}
> >
<div <div
className="bg-card border border-border rounded-3xl max-w-xl w-full max-h-[80vh] md:max-h-[90vh] shadow-2xl relative flex flex-col animate-in zoom-in-95 duration-200 overflow-hidden" className="bg-card border border-border rounded-3xl max-w-xl w-full max-h-[80vh] md:max-h-[90vh] shadow-2xl relative flex flex-col animate-in zoom-in-95 duration-200 overflow-hidden"
@ -114,7 +151,7 @@ export default function TaskModal({ task, onClose, onDelete }: TaskModalProps) {
</div> </div>
<button <button
onClick={onClose} onClick={handleClose}
className="text-foreground-muted hover:text-foreground p-2 rounded-xl bg-background/50 hover:bg-background border border-border transition-all cursor-pointer shrink-0" className="text-foreground-muted hover:text-foreground p-2 rounded-xl bg-background/50 hover:bg-background border border-border transition-all cursor-pointer shrink-0"
aria-label="Close modal" aria-label="Close modal"
> >
@ -238,7 +275,7 @@ export default function TaskModal({ task, onClose, onDelete }: TaskModalProps) {
<button <button
onClick={() => { onClick={() => {
onDelete?.(task.id); onDelete?.(task.id);
onClose(); handleClose();
}} }}
className="inline-flex items-center space-x-2 px-4 py-2.5 text-sm font-medium bg-destructive-bg text-destructive border border-destructive-border hover:bg-destructive/20 rounded-xl transition-all cursor-pointer" className="inline-flex items-center space-x-2 px-4 py-2.5 text-sm font-medium bg-destructive-bg text-destructive border border-destructive-border hover:bg-destructive/20 rounded-xl transition-all cursor-pointer"
> >

View file

@ -39,6 +39,7 @@ export default function SearchBar({
const router = useRouter(); const router = useRouter();
const pathname = usePathname(); const pathname = usePathname();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const isModalOpen = searchParams.get("modal") !== null;
// Check whether the current route supports the search // Check whether the current route supports the search
const isSearchableRoute = SEARCHABLE_ROUTES.some( const isSearchableRoute = SEARCHABLE_ROUTES.some(
@ -58,6 +59,9 @@ export default function SearchBar({
useEffect(() => { useEffect(() => {
if (!isSearchableRoute) return; if (!isSearchableRoute) return;
/**
* Updates the URL search parameters based on the current local input value.
*/
const timer = setTimeout(() => { const timer = setTimeout(() => {
if (localValue !== searchQuery) { if (localValue !== searchQuery) {
const params = new URLSearchParams(searchParams.toString()); const params = new URLSearchParams(searchParams.toString());
@ -83,7 +87,7 @@ export default function SearchBar({
isSearchableRoute, isSearchableRoute,
]); ]);
if (!isSearchableRoute) { if (!isSearchableRoute || isModalOpen) {
return null; return null;
} }