-
Notifications
You must be signed in to change notification settings - Fork 0
Navigation component #60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
| :::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. | ||
| ::: |
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
| if (!areas.has(area)) { | ||
| console.warn(`Area "${area}" not registered. Available areas:`, Array.from(areas)); | ||
| return; | ||
| } |
There was a problem hiding this comment.
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"; |
There was a problem hiding this comment.
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
There was a problem hiding this 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)); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this 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/EventBusto@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')}}> |
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
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.
| <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'}}> |
| | 'left-sholder' | ||
| | 'right-sholder' | ||
| | 'left-sholder-bottom' | ||
| | 'right-sholder-bottom' |
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
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.
| | 'right-sholder-bottom' | |
| | 'right-shoulder-bottom' |
|
|
||
| const App = () => { | ||
| const defaultActions: ActionMap = { | ||
| 'tab-left': {key: {binds: ['Q'], type: ['press']}, button: {binds: ['left-shoulder'], type: 'press'}, callback: menuLeft, global: true}, |
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
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.
|
|
||
| props.componentClasses = () => stepperClasses(); | ||
| const { className, inlineStyles, forwardEvents, forwardAttrs } = useBaseComponent(props); | ||
| const { className, inlineStyles, forwardEvents, forwardAttrs, navigationActions } = useBaseComponent(props); |
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused variable navigationActions.
| const { className, inlineStyles, forwardEvents, forwardAttrs, navigationActions } = useBaseComponent(props); | |
| const { className, inlineStyles, forwardEvents, forwardAttrs } = useBaseComponent(props); |
| // @ts-ignore | ||
| import { gamepad } from 'coherent-gameface-interaction-manager'; | ||
| import NavigationArea from "./NavigationArea"; | ||
| import eventBus from "@components/Utility/EventBus"; |
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused import eventBus.
| import eventBus from "@components/Utility/EventBus"; |
| @@ -0,0 +1,48 @@ | |||
| import { children, createEffect, on, onCleanup, onMount, ParentComponent, useContext } from "solid-js" | |||
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused import useContext.
| import { children, createEffect, on, onCleanup, onMount, ParentComponent, useContext } from "solid-js" | |
| import { children, createEffect, on, onCleanup, onMount, ParentComponent } from "solid-js" |
| @@ -0,0 +1,48 @@ | |||
| import { children, createEffect, on, onCleanup, onMount, ParentComponent, useContext } from "solid-js" | |||
| import { NavigationContext, useNavigation } from "./Navigation"; | |||
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused import NavigationContext.
| import { NavigationContext, useNavigation } from "./Navigation"; | |
| import { useNavigation } from "./Navigation"; |
| 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"; |
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused import eventBus.
| import eventBus from "@components/Utility/EventBus"; |
* 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
b25550c to
e75acae
Compare
I made a minimal integration of the Navigation component inside the Menu UI for easier testing. You can: