feat: improve keyboard shortcuts UI with Command Palette
- Add openCommandPalette() export function for manual triggering - Update ? button to open the colorful Command Palette modal instead of dropdown - Add support for manual opening with Escape and click-outside to close - Clean up unused shortcut dropdown code and state - Maintain Ctrl+Shift hold behavior for quick access 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
c314edac0c
commit
0a95c31974
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useState, useEffect, useRef, useCallback } from "react"
|
import React, { useState, useEffect, useRef, useCallback } from "react"
|
||||||
import { useEditor } from "tldraw"
|
import { useEditor } from "tldraw"
|
||||||
|
|
||||||
// Command Palette that shows when holding Ctrl+Shift
|
// Command Palette that shows when holding Ctrl+Shift or via global event
|
||||||
// Displays available keyboard shortcuts for custom tools and actions
|
// Displays available keyboard shortcuts for custom tools and actions
|
||||||
|
|
||||||
interface ShortcutItem {
|
interface ShortcutItem {
|
||||||
|
|
@ -13,9 +13,15 @@ interface ShortcutItem {
|
||||||
category: 'tool' | 'action'
|
category: 'tool' | 'action'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Global function to open the command palette
|
||||||
|
export function openCommandPalette() {
|
||||||
|
window.dispatchEvent(new CustomEvent('open-command-palette'))
|
||||||
|
}
|
||||||
|
|
||||||
export function CommandPalette() {
|
export function CommandPalette() {
|
||||||
const editor = useEditor()
|
const editor = useEditor()
|
||||||
const [isVisible, setIsVisible] = useState(false)
|
const [isVisible, setIsVisible] = useState(false)
|
||||||
|
const [isManuallyOpened, setIsManuallyOpened] = useState(false)
|
||||||
const holdTimeoutRef = useRef<NodeJS.Timeout | null>(null)
|
const holdTimeoutRef = useRef<NodeJS.Timeout | null>(null)
|
||||||
const keysHeldRef = useRef({ ctrl: false, shift: false })
|
const keysHeldRef = useRef({ ctrl: false, shift: false })
|
||||||
|
|
||||||
|
|
@ -52,6 +58,7 @@ export function CommandPalette() {
|
||||||
// Handle clicking on a tool/action
|
// Handle clicking on a tool/action
|
||||||
const handleItemClick = useCallback((item: ShortcutItem) => {
|
const handleItemClick = useCallback((item: ShortcutItem) => {
|
||||||
setIsVisible(false)
|
setIsVisible(false)
|
||||||
|
setIsManuallyOpened(false)
|
||||||
|
|
||||||
if (item.category === 'tool') {
|
if (item.category === 'tool') {
|
||||||
// Set the current tool
|
// Set the current tool
|
||||||
|
|
@ -71,6 +78,44 @@ export function CommandPalette() {
|
||||||
}
|
}
|
||||||
}, [editor])
|
}, [editor])
|
||||||
|
|
||||||
|
// Handle manual open via custom event
|
||||||
|
useEffect(() => {
|
||||||
|
const handleOpenEvent = () => {
|
||||||
|
setIsManuallyOpened(true)
|
||||||
|
setIsVisible(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('open-command-palette', handleOpenEvent)
|
||||||
|
return () => window.removeEventListener('open-command-palette', handleOpenEvent)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// Handle Escape key and click outside to close when manually opened
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isManuallyOpened) return
|
||||||
|
|
||||||
|
const handleEscape = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
setIsVisible(false)
|
||||||
|
setIsManuallyOpened(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClickOutside = (e: MouseEvent) => {
|
||||||
|
const target = e.target as HTMLElement
|
||||||
|
if (target.closest('.command-palette')) return
|
||||||
|
setIsVisible(false)
|
||||||
|
setIsManuallyOpened(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('keydown', handleEscape)
|
||||||
|
window.addEventListener('mousedown', handleClickOutside)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('keydown', handleEscape)
|
||||||
|
window.removeEventListener('mousedown', handleClickOutside)
|
||||||
|
}
|
||||||
|
}, [isManuallyOpened])
|
||||||
|
|
||||||
// Handle Ctrl+Shift key press/release
|
// Handle Ctrl+Shift key press/release
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkAndShowPalette = () => {
|
const checkAndShowPalette = () => {
|
||||||
|
|
@ -93,8 +138,8 @@ export function CommandPalette() {
|
||||||
} else if (e.key === 'Shift') {
|
} else if (e.key === 'Shift') {
|
||||||
keysHeldRef.current.shift = true
|
keysHeldRef.current.shift = true
|
||||||
checkAndShowPalette()
|
checkAndShowPalette()
|
||||||
} else if (isVisible) {
|
} else if (isVisible && !isManuallyOpened) {
|
||||||
// Hide on any other key press (they're using a shortcut)
|
// Hide on any other key press (they're using a shortcut) - only if not manually opened
|
||||||
setIsVisible(false)
|
setIsVisible(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -106,8 +151,8 @@ export function CommandPalette() {
|
||||||
keysHeldRef.current.shift = false
|
keysHeldRef.current.shift = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide palette if either key is released
|
// Hide palette if either key is released - only if not manually opened
|
||||||
if (!keysHeldRef.current.ctrl || !keysHeldRef.current.shift) {
|
if (!isManuallyOpened && (!keysHeldRef.current.ctrl || !keysHeldRef.current.shift)) {
|
||||||
if (holdTimeoutRef.current) {
|
if (holdTimeoutRef.current) {
|
||||||
clearTimeout(holdTimeoutRef.current)
|
clearTimeout(holdTimeoutRef.current)
|
||||||
holdTimeoutRef.current = null
|
holdTimeoutRef.current = null
|
||||||
|
|
@ -126,7 +171,7 @@ export function CommandPalette() {
|
||||||
clearTimeout(holdTimeoutRef.current)
|
clearTimeout(holdTimeoutRef.current)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [isVisible])
|
}, [isVisible, isManuallyOpened])
|
||||||
|
|
||||||
if (!isVisible) return null
|
if (!isVisible) return null
|
||||||
|
|
||||||
|
|
@ -146,7 +191,7 @@ export function CommandPalette() {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
zIndex: 999999,
|
zIndex: 999999,
|
||||||
pointerEvents: 'none',
|
pointerEvents: isManuallyOpened ? 'auto' : 'none',
|
||||||
animation: 'fadeIn 0.15s ease-out',
|
animation: 'fadeIn 0.15s ease-out',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { CustomToolbar } from "./CustomToolbar"
|
||||||
import { CustomContextMenu } from "./CustomContextMenu"
|
import { CustomContextMenu } from "./CustomContextMenu"
|
||||||
import { FocusLockIndicator } from "./FocusLockIndicator"
|
import { FocusLockIndicator } from "./FocusLockIndicator"
|
||||||
import { MycelialIntelligenceBar } from "./MycelialIntelligenceBar"
|
import { MycelialIntelligenceBar } from "./MycelialIntelligenceBar"
|
||||||
import { CommandPalette } from "./CommandPalette"
|
import { CommandPalette, openCommandPalette } from "./CommandPalette"
|
||||||
import { NetworkGraphPanel } from "../components/networking"
|
import { NetworkGraphPanel } from "../components/networking"
|
||||||
import CryptIDDropdown from "../components/auth/CryptIDDropdown"
|
import CryptIDDropdown from "../components/auth/CryptIDDropdown"
|
||||||
import StarBoardButton from "../components/StarBoardButton"
|
import StarBoardButton from "../components/StarBoardButton"
|
||||||
|
|
@ -47,14 +47,11 @@ const PERMISSION_CONFIG: Record<PermissionLevel, { label: string; color: string;
|
||||||
|
|
||||||
// Custom SharePanel with layout: CryptID -> Star -> Gear -> Question mark
|
// Custom SharePanel with layout: CryptID -> Star -> Gear -> Question mark
|
||||||
function CustomSharePanel() {
|
function CustomSharePanel() {
|
||||||
const tools = useTools()
|
|
||||||
const actions = useActions()
|
|
||||||
const { addDialog, removeDialog } = useDialogs()
|
const { addDialog, removeDialog } = useDialogs()
|
||||||
const { session } = useAuth()
|
const { session } = useAuth()
|
||||||
const { slug } = useParams<{ slug: string }>()
|
const { slug } = useParams<{ slug: string }>()
|
||||||
const boardId = slug || 'mycofi33'
|
const boardId = slug || 'mycofi33'
|
||||||
|
|
||||||
const [showShortcuts, setShowShortcuts] = React.useState(false)
|
|
||||||
const [showSettingsDropdown, setShowSettingsDropdown] = React.useState(false)
|
const [showSettingsDropdown, setShowSettingsDropdown] = React.useState(false)
|
||||||
// const [showVersionHistory, setShowVersionHistory] = React.useState(false) // TODO: Re-enable when version reversion is ready
|
// const [showVersionHistory, setShowVersionHistory] = React.useState(false) // TODO: Re-enable when version reversion is ready
|
||||||
const [showAISection, setShowAISection] = React.useState(false)
|
const [showAISection, setShowAISection] = React.useState(false)
|
||||||
|
|
@ -73,9 +70,7 @@ function CustomSharePanel() {
|
||||||
|
|
||||||
// Refs for dropdown positioning
|
// Refs for dropdown positioning
|
||||||
const settingsButtonRef = React.useRef<HTMLButtonElement>(null)
|
const settingsButtonRef = React.useRef<HTMLButtonElement>(null)
|
||||||
const shortcutsButtonRef = React.useRef<HTMLButtonElement>(null)
|
|
||||||
const [settingsDropdownPos, setSettingsDropdownPos] = React.useState<{ top: number; right: number } | null>(null)
|
const [settingsDropdownPos, setSettingsDropdownPos] = React.useState<{ top: number; right: number } | null>(null)
|
||||||
const [shortcutsDropdownPos, setShortcutsDropdownPos] = React.useState<{ top: number; right: number } | null>(null)
|
|
||||||
|
|
||||||
// Get current permission from session
|
// Get current permission from session
|
||||||
// Authenticated users default to 'edit', unauthenticated to 'view'
|
// Authenticated users default to 'edit', unauthenticated to 'view'
|
||||||
|
|
@ -136,16 +131,6 @@ function CustomSharePanel() {
|
||||||
}
|
}
|
||||||
}, [showSettingsDropdown])
|
}, [showSettingsDropdown])
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (showShortcuts && shortcutsButtonRef.current) {
|
|
||||||
const rect = shortcutsButtonRef.current.getBoundingClientRect()
|
|
||||||
setShortcutsDropdownPos({
|
|
||||||
top: rect.bottom + 8,
|
|
||||||
right: window.innerWidth - rect.right,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, [showShortcuts])
|
|
||||||
|
|
||||||
// ESC key handler for closing dropdowns
|
// ESC key handler for closing dropdowns
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
|
|
@ -153,15 +138,14 @@ function CustomSharePanel() {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
if (showSettingsDropdown) setShowSettingsDropdown(false)
|
if (showSettingsDropdown) setShowSettingsDropdown(false)
|
||||||
if (showShortcuts) setShowShortcuts(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (showSettingsDropdown || showShortcuts) {
|
if (showSettingsDropdown) {
|
||||||
// Use capture phase to intercept before tldraw
|
// Use capture phase to intercept before tldraw
|
||||||
document.addEventListener('keydown', handleKeyDown, true)
|
document.addEventListener('keydown', handleKeyDown, true)
|
||||||
}
|
}
|
||||||
return () => document.removeEventListener('keydown', handleKeyDown, true)
|
return () => document.removeEventListener('keydown', handleKeyDown, true)
|
||||||
}, [showSettingsDropdown, showShortcuts])
|
}, [showSettingsDropdown])
|
||||||
|
|
||||||
// Detect dark mode - use state to trigger re-render on change
|
// Detect dark mode - use state to trigger re-render on change
|
||||||
const [isDarkMode, setIsDarkMode] = React.useState(
|
const [isDarkMode, setIsDarkMode] = React.useState(
|
||||||
|
|
@ -359,84 +343,6 @@ function CustomSharePanel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to extract label string from tldraw label (can be string or {default, menu} object)
|
|
||||||
const getLabelString = (label: any, fallback: string): string => {
|
|
||||||
if (typeof label === 'string') return label
|
|
||||||
if (label && typeof label === 'object' && 'default' in label) return label.default
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect all tools and actions with keyboard shortcuts
|
|
||||||
const allShortcuts = React.useMemo(() => {
|
|
||||||
const shortcuts: { name: string; kbd: string; category: string }[] = []
|
|
||||||
|
|
||||||
// Built-in tools
|
|
||||||
const builtInTools = ['select', 'hand', 'draw', 'eraser', 'arrow', 'text', 'note', 'frame', 'geo', 'line', 'highlight', 'laser']
|
|
||||||
builtInTools.forEach(toolId => {
|
|
||||||
const tool = tools[toolId]
|
|
||||||
if (tool?.kbd) {
|
|
||||||
shortcuts.push({
|
|
||||||
name: getLabelString(tool.label, toolId),
|
|
||||||
kbd: tool.kbd,
|
|
||||||
category: 'Tools'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Custom tools (VideoGen and Map temporarily hidden)
|
|
||||||
const customToolIds = ['VideoChat', 'ChatBox', 'Embed', 'Slide', 'Markdown', 'MycrozineTemplate', 'Prompt', 'ObsidianNote', 'Transcription', 'Holon', 'FathomMeetings', 'ImageGen', 'Multmux']
|
|
||||||
customToolIds.forEach(toolId => {
|
|
||||||
const tool = tools[toolId]
|
|
||||||
if (tool?.kbd) {
|
|
||||||
shortcuts.push({
|
|
||||||
name: getLabelString(tool.label, toolId),
|
|
||||||
kbd: tool.kbd,
|
|
||||||
category: 'Custom Tools'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Built-in actions
|
|
||||||
const builtInActionIds = ['undo', 'redo', 'cut', 'copy', 'paste', 'delete', 'select-all', 'duplicate', 'group', 'ungroup', 'bring-to-front', 'send-to-back', 'zoom-in', 'zoom-out', 'zoom-to-fit', 'zoom-to-100', 'toggle-grid']
|
|
||||||
builtInActionIds.forEach(actionId => {
|
|
||||||
const action = actions[actionId]
|
|
||||||
if (action?.kbd) {
|
|
||||||
shortcuts.push({
|
|
||||||
name: getLabelString(action.label, actionId),
|
|
||||||
kbd: action.kbd,
|
|
||||||
category: 'Actions'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Custom actions
|
|
||||||
const customActionIds = ['copy-link-to-current-view', 'copy-focus-link', 'unlock-camera-focus', 'revert-camera', 'lock-element', 'save-to-pdf', 'search-shapes', 'llm', 'open-obsidian-browser']
|
|
||||||
customActionIds.forEach(actionId => {
|
|
||||||
const action = actions[actionId]
|
|
||||||
if (action?.kbd) {
|
|
||||||
shortcuts.push({
|
|
||||||
name: getLabelString(action.label, actionId),
|
|
||||||
kbd: action.kbd,
|
|
||||||
category: 'Custom Actions'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return shortcuts
|
|
||||||
}, [tools, actions])
|
|
||||||
|
|
||||||
// Group shortcuts by category
|
|
||||||
const groupedShortcuts = React.useMemo(() => {
|
|
||||||
const groups: Record<string, typeof allShortcuts> = {}
|
|
||||||
allShortcuts.forEach(shortcut => {
|
|
||||||
if (!groups[shortcut.category]) {
|
|
||||||
groups[shortcut.category] = []
|
|
||||||
}
|
|
||||||
groups[shortcut.category].push(shortcut)
|
|
||||||
})
|
|
||||||
return groups
|
|
||||||
}, [allShortcuts])
|
|
||||||
|
|
||||||
// Separator component for unified menu
|
// Separator component for unified menu
|
||||||
const Separator = () => (
|
const Separator = () => (
|
||||||
<div style={{
|
<div style={{
|
||||||
|
|
@ -1157,14 +1063,13 @@ function CustomSharePanel() {
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
{/* Help/Keyboard shortcuts button - rightmost */}
|
{/* Help/Keyboard shortcuts button - rightmost - opens Command Palette */}
|
||||||
<div style={{ padding: '0 4px' }}>
|
<div style={{ padding: '0 4px' }}>
|
||||||
<button
|
<button
|
||||||
ref={shortcutsButtonRef}
|
onClick={() => openCommandPalette()}
|
||||||
onClick={() => setShowShortcuts(!showShortcuts)}
|
|
||||||
className="share-panel-btn"
|
className="share-panel-btn"
|
||||||
style={{
|
style={{
|
||||||
background: showShortcuts ? 'var(--color-muted-2)' : 'none',
|
background: 'none',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
padding: '6px',
|
padding: '6px',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
|
|
@ -1173,7 +1078,7 @@ function CustomSharePanel() {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
color: 'var(--color-text-1)',
|
color: 'var(--color-text-1)',
|
||||||
opacity: showShortcuts ? 1 : 0.7,
|
opacity: 0.7,
|
||||||
transition: 'opacity 0.15s, background 0.15s',
|
transition: 'opacity 0.15s, background 0.15s',
|
||||||
pointerEvents: 'all',
|
pointerEvents: 'all',
|
||||||
}}
|
}}
|
||||||
|
|
@ -1182,10 +1087,8 @@ function CustomSharePanel() {
|
||||||
e.currentTarget.style.background = 'var(--color-muted-2)'
|
e.currentTarget.style.background = 'var(--color-muted-2)'
|
||||||
}}
|
}}
|
||||||
onMouseLeave={(e) => {
|
onMouseLeave={(e) => {
|
||||||
if (!showShortcuts) {
|
e.currentTarget.style.opacity = '0.7'
|
||||||
e.currentTarget.style.opacity = '0.7'
|
e.currentTarget.style.background = 'none'
|
||||||
e.currentTarget.style.background = 'none'
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
title="Keyboard shortcuts (?)"
|
title="Keyboard shortcuts (?)"
|
||||||
>
|
>
|
||||||
|
|
@ -1198,117 +1101,6 @@ function CustomSharePanel() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Keyboard shortcuts panel - rendered via portal to break out of parent container */}
|
|
||||||
{showShortcuts && shortcutsDropdownPos && createPortal(
|
|
||||||
<>
|
|
||||||
{/* Backdrop - only uses onClick, not onPointerDown */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: 'fixed',
|
|
||||||
inset: 0,
|
|
||||||
zIndex: 99998,
|
|
||||||
background: 'transparent',
|
|
||||||
}}
|
|
||||||
onClick={() => setShowShortcuts(false)}
|
|
||||||
/>
|
|
||||||
{/* Shortcuts menu */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: 'fixed',
|
|
||||||
top: shortcutsDropdownPos.top,
|
|
||||||
right: shortcutsDropdownPos.right,
|
|
||||||
width: '320px',
|
|
||||||
maxHeight: '50vh',
|
|
||||||
overflowY: 'auto',
|
|
||||||
overflowX: 'hidden',
|
|
||||||
background: 'var(--color-panel, #ffffff)',
|
|
||||||
backgroundColor: 'var(--color-panel, #ffffff)',
|
|
||||||
border: '1px solid var(--color-panel-contrast)',
|
|
||||||
borderRadius: '8px',
|
|
||||||
boxShadow: '0 4px 20px rgba(0,0,0,0.25)',
|
|
||||||
zIndex: 99999,
|
|
||||||
padding: '10px 0',
|
|
||||||
pointerEvents: 'auto',
|
|
||||||
backdropFilter: 'none',
|
|
||||||
opacity: 1,
|
|
||||||
}}
|
|
||||||
onWheel={(e) => e.stopPropagation()}
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
<div style={{
|
|
||||||
padding: '8px 16px 12px',
|
|
||||||
fontSize: '14px',
|
|
||||||
fontWeight: 600,
|
|
||||||
color: 'var(--color-text)',
|
|
||||||
borderBottom: '1px solid var(--color-panel-contrast)',
|
|
||||||
marginBottom: '8px',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
}}>
|
|
||||||
<span>Keyboard Shortcuts</span>
|
|
||||||
<button
|
|
||||||
onClick={() => setShowShortcuts(false)}
|
|
||||||
style={{
|
|
||||||
background: 'none',
|
|
||||||
border: 'none',
|
|
||||||
cursor: 'pointer',
|
|
||||||
padding: '4px',
|
|
||||||
color: 'var(--color-text-3)',
|
|
||||||
fontSize: '16px',
|
|
||||||
lineHeight: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{Object.entries(groupedShortcuts).map(([category, shortcuts]) => (
|
|
||||||
<div key={category} style={{ marginBottom: '12px' }}>
|
|
||||||
<div style={{
|
|
||||||
padding: '4px 16px',
|
|
||||||
fontSize: '10px',
|
|
||||||
fontWeight: 600,
|
|
||||||
color: 'var(--color-text-3)',
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
letterSpacing: '0.5px',
|
|
||||||
}}>
|
|
||||||
{category}
|
|
||||||
</div>
|
|
||||||
{shortcuts.map((shortcut, idx) => (
|
|
||||||
<div
|
|
||||||
key={idx}
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
padding: '6px 16px',
|
|
||||||
fontSize: '13px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span style={{ color: 'var(--color-text)' }}>
|
|
||||||
{shortcut.name.replace('tool.', '').replace('action.', '')}
|
|
||||||
</span>
|
|
||||||
<kbd style={{
|
|
||||||
background: 'var(--color-muted-2)',
|
|
||||||
padding: '2px 6px',
|
|
||||||
borderRadius: '4px',
|
|
||||||
fontSize: '11px',
|
|
||||||
fontFamily: 'inherit',
|
|
||||||
color: 'var(--color-text-1)',
|
|
||||||
border: '1px solid var(--color-panel-contrast)',
|
|
||||||
}}>
|
|
||||||
{shortcut.kbd.toUpperCase()}
|
|
||||||
</kbd>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</>,
|
|
||||||
document.body
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Version Reversion Panel - Coming Soon */}
|
{/* Version Reversion Panel - Coming Soon */}
|
||||||
{/* TODO: Re-enable when version history backend is fully tested
|
{/* TODO: Re-enable when version history backend is fully tested
|
||||||
{showVersionHistory && createPortal(
|
{showVersionHistory && createPortal(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue