Component selection shortcut (#1139)

This PR introduces a new keyboard shortcut to improve the efficiency of
selecting components in the app. Users can now quickly select components
using Meta + Shift + C for Mac and Ctrl + Shift + C for Other devices
(Windows/Linux)

    
<!-- This is an auto-generated description by cubic. -->
---

## Summary by cubic
Add a shortcut to quickly activate the component selector from the
preview. Use Meta+Shift+C on macOS and Ctrl+Shift+C on Windows/Linux.

- **New Features**
- Added useShortcut hook to handle key combos and prevent default on
match.
  - Wired shortcut in PreviewIframe with OS detection for Meta vs Ctrl.
- Forwarded keydown events from the iframe to the parent via postMessage
(dyad-shortcut-triggered) so the shortcut works inside preview content.

<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Mohamed Aziz Mejri
2025-09-06 07:33:44 +01:00
committed by GitHub
parent 207f3fc397
commit 6ee1a93187
3 changed files with 125 additions and 0 deletions

80
src/hooks/useShortcut.ts Normal file
View File

@@ -0,0 +1,80 @@
import { useEffect } from "react";
export function useShortcut(
key: string,
modifiers: { ctrl?: boolean; shift?: boolean; meta?: boolean },
callback: () => void,
isComponentSelectorInitialized: boolean,
iframeRef?: React.RefObject<HTMLIFrameElement | null>,
): void {
useEffect(() => {
const isModifierActive = (
modKey: boolean | undefined,
eventKey: boolean,
) => (modKey ? eventKey : true);
const validateShortcut = (
eventKey: string,
eventModifiers: { ctrl?: boolean; shift?: boolean; meta?: boolean },
) => {
const keyMatches = eventKey === key.toLowerCase();
const ctrlMatches = isModifierActive(
modifiers.ctrl,
eventModifiers.ctrl || false,
);
const shiftMatches = isModifierActive(
modifiers.shift,
eventModifiers.shift || false,
);
const metaMatches = isModifierActive(
modifiers.meta,
eventModifiers.meta || false,
);
if (
keyMatches &&
ctrlMatches &&
shiftMatches &&
metaMatches &&
isComponentSelectorInitialized
) {
callback();
return true;
}
return false;
};
const handleKeyDown = (event: KeyboardEvent) => {
if (
validateShortcut(event.key.toLowerCase(), {
ctrl: event.ctrlKey,
shift: event.shiftKey,
meta: event.metaKey,
})
) {
event.preventDefault();
}
};
const handleMessageEvent = (event: MessageEvent) => {
// Only handle messages from our iframe
if (event.source !== iframeRef?.current?.contentWindow) {
return;
}
if (event.data?.type === "dyad-select-component-shortcut") {
if (isComponentSelectorInitialized) {
callback();
}
}
};
window.addEventListener("keydown", handleKeyDown);
window.addEventListener("message", handleMessageEvent);
return () => {
window.removeEventListener("keydown", handleKeyDown);
window.removeEventListener("message", handleMessageEvent);
};
}, [key, modifiers, callback, isComponentSelectorInitialized, iframeRef]);
}