feat(folder-browser): keyboard nav, context menus, now-playing path UX

Adds full keyboard navigation (arrow keys, Enter, Ctrl+Enter for context menu) across Folder Browser columns. Context menus for all row types with keyboard-operable submenus and star-rating control via arrow keys + Enter. Now-playing path is visually emphasized and stays stable; rebuilds on hotkey re-invoke or active-path follow-along. Adaptive column layout for deep trees with right-side visibility priority. New configurable 'Open Folder Browser' keybinding. StarRating animation sync for keyboard-driven changes. All 7 locales updated.
This commit is contained in:
cucadmuh
2026-04-12 12:27:26 +03:00
committed by GitHub
parent 3d07a877f2
commit ae2e1bcb97
16 changed files with 1082 additions and 77 deletions
+35
View File
@@ -34,6 +34,8 @@ export default function StarRating({
const [clearShrinkStar, setClearShrinkStar] = React.useState<number | null>(null);
/** After clear: ignore hover so stars stay grey until pointer leaves widget or next click */
const [suppressHoverPreview, setSuppressHoverPreview] = React.useState(false);
const prevValueRef = React.useRef(value);
const internalClickRef = React.useRef(false);
const cappedValue = Math.min(Math.max(0, value), selectCap);
@@ -41,6 +43,38 @@ export default function StarRating({
if (value > 0) setSuppressHoverPreview(false);
}, [value]);
// Keep keyboard-driven changes visually in sync with mouse click effects.
React.useEffect(() => {
const prev = prevValueRef.current;
const next = value;
prevValueRef.current = value;
if (internalClickRef.current) {
internalClickRef.current = false;
return;
}
if (prev === next) return;
setPulseStar(null);
setClearShrinkStar(null);
if (next > prev) {
const star = Math.max(1, Math.min(selectCap, next));
requestAnimationFrame(() => {
requestAnimationFrame(() => setPulseStar(star));
});
return;
}
if (next < prev) {
const star = Math.max(1, Math.min(selectCap, prev));
if (next === 0) setSuppressHoverPreview(true);
requestAnimationFrame(() => {
requestAnimationFrame(() => setClearShrinkStar(star));
});
}
}, [value, selectCap]);
const effectiveHover = suppressHoverPreview ? 0 : Math.min(hover, selectCap);
const filled = (n: number) => (effectiveHover || cappedValue) >= n;
@@ -49,6 +83,7 @@ export default function StarRating({
setSuppressHoverPreview(false);
const next = cappedValue === n ? 0 : n;
internalClickRef.current = true;
onChange(next);
setHover(0);