Skip to main content

Navigation & Menu Hooks

Hooks for keyboard-driven menu navigation, profile menu management, home page item logic, and success page feature mapping.

useMenuNavigation

Implements keyboard navigation for dropdown menus and command palettes. Supports arrow keys, Tab, Home/End, Enter for selection, and Escape to close. Compatible with both Tiptap editors and regular DOM containers.

useMenuNavigation<T>(options: MenuNavigationOptions<T>): {
selectedIndex: number | undefined;
setSelectedIndex: Dispatch<SetStateAction<number>>;
}

Options

OptionTypeDefaultDescription
itemsT[]--Array of items to navigate through (required)
editorEditor | null--Tiptap editor instance (for editor-integrated menus)
containerRefRefObject<HTMLElement>--Container element for keyboard event handling
querystring--Search query; resets selection when it changes
onSelect(item: T) => void--Callback when Enter is pressed on an item
onClose() => void--Callback when Escape is pressed
orientation'horizontal' | 'vertical' | 'both'"vertical"Navigation direction
autoSelectFirstItembooleantrueAuto-select the first item on open

Keyboard Bindings

KeyAction
ArrowUp / ArrowDownNavigate vertically (when orientation allows)
ArrowLeft / ArrowRightNavigate horizontally (when orientation allows)
Tab / Shift+TabMove forward/backward through items
Home / EndJump to first/last item
EnterSelect the current item
EscapeClose the menu

Return Values

PropertyTypeDescription
selectedIndexnumber | undefinedCurrently highlighted item index; undefined if no items
setSelectedIndexDispatchManually set the selected index
import { useMenuNavigation } from '@/hooks/use-menu-navigation';

function CommandPalette({ items, onSelect, onClose }) {
const containerRef = useRef<HTMLDivElement>(null);
const [query, setQuery] = useState('');

const filtered = items.filter((item) =>
item.label.toLowerCase().includes(query.toLowerCase())
);

const { selectedIndex } = useMenuNavigation({
items: filtered,
containerRef,
query,
onSelect,
onClose,
orientation: 'vertical',
});

return (
<div ref={containerRef} tabIndex={0}>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<ul>
{filtered.map((item, index) => (
<li
key={item.id}
className={index === selectedIndex ? 'bg-blue-100' : ''}
onClick={() => onSelect(item)}
>
{item.label}
</li>
))}
</ul>
</div>
);
}

useProfileMenu

Manages the profile dropdown menu state with click-outside detection, Escape key handling, and focus management.

useProfileMenu(): UseProfileMenuReturn

Return Values

PropertyTypeDescription
isProfileMenuOpenbooleanWhether the profile menu is open
menuRefRefObject<HTMLDivElement>Ref for the menu container
buttonRefRefObject<HTMLButtonElement>Ref for the trigger button
toggleMenu() => voidToggle the menu open/closed
closeMenu() => voidClose the menu and restore focus to trigger

Behavior

  • Closes when clicking outside both the menu and trigger button
  • Closes on Escape key press
  • Restores focus to the trigger button when closing
  • Event listeners are only attached while the menu is open
import { useProfileMenu } from '@/hooks/use-profile-menu';

function ProfileDropdown() {
const { isProfileMenuOpen, menuRef, buttonRef, toggleMenu, closeMenu } =
useProfileMenu();

return (
<div className="relative">
<button ref={buttonRef} onClick={toggleMenu} aria-expanded={isProfileMenuOpen}>
<Avatar />
</button>

{isProfileMenuOpen && (
<div ref={menuRef} className="absolute right-0 mt-2 w-48 bg-white shadow-lg">
<a href="/profile" onClick={closeMenu}>Profile</a>
<a href="/settings" onClick={closeMenu}>Settings</a>
<button onClick={closeMenu}>Sign Out</button>
</div>
)}
</div>
);
}

useHomeTwoLogic

Filters and paginates items for the "Home Two" layout variant. Supports multi-category filtering.

useHomeTwoLogic(props: UseHome2LogicProps): {
items: ItemData[];
paginatedItems: ItemData[];
}

Parameters

ParamTypeDescription
itemsItemData[]Full array of items to filter
startnumberStarting index for pagination (offset-based)
selectedCategoriesstring[]Category IDs to filter by (empty = show all)

Return Values

PropertyTypeDescription
itemsItemData[]All items matching the category filter
paginatedItemsItemData[]Filtered items sliced by start and PER_PAGE

Category Matching

The hook supports items with categories in multiple formats:

  • String categories: item.category === "AI Tools"
  • Array of strings: item.category.includes("AI Tools")
  • Array of objects: item.category.some(c => c.id === "ai-tools")
import { useHomeTwoLogic } from '@/hooks/use-home-two-logic';

function HomePageTwo({ allItems }) {
const [categories, setCategories] = useState<string[]>([]);
const [page, setPage] = useState(0);

const { items: filtered, paginatedItems } = useHomeTwoLogic({
items: allItems,
start: page * PER_PAGE,
selectedCategories: categories,
});

return (
<div>
<CategoryFilter
selected={categories}
onChange={setCategories}
/>
<p>{filtered.length} items found</p>
<ItemGrid items={paginatedItems} />
<Pagination total={filtered.length} page={page} onChange={setPage} />
</div>
);
}

Helper: getTagId

Exported utility function that normalizes tag values:

getTagId(tag: string | Tag): string

Returns the tag string directly if it is a string, or tag.id if it is a Tag object.


useSuccessPageFeatures

Maps payment plan features to icons and colors for rendering on success/confirmation pages. Reads feature text from usePricingFeatures and assigns appropriate Lucide icons.

useSuccessPageFeatures(): {
getPlanFeaturesWithIcons: (planType: PaymentPlan) => SuccessPageFeature[];
}

SuccessPageFeature Shape

FieldTypeDescription
iconLucideIconLucide icon component for the feature
textstringFeature description text (from pricing features)
colorstringTailwind color class (e.g., "text-blue-400")

Supported Plans

PlanIcon CountExample Icons
FREEUp to 8FileText, ImageIcon, Globe, Eye, Clock, Mail
STANDARDUp to 9FileText, ImageIcon, Shield, Zap, Share2, BarChart3
PREMIUMUp to 11TrendingUp, Star, Shield, Video, Globe, BarChart3, Phone
import { useSuccessPageFeatures } from '@/hooks/use-success-page-features';
import { PaymentPlan } from '@/lib/constants';

function SuccessPage({ plan }) {
const { getPlanFeaturesWithIcons } = useSuccessPageFeatures();
const features = getPlanFeaturesWithIcons(plan);

return (
<div>
<h2>Your plan includes:</h2>
<ul>
{features.map((feature, i) => {
const Icon = feature.icon;
return (
<li key={i} className="flex items-center gap-2">
<Icon className={`w-5 h-5 ${feature.color}`} />
<span>{feature.text}</span>
</li>
);
})}
</ul>
</div>
);
}

Summary Table

HookPurposeSource File
useMenuNavigationKeyboard navigation for menus/palettesuse-menu-navigation.ts
useProfileMenuProfile dropdown state managementuse-profile-menu.ts
useHomeTwoLogicCategory filtering and pagination for Home Twouse-home-two-logic.ts
useSuccessPageFeaturesPlan features with icons for success pagesuse-success-page-features.ts