Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EuiHeader] Update to set a new global --euiFixedHeadersOffset CSS variable #7144

Merged
merged 16 commits into from
Sep 1, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 0 additions & 17 deletions src-docs/src/components/guide_page/_guide_page.scss
Original file line number Diff line number Diff line change
@@ -1,22 +1,5 @@
@import '../../../../src/global_styling/mixins/helpers';

@include euiHeaderAffordForFixed;

.guideBody {
// Override euiHeaderAffordForFixed mixin since the page template handles this now
padding-top: 0 !important; // stylelint-disable-line declaration-no-important
}

.euiBody--headerIsFixed--double {
@include euiHeaderAffordForFixed($euiHeaderHeightCompensation * 2);

.euiHeader:not([data-fixed-header]) {
// Force headers below the fullscreen.
// This shouldn't be necessary in consuming applications because headers should always be at the top of the page
z-index: 0;
}
}

.guideSideNav {
@include euiSideNavEmbellish;
}
Expand Down
2 changes: 1 addition & 1 deletion src-docs/src/components/guide_page/guide_page_chrome.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export class GuidePageChrome extends Component {
scrollNavSectionIntoView = () => {
// wait a bit for react to blow away and re-create the DOM
// then scroll the selected nav section into view
requestAnimationFrame(() => {
setTimeout(() => {
const sideNav = document.querySelector('.guideSideNav__content');
const isMobile = sideNav?.querySelector('.euiSideNav__mobileToggle');

Expand Down
10 changes: 1 addition & 9 deletions src-docs/src/views/header/header_elastic_pattern.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useState } from 'react';
import { Link } from 'react-router-dom';

import {
Expand Down Expand Up @@ -53,14 +53,6 @@ export default () => {
prefix: 'guideHeaderDeploymentPopover',
});

useEffect(() => {
document.body.classList.add('euiBody--headerIsFixed--double');

return () => {
document.body.classList.remove('euiBody--headerIsFixed--double');
};
}, []);

/**
* Collapsible Nav
*/
Expand Down
38 changes: 22 additions & 16 deletions src-docs/src/views/header/header_example.js
Original file line number Diff line number Diff line change
Expand Up @@ -299,23 +299,34 @@ export const HeaderExample = {
<p>
Most consumers need a header that does not scroll away with the page
contents. You can apply this display by applying the property{' '}
cee-chen marked this conversation as resolved.
Show resolved Hide resolved
<EuiCode language="ts">{'position="fixed"'}</EuiCode>. This will
also add a class of <EuiCode>.euiBody--headerIsFixed</EuiCode> to
the window body.
<EuiCode language="ts">{'position="fixed"'}</EuiCode>. Multiple
fixed headers will automatically stack underneath one another
without manual positioning required.
cee-chen marked this conversation as resolved.
Show resolved Hide resolved
</p>
<p>
You will then need to apply your own padding to this body class to
afford for the header height. EUI supplies a helper mixin that also
accounts for this height in flyouts and the collapsible nav. Simply
add{' '}
<EuiCode language="sass">@include euiHeaderAffordForFixed;</EuiCode>{' '}
anywhere in your SASS.
If you're using{' '}
<Link to="/templates/page-template">
<strong>EuiPageTemplate</strong>
</Link>
, a padding top will be automatically set based on the number of
fixed headers on the page.{' '}
<Link to="/layout/flyout">
<strong>EuiFlyouts</strong>
</Link>{' '}
will also automatically account for and sit below fixed headers.
</p>
<p>
If you're using your own custom layout, or have custom UI that needs
to sit below your fixed header(s), EUI provides a global CSS{' '}
<EuiCode language="css">var(--euiFixedHeadersOffset)</EuiCode>{' '}
variable. You can use this variable anywhere, or even override it,
to correctly offset any and all fixed header heights.
</p>
</>
),
snippet: [
'<EuiHeader position="fixed" />',
'@include euiHeaderAffordForFixed;',
'body { padding-block-start: var(--euiFixedHeadersOffset, 0) }',
],
demo: <HeaderPosition />,
demoPanelProps: {
Expand Down Expand Up @@ -446,17 +457,12 @@ export const HeaderExample = {
text: (
<p>
Stacking multiple headers provides a great way to separate global
navigation concerns. However, the{' '}
<EuiCode language="ts">{'position="fixed"'}</EuiCode> option will not
be aware of the number of headers. If you do need fixed{' '}
<strong>and</strong> stacked headers, you will need to apply the SASS
helper mixin and pass in the correct height to afford for.
navigation concerns.
</p>
),
snippet: [
`<EuiHeader theme="dark" position="fixed" />
<EuiHeader position="fixed" />`,
'@include euiHeaderAffordForFixed($euiHeaderHeightCompensation * 2);',
],
demo: <HeaderStacked />,
demoPanelProps: {
Expand Down
10 changes: 1 addition & 9 deletions src-docs/src/views/header/header_stacked.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useState } from 'react';

import {
EuiBreadcrumb,
Expand Down Expand Up @@ -27,14 +27,6 @@ export default () => {
},
];

useEffect(() => {
if (isFixed) document.body.classList.add('euiBody--headerIsFixed--double');

return () => {
document.body.classList.remove('euiBody--headerIsFixed--double');
};
}, [isFixed]);

const headers = (
<>
<EuiHeader
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -404,8 +404,8 @@ export const MultipleFixedHeaders: Story = {
<EuiHeader position="fixed">
<EuiHeaderSection>
<EuiCollapsibleNavBeta {...args}>
This story tests that EuiCollapsibleNav's fixed header detection &
offsetting works as expected
This story tests that EuiCollapsibleNav automatically adjusts its
position & height for multiple fixed headers
</EuiCollapsibleNavBeta>
Second header
</EuiHeaderSection>
Expand Down
16 changes: 14 additions & 2 deletions src/components/collapsible_nav_beta/collapsible_nav_beta.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,30 @@ import { css } from '@emotion/react';
import { UseEuiTheme } from '../../services';
import { logicalCSS, euiYScroll } from '../../global_styling';
import { euiShadowFlat } from '../../themes';
import { euiHeaderVariables } from '../header/header.styles';

export const euiCollapsibleNavBetaStyles = (euiThemeContext: UseEuiTheme) => {
const { euiTheme } = euiThemeContext;

// At least for serverless, EuiCollapsibleNav is only going to be used with 1
// fixed header. For those scenarios, we can prevent a minor layout jump on
// page load by setting the CSS var fallback to the height of a single header
const defaultHeaderHeight = euiHeaderVariables(euiThemeContext).height;
const fixedHeaderOffset = `var(--euiFixedHeadersOffset, ${defaultHeaderHeight})`;

return {
euiCollapsibleNavBeta: css`
/* Fixed header affordance */
${logicalCSS('top', fixedHeaderOffset)}
${logicalCSS('height', `calc(100% - ${fixedHeaderOffset})`)}

/* This extra padding is needed for EuiPopovers to have enough
space to render with the right anchorPosition */
${logicalCSS('padding-bottom', euiTheme.size.xs)}

/* Allow the nav to scroll, in case consumers don't use EuiFlyoutBody/EuiFyoutFooter */
${euiYScroll(euiThemeContext)}
/* Allow the nav to scroll, in case consumers don't use EuiFlyoutBody/EuiFyoutFooter
Height is already set by header affordance above */
${euiYScroll(euiThemeContext, { height: false })}

/* In case things get really dire responsively, ensure the footer doesn't overtake the body */
.euiFlyoutBody {
Expand Down
16 changes: 0 additions & 16 deletions src/components/collapsible_nav_beta/collapsible_nav_beta.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ import { render } from '../../test/rtl';
import { shouldRenderCustomStyles } from '../../test/internal';
import { requiredProps } from '../../test';

import { EuiHeader } from '../header';

import { EuiCollapsibleNavBeta } from './collapsible_nav_beta';

describe('EuiCollapsibleNavBeta', () => {
Expand Down Expand Up @@ -66,20 +64,6 @@ describe('EuiCollapsibleNavBeta', () => {
expect(onCollapseToggle).toHaveBeenLastCalledWith(true);
});

it('automatically accounts for fixed EuiHeaders in its positioning', () => {
const { getByTestSubject } = render(
<EuiHeader position="fixed">
<EuiCollapsibleNavBeta data-test-subj="nav">
Nav content
</EuiCollapsibleNavBeta>
</EuiHeader>
);
expect(getByTestSubject('nav')).toHaveStyle({
'inset-block-start': '48px',
'block-size': 'calc(100% - 48px)',
});
});

describe('responsive behavior', () => {
const mockWindowResize = (width: number) => {
window.innerWidth = width;
Expand Down
32 changes: 1 addition & 31 deletions src/components/collapsible_nav_beta/collapsible_nav_beta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import React, {
import classNames from 'classnames';

import { useEuiTheme, useGeneratedHtmlId, throttle } from '../../services';
import { mathWithUnits, logicalStyle } from '../../global_styling';

import { CommonProps } from '../common';
import { EuiFlyout, EuiFlyoutProps } from '../flyout';
Expand Down Expand Up @@ -83,7 +82,6 @@ export const EuiCollapsibleNavBeta: FunctionComponent<
id,
children,
className,
style,
initialIsCollapsed = false,
onCollapseToggle,
width: _width = 248,
Expand Down Expand Up @@ -144,32 +142,6 @@ export const EuiCollapsibleNavBeta: FunctionComponent<
return _width;
}, [_width, isOverlayFullWidth, isPush, isCollapsed, headerHeight]);

/**
* Header affordance
*/
const [fixedHeadersCount, setFixedHeadersCount] = useState<number | false>(
false
);
useEffect(() => {
setFixedHeadersCount(
document.querySelectorAll('.euiHeader[data-fixed-header]').length
);
}, []);

const stylesWithHeaderOffset = useMemo(() => {
if (!fixedHeadersCount) return style;

const headersOffset = mathWithUnits(
headerHeight,
(x) => x * fixedHeadersCount
);
return {
...style,
...logicalStyle('top', headersOffset),
...logicalStyle('height', `calc(100% - ${headersOffset})`),
};
}, [fixedHeadersCount, style, headerHeight]);

/**
* Prop setup
*/
Expand Down Expand Up @@ -205,15 +177,13 @@ export const EuiCollapsibleNavBeta: FunctionComponent<
isOverlayFullWidth && styles.isOverlayFullWidth,
];

// Wait for any fixed headers to be queried before rendering (prevents position jumping)
const flyout = fixedHeadersCount !== false && (
const flyout = (
<EuiFlyout
aria-label={defaultAriaLabel}
{...rest} // EuiCollapsibleNav is significantly less permissive than EuiFlyout
id={flyoutID}
css={cssStyles}
className={classes}
style={stylesWithHeaderOffset}
size={width}
side={side}
focusTrapProps={focusTrapProps}
Expand Down
15 changes: 13 additions & 2 deletions src/components/datagrid/_data_grid.scss
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,23 @@
}

// This is used to remove extra scrollbars on the body when fullscreen is enabled
// and tweak any components that account for fixed headers
.euiDataGrid__restrictBody {
height: 100vh;
overflow: hidden;

.euiHeader {
z-index: $euiZHeaderBelowDataGrid;
.euiHeader[data-fixed-header] {
// !important needed to override header inline styles
z-index: $euiZHeaderBelowDataGrid !important; // stylelint-disable-line declaration-no-important
}

.euiOverlayMask[data-relative-to-header='below'] {
top: 0;
}

.euiFlyout {
top: 0;
height: 100%;
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/components/flyout/__snapshots__/flyout.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1086,12 +1086,12 @@ Array [
exports[`EuiFlyout renders extra screen reader instructions when fixed EuiHeaders headers exist on the page 1`] = `
<body
class="euiBody--headerIsFixed euiBody--hasFlyout"
data-fixed-headers="1"
>
<div>
<div
class="euiHeader emotion-euiHeader-fixed-default"
data-fixed-header="true"
style="inset-block-start: 0px;"
/>
<div>
<div
Expand Down
4 changes: 2 additions & 2 deletions src/components/flyout/flyout.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,9 @@ export const euiFlyoutStyles = (euiThemeContext: UseEuiTheme) => {
return {
euiFlyout: css`
position: fixed;
${logicalCSS('top', 0)}
${logicalCSS('bottom', 0)}
${logicalCSS('height', '100%')}
${logicalCSS('top', 'var(--euiFixedHeadersOffset, 0)')}
${logicalCSS('height', 'calc(100% - var(--euiFixedHeadersOffset, 0))')}
z-index: ${euiTheme.levels.flyout};
background: ${euiTheme.colors.emptyShade};
display: flex;
Expand Down
1 change: 1 addition & 0 deletions src/components/header/__snapshots__/header.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ exports[`EuiHeader renders in fixed position 1`] = `
<div
class="euiHeader emotion-euiHeader-fixed-default"
data-fixed-header="true"
style="inset-block-start: 0px;"
>
<span>
Hello!
Expand Down
Loading