-
-
Notifications
You must be signed in to change notification settings - Fork 240
Expand file tree
/
Copy pathKeybindRecorder.tsx
More file actions
119 lines (111 loc) · 3.89 KB
/
KeybindRecorder.tsx
File metadata and controls
119 lines (111 loc) · 3.89 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
import { useState, forwardRef, useRef } from 'react';
import { Typography } from './Typography';
import classNames from 'classnames';
import { useFormContext } from 'react-hook-form';
const excludedKeys = [' ', 'SPACE', 'META'];
const maxKeybindLength = 4;
export const KeybindRecorder = forwardRef<
HTMLInputElement,
{
keys: string[];
onKeysChange: (v: string[]) => void;
error?: string;
}
>(function KeybindRecorder({ keys, onKeysChange, error }) {
const [localKeys, setLocalKeys] = useState<string[]>(keys);
const [isRecording, setIsRecording] = useState(false);
const [oldKeys, setOldKeys] = useState<string[]>([]);
const [invalidSlot, setInvalidSlot] = useState<number | null>(null);
const [errorText, setErrorText] = useState<string>('');
const inputRef = useRef<HTMLInputElement>(null);
const displayKeys = isRecording ? localKeys : keys;
const activeIndex = isRecording ? displayKeys.length : -1;
const displayError = errorText || error;
const { clearErrors } = useFormContext();
const handleKeyDown = (e: React.KeyboardEvent) => {
e.preventDefault();
const key = e.key.toUpperCase();
const errorMsg = excludedKeys.includes(key)
? `Cannot use ${key}!`
: displayKeys.includes(key)
? `${key} is a Duplicate Key!`
: null;
if (errorMsg) {
setErrorText(errorMsg);
setInvalidSlot(activeIndex);
setTimeout(() => {
setInvalidSlot(null);
}, 350);
return;
}
if (displayKeys.length < maxKeybindLength) {
const updatedKeys = [...displayKeys, key];
setLocalKeys(updatedKeys);
onKeysChange(updatedKeys);
if (updatedKeys.length == maxKeybindLength) {
inputRef.current?.blur();
}
}
};
const handleOnBlur = () => {
setIsRecording(false);
if (displayKeys.length < maxKeybindLength - 2 || error) {
onKeysChange(oldKeys);
setLocalKeys(oldKeys);
}
};
const handleOnFocus = () => {
clearErrors('keybinds');
const initialKeys: string[] = [];
setOldKeys(keys);
setLocalKeys(initialKeys);
onKeysChange(initialKeys);
setIsRecording(true);
};
return (
<div className="w-full justify-center items-center flex flex-col gap-2">
<div className="flex gap-2 p-2 items-center rounded-lg relative">
<input
autoFocus
ref={inputRef}
className="opacity-0 absolute cursor-pointer w-full"
onFocus={handleOnFocus}
onBlur={handleOnBlur}
onKeyDown={handleKeyDown}
/>
<div className="flex flex-grow gap-2 justify-center h-full">
{Array.from({ length: maxKeybindLength }).map((_, i) => {
const key = displayKeys[i];
const isActive = isRecording && i === activeIndex;
const isInvalid = invalidSlot === i;
return (
<div key={i} className="flex flex-row">
<div
className={classNames(
'flex p-2 rounded-lg min-w-[50px] min-h-[50px] text-main-title justify-center items-center bg-background-80 mobile:text-sm',
{
'keyslot-invalid ring-2 ring-status-critical': isInvalid,
'keyslot-animate ring-2 ring-accent':
isActive && !isInvalid,
'ring-accent': !isInvalid && !isInvalid,
}
)}
>
{key ?? ''}
</div>
<div className="flex pl-2 text-main-title justify-center items-center mobile:text-sm">
{i < maxKeybindLength - 1 ? '+' : ''}
</div>
</div>
);
})}
</div>
</div>
{displayError && (
<div className="isInvalid keyslot-invalid">
<Typography color="text-status-critical">{`${errorText} ${error}`}</Typography>
</div>
)}
</div>
);
});