Skip to content

feat(navigation): Add flagged new sidebar components#3815

Closed
doubleface wants to merge 1 commit into
masterfrom
feat/newSideBarBehindFlag
Closed

feat(navigation): Add flagged new sidebar components#3815
doubleface wants to merge 1 commit into
masterfrom
feat/newSideBarBehindFlag

Conversation

@doubleface

@doubleface doubleface commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Had to centralise the fetching of Nav/NavNext components to avoid to duplicate the flag and component choosing logic

image

Summary by CodeRabbit

  • Refactor

    • Navigation system refactored to support dynamic component resolution, enabling seamless switching between sidebar UI variants through a feature flag.
  • Chores

    • Updated cozy-ui dependency to the latest version.

Had to centralize the fetching of nav components to avoid to duplicate
the flag and component choosing logic
@coderabbitai

coderabbitai Bot commented Apr 27, 2026

Copy link
Copy Markdown

Walkthrough

This pull request upgrades the cozy-ui dependency from ^138.6.2 to ^138.7.0 and refactors navigation components to retrieve UI components dynamically via a new getNavComponents() helper instead of static imports. A new navComponents.ts module is introduced to implement feature flag-based runtime selection between legacy Nav components and their NavNext variants, ensuring consistent component types across the application. Multiple navigation files (ExternalNavItem, FavoriteList, FavoriteListItem, Nav, NavContent, NavItem, NavLink, and ExternalDriveListItem) are updated to use the dynamic lookup pattern.

Possibly related PRs

Suggested reviewers

  • rezk2ll
  • lethemanh
  • JF-Cozy
  • zatteo
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: introducing flagged new sidebar components with centralized navigation component selection logic.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/newSideBarBehindFlag

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.

@codescene-delta-analysis codescene-delta-analysis Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Gates Passed
3 Quality Gates Passed

See analysis details in CodeScene

Quality Gate Profile: The Bare Minimum
Install CodeScene MCP: safeguard and uplift AI-generated code. Catch issues early with our IDE extension and CLI tool.

@bundlemon

bundlemon Bot commented Apr 27, 2026

Copy link
Copy Markdown

BundleMon

Files updated (2)
Status Path Size Limits
static/js/cozy.(hash).js
920.61KB (+337B +0.04%) -
static/js/main.(hash).js
169.1KB (+179B +0.1%) -
Unchanged files (19)
Status Path Size Limits
static/js/(chunkId).(hash).js
1.17MB -
public/static/js/(chunkId).(hash).js
1.1MB -
public/static/js/cozy.(hash).js
782.42KB -
(hash).js
336.11KB -
public/(hash).js
336.11KB -
services/qualificationMigration.js
283.13KB -
services/dacc.js
262.86KB -
public/static/js/public.(hash).js
142.27KB -
public/static/js/lib-react.(hash).js
43.88KB -
static/js/lib-react.(hash).js
43.88KB -
public/static/css/cozy.(hash).css
31.85KB -
static/css/cozy.(hash).css
31.85KB -
public/static/js/lib-router.(hash).js
21.86KB -
static/js/lib-router.(hash).js
21.86KB -
static/css/main.(hash).css
17.04KB -
public/static/css/public.(hash).css
6.76KB -
manifest.webapp
3.04KB -
index.html
702B -
assets/manifest.json
185B -

Total files change +520B +0.01%

