diff --git a/packages/shared-ux/page/kibana_template/impl/src/page_template_inner.tsx b/packages/shared-ux/page/kibana_template/impl/src/page_template_inner.tsx index 762435157f97d..607b8fbd1776b 100644 --- a/packages/shared-ux/page/kibana_template/impl/src/page_template_inner.tsx +++ b/packages/shared-ux/page/kibana_template/impl/src/page_template_inner.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { FC } from 'react'; +import React, { FC, useEffect, useState } from 'react'; import classNames from 'classnames'; import { EuiPageTemplate } from '@elastic/eui'; @@ -21,6 +21,9 @@ const getClasses = (template?: string, className?: string) => { ); }; +const KIBANA_CHROME_SELECTOR = '[data-test-subj="kibanaChrome"]'; +const HEADER_GLOBAL_NAV_SELECTOR = '[data-test-subj="headerGlobalNav"]'; + /** * A thin wrapper around EuiPageTemplate with a few Kibana specific additions */ @@ -35,6 +38,18 @@ export const KibanaPageTemplateInner: FC = ({ }) => { let header; + const [offset, setOffset] = useState(); + + useEffect(() => { + const kibanaChrome = document.querySelector(KIBANA_CHROME_SELECTOR) as HTMLElement; + if (kibanaChrome) { + const kibanaChromeHeader = kibanaChrome.querySelector( + HEADER_GLOBAL_NAV_SELECTOR + ) as HTMLElement; + setOffset(kibanaChromeHeader?.offsetTop + kibanaChromeHeader?.offsetHeight); + } + }, []); + if (isEmptyState && pageHeader && !children) { const { iconType, pageTitle, description, rightSideItems } = pageHeader; const title = pageTitle ?

{pageTitle}

: undefined; @@ -54,15 +69,11 @@ export const KibanaPageTemplateInner: FC = ({ let sideBar; if (pageSideBar) { - sideBar = ( - - {pageSideBar} - - ); + const sideBarProps = { ...pageSideBarProps }; + if (offset) { + sideBarProps.sticky = { offset }; + } + sideBar = {pageSideBar}; } const classes = getClasses(undefined, className); diff --git a/packages/shared-ux/page/solution_nav/BUILD.bazel b/packages/shared-ux/page/solution_nav/BUILD.bazel index d12304fcdd197..0b6b0a8258029 100644 --- a/packages/shared-ux/page/solution_nav/BUILD.bazel +++ b/packages/shared-ux/page/solution_nav/BUILD.bazel @@ -78,6 +78,7 @@ TYPES_DEPS = [ "//packages/kbn-i18n-react:npm_module_types", "//packages/kbn-i18n:npm_module_types", "//packages/shared-ux/avatar/solution:npm_module_types", + "//packages/shared-ux/page/kibana_template/types" ] jsts_transpiler( diff --git a/packages/shared-ux/page/solution_nav/src/__snapshots__/with_solution_nav.test.tsx.snap b/packages/shared-ux/page/solution_nav/src/__snapshots__/with_solution_nav.test.tsx.snap index bae96ec7b65d3..749d0a13ad8d7 100644 --- a/packages/shared-ux/page/solution_nav/src/__snapshots__/with_solution_nav.test.tsx.snap +++ b/packages/shared-ux/page/solution_nav/src/__snapshots__/with_solution_nav.test.tsx.snap @@ -52,7 +52,20 @@ exports[`WithSolutionNav renders wrapped component 1`] = ` } pageSideBarProps={ Object { - "className": "kbnSolutionNav__sidebar kbnStickyMenu", + "className": "kbnStickyMenu", + "css": Object { + "map": undefined, + "name": "sx7fqw", + "next": undefined, + "styles": " + flex: 0 1 0%; + overflow: hidden; + @media screen and (prefers-reduced-motion: no-preference) { + transition: min-width 150ms cubic-bezier(.694, .0482, .335, 1); + } + ", + "toString": [Function], + }, "minWidth": undefined, "paddingSize": "none", } @@ -112,7 +125,20 @@ exports[`WithSolutionNav with children 1`] = ` } pageSideBarProps={ Object { - "className": "kbnSolutionNav__sidebar kbnStickyMenu", + "className": "kbnStickyMenu", + "css": Object { + "map": undefined, + "name": "sx7fqw", + "next": undefined, + "styles": " + flex: 0 1 0%; + overflow: hidden; + @media screen and (prefers-reduced-motion: no-preference) { + transition: min-width 150ms cubic-bezier(.694, .0482, .335, 1); + } + ", + "toString": [Function], + }, "minWidth": undefined, "paddingSize": "none", } diff --git a/packages/shared-ux/page/solution_nav/src/with_solution_nav.scss b/packages/shared-ux/page/solution_nav/src/with_solution_nav.scss deleted file mode 100644 index 00cfb7b9f927a..0000000000000 --- a/packages/shared-ux/page/solution_nav/src/with_solution_nav.scss +++ /dev/null @@ -1,8 +0,0 @@ -// TODO: Can now be converted to Emotion -.kbnSolutionNav__sidebar { - overflow: hidden; - - @include euiCanAnimate { - transition: min-width $euiAnimSpeedFast $euiAnimSlightResistance; - } -} diff --git a/packages/shared-ux/page/solution_nav/src/with_solution_nav.styles.ts b/packages/shared-ux/page/solution_nav/src/with_solution_nav.styles.ts new file mode 100644 index 0000000000000..906f1fdd8e293 --- /dev/null +++ b/packages/shared-ux/page/solution_nav/src/with_solution_nav.styles.ts @@ -0,0 +1,20 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import { css } from '@emotion/react'; +import { euiCanAnimate, EuiThemeComputed } from '@elastic/eui'; + +export const WithSolutionNavStyles = (euiTheme: EuiThemeComputed<{}>) => { + return css` + flex: 0 1 0%; + overflow: hidden; + ${euiCanAnimate} { + transition: min-width ${euiTheme.animation.fast} ${euiTheme.animation.resistance}; + } + `; +}; diff --git a/packages/shared-ux/page/solution_nav/src/with_solution_nav.tsx b/packages/shared-ux/page/solution_nav/src/with_solution_nav.tsx index a618f6d6ba41c..0c3f0359c1f6e 100644 --- a/packages/shared-ux/page/solution_nav/src/with_solution_nav.tsx +++ b/packages/shared-ux/page/solution_nav/src/with_solution_nav.tsx @@ -8,27 +8,20 @@ import React, { ComponentType, ReactNode, useState } from 'react'; import classNames from 'classnames'; -import { - useIsWithinBreakpoints, - useEuiTheme, - useIsWithinMinBreakpoint, - EuiPageSidebarProps, -} from '@elastic/eui'; +import { SerializedStyles } from '@emotion/serialize'; +import { KibanaPageTemplateProps } from '@kbn/shared-ux-page-kibana-template-types'; +import { useIsWithinBreakpoints, useEuiTheme, useIsWithinMinBreakpoint } from '@elastic/eui'; import { SolutionNav, SolutionNavProps } from './solution_nav'; - -import './with_solution_nav.scss'; +import { WithSolutionNavStyles } from './with_solution_nav.styles'; // https://reactjs.org/docs/higher-order-components.html#convention-wrap-the-display-name-for-easy-debugging function getDisplayName(Component: ComponentType) { return Component.displayName || Component.name || 'UnnamedComponent'; } -// TODO: Would be nice to grab these from KibanaPageTemplate or vice-versa -interface TemplateProps { - pageSideBar?: ReactNode; - pageSideBarProps?: Partial; +type TemplateProps = Pick & { children?: ReactNode; -} +}; type Props

= P & TemplateProps & { @@ -58,8 +51,8 @@ export const withSolutionNav =

(WrappedComponent: Compo const { canBeCollapsed = true } = solutionNav; const isSidebarShrunk = isMediumBreakpoint || (canBeCollapsed && isLargerBreakpoint && !isSideNavOpenOnDesktop); + const withSolutionNavStyles = WithSolutionNavStyles(euiTheme); const sideBarClasses = classNames( - 'kbnSolutionNav__sidebar', 'kbnStickyMenu', { 'kbnSolutionNav__sidebar--shrink': isSidebarShrunk, @@ -75,11 +68,12 @@ export const withSolutionNav =

(WrappedComponent: Compo /> ); - const pageSideBarProps: TemplateProps['pageSideBarProps'] = { + const pageSideBarProps: TemplateProps['pageSideBarProps'] & { css: SerializedStyles } = { paddingSize: 'none' as 'none', ...props.pageSideBarProps, minWidth: isSidebarShrunk ? euiTheme.size.xxl : undefined, className: sideBarClasses, + css: withSolutionNavStyles, }; return (