Skip to content

feat (Navbar): add Navbar.center, shadow prop, hideOnScroll Prop, etc.#658

Open
paanSinghCoder wants to merge 11 commits intomainfrom
feat/navbar-improvements-center
Open

feat (Navbar): add Navbar.center, shadow prop, hideOnScroll Prop, etc.#658
paanSinghCoder wants to merge 11 commits intomainfrom
feat/navbar-improvements-center

Conversation

@paanSinghCoder
Copy link
Contributor

@paanSinghCoder paanSinghCoder commented Feb 26, 2026

Description

issue-576

  1. Add var(--space-11)
  2. Add a boolean prop shadow which toggles bottom shadow. Default to true.
  3. Add hideOnScroll prop.
  4. Keeping center layout as default to center will be unpredictable, intent not clear, API cost.
  5. <Navbar.Center /> will be absolutely horizontally centered in the Navbar.
  6. Already extends Flex. Added default gap={5} according to the design requirement.
  7. Navbar default role is already 'navigation'. role='group' is intentional and according to ARIA guidelines The recommended pattern is to give that group an accessible name (e.g. via aria-label or aria-labelledby). A group without a name is often not very useful for assistive tech. ARIA
  8. Added flex: display to container.
  9. Updated docs and better example design.
Screenshot 2026-02-27 at 9 24 41 AM

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactor (no functional changes, no bug fixes just code improvements)
  • Chore (changes to the build process or auxiliary tools and libraries such as documentation generation)
  • Style (changes that do not affect the meaning of the code (white-space, formatting, etc))
  • Test (adding missing tests or correcting existing tests)
  • Improvement (Improvements to existing code)
  • Other (please specify)

How Has This Been Tested?

Manually on examples page

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation (.mdx files)
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Navbar.Center section to enable flexible center-aligned content between Start and End sections
    • Added shadow property to toggle navbar shadow visibility
    • Added hideOnScroll feature to auto-hide navbar when scrolling
    • Enhanced preview layout customization for documentation examples
  • Tests

    • Added comprehensive test coverage for new Navbar features and behaviors

@vercel
Copy link

vercel bot commented Feb 26, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
apsara Ready Ready Preview, Comment Feb 27, 2026 4:12am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 26, 2026

📝 Walkthrough

Walkthrough

Adds Navbar.Center section component and extends NavbarRoot with shadow and hideOnScroll props featuring scroll-based visibility toggling. Introduces previewTop preview layout styling for documentation demos. Updates Navbar documentation, demo configurations, and component implementations with comprehensive test coverage.

Changes

Cohort / File(s) Summary
Demo Preview Styling
apps/www/src/components/demo/demo-preview.tsx, apps/www/src/components/demo/styles.module.css, apps/www/src/components/demo/types.ts
Added previewClassName prop and previewTop layout style (align-items flex-start, zero padding, decorative dashed box with ::after pseudo-element, hidden when sticky navbar present).
Navbar Documentation & Demos
apps/www/src/content/docs/components/navbar/demo.ts, apps/www/src/content/docs/components/navbar/index.mdx, apps/www/src/content/docs/components/navbar/props.ts
Updated demo exports with previewClassName, rewrote sticky/shadow/sections demos, added hideOnScrollDemo. Extended NavbarRootProps with shadow and hideOnScroll booleans. Introduced NavbarCenterProps with aria-label field. Expanded documentation to cover Center section, shadow behavior, and hide-on-scroll functionality.
Navbar Component Implementation
packages/raystack/components/navbar/navbar-root.tsx, packages/raystack/components/navbar/navbar-sections.tsx, packages/raystack/components/navbar/navbar.tsx
Added NavbarCenter component with aria-label and styling support. Extended NavbarRoot with scroll-event handling for hideOnScroll (state tracking, scroll direction detection, data attributes). Updated NavbarStart/End to explicitly handle align and gap props. Updated Navbar export to expose Center slot.
Navbar Styling
packages/raystack/components/navbar/navbar.module.css
Added data-shadow="true" state rule for shadow effect. Added data-hidden="true" state with upward transform translation. Introduced .center utility class (flex: 1 1 auto, justify-content: center). Updated root with CSS variable for min-height and transform transition.
Navbar Tests
packages/raystack/components/navbar/__tests__/navbar.test.tsx
Comprehensive test additions for shadow behavior (default true, can be disabled), hideOnScroll visibility toggling, Navbar.Center rendering with ARIA labeling, custom className support, and multi-section layout integration.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Suggested labels

