-
-
Notifications
You must be signed in to change notification settings - Fork 53
navigation menu [poc] #1246
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
navigation menu [poc] #1246
Conversation
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
✨ Finishing Touches🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
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.
Actionable comments posted: 9
🧹 Nitpick comments (8)
src/components/ui/NavigationMenu/fragments/NavigationMenuTrigger.tsx (2)
11-15: Consider adding accessibility attributes for better screen reader support.The button should include ARIA attributes to indicate its role in the navigation menu and its expanded state. Based on the learning about verifying existing ARIA attributes, let me check if these are needed.
- <button onClick={handleTrigger} className={clsx(`${rootClass}-trigger`, className)}> + <button + onClick={handleTrigger} + className={clsx(`${rootClass}-trigger`, className)} + aria-haspopup="true" + aria-expanded={itemOpen} + >Note: You'll need to access
itemOpenfromNavigationMenuItemContextfor thearia-expandedattribute:- const {handleTrigger} = React.useContext(NavigationMenuItemContext); + const {handleTrigger, itemOpen} = React.useContext(NavigationMenuItemContext);
7-17: Consider using React.forwardRef for better composability.Adding forwardRef would allow parent components to access the button element directly if needed, which is a common pattern in UI libraries.
+export interface NavigationMenuTriggerProps { + children: React.ReactNode; + className?: string; +} -const NavigationMenuTrigger = ({children, className}:any) => { +const NavigationMenuTrigger = React.forwardRef<HTMLButtonElement, NavigationMenuTriggerProps>( + ({children, className}, ref) => { const {handleTrigger} = React.useContext(NavigationMenuItemContext); const {rootClass} = React.useContext(NavigationMenuRootContext); return ( <RovingFocusGroup.Item> - <button onClick={handleTrigger} className={clsx(`${rootClass}-trigger`, className)}> + <button ref={ref} onClick={handleTrigger} className={clsx(`${rootClass}-trigger`, className)}> {children} </button> </RovingFocusGroup.Item> ) -} +}); +NavigationMenuTrigger.displayName = 'NavigationMenuTrigger';src/components/ui/NavigationMenu/fragments/NavigationMenuRoot.tsx (2)
18-18: Remove unused destructured props parameter.The
...propsparameter is destructured but never used in the component.-const NavigationMenuRoot = ({children, value, defaultValue = '', onValueChange, customRootClass, className, ...props}:NavigationMenuRootProps) => { +const NavigationMenuRoot = ({children, value, defaultValue = '', onValueChange, customRootClass, className}:NavigationMenuRootProps) => {
26-36: Consider removing the unnecessary outer div wrapper.The outer div doesn't seem to serve any functional purpose and could be removed to simplify the component structure.
- return ( - <div > - <NavigationMenuRootContext.Provider value={{isOpen, setIsOpen, rootClass}} > - <RovingFocusGroup.Root> - <RovingFocusGroup.Group className={clsx(`${rootClass}-root`, className)}> - {children} - </RovingFocusGroup.Group> - </RovingFocusGroup.Root> - </NavigationMenuRootContext.Provider> - </div> - ) + return ( + <NavigationMenuRootContext.Provider value={{isOpen, setIsOpen, rootClass}} > + <RovingFocusGroup.Root> + <RovingFocusGroup.Group className={clsx(`${rootClass}-root`, className)}> + {children} + </RovingFocusGroup.Group> + </RovingFocusGroup.Root> + </NavigationMenuRootContext.Provider> + )src/components/ui/NavigationMenu/stories/NavigationMenu.stories.tsx (1)
10-33: Consider adding more story variants for enhanced testing.While the current story is good, consider adding additional stories to cover different states and configurations for better testing coverage.
Additional stories you might consider:
export const WithActiveStates = () => ( <SandboxEditor> <NavigationMenu.Root defaultValue="item1"> {/* existing menu structure */} </NavigationMenu.Root> </SandboxEditor> ); export const Controlled = () => { const [value, setValue] = React.useState(''); return ( <SandboxEditor> <NavigationMenu.Root value={value} onValueChange={setValue}> {/* existing menu structure */} </NavigationMenu.Root> </SandboxEditor> ); };styles/themes/components/navigation-menu.scss (3)
45-62: Provide a matching fade-out animation to avoid abrupt close
display: noneis immediately applied on close, so the menu snaps out without the smoothness you added for opening. Consider adata-state="closed"selector (matching Radix primitives) that runs a reverse animation and only setsdisplay:noneafter the animation ends (with JS oranimation-fill-mode: forwards).[performance] [UX]
45-62: Re-evaluatez-index: 100for global stacking context collisionsHard-coding
z-index: 100may still be lower than other pop-ups/tooltips already in the library and can create layering bugs. Either:
- Use an existing design-system token (e.g.
var(--rad-ui-z-layer-popover)) or- Centralise z-index values in a map and import here.
64-67: Prefix the keyframes to prevent name collisionsThe project already uses a
rad-ui-namespace for classes; doing the same for keyframes keeps the global timeline clean and avoids collisions with third-party CSS.-@keyframes rad-ui-navigation-menu-fade-in { +@keyframes rad-ui-navigation-menu--fade-in {Remember to update the reference above.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
src/components/ui/NavigationMenu/NavigationMenu.tsx(1 hunks)src/components/ui/NavigationMenu/contexts/NavigationMenuRootContext.tsx(1 hunks)src/components/ui/NavigationMenu/contexts/NavigationMenyItemContext.tsx(1 hunks)src/components/ui/NavigationMenu/fragments/NavigationMenuContent.tsx(1 hunks)src/components/ui/NavigationMenu/fragments/NavigationMenuItem.tsx(1 hunks)src/components/ui/NavigationMenu/fragments/NavigationMenuLink.tsx(1 hunks)src/components/ui/NavigationMenu/fragments/NavigationMenuRoot.tsx(1 hunks)src/components/ui/NavigationMenu/fragments/NavigationMenuTrigger.tsx(1 hunks)src/components/ui/NavigationMenu/stories/NavigationMenu.stories.tsx(1 hunks)styles/themes/components/navigation-menu.scss(1 hunks)styles/themes/default.scss(1 hunks)
🧠 Learnings (7)
📓 Common learnings
Learnt from: decipher-cs
PR: rad-ui/ui#417
File: src/components/ui/Dropdown/Dropdown.stories.tsx:43-50
Timestamp: 2024-12-12T08:34:33.079Z
Learning: Ensure to verify existing ARIA attributes in components before suggesting additions during code reviews, especially in the `Dropdown.Trigger` component in `src/components/ui/Dropdown/Dropdown.stories.tsx`.
src/components/ui/NavigationMenu/fragments/NavigationMenuRoot.tsx (3)
Learnt from: decipher-cs
PR: #417
File: src/components/ui/Dropdown/Dropdown.tsx:0-0
Timestamp: 2024-12-12T08:22:59.375Z
Learning: The Dropdown.Trigger component is customizable and needs to be used with Dropdown.Root.
Learnt from: kotAPI
PR: #1031
File: src/components/ui/Accordion/fragments/AccordionRoot.tsx:41-44
Timestamp: 2025-04-07T04:38:34.864Z
Learning: The Accordion component in rad-ui/ui supports both controlled and uncontrolled modes through props like value, defaultValue, and onValueChange. When implementing controlled components, remember to: 1) Initialize state from defaultValue, 2) Update internal state when value changes (controlled mode), 3) Call onValueChange callback, and 4) Prevent internal state updates when in controlled mode.
Learnt from: kotAPI
PR: #576
File: src/core/primitives/Toggle/index.tsx:15-22
Timestamp: 2024-11-24T06:43:42.194Z
Learning: In the TogglePrimitive component (src/core/primitives/Toggle/index.tsx), when the component becomes controlled, it's acceptable to not sync the internal isPressed state with the external pressed prop.
src/components/ui/NavigationMenu/contexts/NavigationMenyItemContext.tsx (1)
Learnt from: decipher-cs
PR: #417
File: src/components/ui/Dropdown/Dropdown.stories.tsx:43-50
Timestamp: 2024-12-12T08:34:33.079Z
Learning: Ensure to verify existing ARIA attributes in components before suggesting additions during code reviews, especially in the Dropdown.Trigger component in src/components/ui/Dropdown/Dropdown.stories.tsx.
src/components/ui/NavigationMenu/fragments/NavigationMenuTrigger.tsx (3)
Learnt from: decipher-cs
PR: #417
File: src/components/ui/Dropdown/Dropdown.tsx:0-0
Timestamp: 2024-12-12T08:22:59.375Z
Learning: The Dropdown.Trigger component is customizable and needs to be used with Dropdown.Root.
Learnt from: decipher-cs
PR: #417
File: src/components/ui/Dropdown/Dropdown.stories.tsx:43-50
Timestamp: 2024-12-12T08:34:33.079Z
Learning: Ensure to verify existing ARIA attributes in components before suggesting additions during code reviews, especially in the Dropdown.Trigger component in src/components/ui/Dropdown/Dropdown.stories.tsx.
Learnt from: GoldGroove06
PR: #1215
File: src/core/primitives/CheckboxGroup/fragments/CheckboxGroupPrimitiveTrigger.tsx:25-27
Timestamp: 2025-07-18T16:43:26.143Z
Learning: In the CheckboxGroupPrimitiveTrigger component (src/core/primitives/CheckboxGroup/fragments/CheckboxGroupPrimitiveTrigger.tsx), the component uses two separate useEffect hooks with different purposes: the first useEffect (lines 25-27) with empty dependency array [] is meant to set the initial state only once on mount by syncing with the group's checked values, while the second useEffect (lines 28-34) handles ongoing state updates by propagating local isChecked changes back to the group's checkedValues. This pattern prevents infinite loops while maintaining proper synchronization.
src/components/ui/NavigationMenu/stories/NavigationMenu.stories.tsx (2)
Learnt from: decipher-cs
PR: #417
File: src/components/ui/Dropdown/Dropdown.stories.tsx:43-50
Timestamp: 2024-12-12T08:34:33.079Z
Learning: Ensure to verify existing ARIA attributes in components before suggesting additions during code reviews, especially in the Dropdown.Trigger component in src/components/ui/Dropdown/Dropdown.stories.tsx.
Learnt from: kotAPI
PR: #484
File: src/components/ui/Kbd/stories/Kbd.stories.js:6-6
Timestamp: 2024-10-27T07:20:10.457Z
Learning: In the Storybook files, variations in the title prefixes like 'Components/', 'WIP/', 'Tools/', and 'Examples/' are intentional. These naming patterns are acceptable and should not be flagged as inconsistencies in future code reviews.
src/components/ui/NavigationMenu/fragments/NavigationMenuLink.tsx (1)
Learnt from: decipher-cs
PR: #417
File: src/components/ui/Dropdown/Dropdown.stories.tsx:43-50
Timestamp: 2024-12-12T08:34:33.079Z
Learning: Ensure to verify existing ARIA attributes in components before suggesting additions during code reviews, especially in the Dropdown.Trigger component in src/components/ui/Dropdown/Dropdown.stories.tsx.
src/components/ui/NavigationMenu/fragments/NavigationMenuItem.tsx (1)
Learnt from: decipher-cs
PR: #417
File: src/components/ui/Dropdown/Dropdown.stories.tsx:43-50
Timestamp: 2024-12-12T08:34:33.079Z
Learning: Ensure to verify existing ARIA attributes in components before suggesting additions during code reviews, especially in the Dropdown.Trigger component in src/components/ui/Dropdown/Dropdown.stories.tsx.
🧬 Code Graph Analysis (1)
src/components/ui/NavigationMenu/fragments/NavigationMenuRoot.tsx (1)
src/core/hooks/useControllableState/index.tsx (1)
useControllableState(15-78)
🪛 GitHub Check: lint
src/components/ui/NavigationMenu/fragments/NavigationMenuItem.tsx
[warning] 20-20:
Avoid non-native interactive elements. If using native HTML is not possible, add an appropriate role and support for tabbing, mouse, keyboard, and touch inputs to an interactive content element
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: decipher-cs
PR: rad-ui/ui#417
File: src/components/ui/Dropdown/Dropdown.stories.tsx:43-50
Timestamp: 2024-12-12T08:34:33.079Z
Learning: Ensure to verify existing ARIA attributes in components before suggesting additions during code reviews, especially in the `Dropdown.Trigger` component in `src/components/ui/Dropdown/Dropdown.stories.tsx`.
src/components/ui/NavigationMenu/fragments/NavigationMenuRoot.tsx (3)
Learnt from: decipher-cs
PR: #417
File: src/components/ui/Dropdown/Dropdown.tsx:0-0
Timestamp: 2024-12-12T08:22:59.375Z
Learning: The Dropdown.Trigger component is customizable and needs to be used with Dropdown.Root.
Learnt from: kotAPI
PR: #1031
File: src/components/ui/Accordion/fragments/AccordionRoot.tsx:41-44
Timestamp: 2025-04-07T04:38:34.864Z
Learning: The Accordion component in rad-ui/ui supports both controlled and uncontrolled modes through props like value, defaultValue, and onValueChange. When implementing controlled components, remember to: 1) Initialize state from defaultValue, 2) Update internal state when value changes (controlled mode), 3) Call onValueChange callback, and 4) Prevent internal state updates when in controlled mode.
Learnt from: kotAPI
PR: #576
File: src/core/primitives/Toggle/index.tsx:15-22
Timestamp: 2024-11-24T06:43:42.194Z
Learning: In the TogglePrimitive component (src/core/primitives/Toggle/index.tsx), when the component becomes controlled, it's acceptable to not sync the internal isPressed state with the external pressed prop.
src/components/ui/NavigationMenu/contexts/NavigationMenyItemContext.tsx (1)
Learnt from: decipher-cs
PR: #417
File: src/components/ui/Dropdown/Dropdown.stories.tsx:43-50
Timestamp: 2024-12-12T08:34:33.079Z
Learning: Ensure to verify existing ARIA attributes in components before suggesting additions during code reviews, especially in the Dropdown.Trigger component in src/components/ui/Dropdown/Dropdown.stories.tsx.
src/components/ui/NavigationMenu/fragments/NavigationMenuTrigger.tsx (3)
Learnt from: decipher-cs
PR: #417
File: src/components/ui/Dropdown/Dropdown.tsx:0-0
Timestamp: 2024-12-12T08:22:59.375Z
Learning: The Dropdown.Trigger component is customizable and needs to be used with Dropdown.Root.
Learnt from: decipher-cs
PR: #417
File: src/components/ui/Dropdown/Dropdown.stories.tsx:43-50
Timestamp: 2024-12-12T08:34:33.079Z
Learning: Ensure to verify existing ARIA attributes in components before suggesting additions during code reviews, especially in the Dropdown.Trigger component in src/components/ui/Dropdown/Dropdown.stories.tsx.
Learnt from: GoldGroove06
PR: #1215
File: src/core/primitives/CheckboxGroup/fragments/CheckboxGroupPrimitiveTrigger.tsx:25-27
Timestamp: 2025-07-18T16:43:26.143Z
Learning: In the CheckboxGroupPrimitiveTrigger component (src/core/primitives/CheckboxGroup/fragments/CheckboxGroupPrimitiveTrigger.tsx), the component uses two separate useEffect hooks with different purposes: the first useEffect (lines 25-27) with empty dependency array [] is meant to set the initial state only once on mount by syncing with the group's checked values, while the second useEffect (lines 28-34) handles ongoing state updates by propagating local isChecked changes back to the group's checkedValues. This pattern prevents infinite loops while maintaining proper synchronization.
src/components/ui/NavigationMenu/stories/NavigationMenu.stories.tsx (2)
Learnt from: decipher-cs
PR: #417
File: src/components/ui/Dropdown/Dropdown.stories.tsx:43-50
Timestamp: 2024-12-12T08:34:33.079Z
Learning: Ensure to verify existing ARIA attributes in components before suggesting additions during code reviews, especially in the Dropdown.Trigger component in src/components/ui/Dropdown/Dropdown.stories.tsx.
Learnt from: kotAPI
PR: #484
File: src/components/ui/Kbd/stories/Kbd.stories.js:6-6
Timestamp: 2024-10-27T07:20:10.457Z
Learning: In the Storybook files, variations in the title prefixes like 'Components/', 'WIP/', 'Tools/', and 'Examples/' are intentional. These naming patterns are acceptable and should not be flagged as inconsistencies in future code reviews.
src/components/ui/NavigationMenu/fragments/NavigationMenuLink.tsx (1)
Learnt from: decipher-cs
PR: #417
File: src/components/ui/Dropdown/Dropdown.stories.tsx:43-50
Timestamp: 2024-12-12T08:34:33.079Z
Learning: Ensure to verify existing ARIA attributes in components before suggesting additions during code reviews, especially in the Dropdown.Trigger component in src/components/ui/Dropdown/Dropdown.stories.tsx.
src/components/ui/NavigationMenu/fragments/NavigationMenuItem.tsx (1)
Learnt from: decipher-cs
PR: #417
File: src/components/ui/Dropdown/Dropdown.stories.tsx:43-50
Timestamp: 2024-12-12T08:34:33.079Z
Learning: Ensure to verify existing ARIA attributes in components before suggesting additions during code reviews, especially in the Dropdown.Trigger component in src/components/ui/Dropdown/Dropdown.stories.tsx.
🧬 Code Graph Analysis (1)
src/components/ui/NavigationMenu/fragments/NavigationMenuRoot.tsx (1)
src/core/hooks/useControllableState/index.tsx (1)
useControllableState(15-78)
🪛 GitHub Check: lint
src/components/ui/NavigationMenu/fragments/NavigationMenuItem.tsx
[warning] 20-20:
Avoid non-native interactive elements. If using native HTML is not possible, add an appropriate role and support for tabbing, mouse, keyboard, and touch inputs to an interactive content element
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (17)
src/components/ui/NavigationMenu/contexts/NavigationMenuRootContext.tsx (2)
3-7: LGTM! Well-structured context interface.The interface design is solid. Using
stringforisOpeninstead ofbooleanis a good choice as it likely identifies which specific menu item is open rather than just a binary state.
9-11: Context implementation follows React best practices.The context is properly initialized with a null default value and uses the union type with null, which ensures consumers must be wrapped by a provider.
styles/themes/default.scss (1)
40-40: LGTM! Import follows established pattern.The navigation-menu import is correctly added following the existing component import structure.
src/components/ui/NavigationMenu/fragments/NavigationMenuLink.tsx (2)
6-10: Props interface is well-defined.Clean and appropriate interface with required
hrefandchildren, plus optionalclassNamefor extensibility.
15-19: Excellent accessibility integration.Good use of RovingFocusGroup.Item for keyboard navigation support, which aligns with accessibility best practices for navigation menus.
src/components/ui/NavigationMenu/NavigationMenu.tsx (2)
1-6: Clean import organization.All fragment components are properly imported and the structure is organized.
8-15: Excellent compound component pattern.This follows the established React pattern for compound components, providing a clean API where related components are grouped under a single namespace (e.g.,
NavigationMenu.Root,NavigationMenu.Item).src/components/ui/NavigationMenu/contexts/NavigationMenyItemContext.tsx (2)
3-6: Well-defined interface for item context.The interface clearly defines the item-level state with
itemOpenboolean andhandleTriggerfunction, which is appropriate for individual menu item management.
8-11: Good default values for context.Unlike the root context, this one provides sensible defaults (
itemOpen: false, no-op function) which is appropriate since item context is more localized and the defaults won't cause runtime errors.src/components/ui/NavigationMenu/fragments/NavigationMenuRoot.tsx (2)
10-17: Well-structured TypeScript interface with comprehensive props.The interface properly covers all necessary props for controlled/uncontrolled state management, custom styling, and event handling.
18-24: Proper implementation of controlled/uncontrolled state pattern.The use of
useControllableStatecorrectly handles both controlled and uncontrolled modes, which aligns with the learning about controlled components supporting both patterns.src/components/ui/NavigationMenu/stories/NavigationMenu.stories.tsx (2)
5-8: Proper Storybook structure with appropriate title prefix.The story structure follows Storybook conventions correctly, and the "WIP/" prefix is acceptable based on the established patterns in this codebase.
10-33: Comprehensive demonstration of NavigationMenu usage patterns.The story effectively showcases different menu item types: dropdown triggers with content and direct links, providing good coverage of the component's functionality.
src/components/ui/NavigationMenu/fragments/NavigationMenuItem.tsx (2)
12-14: Clean toggle logic implementation.The handleTrigger function correctly toggles between the item's value and empty string for open/close state.
16-18: Proper keyboard accessibility with Escape key handling.The Escape key handler provides good keyboard navigation support for closing the menu.
src/components/ui/NavigationMenu/fragments/NavigationMenuContent.tsx (2)
20-20: Good conditional rendering pattern.The early return when
itemOpenis false is an efficient pattern for conditional rendering.
23-29: Proper integration with RovingFocusGroup for keyboard navigation.The component correctly wraps children in RovingFocusGroup components to enable proper keyboard navigation within the menu content.
| @@ -0,0 +1,13 @@ | |||
| import React from 'react'; | |||
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.
Fix filename typo.
The filename contains a typo: "NavigationMenyItemContext" should be "NavigationMenuItemContext". This could cause import confusion and should be corrected.
Rename the file from NavigationMenyItemContext.tsx to NavigationMenuItemContext.tsx.
🤖 Prompt for AI Agents
In src/components/ui/NavigationMenu/contexts/NavigationMenyItemContext.tsx at
line 1, the filename contains a typo "NavigationMenyItemContext" instead of
"NavigationMenuItemContext". Rename the file to "NavigationMenuItemContext.tsx"
to fix the typo and prevent import issues.
| import clsx from 'clsx'; | ||
|
|
||
|
|
||
| const NavigationMenuContent = ({children, className}:any) => { |
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.
🛠️ Refactor suggestion
Improve type safety by replacing any with proper TypeScript interface.
Define a proper interface for the component props instead of using any.
+export interface NavigationMenuContentProps {
+ children: React.ReactNode;
+ className?: string;
+}
-const NavigationMenuContent = ({children, className}:any) => {
+const NavigationMenuContent = ({children, className}: NavigationMenuContentProps) => {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const NavigationMenuContent = ({children, className}:any) => { | |
| export interface NavigationMenuContentProps { | |
| children: React.ReactNode; | |
| className?: string; | |
| } | |
| const NavigationMenuContent = ( | |
| { children, className }: NavigationMenuContentProps | |
| ) => { | |
| // ...rest of the implementation | |
| } |
🤖 Prompt for AI Agents
In src/components/ui/NavigationMenu/fragments/NavigationMenuContent.tsx at line
8, replace the use of 'any' for the component props with a properly defined
TypeScript interface that specifies the expected types for 'children' and
'className'. Define an interface above the component and use it to type the
props parameter to improve type safety.
| useEffect(() => { | ||
| if (!contentRef.current) return | ||
| if ( contentRef.current.children[0].children[0]) { | ||
| contentRef.current.children[0]?.children[0].firstChild.focus(); | ||
| } | ||
| }, [itemOpen]); |
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.
Fix unsafe focus management logic that could cause runtime errors.
The current focus logic uses unsafe property chaining and assumes a specific DOM structure, which could lead to runtime errors if the structure doesn't match expectations.
useEffect(() => {
if (!contentRef.current) return
- if ( contentRef.current.children[0].children[0]) {
- contentRef.current.children[0]?.children[0].firstChild.focus();
+ if (itemOpen) {
+ // Safely navigate to the first focusable element
+ const firstGroup = contentRef.current.children[0] as HTMLElement;
+ const firstItem = firstGroup?.children[0] as HTMLElement;
+ const firstFocusable = firstItem?.querySelector('button, a, [tabindex]:not([tabindex="-1"])') as HTMLElement;
+
+ if (firstFocusable && typeof firstFocusable.focus === 'function') {
+ firstFocusable.focus();
+ }
}
}, [itemOpen]);This approach:
- Only attempts focus when opening (itemOpen is true)
- Uses safer type casting
- Uses querySelector to find the first focusable element
- Includes proper null checks before calling focus()
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| useEffect(() => { | |
| if (!contentRef.current) return | |
| if ( contentRef.current.children[0].children[0]) { | |
| contentRef.current.children[0]?.children[0].firstChild.focus(); | |
| } | |
| }, [itemOpen]); | |
| useEffect(() => { | |
| if (!contentRef.current) return | |
| if (itemOpen) { | |
| // Safely navigate to the first focusable element | |
| const firstGroup = contentRef.current.children[0] as HTMLElement; | |
| const firstItem = firstGroup?.children[0] as HTMLElement; | |
| const firstFocusable = firstItem?.querySelector( | |
| 'button, a, [tabindex]:not([tabindex="-1"])' | |
| ) as HTMLElement; | |
| if (firstFocusable && typeof firstFocusable.focus === 'function') { | |
| firstFocusable.focus(); | |
| } | |
| } | |
| }, [itemOpen]); |
🤖 Prompt for AI Agents
In src/components/ui/NavigationMenu/fragments/NavigationMenuContent.tsx around
lines 13 to 18, the current focus logic directly accesses nested children
properties without sufficient null checks, risking runtime errors if the DOM
structure changes. Refactor the useEffect to only run when itemOpen is true, use
querySelector to safely find the first focusable element within
contentRef.current, and add proper null checks before calling focus() to ensure
safe focus management.
| import NavigationMenuRootContext from '../contexts/NavigationMenuRootContext'; | ||
| import clsx from 'clsx'; | ||
|
|
||
| const NavigationMenuItem = ({value, children, className}:any) => { |
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.
🛠️ Refactor suggestion
Improve type safety by replacing any with proper TypeScript interface.
Define a proper interface for the component props instead of using any.
+export interface NavigationMenuItemProps {
+ value: string;
+ children: React.ReactNode;
+ className?: string;
+}
-const NavigationMenuItem = ({value, children, className}:any) => {
+const NavigationMenuItem = ({value, children, className}: NavigationMenuItemProps) => {🤖 Prompt for AI Agents
In src/components/ui/NavigationMenu/fragments/NavigationMenuItem.tsx at line 6,
replace the use of `any` for the component props with a properly defined
TypeScript interface. Define an interface that explicitly types `value`,
`children`, and `className` according to their expected types, then use this
interface as the type annotation for the component props to improve type safety.
| } | ||
| return ( | ||
| <NavigationMenuItemContext.Provider value={{itemOpen, handleTrigger}} > | ||
| <div onMouseEnter={handleTrigger} onMouseLeave={handleTrigger} className={clsx(`${rootClass}-item`, className)} onKeyDown={handleEscape}> |
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.
🛠️ Refactor suggestion
Address accessibility concerns with the interactive div element.
The static analysis warning correctly identifies that using a div with mouse events creates accessibility issues. Consider using semantic HTML elements or adding proper ARIA attributes and keyboard support.
Based on the static analysis hint, here are two approaches to fix this:
Option 1: Use semantic HTML with proper role
- <div onMouseEnter={handleTrigger} onMouseLeave={handleTrigger} className={clsx(`${rootClass}-item`, className)} onKeyDown={handleEscape}>
+ <div
+ role="menuitem"
+ tabIndex={0}
+ onMouseEnter={handleTrigger}
+ onMouseLeave={handleTrigger}
+ className={clsx(`${rootClass}-item`, className)}
+ onKeyDown={handleEscape}
+ aria-expanded={itemOpen}
+ >Option 2: Consider using a more semantic approach
Since this is a navigation menu item, consider if the structure could be reorganized to use native interactive elements more effectively.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/components/ui/NavigationMenu/fragments/NavigationMenuItem.tsx at line 21,
the div element with onMouseEnter and onMouseLeave handlers lacks semantic
meaning and keyboard accessibility, causing accessibility issues. To fix this,
replace the div with a semantic interactive element like a button or an anchor
tag if appropriate, or add role="menuitem" and tabindex="0" to make it focusable
and operable via keyboard. Also, ensure keyboard event handlers are added to
support keyboard interactions such as Enter and Space keys to trigger the same
behavior as mouse events.
| } | ||
|
|
||
| const NavigationMenuLink = ({children, href, className}:NavigationMenuLinkProps) => { | ||
| const {rootClass} = React.useContext(NavigationMenuRootContext); |
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.
Add null check for context value.
The context is accessed without null checking, which could cause a runtime error if the component is used outside of a NavigationMenu.Root provider.
Apply this diff to add proper null checking:
- const {rootClass} = React.useContext(NavigationMenuRootContext);
+ const context = React.useContext(NavigationMenuRootContext);
+ if (!context) {
+ throw new Error('NavigationMenuLink must be used within NavigationMenu.Root');
+ }
+ const {rootClass} = context;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const {rootClass} = React.useContext(NavigationMenuRootContext); | |
| const context = React.useContext(NavigationMenuRootContext); | |
| if (!context) { | |
| throw new Error('NavigationMenuLink must be used within NavigationMenu.Root'); | |
| } | |
| const {rootClass} = context; |
🤖 Prompt for AI Agents
In src/components/ui/NavigationMenu/fragments/NavigationMenuLink.tsx at line 13,
the context value from NavigationMenuRootContext is accessed without checking if
it is null or undefined, which can cause runtime errors if used outside its
provider. Add a null check after retrieving rootClass from the context, and
handle the case where the context is not available, for example by throwing an
error or returning null early to prevent further execution.
| import NavigationMenuRootContext from '../contexts/NavigationMenuRootContext'; | ||
| import clsx from 'clsx'; | ||
|
|
||
| const NavigationMenuTrigger = ({children, className}:any) => { |
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.
🛠️ Refactor suggestion
Improve type safety by replacing any with proper TypeScript interface.
Define a proper interface for the component props instead of using any.
+export interface NavigationMenuTriggerProps {
+ children: React.ReactNode;
+ className?: string;
+}
-const NavigationMenuTrigger = ({children, className}:any) => {
+const NavigationMenuTrigger = ({children, className}: NavigationMenuTriggerProps) => {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const NavigationMenuTrigger = ({children, className}:any) => { | |
| export interface NavigationMenuTriggerProps { | |
| children: React.ReactNode; | |
| className?: string; | |
| } | |
| const NavigationMenuTrigger = ({children, className}: NavigationMenuTriggerProps) => { |
🤖 Prompt for AI Agents
In src/components/ui/NavigationMenu/fragments/NavigationMenuTrigger.tsx at line
7, replace the use of `any` for the component props with a properly defined
TypeScript interface that specifies the expected types for `children` and
`className`. Define an interface above the component and use it to type the
props parameter to improve type safety.
| .rad-ui-navigation-menu-trigger { | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| padding: 8px 16px; | ||
| background: var(--rad-ui-color-accent-100, #f3f4f6); | ||
| color: var(--rad-ui-color-accent-900, #111); | ||
| border: none; | ||
| border-radius: 4px; | ||
| font-size: 1rem; | ||
| cursor: pointer; | ||
| transition: background 0.2s, color 0.2s; | ||
|
|
||
| &:hover{ | ||
| background: var(--rad-ui-color-accent-200, #e5e7eb); | ||
| color: var(--rad-ui-color-accent-900, #111); | ||
| outline: none; | ||
| } | ||
|
|
||
| &[data-state="open"] { | ||
| background: var(--rad-ui-color-accent-300, #d1d5db); | ||
| color: var(--rad-ui-color-accent-900, #111); | ||
| } | ||
| } |
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.
🛠️ Refactor suggestion
Add explicit :focus-visible styles for keyboard accessibility
Triggers currently style :hover and the open state but omit any visual focus indicator. Removing the default outline without a replacement makes it impossible for keyboard users to see which trigger is focused.
&:hover{
background: var(--rad-ui-color-accent-200, #e5e7eb);
color: var(--rad-ui-color-accent-900, #111);
- outline: none;
+ outline: none;
}
+
+ &:focus-visible {
+ outline: 2px solid var(--rad-ui-color-accent-400, #9ca3af);
+ outline-offset: 2px;
+ }[accessibility]
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| .rad-ui-navigation-menu-trigger { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| padding: 8px 16px; | |
| background: var(--rad-ui-color-accent-100, #f3f4f6); | |
| color: var(--rad-ui-color-accent-900, #111); | |
| border: none; | |
| border-radius: 4px; | |
| font-size: 1rem; | |
| cursor: pointer; | |
| transition: background 0.2s, color 0.2s; | |
| &:hover{ | |
| background: var(--rad-ui-color-accent-200, #e5e7eb); | |
| color: var(--rad-ui-color-accent-900, #111); | |
| outline: none; | |
| } | |
| &[data-state="open"] { | |
| background: var(--rad-ui-color-accent-300, #d1d5db); | |
| color: var(--rad-ui-color-accent-900, #111); | |
| } | |
| } | |
| .rad-ui-navigation-menu-trigger { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| padding: 8px 16px; | |
| background: var(--rad-ui-color-accent-100, #f3f4f6); | |
| color: var(--rad-ui-color-accent-900, #111); | |
| border: none; | |
| border-radius: 4px; | |
| font-size: 1rem; | |
| cursor: pointer; | |
| transition: background 0.2s, color 0.2s; | |
| &:hover{ | |
| background: var(--rad-ui-color-accent-200, #e5e7eb); | |
| color: var(--rad-ui-color-accent-900, #111); | |
| outline: none; | |
| } | |
| &:focus-visible { | |
| outline: 2px solid var(--rad-ui-color-accent-400, #9ca3af); | |
| outline-offset: 2px; | |
| } | |
| &[data-state="open"] { | |
| background: var(--rad-ui-color-accent-300, #d1d5db); | |
| color: var(--rad-ui-color-accent-900, #111); | |
| } | |
| } |
🤖 Prompt for AI Agents
In styles/themes/components/navigation-menu.scss between lines 20 and 43, the
.rad-ui-navigation-menu-trigger class removes the default focus outline on hover
but does not provide any visible focus style for keyboard users. Add explicit
:focus-visible styles to this class to provide a clear visual indicator when the
trigger is focused via keyboard navigation, ensuring accessibility without
removing the outline entirely.
| .rad-ui-navigation-menu-link { | ||
| display: flex; | ||
| align-items: center; | ||
| padding: 8px 20px; | ||
| color: var(--rad-ui-color-accent-900, #111); | ||
| text-decoration: none; | ||
| font-size: 1rem; | ||
| border: none; | ||
| background: none; | ||
| cursor: pointer; | ||
| transition: background 0.18s, color 0.18s; | ||
| border-radius: 4px; | ||
|
|
||
| &:hover { | ||
| background: var(--rad-ui-color-accent-100, #f3f4f6); | ||
| color: var(--rad-ui-color-accent-900, #111); | ||
| outline: none; | ||
| } | ||
|
|
||
| &[aria-current="page"], | ||
| &[data-active="true"] { | ||
| background: var(--rad-ui-color-accent-200, #e5e7eb); | ||
| font-weight: 600; | ||
| } | ||
| } |
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.
🛠️ Refactor suggestion
Mirror the focus fix for links
As with the trigger, links lose their default outline. Add :focus-visible styling so keyboard navigation remains perceivable.
&:hover {
background: var(--rad-ui-color-accent-100, #f3f4f6);
color: var(--rad-ui-color-accent-900, #111);
- outline: none;
+ outline: none;
}
+
+ &:focus-visible {
+ outline: 2px solid var(--rad-ui-color-accent-300, #d1d5db);
+ outline-offset: 2px;
+ }[accessibility]
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| .rad-ui-navigation-menu-link { | |
| display: flex; | |
| align-items: center; | |
| padding: 8px 20px; | |
| color: var(--rad-ui-color-accent-900, #111); | |
| text-decoration: none; | |
| font-size: 1rem; | |
| border: none; | |
| background: none; | |
| cursor: pointer; | |
| transition: background 0.18s, color 0.18s; | |
| border-radius: 4px; | |
| &:hover { | |
| background: var(--rad-ui-color-accent-100, #f3f4f6); | |
| color: var(--rad-ui-color-accent-900, #111); | |
| outline: none; | |
| } | |
| &[aria-current="page"], | |
| &[data-active="true"] { | |
| background: var(--rad-ui-color-accent-200, #e5e7eb); | |
| font-weight: 600; | |
| } | |
| } | |
| .rad-ui-navigation-menu-link { | |
| display: flex; | |
| align-items: center; | |
| padding: 8px 20px; | |
| color: var(--rad-ui-color-accent-900, #111); | |
| text-decoration: none; | |
| font-size: 1rem; | |
| border: none; | |
| background: none; | |
| cursor: pointer; | |
| transition: background 0.18s, color 0.18s; | |
| border-radius: 4px; | |
| &:hover { | |
| background: var(--rad-ui-color-accent-100, #f3f4f6); | |
| color: var(--rad-ui-color-accent-900, #111); | |
| outline: none; | |
| } | |
| &:focus-visible { | |
| outline: 2px solid var(--rad-ui-color-accent-300, #d1d5db); | |
| outline-offset: 2px; | |
| } | |
| &[aria-current="page"], | |
| &[data-active="true"] { | |
| background: var(--rad-ui-color-accent-200, #e5e7eb); | |
| font-weight: 600; | |
| } | |
| } |
🤖 Prompt for AI Agents
In styles/themes/components/navigation-menu.scss between lines 69 and 93, the
.rad-ui-navigation-menu-link class removes the default outline on focus, which
harms keyboard navigation accessibility. Add a :focus-visible selector to this
class that restores a visible outline or focus indicator, ensuring keyboard
users can perceive when the link is focused without affecting mouse users.
Screen.Recording.2025-07-22.230051.mp4
Summary by CodeRabbit
New Features
Style