-
Notifications
You must be signed in to change notification settings - Fork 13
feat(oc-docs): add sticky top navigation bar #44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
bijin-bruno
merged 29 commits into
opencollection-dev:main
from
sundram-bruno:feat/bru-3572-top-navigation-bar
Jun 26, 2026
Merged
Changes from all commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
021044a
feat(oc-docs): add sticky top navigation bar (BRU-3572)
sundram-bruno f349f9e
fix(oc-docs): pin Open-in-Bruno CTA to the right with empty slots (BR…
sundram-bruno 7642b9d
feat(oc-docs): initials brand avatar with Bruno amber gradient (BRU-3…
sundram-bruno e8cb550
feat(oc-docs): display collection version with a leading "v" (BRU-3572)
sundram-bruno 4c9f8aa
refactor(oc-docs): address self-review on topbar (BRU-3572)
sundram-bruno 3795aa7
feat(oc-docs): Docs brand label, desktop-only CTA, hamburger below de…
sundram-bruno 52e9fc4
style(oc-docs): borderless ghost icon-buttons in topbar (BRU-3572)
sundram-bruno 64d5d58
feat(oc-docs): gate Open-in-Bruno on device capability, not width (BR…
sundram-bruno aeb4132
refactor(oc-docs): align Topbar to per-component folder convention (B…
sundram-bruno 5070942
feat(oc-docs): compact mobile brand — avatar + "Docs" only (BRU-3572)
sundram-bruno b5a7334
fix(oc-docs): address self-review on topbar capability + shadow (BRU-…
sundram-bruno 2d538b1
fix(oc-docs): align topbar header to design — hamburger, spacing, ava…
sundram-bruno 8e0675f
test(oc-docs): use data-testid selectors in topbar e2e (BRU-3572)
sundram-bruno dfeded3
refactor(oc-docs): drop per-component index.ts barrels per review (BR…
sundram-bruno 41c8711
test(oc-docs): scope header e2e to shipped behavior + adopt e2e struc…
sundram-bruno a91a588
refactor(oc-docs): eager capability check + note slot/toggle gaps (BR…
sundram-bruno c8c20b6
Merge remote-tracking branch 'upstream/main' into feat/bru-3572-top-n…
sundram-bruno b3bd08a
test(oc-docs): migrate request-errors e2e to the page-object structur…
sundram-bruno dbb954f
refactor(oc-docs): align topbar to the team folder conventions (BRU-3…
sundram-bruno e1125b0
Merge main into topbar branch
sundram-bruno 960e2d0
Rewrite pages.fixture comment to document present fixtures
sundram-bruno d2f7ed8
Assert the header bounding box exists before checking its position
sundram-bruno d32de6f
Guard the header bounding box with an explicit null check
sundram-bruno ba50c29
Move getInitials into utils/common.ts
sundram-bruno d8e9b69
Drop the oc- prefix from topbar class names
sundram-bruno f5bf208
Move Topbar into src/ui and strip stylesheet comments
sundram-bruno ee6027f
Select the sidebar toggle by test id
sundram-bruno 2a72ce2
Add a 2px gap between the brand name and version
sundram-bruno 4558718
Move Topbar back to components/
sundram-bruno File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
30 changes: 30 additions & 0 deletions
30
packages/oc-docs/e2e/components/layout/page-header.component.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import type { Page } from '@playwright/test'; | ||
| import { BaseComponent } from '../base.component'; | ||
|
|
||
| /** | ||
| * The page header (sticky top navigation bar). A layout component — present on | ||
| * every screen — so tests get it handed over directly (`pageHeader.brandName`), | ||
| * the same way the sidebar is. | ||
| * | ||
| * Scoped to the `topbar` test id. Exposes the header's own shipped chrome: | ||
| * brand cluster, the Open-in-Bruno CTA, and the menu (hamburger) trigger. | ||
| * Parts are found by test id or accessible name, never by class. | ||
| */ | ||
| export class PageHeaderComponent extends BaseComponent { | ||
| constructor(page: Page) { | ||
| super(page, page.getByTestId('topbar')); | ||
| } | ||
|
|
||
| // Brand cluster | ||
| readonly brand = this.root.getByTestId('brand'); | ||
| readonly brandName = this.root.getByTestId('brand-name'); | ||
| readonly brandVersion = this.root.getByTestId('brand-version'); | ||
| readonly brandInitials = this.root.getByTestId('brand-initials'); | ||
|
|
||
| // Open-in-Bruno CTA | ||
| readonly openInBruno = this.root.getByTestId('open-in-bruno'); | ||
|
|
||
| // Sidebar (hamburger) trigger — shown below desktop. The header only renders | ||
| // the button; the drawer it opens lives elsewhere. | ||
| readonly menuButton = this.root.getByTestId('topbar-menu'); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| import { test, expect } from '../../playwright'; | ||
|
|
||
| /** | ||
| * The page header (sticky top navigation bar): brand cluster + Open-in-Bruno | ||
| * CTA. These tests cover what the mounted app actually renders — the search and | ||
| * env-switcher slots ship empty here, so they aren't exercised in this suite. | ||
| */ | ||
| test.use({ colorScheme: 'light' }); | ||
|
|
||
| const DESKTOP = { width: 1280, height: 900 }; | ||
| const MOBILE = { width: 390, height: 800 }; | ||
|
|
||
| test.describe('Page header', () => { | ||
| test('shows brand (name + version) and a pinned bar', async ({ page, pageHeader }) => { | ||
| await page.setViewportSize(DESKTOP); | ||
| await page.goto('/'); | ||
|
|
||
| await expect(pageHeader.root).toBeVisible(); | ||
| await expect(pageHeader.brandName).toContainText('Bruno Testbench'); | ||
| await expect(pageHeader.brandVersion).toHaveText('v1.0.0'); | ||
|
|
||
| // Sticky: header stays at the top after the page scrolls. | ||
| await page.mouse.wheel(0, 600); | ||
| const box = await pageHeader.root.boundingBox(); | ||
| if (!box) throw new Error('header has no bounding box'); | ||
| expect(box.y).toBeLessThanOrEqual(1); | ||
| }); | ||
|
|
||
| test('shows the initials avatar derived from the collection name', async ({ page, pageHeader }) => { | ||
| await page.setViewportSize(DESKTOP); | ||
| await page.goto('/'); | ||
|
|
||
| // sampleCollection name is "Bruno Testbench" → "BT". | ||
| await expect(pageHeader.brandInitials).toBeVisible(); | ||
| await expect(pageHeader.brandInitials).toHaveText('BT'); | ||
| }); | ||
|
|
||
| test('Open-in-Bruno CTA deep-links via bruno:// and is pinned right', async ({ page, pageHeader }) => { | ||
| await page.setViewportSize(DESKTOP); | ||
| await page.goto('/'); | ||
|
|
||
| await expect(pageHeader.openInBruno).toBeVisible(); | ||
| const href = await pageHeader.openInBruno.getAttribute('href'); | ||
| expect(href).toMatch(/^bruno:\/\/app\/collection\/import\/git\?url=/); | ||
|
|
||
| // CTA hugs the right edge (within the 20px bar padding), not the brand. | ||
| const headerBox = await pageHeader.root.boundingBox(); | ||
| const ctaBox = await pageHeader.openInBruno.boundingBox(); | ||
| const brandBox = await pageHeader.brand.boundingBox(); | ||
| expect((headerBox!.x + headerBox!.width) - (ctaBox!.x + ctaBox!.width)).toBeLessThanOrEqual(24); | ||
| expect(ctaBox!.x).toBeGreaterThan(brandBox!.x + brandBox!.width + 100); | ||
| }); | ||
|
|
||
| test('mobile condenses: hamburger shows, CTA hidden, brand compact', async ({ page, pageHeader }) => { | ||
| await page.setViewportSize(MOBILE); | ||
| await page.goto('/'); | ||
|
|
||
| // Below desktop the sidebar trigger appears. | ||
| await expect(pageHeader.menuButton).toBeVisible(); | ||
| // Open-in-Bruno is desktop-only (no Bruno desktop app on mobile). | ||
| await expect(pageHeader.openInBruno).toHaveCount(0); | ||
|
|
||
| // Compact brand: avatar + "Docs" only — no full name, no version. | ||
| await expect(pageHeader.brandName).toHaveText('Docs'); | ||
| await expect(pageHeader.brandVersion).toHaveCount(0); | ||
| await expect(pageHeader.root).not.toContainText('Bruno Testbench'); | ||
|
|
||
| // No horizontal overflow. | ||
| const scrollW = await page.evaluate(() => document.documentElement.scrollWidth); | ||
| expect(scrollW).toBeLessThanOrEqual(MOBILE.width + 1); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| import React from 'react'; | ||
|
|
||
| /** Bruno mascot — fixed brand colours (a brand mark, not a themeable surface). | ||
| * Used inside the Open-in-Bruno CTA. */ | ||
| export const BrunoGlyph: React.FC = () => ( | ||
| <svg viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> | ||
| <g> | ||
| <path | ||
| fill="#F4AA41" | ||
| d="M23.5,14.5855l-4.5,1.75l-7.25,8.5l-4.5,10.75l2,5.25c1.2554,3.7911,3.5231,7.1832,7.25,10l2.5-3.3333 c0,0,3.8218,7.7098,10.7384,8.9598c0,0,10.2616,1.936,15.5949-0.8765c3.4203-1.8037,4.4167-4.4167,4.4167-4.4167l3.4167-3.4167 l1.5833,2.3333l2.0833-0.0833l5.4167-7.25L64,37.3355l-0.1667-4.5l-2.3333-5.5l-4.8333-7.4167c0,0-2.6667-4.9167-8.1667-3.9167 c0,0-6.5-4.8333-11.8333-4.0833S32.0833,10.6688,23.5,14.5855z" | ||
| /> | ||
| <polygon | ||
| fill="#EA5A47" | ||
| points="36,47.2521 32.9167,49.6688 30.4167,49.6688 30.3333,53.5021 31.0833,57.0021 32.1667,58.9188 35,60.4188 39.5833,59.8355 41.1667,58.0855 42.1667,53.8355 41.9167,49.8355 39.9167,50.0855" | ||
| /> | ||
| <polygon | ||
| fill="#3F3F3F" | ||
| points="32.5,36.9188 30.9167,40.6688 33.0833,41.9188 34.3333,42.4188 38.6667,42.5855 41.5833,40.3355 39.8333,37.0855" | ||
| /> | ||
| </g> | ||
| <g> | ||
| <path | ||
| fill="#000000" | ||
| d="M29.5059,30.1088c0,0-1.8051,1.2424-2.7484,0.6679c-0.9434-0.5745-1.2424-1.8051-0.6679-2.7484 s1.805-1.2424,2.7484-0.6679S29.5059,30.1088,29.5059,30.1088z" | ||
| /> | ||
| <path | ||
| fill="none" | ||
| stroke="#000000" | ||
| strokeLinecap="round" | ||
| strokeLinejoin="round" | ||
| strokeMiterlimit="10" | ||
| strokeWidth="2" | ||
| d="M33.1089,37.006h6.1457c0.4011,0,0.7634,0.2397,0.9203,0.6089l1.1579,2.7245l-2.1792,1.1456 c-0.6156,0.3236-1.3654-0.0645-1.4567-0.754" | ||
| /> | ||
| <path | ||
| fill="none" | ||
| stroke="#000000" | ||
| strokeLinecap="round" | ||
| strokeLinejoin="round" | ||
| strokeMiterlimit="10" | ||
| strokeWidth="2" | ||
| d="M34.7606,40.763c-0.1132,0.6268-0.7757,0.9895-1.3647,0.7471l-2.3132-0.952l1.0899-2.9035 c0.1465-0.3901,0.5195-0.6486,0.9362-0.6486" | ||
| /> | ||
| <path | ||
| fill="none" | ||
| stroke="#000000" | ||
| strokeLinecap="round" | ||
| strokeLinejoin="round" | ||
| strokeMiterlimit="10" | ||
| strokeWidth="2" | ||
| d="M30.4364,50.0268c0,0-0.7187,8.7934,3.0072,9.9375c2.6459,0.8125,5.1497,0.5324,6.0625-0.25 c0.875-0.75,2.6323-4.4741,1.8267-9.6875" | ||
| /> | ||
| <path | ||
| fill="#000000" | ||
| d="M44.2636,30.1088c0,0,1.805,1.2424,2.7484,0.6679c0.9434-0.5745,1.2424-1.8051,0.6679-2.7484 c-0.5745-0.9434-1.805-1.2424-2.7484-0.6679C43.9881,27.9349,44.2636,30.1088,44.2636,30.1088z" | ||
| /> | ||
| <path | ||
| fill="none" | ||
| stroke="#000000" | ||
| strokeLinecap="round" | ||
| strokeLinejoin="round" | ||
| strokeMiterlimit="10" | ||
| strokeWidth="2" | ||
| d="M25.6245,42.8393c-0.475,3.6024,2.2343,5.7505,4.2847,6.8414c1.1968,0.6367,2.6508,0.5182,3.7176-0.3181l2.581-2.0233l2.581,2.0233 c1.0669,0.8363,2.5209,0.9548,3.7176,0.3181c2.0504-1.0909,4.7597-3.239,4.2847-6.8414" | ||
| /> | ||
| <path | ||
| fill="none" | ||
| stroke="#000000" | ||
| strokeLinecap="round" | ||
| strokeLinejoin="round" | ||
| strokeMiterlimit="10" | ||
| strokeWidth="2" | ||
| d="M19.9509,28.3572c-2.3166,5.1597-0.5084,13.0249,0.119,15.3759c0.122,0.4571,0.0755,0.9355-0.1271,1.3631l-1.9874,4.1937 c-0.623,1.3146-2.3934,1.5533-3.331,0.4409c-3.1921-3.7871-8.5584-11.3899-6.5486-16.686 c7.0625-18.6104,15.8677-18.1429,15.8677-18.1429c2.8453-1.9336,13.1042-6.9375,24.8125,0.875c0,0,8.6323-1.7175,14.9375,16.9375 c1.8036,5.3362-3.4297,12.8668-6.5506,16.6442c-0.9312,1.127-2.7162,0.8939-3.3423-0.4272l-1.9741-4.1656 c-0.2026-0.4275-0.2491-0.906-0.1271-1.3631c0.6275-2.3509,2.4356-10.2161,0.119-15.3759" | ||
| /> | ||
| <path | ||
| fill="none" | ||
| stroke="#000000" | ||
| strokeLinecap="round" | ||
| strokeLinejoin="round" | ||
| strokeMiterlimit="10" | ||
| strokeWidth="2" | ||
| d="M52.6309,46.4628c0,0-3.0781,6.7216-7.8049,8.2712" | ||
| /> | ||
| <path | ||
| fill="none" | ||
| stroke="#000000" | ||
| strokeLinecap="round" | ||
| strokeLinejoin="round" | ||
| strokeMiterlimit="10" | ||
| strokeWidth="2" | ||
| d="M19.437,46.969c0,0,3.0781,6.0823,7.8049,7.632" | ||
| /> | ||
| <line | ||
| x1="36.2078" | ||
| x2="36.2078" | ||
| y1="47.3393" | ||
| y2="44.3093" | ||
| fill="none" | ||
| stroke="#000000" | ||
| strokeLinecap="round" | ||
| strokeLinejoin="round" | ||
| strokeMiterlimit="10" | ||
| strokeWidth="2" | ||
| /> | ||
| </g> | ||
| </svg> | ||
| ); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import React from 'react'; | ||
| import { baseIconProps } from './baseIconProps'; | ||
|
|
||
| /** Three bars — Topbar sidebar (hamburger) toggle. */ | ||
| export const HamburgerIcon: React.FC = () => ( | ||
| <svg {...baseIconProps}> | ||
| <path d="M3 6h18M3 12h18M3 18h18" /> | ||
| </svg> | ||
| ); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import React from 'react'; | ||
|
|
||
| /** Vertical ellipsis — Topbar mobile overflow trigger. */ | ||
| export const OverflowIcon: React.FC = () => ( | ||
| <svg width={20} height={20} viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"> | ||
| <circle cx="12" cy="5" r="1.6" /> | ||
| <circle cx="12" cy="12" r="1.6" /> | ||
| <circle cx="12" cy="19" r="1.6" /> | ||
| </svg> | ||
| ); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import React from 'react'; | ||
| import { baseIconProps } from './baseIconProps'; | ||
|
|
||
| /** Magnifying glass — Topbar search toggle. */ | ||
| export const SearchIcon: React.FC = () => ( | ||
| <svg {...baseIconProps}> | ||
| <circle cx="11" cy="11" r="7" /> | ||
| <path d="m21 21-4.3-4.3" /> | ||
| </svg> | ||
| ); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,6 @@ | ||
| export * from './SearchIcon'; | ||
| export * from './HamburgerIcon'; | ||
| export * from './OverflowIcon'; | ||
| export * from './BrunoGlyph'; | ||
| export * from './GlobeIcon'; | ||
| export * from './BookIcon'; |
31 changes: 0 additions & 31 deletions
31
packages/oc-docs/src/components/Docs/Sidebar/FetchInBrunoButton.tsx
This file was deleted.
Oops, something went wrong.
16 changes: 16 additions & 0 deletions
16
packages/oc-docs/src/components/InitialsAvatar/InitialsAvatar.spec.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import React from 'react'; | ||
| import { renderToStaticMarkup } from 'react-dom/server'; | ||
| import { describe, it, expect } from 'vitest'; | ||
| import InitialsAvatar from './InitialsAvatar'; | ||
|
|
||
| describe('InitialsAvatar', () => { | ||
| it('renders the initials for a multi-word collection name', () => { | ||
| const html = renderToStaticMarkup(<InitialsAvatar collectionName="Hotel Booking API" />); | ||
| expect(html).toContain('HB'); | ||
| }); | ||
|
|
||
| it('renders a single letter for a one-word name', () => { | ||
| const html = renderToStaticMarkup(<InitialsAvatar collectionName="Echo" />); | ||
| expect(html).toContain('>E<'); | ||
| }); | ||
| }); |
23 changes: 23 additions & 0 deletions
23
packages/oc-docs/src/components/InitialsAvatar/InitialsAvatar.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| import React from 'react'; | ||
| import { getInitials } from '../../utils/common'; | ||
| import { StyledWrapper } from './StyledWrapper'; | ||
|
|
||
| export interface InitialsAvatarProps { | ||
| collectionName: string; | ||
| testId?: string; | ||
| } | ||
|
|
||
| /** | ||
| * Default brand mark: a rounded badge showing the collection initials over the | ||
| * Bruno amber gradient. | ||
| */ | ||
| const InitialsAvatar: React.FC<InitialsAvatarProps> = ({ | ||
| collectionName, | ||
| testId = 'brand-initials', | ||
| }) => ( | ||
| <StyledWrapper aria-hidden="true" data-testid={testId}> | ||
| {getInitials(collectionName)} | ||
| </StyledWrapper> | ||
| ); | ||
|
|
||
| export default InitialsAvatar; |
19 changes: 19 additions & 0 deletions
19
packages/oc-docs/src/components/InitialsAvatar/StyledWrapper.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import styled from '@emotion/styled'; | ||
|
|
||
| export const StyledWrapper = styled.span` | ||
| display: inline-flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| width: 26px; | ||
| height: 26px; | ||
| flex: none; | ||
| border-radius: var(--oc-border-radius-base); | ||
| background: linear-gradient(135deg, #d37f17 0%, #dc9741 100%); | ||
| color: #fff; | ||
| font-family: var(--font-mono); | ||
| font-size: var(--oc-font-size-xs); | ||
| font-weight: 700; | ||
| line-height: 1; | ||
| letter-spacing: -0.02em; | ||
| user-select: none; | ||
| `; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.