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
All checks were successful
Deploy Flowstate to VPS / deploy (push) Successful in 53s
This commit is contained in:
parent
6f42285405
commit
093944e8c4
2 changed files with 49 additions and 8 deletions
|
|
@ -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"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue