Skip to content

Commit 767f565

Browse files
committed
feat: refactor CommandBar and CommandBarBackdrop for improved keyboard shortcut handling and navigation
1 parent a272adc commit 767f565

File tree

4 files changed

+57
-42
lines changed

4 files changed

+57
-42
lines changed

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,16 @@ const CommandBar = () => {
2020
}
2121

2222
useEffect(() => {
23+
const { keys } = SHORT_CUTS.OPEN_COMMAND_BAR
24+
2325
registerShortcut({
24-
keys: SHORT_CUTS.OPEN_COMMAND_BAR.keys,
26+
keys,
2527
description: SHORT_CUTS.OPEN_COMMAND_BAR.description,
2628
callback: handleOpen,
2729
})
2830

2931
return () => {
30-
unregisterShortcut(SHORT_CUTS.OPEN_COMMAND_BAR.keys)
32+
unregisterShortcut(keys)
3133
}
3234
}, [])
3335

src/Pages/Shared/CommandBar/CommandBarBackdrop.tsx

Lines changed: 33 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {
1212
SearchBar,
1313
stopPropagation,
1414
SupportedKeyboardKeysType,
15+
ToastManager,
16+
ToastVariantType,
1517
updateUserPreferences,
1618
useQuery,
1719
useRegisterShortcut,
@@ -20,7 +22,8 @@ import {
2022

2123
import CommandGroup from './CommandGroup'
2224
import { NAVIGATION_GROUPS, RECENT_ACTIONS_GROUP, RECENT_NAVIGATION_ITEM_ID_PREFIX, SHORT_CUTS } from './constants'
23-
import { CommandBarActionIdType, CommandBarBackdropProps, CommandBarGroupType, CommandBarItemType } from './types'
25+
import { CommandBarBackdropProps, CommandBarGroupType } from './types'
26+
import { getNewSelectedIndex, sanitizeItemId } from './utils'
2427

2528
const CommandBarBackdrop = ({ handleClose }: CommandBarBackdropProps) => {
2629
const history = useHistory()
@@ -49,10 +52,7 @@ const CommandBarBackdrop = ({ handleClose }: CommandBarBackdropProps) => {
4952
if (requiredGroup) {
5053
const requiredItem = requiredGroup.items.find((item) => item.id === action.id)
5154
requiredItem.id = `${RECENT_NAVIGATION_ITEM_ID_PREFIX}${action.id}`
52-
53-
if (requiredItem) {
54-
acc.items.push(structuredClone(requiredItem))
55-
}
55+
acc.items.push(structuredClone(requiredItem))
5656
}
5757
return acc
5858
}, structuredClone(RECENT_ACTIONS_GROUP)),
@@ -74,22 +74,26 @@ const CommandBarBackdrop = ({ handleClose }: CommandBarBackdropProps) => {
7474
itemRefMap.current[id] = el
7575
}
7676

77-
const lowerCaseSearchText = searchText.toLowerCase()
77+
const filteredGroups = useMemo(() => {
78+
const lowerCaseSearchText = searchText.toLowerCase()
7879

79-
const filteredGroups = searchText
80-
? NAVIGATION_GROUPS.reduce<typeof NAVIGATION_GROUPS>((acc, group) => {
81-
const filteredItems = group.items.filter((item) => item.title.toLowerCase().includes(lowerCaseSearchText))
80+
if (!searchText) {
81+
return NAVIGATION_GROUPS
82+
}
8283

83-
if (filteredItems.length > 0) {
84-
acc.push({
85-
...group,
86-
items: filteredItems,
87-
})
88-
}
84+
return NAVIGATION_GROUPS.reduce<typeof NAVIGATION_GROUPS>((acc, group) => {
85+
const filteredItems = group.items.filter((item) => item.title.toLowerCase().includes(lowerCaseSearchText))
8986

90-
return acc
91-
}, [])
92-
: NAVIGATION_GROUPS
87+
if (filteredItems.length > 0) {
88+
acc.push({
89+
...group,
90+
items: filteredItems,
91+
})
92+
}
93+
94+
return acc
95+
}, [])
96+
}, [searchText])
9397

9498
const itemFlatList: CommandBarGroupType['items'] = useMemo(() => {
9599
if (areFiltersApplied) {
@@ -120,21 +124,13 @@ const CommandBarBackdrop = ({ handleClose }: CommandBarBackdropProps) => {
120124
}
121125
}
122126

123-
const getNewSelectedIndex = (prevIndex: number, type: 'up' | 'down') => {
124-
if (type === 'up') {
125-
return prevIndex === 0 ? itemFlatList.length - 1 : prevIndex - 1
126-
}
127-
return prevIndex === itemFlatList.length - 1 ? 0 : prevIndex + 1
128-
}
129-
130127
const handleNavigation = (type: 'up' | 'down') => {
131128
if (!itemFlatList.length) {
132129
return
133130
}
134131

135-
// Want this to have cyclic navigation
136132
setSelectedItemIndex((prevIndex) => {
137-
const newIndex = getNewSelectedIndex(prevIndex, type)
133+
const newIndex = getNewSelectedIndex(prevIndex, type, itemFlatList.length)
138134
const item = itemFlatList[newIndex]
139135
const itemElement = itemRefMap.current[item.id]
140136
if (itemElement) {
@@ -144,14 +140,13 @@ const CommandBarBackdrop = ({ handleClose }: CommandBarBackdropProps) => {
144140
})
145141
}
146142

147-
const sanitizeItemId = (item: CommandBarItemType) =>
148-
(item.id.startsWith(RECENT_NAVIGATION_ITEM_ID_PREFIX)
149-
? item.id.replace(RECENT_NAVIGATION_ITEM_ID_PREFIX, '')
150-
: item.id) as CommandBarActionIdType
151-
152143
const onItemClick = async (item: CommandBarGroupType['items'][number]) => {
153144
if (!item.href) {
154145
logExceptionToSentry(new Error(`CommandBar item with id ${item.id} does not have a valid href`))
146+
ToastManager.showToast({
147+
variant: ToastVariantType.error,
148+
description: `CommandBar item with id ${item.id} does not have a valid href`,
149+
})
155150
return
156151
}
157152

@@ -187,6 +182,7 @@ const CommandBarBackdrop = ({ handleClose }: CommandBarBackdropProps) => {
187182
}
188183
}
189184

185+
// Intention: To retain the selected item index when recent actions are loaded
190186
useEffect(() => {
191187
if (!isLoading && recentActionsGroup?.items?.length && !areFiltersApplied) {
192188
if (selectedItemIndex !== 0) {
@@ -203,11 +199,11 @@ const CommandBarBackdrop = ({ handleClose }: CommandBarBackdropProps) => {
203199
}, [isLoading, recentActionsGroup])
204200

205201
useEffect(() => {
206-
const { keys } = SHORT_CUTS.FOCUS_SEARCH_BAR
202+
const { keys, description } = SHORT_CUTS.FOCUS_SEARCH_BAR
207203

208204
registerShortcut({
209205
keys,
210-
description: SHORT_CUTS.FOCUS_SEARCH_BAR.description,
206+
description,
211207
callback: focusSearchBar,
212208
})
213209

@@ -217,10 +213,11 @@ const CommandBarBackdrop = ({ handleClose }: CommandBarBackdropProps) => {
217213
}, [])
218214

219215
useEffect(() => {
220-
const { keys } = SHORT_CUTS.ENTER_ITEM
216+
const { keys, description } = SHORT_CUTS.ENTER_ITEM
217+
221218
registerShortcut({
222219
keys,
223-
description: SHORT_CUTS.ENTER_ITEM.description,
220+
description,
224221
callback: handleEnterSelectedItem,
225222
})
226223

src/Pages/Shared/CommandBar/CommandGroup.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ const CommandGroup = ({
2222
onItemClick(item)
2323
}
2424

25+
const getIsItemSelected = (itemIndex: number) => selectedItemIndex === baseIndex + itemIndex
26+
2527
const renderContent = () => {
2628
if (isLoading || !items?.length) {
2729
return (
@@ -33,10 +35,10 @@ const CommandGroup = ({
3335

3436
return items.map((item, index) => (
3537
<div
36-
className={`flexbox px-16 py-12 cursor dc__align-items-center dc__gap-12 dc__content-space br-8 bg__hover ${selectedItemIndex === baseIndex + index ? 'command-bar__container--selected-item' : ''}`}
38+
className={`flexbox px-16 py-12 cursor dc__align-items-center dc__gap-12 dc__content-space br-8 bg__hover ${getIsItemSelected(index) ? 'command-bar__container--selected-item' : ''}`}
3739
role="option"
3840
id={item.id}
39-
aria-selected={selectedItemIndex === baseIndex + index}
41+
aria-selected={getIsItemSelected(index)}
4042
ref={updateItemRef(item.id)}
4143
onClick={getHandleItemClick(item)}
4244
tabIndex={0}
@@ -46,7 +48,7 @@ const CommandGroup = ({
4648
<h3 className="m-0 cn-9 fs-14 fw-4 lh-20 dc__truncate">{item.title}</h3>
4749
</div>
4850

49-
{selectedItemIndex === baseIndex + index && <Icon name="ic-key-enter" color="N700" size={20} />}
51+
{getIsItemSelected(index) && <Icon name="ic-key-enter" color="N700" size={20} />}
5052
</div>
5153
))
5254
}
@@ -59,7 +61,7 @@ const CommandGroup = ({
5961
</h2>
6062
</div>
6163

62-
<div className="flexbox-col" key={id} role="group" aria-labelledby={id}>
64+
<div className="flexbox-col" role="group" aria-labelledby={id}>
6365
{renderContent()}
6466
</div>
6567
</div>

src/Pages/Shared/CommandBar/utils.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { RECENT_NAVIGATION_ITEM_ID_PREFIX } from './constants'
2+
import { CommandBarActionIdType, CommandBarItemType } from './types'
3+
4+
export const sanitizeItemId = (item: CommandBarItemType) =>
5+
(item.id.startsWith(RECENT_NAVIGATION_ITEM_ID_PREFIX)
6+
? item.id.replace(RECENT_NAVIGATION_ITEM_ID_PREFIX, '')
7+
: item.id) as CommandBarActionIdType
8+
9+
export const getNewSelectedIndex = (prevIndex: number, type: 'up' | 'down', totalItems: number) => {
10+
if (type === 'up') {
11+
return prevIndex === 0 ? totalItems - 1 : prevIndex - 1
12+
}
13+
return prevIndex === totalItems - 1 ? 0 : prevIndex + 1
14+
}

0 commit comments

Comments
 (0)