Component props.
<HotkeyHelpModal open={open} onClose={() => setOpen(false)} />
export default function HotkeyHelpModal({ open, onClose }: HotkeyHelpModalProps) {
const grouped = useMemo(() => groupByGroup(getHotkeyConfigs()), []);
const groupNames = useMemo(() => Array.from(grouped.keys()), [grouped]);
const [activeGroup, setActiveGroup] = useState<string>(groupNames[0] ?? "");
// Reset to the first tab each time the modal opens — avoids reopening on a
// stale group if the config list changes underneath us.
useEffect(() => {
if (open && groupNames.length > 0) {
setActiveGroup(groupNames[0]);
}
}, [open, groupNames]);
const handleTabChange = (_event: SyntheticEvent, value: string) => {
setActiveGroup(value);
};
const activeEntries = grouped.get(activeGroup) ?? [];
return (
<Modal
data-testid="hotkey-help-modal"
open={open}
onClose={onClose}
aria-labelledby="hotkey-help-title"
sx={{ zIndex: (theme) => theme.zIndex.modal + 10 }}
>
<HotkeyHelpBox
className={styles["hotkey-help-box"]}
onClick={(e) => e.stopPropagation()}
>
<Typography
id="hotkey-help-title"
variant="subtitle1"
component="h2"
className={styles["hotkey-help-title"]}
sx={{ mb: 0.5 }}
>
Keyboard Shortcuts
</Typography>
<Tabs
value={activeGroup}
onChange={handleTabChange}
variant="scrollable"
scrollButtons="auto"
allowScrollButtonsMobile
className={styles["hotkey-tabs"]}
>
{groupNames.map((group) => (
<Tab key={group} label={group} value={group} />
))}
</Tabs>
<Divider />
<div className={styles["hotkey-tab-panel"]}>
{activeEntries.map((entry) => (
<HotkeyRow key={entry.id} className={styles["hotkey-row"]}>
<HotkeyComboGroup className={styles["hotkey-combo-group"]}>
{formatBindingTokens(entry.keys).map((token, i) => (
<HotkeyCombo key={`${entry.id}-${i}`} className={styles["hotkey-combo"]}>
{token}
</HotkeyCombo>
))}
</HotkeyComboGroup>
<Typography variant="body2" className={styles["hotkey-description"]}>
{entry.description}
</Typography>
</HotkeyRow>
))}
</div>
</HotkeyHelpBox>
</Modal>
);
}
Modal that lists every hotkey defined in
config.json, grouped by thegroupfield. Each row shows the formatted key combo (platform-aware) and the human-readable description.Static layout (positioning, sizing, text alignment, font families) lives in the sibling
HotkeyHelpModal.module.scss. Theme-dependent styling (palette, spacing, radius, shadow) lives inHotkeyHelpModal.styled.ts. The modal itself is driven entirely off the config — adding a new entry toconfig.jsonis sufficient to make it appear here with no code change.