このコミットが含まれているのは:
2026-07-05 02:59:48 +09:00
コミット 95b4db1fc2
12個のファイルの変更1017行の追加82行の削除
+173
ファイルの表示
@@ -0,0 +1,173 @@
import { useEffect, useMemo, useState } from 'react'
import { Button } from '@/components/ui/button'
import { toast } from '@/components/ui/use-toast'
import {
formatKeyBinding,
isSpaceBinding,
keyBindingFromKeyboardEvent,
SHORTCUT_DEFINITIONS,
SHORTCUT_SCOPE_LABELS,
} from '@/lib/keyboardShortcuts'
import { useKeyboardShortcutSettings } from '@/lib/useKeyboardShortcuts'
import type { ShortcutActionId } from '@/lib/keyboardShortcuts'
import type { FC } from 'react'
type Props = {
sectionClassName: string
}
const KeyboardSettingsSection: FC<Props> = ({ sectionClassName }) => {
const {
conflictActionIds,
effectiveBindings,
keyboardSettings,
resetAllBindings,
resetBinding,
setBinding,
setEnabled,
} = useKeyboardShortcutSettings ()
const [capturingActionId, setCapturingActionId] =
useState<ShortcutActionId | null> (null)
useEffect (() => {
if (capturingActionId == null)
return
const onKeyDown = (event: KeyboardEvent) => {
if (event.isComposing || event.key === 'Process')
return
event.preventDefault ()
event.stopPropagation ()
if (event.key === 'Escape')
{
setCapturingActionId (null)
return
}
if (event.key === 'Backspace' || event.key === 'Delete')
{
setBinding (capturingActionId, null)
setCapturingActionId (null)
return
}
const binding = keyBindingFromKeyboardEvent (event)
if (binding == null)
return
if (isSpaceBinding (binding))
{
toast ({ title: 'Space は今は割り当てられません.' })
return
}
setBinding (capturingActionId, binding)
setCapturingActionId (null)
}
document.addEventListener ('keydown', onKeyDown, true)
return () => document.removeEventListener ('keydown', onKeyDown, true)
}, [capturingActionId, setBinding])
const rows = useMemo (() => SHORTCUT_DEFINITIONS.map (definition => ({
...definition,
currentBinding: effectiveBindings[definition.id],
hasConflict: conflictActionIds.has (definition.id),
hasCustomBinding: Object.prototype.hasOwnProperty.call (
keyboardSettings.bindings ?? { },
definition.id,
),
})), [conflictActionIds, effectiveBindings, keyboardSettings.bindings])
return (
<section className={sectionClassName}>
<div className="flex flex-wrap items-start justify-between gap-3">
<div className="space-y-1">
<h2 className="text-xl font-bold"></h2>
<p className="text-sm text-muted-foreground">
dispatcher
</p>
</div>
<Button type="button" variant="outline" onClick={resetAllBindings}>
</Button>
</div>
<label className="flex items-center gap-2 text-sm">
<input
type="checkbox"
checked={keyboardSettings.enabled !== false}
onChange={event => {
setEnabled (event.target.checked)
}}/>
<span></span>
</label>
{capturingActionId != null && (
<p className="text-sm text-muted-foreground">
Escape Backspace / Delete
</p>)}
<div className="overflow-x-auto">
<table className="w-full min-w-[52rem] table-fixed border-collapse text-sm">
<thead className="border-b border-border">
<tr>
<th className="p-2 text-left"></th>
<th className="p-2 text-left"></th>
<th className="p-2 text-left"></th>
<th className="p-2 text-left"></th>
<th className="p-2 text-left"></th>
<th className="p-2 text-left"></th>
</tr>
</thead>
<tbody>
{rows.map (row => (
<tr key={row.id} className="border-b border-border/60 last:border-b-0">
<td className="p-2">{row.label}</td>
<td className="p-2">{SHORTCUT_SCOPE_LABELS[row.scope]}</td>
<td className="p-2 font-mono">
{formatKeyBinding (row.currentBinding)}
</td>
<td className="p-2">
<Button
type="button"
variant={capturingActionId === row.id ? 'default' : 'outline'}
className="h-8"
onClick={() => {
setCapturingActionId (
capturingActionId === row.id ? null : row.id,
)
}}>
{capturingActionId === row.id ? '入力待ち…' : '変更'}
</Button>
</td>
<td className="p-2">
<Button
type="button"
variant="ghost"
className="h-8"
disabled={!(row.hasCustomBinding)}
onClick={() => resetBinding (row.id)}>
</Button>
</td>
<td className="p-2">
{row.hasConflict
? <span className="text-red-600 dark:text-red-400"></span>
: 'なし'}
</td>
</tr>))}
</tbody>
</table>
</div>
</section>)
}
export default KeyboardSettingsSection