Do not merge

Suggested reviewers

  • rsbh
  • rohanchkrabrty

Poem

🐰 A Center joins Start and End in the dance,
With shadows that gleam and a scroll-hiding glance!
The navbar now hides when you scroll on down,
While previewTop styles dress up the town,
Flex gaps align, aria-labels ring clear—hooray! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main changes: adding Navbar.center, shadow prop, and hideOnScroll prop, which are the primary features introduced in this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/navbar-improvements-center

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.

- Renamed demo section names for better clarity: 'Start Only' to 'Start', 'End Only' to 'End', and 'Both Sections' to 'Start, Center and End'.
- Added a new demo section for 'Center' to showcase centered content in the Navbar.
- Updated code snippets to reflect the new section names and structure.
- Added `previewClassName` prop to `DemoPreviewProps` for flexible styling.
- Updated `DemoPreview` component to apply custom styles based on `previewClassName`.
- Introduced new CSS classes for top-aligned preview content.
- Updated demo documentation to utilize the new `previewClassName` feature.
- Added a new `shadowDemo` to showcase the Navbar with and without shadow.
- Updated the sticky demo to include a scrollable container for better layout.
- Modified CSS to hide the dashed box in sticky demos with internal scrollable content.
- Updated documentation to reflect the new shadow feature in the Navbar.
- Introduced a new `hideOnScrollDemo` to demonstrate the Navbar's behavior when scrolling.
- Updated the Navbar documentation to include the new demo, showcasing the navbar's visibility toggle on scroll.
- Enhanced code snippets for better clarity and usability in the demo section.
- Updated the description of the `hideOnScroll` feature to specify that the slide animation is only visible when the navbar is sticky or fixed, enhancing user understanding of its functionality.
@paanSinghCoder paanSinghCoder changed the title feat: add center and add shadow prop feat (Navbar): add Navbar.center, shadow prop, hideOnScroll Prop, etc. Feb 27, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/www/src/content/docs/components/navbar/demo.ts (1)

178-189: ⚠️ Potential issue | 🟡 Minor

aria-labelledby demo is missing its referenced label element.

The snippet references nav-heading but doesn’t render an element with that ID, so the example is incomplete/invalid for assistive tech users.

💡 Proposed fix
       <>