Groups updated (1)
Status Path Size Limits
**/*.js
7.61MB (+523B +0.01%) -
Unchanged groups (2)
Status Path Size Limits
**/*.{png,svg,ico}
2.19MB -
**/*.css
121.03KB -

Final result: ✅

View report in BundleMon website ➡️


Current branch size history | Target branch size history

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (2)
src/modules/navigation/navComponents.ts (2)

17-24: Nit: declaring the shared type from NavNew is fine, but worth a note in a comment.

Future readers may wonder why NavComponents is keyed by typeof NavNew. A one-line comment ("New variant is the canonical type; old is asserted to match for migration") would make the choice obvious and the casts on lines 33-40 less surprising.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modules/navigation/navComponents.ts` around lines 17 - 24, Add a one-line
comment above the NavComponents type explaining that NavNew is treated as the
canonical type for the new variant and the older components are asserted to
match for migration; reference NavNew as the canonical shape and mention that
NavDesktopDropdownNew, NavIconNew, NavItemNew, NavLinkNew, and NavTextNew are
expected to conform so the subsequent casts (where older exports are asserted to
that type) are intentional.

29-42: Optional: memoize the resolved component map.

getNavComponents() is invoked on every render across at least 7 navigation components, allocating a fresh object each time. Since the underlying components are module-stable, you can resolve once per flag value:

♻️ Cache by flag value
-export const getNavComponents = (): NavComponents => {
-  const useNewSidebar = isNewSidebarEnabled()
-
-  return {
-    Nav: useNewSidebar ? NavNew : (NavOld as typeof NavNew),
-    NavDesktopDropdown: useNewSidebar
-      ? NavDesktopDropdownNew
-      : (NavDesktopDropdownOld as typeof NavDesktopDropdownNew),
-    NavIcon: useNewSidebar ? NavIconNew : (NavIconOld as typeof NavIconNew),
-    NavItem: useNewSidebar ? NavItemNew : (NavItemOld as typeof NavItemNew),
-    NavLink: useNewSidebar ? NavLinkNew : (NavLinkOld as typeof NavLinkNew),
-    NavText: useNewSidebar ? NavTextNew : (NavTextOld as typeof NavTextNew)
-  }
-}
+const NEW_COMPONENTS: NavComponents = {
+  Nav: NavNew,
+  NavDesktopDropdown: NavDesktopDropdownNew,
+  NavIcon: NavIconNew,
+  NavItem: NavItemNew,
+  NavLink: NavLinkNew,
+  NavText: NavTextNew
+}
+
+const OLD_COMPONENTS: NavComponents = {
+  Nav: NavOld as typeof NavNew,
+  NavDesktopDropdown: NavDesktopDropdownOld as typeof NavDesktopDropdownNew,
+  NavIcon: NavIconOld as typeof NavIconNew,
+  NavItem: NavItemOld as typeof NavItemNew,
+  NavLink: NavLinkOld as typeof NavLinkNew,
+  NavText: NavTextOld as typeof NavTextNew
+}
+
+export const getNavComponents = (): NavComponents =>
+  isNewSidebarEnabled() ? NEW_COMPONENTS : OLD_COMPONENTS
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modules/navigation/navComponents.ts` around lines 29 - 42,
getNavComponents currently allocates a new NavComponents object on every render;
cache the resolved map per flag value so repeated calls return the same object.
Implement a small memoization: add a module-level cache keyed by the boolean
result of isNewSidebarEnabled() (or two module-scoped variables like
cachedNewSidebarTrue/cachedNewSidebarFalse) and inside getNavComponents call
isNewSidebarEnabled(), return the cached object if present, otherwise build the
map using Nav, NavNew, NavOld, NavDesktopDropdownNew, NavDesktopDropdownOld,
NavIconNew, NavIconOld, NavItemNew, NavItemOld, NavLinkNew, NavLinkOld,
NavTextNew, NavTextOld, store it in the cache and return it; preserve the
NavComponents type when storing/returning.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/modules/navigation/navComponents.ts`:
- Around line 26-42: Summary: NavNext (NavNew) may lack static NavLink.className
and NavLink.activeClassName so toggling the new sidebar can yield undefined
class names; fix by ensuring the new NavLink exposes those static properties or
by copying/falling back to the old values. In
getNavComponents/isNewSidebarEnabled, verify NavNew.NavLink has className and
activeClassName at runtime and if missing assign them from NavOld.NavLink (e.g.,
if (useNewSidebar && !NavNew.NavLink.className) { NavNew.NavLink.className =
NavOld.NavLink.className; NavNew.NavLink.activeClassName =
NavOld.NavLink.activeClassName }), and add a proper TS declaration for
'cozy-ui/transpiled/react/NavNext' to include these static props so callers
(NavLink.jsx, ExternalNavItem.jsx, FavoriteListItem.tsx,
ExternalDriveListItem.tsx) are statically safe.

---

Nitpick comments:
In `@src/modules/navigation/navComponents.ts`:
- Around line 17-24: Add a one-line comment above the NavComponents type
explaining that NavNew is treated as the canonical type for the new variant and
the older components are asserted to match for migration; reference NavNew as
the canonical shape and mention that NavDesktopDropdownNew, NavIconNew,
NavItemNew, NavLinkNew, and NavTextNew are expected to conform so the subsequent
casts (where older exports are asserted to that type) are intentional.
- Around line 29-42: getNavComponents currently allocates a new NavComponents
object on every render; cache the resolved map per flag value so repeated calls
return the same object. Implement a small memoization: add a module-level cache
keyed by the boolean result of isNewSidebarEnabled() (or two module-scoped
variables like cachedNewSidebarTrue/cachedNewSidebarFalse) and inside
getNavComponents call isNewSidebarEnabled(), return the cached object if
present, otherwise build the map using Nav, NavNew, NavOld,
NavDesktopDropdownNew, NavDesktopDropdownOld, NavIconNew, NavIconOld,
NavItemNew, NavItemOld, NavLinkNew, NavLinkOld, NavTextNew, NavTextOld, store it
in the cache and return it; preserve the NavComponents type when
storing/returning.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 27f95179-a859-4875-8618-3fd1adf536bc

📥 Commits

Reviewing files that changed from the base of the PR and between a3c6794 and ff0240b.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (10)
  • package.json
  • src/modules/navigation/ExternalNavItem.jsx
  • src/modules/navigation/FavoriteList.tsx
  • src/modules/navigation/FavoriteListItem.tsx
  • src/modules/navigation/Nav.jsx
  • src/modules/navigation/NavContent.tsx
  • src/modules/navigation/NavItem.jsx
  • src/modules/navigation/NavLink.jsx
  • src/modules/navigation/components/ExternalDriveListItem.tsx
  • src/modules/navigation/navComponents.ts

Comment on lines +26 to +42
const isNewSidebarEnabled = (): boolean =>
Boolean(flag('drive.new-sidebar.enabled'))

export const getNavComponents = (): NavComponents => {
const useNewSidebar = isNewSidebarEnabled()

return {
Nav: useNewSidebar ? NavNew : (NavOld as typeof NavNew),
NavDesktopDropdown: useNewSidebar
? NavDesktopDropdownNew
: (NavDesktopDropdownOld as typeof NavDesktopDropdownNew),
NavIcon: useNewSidebar ? NavIconNew : (NavIconOld as typeof NavIconNew),
NavItem: useNewSidebar ? NavItemNew : (NavItemOld as typeof NavItemNew),
NavLink: useNewSidebar ? NavLinkNew : (NavLinkOld as typeof NavLinkNew),
NavText: useNewSidebar ? NavTextNew : (NavTextOld as typeof NavTextNew)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect cozy-ui Nav and NavNext to confirm both expose static className / activeClassName on NavLink
fd -i 'NavNext' node_modules/cozy-ui 2>/dev/null | head -20
fd -i '^Nav\.(js|ts|jsx|tsx|d\.ts)$' node_modules/cozy-ui 2>/dev/null | head -20
# Search for static className/activeClassName assignments in the package
rg -nP --type=ts --type=tsx --type=js --type=jsx -C2 '\bNavLink\.(className|activeClassName)\s*=' node_modules/cozy-ui 2>/dev/null
rg -nP -C2 '(className|activeClassName)\s*=\s*' node_modules/cozy-ui/transpiled/react/NavNext 2>/dev/null
rg -nP -C2 '(className|activeClassName)\s*=\s*' node_modules/cozy-ui/transpiled/react/Nav 2>/dev/null

Repository: linagora/twake-drive

Length of output: 46


🌐 Web query:

cozy-ui NavNext NavLink className activeClassName static property

💡 Result:

No evidence found of NavNext or NavLink components in cozy-ui. Cozy-ui is a React UI library based on Material-UI with documented components like Button, SearchBar, ListItem, but no navigation components named NavNext or NavLink. There is a NavLimiter component mentioned in the CHANGELOG. The terms className, activeClassName, and static property likely refer to standard React props (className for CSS classes, activeClassName from react-router-dom NavLink for active route styling), but these are not specific static properties or components in cozy-ui. Official docs and GitHub repo confirm no such components exist.

Citations:


🏁 Script executed:

# First, let's examine the actual navComponents.ts file
cat -n src/modules/navigation/navComponents.ts

Repository: linagora/twake-drive

Length of output: 1718


🏁 Script executed:

# Search for NavNext and NavOld definitions in the codebase
rg -l "NavNext|NavOld" --type=ts --type=tsx --type=js --type=jsx | head -20

Repository: linagora/twake-drive

Length of output: 92


🏁 Script executed:

# Check the actual imports in navComponents.ts to understand the source
rg "import.*Nav" src/modules/navigation/navComponents.ts -A 2 -B 2

Repository: linagora/twake-drive

Length of output: 337


🏁 Script executed:

# Search for the consumer files mentioned in the review
rg -l "NavLink\.className|NavLink\.activeClassName" --type=js --type=ts | head -20

Repository: linagora/twake-drive

Length of output: 246


🏁 Script executed:

# Check if NavLink.jsx exists and what it does
find . -name "NavLink.jsx" -o -name "NavLink.tsx" -o -name "NavLink.ts" | head -10

Repository: linagora/twake-drive

Length of output: 101


🏁 Script executed:

# Search for any reference to NavLink in the codebase to understand usage
rg "from.*NavLink|import.*NavLink" --type=js --type=ts | head -30

Repository: linagora/twake-drive

Length of output: 154


🏁 Script executed:

# Look for the specific consumer files mentioned
find . -name "ExternalNavItem.jsx" -o -name "FavoriteListItem.tsx" -o -name "ExternalDriveListItem.tsx" | head -10

Repository: linagora/twake-drive

Length of output: 217


🏁 Script executed:

# Read NavLink.jsx to understand its structure
cat -n src/modules/navigation/NavLink.jsx

Repository: linagora/twake-drive

Length of output: 1594


🏁 Script executed:

# Check ExternalNavItem.jsx around line 42
cat -n src/modules/navigation/ExternalNavItem.jsx | sed -n '35,50p'

Repository: linagora/twake-drive

Length of output: 511


🏁 Script executed:

# Check FavoriteListItem.tsx around line 50
cat -n src/modules/navigation/FavoriteListItem.tsx | sed -n '40,60p'

Repository: linagora/twake-drive

Length of output: 739


🏁 Script executed:

# Check ExternalDriveListItem.tsx
cat -n src/modules/navigation/components/ExternalDriveListItem.tsx | head -60

Repository: linagora/twake-drive

Length of output: 1615


🏁 Script executed:

# Search for NavLink type definitions or interfaces in the codebase
rg "NavLink\s*[:=].*className|activeClassName" --type=ts --type=js -B 3 -A 3

Repository: linagora/twake-drive

Length of output: 844


🏁 Script executed:

# Check if there's a package.json to see the cozy-ui version
cat package.json | grep -A 5 "cozy-ui"

Repository: linagora/twake-drive

Length of output: 254


🏁 Script executed:

# Look for any TypeScript configuration or declaration files
find . -name "*.d.ts" -path "*/node_modules/cozy-ui*" | head -10

