refactor(ui): Improve header styling and detach bug

- Remove unused AttachIcon and related SVG asset.
- Replace icon usage with a styled LinkButton to improve consistency.
- Simplify and reformat VirtualKeyboard component for better readability.
This commit is contained in:
Adam Shiervani 2025-09-02 14:27:55 +02:00
parent 771b1387fe
commit a97c0c2465
2 changed files with 53 additions and 49 deletions

View File

@ -1,8 +0,0 @@
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 6C2 4.89543 2.89543 4 4 4H20C21.1046 4 22 4.89543 22 6V18C22 19.1046 21.1046 20 20 20H4C2.89543 20 2 19.1046 2 18V6Z"
fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd"
d="M20 6H4V18H20V6ZM4 4C2.89543 4 2 4.89543 2 6V18C2 19.1046 2.89543 20 4 20H20C21.1046 20 22 19.1046 22 18V6C22 4.89543 21.1046 4 20 4H4Z"
fill="black"/>
<path d="M4 13H20V18H4V13Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 511 B

View File

@ -2,33 +2,31 @@ import { ChevronDownIcon } from "@heroicons/react/16/solid";
import { AnimatePresence, motion } from "framer-motion";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import Keyboard from "react-simple-keyboard";
import { LuKeyboard } from "react-icons/lu";
import Card from "@components/Card";
// eslint-disable-next-line import/order
import { Button } from "@components/Button";
import { Button, LinkButton } from "@components/Button";
import "react-simple-keyboard/build/css/index.css";
import AttachIconRaw from "@/assets/attach-icon.svg";
import DetachIconRaw from "@/assets/detach-icon.svg";
import { cx } from "@/cva.config";
import { useHidStore, useUiStore } from "@/hooks/stores";
import useKeyboard from "@/hooks/useKeyboard";
import useKeyboardLayout from "@/hooks/useKeyboardLayout";
import { keys, modifiers, latchingKeys, decodeModifiers } from "@/keyboardMappings";
import { decodeModifiers, keys, latchingKeys, modifiers } from "@/keyboardMappings";
export const DetachIcon = ({ className }: { className?: string }) => {
return <img src={DetachIconRaw} alt="Detach Icon" className={className} />;
};
const AttachIcon = ({ className }: { className?: string }) => {
return <img src={AttachIconRaw} alt="Attach Icon" className={className} />;
};
function KeyboardWrapper() {
const keyboardRef = useRef<HTMLDivElement>(null);
const { isAttachedVirtualKeyboardVisible, setAttachedVirtualKeyboardVisibility } = useUiStore();
const { keysDownState, /* keyboardLedState,*/ isVirtualKeyboardEnabled, setVirtualKeyboardEnabled } = useHidStore();
const { isAttachedVirtualKeyboardVisible, setAttachedVirtualKeyboardVisibility } =
useUiStore();
const { keysDownState, isVirtualKeyboardEnabled, setVirtualKeyboardEnabled } =
useHidStore();
const { handleKeyPress, executeMacro } = useKeyboard();
const { selectedKeyboard } = useKeyboardLayout();
@ -44,29 +42,28 @@ function KeyboardWrapper() {
return selectedKeyboard.virtualKeyboard;
}, [selectedKeyboard]);
//const isCapsLockActive = useMemo(() => {
// return (keyboardLedState.caps_lock);
//}, [keyboardLedState]);
const { isShiftActive, /*isControlActive, isAltActive, isMetaActive, isAltGrActive*/ } = useMemo(() => {
const { isShiftActive } = useMemo(() => {
return decodeModifiers(keysDownState.modifier);
}, [keysDownState]);
const mainLayoutName = useMemo(() => {
const layoutName = isShiftActive ? "shift": "default";
return layoutName;
return isShiftActive ? "shift" : "default";
}, [isShiftActive]);
const keyNamesForDownKeys = useMemo(() => {
const activeModifierMask = keysDownState.modifier || 0;
const modifierNames = Object.entries(modifiers).filter(([_, mask]) => (activeModifierMask & mask) !== 0).map(([name, _]) => name);
const modifierNames = Object.entries(modifiers)
.filter(([_, mask]) => (activeModifierMask & mask) !== 0)
.map(([name, _]) => name);
const keysDown = keysDownState.keys || [];
const keyNames = Object.entries(keys).filter(([_, value]) => keysDown.includes(value)).map(([name, _]) => name);
const keyNames = Object.entries(keys)
.filter(([_, value]) => keysDown.includes(value))
.map(([name, _]) => name);
return [...modifierNames,...keyNames, ' ']; // we have to have at least one space to avoid keyboard whining
return [...modifierNames, ...keyNames, " "]; // we have to have at least one space to avoid keyboard whining
}, [keysDownState]);
const startDrag = useCallback((e: MouseEvent | TouchEvent) => {
if (!keyboardRef.current) return;
if (e instanceof TouchEvent && e.touches.length > 1) return;
@ -110,6 +107,9 @@ function KeyboardWrapper() {
}, []);
useEffect(() => {
// Is the keyboard detached or attached?
if (isAttachedVirtualKeyboardVisible) return;
const handle = keyboardRef.current;
if (handle) {
handle.addEventListener("touchstart", startDrag);
@ -134,15 +134,12 @@ function KeyboardWrapper() {
document.removeEventListener("mousemove", onDrag);
document.removeEventListener("touchmove", onDrag);
};
}, [endDrag, onDrag, startDrag]);
}, [isAttachedVirtualKeyboardVisible, endDrag, onDrag, startDrag]);
const onKeyUp = useCallback(
async (_: string, e: MouseEvent | undefined) => {
e?.preventDefault();
e?.stopPropagation();
},
[]
);
const onKeyUp = useCallback(async (_: string, e: MouseEvent | undefined) => {
e?.preventDefault();
e?.stopPropagation();
}, []);
const onKeyDown = useCallback(
async (key: string, e: MouseEvent | undefined) => {
@ -151,24 +148,30 @@ function KeyboardWrapper() {
// handle the fake key-macros we have defined for common combinations
if (key === "CtrlAltDelete") {
await executeMacro([ { keys: ["Delete"], modifiers: ["ControlLeft", "AltLeft"], delay: 100 } ]);
await executeMacro([
{ keys: ["Delete"], modifiers: ["ControlLeft", "AltLeft"], delay: 100 },
]);
return;
}
if (key === "AltMetaEscape") {
await executeMacro([ { keys: ["Escape"], modifiers: ["AltLeft", "MetaLeft"], delay: 100 } ]);
await executeMacro([
{ keys: ["Escape"], modifiers: ["AltLeft", "MetaLeft"], delay: 100 },
]);
return;
}
if (key === "CtrlAltBackspace") {
await executeMacro([ { keys: ["Backspace"], modifiers: ["ControlLeft", "AltLeft"], delay: 100 } ]);
await executeMacro([
{ keys: ["Backspace"], modifiers: ["ControlLeft", "AltLeft"], delay: 100 },
]);
return;
}
// if they press any of the latching keys, we send a keypress down event and the release it automatically (on timer)
if (latchingKeys.includes(key)) {
console.debug(`Latching key pressed: ${key} sending down and delayed up pair`);
handleKeyPress(keys[key], true)
handleKeyPress(keys[key], true);
setTimeout(() => handleKeyPress(keys[key], false), 100);
return;
}
@ -176,8 +179,10 @@ function KeyboardWrapper() {
// if they press any of the dynamic keys, we send a keypress down event but we don't release it until they click it again
if (Object.keys(modifiers).includes(key)) {
const currentlyDown = keyNamesForDownKeys.includes(key);
console.debug(`Dynamic key pressed: ${key} was currently down: ${currentlyDown}, toggling state`);
handleKeyPress(keys[key], !currentlyDown)
console.debug(
`Dynamic key pressed: ${key} was currently down: ${currentlyDown}, toggling state`,
);
handleKeyPress(keys[key], !currentlyDown);
return;
}
@ -211,7 +216,7 @@ function KeyboardWrapper() {
<div
className={cx(
!isAttachedVirtualKeyboardVisible
? "fixed left-0 top-0 z-50 select-none"
? "fixed top-0 left-0 z-10 select-none"
: "relative",
)}
ref={keyboardRef}
@ -224,7 +229,7 @@ function KeyboardWrapper() {
<Card
className={cx("overflow-hidden", {
"rounded-none": isAttachedVirtualKeyboardVisible,
"keyboard-detached": !isAttachedVirtualKeyboardVisible
"keyboard-detached": !isAttachedVirtualKeyboardVisible,
})}
>
<div className="flex items-center justify-center border-b border-b-slate-800/30 bg-white px-2 py-4 dark:border-b-slate-300/20 dark:bg-slate-800">
@ -241,15 +246,22 @@ function KeyboardWrapper() {
size="XS"
theme="light"
text="Attach"
LeadingIcon={AttachIcon}
onClick={() => setAttachedVirtualKeyboardVisibility(true)}
/>
)}
</div>
<h2 className="select-none self-center font-sans text-[12px] text-slate-700 dark:text-slate-300">
Virtual Keyboard<span className="text-[10px]"> - {selectedKeyboard.name}</span>
<h2 className="self-center font-sans text-sm leading-none font-medium text-slate-700 select-none dark:text-slate-300">
Virtual Keyboard
</h2>
<div className="absolute right-2">
<div className="absolute right-2 flex items-center gap-x-2">
<LinkButton
size="XS"
to="settings/keyboard"
theme="light"
text={selectedKeyboard.name}
TrailingIcon={LuKeyboard}
/>
<div className="h-[16px] w-px bg-slate-800/20 dark:bg-slate-200/20" />
<Button
size="XS"
theme="light"
@ -318,7 +330,7 @@ function KeyboardWrapper() {
stopMouseUpPropagation={true}
/>
</div>
{ /* TODO add optional number pad */ }
{/* TODO add optional number pad */}
</div>
</div>
</Card>