-
-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathShortcutPicker.tsx
More file actions
123 lines (99 loc) · 3.02 KB
/
ShortcutPicker.tsx
File metadata and controls
123 lines (99 loc) · 3.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import { FC, KeyboardEvent, memo, useCallback, useEffect, useState } from 'react'
type ParsedShortcut = {
isCmd: boolean
isAlt: boolean
isCtrl: boolean
isShift: boolean
natural: string
}
type Props = {
value: string
onChange: (value: string) => void
}
const ShortcutPicker: FC<Props> = ({ value, onChange }) => {
const [shortcut, setShortcut] = useState<ParsedShortcut>({
isCmd: true,
isAlt: true,
natural: 'C',
isCtrl: false,
isShift: true,
})
console.log(shortcut)
useEffect(() => {
setShortcut(parseShortcut(value))
}, [value])
const handleKeyDown = useCallback(
(event: KeyboardEvent<HTMLDivElement>) => {
event.preventDefault()
const natural = getKey(event)
const shortcut = {
natural,
isAlt: event.altKey,
isCmd: event.metaKey,
isCtrl: event.ctrlKey,
isShift: event.shiftKey,
} satisfies ParsedShortcut
setShortcut(shortcut)
if (natural.trim() != '') {
;(event.target as HTMLDivElement).blur()
}
onChange(buildShortcut(shortcut))
},
[onChange]
)
return (
<div
tabIndex={0}
onKeyUp={handleKeyDown}
onKeyDown={handleKeyDown}
onBlur={() => setShortcut(parseShortcut(value))}
onFocus={() => setShortcut({ isCmd: false, isAlt: false, isCtrl: false, isShift: false, natural: '' })}
className="py-1 bg-black/[.01] dark:bg-white/[.01] hover:bg-black/[.03] dark:hover:bg-white/[0.03] text-black/70 dark:text-white/70 focus:text-black/60 px-2 border dark:border-white/5 rounded-lg focus:cursor-pointer focus:outline focus:outline-blue-400 dark:focus:outline-blue-600/40 animate duration-100"
>
{displayShortcut(shortcut)}
</div>
)
}
const parseShortcut = (shortcut: string): ParsedShortcut => {
const parts = shortcut.split('+')
return {
isAlt: parts.includes('Alt'),
isCtrl: parts.includes('Ctrl'),
isShift: parts.includes('Shift'),
natural: parts[parts.length - 1].toUpperCase(),
isCmd: parts.includes('CommandOrControl') || parts.includes('Cmd'),
}
}
const displayShortcut = (shortcut: ParsedShortcut): string => {
let parts = []
if (shortcut.isCmd) parts.push('⌘')
if (shortcut.isCtrl) parts.push('⌃')
if (shortcut.isShift) parts.push('⇧')
if (shortcut.isAlt) parts.push('⌥')
parts.push(shortcut.natural.toUpperCase())
const display = parts.join('')
return display.trim() == '' ? 'Recording...' : display
}
const buildShortcut = (shortcut: ParsedShortcut): string => {
let parts = []
if (shortcut.isAlt) parts.push('Alt')
if (shortcut.isCtrl) parts.push('Ctrl')
if (shortcut.isShift) parts.push('Shift')
if (shortcut.isCmd) parts.push('CommandOrControl')
parts.push(shortcut.natural.toUpperCase())
return parts.join('+')
}
const getKey = (event: KeyboardEvent<HTMLDivElement>) => {
return [' ', 'Meta', 'Control', 'Shift', 'Alt'].includes(event.key)
? ''
: event.key.length > 1
? event.key
: event.code.startsWith('Key')
? event.code.replace('Key', '')
: event.key
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.toUpperCase()
.replace('Dead', 'I')
}
export default memo(ShortcutPicker)