+        <Text id="nav-heading" size="small" weight="medium">
+          Main navigation
+        </Text>
         <Navbar aria-labelledby="nav-heading">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/www/src/content/docs/components/navbar/demo.ts` around lines 178 - 189,
The demo named "With aria-labelledby" references aria-labelledby="nav-heading"
but never renders the referenced label; update the demo so there is a labeled
element with id "nav-heading" (for example a visually-hidden heading or a Text
component with id="nav-heading" and appropriate text) placed before or inside
the <Navbar> so assistive tech can resolve the label; ensure the label text is
meaningful and use the existing Navbar, Navbar.Start/End components in the
changed snippet.
packages/raystack/components/navbar/navbar-sections.tsx (1)

16-30: ⚠️ Potential issue | 🟠 Major

Also honor aria-labelledby when assigning role="group" on sections.

Right now, role="group" is only set for aria-label. If consumers use aria-labelledby, group semantics are not applied consistently. Please mirror the role condition for either accessible-name path across Start/Center/End.

💡 Proposed fix pattern (apply to all three section components)
-      'aria-label': ariaLabel,
+      'aria-label': ariaLabel,
+      'aria-labelledby': ariaLabelledby,
       align = 'center',
       gap = 5,
       ...props
@@
-      role={ariaLabel ? 'group' : undefined}
+      role={ariaLabel || ariaLabelledby ? 'group' : undefined}
       aria-label={ariaLabel}
+      aria-labelledby={ariaLabelledby}
       {...props}

Also applies to: 47-61, 77-91

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

In `@packages/raystack/components/navbar/navbar-sections.tsx` around lines 16 -
30, The Start/Center/End section components currently set role="group" only when
ariaLabel is present; update the conditional to also check for ariaLabelledBy
(e.g., use role={ariaLabel || ariaLabelledBy ? 'group' : undefined}) and ensure
the aria-labelledby prop is forwarded (aria-labelledby={ariaLabelledBy})
alongside aria-label so both accessible-name paths trigger group semantics;
apply this same change to all three section components (the blocks handling
ariaLabel/role/props around the Flex element).
🧹 Nitpick comments (1)
packages/raystack/components/navbar/__tests__/navbar.test.tsx (1)

137-150: hideOnScroll test currently validates only initial state, not scroll transitions.

On Line 145, this suite confirms the default data-hidden='false' state but doesn’t assert hide/reveal on downward/upward scroll, which is the core behavior.

Suggested test expansion
 describe('hideOnScroll', () => {
   it('sets data-hidden when hideOnScroll is true', () => {
-    render(<BasicNavbar hideOnScroll />);
+    render(<BasicNavbar hideOnScroll sticky />);
 
     const nav = screen.getByRole('navigation');
     expect(nav).toHaveAttribute('data-hidden', 'false');
+
+    Object.defineProperty(window, 'scrollY', { configurable: true, value: 200 });
+    window.dispatchEvent(new Event('scroll'));
+    expect(nav).toHaveAttribute('data-hidden', 'true');
+
+    Object.defineProperty(window, 'scrollY', { configurable: true, value: 50 });
+    window.dispatchEvent(new Event('scroll'));
+    expect(nav).toHaveAttribute('data-hidden', 'false');
   });
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/navbar/__tests__/navbar.test.tsx` around lines
137 - 150, The test only checks the initial data-hidden value; update the 'sets
data-hidden when hideOnScroll is true' test to simulate user scrolling and
assert transitions: render <BasicNavbar hideOnScroll />, ensure initial nav has
data-hidden="false", then simulate a downward scroll (increase
window.pageYOffset or mock scroll position and dispatch a window 'scroll' event)
and assert the nav element (screen.getByRole('navigation')) has
data-hidden="true", then simulate an upward scroll (decrease pageYOffset and
dispatch another 'scroll') and assert data-hidden returns to "false"; reference
the BasicNavbar component and the navigation element in your assertions and use
testing-library's fireEvent or window.dispatchEvent for the scroll events.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/raystack/components/navbar/__tests__/navbar.test.tsx`:
- Around line 484-499: The test "positions Start, Center, and End correctly"
only asserts presence; either rename it to "renders Start, Center, and End
sections" to reflect the current checks, or add explicit position/order
assertions: locate the elements rendered by BasicNavbar (Navbar.Start,
Navbar.Center, Navbar.End) via container.querySelector / querySelectorAll with
styles.start, styles.center, styles.end and assert DOM order (e.g., verify the
NodeList order or use compareDocumentPosition) so the test name matches its
intent.

In `@packages/raystack/components/navbar/navbar-root.tsx`:
- Around line 69-82: The scroll handler (handleScroll) currently hides the
navbar via setHidden based only on scroll position and lastScrollY, which allows
the bar to disappear while a control inside it has focus; modify handleScroll to
detect if the navbar DOM contains document.activeElement (use the existing
navbar/root ref, e.g., navRef or rootRef) and, when focus is inside the navbar,
force visibility (call setHidden(false)) and skip hiding logic (still update
lastScrollY as needed). Ensure the check happens before the existing scroll
threshold checks so keyboard focus inside the navbar prevents hideOnScroll from
running.

---

Outside diff comments:
In `@apps/www/src/content/docs/components/navbar/demo.ts`:
- Around line 178-189: The demo named "With aria-labelledby" references
aria-labelledby="nav-heading" but never renders the referenced label; update the
demo so there is a labeled element with id "nav-heading" (for example a
visually-hidden heading or a Text component with id="nav-heading" and
appropriate text) placed before or inside the <Navbar> so assistive tech can
resolve the label; ensure the label text is meaningful and use the existing
Navbar, Navbar.Start/End components in the changed snippet.

