[WEB-3978] chore: cmd k search result redirection improvements (#7012)

* fix: work item tab highlight

* chore: projectListOpen state and toggle method added to command palette store

* chore: openProjectAndScrollToSidebar helper function and highlight keyframes added

* chore: SidebarProjectsListItem updated

* chore: openProjectAndScrollToSidebar implementation

* chore: code refactor

* chore: code refactor

* chore: code refactor

* chore: code refactor

* chore: code refactor

* chore: code refactor

* chore: code refactor
This commit is contained in:
Anmol Singh Bhatia 2025-05-12 19:15:39 +05:30 committed by GitHub
parent 5f8d5ea388
commit 079c3a3a99
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 72 additions and 5 deletions

View file

@ -0,0 +1,25 @@
import { store } from "@/lib/store-context";
export const openProjectAndScrollToSidebar = (itemProjectId: string | undefined) => {
if (!itemProjectId) {
console.warn("No project id provided. Cannot open project and scroll to sidebar.");
return;
}
// open the project list
store.commandPalette.toggleProjectListOpen(itemProjectId, true);
// scroll to the element
const scrollElementId = `sidebar-${itemProjectId}-JOINED`;
const scrollElement = document.getElementById(scrollElementId);
// if the element exists, scroll to it
if (scrollElement) {
setTimeout(() => {
scrollElement.scrollIntoView({ behavior: "smooth", block: "start" });
// Restart the highlight animation every time
scrollElement.style.animation = "none";
// Trigger a reflow to ensure the animation is restarted
void scrollElement.offsetWidth;
// Restart the highlight animation
scrollElement.style.animation = "highlight 2s ease-in-out";
});
}
};

View file

@ -1,6 +1,7 @@
"use client";
import { Command } from "cmdk";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// plane imports
import { IWorkspaceSearchResults } from "@plane/types";
@ -8,13 +9,15 @@ import { IWorkspaceSearchResults } from "@plane/types";
import { useAppRouter } from "@/hooks/use-app-router";
// plane web imports
import { commandGroups } from "@/plane-web/components/command-palette";
// helpers
import { openProjectAndScrollToSidebar } from "./helper";
type Props = {
closePalette: () => void;
results: IWorkspaceSearchResults;
};
export const CommandPaletteSearchResults: React.FC<Props> = (props) => {
export const CommandPaletteSearchResults: React.FC<Props> = observer((props) => {
const { closePalette, results } = props;
// router
const router = useAppRouter();
@ -38,6 +41,12 @@ export const CommandPaletteSearchResults: React.FC<Props> = (props) => {
onSelect={() => {
closePalette();
router.push(currentSection.path(item, projectId));
const itemProjectId =
item?.project_id ||
(Array.isArray(item?.project_ids) && item?.project_ids?.length > 0
? item?.project_ids[0]
: undefined);
if (itemProjectId) openProjectAndScrollToSidebar(itemProjectId);
}}
value={`${key}-${item?.id}-${item.name}-${item.project__identifier ?? ""}-${item.sequence_id ?? ""}`}
className="focus:outline-none"
@ -54,4 +63,4 @@ export const CommandPaletteSearchResults: React.FC<Props> = (props) => {
})}
</>
);
};
});

View file

