このコミットが含まれているのは:
2026-07-05 02:59:48 +09:00
コミット 95b4db1fc2
12個のファイルの変更1017行の追加82行の削除
+13 -1
ファイルの表示
@@ -7,7 +7,7 @@ import { useOverlayStore } from '@/components/RouteBlockerOverlay'
import { prefetchForURL } from '@/lib/prefetchers'
import { cn } from '@/lib/utils'
import type { AnchorHTMLAttributes, MouseEvent, TouchEvent } from 'react'
import type { AnchorHTMLAttributes, KeyboardEvent, MouseEvent, TouchEvent } from 'react'
import type { To } from 'react-router-dom'
type Props = AnchorHTMLAttributes<HTMLAnchorElement> & {
@@ -25,6 +25,7 @@ export default forwardRef<HTMLAnchorElement, Props> (({
state,
onMouseEnter,
onTouchStart,
onKeyDown,
onClick,
cancelOnError = false,
...rest }, ref) => {
@@ -97,11 +98,22 @@ export default forwardRef<HTMLAnchorElement, Props> (({
}
}
const handleKeyDown = (ev: KeyboardEvent<HTMLAnchorElement>) => {
onKeyDown?.(ev)
if (ev.defaultPrevented)
return
if (ev.key === ' ' || ev.key === 'Spacebar')
ev.preventDefault ()
}
return (
<a ref={ref}
href={typeof to === 'string' ? to : createPath (to)}
onMouseEnter={handleMouseEnter}
onTouchStart={handleTouchStart}
onKeyDown={handleKeyDown}
onClick={handleClick}
className={cn ('cursor-pointer', className)}
{...rest}/>)
+5 -5
ファイルの表示
@@ -249,16 +249,16 @@ const TopNav: FC<Props> = ({ user }) => {
<TopNavUser user={user}/>
<a href="#"
className="md:hidden ml-auto pr-4
<button
type="button"
className="md:hidden ml-auto border-0 bg-transparent pr-4
text-pink-600 hover:text-pink-400
dark:text-pink-300 dark:hover:text-pink-100"
onClick={ev => {
ev.preventDefault ()
onClick={() => {
setMenuOpen (!(menuOpen))
}}>
{menuOpen ? '×' : 'Menu'}
</a>
</button>
</nav>
<AnimatePresence initial={false}>
+6 -6
ファイルの表示
@@ -28,15 +28,15 @@ const TabGroup: FC<Props> = ({ children, rightAddon }) => {
<div className="flex items-center justify-between gap-4">
<div className="flex min-w-0 gap-4">
{tabs.map ((tab, i) => (
<a key={i}
href="#"
className={cn (i === current && 'font-bold')}
onClick={ev => {
ev.preventDefault ()
<button
key={i}
type="button"
className={cn ('bg-transparent p-0 text-left', i === current && 'font-bold')}
onClick={() => {
setCurrent (i)
}}>
{tab.props.name}
</a>))}
</button>))}
</div>
{rightAddon && (
<div className="shrink-0">
+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