Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
f2c4298
chore: setup chrome navigation package
weronikaolejniczak Jul 15, 2025
06c1649
feat: add side navigation components
weronikaolejniczak Jul 16, 2025
b45b768
feat: remove layout navigation panel
weronikaolejniczak Jul 16, 2025
a5c0a42
fix: make navigation layout component flex
weronikaolejniczak Jul 16, 2025
7375f35
fix(a11y): add focus border to the application root
weronikaolejniczak Jul 16, 2025
5a2a29c
chore: move old navigational stories under "OldNavigation"
weronikaolejniczak Jul 16, 2025
e66051d
chore: add navigation stories
weronikaolejniczak Jul 16, 2025
8b9e82d
fix: pass a missing isCollapsed to nested secondary menu primary item
weronikaolejniczak Jul 16, 2025
aafd018
fix: use EuiButtonIcon and adjust the spacing for the back button
weronikaolejniczak Jul 16, 2025
6f6b17c
feat: display max 5 footer items
weronikaolejniczak Jul 16, 2025
502eade
fix: use appropriate semantics for the layout cell and navigation comp
weronikaolejniczak Jul 16, 2025
9590610
chore: add a todo comment
weronikaolejniczak Jul 16, 2025
6c6f2f4
fix(a11y): use button for popover trigger
weronikaolejniczak Jul 17, 2025
c73a0ed
feat: increase primary menu item limit to 10
weronikaolejniczak Jul 17, 2025
10e221b
feat: force collapsed mode on mobile
weronikaolejniczak Jul 17, 2025
cb26c8b
fix: adjust padding and top position for dark mode in SecondaryMenu
weronikaolejniczak Jul 17, 2025
0a4717d
refactor: rename hasSubmenu to getHasSubmenu
weronikaolejniczak Jul 17, 2025
45ab539
refactor: move popover to a separate folder
weronikaolejniczak Jul 17, 2025
1fe0cda
feat: handle Home and End keys in a popover
weronikaolejniczak Jul 17, 2025
11c4993
chore: add SkipLink to the Layout story
weronikaolejniczak Jul 17, 2025
9e71983
refactor: remove NavigationProvider from index.ts
weronikaolejniczak Jul 17, 2025
ed90711
fix(a11y): add a side nav footer label
weronikaolejniczak Jul 17, 2025
ac531bb
fix(a11y): remove incorrect aria attr in footer item
weronikaolejniczak Jul 17, 2025
092435d
fix: pass href to side nav footer item
weronikaolejniczak Jul 17, 2025
3edf133
fix(a11y): add label to the popover dialog
weronikaolejniczak Jul 17, 2025
b3371af
refactor: remove redundant useClickOutside hook
weronikaolejniczak Jul 18, 2025
f86771c
refactor: move useHoverTimeout to a separate file
weronikaolejniczak Jul 18, 2025
b957f2d
refactor: move usePopoverHover to a separate file
weronikaolejniczak Jul 18, 2025
303673c
refactor: move useNestedMenu to NestedSecondaryMenu folder
weronikaolejniczak Jul 18, 2025
6fd1546
refactor: rename and use usePersistentPopover hook
weronikaolejniczak Jul 18, 2025
dd9c1cb
refactor: move utility functions to separate files
weronikaolejniczak Jul 18, 2025
272ce10
refactor: rename "constants" folder to "mocks"
weronikaolejniczak Jul 18, 2025
c9e46fd
feat: support entering popover on Space
weronikaolejniczak Jul 18, 2025
4e733c4
wip: roving index
weronikaolejniczak Jul 18, 2025
66af8ee
basic kibana integration
Dosant Jul 18, 2025
1a3541b
fixes, clean up
Dosant Jul 18, 2025
8bfe28c
fallback euiCollapsibleNavOffset
Dosant Jul 18, 2025
3159de7
improve setWidth integration
Dosant Jul 18, 2025
f78c0e2
remove comment
Dosant Jul 18, 2025
737de76
[CI] Auto-commit changed files from 'node scripts/build_plugin_list_d…
kibanamachine Jul 21, 2025
d76d4b7
Update src/core/packages/chrome/navigation/kibana.jsonc
weronikaolejniczak Jul 22, 2025
56a8808
fix types
weronikaolejniczak Jul 23, 2025
ce3c862
[CI] Auto-commit changed files from 'node scripts/yarn_deduplicate'
kibanamachine Jul 24, 2025
dc1091f
fix: remove a colliding classname for primary menu items
weronikaolejniczak Jul 24, 2025
dcfbe3c
fix: update the primary menu font properties
weronikaolejniczak Jul 24, 2025
a594ec0
Merge branch 'main' of github.com:elastic/kibana into feat/global-nav…
Dosant Jul 28, 2025
9f70c39
post merge fix
Dosant Jul 28, 2025
d4c8f51
Merge branch 'main' of github.com:elastic/kibana into feat/global-nav…
Dosant Jul 29, 2025
cb54c94
Merge branch 'main' into feat/global-navigation-components
weronikaolejniczak Jul 29, 2025
30c3ff2
refactor: reuse fallbackContentQueries between browser and navigation…
weronikaolejniczak Jul 30, 2025
7be1bc4
refactor(navigation): add data-test-subj and allow spreading props
weronikaolejniczak Jul 30, 2025
b8ecf6c
Merge branch 'main' into feat/global-navigation-components
elasticmachine Jul 31, 2025
e33101f
fix(navigation): use id instead of href and random
weronikaolejniczak Jul 31, 2025
b2f4480
refactor: rename and move fallbackContentQueries to layout constants pkg
weronikaolejniczak Jul 31, 2025
6c2dd39
[CI] Auto-commit changed files from 'node scripts/notice'
kibanamachine Jul 31, 2025
fbfcef6
fix(navigation): remove useMenuItemClick hook
weronikaolejniczak Jul 31, 2025
1ba23ec
fix(navigation): remove preventDefaults
weronikaolejniczak Jul 31, 2025
f516feb
refactor(navigation): remove the I prefix from interfaces
weronikaolejniczak Jul 31, 2025
e7b4b81
refactor(navigation): remove explicit props that can be spread
weronikaolejniczak Jul 31, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ src/core/packages/chrome/browser-mocks @elastic/appex-sharedux
src/core/packages/chrome/layout/core-chrome-layout @elastic/appex-sharedux
src/core/packages/chrome/layout/core-chrome-layout-components @elastic/appex-sharedux
src/core/packages/chrome/layout/core-chrome-layout-constants @elastic/appex-sharedux
src/core/packages/chrome/navigation @elastic/eui-team
src/core/packages/config/server-internal @elastic/kibana-core
src/core/packages/custom-branding/browser @elastic/appex-sharedux
src/core/packages/custom-branding/browser-internal @elastic/appex-sharedux
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@
"@kbn/core-chrome-layout": "link:src/core/packages/chrome/layout/core-chrome-layout",
"@kbn/core-chrome-layout-components": "link:src/core/packages/chrome/layout/core-chrome-layout-components",
"@kbn/core-chrome-layout-constants": "link:src/core/packages/chrome/layout/core-chrome-layout-constants",
"@kbn/core-chrome-navigation": "link:src/core/packages/chrome/navigation",
"@kbn/core-config-server-internal": "link:src/core/packages/config/server-internal",
"@kbn/core-custom-branding-browser": "link:src/core/packages/custom-branding/browser",
"@kbn/core-custom-branding-browser-internal": "link:src/core/packages/custom-branding/browser-internal",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import React, { FC, useState, useEffect } from 'react';
import useObservable from 'react-use/lib/useObservable';
import { EuiScreenReaderLive, EuiSkipLink } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { MAIN_CONTENT_SELECTORS } from '@kbn/core-chrome-layout-constants';