In `@packages/raystack/components/navbar/navbar-sections.tsx`:
- Around line 16-30: The Start/Center/End section components currently set
role="group" only when ariaLabel is present; update the conditional to also
check for ariaLabelledBy (e.g., use role={ariaLabel || ariaLabelledBy ? 'group'
: undefined}) and ensure the aria-labelledby prop is forwarded
(aria-labelledby={ariaLabelledBy}) alongside aria-label so both accessible-name
paths trigger group semantics; apply this same change to all three section
components (the blocks handling ariaLabel/role/props around the Flex element).

---

Nitpick comments:
In `@packages/raystack/components/navbar/__tests__/navbar.test.tsx`:
- Around line 137-150: The test only checks the initial data-hidden value;
update the 'sets data-hidden when hideOnScroll is true' test to simulate user
scrolling and assert transitions: render <BasicNavbar hideOnScroll />, ensure
initial nav has data-hidden="false", then simulate a downward scroll (increase
window.pageYOffset or mock scroll position and dispatch a window 'scroll' event)
and assert the nav element (screen.getByRole('navigation')) has
data-hidden="true", then simulate an upward scroll (decrease pageYOffset and
dispatch another 'scroll') and assert data-hidden returns to "false"; reference
the BasicNavbar component and the navigation element in your assertions and use
testing-library's fireEvent or window.dispatchEvent for the scroll events.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7d24df8 and 7500a58.

📒 Files selected for processing (11)
  • apps/www/src/components/demo/demo-preview.tsx
  • apps/www/src/components/demo/styles.module.css
  • apps/www/src/components/demo/types.ts
  • apps/www/src/content/docs/components/navbar/demo.ts
  • apps/www/src/content/docs/components/navbar/index.mdx
  • apps/www/src/content/docs/components/navbar/props.ts
  • packages/raystack/components/navbar/__tests__/navbar.test.tsx
  • packages/raystack/components/navbar/navbar-root.tsx
  • packages/raystack/components/navbar/navbar-sections.tsx
  • packages/raystack/components/navbar/navbar.module.css
  • packages/raystack/components/navbar/navbar.tsx

