Skip to content

Conversation

@MartinBozhilov-coh
Copy link
Contributor

I made a minimal integration of the Navigation component inside the Menu UI for easier testing. You can:

  • Navigate between the gameplay and graphics tabs with Q and E
  • Navigate the menu item components with arrow buttons (and gamepad d-pad)
  • Press left and right arrow when the menu item with the stepper is focused to switch its options

Comment on lines 80 to 96
:::caution[Reserved Action Names]
Avoid creating custom actions with names starting with `move-focus-*` (e.g., `move-focus-left`, `move-focus-right`, etc.). These names are reserved and used internally by the spatial navigation system for managing focus between UI elements.
:::
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we outline all the actions that are reserved? For example we can describe them in the IM documentation and here we can leave a link to them.


**With an anchor element:**

Sometimes you want a component to respond to actions when a different element is focused (like a child button):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add more descriptive example - for example how to propagate action when a row with a dropdown is focused. The dropdown should receive the actions from the row via the anchor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a new section with more in-depth explanation

Comment on lines 68 to 71
if (!areas.has(area)) {
console.warn(`Area "${area}" not registered. Available areas:`, Array.from(areas));
return;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can move this into method (e.g isAreaValid) and reuse it.

 const isAreaValid = (area: string) => {
        if (!areas.has(area)) {
            console.warn(`Area "${area}" not registered. Available areas:`, Array.from(areas));
            return false;
        }

        return true;
 }
 const focusFirst = (area: string) => {
        if (!isAreaValid(area)) return;

import { keybindPresetContent } from "@custom-components/Menu/SidePanel/keybindsPanelContent";
import KeyBind from "@custom-components/Menu/KeyBind/KeyBind";
import eventBus from "@components/tools/EventBus";
import eventBus from "@components/Utility/EventBus";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some import statements here are not used. we can remove them

Copy link
Collaborator

@alien1976 alien1976 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems that stepper is not working when you navigate to graphics and then return to gameplay.

Press E -> navigates to Graphics
Press Q -> Navigates back to gameplay
Press Arrow Left/Right -> should change the stepper value but it doesn't

let anchorElement: HTMLElement | null = null;
if (anchor) {
if (typeof anchor === 'string') {
waitForFrames(() => anchorElement = document.querySelector(anchor));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we wait for frames here?

Copy link
Contributor Author

@MartinBozhilov-coh MartinBozhilov-coh Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometimes some elements can't be found.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a comprehensive Navigation component that provides keyboard and gamepad input handling for the Menu UI. The integration enables tab switching between Gameplay and Graphics sections using Q/E keys, menu item navigation with arrow keys and D-pad, and stepper control via left/right inputs. The implementation is built on top of the Coherent Gameface Interaction Manager and includes spatial navigation, a flexible action mapping system, and context-aware scope management.

  • Adds new Navigation component with action and area management systems
  • Integrates Navigation into the Menu UI with keyboard (Q/E, arrows) and gamepad (D-pad, shoulder buttons) controls
  • Updates EventBus import paths from @components/tools/EventBus to @components/Utility/EventBus

Reviewed changes

Copilot reviewed 21 out of 24 changed files in this pull request and generated 23 comments.

Show a summary per file
File Description
src/views/menu/Menu.tsx Wraps menu in Navigation component, adds default actions for tab switching and test buttons for development
src/views/menu/util/index.ts Updates EventBus import path to new Utility directory
src/custom-components/Menu/Options/Graphics/Graphics.tsx Wraps content in Navigation.Area for spatial navigation
src/custom-components/Menu/Options/Gameplay/Gameplay.tsx Wraps content in Navigation.Area, adds navigation actions to Stepper, changes subtitle default to visible
src/custom-components/Menu/Options/Audio/Audio.tsx Updates EventBus import path
src/custom-components/Menu/MenuItem/MenuItem.tsx Adds focus handling and menu-item class for navigation targeting
src/components/types/ComponentProps.d.ts Adds navigation-actions prop and anchor support for components
src/components/Utility/Navigation/types.ts Defines action configuration types and navigation config interface
src/components/Utility/Navigation/keybindings/keybindings.types.ts Defines keyboard and gamepad button binding types
src/components/Utility/Navigation/defaults.ts Configures default navigation actions (move, select, back)
src/components/Utility/Navigation/areaMethods/useAreaMethods.ts Implements area registration, focus management, and pause/resume functionality
src/components/Utility/Navigation/areaMethods/areaMethods.types.ts Defines interface for area-related navigation methods
src/components/Utility/Navigation/actionMethods/useActionMethods.ts Implements action registration, execution, and state management
src/components/Utility/Navigation/actionMethods/actionMethods.types.ts Defines interface for action-related navigation methods
src/components/Utility/Navigation/NavigationArea.tsx Subcomponent for defining navigable UI areas with automatic cleanup
src/components/Utility/Navigation/Navigation.tsx Main Navigation component providing context and API for navigation system
src/components/Utility/EventBus/index.ts Adds line numbers to EventBus (no functional changes)
src/components/Basic/Stepper/Stepper.tsx Integrates navigationActions directive for move-left/move-right support
src/components/BaseComponent/BaseComponent.tsx Adds navigationActions directive for subscribing components to navigation actions
package.json Adds coherent-gameface-interaction-manager dependency (v2.4.4)
docs/src/content/docs/components/index.mdx Adds Utility category to component documentation
docs/src/content/docs/components/Utility/Navigation.mdx Comprehensive documentation for Navigation component and action system
docs/src/content/docs/components/Basic/stepper.mdx Documents Stepper's navigation action support
docs/src/assets/components/utility/navigation.svg Adds Navigation component icon

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

</Stepper>
<Navigation.Area name="menu" selector="menu-item" focused>
<MenuItem id="difficulty" name='Difficulty'>
<Stepper anchor=".menu-item" onChange={emitChange} style={{width: '15vmax'}} navigation-actions={{select: () => console.log('custom select implementation')}}>
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The console.log statement 'custom select implementation' appears to be debug/placeholder code. Consider removing this or implementing the actual custom select behavior for the difficulty stepper.

Suggested change
<Stepper anchor=".menu-item" onChange={emitChange} style={{width: '15vmax'}} navigation-actions={{select: () => console.log('custom select implementation')}}>
<Stepper anchor=".menu-item" onChange={emitChange} style={{width: '15vmax'}}>

Copilot uses AI. Check for mistakes.
| 'left-sholder'
| 'right-sholder'
| 'left-sholder-bottom'
| 'right-sholder-bottom'
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The gamepad button binding 'right-sholder-bottom' is misspelled. It should be 'right-shoulder-bottom' to match the correct spelling.

Suggested change
| 'right-sholder-bottom'
| 'right-shoulder-bottom'

Copilot uses AI. Check for mistakes.

const App = () => {
const defaultActions: ActionMap = {
'tab-left': {key: {binds: ['Q'], type: ['press']}, button: {binds: ['left-shoulder'], type: 'press'}, callback: menuLeft, global: true},
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The word 'sholder' in the comment is misspelled. It should be 'shoulder' to be consistent with the corrected gamepad button names.

Copilot uses AI. Check for mistakes.

props.componentClasses = () => stepperClasses();
const { className, inlineStyles, forwardEvents, forwardAttrs } = useBaseComponent(props);
const { className, inlineStyles, forwardEvents, forwardAttrs, navigationActions } = useBaseComponent(props);
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused variable navigationActions.

Suggested change
const { className, inlineStyles, forwardEvents, forwardAttrs, navigationActions } = useBaseComponent(props);
const { className, inlineStyles, forwardEvents, forwardAttrs } = useBaseComponent(props);

Copilot uses AI. Check for mistakes.
// @ts-ignore
import { gamepad } from 'coherent-gameface-interaction-manager';
import NavigationArea from "./NavigationArea";
import eventBus from "@components/Utility/EventBus";
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import eventBus.

Suggested change
import eventBus from "@components/Utility/EventBus";

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,48 @@
import { children, createEffect, on, onCleanup, onMount, ParentComponent, useContext } from "solid-js"
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import useContext.

Suggested change
import { children, createEffect, on, onCleanup, onMount, ParentComponent, useContext } from "solid-js"
import { children, createEffect, on, onCleanup, onMount, ParentComponent } from "solid-js"

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,48 @@
import { children, createEffect, on, onCleanup, onMount, ParentComponent, useContext } from "solid-js"
import { NavigationContext, useNavigation } from "./Navigation";
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import NavigationContext.

Suggested change
import { NavigationContext, useNavigation } from "./Navigation";
import { useNavigation } from "./Navigation";

Copilot uses AI. Check for mistakes.
import { keybindPresetContent } from "@custom-components/Menu/SidePanel/keybindsPanelContent";
import KeyBind from "@custom-components/Menu/KeyBind/KeyBind";
import eventBus from "@components/tools/EventBus";
import eventBus from "@components/Utility/EventBus";
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import eventBus.

Suggested change
import eventBus from "@components/Utility/EventBus";

Copilot uses AI. Check for mistakes.
* Extend button, toggleButton and checkbox and add docs

* Extend sliders to use navigation

* Extend tooltip to implement actions

* Extend dropdown with navigation actions

* handle dropdown nav edge cases

* Add nav actions to inputs and add reusable input wrapper component

* Refactor Navigation pause/resume to avoid deinit cycles

* Extend Accordion to use navigation actions

* Fix anchor and action type inconsistencies across components

* Fix IM types after ts migration

* resolve pr comments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants