diff --git a/src/core/packages/chrome/browser-internal/src/chrome_service.tsx b/src/core/packages/chrome/browser-internal/src/chrome_service.tsx index 5d33fccdaff31..3809e78eda002 100644 --- a/src/core/packages/chrome/browser-internal/src/chrome_service.tsx +++ b/src/core/packages/chrome/browser-internal/src/chrome_service.tsx @@ -73,7 +73,6 @@ import { handleSystemColorModeChange } from './handle_system_colormode_change'; import { AppMenuBar } from './ui/project/app_menu'; import { GridLayoutProjectSideNav } from './ui/project/sidenav/grid_layout_sidenav'; import { FixedLayoutProjectSideNav } from './ui/project/sidenav/fixed_layout_sidenav'; -import { SideNavCollapseButton } from './ui/project/sidenav/collapse_button'; import type { NavigationProps } from './ui/project/sidenav/types'; const IS_SIDENAV_COLLAPSED_KEY = 'core.chrome.isSideNavCollapsed'; @@ -422,6 +421,7 @@ export class ChromeService { dataTestSubj$: activeDataTestSubj$, isFeedbackBtnVisible$: this.isFeedbackBtnVisible$, feedbackUrlParams$, + onToggleCollapsed: setIsSideNavCollapsed, }; const getProjectHeader = ({ @@ -471,19 +471,13 @@ export class ChromeService { kibanaVersion={injectedMetadata.getKibanaVersion()} prependBasePath={http.basePath.prepend} > - {includeSideNav ? ( + {includeSideNav && ( - ) : ( - )} ); diff --git a/src/core/packages/chrome/browser-internal/src/ui/project/app_menu.tsx b/src/core/packages/chrome/browser-internal/src/ui/project/app_menu.tsx index 458acddc6419c..b532bfcce8a45 100644 --- a/src/core/packages/chrome/browser-internal/src/ui/project/app_menu.tsx +++ b/src/core/packages/chrome/browser-internal/src/ui/project/app_menu.tsx @@ -37,7 +37,7 @@ const useAppMenuBarStyles = (euiTheme: UseEuiTheme['euiTheme']) => justifyContent: 'end', alignItems: 'center', padding: `0 ${euiTheme.size.s}`, - background: euiTheme.colors.body, + background: euiTheme.colors.backgroundBasePlain, borderBottom: euiTheme.border.thin, marginBottom: `-${euiTheme.border.width.thin}`, }; diff --git a/src/core/packages/chrome/browser-internal/src/ui/project/header.tsx b/src/core/packages/chrome/browser-internal/src/ui/project/header.tsx index 7a383c77907e2..a4ec069d33605 100644 --- a/src/core/packages/chrome/browser-internal/src/ui/project/header.tsx +++ b/src/core/packages/chrome/browser-internal/src/ui/project/header.tsx @@ -52,18 +52,13 @@ const getHeaderCss = ({ size, colors }: EuiThemeComputed) => ({ display: flex; align-items: center; justify-content: center; - min-width: 56px; /* 56 = 40 + 8 + 8 */ + min-width: ${size.xxl}; cursor: pointer; `, logo: css` min-width: 0; /* overrides min-width: 40px */ padding: 0; `, - spinner: css` - position: relative; - left: 4px; - top: 2px; - `, }, leftHeaderSection: css` // needed to enable breadcrumbs truncation @@ -202,7 +197,7 @@ const Logo = ({ {loadingCount === 0 ? ( renderLogo() ) : ( - + css` box-shadow: none !important; + background-color: ${euiTheme.colors.backgroundTransparent}; + border-bottom-color: ${euiTheme.colors.backgroundTransparent}; + padding-inline: 4px 8px; `; return ( diff --git a/src/core/packages/chrome/browser-internal/src/ui/project/sidenav/fixed_layout_sidenav.tsx b/src/core/packages/chrome/browser-internal/src/ui/project/sidenav/fixed_layout_sidenav.tsx index 3ae04686d45c4..59c1b9096e546 100644 --- a/src/core/packages/chrome/browser-internal/src/ui/project/sidenav/fixed_layout_sidenav.tsx +++ b/src/core/packages/chrome/browser-internal/src/ui/project/sidenav/fixed_layout_sidenav.tsx @@ -15,31 +15,23 @@ import type { BehaviorSubject } from 'rxjs'; import { css, Global } from '@emotion/react'; import { Navigation } from './navigation'; -import { SideNavCollapseButton } from './collapse_button'; import type { NavigationProps } from './types'; interface CollapsibleNavigationProps { - toggle: (isVisible: boolean) => void; isCollapsed$: BehaviorSubject; navProps: NavigationProps; } export const FixedLayoutProjectSideNav: FunctionComponent = ({ - toggle, isCollapsed$, navProps, }) => { const isCollapsed = useObservable(isCollapsed$, isCollapsed$.getValue()); return ( - <> - - - {({ setWidth }) => ( - - )} - - + + {({ setWidth }) => } + ); }; diff --git a/src/core/packages/chrome/browser-internal/src/ui/project/sidenav/navigation/navigation.tsx b/src/core/packages/chrome/browser-internal/src/ui/project/sidenav/navigation/navigation.tsx index 51396e27ed77f..181b1b813c396 100644 --- a/src/core/packages/chrome/browser-internal/src/ui/project/sidenav/navigation/navigation.tsx +++ b/src/core/packages/chrome/browser-internal/src/ui/project/sidenav/navigation/navigation.tsx @@ -49,6 +49,9 @@ export interface ChromeNavigationProps { dataTestSubj$?: Observable; feedbackUrlParams$: Observable; + + // collapse toggle callback + onToggleCollapsed: (isCollapsed: boolean) => void; } export const Navigation = (props: ChromeNavigationProps) => { @@ -75,6 +78,7 @@ export const Navigation = (props: ChromeNavigationProps) => { } isCollapsed={props.isCollapsed} setWidth={props.setWidth} + onToggleCollapsed={props.onToggleCollapsed} activeItemId={activeItemId} data-test-subj={classnames(dataTestSubj, 'projectSideNav', 'projectSideNavV2')} /> diff --git a/src/core/packages/chrome/browser-internal/src/ui/project/sidenav/navigation/navigation_feedback_snippet.tsx b/src/core/packages/chrome/browser-internal/src/ui/project/sidenav/navigation/navigation_feedback_snippet.tsx index f611640983ef9..c8148b3ddf9a0 100644 --- a/src/core/packages/chrome/browser-internal/src/ui/project/sidenav/navigation/navigation_feedback_snippet.tsx +++ b/src/core/packages/chrome/browser-internal/src/ui/project/sidenav/navigation/navigation_feedback_snippet.tsx @@ -63,14 +63,17 @@ export const NavigationFeedbackSnippet = ({ const { euiTheme } = useEuiTheme(); return ( - + > + + ); }; diff --git a/src/core/packages/chrome/layout/core-chrome-layout-components/application/layout_application.styles.ts b/src/core/packages/chrome/layout/core-chrome-layout-components/application/layout_application.styles.ts index ba7c47371b223..b60ce1c54c59e 100644 --- a/src/core/packages/chrome/layout/core-chrome-layout-components/application/layout_application.styles.ts +++ b/src/core/packages/chrome/layout/core-chrome-layout-components/application/layout_application.styles.ts @@ -9,21 +9,45 @@ import { css } from '@emotion/react'; import { layoutVar, layoutLevels } from '@kbn/core-chrome-layout-constants'; +import { euiOverflowScroll, euiShadow } from '@elastic/eui'; +import { getHighContrastBorder } from '@kbn/core-chrome-layout-utils'; import type { EmotionFn } from '../types'; -const root: EmotionFn = ({ euiTheme }) => +const root: EmotionFn = (useEuiTheme) => css` grid-area: application; - height: 100%; - position: relative; - width: 100%; + + height: calc(100% - ${layoutVar('application.marginBottom')}); + width: calc(100% - ${layoutVar('application.marginRight')}); + margin-bottom: ${layoutVar('application.marginBottom')}; + margin-right: ${layoutVar('application.marginRight')}; + z-index: ${layoutLevels.content}; + position: relative; display: flex; flex-direction: column; + background-color: ${useEuiTheme.euiTheme.colors.backgroundBasePlain}; + border-radius: ${useEuiTheme.euiTheme.border.radius.medium}; + border: ${getHighContrastBorder(useEuiTheme)}; + ${euiShadow(useEuiTheme, 'xs', { border: 'none' })}; + &:focus-visible { - border: 2px solid ${euiTheme.colors.textParagraph}; + border: 2px solid ${useEuiTheme.euiTheme.colors.textParagraph}; + } + + // only restrict overflow scroll on screen (not print) to allow for full page printing + @media screen { + ${euiOverflowScroll(useEuiTheme, { direction: 'y' })}; + // reset the height back to respect the margin bottom + height: calc(100% - ${layoutVar('application.marginBottom')}); + + // Hide scrollbar + scrollbar-width: none; /* Firefox */ + &::-webkit-scrollbar { + display: none; /* Chrome, Safari, Edge */ + } } `; diff --git a/src/core/packages/chrome/layout/core-chrome-layout-components/application/layout_application.tsx b/src/core/packages/chrome/layout/core-chrome-layout-components/application/layout_application.tsx index 32e4b5883a752..ef1a2ba7f2fff 100644 --- a/src/core/packages/chrome/layout/core-chrome-layout-components/application/layout_application.tsx +++ b/src/core/packages/chrome/layout/core-chrome-layout-components/application/layout_application.tsx @@ -10,8 +10,6 @@ import type { ReactNode } from 'react'; import React from 'react'; -import { css } from '@emotion/react'; -import { useEuiOverflowScroll } from '@elastic/eui'; import { APP_MAIN_SCROLL_CONTAINER_ID } from '@kbn/core-chrome-layout-constants'; import { styles } from './layout_application.styles'; @@ -31,16 +29,9 @@ export const LayoutApplication = ({ topBar?: ReactNode; bottomBar?: ReactNode; }) => { - // only restrict overflow scroll on screen (not print) to allow for full page printing - const overflow = css` - @media screen { - ${useEuiOverflowScroll('y')}; - } - `; - return (
; /** 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" >
+
+
+ +
diff --git a/src/core/packages/chrome/navigation/src/__tests__/__snapshots__/expanded_mode.test.tsx.snap b/src/core/packages/chrome/navigation/src/__tests__/__snapshots__/expanded_mode.test.tsx.snap index 4993892bf4b76..8917faca17004 100644 --- a/src/core/packages/chrome/navigation/src/__tests__/__snapshots__/expanded_mode.test.tsx.snap +++ b/src/core/packages/chrome/navigation/src/__tests__/__snapshots__/expanded_mode.test.tsx.snap @@ -8,15 +8,15 @@ exports[`Expanded mode should render the side navigation 1`] = ` id="kbnChromeNav-root" >
+
+
+ +
diff --git a/src/core/packages/chrome/navigation/src/__tests__/test_component.tsx b/src/core/packages/chrome/navigation/src/__tests__/test_component.tsx index 40b525461dc4c..0f9443258e7de 100644 --- a/src/core/packages/chrome/navigation/src/__tests__/test_component.tsx +++ b/src/core/packages/chrome/navigation/src/__tests__/test_component.tsx @@ -22,12 +22,13 @@ interface TestComponentProps { } export const TestComponent = ({ - isCollapsed = false, + isCollapsed: isCollapsedProp = false, initialActiveItemId, items, logo, }: TestComponentProps) => { const [activeItemId, setActiveItemId] = useState(initialActiveItemId); + const [isCollapsed, setIsCollapsed] = useState(isCollapsedProp); const handleItemClick = (item: SideNavLogo | MenuItem | SecondaryMenuItem) => { setActiveItemId(item.id); @@ -43,6 +44,7 @@ export const TestComponent = ({ items={items} logo={logo} onItemClick={handleItemClick} + onToggleCollapsed={setIsCollapsed} setWidth={() => {}} /> diff --git a/src/core/packages/chrome/browser-internal/src/ui/project/sidenav/collapse_button.tsx b/src/core/packages/chrome/navigation/src/components/collapse_button.tsx similarity index 54% rename from src/core/packages/chrome/browser-internal/src/ui/project/sidenav/collapse_button.tsx rename to src/core/packages/chrome/navigation/src/components/collapse_button.tsx index 1cf11ba85984b..fa6f5cb039d77 100644 --- a/src/core/packages/chrome/browser-internal/src/ui/project/sidenav/collapse_button.tsx +++ b/src/core/packages/chrome/navigation/src/components/collapse_button.tsx @@ -8,41 +8,25 @@ */ import type { UseEuiTheme } from '@elastic/eui'; -import { - EuiButtonIcon, - logicalCSS, - logicalSizeCSS, - useEuiTheme, - useIsWithinBreakpoints, -} from '@elastic/eui'; +import { EuiButtonIcon, useEuiTheme } from '@elastic/eui'; import { css } from '@emotion/react'; import type { FC } from 'react'; import React, { useMemo } from 'react'; -import type { Observable } from 'rxjs'; -import { isObservable, of } from 'rxjs'; -import useObservable from 'react-use/lib/useObservable'; import { i18n } from '@kbn/i18n'; -import { PRIMARY_NAVIGATION_ID } from '@kbn/core-chrome-navigation/src/constants'; +import { PRIMARY_NAVIGATION_ID } from '../constants'; interface Props { - isCollapsed: boolean | Observable; + isCollapsed: boolean; toggle: (isCollapsed: boolean) => void; } const sideNavCollapseButtonStyles = (euiTheme: UseEuiTheme['euiTheme']) => { - // packages/eui/src/components/header/header.styles.ts - const height = euiTheme.size.xxxl; // TODO: hardcoded height of the euiHeader header - const padding = euiTheme.size.s; // TODO: hardcoded padding of the euiHeader header - return { sideNavCollapseButtonWrapper: css` display: flex; align-items: center; justify-content: center; - ${logicalSizeCSS(height)} - ${logicalCSS('border-right', euiTheme.border.thin)} - ${logicalCSS('margin-left', `-${padding}`)} - ${logicalCSS('margin-right', padding)} + min-width: ${euiTheme.size.xxl}; `, sideNavCollapseButton: css` &.euiButtonIcon:hover { @@ -53,25 +37,12 @@ const sideNavCollapseButtonStyles = (euiTheme: UseEuiTheme['euiTheme']) => { }; /** - * Reimplementation of EuiCollapsibleNavBeta Collapse Button to survey new sidenav and new layout use-cases + * Collapse button for the side navigation */ -export const SideNavCollapseButton: FC = ({ isCollapsed, toggle, ...rest }) => { - const collapsedObservable = useMemo( - () => (isObservable(isCollapsed) ? isCollapsed : of(isCollapsed)), - [isCollapsed] - ); - - const collapsed = useObservable( - collapsedObservable, - typeof isCollapsed === 'boolean' ? isCollapsed : false - ); - - const iconType = collapsed ? 'transitionLeftIn' : 'transitionLeftOut'; +export const SideNavCollapseButton: FC = ({ isCollapsed, toggle }) => { + const iconType = isCollapsed ? 'transitionLeftIn' : 'transitionLeftOut'; const { euiTheme } = useEuiTheme(); - const styles = sideNavCollapseButtonStyles(euiTheme); - - const isSmall = useIsWithinBreakpoints(['xs', 's']); - if (isSmall) return null; + const styles = useMemo(() => sideNavCollapseButtonStyles(euiTheme), [euiTheme]); return (
@@ -82,7 +53,7 @@ export const SideNavCollapseButton: FC = ({ isCollapsed, toggle, ...rest color="text" iconType={iconType} aria-label={ - collapsed + isCollapsed ? i18n.translate('core.ui.chrome.sideNavigation.expandButtonLabel', { defaultMessage: 'Expand navigation menu', }) @@ -90,10 +61,10 @@ export const SideNavCollapseButton: FC = ({ isCollapsed, toggle, ...rest defaultMessage: 'Collapse navigation menu', }) } - aria-pressed={!collapsed} - aria-expanded={!collapsed} + aria-pressed={!isCollapsed} + aria-expanded={!isCollapsed} aria-controls={PRIMARY_NAVIGATION_ID} - onClick={() => toggle(!collapsed)} + onClick={() => toggle(!isCollapsed)} />
); diff --git a/src/core/packages/chrome/navigation/src/components/footer/index.tsx b/src/core/packages/chrome/navigation/src/components/footer/index.tsx index c5fb446a112c2..2036020de24d1 100644 --- a/src/core/packages/chrome/navigation/src/components/footer/index.tsx +++ b/src/core/packages/chrome/navigation/src/components/footer/index.tsx @@ -12,9 +12,11 @@ import type { ForwardRefExoticComponent, ReactNode, RefAttributes } from 'react' import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; import { + EuiHorizontalRule, EuiScreenReaderOnly, useEuiTheme, useGeneratedHtmlId, + useIsWithinBreakpoints, type UseEuiTheme, } from '@elastic/eui'; @@ -23,16 +25,30 @@ import { getFocusableElements } from '../../utils/get_focusable_elements'; import { handleRovingIndex } from '../../utils/handle_roving_index'; import { updateTabIndices } from '../../utils/update_tab_indices'; import { NAVIGATION_SELECTOR_PREFIX } from '../../constants'; +import { getHighContrastSeparator } from '../../hooks/use_high_contrast_mode_styles'; -const getWrapperStyles = (theme: UseEuiTheme['euiTheme'], isCollapsed: boolean) => css` - align-items: center; - border-top: ${theme.border.width.thin} solid ${theme.colors.borderBaseSubdued}; - display: flex; - flex-direction: column; - gap: ${theme.size.xs}; - justify-content: center; - padding-top: ${isCollapsed ? theme.size.s : theme.size.m}; -`; +const getFooterWrapperStyles = (euiThemeContext: UseEuiTheme, isCollapsed: boolean) => { + const { euiTheme: theme } = euiThemeContext; + return { + root: css` + align-items: center; + display: flex; + position: relative; + flex-direction: column; + gap: ${theme.size.xs}; + justify-content: center; + padding-top: ${isCollapsed ? theme.size.s : theme.size.m}; + + ${getHighContrastSeparator(euiThemeContext, { side: 'top' })} + `, + collapseDivider: css` + position: relative; + background-color: transparent; + + ${getHighContrastSeparator(euiThemeContext, { side: 'top' })} + `, + }; +}; export interface FooterIds { footerNavigationInstructionsId: string; @@ -43,6 +59,7 @@ export type FooterChildren = ReactNode | ((ids: FooterIds) => ReactNode); export interface FooterProps { children: FooterChildren; isCollapsed: boolean; + collapseButton?: ReactNode; } interface FooterComponent @@ -50,63 +67,72 @@ interface FooterComponent Item: typeof FooterItem; } -const FooterBase = forwardRef(({ children, isCollapsed }, ref) => { - const { euiTheme } = useEuiTheme(); - const footerNavigationInstructionsId = useGeneratedHtmlId({ - prefix: 'footer-navigation-instructions', - }); - - const handleRef = (node: HTMLElement | null) => { - if (typeof ref === 'function') { - ref(node); - } else if (ref) { - ref.current = node; - } - - if (node) { - const elements = getFocusableElements(node); - updateTabIndices(elements); - } - }; +const FooterBase = forwardRef( + ({ children, isCollapsed, collapseButton }, ref) => { + const euiThemeContext = useEuiTheme(); + const isSmall = useIsWithinBreakpoints(['xs', 's']); + const footerNavigationInstructionsId = useGeneratedHtmlId({ + prefix: 'footer-navigation-instructions', + }); - const wrapperStyles = useMemo( - () => getWrapperStyles(euiTheme, isCollapsed), - [euiTheme, isCollapsed] - ); + const handleRef = (node: HTMLElement | null) => { + if (typeof ref === 'function') { + ref(node); + } else if (ref) { + ref.current = node; + } - const renderChildren = () => { - if (typeof children === 'function') { - return children({ footerNavigationInstructionsId }); - } - return children; - }; + if (node) { + const elements = getFocusableElements(node); + updateTabIndices(elements); + } + }; + + const wrapperStyles = useMemo( + () => getFooterWrapperStyles(euiThemeContext, isCollapsed), + [euiThemeContext, isCollapsed] + ); + + const renderChildren = () => { + if (typeof children === 'function') { + return children({ footerNavigationInstructionsId }); + } + return children; + }; - return ( - <> - -

- {i18n.translate('core.ui.chrome.sideNavigation.footerInstructions', { - defaultMessage: - 'You are in the main navigation footer menu. Use Up and Down arrow keys to navigate the menu.', + return ( + <> + +

+ {i18n.translate('core.ui.chrome.sideNavigation.footerInstructions', { + defaultMessage: + 'You are in the main navigation footer menu. Use Up and Down arrow keys to navigate the menu.', + })} +

+
+ {/* The footer itself is not interactive but the children are */} + {/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */} +
- - {/* The footer itself is not interactive but the children are */} - {/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */} -
- {renderChildren()} -
- - ); -}); + css={wrapperStyles.root} + onKeyDown={handleRovingIndex} + ref={handleRef} + data-test-subj={`${NAVIGATION_SELECTOR_PREFIX}-footer`} + > + {renderChildren()} + {!isSmall && ( + <> + + {collapseButton} + + )} +
+ + ); + } +); export const Footer = Object.assign(FooterBase, { Item: FooterItem, diff --git a/src/core/packages/chrome/navigation/src/components/menu_item/index.tsx b/src/core/packages/chrome/navigation/src/components/menu_item/index.tsx index 170f4691fb296..c13fd622fc35c 100644 --- a/src/core/packages/chrome/navigation/src/components/menu_item/index.tsx +++ b/src/core/packages/chrome/navigation/src/components/menu_item/index.tsx @@ -99,7 +99,7 @@ export const MenuItem = forwardRef void; + /** + * Callback fired when the collapse button is toggled. + * + * The collapsed state's source of truth lives in chrome_service.tsx as a BehaviorSubject + * that is persisted to localStorage. External consumers rely on this state. + */ + onToggleCollapsed: (isCollapsed: boolean) => void; /** * (optional) Content to display inside the side panel footer. */ @@ -73,6 +81,7 @@ export const Navigation = ({ items, logo, onItemClick, + onToggleCollapsed, setWidth, sidePanelFooter, ...rest @@ -103,6 +112,11 @@ export const Navigation = ({ useLayoutWidth({ isCollapsed, isSidePanelOpen, setWidth }); + // Create the collapse button if a toggle callback is provided + const collapseButton = onToggleCollapsed ? ( + + ) : null; + return (
- + {({ footerNavigationInstructionsId }) => ( <> {items.footerItems.slice(0, MAX_FOOTER_ITEMS).map((item, index) => { diff --git a/src/core/packages/chrome/navigation/src/components/secondary_menu/index.tsx b/src/core/packages/chrome/navigation/src/components/secondary_menu/index.tsx index 96a28de326322..7c2b9df1d21e0 100644 --- a/src/core/packages/chrome/navigation/src/components/secondary_menu/index.tsx +++ b/src/core/packages/chrome/navigation/src/components/secondary_menu/index.tsx @@ -32,7 +32,7 @@ interface SecondaryMenuComponent } const SecondaryMenuBase = forwardRef( - ({ badgeType, children, isPanel = false, title }, ref) => { + ({ badgeType, children, title }, ref) => { const { euiTheme } = useEuiTheme(); const headerStyle = useMenuHeaderStyle(); @@ -44,9 +44,7 @@ const SecondaryMenuBase = forwardRef( const titleStyles = css` ${headerStyle} - background: ${isPanel - ? euiTheme.colors.backgroundBaseSubdued - : euiTheme.colors.backgroundBasePlain}; + background: ${euiTheme.colors.backgroundBasePlain}; border-radius: ${euiTheme.border.radius.medium}; `; diff --git a/src/core/packages/chrome/navigation/src/components/secondary_menu/section.tsx b/src/core/packages/chrome/navigation/src/components/secondary_menu/section.tsx index c2929fc9ff302..13ab4cca5cccf 100644 --- a/src/core/packages/chrome/navigation/src/components/secondary_menu/section.tsx +++ b/src/core/packages/chrome/navigation/src/components/secondary_menu/section.tsx @@ -21,15 +21,35 @@ export const SecondaryMenuSectionComponent = ({ children, label, }: SecondaryMenuSectionProps): JSX.Element => { - const { euiTheme } = useEuiTheme(); + const euiThemeContext = useEuiTheme(); + const { euiTheme, highContrastMode } = euiThemeContext; const sectionId = label ? label.replace(/\s+/g, '-').toLowerCase() : undefined; - const wrapperStyles = css` + const secondaryMenuWrapperStyles = css` padding: ${euiTheme.size.m}; + position: relative; &:not(:last-child) { - border-bottom: 1px ${euiTheme.colors.borderBaseSubdued} solid; + ${highContrastMode + ? ` + border-bottom: ${euiTheme.border.width.thin} solid ${euiTheme.border.color}; + margin-left: ${euiTheme.size.m}; + margin-right: ${euiTheme.size.m}; + padding-left: 0; + padding-right: 0; + ` + : ` + &::after { + content: ''; + position: absolute; + bottom: 0; + left: ${euiTheme.size.m}; + right: ${euiTheme.size.m}; + height: ${euiTheme.border.width.thin}; + background-color: ${euiTheme.colors.borderBaseSubdued}; + } + `} } `; @@ -52,7 +72,7 @@ export const SecondaryMenuSectionComponent = ({ `; return ( -
+
{label && ( {label} diff --git a/src/core/packages/chrome/navigation/src/components/side_nav/index.tsx b/src/core/packages/chrome/navigation/src/components/side_nav/index.tsx index 3f0be83fba3d1..f9a15f38e5b47 100644 --- a/src/core/packages/chrome/navigation/src/components/side_nav/index.tsx +++ b/src/core/packages/chrome/navigation/src/components/side_nav/index.tsx @@ -22,15 +22,14 @@ import { SecondaryMenu } from '../secondary_menu'; import { SidePanel } from './side_panel'; import { NAVIGATION_ROOT_SELECTOR } from '../../constants'; -const getWrapperStyles = (theme: UseEuiTheme['euiTheme'], isCollapsed: boolean) => css` +const getNavWrapperStyles = (theme: UseEuiTheme['euiTheme'], isCollapsed: boolean) => css` box-sizing: border-box; - background-color: ${theme.colors.backgroundBasePlain}; - border-right: ${theme.border.width.thin} solid ${theme.colors.borderBaseSubdued}; + background-color: ${theme.colors.backgroundTransparent}; display: flex; flex-direction: column; gap: ${isCollapsed ? theme.size.s : theme.size.m}; height: 100%; - padding-bottom: ${theme.size.base}; + padding-bottom: ${theme.size.s}; width: ${isCollapsed ? COLLAPSED_WIDTH : EXPANDED_WIDTH}px; `; @@ -62,7 +61,7 @@ export const SideNav: SideNavComponent = ({ children, isCollapsed }) => { const { euiTheme } = useEuiTheme(); const wrapperStyles = useMemo( - () => getWrapperStyles(euiTheme, isCollapsed), + () => getNavWrapperStyles(euiTheme, isCollapsed), [euiTheme, isCollapsed] ); diff --git a/src/core/packages/chrome/navigation/src/components/side_nav/logo.tsx b/src/core/packages/chrome/navigation/src/components/side_nav/logo.tsx index d7f0ba776e324..e9cd291e8093c 100644 --- a/src/core/packages/chrome/navigation/src/components/side_nav/logo.tsx +++ b/src/core/packages/chrome/navigation/src/components/side_nav/logo.tsx @@ -17,6 +17,7 @@ import type { SideNavLogo } from '../../../types'; import { MenuItem } from '../menu_item'; import { NAVIGATION_SELECTOR_PREFIX } from '../../constants'; import { useTooltip } from '../../hooks/use_tooltip'; +import { getHighContrastSeparator } from '../../hooks/use_high_contrast_mode_styles'; export interface LogoProps extends Omit, 'onClick'>, SideNavLogo { id: string; @@ -33,7 +34,8 @@ export const Logo = ({ label, ...props }: LogoProps): JSX.Element => { - const { euiTheme } = useEuiTheme(); + const euiThemeContext = useEuiTheme(); + const { euiTheme } = euiThemeContext; const { tooltipRef, handleMouseOut } = useTooltip(); /** @@ -48,10 +50,12 @@ export const Logo = ({ * We cannot use `euiTheme.size.s` because it's 8px. */ const wrapperStyles = css` - border-bottom: ${euiTheme.border.width.thin} solid ${euiTheme.colors.borderBaseSubdued}; + position: relative; padding-top: ${isCollapsed ? euiTheme.size.s : euiTheme.size.m}; padding-bottom: ${isCollapsed ? '7px' : euiTheme.size.m}; + ${getHighContrastSeparator(euiThemeContext)} + .euiText { font-weight: ${euiTheme.font.weight.bold}; } diff --git a/src/core/packages/chrome/navigation/src/components/side_nav/side_panel.tsx b/src/core/packages/chrome/navigation/src/components/side_nav/side_panel.tsx index 6dfbcb80e2056..255305f1d3fe0 100644 --- a/src/core/packages/chrome/navigation/src/components/side_nav/side_panel.tsx +++ b/src/core/packages/chrome/navigation/src/components/side_nav/side_panel.tsx @@ -10,6 +10,7 @@ import React, { type ReactNode, useMemo } from 'react'; import { EuiScreenReaderOnly, + euiShadow, EuiSplitPanel, useEuiTheme, useGeneratedHtmlId, @@ -17,6 +18,7 @@ import { } from '@elastic/eui'; import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; +import { layoutVar, SIDE_PANEL_CONTENT_GAP } from '@kbn/core-chrome-layout-constants'; import type { MenuItem } from '../../../types'; import { SIDE_PANEL_WIDTH } from '../../hooks/use_layout_width'; @@ -25,19 +27,19 @@ import { handleRovingIndex } from '../../utils/handle_roving_index'; import { updateTabIndices } from '../../utils/update_tab_indices'; import { useScroll } from '../../hooks/use_scroll'; import { NAVIGATION_SELECTOR_PREFIX } from '../../constants'; +import { getHighContrastBorder } from '../../hooks/use_high_contrast_mode_styles'; -/** - * **Border and shadow** - * - * For instance, only plain or transparent panels can have a border and/or shadow. - * Source: {@link https://eui.elastic.co/docs/components/containers/panel/} - */ -const getWrapperStyles = (theme: UseEuiTheme['euiTheme']) => css` +const getSidePanelWrapperStyles = (euiThemeContext: UseEuiTheme) => css` box-sizing: border-box; - border-right: ${theme.border.width.thin} ${theme.colors.borderBaseSubdued} solid; + position: relative; display: flex; flex-direction: column; - width: ${SIDE_PANEL_WIDTH}px; + width: ${SIDE_PANEL_WIDTH - SIDE_PANEL_CONTENT_GAP}px; + margin-bottom: ${layoutVar('application.marginBottom', '0px')}; + background-color: ${euiThemeContext.euiTheme.colors.backgroundBasePlain}; + border-radius: ${euiThemeContext.euiTheme.border.radius.medium}; + border: ${getHighContrastBorder(euiThemeContext)}; + ${euiShadow(euiThemeContext, 'xs', { border: 'none' })}; `; export interface SidePanelIds { @@ -57,9 +59,12 @@ export interface SidePanelProps { * Shows only in expanded mode. */ export const SidePanel = ({ children, footer, openerNode }: SidePanelProps): JSX.Element => { - const { euiTheme } = useEuiTheme(); + const euiThemeContext = useEuiTheme(); const scrollStyles = useScroll(); - const wrapperStyles = useMemo(() => getWrapperStyles(euiTheme), [euiTheme]); + const wrapperStyles = useMemo( + () => getSidePanelWrapperStyles(euiThemeContext), + [euiThemeContext] + ); const secondaryNavigationInstructionsId = useGeneratedHtmlId({ prefix: 'secondary-navigation-instructions', }); @@ -114,9 +119,10 @@ export const SidePanel = ({ children, footer, openerNode }: SidePanelProps): JSX data-test-subj={`${sidePanelClassName} ${sidePanelClassName}_${openerNode.id}`} hasShadow={false} role="region" + color="transparent" > ` outline-color: var(--high-contrast-hover-indicator-color, ${euiTheme.border.color}); diff --git a/src/core/packages/chrome/navigation/src/hooks/use_layout_width.ts b/src/core/packages/chrome/navigation/src/hooks/use_layout_width.ts index c9d06607a47f2..3b6bbc3a9e501 100644 --- a/src/core/packages/chrome/navigation/src/hooks/use_layout_width.ts +++ b/src/core/packages/chrome/navigation/src/hooks/use_layout_width.ts @@ -10,8 +10,8 @@ import { useEffect } from 'react'; export const COLLAPSED_WIDTH = 48; -export const EXPANDED_WIDTH = 92; -export const SIDE_PANEL_WIDTH = 240; +export const EXPANDED_WIDTH = 100; +export const SIDE_PANEL_WIDTH = 248; interface UseLayoutWidthArgs { isCollapsed: boolean; diff --git a/src/core/packages/chrome/navigation/tsconfig.json b/src/core/packages/chrome/navigation/tsconfig.json index d6cfe31809afb..b5b1234c32c5a 100644 --- a/src/core/packages/chrome/navigation/tsconfig.json +++ b/src/core/packages/chrome/navigation/tsconfig.json @@ -19,6 +19,7 @@ "@kbn/core-chrome-layout-components", "@kbn/core-chrome-layout-constants", "@kbn/i18n", - "@kbn/i18n-react" + "@kbn/i18n-react", + "@kbn/core-chrome-layout-utils" ] } diff --git a/src/platform/packages/shared/shared-ux/feedback_snippet/src/__snapshots__/feedback_panel.test.tsx.snap b/src/platform/packages/shared/shared-ux/feedback_snippet/src/__snapshots__/feedback_panel.test.tsx.snap index 995c7aba8b71a..222e018f919d3 100644 --- a/src/platform/packages/shared/shared-ux/feedback_snippet/src/__snapshots__/feedback_panel.test.tsx.snap +++ b/src/platform/packages/shared/shared-ux/feedback_snippet/src/__snapshots__/feedback_panel.test.tsx.snap @@ -2,7 +2,7 @@ exports[`FeedbackPanel renders with the expected inner components based on the feedback view: negative 1`] = `