Comment on lines +484 to 499
it('positions Start, Center, and End correctly', () => {
const { container } = render(
<BasicNavbar>
<Navbar.Start data-testid='start'>Start</Navbar.Start>
<Navbar.Center data-testid='center'>Center</Navbar.Center>
<Navbar.End data-testid='end'>End</Navbar.End>
</BasicNavbar>
);

const start = container.querySelector(`.${styles.start}`);
const center = container.querySelector(`.${styles.center}`);
const end = container.querySelector(`.${styles.end}`);

expect(start).toBeInTheDocument();
expect(center).toBeInTheDocument();
expect(end).toBeInTheDocument();
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Test name and assertion intent are misaligned.

Line 484 says “positions ... correctly”, but the assertions only verify section presence. Either rename to reflect render presence or add order/position assertions.

Minimal alignment fix
-it('positions Start, Center, and End correctly', () => {
+it('renders Start, Center, and End sections', () => {
📝 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.

Suggested change
it('positions Start, Center, and End correctly', () => {
const { container } = render(
<BasicNavbar>
<Navbar.Start data-testid='start'>Start</Navbar.Start>
<Navbar.Center data-testid='center'>Center</Navbar.Center>
<Navbar.End data-testid='end'>End</Navbar.End>
</BasicNavbar>
);
const start = container.querySelector(`.${styles.start}`);
const center = container.querySelector(`.${styles.center}`);
const end = container.querySelector(`.${styles.end}`);
expect(start).toBeInTheDocument();
expect(center).toBeInTheDocument();
expect(end).toBeInTheDocument();
it('renders Start, Center, and End sections', () => {
const { container } = render(
<BasicNavbar>
<Navbar.Start data-testid='start'>Start</Navbar.Start>
<Navbar.Center data-testid='center'>Center</Navbar.Center>
<Navbar.End data-testid='end'>End</Navbar.End>
</BasicNavbar>
);
const start = container.querySelector(`.${styles.start}`);
const center = container.querySelector(`.${styles.center}`);
const end = container.querySelector(`.${styles.end}`);
expect(start).toBeInTheDocument();
expect(center).toBeInTheDocument();
expect(end).toBeInTheDocument();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/navbar/__tests__/navbar.test.tsx` around lines
484 - 499, The test "positions Start, Center, and End correctly" only asserts
presence; either rename it to "renders Start, Center, and End sections" to
reflect the current checks, or add explicit position/order assertions: locate
the elements rendered by BasicNavbar (Navbar.Start, Navbar.Center, Navbar.End)
via container.querySelector / querySelectorAll with styles.start, styles.center,
styles.end and assert DOM order (e.g., verify the NodeList order or use
compareDocumentPosition) so the test name matches its intent.

Comment on lines +69 to +82
const handleScroll = () => {
if (ticking) return;
ticking = true;
requestAnimationFrame(() => {
const scrollY = isWindow
? (window.scrollY ?? document.documentElement.scrollTop)
: scrollContainer!.scrollTop;
if (scrollY <= SHOW_AT_TOP_THRESHOLD) {
setHidden(false);
} else if (scrollY > lastScrollY.current + SCROLL_THRESHOLD) {
setHidden(true);
} else if (scrollY < lastScrollY.current - SCROLL_THRESHOLD) {
setHidden(false);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Prevent hiding while navbar contains focus.

hideOnScroll can move the navbar off-screen even when one of its controls is focused, which breaks keyboard navigation and focus visibility. Keep it visible while focus is within the navbar.

💡 Proposed fix
       requestAnimationFrame(() => {
         const scrollY = isWindow
           ? (window.scrollY ?? document.documentElement.scrollTop)
           : scrollContainer!.scrollTop;
+        const activeEl = document.activeElement;
+        if (activeEl && el.contains(activeEl)) {
+          setHidden(false);
+          lastScrollY.current = scrollY;
+          ticking = false;
+          return;
+        }
         if (scrollY <= SHOW_AT_TOP_THRESHOLD) {
           setHidden(false);
         } else if (scrollY > lastScrollY.current + SCROLL_THRESHOLD) {
📝 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.

Suggested change
const handleScroll = () => {
if (ticking) return;
ticking = true;
requestAnimationFrame(() => {
const scrollY = isWindow
? (window.scrollY ?? document.documentElement.scrollTop)
: scrollContainer!.scrollTop;
if (scrollY <= SHOW_AT_TOP_THRESHOLD) {
setHidden(false);
} else if (scrollY > lastScrollY.current + SCROLL_THRESHOLD) {
setHidden(true);
} else if (scrollY < lastScrollY.current - SCROLL_THRESHOLD) {
setHidden(false);
}
const handleScroll = () => {
if (ticking) return;
ticking = true;
requestAnimationFrame(() => {
const scrollY = isWindow
? (window.scrollY ?? document.documentElement.scrollTop)
: scrollContainer!.scrollTop;
const activeEl = document.activeElement;
if (activeEl && el.contains(activeEl)) {
setHidden(false);
lastScrollY.current = scrollY;
ticking = false;
return;
}
if (scrollY <= SHOW_AT_TOP_THRESHOLD) {
setHidden(false);
} else if (scrollY > lastScrollY.current + SCROLL_THRESHOLD) {
setHidden(true);
} else if (scrollY < lastScrollY.current - SCROLL_THRESHOLD) {
setHidden(false);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/navbar/navbar-root.tsx` around lines 69 - 82,
The scroll handler (handleScroll) currently hides the navbar via setHidden based
only on scroll position and lastScrollY, which allows the bar to disappear while
a control inside it has focus; modify handleScroll to detect if the navbar DOM
contains document.activeElement (use the existing navbar/root ref, e.g., navRef
or rootRef) and, when focus is inside the navbar, force visibility (call
setHidden(false)) and skip hiding logic (still update lastScrollY as needed).
Ensure the check happens before the existing scroll threshold checks so keyboard
focus inside the navbar prevents hideOnScroll from running.

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