Building a React Command Palette
This challenge involves creating a modern command palette component in React using TypeScript. Command palettes are a powerful UI pattern found in many applications (like VS Code, GitHub, Notion) that allow users to quickly access actions, search for information, or navigate the application using keyboard shortcuts. Mastering this will enhance your ability to build efficient and user-friendly interfaces.
Problem Description
You are tasked with building a reusable CommandPalette component in React with TypeScript. This component should allow users to input commands, filter a predefined list of actions, and execute a selected action.
Key Requirements:
- Display Actions: The component should display a list of available actions. Each action will have a
label(what the user sees) and anonExecutecallback function. - Search and Filter: Users should be able to type into an input field to filter the displayed actions based on their
label. The filtering should be case-insensitive. - Keyboard Navigation:
- The command palette should be toggleable using a keyboard shortcut (e.g.,
Ctrl+KorCmd+K). - When open, users should be able to navigate the filtered list of actions using the
UpandDownarrow keys. - Pressing
Enteron a highlighted action should execute itsonExecutecallback. - Pressing
Escapeshould close the command palette.
- The command palette should be toggleable using a keyboard shortcut (e.g.,
- Visual Feedback:
- The currently highlighted action in the list should be visually distinct.
- The input field should be focused when the command palette is opened.
- State Management: Manage the open/closed state of the palette, the current search query, and the index of the currently highlighted action.
Expected Behavior:
- On initial load, the command palette is closed.
- Pressing
Ctrl+K(orCmd+K) opens the palette, focuses the input, and displays all available actions. - Typing into the input filters the actions in real-time.
- Arrow keys navigate the filtered list, wrapping around if necessary.
Enterexecutes the selected action and closes the palette.Escapecloses the palette without executing any action.- Clicking outside the command palette (or on a backdrop) should also close it.
Edge Cases:
- What happens when there are no matching actions for a given query? (Display a "No results found" message).
- What happens if the
onExecutecallback throws an error? (For this challenge, you can assume callbacks are well-behaved, but consider how you might handle this in a real application). - Handling multiple commands with similar labels.
Examples
Let's define a simple set of actions:
interface Command {
id: string; // Unique identifier for the command
label: string;
onExecute: () => void;
}
const mockCommands: Command[] = [
{ id: 'open-settings', label: 'Open Settings', onExecute: () => console.log('Opening settings...') },
{ id: 'export-data', label: 'Export Data', onExecute: () => console.log('Exporting data...') },
{ id: 'import-data', label: 'Import Data', onExecute: () => console.log('Importing data...') },
{ id: 'show-help', label: 'Show Help Center', onExecute: () => console.log('Showing help center...') },
{ id: 'new-tab', label: 'New Tab', onExecute: () => console.log('Opening a new tab...') },
{ id: 'close-all-tabs', label: 'Close All Tabs', onExecute: () => console.log('Closing all tabs...') },
];
Example 1: Opening and Filtering
- Input: User presses
Ctrl+K. - Output: Command palette opens, input field is focused, and all
mockCommandsare displayed. - Input: User types "ex" into the input field.
- Output: Only actions with "ex" in their label are displayed: "Export Data" and "Import Data".
Example 2: Navigation and Execution
- Input: Command palette is open, displaying "Export Data" and "Import Data". "Export Data" is highlighted.
- Input: User presses the
Downarrow key. - Output: "Import Data" becomes highlighted.
- Input: User presses
Enter. - Output: The
onExecutefunction for "Import Data" is called, and the command palette closes.
Example 3: No Results
- Input: Command palette is open, user types "xyz" into the input.
- Output: The list of actions is empty, and a message like "No results found" is displayed.
Constraints
- The
CommandPalettecomponent should be a functional component using React hooks. - All code must be written in TypeScript.
- The solution should be performant enough for a reasonable number of commands (e.g., up to 100 commands) without significant lag.
- Do not use external state management libraries (like Redux, Zustand) for this challenge. Rely on React's built-in hooks.
- The keyboard shortcut
Ctrl+K(orCmd+Kon macOS) should be the primary way to open/close the palette.
Notes
- Consider how you will manage focus within the component and ensure accessibility.
- You'll need to handle event listeners for keyboard shortcuts and input changes. Remember to clean up event listeners when the component unmounts.
- Think about the structure of your component. You might want to break it down into smaller, manageable sub-components (e.g., an input area, a list area).
- For the global keyboard shortcut, consider attaching it to the
windowordocumentobject. - When navigating with arrow keys, ensure the highlighted item is always visible in the viewport if the list is scrollable.