Repository: linagora/twake-drive

Length of output: 46


🏁 Script executed:

# Check if the cozy-ui types are available in the codebase
rg "declare.*NavLink|export.*NavLink" --type=ts | head -20

Repository: linagora/twake-drive

Length of output: 157


🏁 Script executed:

# Read the full declarations.d.ts to see all module declarations
cat -n src/declarations.d.ts

Repository: linagora/twake-drive

Length of output: 7611


🏁 Script executed:

# Search for any NavNext type declarations
rg "NavNext|navnext" src/declarations.d.ts -i

Repository: linagora/twake-drive

Length of output: 46


🏁 Script executed:

# Check if there are any type definition files for NavNext
find . -name "*.d.ts" -type f | xargs grep -l "NavNext" 2>/dev/null

Repository: linagora/twake-drive

Length of output: 48


🏁 Script executed:

# Look for `@types` packages or type definitions in package.json
cat package.json | grep -E "@types|types"

Repository: linagora/twake-drive

Length of output: 307


Verify that NavNew (from cozy-ui/transpiled/react/NavNext) exposes the same static className and activeClassName properties on NavLink as NavOld.

Multiple consumers (NavLink.jsx lines 33–34, ExternalNavItem.jsx line 42, FavoriteListItem.tsx line 50, ExternalDriveListItem.tsx line 30) access NavLink.className and NavLink.activeClassName as static properties. The type assertions on lines 33–40 (NavOld as typeof NavNew, etc.) suppress structural type checking. declarations.d.ts declares the Nav variant's NavLink with these properties, but NavNext has no corresponding type declarations. If NavNext.NavLink lacks these properties, toggling the feature flag will silently produce className={undefined}, breaking link styling.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modules/navigation/navComponents.ts` around lines 26 - 42, Summary:
NavNext (NavNew) may lack static NavLink.className and NavLink.activeClassName
so toggling the new sidebar can yield undefined class names; fix by ensuring the
new NavLink exposes those static properties or by copying/falling back to the
old values. In getNavComponents/isNewSidebarEnabled, verify NavNew.NavLink has
className and activeClassName at runtime and if missing assign them from
NavOld.NavLink (e.g., if (useNewSidebar && !NavNew.NavLink.className) {
NavNew.NavLink.className = NavOld.NavLink.className;
NavNew.NavLink.activeClassName = NavOld.NavLink.activeClassName }), and add a
proper TS declaration for 'cozy-ui/transpiled/react/NavNext' to include these
static props so callers (NavLink.jsx, ExternalNavItem.jsx, FavoriteListItem.tsx,
ExternalDriveListItem.tsx) are statically safe.

@zatteo

zatteo commented Apr 27, 2026

Copy link
Copy Markdown
Member

@JF-Cozy this is the approach you don't like when changing an existing component? Because I see only two possible options for this kind of update:

  • like this
  • iterate with a custom build until the product + design + dev agree

@doubleface doubleface closed this Apr 28, 2026
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.

2 participants