핫키 3대 버그 수정: Alt 메뉴 차단 + 조합키 녹화 + aria-hidden
- WindowManager: Menu.setApplicationMenu(null)로 Alt 키 메뉴 완전 제거 - HotkeyRecordModal: 조합키 로직 재설계 — modifier 누른 채로 일반키 입력 시 조합 확정 - HotkeyRecordModal/SettingsModal: disableEnforceFocus로 aria-hidden 에러 방지
This commit is contained in:
parent
5741d55c0b
commit
48582e46ec
3 changed files with 135 additions and 169 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
// src/main/windows/WindowManager.ts
|
// src/main/windows/WindowManager.ts
|
||||||
// 설계서 01 WindowManagerService: 메인 윈도우 + 팝업 프리로딩 + 2-phase 리사이즈
|
// 설계서 01 WindowManagerService: 메인 윈도우 + 팝업 프리로딩 + 2-phase 리사이즈
|
||||||
|
|
||||||
import { BrowserWindow, shell, screen, ipcMain } from 'electron'
|
import { BrowserWindow, shell, screen, ipcMain, Menu } from 'electron'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { is } from '@electron-toolkit/utils'
|
import { is } from '@electron-toolkit/utils'
|
||||||
import { WINDOW_SIZE } from '@shared/constants'
|
import { WINDOW_SIZE } from '@shared/constants'
|
||||||
|
|
@ -40,6 +40,10 @@ export function createMainWindow(): BrowserWindow {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Alt 키로 메뉴 활성화 방지: 메뉴 완전 제거
|
||||||
|
mainWindow.setMenu(null)
|
||||||
|
Menu.setApplicationMenu(null)
|
||||||
|
|
||||||
mainWindow.on('ready-to-show', () => {
|
mainWindow.on('ready-to-show', () => {
|
||||||
mainWindow?.show()
|
mainWindow?.show()
|
||||||
if (is.dev) {
|
if (is.dev) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
// src/renderer/components/HotkeyRecordModal.tsx
|
// src/renderer/components/HotkeyRecordModal.tsx
|
||||||
// Speakly HotkeyRecordModal 패턴: Modal → 키 입력 대기 → Chip 표시 → 저장/취소
|
// Speakly HotkeyRecordModal 패턴: Modal → 키 입력 대기 → Chip 표시 → 저장/취소
|
||||||
|
// 조합키: modifier(Ctrl/Alt/Shift) 누른 상태에서 일반키 입력 → 조합 완성
|
||||||
|
// 단일키: modifier만 눌렀다 놓으면 해당 modifier가 단독 핫키로 등록
|
||||||
|
|
||||||
import { useState, useEffect, useCallback, useRef } from 'react'
|
import { useState, useEffect, useCallback, useRef } from 'react'
|
||||||
import {
|
import {
|
||||||
|
|
@ -16,71 +18,48 @@ import {
|
||||||
import { d3roPalette } from '../theme'
|
import { d3roPalette } from '../theme'
|
||||||
import type { HotkeyBinding } from '@shared/types'
|
import type { HotkeyBinding } from '@shared/types'
|
||||||
|
|
||||||
// ── 키 이름 매핑 (Windows 전용) ──────────────────────────
|
// ── 키 이름 매핑 (Windows) ──────────────────────────────
|
||||||
const KEY_DISPLAY_MAP: Record<number, string> = {
|
const KEY_DISPLAY_MAP: Record<number, string> = {
|
||||||
// Modifier keys
|
// Modifier keys
|
||||||
160: 'Left Shift',
|
16: 'Shift', 17: 'Ctrl', 18: 'Alt', 91: 'Win', 92: 'Win',
|
||||||
161: 'Right Shift',
|
160: 'Left Shift', 161: 'Right Shift',
|
||||||
162: 'Left Ctrl',
|
162: 'Left Ctrl', 163: 'Right Ctrl',
|
||||||
163: 'Right Ctrl',
|
164: 'Left Alt', 165: 'Right Alt',
|
||||||
164: 'Left Alt',
|
|
||||||
165: 'Right Alt',
|
|
||||||
91: 'Left Win',
|
|
||||||
92: 'Right Win',
|
|
||||||
// Common keys
|
// Common keys
|
||||||
8: 'Backspace',
|
8: 'Backspace', 9: 'Tab', 13: 'Enter', 19: 'Pause', 20: 'CapsLock',
|
||||||
9: 'Tab',
|
27: 'Esc', 32: 'Space',
|
||||||
13: 'Enter',
|
33: 'PgUp', 34: 'PgDn', 35: 'End', 36: 'Home',
|
||||||
19: 'Pause',
|
37: '←', 38: '↑', 39: '→', 40: '↓',
|
||||||
20: 'Caps Lock',
|
45: 'Insert', 46: 'Delete',
|
||||||
27: 'Escape',
|
|
||||||
32: 'Space',
|
|
||||||
33: 'Page Up',
|
|
||||||
34: 'Page Down',
|
|
||||||
35: 'End',
|
|
||||||
36: 'Home',
|
|
||||||
37: 'Left',
|
|
||||||
38: 'Up',
|
|
||||||
39: 'Right',
|
|
||||||
40: 'Down',
|
|
||||||
45: 'Insert',
|
|
||||||
46: 'Delete',
|
|
||||||
// F-keys
|
// F-keys
|
||||||
112: 'F1', 113: 'F2', 114: 'F3', 115: 'F4',
|
112: 'F1', 113: 'F2', 114: 'F3', 115: 'F4',
|
||||||
116: 'F5', 117: 'F6', 118: 'F7', 119: 'F8',
|
116: 'F5', 117: 'F6', 118: 'F7', 119: 'F8',
|
||||||
120: 'F9', 121: 'F10', 122: 'F11', 123: 'F12',
|
120: 'F9', 121: 'F10', 122: 'F11', 123: 'F12',
|
||||||
// Number keys
|
// Numpad
|
||||||
48: '0', 49: '1', 50: '2', 51: '3', 52: '4',
|
96: 'Num0', 97: 'Num1', 98: 'Num2', 99: 'Num3', 100: 'Num4',
|
||||||
53: '5', 54: '6', 55: '7', 56: '8', 57: '9',
|
101: 'Num5', 102: 'Num6', 103: 'Num7', 104: 'Num8', 105: 'Num9',
|
||||||
|
106: 'Num*', 107: 'Num+', 109: 'Num-', 110: 'Num.', 111: 'Num/',
|
||||||
|
// Special
|
||||||
|
186: ';', 187: '=', 188: ',', 189: '-', 190: '.', 191: '/', 192: '`',
|
||||||
|
219: '[', 220: '\\', 221: ']', 222: "'",
|
||||||
}
|
}
|
||||||
|
|
||||||
// 시스템 예약 단축키 블랙리스트 (Windows)
|
// 시스템 예약 조합
|
||||||
const RESERVED_COMBOS: Array<{ ctrl: boolean; alt: boolean; shift: boolean; key: string }> = [
|
const RESERVED_COMBOS = [
|
||||||
{ ctrl: true, alt: false, shift: false, key: 'c' }, // Copy
|
'Ctrl+C', 'Ctrl+V', 'Ctrl+X', 'Ctrl+Z', 'Ctrl+A', 'Ctrl+S', 'Ctrl+W',
|
||||||
{ ctrl: true, alt: false, shift: false, key: 'v' }, // Paste
|
'Alt+F4', 'Alt+Tab', 'Ctrl+Alt+Delete',
|
||||||
{ ctrl: true, alt: false, shift: false, key: 'x' }, // Cut
|
|
||||||
{ ctrl: true, alt: false, shift: false, key: 'z' }, // Undo
|
|
||||||
{ ctrl: true, alt: false, shift: false, key: 'a' }, // Select All
|
|
||||||
{ ctrl: true, alt: false, shift: false, key: 's' }, // Save
|
|
||||||
{ ctrl: true, alt: false, shift: false, key: 'w' }, // Close tab
|
|
||||||
{ ctrl: false, alt: true, shift: false, key: 'F4' }, // Close window
|
|
||||||
{ ctrl: false, alt: false, shift: false, key: 'F5' }, // Refresh
|
|
||||||
]
|
]
|
||||||
|
|
||||||
function getKeyDisplayName(keyCode: number, key: string): string {
|
const MODIFIER_KEYCODES = new Set([16, 17, 18, 91, 92, 160, 161, 162, 163, 164, 165])
|
||||||
|
|
||||||
|
function getKeyName(keyCode: number, key: string): string {
|
||||||
if (KEY_DISPLAY_MAP[keyCode]) return KEY_DISPLAY_MAP[keyCode]
|
if (KEY_DISPLAY_MAP[keyCode]) return KEY_DISPLAY_MAP[keyCode]
|
||||||
if (key.length === 1) return key.toUpperCase()
|
if (key.length === 1) return key.toUpperCase()
|
||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
function isModifierKey(keyCode: number): boolean {
|
function isModifier(keyCode: number): boolean {
|
||||||
return (keyCode >= 160 && keyCode <= 165) || keyCode === 91 || keyCode === 92
|
return MODIFIER_KEYCODES.has(keyCode)
|
||||||
}
|
|
||||||
|
|
||||||
function isReservedCombo(ctrl: boolean, alt: boolean, shift: boolean, key: string): boolean {
|
|
||||||
return RESERVED_COMBOS.some(
|
|
||||||
(c) => c.ctrl === ctrl && c.alt === alt && c.shift === shift && c.key === key
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HotkeyRecordModalProps {
|
interface HotkeyRecordModalProps {
|
||||||
|
|
@ -91,12 +70,6 @@ interface HotkeyRecordModalProps {
|
||||||
title?: string
|
title?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RecordedKey {
|
|
||||||
keyCode: number
|
|
||||||
displayName: string
|
|
||||||
isModifier: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export function HotkeyRecordModal({
|
export function HotkeyRecordModal({
|
||||||
open,
|
open,
|
||||||
onClose,
|
onClose,
|
||||||
|
|
@ -104,67 +77,57 @@ export function HotkeyRecordModal({
|
||||||
currentBinding,
|
currentBinding,
|
||||||
title = '단축키 설정',
|
title = '단축키 설정',
|
||||||
}: HotkeyRecordModalProps): React.ReactElement {
|
}: HotkeyRecordModalProps): React.ReactElement {
|
||||||
const [recordedKeys, setRecordedKeys] = useState<RecordedKey[]>([])
|
// 현재 눌려있는 키들을 실시간 추적
|
||||||
const [isRecording, setIsRecording] = useState(false)
|
const [pressedKeys, setPressedKeys] = useState<Array<{ keyCode: number; name: string }>>([])
|
||||||
const [validationError, setValidationError] = useState<string | null>(null)
|
// 확정된 조합 (녹화 완료 후)
|
||||||
const keysRef = useRef<Set<number>>(new Set())
|
const [captured, setCaptured] = useState<Array<{ keyCode: number; name: string }> | null>(null)
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
const pressedRef = useRef<Map<number, string>>(new Map())
|
||||||
|
|
||||||
// Modal 열릴 때 녹화 시작
|
// Modal 열릴 때 리셋
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open) {
|
if (open) {
|
||||||
setRecordedKeys([])
|
setPressedKeys([])
|
||||||
setValidationError(null)
|
setCaptured(null)
|
||||||
setIsRecording(true)
|
setError(null)
|
||||||
keysRef.current.clear()
|
pressedRef.current.clear()
|
||||||
} else {
|
|
||||||
setIsRecording(false)
|
|
||||||
}
|
}
|
||||||
}, [open])
|
}, [open])
|
||||||
|
|
||||||
const handleKeyDown = useCallback(
|
const handleKeyDown = useCallback((e: KeyboardEvent) => {
|
||||||
(e: KeyboardEvent) => {
|
if (captured) return // 이미 확정됨
|
||||||
if (!isRecording) return
|
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|
||||||
const keyCode = e.keyCode || e.which
|
const keyCode = e.keyCode || e.which
|
||||||
if (keysRef.current.has(keyCode)) return
|
if (pressedRef.current.has(keyCode)) return
|
||||||
keysRef.current.add(keyCode)
|
|
||||||
|
|
||||||
const displayName = getKeyDisplayName(keyCode, e.key)
|
const name = getKeyName(keyCode, e.key)
|
||||||
const isMod = isModifierKey(keyCode)
|
pressedRef.current.set(keyCode, name)
|
||||||
|
|
||||||
setRecordedKeys((prev) => {
|
const keys = Array.from(pressedRef.current.entries()).map(([kc, n]) => ({ keyCode: kc, name: n }))
|
||||||
const next = [...prev, { keyCode, displayName, isModifier: isMod }]
|
setPressedKeys(keys)
|
||||||
// modifier가 아닌 키가 들어오면 녹화 완료 (modifier + key 조합)
|
|
||||||
if (!isMod) {
|
// modifier가 아닌 키가 눌리면 → 조합 확정
|
||||||
setIsRecording(false)
|
if (!isModifier(keyCode)) {
|
||||||
|
setCaptured(keys)
|
||||||
}
|
}
|
||||||
return next
|
}, [captured])
|
||||||
})
|
|
||||||
setValidationError(null)
|
const handleKeyUp = useCallback((e: KeyboardEvent) => {
|
||||||
},
|
if (captured) return // 이미 확정됨
|
||||||
[isRecording]
|
|
||||||
)
|
|
||||||
|
|
||||||
const handleKeyUp = useCallback(
|
|
||||||
(e: KeyboardEvent) => {
|
|
||||||
const keyCode = e.keyCode || e.which
|
const keyCode = e.keyCode || e.which
|
||||||
keysRef.current.delete(keyCode)
|
pressedRef.current.delete(keyCode)
|
||||||
|
|
||||||
// modifier만 눌렀다 놓은 경우: 단일 modifier 키 등록
|
// 모든 키를 놓았고 modifier만 눌렀었다면 → 단일 modifier 확정
|
||||||
if (isRecording && isModifierKey(keyCode) && keysRef.current.size === 0) {
|
if (pressedRef.current.size === 0 && pressedKeys.length > 0 && pressedKeys.every(k => isModifier(k.keyCode))) {
|
||||||
setRecordedKeys((prev) => {
|
setCaptured(pressedKeys)
|
||||||
if (prev.length > 0 && prev.every((k) => k.isModifier)) {
|
|
||||||
setIsRecording(false)
|
|
||||||
return prev
|
|
||||||
}
|
}
|
||||||
return prev
|
|
||||||
})
|
const keys = Array.from(pressedRef.current.entries()).map(([kc, n]) => ({ keyCode: kc, name: n }))
|
||||||
}
|
setPressedKeys(keys)
|
||||||
},
|
}, [captured, pressedKeys])
|
||||||
[isRecording]
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open) return
|
if (!open) return
|
||||||
|
|
@ -176,79 +139,73 @@ export function HotkeyRecordModal({
|
||||||
}
|
}
|
||||||
}, [open, handleKeyDown, handleKeyUp])
|
}, [open, handleKeyDown, handleKeyUp])
|
||||||
|
|
||||||
|
const displayKeys = captured ?? pressedKeys
|
||||||
|
const isReady = captured !== null && captured.length > 0
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
if (recordedKeys.length === 0) {
|
if (!captured || captured.length === 0) {
|
||||||
setValidationError('키를 입력해주세요')
|
setError('키를 입력해주세요')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasModifier = recordedKeys.some((k) => k.isModifier)
|
// 예약 단축키 체크
|
||||||
const mainKey = recordedKeys.find((k) => !k.isModifier)
|
const label = captured.map(k => k.name).join('+')
|
||||||
|
if (RESERVED_COMBOS.includes(label)) {
|
||||||
// 시스템 예약 단축키 체크
|
setError(`${label}은 시스템 예약 단축키입니다`)
|
||||||
if (mainKey) {
|
handleReset()
|
||||||
const ctrl = recordedKeys.some((k) => k.keyCode === 162 || k.keyCode === 163)
|
|
||||||
const alt = recordedKeys.some((k) => k.keyCode === 164 || k.keyCode === 165)
|
|
||||||
const shift = recordedKeys.some((k) => k.keyCode === 160 || k.keyCode === 161)
|
|
||||||
|
|
||||||
if (isReservedCombo(ctrl, alt, shift, mainKey.displayName)) {
|
|
||||||
setValidationError('시스템 예약 단축키입니다')
|
|
||||||
resetRecording()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// HotkeyBinding 생성
|
// HotkeyBinding 생성
|
||||||
const displayLabel = recordedKeys.map((k) => k.displayName).join(' + ')
|
const mainKey = captured.find(k => !isModifier(k.keyCode))
|
||||||
const primaryKeyCode = mainKey?.keyCode ?? recordedKeys[0].keyCode
|
const primaryKeyCode = mainKey?.keyCode ?? captured[0].keyCode
|
||||||
|
|
||||||
const binding: HotkeyBinding = {
|
const binding: HotkeyBinding = {
|
||||||
keyCode: primaryKeyCode,
|
keyCode: primaryKeyCode,
|
||||||
ctrl: recordedKeys.some((k) => k.keyCode === 162 || k.keyCode === 163),
|
ctrl: captured.some(k => k.keyCode === 17 || k.keyCode === 162 || k.keyCode === 163),
|
||||||
alt: recordedKeys.some((k) => k.keyCode === 164 || k.keyCode === 165),
|
alt: captured.some(k => k.keyCode === 18 || k.keyCode === 164 || k.keyCode === 165),
|
||||||
shift: recordedKeys.some((k) => k.keyCode === 160 || k.keyCode === 161),
|
shift: captured.some(k => k.keyCode === 16 || k.keyCode === 160 || k.keyCode === 161),
|
||||||
meta: recordedKeys.some((k) => k.keyCode === 91 || k.keyCode === 92),
|
meta: captured.some(k => k.keyCode === 91 || k.keyCode === 92),
|
||||||
displayLabel,
|
displayLabel: label,
|
||||||
}
|
|
||||||
|
|
||||||
// 단일 modifier만 입력한 경우 (예: Right Alt만)
|
|
||||||
if (!mainKey && hasModifier && recordedKeys.length === 1) {
|
|
||||||
const modKey = recordedKeys[0]
|
|
||||||
binding.keyCode = modKey.keyCode
|
|
||||||
binding.ctrl = modKey.keyCode === 162 || modKey.keyCode === 163
|
|
||||||
binding.alt = modKey.keyCode === 164 || modKey.keyCode === 165
|
|
||||||
binding.shift = modKey.keyCode === 160 || modKey.keyCode === 161
|
|
||||||
binding.meta = modKey.keyCode === 91 || modKey.keyCode === 92
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onSave(binding)
|
onSave(binding)
|
||||||
onClose()
|
onClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
const resetRecording = () => {
|
const handleReset = () => {
|
||||||
setRecordedKeys([])
|
setPressedKeys([])
|
||||||
setIsRecording(true)
|
setCaptured(null)
|
||||||
keysRef.current.clear()
|
pressedRef.current.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
setRecordedKeys([])
|
handleReset()
|
||||||
setIsRecording(false)
|
|
||||||
keysRef.current.clear()
|
|
||||||
onClose()
|
onClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onClose={handleCancel} maxWidth="xs" fullWidth>
|
<Dialog
|
||||||
|
open={open}
|
||||||
|
onClose={handleCancel}
|
||||||
|
maxWidth="xs"
|
||||||
|
fullWidth
|
||||||
|
// aria-hidden 에러 방지: disableEnforceFocus + disableAutoFocus
|
||||||
|
disableEnforceFocus
|
||||||
|
disableAutoFocus
|
||||||
|
disableRestoreFocus
|
||||||
|
>
|
||||||
<DialogTitle sx={{ fontWeight: 700, fontSize: '16px' }}>{title}</DialogTitle>
|
<DialogTitle sx={{ fontWeight: 700, fontSize: '16px' }}>{title}</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
{/* 녹화 영역 */}
|
{/* 녹화 영역 */}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
border: `2px solid ${
|
border: `2px solid ${
|
||||||
validationError
|
error
|
||||||
? d3roPalette.tag.red
|
? d3roPalette.tag.red
|
||||||
: isRecording
|
: isReady
|
||||||
|
? d3roPalette.tag.green
|
||||||
|
: displayKeys.length > 0
|
||||||
? d3roPalette.accent.amber
|
? d3roPalette.accent.amber
|
||||||
: d3roPalette.border.strong
|
: d3roPalette.border.strong
|
||||||
}`,
|
}`,
|
||||||
|
|
@ -262,20 +219,21 @@ export function HotkeyRecordModal({
|
||||||
transition: 'border-color 0.2s ease',
|
transition: 'border-color 0.2s ease',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{recordedKeys.length > 0 ? (
|
{displayKeys.length > 0 ? (
|
||||||
<Stack direction="row" spacing={1} flexWrap="wrap" justifyContent="center">
|
<Stack direction="row" spacing={1} flexWrap="wrap" justifyContent="center">
|
||||||
{recordedKeys.map((key, i) => (
|
{displayKeys.map((key, i) => (
|
||||||
<Chip
|
<Chip
|
||||||
key={`${key.keyCode}-${i}`}
|
key={`${key.keyCode}-${i}`}
|
||||||
label={key.displayName}
|
label={key.name}
|
||||||
sx={{
|
sx={{
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
fontSize: '13px',
|
fontSize: '14px',
|
||||||
bgcolor: d3roPalette.bg.chassis,
|
bgcolor: d3roPalette.bg.chassis,
|
||||||
color: d3roPalette.text.primary,
|
color: d3roPalette.text.primary,
|
||||||
border: `1px solid ${d3roPalette.border.default}`,
|
border: `1px solid ${d3roPalette.border.default}`,
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
height: 36,
|
height: 40,
|
||||||
|
px: 1,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
@ -283,48 +241,52 @@ export function HotkeyRecordModal({
|
||||||
) : (
|
) : (
|
||||||
<Typography
|
<Typography
|
||||||
sx={{
|
sx={{
|
||||||
color: isRecording ? d3roPalette.accent.amber : d3roPalette.text.inactive,
|
color: d3roPalette.accent.amber,
|
||||||
fontSize: '13px',
|
fontSize: '13px',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isRecording ? '키 조합을 눌러주세요...' : '키를 입력하세요'}
|
키 조합을 눌러주세요...
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* 검증 오류 */}
|
{/* 상태 표시 */}
|
||||||
{validationError && (
|
{isReady && !error && (
|
||||||
<Typography sx={{ color: d3roPalette.tag.red, fontSize: '12px', mt: 1 }}>
|
<Typography sx={{ color: d3roPalette.tag.green, fontSize: '12px', mt: 1, fontWeight: 600 }}>
|
||||||
{validationError}
|
✓ {captured?.map(k => k.name).join(' + ')} — 저장을 눌러주세요
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<Typography sx={{ color: d3roPalette.tag.red, fontSize: '12px', mt: 1 }}>
|
||||||
|
{error}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 현재 설정 표시 */}
|
|
||||||
{currentBinding && (
|
{currentBinding && (
|
||||||
<Typography sx={{ color: d3roPalette.text.inactive, fontSize: '12px', mt: 2 }}>
|
<Typography sx={{ color: d3roPalette.text.inactive, fontSize: '12px', mt: 2 }}>
|
||||||
현재: {currentBinding.displayLabel}
|
현재: {currentBinding.displayLabel}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 안내 */}
|
|
||||||
<Typography sx={{ color: d3roPalette.text.muted, fontSize: '11px', mt: 1 }}>
|
<Typography sx={{ color: d3roPalette.text.muted, fontSize: '11px', mt: 1 }}>
|
||||||
단일 키(예: Right Alt) 또는 조합키(예: Ctrl+Shift+Q)를 입력하세요
|
조합키(예: Ctrl+Shift+Q) 또는 단일키(예: F5)를 입력하세요
|
||||||
</Typography>
|
</Typography>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions sx={{ px: 3, pb: 2 }}>
|
<DialogActions sx={{ px: 3, pb: 2 }}>
|
||||||
<Button onClick={handleCancel} sx={{ color: d3roPalette.text.inactive }}>
|
<Button onClick={handleCancel} sx={{ color: d3roPalette.text.inactive }}>
|
||||||
취소
|
취소
|
||||||
</Button>
|
</Button>
|
||||||
{!isRecording && recordedKeys.length > 0 && (
|
{isReady && (
|
||||||
<Button onClick={resetRecording} sx={{ color: d3roPalette.text.inactive }}>
|
<Button onClick={handleReset} sx={{ color: d3roPalette.text.inactive }}>
|
||||||
다시 입력
|
다시 입력
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={isRecording || recordedKeys.length === 0}
|
disabled={!isReady}
|
||||||
>
|
>
|
||||||
저장
|
저장
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -260,7 +260,7 @@ export function SettingsModal({ open, onClose }: SettingsModalProps): React.Reac
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Dialog open={open} onClose={onClose} fullWidth maxWidth="sm">
|
<Dialog open={open} onClose={onClose} fullWidth maxWidth="sm" disableEnforceFocus>
|
||||||
<DialogTitle sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', fontWeight: 700 }}>
|
<DialogTitle sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', fontWeight: 700 }}>
|
||||||
설정
|
설정
|
||||||
<IconButton onClick={onClose} size="small" sx={{ color: d3roPalette.text.label }}>
|
<IconButton onClick={onClose} size="small" sx={{ color: d3roPalette.text.label }}>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue