diff --git a/web/core/components/command-palette/command-modal.tsx b/web/core/components/command-palette/command-modal.tsx index db7202319ca..19bf7eb5bee 100644 --- a/web/core/components/command-palette/command-modal.tsx +++ b/web/core/components/command-palette/command-modal.tsx @@ -5,13 +5,13 @@ import { Command } from "cmdk"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import useSWR from "swr"; -import { FolderPlus, Search, Settings } from "lucide-react"; +import { CommandIcon, FolderPlus, Search, Settings } from "lucide-react"; import { Dialog, Transition } from "@headlessui/react"; // plane imports import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { IWorkspaceSearchResults } from "@plane/types"; -import { LayersIcon, Loader, ToggleSwitch, Tooltip } from "@plane/ui"; +import { LayersIcon, Loader, ToggleSwitch } from "@plane/ui"; // components import { ChangeIssueAssignee, @@ -66,13 +66,13 @@ export const CommandModal: React.FC = observer(() => { page: [], }, }); - const [isWorkspaceLevel, setIsWorkspaceLevel] = useState(false); + const [isWorkspaceLevel, setIsWorkspaceLevel] = useState(true); const [pages, setPages] = useState([]); // plane hooks const { t } = useTranslation(); // hooks const { workspaceProjectIds } = useProject(); - const { isMobile } = usePlatformOS(); + const { platform, isMobile } = usePlatformOS(); const { canPerformAnyCreateAction } = useUser(); const { isCommandPaletteOpen, toggleCommandPaletteModal, toggleCreateIssueModal, toggleCreateProjectModal } = useCommandPalette(); @@ -176,22 +176,60 @@ export const CommandModal: React.FC = observer(() => { leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - +
{ if (value.toLowerCase().includes(search.toLowerCase())) return 1; return 0; }} - onKeyDown={(e) => { - // when search term is not empty, esc should clear the search term - if (e.key === "Escape" && searchTerm) setSearchTerm(""); + shouldFilter={searchTerm.length > 0} + onKeyDown={(e: any) => { + if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") { + e.preventDefault(); + e.stopPropagation(); + closePalette(); + return; + } + + if (e.key === "Tab") { + e.preventDefault(); + const commandList = document.querySelector("[cmdk-list]"); + const items = commandList?.querySelectorAll("[cmdk-item]") || []; + const selectedItem = commandList?.querySelector('[aria-selected="true"]'); + if (items.length === 0) return; + + const currentIndex = Array.from(items).indexOf(selectedItem as Element); + let nextIndex; + + if (e.shiftKey) { + nextIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1; + } else { + nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0; + } + + const nextItem = items[nextIndex] as HTMLElement; + if (nextItem) { + nextItem.setAttribute("aria-selected", "true"); + selectedItem?.setAttribute("aria-selected", "false"); + nextItem.focus(); + nextItem.scrollIntoView({ + behavior: "smooth", + block: "nearest", + }); + } + } + + if (e.key === "Escape" && searchTerm) { + e.preventDefault(); + setSearchTerm(""); + } - // when user tries to close the modal with esc - if (e.key === "Escape" && !page && !searchTerm) closePalette(); + if (e.key === "Escape" && !page && !searchTerm) { + e.preventDefault(); + closePalette(); + } - // Escape goes to previous page - // Backspace goes to previous page when search is empty if (e.key === "Escape" || (e.key === "Backspace" && !searchTerm)) { e.preventDefault(); setPages((pages) => pages.slice(0, -1)); @@ -200,7 +238,7 @@ export const CommandModal: React.FC = observer(() => { }} >
@@ -216,23 +254,6 @@ export const CommandModal: React.FC = observer(() => { {issueDetails.name}
)} - {projectId && ( - -
- - setIsWorkspaceLevel((prevData) => !prevData)} - /> -
-
- )}
{
+ {/* Bottom overlay */} +
+
+ Actions +
+
+ {platform === "MacOS" ? : "Ctrl"} +
+ + K + +
+
+
+ Workspace Level + setIsWorkspaceLevel((prevData) => !prevData)} + size="sm" + /> +
+