[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:
parent
5f8d5ea388
commit
079c3a3a99
5 changed files with 72 additions and 5 deletions
25
web/core/components/command-palette/actions/helper.tsx
Normal file
25
web/core/components/command-palette/actions/helper.tsx
Normal 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";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Command } from "cmdk";
|
import { Command } from "cmdk";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { IWorkspaceSearchResults } from "@plane/types";
|
import { IWorkspaceSearchResults } from "@plane/types";
|
||||||
|
|
@ -8,13 +9,15 @@ import { IWorkspaceSearchResults } from "@plane/types";
|
||||||
import { useAppRouter } from "@/hooks/use-app-router";
|
import { useAppRouter } from "@/hooks/use-app-router";
|
||||||
// plane web imports
|
// plane web imports
|
||||||
import { commandGroups } from "@/plane-web/components/command-palette";
|
import { commandGroups } from "@/plane-web/components/command-palette";
|
||||||
|
// helpers
|
||||||
|
import { openProjectAndScrollToSidebar } from "./helper";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
closePalette: () => void;
|
closePalette: () => void;
|
||||||
results: IWorkspaceSearchResults;
|
results: IWorkspaceSearchResults;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CommandPaletteSearchResults: React.FC<Props> = (props) => {
|
export const CommandPaletteSearchResults: React.FC<Props> = observer((props) => {
|
||||||
const { closePalette, results } = props;
|
const { closePalette, results } = props;
|
||||||
// router
|
// router
|
||||||
const router = useAppRouter();
|
const router = useAppRouter();
|
||||||
|
|
@ -38,6 +41,12 @@ export const CommandPaletteSearchResults: React.FC<Props> = (props) => {
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
closePalette();
|
closePalette();
|
||||||
router.push(currentSection.path(item, projectId));
|
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 ?? ""}`}
|
value={`${key}-${item?.id}-${item.name}-${item.project__identifier ?? ""}-${item.sequence_id ?? ""}`}
|
||||||
className="focus:outline-none"
|
className="focus:outline-none"
|
||||||
|
|
@ -54,4 +63,4 @@ export const CommandPaletteSearchResults: React.FC<Props> = (props) => {
|
||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import { LeaveProjectModal, PublishProjectModal } from "@/components/project";
|
||||||
// helpers
|
// helpers
|
||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
// hooks
|
// 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";
|
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||||
// plane-web components
|
// plane-web components
|
||||||
import { ProjectNavigationRoot } from "@/plane-web/components/sidebar";
|
import { ProjectNavigationRoot } from "@/plane-web/components/sidebar";
|
||||||
|
|
@ -64,12 +64,13 @@ export const SidebarProjectsListItem: React.FC<Props> = observer((props) => {
|
||||||
const { getPartialProjectById } = useProject();
|
const { getPartialProjectById } = useProject();
|
||||||
const { isMobile } = usePlatformOS();
|
const { isMobile } = usePlatformOS();
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
|
const { getIsProjectListOpen, toggleProjectListOpen } = useCommandPalette();
|
||||||
// states
|
// states
|
||||||
const [leaveProjectModalOpen, setLeaveProjectModal] = useState(false);
|
const [leaveProjectModalOpen, setLeaveProjectModal] = useState(false);
|
||||||
const [publishModalOpen, setPublishModal] = useState(false);
|
const [publishModalOpen, setPublishModal] = useState(false);
|
||||||
const [isMenuActive, setIsMenuActive] = useState(false);
|
const [isMenuActive, setIsMenuActive] = useState(false);
|
||||||
const [isDragging, setIsDragging] = 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);
|
const [instruction, setInstruction] = useState<"DRAG_OVER" | "DRAG_BELOW" | undefined>(undefined);
|
||||||
// refs
|
// refs
|
||||||
const actionSectionRef = useRef<HTMLDivElement | null>(null);
|
const actionSectionRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
@ -79,6 +80,8 @@ export const SidebarProjectsListItem: React.FC<Props> = observer((props) => {
|
||||||
const { workspaceSlug, projectId: URLProjectId } = useParams();
|
const { workspaceSlug, projectId: URLProjectId } = useParams();
|
||||||
// derived values
|
// derived values
|
||||||
const project = getPartialProjectById(projectId);
|
const project = getPartialProjectById(projectId);
|
||||||
|
// toggle project list open
|
||||||
|
const setIsProjectListOpen = (value: boolean) => toggleProjectListOpen(projectId, value);
|
||||||
// auth
|
// auth
|
||||||
const isAdmin = allowPermissions(
|
const isAdmin = allowPermissions(
|
||||||
[EUserPermissions.ADMIN],
|
[EUserPermissions.ADMIN],
|
||||||
|
|
@ -198,7 +201,7 @@ export const SidebarProjectsListItem: React.FC<Props> = observer((props) => {
|
||||||
if (URLProjectId === project.id) setIsProjectListOpen(true);
|
if (URLProjectId === project.id) setIsProjectListOpen(true);
|
||||||
}, [URLProjectId]);
|
}, [URLProjectId]);
|
||||||
|
|
||||||
const handleItemClick = () => setIsProjectListOpen((prev) => !prev);
|
const handleItemClick = () => setIsProjectListOpen(!isProjectListOpen);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PublishProjectModal isOpen={publishModalOpen} project={project} onClose={() => setPublishModal(false)} />
|
<PublishProjectModal isOpen={publishModalOpen} project={project} onClose={() => setPublishModal(false)} />
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { observable, action, makeObservable } from "mobx";
|
import { observable, action, makeObservable } from "mobx";
|
||||||
|
import { computedFn } from "mobx-utils";
|
||||||
import {
|
import {
|
||||||
EIssuesStoreType,
|
EIssuesStoreType,
|
||||||
TCreateModalStoreTypes,
|
TCreateModalStoreTypes,
|
||||||
|
|
@ -26,6 +27,8 @@ export interface IBaseCommandPaletteStore {
|
||||||
isBulkDeleteIssueModalOpen: boolean;
|
isBulkDeleteIssueModalOpen: boolean;
|
||||||
createIssueStoreType: TCreateModalStoreTypes;
|
createIssueStoreType: TCreateModalStoreTypes;
|
||||||
allStickiesModal: boolean;
|
allStickiesModal: boolean;
|
||||||
|
projectListOpenMap: Record<string, boolean>;
|
||||||
|
getIsProjectListOpen: (projectId: string) => boolean;
|
||||||
// toggle actions
|
// toggle actions
|
||||||
toggleCommandPaletteModal: (value?: boolean) => void;
|
toggleCommandPaletteModal: (value?: boolean) => void;
|
||||||
toggleShortcutModal: (value?: boolean) => void;
|
toggleShortcutModal: (value?: boolean) => void;
|
||||||
|
|
@ -38,6 +41,7 @@ export interface IBaseCommandPaletteStore {
|
||||||
toggleDeleteIssueModal: (value?: boolean) => void;
|
toggleDeleteIssueModal: (value?: boolean) => void;
|
||||||
toggleBulkDeleteIssueModal: (value?: boolean) => void;
|
toggleBulkDeleteIssueModal: (value?: boolean) => void;
|
||||||
toggleAllStickiesModal: (value?: boolean) => void;
|
toggleAllStickiesModal: (value?: boolean) => void;
|
||||||
|
toggleProjectListOpen: (projectId: string, value?: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class BaseCommandPaletteStore implements IBaseCommandPaletteStore {
|
export abstract class BaseCommandPaletteStore implements IBaseCommandPaletteStore {
|
||||||
|
|
@ -54,6 +58,7 @@ export abstract class BaseCommandPaletteStore implements IBaseCommandPaletteStor
|
||||||
createPageModal: TCreatePageModal = DEFAULT_CREATE_PAGE_MODAL_DATA;
|
createPageModal: TCreatePageModal = DEFAULT_CREATE_PAGE_MODAL_DATA;
|
||||||
createIssueStoreType: TCreateModalStoreTypes = EIssuesStoreType.PROJECT;
|
createIssueStoreType: TCreateModalStoreTypes = EIssuesStoreType.PROJECT;
|
||||||
allStickiesModal: boolean = false;
|
allStickiesModal: boolean = false;
|
||||||
|
projectListOpenMap: Record<string, boolean> = {};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
|
|
@ -70,6 +75,7 @@ export abstract class BaseCommandPaletteStore implements IBaseCommandPaletteStor
|
||||||
createPageModal: observable,
|
createPageModal: observable,
|
||||||
createIssueStoreType: observable,
|
createIssueStoreType: observable,
|
||||||
allStickiesModal: observable,
|
allStickiesModal: observable,
|
||||||
|
projectListOpenMap: observable,
|
||||||
// projectPages: computed,
|
// projectPages: computed,
|
||||||
// toggle actions
|
// toggle actions
|
||||||
toggleCommandPaletteModal: action,
|
toggleCommandPaletteModal: action,
|
||||||
|
|
@ -83,6 +89,7 @@ export abstract class BaseCommandPaletteStore implements IBaseCommandPaletteStor
|
||||||
toggleDeleteIssueModal: action,
|
toggleDeleteIssueModal: action,
|
||||||
toggleBulkDeleteIssueModal: action,
|
toggleBulkDeleteIssueModal: action,
|
||||||
toggleAllStickiesModal: action,
|
toggleAllStickiesModal: action,
|
||||||
|
toggleProjectListOpen: action,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,6 +111,18 @@ export abstract class BaseCommandPaletteStore implements IBaseCommandPaletteStor
|
||||||
this.allStickiesModal
|
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
|
* Toggles the command palette modal
|
||||||
|
|
|
||||||
|
|
@ -942,3 +942,14 @@ div.web-view-spinner div.bar12 {
|
||||||
.animate-fade-out {
|
.animate-fade-out {
|
||||||
animation: fadeOut 500ms ease-in 100ms forwards;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue