@@ -2,6 +2,8 @@ import { Play, Settings } from 'lucide-react';
22import { Button } from '@/components/ui/button' ;
33import { Tooltip , TooltipContent , TooltipTrigger } from '@/components/ui/tooltip' ;
44import { useGraphStore } from '@/features/graph/state/use-graph-store' ;
5+ import { useEffect , useRef } from 'react' ;
6+ import { isMacOs } from '@/lib/utils' ;
57
68interface ToolbarProps {
79 onSubmit : ( ) => void ;
@@ -17,6 +19,7 @@ export function Toolbar({
1719 setShowPlayground,
1820} : ToolbarProps ) {
1921 const dirty = useGraphStore ( ( state ) => state . dirty ) ;
22+ const saveButtonRef = useRef < HTMLButtonElement > ( null ! ) ;
2023 const PreviewButton = (
2124 < Button
2225 disabled = { dirty || isPreviewDisabled }
@@ -29,6 +32,21 @@ export function Toolbar({
2932 </ Button >
3033 ) ;
3134
35+ useEffect ( ( ) => {
36+ function handleSaveShortcut ( event : KeyboardEvent ) {
37+ const isShortcutPressed = ( isMacOs ( ) ? event . metaKey : event . ctrlKey ) && event . key === 's' ;
38+ if ( ! isShortcutPressed ) return ;
39+ event . preventDefault ( ) ;
40+ // Using button ref instead onSubmit to respect button's disabled state
41+ saveButtonRef . current . click ( ) ;
42+ }
43+
44+ window . addEventListener ( 'keydown' , handleSaveShortcut ) ;
45+ return ( ) => {
46+ window . removeEventListener ( 'keydown' , handleSaveShortcut ) ;
47+ } ;
48+ } , [ ] ) ;
49+
3250 return (
3351 < div className = "flex gap-2" >
3452 { dirty || isPreviewDisabled ? (
@@ -49,6 +67,7 @@ export function Toolbar({
4967 onClick = { onSubmit }
5068 variant = { dirty ? 'default' : 'outline' }
5169 disabled = { ! dirty && ! isPreviewDisabled }
70+ ref = { saveButtonRef }
5271 >
5372 { isPreviewDisabled ? 'Save' : 'Save changes' }
5473 </ Button >
0 commit comments