feat(navigation): add dropdown menu component with keyboard nav and animations#446
feat(navigation): add dropdown menu component with keyboard nav and animations#446KeenIsHere wants to merge 4 commits intocodse:mainfrom
Conversation
📝 WalkthroughWalkthroughAdds a client-side DropdownMenu React component with keyboard navigation, click-outside handling, ARIA roles, framer-motion entry/exit animations (respects prefers-reduced-motion), plus Storybook stories and an MDX documentation page describing usage, props, and accessibility. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant Trigger as "Trigger Button"
participant Menu as "DropdownMenu"
participant Motion as "Framer Motion"
participant Listeners as "Global Listeners"
User->>Trigger: click
Trigger->>Menu: toggle open
Menu->>Motion: start entry animation
Menu->>Listeners: register keydown & mousedown
Note over User,Menu: Menu open, items rendered
User->>Listeners: press ArrowDown
Listeners->>Menu: update selectedIndex
Menu->>Menu: highlight item
User->>Listeners: press Enter
Listeners->>Menu: invoke selected item's onClick
Menu->>Motion: start exit animation
Motion->>Menu: animation complete
Menu->>Listeners: unregister listeners
Menu->>Trigger: return focus & close
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (4)
animata/navigation/dropdown-menu.stories.tsx (1)
32-37: Deduplicate the shared menu items array.
PrimaryandRightAlignuse identicalitems. Consider hoisting to a module-level constant so the stories only diverge by thealign/triggerLabelthey exercise.♻️ Suggested refactor
+const sampleItems = [ + { label: "Profile", icon: <User className="h-4 w-4" /> }, + { label: "Settings", icon: <Settings className="h-4 w-4" /> }, + { label: "Help", icon: <HelpCircle className="h-4 w-4" /> }, + { label: "Sign Out", icon: <LogOut className="h-4 w-4" /> }, +]; + export const Primary: Story = { args: { triggerLabel: "Options", align: "left", - items: [ - { label: "Profile", icon: <User className="h-4 w-4" /> }, - { label: "Settings", icon: <Settings className="h-4 w-4" /> }, - { label: "Help", icon: <HelpCircle className="h-4 w-4" /> }, - { label: "Sign Out", icon: <LogOut className="h-4 w-4" /> }, - ], + items: sampleItems, }, ... }; export const RightAlign: Story = { args: { triggerLabel: "Menu", align: "right", - items: [ - { label: "Profile", icon: <User className="h-4 w-4" /> }, - { label: "Settings", icon: <Settings className="h-4 w-4" /> }, - { label: "Help", icon: <HelpCircle className="h-4 w-4" /> }, - { label: "Sign Out", icon: <LogOut className="h-4 w-4" /> }, - ], + items: sampleItems, }, ... };Also applies to: 50-55
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@animata/navigation/dropdown-menu.stories.tsx` around lines 32 - 37, Hoist the repeated menu items array into a module-level constant (e.g., const MENU_ITEMS) and have the story exports reuse it instead of duplicating the inline array; update the Primary and RightAlign story definitions to reference MENU_ITEMS and only set the differing props (align, triggerLabel) so the only differences between Primary and RightAlign are the props they pass while the shared items come from MENU_ITEMS.content/docs/navigation/dropdown-menu.mdx (1)
17-21: Document theMenuItemshape used byitems.The props table references
MenuItem[]but doesn't define what aMenuItemlooks like. Since the component exportsMenuItem({ label: string; icon?: React.ReactNode; onClick?: () => void }), consumers benefit from seeing the shape and a small usage example withicon/onClick.📝 Suggested addition
## Props | Prop | Type | Default | Description | |---|---|---|---| | items | MenuItem[] | [] | Array of menu items | | triggerLabel | string | "Options" | Button label | | align | "left" \| "right" | "left" | Dropdown alignment | + +### MenuItem + +| Field | Type | Required | Description | +|---|---|---|---| +| label | string | yes | Visible label of the item | +| icon | React.ReactNode | no | Optional leading icon | +| onClick | () => void | no | Invoked when the item is selected |🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@content/docs/navigation/dropdown-menu.mdx` around lines 17 - 21, Add documentation for the MenuItem shape used by the items prop: describe the exported MenuItem type ({ label: string; icon?: React.ReactNode; onClick?: () => void }) and show a concise usage example demonstrating an items array with one item that includes an icon and an onClick handler; reference the items prop and triggerLabel in the example so consumers see how to pass MenuItem[] into the dropdown component and how icon/onClick behave.animata/navigation/dropdown-menu.tsx (2)
35-39:prefersReducedMotionin a ref won't react to user preference changes and is fragile on first open.Storing the result in a
useRefplus auseEffectmeans:
- The ref update doesn't trigger a re-render. It only "happens to work" because the user must click the trigger (which causes a re-render) before the menu animates — relying on this ordering is brittle.
- If the user toggles their OS-level reduced-motion preference while the page is mounted, the component never picks it up.
Prefer
useStateplus achangelistener on the media query.♻️ Suggested refactor
- const prefersReducedMotion = useRef(false); - - useEffect(() => { - prefersReducedMotion.current = window.matchMedia("(prefers-reduced-motion: reduce)").matches; - }, []); + const [prefersReducedMotion, setPrefersReducedMotion] = useState(false); + + useEffect(() => { + const mql = window.matchMedia("(prefers-reduced-motion: reduce)"); + setPrefersReducedMotion(mql.matches); + const onChange = (e: MediaQueryListEvent) => setPrefersReducedMotion(e.matches); + mql.addEventListener("change", onChange); + return () => mql.removeEventListener("change", onChange); + }, []); @@ - const animationProps = prefersReducedMotion.current + const animationProps = prefersReducedMotion ? {} : { initial: { opacity: 0, translateY: -8 }, animate: { opacity: 1, translateY: 0 }, exit: { opacity: 0, translateY: -8 }, };Also applies to: 87-93
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@animata/navigation/dropdown-menu.tsx` around lines 35 - 39, The prefersReducedMotion currently stored in a useRef and set once inside useEffect is brittle and won't re-render or respond to OS preference changes; replace the useRef with useState (prefersReducedMotion state) and in useEffect create a media query via window.matchMedia("(prefers-reduced-motion: reduce)"), set the initial state from mq.matches, add an mq.addEventListener('change', handler) or mq.addListener fallback to update state on changes, and clean up the listener on unmount; update any logic referencing prefersReducedMotion to use the state value (references: prefersReducedMotion, useEffect, useRef) and apply the same refactor to the other occurrence around lines 87-93.
41-67: Avoid re-binding the globalkeydownlistener on every selection change.The effect lists
selectedIndex(anditems) as dependencies, so it tears down and re-attaches thewindowlistener on every arrow press — and on every render of the parent that passes a freshitemsarray. Reading the latest values from refs keeps the listener stable.♻️ Suggested refactor
+ const selectedIndexRef = useRef(0); + const itemsRef = useRef(items); + useEffect(() => { selectedIndexRef.current = selectedIndex; }, [selectedIndex]); + useEffect(() => { itemsRef.current = items; }, [items]); + useEffect(() => { if (!isOpen) { setSelectedIndex(0); return; } const handleKeyDown = (e: KeyboardEvent) => { + const currentItems = itemsRef.current; if (e.key === "ArrowDown") { e.preventDefault(); - setSelectedIndex((prev) => (prev + 1) % items.length); + setSelectedIndex((prev) => (prev + 1) % currentItems.length); } else if (e.key === "ArrowUp") { e.preventDefault(); - setSelectedIndex((prev) => (prev - 1 + items.length) % items.length); + setSelectedIndex((prev) => (prev - 1 + currentItems.length) % currentItems.length); } else if (e.key === "Enter") { e.preventDefault(); - items[selectedIndex]?.onClick?.(); + currentItems[selectedIndexRef.current]?.onClick?.(); setIsOpen(false); } else if (e.key === "Escape") { e.preventDefault(); setIsOpen(false); triggerRef.current?.focus(); } }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); - }, [isOpen, selectedIndex, items]); + }, [isOpen]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@animata/navigation/dropdown-menu.tsx` around lines 41 - 67, The effect is re-attaching the global keydown listener whenever selectedIndex or items change; instead keep a stable handler by reading mutable refs inside it: create refs like selectedIndexRef and itemsRef, update those refs whenever setSelectedIndex or items change, and have handleKeyDown read from selectedIndexRef.current and itemsRef.current and call itemsRef.current[selectedIndexRef.current]?.onClick; then make the useEffect that adds/removes window.addEventListener only depend on isOpen (and triggerRef/setIsOpen setters if needed) so the listener is not rebound on every selection or on fresh items arrays.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@animata/navigation/dropdown-menu.tsx`:
- Around line 97-111: The dropdown's button elements (e.g., the trigger with
ref={triggerRef} that toggles setIsOpen and the item buttons later in this file)
lack an explicit type and will default to type="submit" inside forms; update
every <button> in this component to include type="button" so clicking the
trigger or menu items won't submit a surrounding form (add type="button" to the
trigger button using triggerRef and to each menu/item button instance).
- Around line 95-111: Generate a stable menu id (e.g., with React's useId) and
set it as the id on the menu container (the motion.div rendered by the dropdown)
and also set aria-controls={menuId} on the trigger button (triggerRef block that
calls setIsOpen). Give each menu item an id like `${menuId}-item-${index}` and
update the menu container to expose the current selection via
aria-activedescendant using the selectedIndex state so screen readers announce
the highlighted item. In the dropdown keydown handler (the function that updates
selectedIndex on Arrow keys), handle Tab by closing the menu (call
setIsOpen(false)) or trapping focus (preventDefault and manage focus), and
ensure aria-expanded remains correct on the trigger; update references to
selectedIndex, triggerRef, setIsOpen, and the motion.div/menuId accordingly.
---
Nitpick comments:
In `@animata/navigation/dropdown-menu.stories.tsx`:
- Around line 32-37: Hoist the repeated menu items array into a module-level
constant (e.g., const MENU_ITEMS) and have the story exports reuse it instead of
duplicating the inline array; update the Primary and RightAlign story
definitions to reference MENU_ITEMS and only set the differing props (align,
triggerLabel) so the only differences between Primary and RightAlign are the
props they pass while the shared items come from MENU_ITEMS.
In `@animata/navigation/dropdown-menu.tsx`:
- Around line 35-39: The prefersReducedMotion currently stored in a useRef and
set once inside useEffect is brittle and won't re-render or respond to OS
preference changes; replace the useRef with useState (prefersReducedMotion
state) and in useEffect create a media query via
window.matchMedia("(prefers-reduced-motion: reduce)"), set the initial state
from mq.matches, add an mq.addEventListener('change', handler) or mq.addListener
fallback to update state on changes, and clean up the listener on unmount;
update any logic referencing prefersReducedMotion to use the state value
(references: prefersReducedMotion, useEffect, useRef) and apply the same
refactor to the other occurrence around lines 87-93.
- Around line 41-67: The effect is re-attaching the global keydown listener
whenever selectedIndex or items change; instead keep a stable handler by reading
mutable refs inside it: create refs like selectedIndexRef and itemsRef, update
those refs whenever setSelectedIndex or items change, and have handleKeyDown
read from selectedIndexRef.current and itemsRef.current and call
itemsRef.current[selectedIndexRef.current]?.onClick; then make the useEffect
that adds/removes window.addEventListener only depend on isOpen (and
triggerRef/setIsOpen setters if needed) so the listener is not rebound on every
selection or on fresh items arrays.
In `@content/docs/navigation/dropdown-menu.mdx`:
- Around line 17-21: Add documentation for the MenuItem shape used by the items
prop: describe the exported MenuItem type ({ label: string; icon?:
React.ReactNode; onClick?: () => void }) and show a concise usage example
demonstrating an items array with one item that includes an icon and an onClick
handler; reference the items prop and triggerLabel in the example so consumers
see how to pass MenuItem[] into the dropdown component and how icon/onClick
behave.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: cd67eb7a-aa6a-4a39-958d-c51b4408fcf7
📒 Files selected for processing (3)
animata/navigation/dropdown-menu.stories.tsxanimata/navigation/dropdown-menu.tsxcontent/docs/navigation/dropdown-menu.mdx
| return ( | ||
| <div className="relative inline-block"> | ||
| <button | ||
| ref={triggerRef} | ||
| onClick={() => setIsOpen(!isOpen)} | ||
| aria-haspopup="menu" | ||
| aria-expanded={isOpen} | ||
| className={cn( | ||
| "min-h-11 min-w-11 inline-flex items-center justify-center gap-2 rounded-lg", | ||
| "bg-background border border-border px-3 py-2 text-sm font-medium", | ||
| "text-foreground transition-colors duration-200", | ||
| "hover:bg-muted hover:text-muted-foreground", | ||
| "focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", | ||
| "dark:focus:ring-offset-background", | ||
| "active:scale-95", | ||
| )} | ||
| > |
There was a problem hiding this comment.
A11y: wire up aria-controls/aria-activedescendant and handle Tab.
The trigger advertises aria-haspopup="menu" / aria-expanded, but the menu has no id and there is no aria-controls linking the two. Selection is only tracked via the selectedIndex state for visual highlight — DOM focus never moves and there's no aria-activedescendant, so screen readers won't announce the highlighted item as the user arrows through it. Tab is also not handled while the menu is open: focus can escape into the page while the menu remains visible.
Suggested minimum changes:
- Generate a stable id (e.g.,
useId()) for the menu, set it onmotion.div, and pass it asaria-controlson the trigger. - Set per-item ids (
${menuId}-item-${index}) and reflect the current one viaaria-activedescendanton the menu. - Close (or trap) on
Tabin the keydown handler.
♿ Sketch of the changes
-import { useEffect, useRef, useState } from "react";
+import { useEffect, useId, useRef, useState } from "react";
@@
const [isOpen, setIsOpen] = useState(false);
const [selectedIndex, setSelectedIndex] = useState(0);
+ const menuId = useId();
+ const activeItemId = `${menuId}-item-${selectedIndex}`;
@@
- } else if (e.key === "Escape") {
+ } else if (e.key === "Escape" || e.key === "Tab") {
e.preventDefault();
setIsOpen(false);
triggerRef.current?.focus();
}
@@
<button
ref={triggerRef}
type="button"
onClick={() => setIsOpen(!isOpen)}
aria-haspopup="menu"
aria-expanded={isOpen}
+ aria-controls={menuId}
@@
<motion.div
ref={menuRef}
+ id={menuId}
role="menu"
+ tabIndex={-1}
+ aria-activedescendant={activeItemId}
@@
<button
key={index}
+ id={`${menuId}-item-${index}`}
type="button"
role="menuitem"Note: preventing default on Tab will swallow the navigation; if you'd rather let focus move naturally, just call setIsOpen(false) without preventDefault().
Also applies to: 128-140, 141-162
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@animata/navigation/dropdown-menu.tsx` around lines 95 - 111, Generate a
stable menu id (e.g., with React's useId) and set it as the id on the menu
container (the motion.div rendered by the dropdown) and also set
aria-controls={menuId} on the trigger button (triggerRef block that calls
setIsOpen). Give each menu item an id like `${menuId}-item-${index}` and update
the menu container to expose the current selection via aria-activedescendant
using the selectedIndex state so screen readers announce the highlighted item.
In the dropdown keydown handler (the function that updates selectedIndex on
Arrow keys), handle Tab by closing the menu (call setIsOpen(false)) or trapping
focus (preventDefault and manage focus), and ensure aria-expanded remains
correct on the trigger; update references to selectedIndex, triggerRef,
setIsOpen, and the motion.div/menuId accordingly.
| <button | ||
| ref={triggerRef} | ||
| onClick={() => setIsOpen(!isOpen)} | ||
| aria-haspopup="menu" | ||
| aria-expanded={isOpen} | ||
| className={cn( | ||
| "min-h-11 min-w-11 inline-flex items-center justify-center gap-2 rounded-lg", | ||
| "bg-background border border-border px-3 py-2 text-sm font-medium", | ||
| "text-foreground transition-colors duration-200", | ||
| "hover:bg-muted hover:text-muted-foreground", | ||
| "focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", | ||
| "dark:focus:ring-offset-background", | ||
| "active:scale-95", | ||
| )} | ||
| > |
There was a problem hiding this comment.
Add type="button" to all <button> elements.
Without an explicit type, buttons default to type="submit". If this dropdown is rendered inside a <form>, clicking the trigger or any item will submit the form — silently breaking forms that use this navigation primitive.
🐛 Proposed fix
<button
ref={triggerRef}
+ type="button"
onClick={() => setIsOpen(!isOpen)}
aria-haspopup="menu"
aria-expanded={isOpen} <button
key={index}
+ type="button"
role="menuitem"
onClick={() => {
item.onClick?.();
setIsOpen(false);
}}Also applies to: 142-148
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@animata/navigation/dropdown-menu.tsx` around lines 97 - 111, The dropdown's
button elements (e.g., the trigger with ref={triggerRef} that toggles setIsOpen
and the item buttons later in this file) lack an explicit type and will default
to type="submit" inside forms; update every <button> in this component to
include type="button" so clicking the trigger or menu items won't submit a
surrounding form (add type="button" to the trigger button using triggerRef and
to each menu/item button instance).
|
🚀 Preview deployed to: https://489781b1.animata.pages.dev |
There was a problem hiding this comment.
🧹 Nitpick comments (3)
content/docs/navigation/dropdown-menu.mdx (3)
60-60: Consider clarifying the default items description.The description "4 default items" is accurate but vague. Consider being more specific about what those default items are to help users understand the component's out-of-the-box behavior.
📋 Suggested improvement
-| `items` | `MenuItem[]` | 4 default items | Array of menu items | +| `items` | `MenuItem[]` | Profile, Settings, Help, Sign Out | Array of menu items |Or alternatively:
-| `items` | `MenuItem[]` | 4 default items | Array of menu items | +| `items` | `MenuItem[]` | 4 generic items | Array of menu items (defaults: Profile, Settings, Help, Sign Out) |🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@content/docs/navigation/dropdown-menu.mdx` at line 60, Update the table row for the `items` prop (type `MenuItem[]`) to replace the vague "4 default items" text with a specific list of those defaults (e.g., the actual labels such as "Profile", "Settings", "Help", "Sign out") and a short note that these are the out-of-the-box menu entries; keep the description concise so users immediately know what the default menu contains and that it can be overridden by providing their own `items`.
47-49: Consider adding a note about onClick handlers.The empty
onClick: () => {}handlers are appropriate placeholders for documentation, but you might consider adding a brief comment indicating these should be replaced with actual handler functions in production use.💡 Optional enhancement
Add a brief note after the code example:
/> ); } + +// Note: Replace empty onClick handlers with your application logic🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@content/docs/navigation/dropdown-menu.mdx` around lines 47 - 49, Update the example to clarify that the placeholder onClick handlers should be replaced in real use: add a short inline comment or a brief explanatory sentence after the code block stating that items like the objects with label, icon (User, Settings, LogOut) and onClick: () => {} are placeholders and should be replaced with actual handler functions (e.g., onClick handlers that perform navigation, state updates, or sign-out logic) in production.
23-23: Minor grammar improvement: hyphenate compound adjective.The phrase "left or right aligned" should be hyphenated when used as a compound adjective modifying "dropdown panel."
📝 Suggested fix
-- **Alignment** — left or right aligned dropdown panel +- **Alignment** — left- or right-aligned dropdown panel🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@content/docs/navigation/dropdown-menu.mdx` at line 23, Update the compound adjective in the "Alignment — left or right aligned dropdown panel" line by hyphenating it; change the phrase to "left- or right-aligned dropdown panel" so the modifiers correctly attach to "dropdown panel" (locate the string "Alignment — left or right aligned dropdown panel" in the dropdown-menu.mdx content and replace accordingly).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@content/docs/navigation/dropdown-menu.mdx`:
- Line 60: Update the table row for the `items` prop (type `MenuItem[]`) to
replace the vague "4 default items" text with a specific list of those defaults
(e.g., the actual labels such as "Profile", "Settings", "Help", "Sign out") and
a short note that these are the out-of-the-box menu entries; keep the
description concise so users immediately know what the default menu contains and
that it can be overridden by providing their own `items`.
- Around line 47-49: Update the example to clarify that the placeholder onClick
handlers should be replaced in real use: add a short inline comment or a brief
explanatory sentence after the code block stating that items like the objects
with label, icon (User, Settings, LogOut) and onClick: () => {} are placeholders
and should be replaced with actual handler functions (e.g., onClick handlers
that perform navigation, state updates, or sign-out logic) in production.
- Line 23: Update the compound adjective in the "Alignment — left or right
aligned dropdown panel" line by hyphenating it; change the phrase to "left- or
right-aligned dropdown panel" so the modifiers correctly attach to "dropdown
panel" (locate the string "Alignment — left or right aligned dropdown panel" in
the dropdown-menu.mdx content and replace accordingly).
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: cc72309c-301d-42b2-86c1-b29333842bee
📒 Files selected for processing (1)
content/docs/navigation/dropdown-menu.mdx
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@content/docs/navigation/dropdown-menu.mdx`:
- Line 23: Update the sentence that currently reads "Alignment — left or right
aligned dropdown panel" to hyphenate the compound modifier for readability by
changing it to "Alignment — left- or right-aligned dropdown panel" (or at
minimum "right-aligned dropdown panel"); locate and edit the exact string "left
or right aligned dropdown panel" in the navigation/dropdown-menu content and
replace with the hyphenated form.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e9fa8182-48ae-47d2-bdf4-4a469d81f1ed
📒 Files selected for processing (1)
content/docs/navigation/dropdown-menu.mdx
| - **prefers-reduced-motion** — animation disabled when set | ||
| - **Accessible** — aria-expanded, aria-haspopup, role="menu", role="menuitem" | ||
| - **Mobile ready** — tap to open/close, 44px minimum touch targets | ||
| - **Alignment** — left or right aligned dropdown panel |
There was a problem hiding this comment.
Hyphenate compound modifier for readability.
Use “right-aligned dropdown panel” instead of “right aligned dropdown panel”.
🧰 Tools
🪛 LanguageTool
[grammar] ~23-~23: Use a hyphen to join words.
Context: ... targets - Alignment — left or right aligned dropdown panel ## Usage ```tsx...
(QB_NEW_EN_HYPHEN)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@content/docs/navigation/dropdown-menu.mdx` at line 23, Update the sentence
that currently reads "Alignment — left or right aligned dropdown panel" to
hyphenate the compound modifier for readability by changing it to "Alignment —
left- or right-aligned dropdown panel" (or at minimum "right-aligned dropdown
panel"); locate and edit the exact string "left or right aligned dropdown panel"
in the navigation/dropdown-menu content and replace with the hyphenated form.
There was a problem hiding this comment.
♻️ Duplicate comments (1)
content/docs/navigation/dropdown-menu.mdx (1)
23-23:⚠️ Potential issue | 🟡 MinorHyphenate the compound modifier in the alignment bullet.
Line 23 should use hyphenation for readability: “left- or right-aligned dropdown panel.”
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@content/docs/navigation/dropdown-menu.mdx` at line 23, Update the "Alignment" bullet text that currently reads "left or right aligned dropdown panel" to use proper hyphenation: change the phrase to "left- or right-aligned dropdown panel" so the compound modifier is correctly hyphenated for readability; locate the bullet under the dropdown menu documentation where the "Alignment" line contains "left or right aligned dropdown panel" and replace it with the hyphenated version.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@content/docs/navigation/dropdown-menu.mdx`:
- Line 23: Update the "Alignment" bullet text that currently reads "left or
right aligned dropdown panel" to use proper hyphenation: change the phrase to
"left- or right-aligned dropdown panel" so the compound modifier is correctly
hyphenated for readability; locate the bullet under the dropdown menu
documentation where the "Alignment" line contains "left or right aligned
dropdown panel" and replace it with the hyphenated version.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 59f2f4b9-b797-4c7c-a091-71f911e41ca8
📒 Files selected for processing (1)
content/docs/navigation/dropdown-menu.mdx
What this PR does
Adds a new Dropdown Menu navigation primitive.
Features
Screenshots
Checklist
Summary by CodeRabbit
New Features
Documentation