Skip to content

Feature/nav grouped dropdowns#2186

Merged
anglilian merged 5 commits into
masterfrom
feature/nav-grouped-dropdowns
Mar 20, 2026
Merged

Feature/nav grouped dropdowns#2186
anglilian merged 5 commits into
masterfrom
feature/nav-grouped-dropdowns

Conversation

@anglilian

@anglilian anglilian commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

Description

Splitting apart courses and projects. Decluttering the nav bar

Issue

Fixes #

Developer checklist

Screenshot

CleanShot 2026-03-20 at 16 18 29@2x CleanShot 2026-03-20 at 16 19 01@2x CleanShot 2026-03-20 at 16 19 16@2x CleanShot 2026-03-20 at 16 19 16@2x CleanShot 2026-03-20 at 16 20 10@2x CleanShot 2026-03-20 at 16 20 20@2x

…sections

Refactored the navigation component to split courses into two distinct sections:
- Added separate "Courses" dropdown for non-project courses
- Added new "Projects" dropdown for project-type courses
- Moved "Events" link into an "Explore" dropdown alongside Blog and Grants
- Updated state management to handle the new navigation structure
- Maintained responsive behavior across mobile and desktop views

This improves navigation clarity by categorizing content more intuitively and provides better organization for different course types.
- Add dedicated projects section with visual separator and mobile heading
- Update navigation to link directly to projects section via anchor (#projects)
- Split course sorting logic to handle courses and projects separately
- Enhance breadcrumb menu to show projects as distinct category
- Maintain consistent display ordering for both courses and projects
…ropdown

Cleaned up navigation dropdown by removing an unnecessary divider that was
placed after the "Technical AI Safety" menu item. This simplifies the menu
structure and improves visual consistency.
Refactor navigation to include dropdown menus for "Projects" and "Explore" sections, replacing direct links with expandable menu items. Updates include new dropdown components with proper ARIA attributes, responsive styling, and organized sub-navigation links for better content discovery.
@coderabbitai

coderabbitai Bot commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@anglilian has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 18 minutes and 9 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8a46a0e4-0335-44d7-97a2-3699ff2c030f

📥 Commits

Reviewing files that changed from the base of the PR and between 82d050c and 01b2f8c.

📒 Files selected for processing (1)
  • apps/website/src/components/Nav/_NavLinks.tsx
📝 Walkthrough

Walkthrough

This pull request restructures the navigation component's dropdown state management by replacing the about flag in ExpandedSectionsState with separate courses and projects flags. The changes update all navigation files (Nav.tsx, _MobileNavLinks.tsx, _NavLinks.tsx, _ProfileLinks.tsx, and utils.ts) to reflect this new state shape and split the "Explore" dropdown functionality. The courses page is refactored to sort and display courses and projects as distinct lists with updated breadcrumb navigation and JSON-LD schema generation.

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Feature/nav grouped dropdowns' accurately reflects the main change of reorganising navigation dropdowns to group Courses, Projects, and Explore sections.
Description check ✅ Passed The PR description explains the purpose ('Splitting apart courses and projects. Decluttering the nav bar'), includes screenshots showing the visual changes, but does not fill in the Issue number or complete the developer checklist items.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/nav-grouped-dropdowns

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR refactors the website navigation bar to split the previous single "Courses" dropdown into three separate grouped dropdowns — Courses, Projects, and Explore — decluttering the top-level nav by grouping Events, Blog, and Grants under "Explore", and separating courses from projects across both desktop and mobile layouts. The courses index page is also updated to render courses and projects in distinct sections with a shared breadcrumb menu.

Key changes:

  • ExpandedSectionsState gains a courses key (renamed from about) and a new projects key to track the three independent dropdown states.
  • NavDropdown now supports an external link flag, used by the new "Explore" dropdown entries (Events, Blog).
  • The mutual-exclusion logic (opening one dropdown closes the others) is consistently applied across all three dropdowns and the profile icon.
  • courses/index.tsx refactors sort logic into a shared sortByDisplayOrder helper and renders courses and projects in separate sections, including both in the JSON-LD structured data.
  • The 'See all projects' condition in the separator logic (_NavLinks.tsx line 243) is dead code — the projects dropdown footer link is titled 'See upcoming rounds', not 'See all projects', so this branch is never reached.
  • sortByDisplayOrder is defined inside useSortedCourses but not memoised with useCallback, and is absent from the useMemo dependency arrays, which may trigger react-hooks/exhaustive-deps lint warnings.
  • No Storybook stories or functional tests were added for the new grouped dropdown behaviour.

Confidence Score: 3/5

  • Safe to merge with minor fixes — one dead-code condition in the separator logic and a missing useCallback that may trigger lint warnings.
  • The overall approach is well-structured and the mutual-exclusion dropdown logic is applied consistently. However, the 'See all projects' separator check is dead code (could indicate a forgotten rename), and sortByDisplayOrder missing from useMemo deps may cause lint failures in CI. No functional tests cover the new dropdown interaction behaviour.
  • apps/website/src/components/Nav/_NavLinks.tsx (dead-code separator condition) and apps/website/src/pages/courses/index.tsx (sortByDisplayOrder memoisation).

Important Files Changed

Filename Overview
apps/website/src/components/Nav/_NavLinks.tsx Core change: splits "Courses" dropdown into separate "Courses", "Projects", and "Explore" grouped dropdowns with mutual-exclusion toggle logic. Contains dead code ('See all projects' separator check that never matches) and missing tests/stories.
apps/website/src/components/Nav/utils.ts Renames about key to courses and adds projects key in ExpandedSectionsState; straightforward type update with no issues.
apps/website/src/components/Nav/_MobileNavLinks.tsx Updated to reset projects state in toggle/close handlers to match the new ExpandedSectionsState; no issues.
apps/website/src/components/Nav/_ProfileLinks.tsx Updated onToggleProfile to close courses and projects instead of about; minor housekeeping change with no issues.
apps/website/src/pages/courses/index.tsx Refactors sorting logic into a shared sortByDisplayOrder helper and separates courses from projects for display; sortByDisplayOrder is not wrapped in useCallback, which may trigger exhaustive-deps lint warnings.
apps/website/src/components/Nav/Nav.tsx Initialises projects: false in the new ExpandedSectionsState and resets it on breakpoint change; clean update with no issues.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    User([User clicks nav button]) --> NavLinks

    NavLinks --> CoursesBtn[Courses button]
    NavLinks --> ProjectsBtn[Projects button]
    NavLinks --> ExploreBtn[Explore button]

    CoursesBtn --> |"updateExpandedSections\n{ courses: !courses,\n  projects: false,\n  explore: false }"| CoursesDropdown[Courses Dropdown]
    ProjectsBtn --> |"updateExpandedSections\n{ courses: false,\n  projects: !projects,\n  explore: false }"| ProjectsDropdown[Projects Dropdown]
    ExploreBtn --> |"updateExpandedSections\n{ courses: false,\n  projects: false,\n  explore: !explore }"| ExploreDropdown[Explore Dropdown]

    CoursesDropdown --> |"allCourses.filter\ntype !== 'Project'"| CourseLinks[Course links\n+ 'See upcoming rounds']
    ProjectsDropdown --> |"allCourses.filter\ntype === 'Project'"| ProjectLinks[Project links\n+ 'See upcoming rounds → #projects']
    ExploreDropdown --> StaticLinks[Events / Blog / Grants]

    CourseLinks --> |"useClickOutside"| CloseAll[Close dropdown]
    ProjectLinks --> |"useClickOutside"| CloseAll
    ExploreDropdown --> |"useClickOutside"| CloseAll
Loading

Comments Outside Diff (1)

  1. apps/website/src/components/Nav/_NavLinks.tsx, line 142-154 (link)

    P2 Storybook stories not added for new NavDropdown component

    The new NavDropdown component (including the "Courses", "Projects", and "Explore" grouped dropdown variants) is a significant UI addition with hover states, expand/collapse animation, and both desktop and mobile layouts. Consider adding Storybook stories to document and visually test the different states (open/closed, colored background, loading, mobile).

    Rule Used: Consider adding Storybook stories for new componen... (source)

    Learnt From
    bluedotimpact/bluedot#956
    bluedotimpact/bluedot#969
    bluedotimpact/bluedot#958

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Last reviewed commit: "feat: add dropdown m..."

Comment thread apps/website/src/components/Nav/_NavLinks.tsx Outdated
// Sort courses by fixed display order
const sortedCourses = useMemo(() => {
return [...displayedCourses].sort((a, b) => {
const sortByDisplayOrder = (items: Course[]) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 sortByDisplayOrder missing from useMemo dependencies

sortByDisplayOrder is defined inside useSortedCourses but is not included in the dependency arrays of sortedCourses or sortedProjects. This will trigger the react-hooks/exhaustive-deps lint rule. Since sortByDisplayOrder doesn't capture any reactive values, moving it outside the hook (or wrapping it in useCallback) is the cleanest fix.

Suggested change
const sortByDisplayOrder = (items: Course[]) => {
const sortByDisplayOrder = useCallback((items: Course[]) => {

You'll also need to add useCallback to the import at the top and add sortByDisplayOrder to the useMemo dependency arrays.

Rule Used: Consider refactoring complex lines of code for bet... (source)

Learnt From
bluedotimpact/bluedot#963

Comment on lines 78 to +125
<div className={clsx('nav-links flex gap-9 [&>*]:w-fit', className)}>
<NavDropdown
expandedSections={expandedSections}
isExpanded={expandedSections.explore}
isExpanded={expandedSections.courses}
onColoredBackground={onColoredBackground}
links={navCourses}
onToggle={() => updateExpandedSections({
about: false,
explore: !expandedSections.explore,
courses: !expandedSections.courses,
projects: false,
explore: false,
mobileNav: expandedSections.mobileNav,
profile: false,
})}
onClose={() => updateExpandedSections({ explore: false })}
onClose={() => updateExpandedSections({ courses: false })}
title="Courses"
loading={loading}
/>
<A
href="https://lu.ma/bluedotevents?utm_source=website&utm_campaign=nav"
target="_blank"
rel="noopener noreferrer"
className={getLinkClasses()}
aria-label="Events (opens in new tab)"
>
Events
</A>
<A
href={ROUTES.blog.url}
target="_blank"
rel="noopener noreferrer"
className={getLinkClasses(isCurrentPath(ROUTES.blog.url))}
aria-label="Blog (opens in new tab)"
>
Blog
</A>
<NavDropdown
expandedSections={expandedSections}
isExpanded={expandedSections.projects}
onColoredBackground={onColoredBackground}
links={navProjects}
onToggle={() => updateExpandedSections({
courses: false,
projects: !expandedSections.projects,
explore: false,
mobileNav: expandedSections.mobileNav,
profile: false,
})}
onClose={() => updateExpandedSections({ projects: false })}
title="Projects"
loading={loading}
/>
<NavDropdown
expandedSections={expandedSections}
isExpanded={expandedSections.explore}
onColoredBackground={onColoredBackground}
links={exploreLinks}
onToggle={() => updateExpandedSections({
courses: false,
projects: false,
explore: !expandedSections.explore,
mobileNav: expandedSections.mobileNav,
profile: false,
})}
onClose={() => updateExpandedSections({ explore: false })}
title="Explore"
/>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 No tests for new grouped dropdown behaviour

The refactoring introduces three separate NavDropdown instances (Courses, Projects, Explore) with mutual-exclusion logic (opening one closes the others). This interactive behaviour is a good candidate for functional tests — e.g. verifying that clicking "Courses" closes the "Projects" dropdown, or that keyboard navigation works correctly. Consider adding tests alongside the snapshot updates.

Rule Used: Consider adding tests for any new functionality in... (source)

Learnt From
bluedotimpact/bluedot#956
bluedotimpact/bluedot#969
bluedotimpact/bluedot#958

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/website/src/pages/courses/index.tsx (1)

51-73: Consider extracting sortByDisplayOrder outside the hook.

The sortByDisplayOrder function is recreated on every render since it's defined inside the hook. Although it's used within useMemo calls (so the sorting itself is memoized), the function recreation is a minor inefficiency. Since it only depends on the static COURSE_DISPLAY_ORDER constant, it could be extracted to module scope.

♻️ Optional: Extract to module scope
+/* Sort items by fixed display order */
+const sortByDisplayOrder = (items: Course[]) => {
+  return [...items].sort((a, b) => {
+    const aIndex = COURSE_DISPLAY_ORDER.indexOf(a.slug);
+    const bIndex = COURSE_DISPLAY_ORDER.indexOf(b.slug);
+
+    if (aIndex !== -1 && bIndex !== -1) {
+      return aIndex - bIndex;
+    }
+
+    if (aIndex !== -1) {
+      return -1;
+    }
+
+    if (bIndex !== -1) {
+      return 1;
+    }
+
+    return a.title.localeCompare(b.title);
+  });
+};
+
 /* Custom hook to fetch and sort courses with their round data */
 const useSortedCourses = () => {
   const { data: courses, isLoading: coursesLoading, error } = trpc.courses.getAll.useQuery();

   const displayedCourses = useMemo(() => {
     if (!courses) {
       return [];
     }

     return courses.filter((course) => course.displayOnCourseHubIndex);
   }, [courses]);

-  // Sort courses by fixed display order
-  const sortByDisplayOrder = (items: Course[]) => {
-    return [...items].sort((a, b) => {
-      const aIndex = COURSE_DISPLAY_ORDER.indexOf(a.slug);
-      const bIndex = COURSE_DISPLAY_ORDER.indexOf(b.slug);
-
-      if (aIndex !== -1 && bIndex !== -1) {
-        return aIndex - bIndex;
-      }
-
-      if (aIndex !== -1) {
-        return -1;
-      }
-
-      if (bIndex !== -1) {
-        return 1;
-      }
-
-      return a.title.localeCompare(b.title);
-    });
-  };
-
   const sortedCourses = useMemo(() => sortByDisplayOrder(displayedCourses.filter((c) => c.type !== 'Project')), [displayedCourses]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/website/src/pages/courses/index.tsx` around lines 51 - 73, Move the
sortByDisplayOrder function out of the component/module scope so it is not
recreated on every render; it only depends on the constant COURSE_DISPLAY_ORDER.
Replace the inline definition used by sortedCourses and sortedProjects (which
call useMemo over displayedCourses) with the newly exported module-level
function named sortByDisplayOrder, keeping its current logic and references to
COURSE_DISPLAY_ORDER, and ensure sortedCourses and sortedProjects still call the
same function inside their useMemo callbacks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/website/src/components/Nav/_NavLinks.tsx`:
- Around line 242-245: The condition checking for link.title === 'See all
projects' is dead because navProjects uses 'See upcoming rounds'; update the
separator condition in the NavLinks component to remove the redundant 'See all
projects' check and only test for the actual footer title (e.g., keep link.title
=== 'See upcoming rounds'), or if you intended a different footer label, make
navProjects and the condition consistent by renaming the footer link; locate the
check that references link.title and adjust accordingly.

---

Nitpick comments:
In `@apps/website/src/pages/courses/index.tsx`:
- Around line 51-73: Move the sortByDisplayOrder function out of the
component/module scope so it is not recreated on every render; it only depends
on the constant COURSE_DISPLAY_ORDER. Replace the inline definition used by
sortedCourses and sortedProjects (which call useMemo over displayedCourses) with
the newly exported module-level function named sortByDisplayOrder, keeping its
current logic and references to COURSE_DISPLAY_ORDER, and ensure sortedCourses
and sortedProjects still call the same function inside their useMemo callbacks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 929a4074-116f-4fcd-8bcf-cbd21d6739b7

📥 Commits

Reviewing files that changed from the base of the PR and between fe77ac2 and 82d050c.

⛔ Files ignored due to path filters (6)
  • apps/website/src/__tests__/pages/__snapshots__/about.test.tsx.snap is excluded by !**/*.snap
  • apps/website/src/__tests__/pages/__snapshots__/join-us.test.tsx.snap is excluded by !**/*.snap
  • apps/website/src/components/Nav/__snapshots__/Nav.test.tsx.snap is excluded by !**/*.snap
  • apps/website/src/components/homepage/__snapshots__/HomeHeroContent.test.tsx.snap is excluded by !**/*.snap
  • apps/website/src/components/lander/__snapshots__/AgiStrategyLander.test.tsx.snap is excluded by !**/*.snap
  • apps/website/src/components/lander/__snapshots__/IncubatorWeekLander.test.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (6)
  • apps/website/src/components/Nav/Nav.tsx
  • apps/website/src/components/Nav/_MobileNavLinks.tsx
  • apps/website/src/components/Nav/_NavLinks.tsx
  • apps/website/src/components/Nav/_ProfileLinks.tsx
  • apps/website/src/components/Nav/utils.ts
  • apps/website/src/pages/courses/index.tsx

Comment thread apps/website/src/components/Nav/_NavLinks.tsx
@render render Bot temporarily deployed to feature/nav-grouped-dropdowns - bluedot-preview PR #2186 March 20, 2026 16:26 Destroyed
@anglilian anglilian merged commit f588db9 into master Mar 20, 2026
5 checks passed
@anglilian anglilian deleted the feature/nav-grouped-dropdowns branch March 20, 2026 16:47
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.

1 participant