@@ -5,13 +5,13 @@ import { Command } from "cmdk";
55import { observer } from "mobx-react" ;
66import { useParams } from "next/navigation" ;
77import useSWR from "swr" ;
8- import { FolderPlus , Search , Settings } from "lucide-react" ;
8+ import { CommandIcon , FolderPlus , Search , Settings } from "lucide-react" ;
99import { Dialog , Transition } from "@headlessui/react" ;
1010// plane imports
1111import { EUserPermissions , EUserPermissionsLevel } from "@plane/constants" ;
1212import { useTranslation } from "@plane/i18n" ;
1313import { IWorkspaceSearchResults } from "@plane/types" ;
14- import { LayersIcon , Loader , ToggleSwitch , Tooltip } from "@plane/ui" ;
14+ import { LayersIcon , Loader , ToggleSwitch } from "@plane/ui" ;
1515// components
1616import {
1717 ChangeIssueAssignee ,
@@ -66,13 +66,13 @@ export const CommandModal: React.FC = observer(() => {
6666 page : [ ] ,
6767 } ,
6868 } ) ;
69- const [ isWorkspaceLevel , setIsWorkspaceLevel ] = useState ( false ) ;
69+ const [ isWorkspaceLevel , setIsWorkspaceLevel ] = useState ( true ) ;
7070 const [ pages , setPages ] = useState < string [ ] > ( [ ] ) ;
7171 // plane hooks
7272 const { t } = useTranslation ( ) ;
7373 // hooks
7474 const { workspaceProjectIds } = useProject ( ) ;
75- const { isMobile } = usePlatformOS ( ) ;
75+ const { platform , isMobile } = usePlatformOS ( ) ;
7676 const { canPerformAnyCreateAction } = useUser ( ) ;
7777 const { isCommandPaletteOpen, toggleCommandPaletteModal, toggleCreateIssueModal, toggleCreateProjectModal } =
7878 useCommandPalette ( ) ;
@@ -176,22 +176,60 @@ export const CommandModal: React.FC = observer(() => {
176176 leaveFrom = "opacity-100 translate-y-0 sm:scale-100"
177177 leaveTo = "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
178178 >
179- < 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" >
179+ < 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" >
180180 < div className = "w-full max-w-2xl" >
181181 < Command
182182 filter = { ( value , search ) => {
183183 if ( value . toLowerCase ( ) . includes ( search . toLowerCase ( ) ) ) return 1 ;
184184 return 0 ;
185185 } }
186- onKeyDown = { ( e ) => {
187- // when search term is not empty, esc should clear the search term
188- if ( e . key === "Escape" && searchTerm ) setSearchTerm ( "" ) ;
186+ shouldFilter = { searchTerm . length > 0 }
187+ onKeyDown = { ( e : any ) => {
188+ if ( ( e . metaKey || e . ctrlKey ) && e . key . toLowerCase ( ) === "k" ) {
189+ e . preventDefault ( ) ;
190+ e . stopPropagation ( ) ;
191+ closePalette ( ) ;
192+ return ;
193+ }
194+
195+ if ( e . key === "Tab" ) {
196+ e . preventDefault ( ) ;
197+ const commandList = document . querySelector ( "[cmdk-list]" ) ;
198+ const items = commandList ?. querySelectorAll ( "[cmdk-item]" ) || [ ] ;
199+ const selectedItem = commandList ?. querySelector ( '[aria-selected="true"]' ) ;
200+ if ( items . length === 0 ) return ;
201+
202+ const currentIndex = Array . from ( items ) . indexOf ( selectedItem as Element ) ;
203+ let nextIndex ;
204+
205+ if ( e . shiftKey ) {
206+ nextIndex = currentIndex > 0 ? currentIndex - 1 : items . length - 1 ;
207+ } else {
208+ nextIndex = currentIndex < items . length - 1 ? currentIndex + 1 : 0 ;
209+ }
210+
211+ const nextItem = items [ nextIndex ] as HTMLElement ;
212+ if ( nextItem ) {
213+ nextItem . setAttribute ( "aria-selected" , "true" ) ;
214+ selectedItem ?. setAttribute ( "aria-selected" , "false" ) ;
215+ nextItem . focus ( ) ;
216+ nextItem . scrollIntoView ( {
217+ behavior : "smooth" ,
218+ block : "nearest" ,
219+ } ) ;
220+ }
221+ }
222+
223+ if ( e . key === "Escape" && searchTerm ) {
224+ e . preventDefault ( ) ;
225+ setSearchTerm ( "" ) ;
226+ }
189227
190- // when user tries to close the modal with esc
191- if ( e . key === "Escape" && ! page && ! searchTerm ) closePalette ( ) ;
228+ if ( e . key === "Escape" && ! page && ! searchTerm ) {
229+ e . preventDefault ( ) ;
230+ closePalette ( ) ;
231+ }
192232
193- // Escape goes to previous page
194- // Backspace goes to previous page when search is empty
195233 if ( e . key === "Escape" || ( e . key === "Backspace" && ! searchTerm ) ) {
196234 e . preventDefault ( ) ;
197235 setPages ( ( pages ) => pages . slice ( 0 , - 1 ) ) ;
@@ -200,7 +238,7 @@ export const CommandModal: React.FC = observer(() => {
200238 } }
201239 >
202240 < div
203- className = { `flex gap-4 p-3 pb-0 sm:items-center ${
241+ className = { `flex gap-4 pb-0 sm:items-center ${
204242 issueDetails ? "flex-col justify-between sm:flex-row" : "justify-end"
205243 } `}
206244 >
@@ -216,23 +254,6 @@ export const CommandModal: React.FC = observer(() => {
216254 { issueDetails . name }
217255 </ div >
218256 ) }
219- { projectId && (
220- < Tooltip tooltipContent = "Toggle workspace level search" isMobile = { isMobile } >
221- < div className = "flex flex-shrink-0 cursor-pointer items-center gap-1 self-end text-xs sm:self-center" >
222- < button
223- type = "button"
224- onClick = { ( ) => setIsWorkspaceLevel ( ( prevData ) => ! prevData ) }
225- className = "flex-shrink-0"
226- >
227- Workspace Level
228- </ button >
229- < ToggleSwitch
230- value = { isWorkspaceLevel }
231- onChange = { ( ) => setIsWorkspaceLevel ( ( prevData ) => ! prevData ) }
232- />
233- </ div >
234- </ Tooltip >
235- ) }
236257 </ div >
237258 < div className = "relative" >
238259 < Search
@@ -413,6 +434,28 @@ export const CommandModal: React.FC = observer(() => {
413434 </ Command . List >
414435 </ Command >
415436 </ div >
437+ { /* Bottom overlay */ }
438+ < 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" >
439+ < div className = "flex items-center gap-2" >
440+ < span className = "text-xs text-custom-text-300" > Actions</ span >
441+ < div className = "flex items-center gap-1" >
442+ < 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" >
443+ { platform === "MacOS" ? < CommandIcon className = "h-2.5 w-2.5 text-custom-text-200" /> : "Ctrl" }
444+ </ div >
445+ < 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" >
446+ K
447+ </ kbd >
448+ </ div >
449+ </ div >
450+ < div className = "flex items-center gap-2" >
451+ < span className = "text-xs text-custom-text-300" > Workspace Level</ span >
452+ < ToggleSwitch
453+ value = { isWorkspaceLevel }
454+ onChange = { ( ) => setIsWorkspaceLevel ( ( prevData ) => ! prevData ) }
455+ size = "sm"
456+ />
457+ </ div >
458+ </ div >
416459 </ Dialog . Panel >
417460 </ Transition . Child >
418461 </ div >
0 commit comments