Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 73 additions & 30 deletions web/core/components/command-palette/command-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<string[]>([]);
// 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();
Expand Down Expand Up @@ -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"
>
<Dialog.Panel className="relative flex w-full max-w-2xl transform items-center justify-center divide-y divide-custom-border-200 divide-opacity-10 rounded-lg bg-custom-background-100 shadow-custom-shadow-md transition-all">
<Dialog.Panel className="relative flex w-full max-w-2xl transform flex-col items-center justify-center divide-y divide-custom-border-200 divide-opacity-10 rounded-lg bg-custom-background-100 shadow-custom-shadow-md transition-all">
<div className="w-full max-w-2xl">
<Command
filter={(value, search) => {
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));
Expand All @@ -200,7 +238,7 @@ export const CommandModal: React.FC = observer(() => {
}}
>
<div
className={`flex gap-4 p-3 pb-0 sm:items-center ${
className={`flex gap-4 pb-0 sm:items-center ${
issueDetails ? "flex-col justify-between sm:flex-row" : "justify-end"
}`}
>
Expand All @@ -216,23 +254,6 @@ export const CommandModal: React.FC = observer(() => {
{issueDetails.name}
</div>
)}
{projectId && (
<Tooltip tooltipContent="Toggle workspace level search" isMobile={isMobile}>
<div className="flex flex-shrink-0 cursor-pointer items-center gap-1 self-end text-xs sm:self-center">
<button
type="button"
onClick={() => setIsWorkspaceLevel((prevData) => !prevData)}
className="flex-shrink-0"
>
Workspace Level
</button>
<ToggleSwitch
value={isWorkspaceLevel}
onChange={() => setIsWorkspaceLevel((prevData) => !prevData)}
/>
</div>
</Tooltip>
)}
</div>
<div className="relative">
<Search
Expand Down Expand Up @@ -413,6 +434,28 @@ export const CommandModal: React.FC = observer(() => {
</Command.List>
</Command>
</div>
{/* Bottom overlay */}
<div className="w-full flex items-center justify-between px-4 py-2 border-t border-custom-border-200 bg-custom-background-90/80 rounded-b-lg">
<div className="flex items-center gap-2">
<span className="text-xs text-custom-text-300">Actions</span>
<div className="flex items-center gap-1">
<div className="grid h-6 min-w-[1.5rem] place-items-center rounded bg-custom-background-80 border-[0.5px] border-custom-border-200 px-1.5 text-[10px] text-custom-text-200">
{platform === "MacOS" ? <CommandIcon className="h-2.5 w-2.5 text-custom-text-200" /> : "Ctrl"}
</div>
<kbd className="grid h-6 min-w-[1.5rem] place-items-center rounded bg-custom-background-80 border-[0.5px] border-custom-border-200 px-1.5 text-[10px] text-custom-text-200">
K
</kbd>
</div>
</div>
<div className="flex items-center gap-2">
<span className="text-xs text-custom-text-300">Workspace Level</span>
<ToggleSwitch
value={isWorkspaceLevel}
onChange={() => setIsWorkspaceLevel((prevData) => !prevData)}
size="sm"
/>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
Expand Down
Loading