;
/**
diff --git a/src/core/packages/chrome/layout/core-chrome-layout-components/layout_global_css.tsx b/src/core/packages/chrome/layout/core-chrome-layout-components/layout_global_css.tsx
index 525e6e7530c1a..3e289eb6ae458 100644
--- a/src/core/packages/chrome/layout/core-chrome-layout-components/layout_global_css.tsx
+++ b/src/core/packages/chrome/layout/core-chrome-layout-components/layout_global_css.tsx
@@ -26,6 +26,8 @@ export const LayoutGlobalCSS = () => {
sidebarWidth,
applicationTopBarHeight,
applicationBottomBarHeight,
+ applicationMarginBottom,
+ applicationMarginRight,
} = useLayoutState();
const banner = css`
@@ -80,10 +82,17 @@ export const LayoutGlobalCSS = () => {
`;
const application = css`
+ ${layoutVarName('application.marginBottom')}: ${applicationMarginBottom}px;
+ ${layoutVarName('application.marginRight')}: ${applicationMarginRight}px;
+
${layoutVarName('application.top')}: ${bannerHeight + headerHeight}px;
- ${layoutVarName('application.bottom')}: ${layoutVar('footer.height')};
+ ${layoutVarName('application.bottom')}: calc(${layoutVar('footer.height')} + ${layoutVar(
+ 'application.marginBottom'
+ )});
${layoutVarName('application.left')}: ${navigationWidth}px;
- ${layoutVarName('application.right')}: ${sidebarWidth}px;
+ ${layoutVarName('application.right')}: calc(${layoutVar(
+ 'application.marginRight'
+ )} + ${sidebarWidth}px);
${layoutVarName('application.height')}: calc(
100vh - ${layoutVar('application.top')} - ${layoutVar('application.bottom')}
);
diff --git a/src/core/packages/chrome/layout/core-chrome-layout-components/layout_state_context.tsx b/src/core/packages/chrome/layout/core-chrome-layout-components/layout_state_context.tsx
index f75770d155d17..32451255f6e76 100644
--- a/src/core/packages/chrome/layout/core-chrome-layout-components/layout_state_context.tsx
+++ b/src/core/packages/chrome/layout/core-chrome-layout-components/layout_state_context.tsx
@@ -63,6 +63,8 @@ export const LayoutStateProvider = ({ children, ...props }: LayoutStateProps) =>
applicationBottomBarHeight: hasApplicationBottomBar
? layoutConfig.applicationBottomBarHeight ?? 0
: 0,
+ applicationMarginRight: layoutConfig.applicationMarginRight ?? 0,
+ applicationMarginBottom: layoutConfig.applicationMarginBottom ?? 0,
};
return
{children};
diff --git a/src/core/packages/chrome/layout/core-chrome-layout-components/moon.yml b/src/core/packages/chrome/layout/core-chrome-layout-components/moon.yml
index dd0305f128c33..ae35e9abe24fa 100644
--- a/src/core/packages/chrome/layout/core-chrome-layout-components/moon.yml
+++ b/src/core/packages/chrome/layout/core-chrome-layout-components/moon.yml
@@ -19,6 +19,7 @@ project:
sourceRoot: src/core/packages/chrome/layout/core-chrome-layout-components
dependsOn:
- '@kbn/core-chrome-layout-constants'
+ - '@kbn/core-chrome-layout-utils'
tags:
- shared-browser
- package
diff --git a/src/core/packages/chrome/layout/core-chrome-layout-components/tsconfig.json b/src/core/packages/chrome/layout/core-chrome-layout-components/tsconfig.json
index 6367dda4f0cf8..d2ecadb871329 100644
--- a/src/core/packages/chrome/layout/core-chrome-layout-components/tsconfig.json
+++ b/src/core/packages/chrome/layout/core-chrome-layout-components/tsconfig.json
@@ -21,5 +21,6 @@
],
"kbn_references": [
"@kbn/core-chrome-layout-constants",
+ "@kbn/core-chrome-layout-utils",
]
}
diff --git a/src/core/packages/chrome/layout/core-chrome-layout-components/types.ts b/src/core/packages/chrome/layout/core-chrome-layout-components/types.ts
index 40b5f82599732..e3c261efdb560 100644
--- a/src/core/packages/chrome/layout/core-chrome-layout-components/types.ts
+++ b/src/core/packages/chrome/layout/core-chrome-layout-components/types.ts
@@ -7,13 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-import type { EuiThemeColorMode, EuiThemeComputed } from '@elastic/eui';
+import type { UseEuiTheme } from '@elastic/eui';
import type { SerializedStyles } from '@emotion/serialize';
-export type EmotionFn = ({
- euiTheme,
- colorMode,
-}: {
- euiTheme: EuiThemeComputed;
- colorMode: EuiThemeColorMode;
-}) => SerializedStyles;
+export type EmotionFn = (useEuiTheme: UseEuiTheme) => SerializedStyles;
diff --git a/src/core/packages/chrome/layout/core-chrome-layout-constants/index.ts b/src/core/packages/chrome/layout/core-chrome-layout-constants/index.ts
index 42909d6f0122e..51c948dd115e8 100644
--- a/src/core/packages/chrome/layout/core-chrome-layout-constants/index.ts
+++ b/src/core/packages/chrome/layout/core-chrome-layout-constants/index.ts
@@ -41,6 +41,11 @@ export const MAIN_CONTENT_SELECTORS = [
'.kbnAppWrapper', // Last-ditch fallback for all plugins regardless of page template
];
+/**
+ * The gap (in pixels) between the secondary side navigation panel and the main app content.
+ */
+export const SIDE_PANEL_CONTENT_GAP = 8;
+
/**
* The selector for elements that should be included in the focus trap of a flyout.
* This will allow the flyout focus trap to include header and sidenav by default.
diff --git a/src/core/packages/chrome/layout/core-chrome-layout-constants/src/css_variables.ts b/src/core/packages/chrome/layout/core-chrome-layout-constants/src/css_variables.ts
index 86a66b76fe9d0..b7f792b31dc37 100644
--- a/src/core/packages/chrome/layout/core-chrome-layout-constants/src/css_variables.ts
+++ b/src/core/packages/chrome/layout/core-chrome-layout-constants/src/css_variables.ts
@@ -7,6 +7,8 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
+import type React from 'react';
+
export type LayoutComponent =
| 'banner'
| 'header'
@@ -15,7 +17,10 @@ export type LayoutComponent =
| 'sidebar'
| 'application';
export type ApplicationComponent = 'topBar' | 'bottomBar' | 'content';
-export type LayoutProperty = 'top' | 'bottom' | 'left' | 'right' | 'height' | 'width';
+export type LayoutProperty = keyof Pick<
+ React.CSSProperties,
+ 'top' | 'bottom' | 'left' | 'right' | 'height' | 'width' | 'marginBottom' | 'marginRight'
+>;
export type LayoutVarName = `${LayoutComponent}.${LayoutProperty}`;
export type ApplicationVarName = `application.${ApplicationComponent}.${LayoutProperty}`;
diff --git a/src/core/packages/chrome/layout/core-chrome-layout-utils/index.ts b/src/core/packages/chrome/layout/core-chrome-layout-utils/index.ts
index 712c44b3b2926..616dd8bba4b61 100644
--- a/src/core/packages/chrome/layout/core-chrome-layout-utils/index.ts
+++ b/src/core/packages/chrome/layout/core-chrome-layout-utils/index.ts
@@ -20,3 +20,9 @@ export {
scrollBy,
isAtBottomOfPage,
} from './src/scroll';
+
+export {
+ type HighContrastSeparatorOptions,
+ getHighContrastBorder,
+ getHighContrastSeparator,
+} from './src/high_contrast';
diff --git a/src/core/packages/chrome/layout/core-chrome-layout-utils/src/high_contrast.ts b/src/core/packages/chrome/layout/core-chrome-layout-utils/src/high_contrast.ts
new file mode 100644
index 0000000000000..5c0ba923fb0bb
--- /dev/null
+++ b/src/core/packages/chrome/layout/core-chrome-layout-utils/src/high_contrast.ts
@@ -0,0 +1,82 @@
+/*
+ * 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".
+ */
+
+import type { UseEuiTheme } from '@elastic/eui';
+
+/**
+ * Helper function to get container border styles for high contrast mode.
+ * In high contrast mode, renders a solid border. Otherwise, renders based on color mode.
+ *
+ * @param euiThemeContext - EUI theme context
+ * @param highContrastMode - High contrast mode setting
+ * @returns CSS border string
+ */
+export const getHighContrastBorder = (euiThemeContext: UseEuiTheme): string => {
+ const { euiTheme, highContrastMode } = euiThemeContext;
+
+ if (highContrastMode) {
+ return `${euiTheme.border.width.thin} solid ${euiTheme.border.color}`;
+ }
+ if (euiThemeContext.colorMode === 'DARK') {
+ const borderThin = euiTheme.border.thin;
+ return borderThin ? String(borderThin) : 'none';
+ }
+ return 'none';
+};
+
+export interface HighContrastSeparatorOptions {
+ /** The side to place the border separator ('top' or 'bottom'). Default: 'bottom' */
+ side?: 'top' | 'bottom';
+ /** Width of the separator line. Default: theme.size.xl */
+ width?: string;
+ /** Left position for the separator. Default: '0' */
+ left?: string;
+ /** Right position for the separator. Default: '0' */
+ right?: string;
+}
+
+/**
+ * Helper function to get separator border styles for high contrast mode.
+ * In high contrast mode, renders a real border. Otherwise, renders a pseudo-element with subdued styling.
+ *
+ * @param euiTheme - EUI theme object
+ * @param highContrastMode - High contrast mode setting
+ * @param options - Configuration options for the separator
+ * @returns CSS string for the separator
+ */
+export const getHighContrastSeparator = (
+ euiThemeContext: UseEuiTheme,
+ options: HighContrastSeparatorOptions = {}
+): string => {
+ const { euiTheme, highContrastMode } = euiThemeContext;
+
+ const { side = 'bottom', width = euiTheme.size.xl, left = '0', right = '0' } = options;
+
+ const borderSide = side === 'top' ? 'border-top' : 'border-bottom';
+
+ if (highContrastMode) {
+ return `
+ ${borderSide}: ${euiTheme.border.width.thin} solid ${euiTheme.border.color};
+ `;
+ }
+
+ return `
+ &::${side === 'top' ? 'before' : 'after'} {
+ content: '';
+ position: absolute;
+ ${side}: 0;
+ left: ${left};
+ right: ${right};
+ width: ${width};
+ margin: 0 auto;
+ height: ${euiTheme.border.width.thin};
+ background-color: ${euiTheme.colors.borderBaseSubdued};
+ }
+ `;
+};
diff --git a/src/core/packages/chrome/layout/core-chrome-layout/layouts/grid/grid_global_app_style.tsx b/src/core/packages/chrome/layout/core-chrome-layout/layouts/grid/grid_global_app_style.tsx
index 584c46b1a90f1..0bb667da2b6df 100644
--- a/src/core/packages/chrome/layout/core-chrome-layout/layouts/grid/grid_global_app_style.tsx
+++ b/src/core/packages/chrome/layout/core-chrome-layout/layouts/grid/grid_global_app_style.tsx
@@ -17,73 +17,75 @@ import {
} from '@kbn/core-chrome-layout-constants';
import { CommonGlobalAppStyles } from '../common/global_app_styles';
-const globalLayoutStyles = (euiTheme: UseEuiTheme['euiTheme']) => css`
- :root {
- // TODO: these variables are legacy and we keep them for backward compatibility
- // https://github.com/elastic/kibana/issues/225264
+const globalLayoutStyles = (euiThemeContext: UseEuiTheme) => {
+ return css`
+ :root {
+ // TODO: these variables are legacy and we keep them for backward compatibility
+ // https://github.com/elastic/kibana/issues/225264
- // there is no fixed header in the grid layout, so we want to set the offset to 0
- --euiFixedHeadersOffset: 0px;
+ // there is no fixed header in the grid layout, so we want to set the offset to 0
+ --euiFixedHeadersOffset: 0px;
- // height of the header banner
- --kbnHeaderBannerHeight: ${layoutVar('banner.height', '0px')};
+ // height of the header banner
+ --kbnHeaderBannerHeight: ${layoutVar('banner.height', '0px')};
- // the current total height of all app-area headers, this variable can be used for sticky headers offset relative to the top of the application area
- --kbnAppHeadersOffset: ${layoutVar('application.topBar.height', '0px')};
+ // the current total height of all app-area headers, this variable can be used for sticky headers offset relative to the top of the application area
+ --kbnAppHeadersOffset: ${layoutVar('application.topBar.height', '0px')};
- // backward compatible way to position sticky sub-headers
- --kbn-application--sticky-headers-offset: ${layoutVar('application.topBar.height', '0px')};
+ // backward compatible way to position sticky sub-headers
+ --kbn-application--sticky-headers-offset: ${layoutVar('application.topBar.height', '0px')};
- // height of the project header app action menu which is part of the application area
- --kbnProjectHeaderAppActionMenuHeight: ${layoutVar('application.topBar.height', '0px')};
- }
+ // height of the project header app action menu which is part of the application area
+ --kbnProjectHeaderAppActionMenuHeight: ${layoutVar('application.topBar.height', '0px')};
+ }
- // disable document-level scroll, since the application area handles it, but only when not printing
- @media screen {
- :root {
- overflow: hidden;
+ // disable document-level scroll, since the application area handles it, but only when not printing
+ @media screen {
+ :root {
+ overflow: hidden;
+ }
}
- }
- #kibana-body {
- // DO NOT ADD ANY OVERFLOW BEHAVIORS HERE
- // It will break the sticky navigation
- min-height: 100%;
- display: flex;
- flex-direction: column;
- }
+ #kibana-body {
+ // DO NOT ADD ANY OVERFLOW BEHAVIORS HERE
+ // It will break the sticky navigation
+ min-height: 100%;
+ display: flex;
+ flex-direction: column;
+ }
- // Affixes a div to restrict the position of charts tooltip to the visible viewport minus the header
- #${APP_FIXED_VIEWPORT_ID} {
- pointer-events: none;
- visibility: hidden;
- position: fixed;
- top: ${layoutVar('application.content.top', '0px')};
- right: ${layoutVar('application.content.right', '0px')};
- bottom: ${layoutVar('application.content.bottom', '0px')};
- left: ${layoutVar('application.content.left', '0px')};
- }
+ // Affixes a div to restrict the position of charts tooltip to the visible viewport minus the header
+ #${APP_FIXED_VIEWPORT_ID} {
+ pointer-events: none;
+ visibility: hidden;
+ position: fixed;
+ top: ${layoutVar('application.content.top', '0px')};
+ right: ${layoutVar('application.content.right', '0px')};
+ bottom: ${layoutVar('application.content.bottom', '0px')};
+ left: ${layoutVar('application.content.left', '0px')};
+ }
- .kbnAppWrapper {
- // DO NOT ADD ANY OTHER STYLES TO THIS SELECTOR
- // This a very nested dependency happening in "all" apps
- display: flex;
- flex-flow: column nowrap;
- flex-grow: 1;
- z-index: 0; // This effectively puts every high z-index inside the scope of this wrapper to it doesn't interfere with the header and/or overlay mask
- position: relative; // This is temporary for apps that relied on this being present on \`.application\`
- }
+ .kbnAppWrapper {
+ // DO NOT ADD ANY OTHER STYLES TO THIS SELECTOR
+ // This a very nested dependency happening in "all" apps
+ display: flex;
+ flex-flow: column nowrap;
+ flex-grow: 1;
+ z-index: 0; // This effectively puts every high z-index inside the scope of this wrapper to it doesn't interfere with the header and/or overlay mask
+ position: relative; // This is temporary for apps that relied on this being present on \`.application\`
+ }
- // make data grid full screen mode respect the header banner
- #kibana-body .euiDataGrid--fullScreen {
- height: calc(100vh - var(--kbnHeaderBannerHeight));
- top: var(--kbnHeaderBannerHeight);
- }
-`;
+ // make data grid full screen mode respect the header banner
+ #kibana-body .euiDataGrid--fullScreen {
+ height: calc(100vh - var(--kbnHeaderBannerHeight));
+ top: var(--kbnHeaderBannerHeight);
+ }
+ `;
+};
// temporary hacks that need to be removed after better flyout and global sidenav customization support in EUI
// https://github.com/elastic/eui/issues/8820
-const globalTempHackStyles = (euiTheme: UseEuiTheme['euiTheme']) => css`
+const globalTempHackStyles = (_euiTheme: UseEuiTheme['euiTheme']) => css`
// adjust position of the classic/project side-navigation
.kbnBody .euiFlyout.euiCollapsibleNav {
${logicalCSS('top', layoutVar('application.top', '0px'))};
@@ -135,10 +137,10 @@ const globalTempHackStyles = (euiTheme: UseEuiTheme['euiTheme']) => css`
`;
export const GridLayoutGlobalStyles = () => {
- const { euiTheme } = useEuiTheme();
+ const euiTheme = useEuiTheme();
return (
<>
-
+
>
);
diff --git a/src/core/packages/chrome/layout/core-chrome-layout/layouts/grid/grid_layout.tsx b/src/core/packages/chrome/layout/core-chrome-layout/layouts/grid/grid_layout.tsx
index 6bd69dc67c399..f0b39af84aac5 100644
--- a/src/core/packages/chrome/layout/core-chrome-layout/layouts/grid/grid_layout.tsx
+++ b/src/core/packages/chrome/layout/core-chrome-layout/layouts/grid/grid_layout.tsx
@@ -44,6 +44,9 @@ const layoutConfigs: { classic: ChromeLayoutConfig; project: ChromeLayoutConfig
/** we use it only in project style, because in classic it is included as part of the global header */
applicationTopBarHeight: 48,
+ applicationMarginRight: 8,
+ applicationMarginBottom: 8,
+
/** for debug for now */
sidebarWidth: 48,
footerHeight: 0,
diff --git a/src/core/packages/chrome/navigation/moon.yml b/src/core/packages/chrome/navigation/moon.yml
index 7e53ab9c144ae..14f08d2486800 100644
--- a/src/core/packages/chrome/navigation/moon.yml
+++ b/src/core/packages/chrome/navigation/moon.yml
@@ -22,6 +22,7 @@ dependsOn:
- '@kbn/core-chrome-layout-constants'
- '@kbn/i18n'
- '@kbn/i18n-react'
+ - '@kbn/core-chrome-layout-utils'
tags:
- shared-browser
- package
diff --git a/src/core/packages/chrome/navigation/src/__stories__/navigation.stories.tsx b/src/core/packages/chrome/navigation/src/__stories__/navigation.stories.tsx
index dd6d4ae153cea..85f4445d7df23 100644
--- a/src/core/packages/chrome/navigation/src/__stories__/navigation.stories.tsx
+++ b/src/core/packages/chrome/navigation/src/__stories__/navigation.stories.tsx
@@ -87,24 +87,6 @@ export const Default: StoryObj
= {
render: (args) => ,
};
-export const Collapsed: StoryObj = {
- name: 'Collapsed Navigation',
- decorators: [
- (Story) => {
- return (
- <>
-
-
- >
- );
- },
- ],
- args: {
- isCollapsed: true,
- },
- render: (args) => ,
-};
-
export const WithMinimalItems: StoryObj = {
name: 'Navigation with Minimal Items',
decorators: [
@@ -174,12 +156,15 @@ export const WithinLayout: StoryObj = {
const ControlledNavigation = ({ ...props }: PropsAndArgs) => {
const [activeItemId, setActiveItemId] = useState(props.activeItemId || PRIMARY_MENU_ITEMS[0].id);
+ const [isCollapsed, setIsCollapsed] = useState(props.isCollapsed ?? false);
return (
setActiveItemId(item.id)}
+ onToggleCollapsed={setIsCollapsed}
/>
);
};
@@ -187,6 +172,8 @@ const ControlledNavigation = ({ ...props }: PropsAndArgs) => {
const Layout = ({ ...props }: PropsAndArgs) => {
const { euiTheme } = useEuiTheme();
const [navigationWidth, setNavigationWidth] = useState(0);
+ const [activeItemId, setActiveItemId] = useState(props.activeItemId || PRIMARY_MENU_ITEMS[0].id);
+ const [isCollapsed, setIsCollapsed] = useState(props.isCollapsed ?? false);
const headerHeight = 48;
@@ -226,7 +213,16 @@ const Layout = ({ ...props }: PropsAndArgs) => {
backgroundColor={euiTheme.colors.backgroundFilledText}
/>
}
- navigation={}
+ navigation={
+ setActiveItemId(item.id)}
+ onToggleCollapsed={setIsCollapsed}
+ />
+ }
sidebar={
diff --git a/src/core/packages/chrome/navigation/src/__tests__/__snapshots__/collapsed_mode.test.tsx.snap b/src/core/packages/chrome/navigation/src/__tests__/__snapshots__/collapsed_mode.test.tsx.snap
index 9356a70529d81..f0edba82c0cdf 100644
--- a/src/core/packages/chrome/navigation/src/__tests__/__snapshots__/collapsed_mode.test.tsx.snap
+++ b/src/core/packages/chrome/navigation/src/__tests__/__snapshots__/collapsed_mode.test.tsx.snap
@@ -8,19 +8,19 @@ exports[`Collapsed mode should render the side navigation 1`] = `
id="kbnChromeNav-root"
>