KeyboardShortcutsModal.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. import { useEffect } from 'react';
  2. import { X, Keyboard, ExternalLink } from 'lucide-react';
  3. import { useTranslation } from 'react-i18next';
  4. import { Card, CardContent } from './Card';
  5. interface NavItem {
  6. id: string;
  7. to: string;
  8. labelKey: string;
  9. }
  10. interface SidebarItem {
  11. type: 'nav' | 'external';
  12. label: string;
  13. labelKey?: string;
  14. }
  15. interface KeyboardShortcutsModalProps {
  16. onClose: () => void;
  17. navItems?: NavItem[];
  18. sidebarItems?: SidebarItem[];
  19. }
  20. function getShortcuts(
  21. sidebarItems: SidebarItem[] | undefined,
  22. navItems: NavItem[] | undefined,
  23. t: (key: string) => string
  24. ) {
  25. // Use sidebarItems if provided (new format), otherwise fall back to navItems
  26. const navShortcuts = sidebarItems
  27. ? sidebarItems.slice(0, 9).map((item, index) => ({
  28. keys: [String(index + 1)],
  29. description: item.type === 'external'
  30. ? `Open ${item.label}`
  31. : `Go to ${item.labelKey ? t(item.labelKey) : item.label}`,
  32. isExternal: item.type === 'external',
  33. }))
  34. : navItems
  35. ? navItems.map((item, index) => ({
  36. keys: [String(index + 1)],
  37. description: `Go to ${t(item.labelKey)}`,
  38. isExternal: false,
  39. }))
  40. : [
  41. { keys: ['1'], description: 'Go to Printers', isExternal: false },
  42. { keys: ['2'], description: 'Go to Archives', isExternal: false },
  43. { keys: ['3'], description: 'Go to Queue', isExternal: false },
  44. { keys: ['4'], description: 'Go to Statistics', isExternal: false },
  45. { keys: ['5'], description: 'Go to Cloud Profiles', isExternal: false },
  46. { keys: ['6'], description: 'Go to Settings', isExternal: false },
  47. ];
  48. return [
  49. { category: 'Navigation', items: navShortcuts },
  50. { category: 'Archives', items: [
  51. { keys: ['/'], description: 'Focus search', isExternal: false },
  52. { keys: ['U'], description: 'Open upload modal', isExternal: false },
  53. { keys: ['Esc'], description: 'Clear selection / blur input', isExternal: false },
  54. { keys: ['Right-click'], description: 'Context menu on cards', isExternal: false },
  55. ]},
  56. { category: 'K-Profiles', items: [
  57. { keys: ['R'], description: 'Refresh profiles', isExternal: false },
  58. { keys: ['N'], description: 'New profile', isExternal: false },
  59. { keys: ['Esc'], description: 'Exit selection mode', isExternal: false },
  60. ]},
  61. { category: 'General', items: [
  62. { keys: ['?'], description: 'Show this help', isExternal: false },
  63. ]},
  64. ];
  65. }
  66. function KeyBadge({ children }: { children: string }) {
  67. return (
  68. <kbd className="px-2 py-1 text-xs font-mono bg-bambu-dark border border-bambu-dark-tertiary rounded text-white">
  69. {children}
  70. </kbd>
  71. );
  72. }
  73. export function KeyboardShortcutsModal({ onClose, navItems, sidebarItems }: KeyboardShortcutsModalProps) {
  74. const { t } = useTranslation();
  75. const shortcuts = getShortcuts(sidebarItems, navItems, t);
  76. // Close on Escape key
  77. useEffect(() => {
  78. const handleKeyDown = (e: KeyboardEvent) => {
  79. if (e.key === 'Escape') onClose();
  80. };
  81. window.addEventListener('keydown', handleKeyDown);
  82. return () => window.removeEventListener('keydown', handleKeyDown);
  83. }, [onClose]);
  84. return (
  85. <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4" onClick={onClose}>
  86. <Card className="w-full max-w-md" onClick={(e) => e.stopPropagation()}>
  87. <CardContent className="p-0">
  88. {/* Header */}
  89. <div className="flex items-center justify-between p-4 border-b border-bambu-dark-tertiary">
  90. <div className="flex items-center gap-2">
  91. <Keyboard className="w-5 h-5 text-bambu-green" />
  92. <h2 className="text-xl font-semibold text-white">Keyboard Shortcuts</h2>
  93. </div>
  94. <button
  95. onClick={onClose}
  96. className="text-bambu-gray hover:text-white transition-colors"
  97. >
  98. <X className="w-5 h-5" />
  99. </button>
  100. </div>
  101. {/* Shortcuts List */}
  102. <div className="p-4 space-y-6 max-h-[60vh] overflow-y-auto">
  103. {shortcuts.map((section) => (
  104. <div key={section.category}>
  105. <h3 className="text-sm font-medium text-bambu-gray mb-3">{section.category}</h3>
  106. <div className="space-y-2">
  107. {section.items.map((shortcut) => (
  108. <div key={shortcut.description} className="flex items-center justify-between">
  109. <span className="text-white text-sm flex items-center gap-1.5">
  110. {shortcut.description}
  111. {shortcut.isExternal && (
  112. <ExternalLink className="w-3 h-3 text-bambu-gray" />
  113. )}
  114. </span>
  115. <div className="flex gap-1">
  116. {shortcut.keys.map((key) => (
  117. <KeyBadge key={key}>{key}</KeyBadge>
  118. ))}
  119. </div>
  120. </div>
  121. ))}
  122. </div>
  123. </div>
  124. ))}
  125. </div>
  126. {/* Footer */}
  127. <div className="p-4 border-t border-bambu-dark-tertiary">
  128. <p className="text-xs text-bambu-gray text-center">
  129. Press <KeyBadge>Esc</KeyBadge> or click outside to close
  130. </p>
  131. </div>
  132. </CardContent>
  133. </Card>
  134. </div>
  135. );
  136. }