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

EuiBottomBar: allow customization of whether the component makes room for itself #4156

Merged
merged 13 commits into from
Oct 27, 2020
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
- Improved `EuiButtonGroup` focus, hover, selected and disabled states ([#4142](https://github.com/elastic/eui/pull/4142))
- Added `display` prop to `EuiToolTip` for common display block needs ([#4148](https://github.com/elastic/eui/pull/4148))
- Added support for more colors in `EuiProgress` such as `vis0` through `vis9`, `warning`, `success` and custom colors ([#4130](https://github.com/elastic/eui/pull/4130))
- Added `affordForDisplacement` prop to `EuiBottomBar` ([#4156](https://github.com/elastic/eui/pull/4156))

**Bug fixes**

Expand Down
2 changes: 1 addition & 1 deletion src-docs/src/views/bottom_bar/bottom_bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default () => {
const [showBar, setShowBar] = useState(false);

const button = (
<EuiButton color="primary" onClick={() => setShowBar(!showBar)}>
<EuiButton color="primary" onClick={() => setShowBar((show) => !show)}>
Toggle appearance of the bottom bar
</EuiButton>
);
Expand Down
58 changes: 58 additions & 0 deletions src-docs/src/views/bottom_bar/bottom_bar_displacement.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, { useState } from 'react';

import {
EuiBottomBar,
EuiButtonGroup,
EuiButtonEmpty,
EuiFlexGroup,
EuiFlexItem,
} from '../../../../src/components';

export default () => {
const [toggleIdSelected, setToggleIdSelected] = useState(null);

const toggleButtons = [
{
id: 'bottomBarStandard',
label: 'Show bottom bar',
},
{
id: 'bottomBarWithoutAffordForDisplacement',
label: 'Show bottom bar (without affordForDisplacement behavior)',
},
];

const onChange = (optionId) => {
setToggleIdSelected(optionId);
};

return (
<div>
<EuiButtonGroup
legend="Bottom Bar demo toggle buttons group"
type="single"
buttonSize="m"
options={toggleButtons}
idSelected={toggleIdSelected}
onChange={(id) => onChange(id)}
/>

{toggleIdSelected && (
<EuiBottomBar
affordForDisplacement={toggleIdSelected === 'bottomBarStandard'}>
<EuiFlexGroup justifyContent="flexEnd">
<EuiFlexItem grow={false}>
<EuiButtonEmpty
onClick={() => setToggleIdSelected(null)}
color="ghost"
size="s"
iconType="cross">
close
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
</EuiBottomBar>
)}
</div>
);
};
42 changes: 39 additions & 3 deletions src-docs/src/views/bottom_bar/bottom_bar_example.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ const bottomBarSnippet = `<EuiBottomBar paddingSize="s">
<!-- Content goes here -->
</EuiBottomBar>`;

import BottomBarDisplacement from './bottom_bar_displacement';
const bottomBarDisplacementSource = require('!!raw-loader!./bottom_bar_displacement');
const bottomBarDisplacementHtml = renderToHtml(BottomBarDisplacement);

const bottomBarDisplacementSnippet = `<EuiBottomBar affordForDisplacement={false}>
<!-- Content goes here -->
</EuiBottomBar>`;

export const BottomBarExample = {
title: 'Bottom bar',
sections: [
Expand All @@ -29,7 +37,7 @@ export const BottomBarExample = {
},
],
text: (
<div>
<>
<p>
<strong>EuiBottomBar</strong> is a simple wrapper component that
does nothing but fix a bottom bar (usually filled with buttons) to
Expand All @@ -41,13 +49,41 @@ export const BottomBarExample = {
Like many of our other wrapper components,{' '}
<strong>EuiBottomBar</strong> accepts a{' '}
<EuiCode>paddingSize</EuiCode> prop, which can be set to{' '}
<EuiCode>s / m / l / none</EuiCode>.
<EuiCode>s / m (default) / l / none</EuiCode>.
</p>
</div>
</>
),
props: { EuiBottomBar },
snippet: bottomBarSnippet,
demo: <BottomBar />,
},
{
title: 'Displacement',
source: [
{
type: GuideSectionTypes.JS,
code: bottomBarDisplacementSource,
},
{
type: GuideSectionTypes.HTML,
code: bottomBarDisplacementHtml,
},
],
text: (
<>
<p>
There is an <EuiCode>affordForDisplacement</EuiCode> prop
(defaulting to <EuiCode>true</EuiCode>), which determines whether
the component makes room for itself by adding bottom padding
equivalent to its own height on the document body element. Setting
this to <EuiCode>false</EuiCode> can be useful to minimize scrollbar
visibility but will cause the bottom bar to overlap body content.
</p>
</>
),
props: { EuiBottomBar },
snippet: bottomBarDisplacementSnippet,
demo: <BottomBarDisplacement />,
},
],
};
34 changes: 34 additions & 0 deletions src/components/bottom_bar/__snapshots__/bottom_bar.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,40 @@ Array [
]
`;

exports[`EuiBottomBar props affordForDisplacement can be false 1`] = `
Array [
<section
aria-label="Page level controls"
class="euiBottomBar euiBottomBar--paddingMedium"
>
<h2
class="euiScreenReaderOnly"
>
Page level controls
</h2>
</section>,
<p
aria-live="assertive"
class="euiScreenReaderOnly"
>
There is a new region landmark with page level controls at the end of the document.
</p>,
]
`;

exports[`EuiBottomBar props bodyClassName is rendered 1`] = `
<section
aria-label="Page level controls"
class="euiBottomBar euiBottomBar--paddingMedium"
>
<h2
class="euiScreenReaderOnly"
>
Page level controls
</h2>
</section>
`;

exports[`EuiBottomBar props paddingSize l is rendered 1`] = `
Array [
<section
Expand Down
23 changes: 20 additions & 3 deletions src/components/bottom_bar/bottom_bar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,19 @@

import React from 'react';
import ReactDOM from 'react-dom';
import { render } from 'enzyme';
import { requiredProps } from '../../test/required_props';
import { render, mount } from 'enzyme';
import { keysOf } from '../common';
import { requiredProps, takeMountedSnapshot } from '../../test';

import { EuiBottomBar, paddingSizeToClassNameMap } from './bottom_bar';

// @ts-ignore TODO: Temporary hack which we can remove once react-test-renderer supports portals.
// More info at https://github.com/facebook/react/issues/11565.
ReactDOM.createPortal = (node) => node;
ReactDOM.createPortal = (children) => {
// hack to make enzyme treat the portal as a fragment
if (children == null) return [['nested']];
return children;
};

describe('EuiBottomBar', () => {
test('is rendered', () => {
Expand All @@ -48,5 +52,18 @@ describe('EuiBottomBar', () => {
});
});
});

test('affordForDisplacement can be false', () => {
const component = render(<EuiBottomBar affordForDisplacement={false} />);

expect(component).toMatchSnapshot();
});

test('bodyClassName is rendered', () => {
const component = mount(<EuiBottomBar bodyClassName={'customClass'} />);

expect(takeMountedSnapshot(component)).toMatchSnapshot();
expect(document.body.classList.contains('customClass')).toBe(true);
});
});
});
58 changes: 49 additions & 9 deletions src/components/bottom_bar/bottom_bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,34 +38,73 @@ export const paddingSizeToClassNameMap: {

interface Props extends CommonProps {
/**
* Optional class applied to the body class
* Padding applied to the bar. Default is 'm'.
*/
bodyClassName?: string;
paddingSize: BottomBarPaddingSize;

/**
* Whether the component should apply padding on the document body element to afford for its own displacement height.
* Default is true.
*/
affordForDisplacement: boolean;

/**
* Padding applied to the bar
* Optional class applied to the body element on mount
*/
paddingSize?: BottomBarPaddingSize;
bodyClassName?: string;

/**
* Customize the screen reader heading that helps users find this control. Default is "Page level controls".
* Customize the screen reader heading that helps users find this control. Default is 'Page level controls'.
*/
landmarkHeading?: string;
}

export class EuiBottomBar extends Component<Props> {
static defaultProps = {
paddingSize: 'm',
affordForDisplacement: true,
};

private bar: HTMLElement | null = null;

componentDidMount() {
const height = this.bar ? this.bar.clientHeight : -1;
document.body.style.paddingBottom = `${height}px`;
if (this.props.affordForDisplacement) {
cchaos marked this conversation as resolved.
Show resolved Hide resolved
const height = this.bar ? this.bar.clientHeight : -1;
document.body.style.paddingBottom = `${height}px`;
}

if (this.props.bodyClassName) {
document.body.classList.add(this.props.bodyClassName);
}
}

componentDidUpdate(prevProps: Props) {
if (prevProps.affordForDisplacement !== this.props.affordForDisplacement) {
if (this.props.affordForDisplacement) {
// start affording for displacement
const height = this.bar ? this.bar.clientHeight : -1;
document.body.style.paddingBottom = `${height}px`;
} else {
// stop affording for displacement
document.body.style.paddingBottom = '';
}
}

if (prevProps.bodyClassName !== this.props.bodyClassName) {
if (prevProps.bodyClassName) {
document.body.classList.remove(prevProps.bodyClassName);
}
if (this.props.bodyClassName) {
document.body.classList.add(this.props.bodyClassName);
}
}
}

componentWillUnmount() {
document.body.style.paddingBottom = '';
if (this.props.affordForDisplacement) {
cchaos marked this conversation as resolved.
Show resolved Hide resolved
document.body.style.paddingBottom = '';
}

if (this.props.bodyClassName) {
document.body.classList.remove(this.props.bodyClassName);
}
Expand All @@ -75,9 +114,10 @@ export class EuiBottomBar extends Component<Props> {
const {
children,
className,
paddingSize = 'm',
paddingSize,
bodyClassName,
landmarkHeading,
affordForDisplacement,
...rest
} = this.props;

Expand Down