Skip to content

Commit 69d74b4

Browse files
committed
feat: enhance CommandBar with search functionality and keyboard shortcuts
1 parent e97b69a commit 69d74b4

File tree

2 files changed

+92
-48
lines changed

2 files changed

+92
-48
lines changed

src/Pages/Shared/CommandBar/CommandBar.component.tsx

Lines changed: 86 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useState } from 'react'
1+
import { useEffect, useRef, useState } from 'react'
22

33
import {
44
API_STATUS_CODES,
@@ -7,8 +7,10 @@ import {
77
KeyboardShortcut,
88
ResponseType,
99
SearchBar,
10+
stopPropagation,
1011
useQuery,
1112
useRegisterShortcut,
13+
UseRegisterShortcutProvider,
1214
} from '@devtron-labs/devtron-fe-common-lib'
1315

1416
import CommandGroup from './CommandGroup'
@@ -19,6 +21,7 @@ import './CommandBar.scss'
1921

2022
const CommandBar = () => {
2123
const [showCommandBar, setShowCommandBar] = useState(false)
24+
const [searchText, setSearchText] = useState('')
2225

2326
const { data: recentActionsGroup, isLoading } = useQuery({
2427
queryFn: ({ signal }) =>
@@ -48,25 +51,54 @@ const CommandBar = () => {
4851
}, structuredClone(RECENT_ACTIONS_GROUP)),
4952
})
5053

54+
const searchBarRef = useRef<HTMLInputElement>(null)
55+
5156
const { registerShortcut, unregisterShortcut } = useRegisterShortcut()
5257

58+
const handleSearchChange = (value: string) => {
59+
setSearchText(value)
60+
}
61+
5362
const handleClose = () => {
63+
setSearchText('')
5464
setShowCommandBar(false)
5565
}
5666

67+
const handleEscape = () => {
68+
if (searchText) {
69+
handleSearchChange('')
70+
return
71+
}
72+
73+
handleClose()
74+
}
75+
5776
const handleOpen = () => {
5877
setShowCommandBar(true)
5978
}
6079

80+
const focusSearchBar = () => {
81+
if (searchBarRef.current) {
82+
searchBarRef.current.focus()
83+
}
84+
}
85+
6186
useEffect(() => {
6287
registerShortcut({
6388
keys: SHORT_CUTS.OPEN_COMMAND_BAR.keys,
64-
description: 'Open Command Bar',
89+
description: SHORT_CUTS.OPEN_COMMAND_BAR.description,
6590
callback: handleOpen,
6691
})
6792

93+
registerShortcut({
94+
keys: SHORT_CUTS.FOCUS_SEARCH_BAR.keys,
95+
description: SHORT_CUTS.FOCUS_SEARCH_BAR.description,
96+
callback: focusSearchBar,
97+
})
98+
6899
return () => {
69100
unregisterShortcut(SHORT_CUTS.OPEN_COMMAND_BAR.keys)
101+
unregisterShortcut(SHORT_CUTS.FOCUS_SEARCH_BAR.keys)
70102
}
71103
}, [])
72104

@@ -75,59 +107,67 @@ const CommandBar = () => {
75107
}
76108

77109
return (
78-
<Backdrop onEscape={handleClose}>
79-
<div className="dc__mxw-800 mxh-450 flexbox-col dc__overflow-auto dc__content-space br-12 bg__modal--primary command-bar__container w-100 h-100">
80-
<div className="flexbox-col dc__overflow-auto">
81-
<div className="px-20 py-8">
82-
<SearchBar
83-
inputProps={{
84-
autoFocus: true,
85-
placeholder: 'Search or jump to…',
86-
}}
87-
noBackgroundAndBorder
88-
/>
89-
</div>
90-
91-
<div
92-
className="flexbox-col dc__overflow-auto border__primary--top pt-8"
93-
role="listbox"
94-
aria-label="Command Menu"
95-
// TODO: aria-activedescendant for the currently focused item
96-
>
97-
<CommandGroup isLoading={isLoading} {...(recentActionsGroup || RECENT_ACTIONS_GROUP)} />
98-
99-
{NAVIGATION_GROUPS.map((group) => (
100-
<CommandGroup key={group.id} {...group} />
101-
))}
102-
</div>
103-
</div>
110+
<UseRegisterShortcutProvider ignoreTags={[]}>
111+
<Backdrop onEscape={handleEscape} onClick={handleClose} deactivateFocusOnEscape={!!searchText}>
112+
<div
113+
onClick={stopPropagation}
114+
className="dc__mxw-800 mxh-450 flexbox-col dc__overflow-auto dc__content-space br-12 bg__modal--primary command-bar__container w-100 h-100"
115+
>
116+
<div className="flexbox-col dc__overflow-auto">
117+
<div className="px-20 py-8">
118+
<SearchBar
119+
inputProps={{
120+
autoFocus: true,
121+
placeholder: 'Search or jump to…',
122+
ref: searchBarRef,
123+
}}
124+
initialSearchText={searchText}
125+
handleSearchChange={handleSearchChange}
126+
noBackgroundAndBorder
127+
/>
128+
</div>
104129

105-
<div className="flexbox dc__content-space dc__align-items-center px-20 py-12 border__primary--top bg__secondary">
106-
<div className="flexbox dc__gap-20 dc__align-items-center">
107-
<div className="flexbox dc__gap-8 dc__align-items-center">
108-
<KeyboardShortcut keyboardKey="ArrowUp" />
109-
<KeyboardShortcut keyboardKey="ArrowDown" />
110-
<span className="cn-9 fs-12 fw-4 lh-20">to navigate</span>
130+
<div
131+
className="flexbox-col dc__overflow-auto border__primary--top pt-8"
132+
role="listbox"
133+
aria-label="Command Menu"
134+
// TODO: aria-activedescendant for the currently focused item
135+
>
136+
<CommandGroup isLoading={isLoading} {...(recentActionsGroup || RECENT_ACTIONS_GROUP)} />
137+
138+
{NAVIGATION_GROUPS.map((group) => (
139+
<CommandGroup key={group.id} {...group} />
140+
))}
111141
</div>
142+
</div>
112143

113-
<div className="flexbox dc__gap-8 dc__align-items-center">
114-
<KeyboardShortcut keyboardKey="Enter" />
115-
<span className="cn-9 fs-12 fw-4 lh-20">to select</span>
144+
<div className="flexbox dc__content-space dc__align-items-center px-20 py-12 border__primary--top bg__secondary">
145+
<div className="flexbox dc__gap-20 dc__align-items-center">
146+
<div className="flexbox dc__gap-8 dc__align-items-center">
147+
<KeyboardShortcut keyboardKey="ArrowUp" />
148+
<KeyboardShortcut keyboardKey="ArrowDown" />
149+
<span className="cn-9 fs-12 fw-4 lh-20">to navigate</span>
150+
</div>
151+
152+
<div className="flexbox dc__gap-8 dc__align-items-center">
153+
<KeyboardShortcut keyboardKey="Enter" />
154+
<span className="cn-9 fs-12 fw-4 lh-20">to select</span>
155+
</div>
156+
157+
<div className="flexbox dc__gap-8 dc__align-items-center">
158+
<KeyboardShortcut keyboardKey="Escape" />
159+
<span className="cn-9 fs-12 fw-4 lh-20">to close</span>
160+
</div>
116161
</div>
117162

118163
<div className="flexbox dc__gap-8 dc__align-items-center">
119-
<KeyboardShortcut keyboardKey="Escape" />
120-
<span className="cn-9 fs-12 fw-4 lh-20">to close</span>
164+
<KeyboardShortcut keyboardKey=">" />
165+
<span className="cn-9 fs-12 fw-4 lh-20">to search actions</span>
121166
</div>
122167
</div>
123-
124-
<div className="flexbox dc__gap-8 dc__align-items-center">
125-
<KeyboardShortcut keyboardKey=">" />
126-
<span className="cn-9 fs-12 fw-4 lh-20">to search actions</span>
127-
</div>
128168
</div>
129-
</div>
130-
</Backdrop>
169+
</Backdrop>
170+
</UseRegisterShortcutProvider>
131171
)
132172
}
133173

