fix: workspace level toggle position, paddings, and tab navigation (#6580)
* fix: workspace level toggle position, paddings, and tab navigation * chore: platform-specific command icons --------- Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@plane.so>
This commit is contained in:
parent
ac74cd9e92
commit
6aa139a851
1 changed files with 73 additions and 30 deletions
|
|
@ -5,13 +5,13 @@ import { Command } from "cmdk";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import useSWR from "swr";
|
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";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { IWorkspaceSearchResults } from "@plane/types";
|
import { IWorkspaceSearchResults } from "@plane/types";
|
||||||
import { LayersIcon, Loader, ToggleSwitch, Tooltip } from "@plane/ui";
|
import { LayersIcon, Loader, ToggleSwitch } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import {
|
import {
|
||||||
ChangeIssueAssignee,
|
ChangeIssueAssignee,
|
||||||
|
|
@ -66,13 +66,13 @@ export const CommandModal: React.FC = observer(() => {
|
||||||
page: [],
|
page: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const [isWorkspaceLevel, setIsWorkspaceLevel] = useState(false);
|
const [isWorkspaceLevel, setIsWorkspaceLevel] = useState(true);
|
||||||
const [pages, setPages] = useState<string[]>([]);
|
const [pages, setPages] = useState<string[]>([]);
|
||||||
// plane hooks
|
// plane hooks
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
// hooks
|
// hooks
|
||||||
const { workspaceProjectIds } = useProject();
|
const { workspaceProjectIds } = useProject();
|
||||||
const { isMobile } = usePlatformOS();
|
const { platform, isMobile } = usePlatformOS();
|
||||||
const { canPerformAnyCreateAction } = useUser();
|
const { canPerformAnyCreateAction } = useUser();
|
||||||
const { isCommandPaletteOpen, toggleCommandPaletteModal, toggleCreateIssueModal, toggleCreateProjectModal } =
|
const { isCommandPaletteOpen, toggleCommandPaletteModal, toggleCreateIssueModal, toggleCreateProjectModal } =
|
||||||
useCommandPalette();
|
useCommandPalette();
|
||||||
|
|
@ -176,22 +176,60 @@ export const CommandModal: React.FC = observer(() => {
|
||||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
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">
|
<div className="w-full max-w-2xl">
|
||||||
<Command
|
<Command
|
||||||
filter={(value, search) => {
|
filter={(value, search) => {
|
||||||
if (value.toLowerCase().includes(search.toLowerCase())) return 1;
|
if (value.toLowerCase().includes(search.toLowerCase())) return 1;
|
||||||
return 0;
|
return 0;
|
||||||
}}
|
}}
|
||||||
onKeyDown={(e) => {
|
shouldFilter={searchTerm.length > 0}
|
||||||
// when search term is not empty, esc should clear the search term
|
onKeyDown={(e: any) => {
|
||||||
if (e.key === "Escape" && searchTerm) setSearchTerm("");
|
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
closePalette();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// when user tries to close the modal with esc
|
if (e.key === "Tab") {
|
||||||
if (e.key === "Escape" && !page && !searchTerm) closePalette();
|
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("");
|
||||||
|
}
|
||||||
|
|
||||||
|
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)) {
|
if (e.key === "Escape" || (e.key === "Backspace" && !searchTerm)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setPages((pages) => pages.slice(0, -1));
|
setPages((pages) => pages.slice(0, -1));
|
||||||
|
|
@ -200,7 +238,7 @@ export const CommandModal: React.FC = observer(() => {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<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"
|
issueDetails ? "flex-col justify-between sm:flex-row" : "justify-end"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|
@ -216,23 +254,6 @@ export const CommandModal: React.FC = observer(() => {
|
||||||
{issueDetails.name}
|
{issueDetails.name}
|
||||||
</div>
|
</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>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Search
|
<Search
|
||||||
|
|
@ -413,6 +434,28 @@ export const CommandModal: React.FC = observer(() => {
|
||||||
</Command.List>
|
</Command.List>
|
||||||
</Command>
|
</Command>
|
||||||
</div>
|
</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>
|
</Dialog.Panel>
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue