feat(admin): Control Center redesign, Explore dual-view и полный RU/EN i18n
Refs EventHub/EventHubFrontAdmin#32
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
/**
|
||||
* Горячие клавиши inbox (когда фокус не в input/textarea/select/contenteditable).
|
||||
* j/k — соседний элемент, 1/2 — действия, Esc — снять фокус списка (опционально).
|
||||
*/
|
||||
export function useInboxHotkeys(options: {
|
||||
enabled?: boolean;
|
||||
onNext: () => void;
|
||||
onPrev: () => void;
|
||||
onPrimary?: () => void;
|
||||
onSecondary?: () => void;
|
||||
onOpen?: () => void;
|
||||
}) {
|
||||
const { enabled = true, onNext, onPrev, onPrimary, onSecondary, onOpen } = options;
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled) return;
|
||||
|
||||
const isTypingTarget = (el: EventTarget | null) => {
|
||||
if (!(el instanceof HTMLElement)) return false;
|
||||
const tag = el.tagName;
|
||||
return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || el.isContentEditable;
|
||||
};
|
||||
|
||||
const handler = (e: KeyboardEvent) => {
|
||||
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
||||
if (isTypingTarget(e.target)) return;
|
||||
|
||||
const key = e.key.toLowerCase();
|
||||
if (key === 'j' || key === 'arrowdown') {
|
||||
e.preventDefault();
|
||||
onNext();
|
||||
} else if (key === 'k' || key === 'arrowup') {
|
||||
e.preventDefault();
|
||||
onPrev();
|
||||
} else if (key === 'enter' && onOpen) {
|
||||
e.preventDefault();
|
||||
onOpen();
|
||||
} else if (key === '1' && onPrimary) {
|
||||
e.preventDefault();
|
||||
onPrimary();
|
||||
} else if (key === '2' && onSecondary) {
|
||||
e.preventDefault();
|
||||
onSecondary();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handler);
|
||||
return () => window.removeEventListener('keydown', handler);
|
||||
}, [enabled, onNext, onPrev, onPrimary, onSecondary, onOpen]);
|
||||
}
|
||||
|
||||
export function selectNeighborId(
|
||||
ids: string[],
|
||||
currentId: string,
|
||||
direction: 1 | -1
|
||||
): string | null {
|
||||
if (ids.length === 0) return null;
|
||||
const idx = ids.indexOf(currentId);
|
||||
if (idx < 0) return ids[0] ?? null;
|
||||
const next = idx + direction;
|
||||
if (next < 0 || next >= ids.length) return currentId;
|
||||
return ids[next];
|
||||
}
|
||||
|
||||
/** Следующий после текущего; если конец — предыдущий. */
|
||||
export function selectAfterRemove(ids: string[], currentId: string): string | null {
|
||||
const idx = ids.indexOf(currentId);
|
||||
if (idx < 0) return ids[0] ?? null;
|
||||
return ids[idx + 1] ?? ids[idx - 1] ?? null;
|
||||
}
|
||||
Reference in New Issue
Block a user