- Copy complete source code packages from original CodeNomad project - Add root package.json with npm workspace configuration - Include electron-app, server, ui, tauri-app, and opencode-config packages - Fix Launch-Windows.bat and Launch-Dev-Windows.bat to work with correct npm scripts - Fix Launch-Unix.sh to work with correct npm scripts - Launchers now correctly call npm run dev:electron which launches Electron app
80 lines
2.2 KiB
TypeScript
80 lines
2.2 KiB
TypeScript
import { Component, JSX, For } from "solid-js"
|
|
import { isMac } from "../lib/keyboard-utils"
|
|
|
|
interface KbdProps {
|
|
children?: JSX.Element
|
|
shortcut?: string
|
|
class?: string
|
|
}
|
|
|
|
const SPECIAL_KEY_LABELS: Record<string, string> = {
|
|
enter: "Enter",
|
|
return: "Enter",
|
|
esc: "Esc",
|
|
escape: "Esc",
|
|
tab: "Tab",
|
|
space: "Space",
|
|
backspace: "Backspace",
|
|
delete: "Delete",
|
|
pageup: "Page Up",
|
|
pagedown: "Page Down",
|
|
home: "Home",
|
|
end: "End",
|
|
arrowup: "↑",
|
|
arrowdown: "↓",
|
|
arrowleft: "←",
|
|
arrowright: "→",
|
|
}
|
|
|
|
const Kbd: Component<KbdProps> = (props) => {
|
|
const parts = () => {
|
|
if (props.children) return [{ text: props.children, isModifier: false }]
|
|
if (!props.shortcut) return []
|
|
|
|
const result: { text: string | JSX.Element; isModifier: boolean }[] = []
|
|
const shortcut = props.shortcut.toLowerCase()
|
|
const tokens = shortcut.split("+")
|
|
|
|
tokens.forEach((token) => {
|
|
const trimmed = token.trim()
|
|
const lower = trimmed.toLowerCase()
|
|
|
|
if (lower === "cmd" || lower === "command") {
|
|
result.push({ text: isMac() ? "Cmd" : "Ctrl", isModifier: false })
|
|
} else if (lower === "shift") {
|
|
result.push({ text: "Shift", isModifier: false })
|
|
} else if (lower === "alt" || lower === "option") {
|
|
result.push({ text: isMac() ? "Option" : "Alt", isModifier: false })
|
|
} else if (lower === "ctrl" || lower === "control") {
|
|
result.push({ text: "Ctrl", isModifier: false })
|
|
} else {
|
|
const label = SPECIAL_KEY_LABELS[lower]
|
|
if (label) {
|
|
result.push({ text: label, isModifier: false })
|
|
} else if (trimmed.length === 1) {
|
|
result.push({ text: trimmed.toUpperCase(), isModifier: false })
|
|
} else {
|
|
result.push({ text: trimmed.charAt(0).toUpperCase() + trimmed.slice(1), isModifier: false })
|
|
}
|
|
}
|
|
})
|
|
|
|
return result
|
|
}
|
|
|
|
return (
|
|
<kbd class={`kbd ${props.class || ""}`}>
|
|
<For each={parts()}>
|
|
{(part, index) => (
|
|
<>
|
|
{index() > 0 && <span class="kbd-separator">+</span>}
|
|
<span>{part.text}</span>
|
|
</>
|
|
)}
|
|
</For>
|
|
</kbd>
|
|
)
|
|
}
|
|
|
|
export default Kbd
|