@ -24,7 +24,7 @@ import { LeaveProjectModal, PublishProjectModal } from "@/components/project";
// helpers
import { cn } from "@/helpers/common.helper";
// hooks
import { useAppTheme, useEventTracker, useProject, useUserPermissions } from "@/hooks/store";
import { useAppTheme, useCommandPalette, useEventTracker, useProject, useUserPermissions } from "@/hooks/store";
import { usePlatformOS } from "@/hooks/use-platform-os";
// plane-web components
import { ProjectNavigationRoot } from "@/plane-web/components/sidebar";
@ -64,12 +64,13 @@ export const SidebarProjectsListItem: React.FC<Props> = observer((props) => {
const { getPartialProjectById } = useProject();
const { isMobile } = usePlatformOS();
const { allowPermissions } = useUserPermissions();
const { getIsProjectListOpen, toggleProjectListOpen } = useCommandPalette();
// states
const [leaveProjectModalOpen, setLeaveProjectModal] = useState(false);
const [publishModalOpen, setPublishModal] = useState(false);
const [isMenuActive, setIsMenuActive] = useState(false);
const [isDragging, setIsDragging] = useState(false);
const [isProjectListOpen, setIsProjectListOpen] = useState(false);
const isProjectListOpen = getIsProjectListOpen(projectId);
const [instruction, setInstruction] = useState<"DRAG_OVER" | "DRAG_BELOW" | undefined>(undefined);
// refs
const actionSectionRef = useRef<HTMLDivElement | null>(null);
@ -79,6 +80,8 @@ export const SidebarProjectsListItem: React.FC<Props> = observer((props) => {
const { workspaceSlug, projectId: URLProjectId } = useParams();
// derived values
const project = getPartialProjectById(projectId);
// toggle project list open
const setIsProjectListOpen = (value: boolean) => toggleProjectListOpen(projectId, value);
// auth
const isAdmin = allowPermissions(
[EUserPermissions.ADMIN],
@ -198,7 +201,7 @@ export const SidebarProjectsListItem: React.FC<Props> = observer((props) => {
if (URLProjectId === project.id) setIsProjectListOpen(true);
}, [URLProjectId]);
const handleItemClick = () => setIsProjectListOpen((prev) => !prev);
const handleItemClick = () => setIsProjectListOpen(!isProjectListOpen);
return (
<>
<PublishProjectModal isOpen={publishModalOpen} project={project} onClose={() => setPublishModal(false)} />

View file

@ -1,4 +1,5 @@
import { observable, action, makeObservable } from "mobx";
import { computedFn } from "mobx-utils";
import {
EIssuesStoreType,
TCreateModalStoreTypes,
@ -26,6 +27,8 @@ export interface IBaseCommandPaletteStore {
isBulkDeleteIssueModalOpen: boolean;
createIssueStoreType: TCreateModalStoreTypes;
allStickiesModal: boolean;
projectListOpenMap: Record<string, boolean>;
getIsProjectListOpen: (projectId: string) => boolean;
// toggle actions
toggleCommandPaletteModal: (value?: boolean) => void;
toggleShortcutModal: (value?: boolean) => void;
@ -38,6 +41,7 @@ export interface IBaseCommandPaletteStore {
toggleDeleteIssueModal: (value?: boolean) => void;
toggleBulkDeleteIssueModal: (value?: boolean) => void;
toggleAllStickiesModal: (value?: boolean) => void;
toggleProjectListOpen: (projectId: string, value?: boolean) => void;
}
export abstract class BaseCommandPaletteStore implements IBaseCommandPaletteStore {
@ -54,6 +58,7 @@ export abstract class BaseCommandPaletteStore implements IBaseCommandPaletteStor
createPageModal: TCreatePageModal = DEFAULT_CREATE_PAGE_MODAL_DATA;
createIssueStoreType: TCreateModalStoreTypes = EIssuesStoreType.PROJECT;
allStickiesModal: boolean = false;
projectListOpenMap: Record<string, boolean> = {};
constructor() {
makeObservable(this, {
@ -70,6 +75,7 @@ export abstract class BaseCommandPaletteStore implements IBaseCommandPaletteStor
createPageModal: observable,
createIssueStoreType: observable,
allStickiesModal: observable,
projectListOpenMap: observable,
// projectPages: computed,
// toggle actions
toggleCommandPaletteModal: action,
@ -83,6 +89,7 @@ export abstract class BaseCommandPaletteStore implements IBaseCommandPaletteStor
toggleDeleteIssueModal: action,
toggleBulkDeleteIssueModal: action,
toggleAllStickiesModal: action,
toggleProjectListOpen: action,
});
}
@ -104,6 +111,18 @@ export abstract class BaseCommandPaletteStore implements IBaseCommandPaletteStor
this.allStickiesModal
);
}
// computedFn
getIsProjectListOpen = computedFn((projectId: string) => this.projectListOpenMap[projectId]);
/**
* Toggles the project list open state
* @param projectId
* @param value
*/
toggleProjectListOpen = (projectId: string, value?: boolean) => {
if (value !== undefined) this.projectListOpenMap[projectId] = value;
else this.projectListOpenMap[projectId] = !this.projectListOpenMap[projectId];
};
/**
* Toggles the command palette modal

View file

@ -942,3 +942,14 @@ div.web-view-spinner div.bar12 {
.animate-fade-out {
animation: fadeOut 500ms ease-in 100ms forwards;
}
@keyframes highlight {
0% {
background-color: rgba(var(--color-background-90), 1);
border-radius: 4px;
}
100% {
background-color: transparent;
border-radius: 4px;
}
}