`;
+
+exports[`EuiPageTemplate with bottomBar is rendered 1`] = `
+
+
+
+
+
+ Page level controls
+
+
+ Bottom Bar
+
+
+
+ There is a new region landmark with page level controls at the end of the document.
+
+
+
+`;
diff --git a/src/components/page/page_body/_page_body.scss b/src/components/page/page_body/_page_body.scss
index 32814540500..00f96e7c555 100644
--- a/src/components/page/page_body/_page_body.scss
+++ b/src/components/page/page_body/_page_body.scss
@@ -24,11 +24,8 @@
& > .euiPageHeader:not([class*='--padding']) {
// Match the body's padding for spacing if it doesn't have it's own
margin-bottom: $amount;
- }
-
- // When the page header is actually inside of a panelled page body,
- // We want to add some extra separation between it and the content body
- &.euiPanel > .euiPageHeader {
+ // When the page header is actually inside of a panelled page body,
+ // We want to add some extra separation between it and the content body
border-bottom: $euiBorderThin;
&:not(.euiPageHeader--tabsAtBottom) {
diff --git a/src/components/page/page_content/__snapshots__/page_content.test.tsx.snap b/src/components/page/page_content/__snapshots__/page_content.test.tsx.snap
index a3eb77316cd..f5c6e8c7862 100644
--- a/src/components/page/page_content/__snapshots__/page_content.test.tsx.snap
+++ b/src/components/page/page_content/__snapshots__/page_content.test.tsx.snap
@@ -1,9 +1,37 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`EuiPageContent accepts panel props 1`] = `
+
+`;
+
+exports[`EuiPageContent horizontalPosition is rendered 1`] = `
+
+`;
+
exports[`EuiPageContent is rendered 1`] = `
+`;
+
+exports[`EuiPageContent role can be removed 1`] = `
+
+`;
+
+exports[`EuiPageContent verticalPosition is rendered 1`] = `
+
`;
diff --git a/src/components/page/page_content/page_content.test.tsx b/src/components/page/page_content/page_content.test.tsx
index 2099ece58b0..595bf98ddd7 100644
--- a/src/components/page/page_content/page_content.test.tsx
+++ b/src/components/page/page_content/page_content.test.tsx
@@ -29,4 +29,34 @@ describe('EuiPageContent', () => {
expect(component).toMatchSnapshot();
});
+
+ test('verticalPosition is rendered', () => {
+ const component = render(
);
+
+ expect(component).toMatchSnapshot();
+ });
+
+ test('horizontalPosition is rendered', () => {
+ const component = render(
);
+
+ expect(component).toMatchSnapshot();
+ });
+
+ test('role can be removed', () => {
+ const component = render(
);
+
+ expect(component).toMatchSnapshot();
+ });
+
+ test('accepts panel props', () => {
+ const component = render(
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
});
diff --git a/src/components/page/page_content/page_content.tsx b/src/components/page/page_content/page_content.tsx
index 8650547b8fa..7af6cb74047 100644
--- a/src/components/page/page_content/page_content.tsx
+++ b/src/components/page/page_content/page_content.tsx
@@ -21,7 +21,13 @@ import React, { FunctionComponent } from 'react';
import classNames from 'classnames';
import { CommonProps } from '../../common';
-import { EuiPanel, PanelPaddingSize, EuiPanelProps } from '../../panel/panel';
+import {
+ EuiPanel,
+ PanelPaddingSize,
+ _EuiPanelProps,
+ _EuiPanelDivlike,
+} from '../../panel/panel';
+import { HTMLAttributes } from 'enzyme';
export type EuiPageContentVerticalPositions = 'center';
export type EuiPageContentHorizontalPositions = 'center';
@@ -39,13 +45,20 @@ const horizontalPositionToClassNameMap: {
};
export type EuiPageContentProps = CommonProps &
- EuiPanelProps & {
+ // Use only the div properties of EuiPanel (not button)
+ _EuiPanelProps &
+ Omit<_EuiPanelDivlike, 'onClick' | 'role'> & {
/**
* **DEPRECATED: use `paddingSize` instead.**
*/
panelPaddingSize?: PanelPaddingSize;
verticalPosition?: EuiPageContentVerticalPositions;
horizontalPosition?: EuiPageContentHorizontalPositions;
+ /**
+ * There should only be one EuiPageContent per page and should contain the main contents.
+ * If this is untrue, set role = `null`, or change it to match your needed aria role
+ */
+ role?: HTMLAttributes['role'] | null;
};
export const EuiPageContent: FunctionComponent
= ({
@@ -56,8 +69,11 @@ export const EuiPageContent: FunctionComponent = ({
borderRadius,
children,
className,
+ role: _role = 'main',
...rest
}) => {
+ const role = _role === null ? undefined : _role;
+
const borderRadiusClass =
borderRadius === 'none' ? 'euiPageContent--borderRadiusNone' : '';
@@ -76,6 +92,7 @@ export const EuiPageContent: FunctionComponent = ({
className={classes}
paddingSize={panelPaddingSize ?? paddingSize}
borderRadius={borderRadius}
+ role={role}
{...rest}>
{children}
diff --git a/src/components/page/page_header/_page_header.scss b/src/components/page/page_header/_page_header.scss
index 992ef5ec2f6..b8c4060c33b 100644
--- a/src/components/page/page_header/_page_header.scss
+++ b/src/components/page/page_header/_page_header.scss
@@ -13,19 +13,21 @@
flex-shrink: 0; // Ensures Safari doesn't shrink beyond contents
}
+.euiPageHeader--bottomBorder {
+ border-bottom: $euiBorderThin;
+}
+
// Uses the same values as EuiPanel
@each $modifier, $amount in $euiPanelPaddingModifiers {
.euiPageHeader--#{$modifier} {
- padding-top: $amount;
- padding-left: $amount;
- padding-right: $amount;
- // Use margin for the bottom in case there's a border
- margin-bottom: $amount;
- }
-}
+ padding: $amount;
-.euiPageHeader--tabsAtBottom {
- margin-bottom: 0;
+ &.euiPageHeader--tabsAtBottom {
+ // Use margin if there are tabs to keep border close to tabs
+ padding-bottom: 0;
+ margin-bottom: $amount;
+ }
+ }
}
.euiPageHeader--top {
@@ -41,7 +43,6 @@
}
@include euiBreakpoint('xs', 's') {
-
.euiPageHeader--responsive {
flex-direction: column;
}
diff --git a/src/components/page/page_header/page_header.tsx b/src/components/page/page_header/page_header.tsx
index 03ef15a479e..1bed5bfeb92 100644
--- a/src/components/page/page_header/page_header.tsx
+++ b/src/components/page/page_header/page_header.tsx
@@ -50,12 +50,14 @@ export type EuiPageHeaderProps = CommonProps &
/**
* Adds a bottom border to separate it from the content after
*/
+ bottomBorder?: boolean;
};
export const EuiPageHeader: FunctionComponent = ({
className,
restrictWidth = false,
paddingSize = 'none',
+ bottomBorder,
style,
// Page header content shared props:
@@ -83,6 +85,7 @@ export const EuiPageHeader: FunctionComponent = ({
'euiPageHeader',
paddingSizeToClassNameMap[paddingSize],
{
+ 'euiPageHeader--bottomBorder': bottomBorder,
'euiPageHeader--responsive': responsive === true,
'euiPageHeader--responsiveReverse': responsive === 'reverse',
'euiPageHeader--tabsAtBottom': pageTitle && tabs,
diff --git a/src/components/page/page_template.test.tsx b/src/components/page/page_template.test.tsx
index ab102774610..d80cd1edfd1 100644
--- a/src/components/page/page_template.test.tsx
+++ b/src/components/page/page_template.test.tsx
@@ -127,4 +127,17 @@ describe('EuiPageTemplate', () => {
});
});
});
+
+ describe('with bottomBar', () => {
+ test('is rendered', () => {
+ const component = render(
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+ });
});
diff --git a/src/components/page/page_template.tsx b/src/components/page/page_template.tsx
index 3df592fe4b3..428ae8008c8 100644
--- a/src/components/page/page_template.tsx
+++ b/src/components/page/page_template.tsx
@@ -29,6 +29,8 @@ import {
EuiPageContentProps,
EuiPageContentBodyProps,
} from './page_content';
+import { EuiBottomBarProps, EuiBottomBar } from '../bottom_bar';
+import { ExclusiveUnion } from '../common';
export const TEMPLATES = [
'default',
@@ -37,47 +39,65 @@ export const TEMPLATES = [
'empty',
] as const;
-export type EuiPageTemplateProps = Omit & {
- /**
- * Choose between 3 types of templates.
- * `default`: Typical layout with nothing centered
- * `centeredBody`: The panelled content is centered
- * `centeredContent`: The content inside the panel is centered
- * `empty`: Removes the panneling of the page content
- */
- template?: typeof TEMPLATES[number];
- /**
- *
- * Padding size will not get applie to the over-arching #EuiPage,
- * but will propogate through all the components to keep them in sync
- */
- paddingSize?: typeof SIZES[number];
- /**
- * Optionally include #EuiPageSideBar content.
- * The inclusion of this will affect the whole layout
- */
- pageSideBar?: ReactNode;
- /**
- * Gets passed along to the #EuiPageSideBar component
- */
- pageSideBarProps?: EuiPageSideBarProps;
- /**
- * Optionally include an #EuiPageHeader by passing an object of its props
- */
- pageHeader?: EuiPageHeaderProps;
- /**
- * Gets passed along to the #EuiPageBody component
- */
- pageBodyProps?: EuiPageBodyProps;
- /**
- * Gets passed along to the #EuiPageContent component
- */
- pageContentProps?: EuiPageContentProps;
- /**
- * Gets passed along to the #EuiPageContentBody component
- */
- pageContentBodyProps?: EuiPageContentBodyProps;
-};
+type _EuiPageTemplateTypes = ExclusiveUnion<
+ {
+ template?: 'default';
+ /**
+ * Adds contents inside of an EuiBottomBar.
+ * Only works when `template = 'default'`
+ */
+ bottomBar?: EuiBottomBarProps['children'];
+ /**
+ * Gets passed along to the #EuiBottomBar component if `bottomBar` has contents
+ */
+ bottomBarProps?: EuiBottomBarProps;
+ },
+ {
+ /**
+ * Choose between 3 types of templates.
+ * `default`: Typical layout with nothing centered
+ * `centeredBody`: The panelled content is centered
+ * `centeredContent`: The content inside the panel is centered
+ * `empty`: Removes the panneling of the page content
+ */
+ template: typeof TEMPLATES[number];
+ }
+>;
+
+export type EuiPageTemplateProps = Omit &
+ _EuiPageTemplateTypes & {
+ /**
+ *
+ * Padding size will not get applie to the over-arching #EuiPage,
+ * but will propogate through all the components to keep them in sync
+ */
+ paddingSize?: typeof SIZES[number];
+ /**
+ * Optionally include #EuiPageSideBar content.
+ * The inclusion of this will affect the whole layout
+ */
+ pageSideBar?: ReactNode;
+ /**
+ * Gets passed along to the #EuiPageSideBar component
+ */
+ pageSideBarProps?: EuiPageSideBarProps;
+ /**
+ * Optionally include an #EuiPageHeader by passing an object of its props
+ */
+ pageHeader?: EuiPageHeaderProps;
+ /**
+ * Gets passed along to the #EuiPageBody component
+ */
+ pageBodyProps?: EuiPageBodyProps;
+ /**
+ * Gets passed along to the #EuiPageContent component
+ */
+ pageContentProps?: EuiPageContentProps;
+ /**
+ * Gets passed along to the #EuiPageContentBody component
+ */
+ pageContentBodyProps?: EuiPageContentBodyProps;
+ };
export const EuiPageTemplate: FunctionComponent = ({
template = 'default',
@@ -92,11 +112,15 @@ export const EuiPageTemplate: FunctionComponent = ({
pageBodyProps,
pageContentProps,
pageContentBodyProps,
+ bottomBar,
+ bottomBarProps,
...rest
}) => {
const classes = classNames('euiPageTemplate', className);
- // This seems very repitious but it's the most readable, scalable, and maintainable
+ /**
+ * This seems very repetitious but it's the most readable, scalable, and maintainable
+ */
switch (template) {
/**
@@ -257,7 +281,7 @@ export const EuiPageTemplate: FunctionComponent = ({
restrictWidth={restrictWidth}
paddingSize={paddingSize}
{...pageHeader}
- style={{ marginBottom: 0, ...pageHeader?.style }}
+ style={{ paddingBottom: 0, ...pageHeader?.style }}
/>
)}
= ({
* Typical layout with nothing "centered"
*/
default:
+ // Only the default template can display a bottom bar
+ const bottomBarNode = bottomBar ? (
+
+ {/* Wrapping the contents with EuiPageContentBody allows us to match the restrictWidth to keep the contents aligned */}
+
+ {bottomBar}
+
+
+ ) : undefined;
+
return pageSideBar ? (
{pageSideBar}
-
- {pageHeader && (
-
- )}
-
-
- {children}
-
-
+ {/* The extra PageBody is to accomodate the bottom bar stretching to both sides */}
+
+
+ {pageHeader && (
+
+ )}
+
+
+ {children}
+
+
+
+ {bottomBarNode}
) : (
@@ -332,6 +380,7 @@ export const EuiPageTemplate: FunctionComponent = ({
{children}
+ {bottomBarNode}
);
diff --git a/src/components/panel/panel.tsx b/src/components/panel/panel.tsx
index cb2fc0a8a76..82141c57e8b 100644
--- a/src/components/panel/panel.tsx
+++ b/src/components/panel/panel.tsx
@@ -97,19 +97,22 @@ export interface _EuiPanelProps extends CommonProps {
color?: PanelColor;
}
-interface Divlike
+export interface _EuiPanelDivlike
extends _EuiPanelProps,
Omit, 'color'> {
element?: 'div';
}
-interface Buttonlike
+export interface _EuiPanelButtonlike
extends _EuiPanelProps,
Omit, 'color'> {
element?: 'button';
}
-export type EuiPanelProps = ExclusiveUnion;
+export type EuiPanelProps = ExclusiveUnion<
+ _EuiPanelButtonlike,
+ _EuiPanelDivlike
+>;
export const EuiPanel: FunctionComponent = ({
children,
diff --git a/src/global_styling/variables/_header.scss b/src/global_styling/variables/_header.scss
index 8fbfea8967a..ca97ee110ec 100644
--- a/src/global_styling/variables/_header.scss
+++ b/src/global_styling/variables/_header.scss
@@ -1,5 +1,6 @@
// Themeable colors
$euiHeaderBackgroundColor: $euiColorEmptyShade !default;
+$euiHeaderDarkBackgroundColor: lightOrDarkTheme(shade($euiColorDarkestShade, 28%), shade($euiColorLightestShade, 50%)) !default;
$euiHeaderBorderColor: $euiBorderColor !default;
$euiHeaderBreadcrumbColor: $euiColorDarkestShade !default;