import type { InternalApplicationStart } from '@kbn/core-application-browser-internal';
import type { HeaderProps } from './header';
Expand Down Expand Up @@ -57,18 +58,12 @@ export const ScreenReaderRouteAnnouncements: FC<{
);
};

const fallbackContentQueries = [
'main', // Ideal target for all plugins using KibanaPageTemplate
'[role="main"]', // Fallback for plugins using deprecated EuiPageContent
'.kbnAppWrapper', // Last-ditch fallback for all plugins regardless of page template
];

export const SkipToMainContent = () => {
return (
<EuiSkipLink
position="fixed"
destinationId="" // TODO: Potentially allow this to be customizable per-plugin
fallbackDestination={fallbackContentQueries}
fallbackDestination={MAIN_CONTENT_SELECTORS}
overrideLinkBehavior
href="" // Render a button
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,19 @@ import React, { FunctionComponent } from 'react';
import useObservable from 'react-use/lib/useObservable';
import type { BehaviorSubject } from 'rxjs';
import { css, Global } from '@emotion/react';
// import {
// LOGO,
// PRIMARY_MENU_FOOTER_ITEMS,
// PRIMARY_MENU_ITEMS,
// // eslint-disable-next-line @kbn/imports/no_boundary_crossing
// } from '@kbn/core-chrome-navigation/src/mocks/observability';
// import { Navigation as NavigationComponent } from '@kbn/core-chrome-navigation';
import {
LOGO,
PRIMARY_MENU_FOOTER_ITEMS,
PRIMARY_MENU_ITEMS,
// eslint-disable-next-line @kbn/imports/no_boundary_crossing
} from '@kbn/core-chrome-navigation/src/mocks/observability';
import { Navigation as NavigationComponent } from '@kbn/core-chrome-navigation';
import { SideNavV2CollapseButton } from './collapse_button';

// const demoItems = {
// primaryItems: PRIMARY_MENU_ITEMS,
// footerItems: PRIMARY_MENU_FOOTER_ITEMS,
// };
const demoItems = {
primaryItems: PRIMARY_MENU_ITEMS,
footerItems: PRIMARY_MENU_FOOTER_ITEMS,
};

interface CollapsibleNavigationProps {
toggle: (isVisible: boolean) => void;
Expand All @@ -41,16 +41,15 @@ export const FixedLayoutProjectSideNavV2: FunctionComponent<CollapsibleNavigatio
<>
<SideNavV2CollapseButton isCollapsed={isCollapsed} toggle={toggle} />
<CollapsibleNavigationFlyout>
{
({ setWidth }) => null
// <NavigationComponent
// isCollapsed={isCollapsed}
// items={demoItems}
// logoLabel={LOGO.label}
// logoType={LOGO.logoType}
// setWidth={setWidth}
// />
}
{({ setWidth }) => (
<NavigationComponent
isCollapsed={isCollapsed}
items={demoItems}
logoLabel={LOGO.label}
logoType={LOGO.logoType}
setWidth={setWidth}
/>
)}
</CollapsibleNavigationFlyout>
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,37 +7,37 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

// import useObservable from 'react-use/lib/useObservable';
// import { useLayoutUpdate } from '@kbn/core-chrome-layout-components';
import React from 'react';
import useObservable from 'react-use/lib/useObservable';
import { useLayoutUpdate } from '@kbn/core-chrome-layout-components';
import React, { useCallback } from 'react';
import { BehaviorSubject } from 'rxjs';
import { css, Global } from '@emotion/react';
// import { Navigation as NavigationComponent } from '@kbn/core-chrome-navigation';
// import {
// LOGO,
// PRIMARY_MENU_FOOTER_ITEMS,
// PRIMARY_MENU_ITEMS,
// // eslint-disable-next-line @kbn/imports/no_boundary_crossing
// } from '@kbn/core-chrome-navigation/src/mocks/observability';
import { Navigation as NavigationComponent } from '@kbn/core-chrome-navigation';
import {
LOGO,
PRIMARY_MENU_FOOTER_ITEMS,
PRIMARY_MENU_ITEMS,
// eslint-disable-next-line @kbn/imports/no_boundary_crossing
} from '@kbn/core-chrome-navigation/src/mocks/observability';

// const demoItems = {
// primaryItems: PRIMARY_MENU_ITEMS,
// footerItems: PRIMARY_MENU_FOOTER_ITEMS,
// };
const demoItems = {
primaryItems: PRIMARY_MENU_ITEMS,
footerItems: PRIMARY_MENU_FOOTER_ITEMS,
};

export interface Props {
isCollapsed$: BehaviorSubject<boolean>;
}

export const GridLayoutProjectSideNavV2 = ({ isCollapsed$ }: Props) => {
// const isCollapsed = useObservable(isCollapsed$, isCollapsed$.getValue());
// const updateLayout = useLayoutUpdate();
// const setWidth = useCallback(
// (width: number) => {
// updateLayout({ navigationWidth: width });
// },
// [updateLayout]
// );
const isCollapsed = useObservable(isCollapsed$, isCollapsed$.getValue());
const updateLayout = useLayoutUpdate();
const setWidth = useCallback(
(width: number) => {
updateLayout({ navigationWidth: width });
},
[updateLayout]
);

return (
<>
Expand All @@ -49,13 +49,13 @@ export const GridLayoutProjectSideNavV2 = ({ isCollapsed$ }: Props) => {
}
`}
/>
{/* <NavigationComponent*/}
{/* isCollapsed={isCollapsed}*/}
{/* items={demoItems}*/}
{/* logoLabel={LOGO.label}*/}
{/* logoType={LOGO.logoType}*/}
{/* setWidth={setWidth}*/}
{/* />*/}
<NavigationComponent
isCollapsed={isCollapsed}
items={demoItems}
logoLabel={LOGO.label}
logoType={LOGO.logoType}
setWidth={setWidth}
/>
</>
);
};
3 changes: 3 additions & 0 deletions src/core/packages/chrome/browser-internal/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@
"@kbn/core-user-profile-common",
"@kbn/core-theme-browser-internal",
"@kbn/shared-ux-chrome-navigation",
"@kbn/core-chrome-navigation",
"@kbn/core-chrome-layout-components",
"@kbn/core-chrome-layout-constants"
],
"exclude": [
"target/**/*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

/**
* The ID of the main scroll container in the application.
* `document.getElementById(APP_MAIN_SCROLL_CONTAINER_ID)` can be used to find the main scroll container
* `document.getElementById(APP_MAIN_SCROLL_CONTAINER_ID)` can be used to find the main scroll container.
*/
export const APP_MAIN_SCROLL_CONTAINER_ID = 'app-main-scroll';

Expand All @@ -18,3 +18,15 @@ export const APP_MAIN_SCROLL_CONTAINER_ID = 'app-main-scroll';
* This div is rendered by the `AppFixedViewport` component on the top of the application area and can be used to render fixed elements that should not scroll with the main content.
*/
export const APP_FIXED_VIEWPORT_ID = 'app-fixed-viewport';

/**
* The ID of the main content container in the application, regardless of the type of the layout used.
* `document.querySelector(MAIN_CONTENT_SELECTORS.join(','))` can be used to find the main content container.
*
* TODO: Potentially allow this to be customizable per-plugin
*/
export const MAIN_CONTENT_SELECTORS = [
'main', // Ideal target for all plugins using KibanaPageTemplate
'[role="main"]', // Fallback for plugins using deprecated EuiPageContent
'.kbnAppWrapper', // Last-ditch fallback for all plugins regardless of page template
];
168 changes: 168 additions & 0 deletions src/core/packages/chrome/navigation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# @kbn/core-chrome-navigation

An adaptive side navigation system built with [Elastic UI](https://eui.elastic.co/). Features responsive design, nested menu structures, and accessibility-first user experience. Exported as a self-contained widget.

| Expanded mode | Collapsed mode |
| ----------------------------- | ------------------------------ |
| ![image](./expanded-mode.png) | ![image](./collapsed-mode.png) |

## Features

- **Responsive design** - automatically adapts between collapsed and expanded states based on screen size.
- **Smart menu system** - dynamic "More" menu that consolidates overflow items during window resize.
- **Nested navigation** - multi-level menu support with nested popover panels in collapsed mode.
- **Accessibility-first** - WCAG-compliant with proper ARIA labels, keyboard navigation, and screen reader support.
- **Modular architecture** - composable components with clean separation of concerns. Exported as a self-contained widget.
- **Dark mode** and **High contrast mode support**

## Usage

### Basic setup

```tsx
import { Navigation } from '@kbn/core-chrome-navigation';

const navigationItems = {
primaryItems: [
{
id: 'dashboard',
label: 'Dashboard',
icon: 'dashboardApp',
href: '/dashboard',
},
{
id: 'analytics',
label: 'Analytics',
icon: 'graphApp',
href: '/analytics',
secondaryItems: [
{
id: 'overview',
label: 'Overview',
href: '/analytics',
},
{
id: 'reports',
label: 'Reports',
href: '/analytics/reports',
},
{
id: 'metrics',
label: 'Metrics',
href: '/analytics/metrics',
},
],
},
],
footerItems: [
{
id: 'settings',
label: 'Settings',
icon: 'gear',
href: '/settings',
},
],
};

function App() {
const [isCollapsed, setIsCollapsed] = useState(false);

return (
<div className="app">
<TopBar isCollapsed={isCollapsed} setIsCollapsed={setIsCollapsed} />
<Navigation
title="Observability"
logo="observabilityApp"
items={navigationItems}
isCollapsed={isCollapsed}
/>
<main className="app-content">{/* Your application content */}</main>
</div>
);
}
```

### Navigation structure

The navigation is configured by passing the structure to `items` prop. The structure is an array of `MenuItem` objects, where each `MenuItem` can have an optional `sections` array of `Section` objects.

```js
export const navigationItems = {
primaryItems: [
// Simple menu item
{
id: 'overview',
label: 'Overview',
iconType: 'info',
href: '/overview',
},
// Menu item with nested sections
{
id: 'analytics',
label: 'Analytics',
iconType: 'graphApp',
href: '/analytics/reports',
sections: [
{
id: 'reports-section',
label: 'Reports', // or null for unlabeled sections
items: [
{
id: 'overview',
label: 'Overview',
href: '/analytics/reports', // has the same `href` as the parent item
},
{
id: 'sales-report',
label: 'Sales report',
href: '/analytics/sales',
},
{
id: 'traffic-report',
label: 'Traffic report',
href: '/analytics/traffic',
external: true, // opens in new tab and shows an "external resource" icon
},
],
},
],
},
],
footerItems: [
{
id: 'settings',
label: 'Settings', // it's required for accessibility purposes
iconType: 'gear',
href: '/settings',
},
],
};
```

## Development

1. Install dependencies:

```bash
yarn kbn bootstrap
```

2. Start Storybook:

```bash
yarn storybook shared_ux
```

Open [http://localhost:9001](http://localhost:9001) to view the application.

## Testing

The project includes comprehensive test coverage using Jest and RTL.

Run tests with:

```bash
yarn test:jest src/core/packages/chrome/navigation # Run all tests
yarn test:jest src/core/packages/chrome/navigation --watch # Run in watch mode
yarn test:jest src/core/packages/chrome/navigation --coverage # Generate coverage report
```
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions src/core/packages/chrome/navigation/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

export { Navigation } from './src/components/navigation';
export { useNavigation } from './src/hooks/use_navigation';
Loading