src/Pages/Shared/CommandBar/constants.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ export const NAVIGATION_GROUPS: CommandBarGroupType[] = NAVIGATION_LIST.map((gro
413413
items: group.items.flatMap(({ hasSubMenu, subItems, title, href, id, icon }) => {
414414
if (hasSubMenu && subItems) {
415415
return subItems.map((subItem) => ({
416-
title: `${group.title} / ${subItem.title}`,
416+
title: `${title} / ${subItem.title}`,
417417
id,
418418
dataTestId: subItem.dataTestId,
419419
// Since icon is not present for some subItems, using from group
@@ -440,7 +440,7 @@ export const RECENT_ACTIONS_GROUP: CommandBarGroupType = {
440440
}
441441

442442
export const SHORT_CUTS: Record<
443-
'OPEN_COMMAND_BAR',
443+
'OPEN_COMMAND_BAR' | 'FOCUS_SEARCH_BAR',
444444
{
445445
keys: SupportedKeyboardKeysType[]
446446
description: string
@@ -450,4 +450,8 @@ export const SHORT_CUTS: Record<
450450
keys: ['Meta', 'K'],
451451
description: 'Open Command Bar',
452452
},
453+
FOCUS_SEARCH_BAR: {
454+
keys: ['Shift', '>'],
455+
description: 'Focus Search Bar',
456+
},
453457
} as const

0 commit comments

Comments
 (0)