Skip to content

Commit 9ddc852

Browse files
Dosantkibanamachine
authored andcommitted
Add Chrome Next foundation behind feature flag (elastic#268201)
## Summary Part of elastic/kibana-team#3115 Extracted from elastic#259318 Adds the first Chrome Next foundation slice behind the default-off `core.chrome.next` feature flag. This PR intentionally keeps the shipped surface small: - Adds `@kbn/core-chrome-feature-flags` with `NEXT_CHROME_FEATURE_FLAG_KEY` and `isNextChrome(featureFlags)`. - Exposes `core.chrome.next.isEnabled` as the initial Chrome Next public API. - Wires the project layout to render a minimal `ChromeNextGlobalHeader` shell when the flag is enabled. - Adds package-local documentation for the feature flag utility. Follow-up PRs will add the actual Chrome Next feature slices, such as the context switcher, side navigation changes, user menu, help menu, search, and app-header migration APIs. ## Testing ``` feature_flags.overrides: core.chrome.next: true ``` <img width="2059" height="1193" alt="Screenshot 2026-05-07 at 15 57 58" src="https://github.com/user-attachments/assets/45ddba2d-48de-425b-b155-f9f412d803af" /> --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
1 parent 9ac929c commit 9ddc852

30 files changed

Lines changed: 484 additions & 8 deletions

File tree

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ src/core/packages/chrome/browser-hooks @elastic/appex-sharedux
145145
src/core/packages/chrome/browser-internal @elastic/appex-sharedux
146146
src/core/packages/chrome/browser-internal-types @elastic/appex-sharedux
147147
src/core/packages/chrome/browser-mocks @elastic/appex-sharedux
148+
src/core/packages/chrome/feature-flags @elastic/appex-sharedux
148149
src/core/packages/chrome/layout/core-chrome-layout @elastic/appex-sharedux
149150
src/core/packages/chrome/layout/core-chrome-layout-components @elastic/appex-sharedux
150151
src/core/packages/chrome/layout/core-chrome-layout-constants @elastic/appex-sharedux

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@
359359
"@kbn/core-chrome-browser-hooks": "link:src/core/packages/chrome/browser-hooks",
360360
"@kbn/core-chrome-browser-internal": "link:src/core/packages/chrome/browser-internal",
361361
"@kbn/core-chrome-browser-internal-types": "link:src/core/packages/chrome/browser-internal-types",
362+
"@kbn/core-chrome-feature-flags": "link:src/core/packages/chrome/feature-flags",
362363
"@kbn/core-chrome-layout": "link:src/core/packages/chrome/layout/core-chrome-layout",
363364
"@kbn/core-chrome-layout-components": "link:src/core/packages/chrome/layout/core-chrome-layout-components",
364365
"@kbn/core-chrome-layout-constants": "link:src/core/packages/chrome/layout/core-chrome-layout-constants",

src/core/packages/chrome/browser-components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export { ChromeComponentsProvider } from './src/context';
1111
export type { ChromeComponentsDeps } from './src/context';
1212

1313
export { ClassicHeader } from './src/classic';
14+
export { ChromeNextGlobalHeader } from './src/chrome_next';
1415
export { ProjectHeader } from './src/project';
1516
export { GridLayoutProjectSideNav } from './src/project/sidenav/grid_layout_sidenav';
1617
export { Sidebar } from './src/sidebar';
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
import React from 'react';
11+
import { ChromeNextGlobalHeaderShell } from './global_header_shell';
12+
import { ChromeNextGlobalHeaderLogo } from './global_header_logo';
13+
14+
export const ChromeNextGlobalHeader = React.memo(() => (
15+
<ChromeNextGlobalHeaderShell logo={<ChromeNextGlobalHeaderLogo />} />
16+
));
17+
18+
ChromeNextGlobalHeader.displayName = 'ChromeNextGlobalHeader';
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
import React, { useMemo } from 'react';
11+
import { useEuiTheme } from '@elastic/eui';
12+
import { i18n } from '@kbn/i18n';
13+
import { css } from '@emotion/react';
14+
import { useBasePath, useCustomBranding, useProjectHome } from '../../shared/chrome_hooks';
15+
import { LoadingIndicator } from '../../shared/loading_indicator';
16+
17+
const LOGO_ARIA_LABEL = i18n.translate('core.ui.chrome.globalHeader.logoAriaLabel', {
18+
defaultMessage: 'Elastic home',
19+
});
20+
21+
const useLogoStyles = () => {
22+
const { euiTheme } = useEuiTheme();
23+
24+
return useMemo(
25+
() => css`
26+
display: inline-flex;
27+
align-items: center;
28+
justify-content: center;
29+
width: 32px;
30+
height: 32px;
31+
border-radius: ${euiTheme.border.radius.medium};
32+
color: ${euiTheme.colors.text};
33+
text-decoration: none;
34+
35+
&:hover {
36+
background: ${euiTheme.colors.backgroundBaseInteractiveHover};
37+
}
38+
39+
&:focus-visible {
40+
outline: 2px solid ${euiTheme.colors.primary};
41+
outline-offset: -2px;
42+
}
43+
44+
svg {
45+
width: 20px;
46+
height: 20px;
47+
}
48+
`,
49+
[euiTheme]
50+
);
51+
};
52+
53+
export const ChromeNextGlobalHeaderLogo = React.memo(() => {
54+
const basePath = useBasePath();
55+
const homeHref = basePath.prepend(useProjectHome());
56+
const { logo: customLogo } = useCustomBranding();
57+
const logoStyles = useLogoStyles();
58+
59+
return (
60+
<a
61+
href={homeHref}
62+
aria-label={LOGO_ARIA_LABEL}
63+
data-test-subj="chromeNextGlobalHeaderLogo"
64+
css={logoStyles}
65+
>
66+
<LoadingIndicator customLogo={customLogo} />
67+
</a>
68+
);
69+
});
70+
71+
ChromeNextGlobalHeaderLogo.displayName = 'ChromeNextGlobalHeaderLogo';
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
import type { ReactNode } from 'react';
11+
import React, { useMemo } from 'react';
12+
import { useEuiTheme } from '@elastic/eui';
13+
import { css } from '@emotion/react';
14+
import { COLLAPSED_WIDTH, EXPANDED_WIDTH } from '@kbn/ui-side-navigation';
15+
import { useSideNavWidth } from '@kbn/core-chrome-browser-hooks';
16+
17+
const GLOBAL_HEADER_HEIGHT_PX = 48;
18+
19+
const logoSlot = css({
20+
display: 'flex',
21+
alignItems: 'center',
22+
justifyContent: 'center',
23+
width: 'var(--logo-width)',
24+
height: GLOBAL_HEADER_HEIGHT_PX,
25+
flexShrink: 0,
26+
});
27+
28+
export interface ChromeNextGlobalHeaderShellProps {
29+
logo?: ReactNode;
30+
switcher?: ReactNode;
31+
search?: ReactNode;
32+
help?: ReactNode;
33+
actions?: ReactNode;
34+
userMenu?: ReactNode;
35+
}
36+
37+
const useGlobalHeaderStyles = () => {
38+
const { euiTheme } = useEuiTheme();
39+
40+
return useMemo(() => {
41+
const root = css`
42+
display: flex;
43+
align-items: center;
44+
height: ${GLOBAL_HEADER_HEIGHT_PX}px;
45+
box-sizing: border-box;
46+
padding: 0 ${euiTheme.size.s} 0 0;
47+
background: ${euiTheme.colors.backgroundTransparent};
48+
`;
49+
50+
const leftGroup = css`
51+
display: flex;
52+
align-items: center;
53+
flex-shrink: 0;
54+
`;
55+
56+
const switcherSlot = css`
57+
display: flex;
58+
align-items: center;
59+
gap: ${euiTheme.size.xs};
60+
margin-inline-end: ${euiTheme.size.xs};
61+
`;
62+
63+
const spacer = css`
64+
flex: 1 1 auto;
65+
min-width: 0;
66+
`;
67+
68+
const rightGroup = css`
69+
display: flex;
70+
align-items: center;
71+
flex-shrink: 0;
72+
gap: ${euiTheme.size.s};
73+
`;
74+
75+
const searchSlot = css`
76+
display: flex;
77+
align-items: center;
78+
flex-shrink: 0;
79+
`;
80+
81+
const actionsSlot = css`
82+
display: flex;
83+
align-items: center;
84+
gap: ${euiTheme.size.s};
85+
`;
86+
87+
const helpSlot = css`
88+
display: flex;
89+
align-items: center;
90+
`;
91+
92+
const userMenuSlot = css`
93+
display: flex;
94+
align-items: center;
95+
`;
96+
97+
const separator = css`
98+
width: 1px;
99+
height: 24px;
100+
flex-shrink: 0;
101+
background: ${euiTheme.colors.borderBaseSubdued};
102+
`;
103+
104+
return {
105+
root,
106+
leftGroup,
107+
switcherSlot,
108+
spacer,
109+
rightGroup,
110+
searchSlot,
111+
actionsSlot,
112+
helpSlot,
113+
userMenuSlot,
114+
separator,
115+
};
116+
}, [euiTheme]);
117+
};
118+
119+
export const ChromeNextGlobalHeaderShell = React.memo<ChromeNextGlobalHeaderShellProps>(
120+
({ logo, switcher, search, help, actions, userMenu }) => {
121+
const sideNavWidth = useSideNavWidth();
122+
const styles = useGlobalHeaderStyles();
123+
const logoWidth = sideNavWidth <= COLLAPSED_WIDTH ? COLLAPSED_WIDTH : EXPANDED_WIDTH;
124+
125+
return (
126+
<header css={styles.root} data-test-subj="chromeNextGlobalHeader">
127+
<div css={styles.leftGroup}>
128+
<div css={logoSlot} style={{ '--logo-width': `${logoWidth}px` } as React.CSSProperties}>
129+
{logo}
130+
</div>
131+
{switcher && (
132+
<div css={styles.switcherSlot} data-test-subj="chromeNextGlobalHeaderSwitcher">
133+
{switcher}
134+
</div>
135+
)}
136+
</div>
137+
<div css={styles.separator} />
138+
<div css={styles.spacer} />
139+
<div css={styles.separator} />
140+
<div css={styles.rightGroup}>
141+
{search && (
142+
<div css={styles.searchSlot} data-test-subj="chromeNextGlobalHeaderSearch">
143+
{search}
144+
</div>
145+
)}
146+
{help && (
147+
<div css={styles.helpSlot} data-test-subj="chromeNextGlobalHeaderHelp">
148+
{help}
149+
</div>
150+
)}
151+
{actions && (
152+
<div css={styles.actionsSlot} data-test-subj="chromeNextGlobalHeaderActions">
153+
{actions}
154+
</div>
155+
)}
156+
{userMenu && (
157+
<div css={styles.userMenuSlot} data-test-subj="chromeNextGlobalHeaderUserMenu">
158+
{userMenu}
159+
</div>
160+
)}
161+
</div>
162+
</header>
163+
);
164+
}
165+
);
166+
167+
ChromeNextGlobalHeaderShell.displayName = 'ChromeNextGlobalHeaderShell';
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
export { ChromeNextGlobalHeader } from './global_header';
11+
export { ChromeNextGlobalHeaderShell } from './global_header_shell';
12+
export type { ChromeNextGlobalHeaderShellProps } from './global_header_shell';
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
export { ChromeNextGlobalHeader, ChromeNextGlobalHeaderShell } from './global_header';
11+
export type { ChromeNextGlobalHeaderShellProps } from './global_header';

src/core/packages/chrome/browser-internal/moon.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ dependsOn:
5656
- '@kbn/core-theme-browser-internal'
5757
- '@kbn/core-feature-flags-browser'
5858
- '@kbn/core-feature-flags-browser-mocks'
59+
- '@kbn/core-chrome-feature-flags'
5960
- '@kbn/core-chrome-app-menu-components'
6061
- '@kbn/core-chrome-sidebar-internal'
6162
- '@kbn/core-chrome-sidebar-context'

src/core/packages/chrome/browser-internal/src/chrome_api.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import type { RecentlyAccessedService } from '@kbn/recently-accessed';
1313
import { SidebarServiceProvider } from '@kbn/core-chrome-sidebar-context';
1414
import { ChromeServiceProvider } from '@kbn/core-chrome-browser-context';
1515
import type { SidebarStart } from '@kbn/core-chrome-sidebar';
16+
import type { FeatureFlagsStart } from '@kbn/core-feature-flags-browser';
17+
import { isNextChrome } from '@kbn/core-chrome-feature-flags';
1618
import type { InternalChromeStart } from './types';
1719
import type { ChromeState } from './state/chrome_state';
1820
import type { NavControlsService } from './services/nav_controls';
@@ -36,9 +38,15 @@ export interface ChromeApiDeps {
3638
projectNavigation: ProjectNavigationStart;
3739
};
3840
sidebar: SidebarStart;
41+
featureFlags: FeatureFlagsStart;
3942
}
4043

41-
export function createChromeApi({ state, services, sidebar }: ChromeApiDeps): InternalChromeStart {
44+
export function createChromeApi({
45+
state,
46+
services,
47+
sidebar,
48+
featureFlags,
49+
}: ChromeApiDeps): InternalChromeStart {
4250
const { projectNavigation } = services;
4351

4452
const validateProjectStyle = () => {
@@ -166,6 +174,11 @@ export function createChromeApi({ state, services, sidebar }: ChromeApiDeps): In
166174
>,
167175
getActiveSolutionNavId: () => projectNavigation.getActiveSolutionNavId(),
168176
project,
177+
next: {
178+
get isEnabled() {
179+
return isNextChrome(featureFlags);
180+
},
181+
},
169182
sidebar,
170183
};
171184

0 commit comments

Comments
 (0)