diff --git a/CHANGELOG.md b/CHANGELOG.md index 051264f2191..f6f3c1f9b94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,27 @@ - Fixed the return type of `getDefaultEuiMarkdownUiPlugins` ([#4567](https://github.com/elastic/eui/pull/4567)) - Fixed inverse handling of boolean sorting in `EuiDataGrid` ([#4561](https://github.com/elastic/eui/pull/4561)) +## Feature: EuiPageTemplate ([#4517](https://github.com/elastic/eui/pull/4517)) + +- Added new `EuiPageTemplate` component as a shortcut for creating the different types of page layout patterns +- Added props `grow` and `direction` to `EuiPage` +- Added props `panelled`, `panelProps`, and `paddingSize` to `EuiPageBody` +- Added props `restrictWidth` and `paddingSize` to `EuiPageBody` +- Added prop `paddingSize` to `EuiPageHeader` +- Updated `tabs` prop of `EuiPageHeaderContent` to render `large` size +- Added prop `sticky` to `EuiPageSideBar` +- Added Sass variable `$euiPageSidebarMinWidth` for changing default `min-width` of `EuiPageSideBar` +- Added `href` and other anchor props to `EuiHeaderSectionItemButton` + +**Bug fixes** + +- Fixed horizontal overflow of `EuiPageContent` +- Fixed horizontal overflow of `EuiBreadcrumbs` + ## [`31.8.0`](https://github.com/elastic/eui/tree/v31.8.0) - Reverted part of [#4509](https://github.com/elastic/eui/pull/4509) and returned `EuiDataGrid`'s background content area to an empty shade ([#4542](https://github.com/elastic/eui/pull/4542)) -- Added exports for all EUI component props matching `EuiComponentProps` name pattern. ([#4517](https://github.com/elastic/eui/pull/4517)) +- Added exports for all EUI component props matching `EuiComponentProps` name pattern ([#4517](https://github.com/elastic/eui/pull/4517)) - Added `truncate`, `disabled`, and `emphasize` props to `EuiSideNavItem` ([#4488](https://github.com/elastic/eui/pull/4488)) - Added `truncate` prop to `EuiSideNav` ([#4488](https://github.com/elastic/eui/pull/4488)) - Added support for all `color`s of `EuiPanel` ([#4504](https://github.com/elastic/eui/pull/4504)) @@ -37,7 +54,7 @@ ## [`31.7.0`](https://github.com/elastic/eui/tree/v31.7.0) -- Added `whiteSpace` prop to `EuiCodeBlock`. ([#4475](https://github.com/elastic/eui/pull/4475)) +- Added `whiteSpace` prop to `EuiCodeBlock` ([#4475](https://github.com/elastic/eui/pull/4475)) - Added a light background to `EuiDataGrid` and removed unnecessary height on its container ([#4509](https://github.com/elastic/eui/pull/4509)) **Bug fixes** diff --git a/docs/index.html b/docs/index.html index 20f761c1c1f..8c06e96d65d 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1 +1 @@ -
\ No newline at end of file +
diff --git a/scripts/a11y-testing.js b/scripts/a11y-testing.js index e9442604b79..0dbf913fc80 100644 --- a/scripts/a11y-testing.js +++ b/scripts/a11y-testing.js @@ -24,6 +24,7 @@ const { AxePuppeteer } = require('@axe-core/puppeteer'); const docsPages = async (root, page) => { const pagesToSkip = [ `${root}#/layout/resizable-container`, + `${root}#/layout/page`, // Has duplicate `
` element `${root}#/tabular-content/tables`, `${root}#/tabular-content/in-memory-tables`, `${root}#/display/aspect-ratio`, diff --git a/src-docs/src/components/codesandbox/link.js b/src-docs/src/components/codesandbox/link.js index 2ac2df6bc19..eba894c2a89 100644 --- a/src-docs/src/components/codesandbox/link.js +++ b/src-docs/src/components/codesandbox/link.js @@ -6,8 +6,6 @@ import { listExtraDeps, } from '../../services'; -import { EuiSpacer } from '../../../../src/components'; - const pkg = require('../../../../package.json'); const getVersion = (packageName) => { @@ -28,19 +26,48 @@ const getVersion = (packageName) => { * 5. Through regex we read the dependencies of both `content` and `display_toggles` and pass that to CS. * 6. We pass the files and dependencies as params to CS through a POST call. * */ +import { ThemeContext } from '../with_theme'; const displayTogglesRawCode = require('!!raw-loader!../../views/form_controls/display_toggles') .default; +export const CodeSandboxLink = ({ ...rest }) => { + return ( + + {(context) => } + + ); +}; + /* 1 */ -export const CodeSandboxLink = ({ children, content }) => { +export const CodeSandboxLinkComponent = ({ + children, + className, + content, + context, +}) => { + let cssFile; + switch (context.theme) { + case 'amsterdam-light': + cssFile = '@elastic/eui/dist/eui_theme_amsterdam_light.css'; + break; + case 'amsterdam-dark': + cssFile = '@elastic/eui/dist/eui_theme_amsterdam_dark.css'; + break; + case 'dark': + cssFile = '@elastic/eui/dist/eui_theme_dark.css'; + break; + default: + cssFile = '@elastic/eui/dist/eui_theme_light.css'; + break; + } + let indexContent; if (!content) { /* 2 */ indexContent = `import ReactDOM from 'react-dom'; -import '@elastic/eui/dist/eui_theme_light.css' -// import '@elastic/eui/dist/eui_theme_dark.css' +import '${cssFile}'; import React from 'react'; import { @@ -82,8 +109,7 @@ ReactDOM.render( // The Code Sanbbox demo needs to import CSS at the top of the document. CS has trouble // with our dynamic imports so we need to warn the user for now const exampleStart = `import ReactDOM from 'react-dom'; -// import '@elastic/eui/dist/eui_theme_dark.css'; -import '@elastic/eui/dist/eui_theme_light.css'`; +import '${cssFile}';`; // Concat the three pieces of the example into a single string to use for index.js const cleanedContent = `${exampleStart} @@ -155,13 +181,10 @@ ${exampleClose} action="https://codesandbox.io/api/v1/sandboxes/define" method="POST" target="_blank" - className="eui-textRight"> + className={className}> {/* 6 */} - - {childWithSubmit} - ); }; diff --git a/src-docs/src/components/guide_components.scss b/src-docs/src/components/guide_components.scss index 73cbebe954d..bdf1b4d95fc 100644 --- a/src-docs/src/components/guide_components.scss +++ b/src-docs/src/components/guide_components.scss @@ -3,29 +3,17 @@ $guideSideNavWidth: 240px; $guideZLevelHighest: $euiZLevel9 + 1000; -.guideBody { - background: linear-gradient(90deg, $euiPageBackgroundColor 50%, $euiColorEmptyShade 50%); - - &--overflowHidden { - overflow: hidden; - } +#guide { // sass-lint:disable-line no-ids + display: flex; + flex-direction: column; + min-height: calc(100vh - #{$euiHeaderHeightCompensation}); } @include euiHeaderAffordForFixed; -.euiBody--headerIsFixed { - .guideSideNav { - top: $euiHeaderHeightCompensation; - } -} - .euiBody--headerIsFixed--double { @include euiHeaderAffordForFixed($euiHeaderHeightCompensation * 2); - .guideSideNav { - top: $euiHeaderHeightCompensation * 2; - } - .euiHeader:not(.euiHeader--fixed) { // Force headers below the full screen. // This shouldn't be necessary in consuming applications because headers should always be at the top of the page @@ -33,36 +21,18 @@ $guideZLevelHighest: $euiZLevel9 + 1000; } } -.guidePage { - padding: 0; -} - .guideSideNav { - width: $guideSideNavWidth; - position: fixed; - top: 0; - bottom: 0; - - .guideSideNav__identity { - border-bottom: $euiBorderThin; - padding: $euiSize; - } - - .guideSideNav__theme { - font-size: $euiFontSizeS; - color: $euiColorDarkShade; - } + min-width: $guideSideNavWidth; +} - .guideSideNav__content { - @include euiScrollBar; +.guideSideNav__content { + @include euiYScroll; + padding: 0 $euiSizeL $euiSizeL; +} - width: $guideSideNavWidth; - padding: $euiSize; - position: absolute; - bottom: 0; - top: 132px; - overflow-y: auto; - } +.guideSideNav__search { + flex-shrink: 0; + padding: $euiSizeL; } .guideSideNav__item { @@ -90,31 +60,36 @@ $guideZLevelHighest: $euiZLevel9 + 1000; color: $euiColorDarkShade; } -.guidePageContent { - flex: 1 1 auto; - padding: $euiSize $euiSizeXL; - min-height: 100vh; - background-color: $euiColorEmptyShade; - border-left: $euiBorderThin; - max-width: $euiPageDefaultMaxWidth; - margin-left: 240px; +.guideDemo__highlightLayout, +.guideDemo__highlightLayout--playground, +.guideDemo__highlightLayout--legacy { + border: 1px solid transparentize($euiColorPrimary, .9); + overflow: hidden; + height: 460px; + display: flex; + + .euiPageSideBar--sticky { + top: 0; + } } -.guideDemo__highlightLayout { - .euiPageBody { - min-height: 460px; +.guideDemo__highlightLayout--playground > div:not(.euiPage) { + height: 100%; + width: 100%; + padding: 0 !important; // sass-lint:disable-line no-important + + > .euiPage--grow { + height: 100%; } +} +.guideDemo__highlightLayout--legacy { > div, > div > div { background: transparentize($euiColorPrimary, .9); } } -.guideDemo__highlightLayout--single { - background: transparentize($euiColorPrimary, .9); -} - .guideDemo__highlightSpacer { .euiSpacer { background: transparentize($euiColorPrimary, .9); @@ -124,14 +99,14 @@ $guideZLevelHighest: $euiZLevel9 + 1000; .guideDemo__highlightGrid { .euiFlexItem { background: transparentize($euiColorPrimary, .9); - padding: 16px; + padding: $euiSize; } } .guideDemo__highlightGridWrap { .euiFlexItem div { background: transparentize($euiColorPrimary, .9); - padding: 16px; + padding: $euiSize; } } @@ -239,6 +214,25 @@ $guideZLevelHighest: $euiZLevel9 + 1000; height: 100%; left: 0; top: 0; + z-index: $euiZHeader + 1; + overflow: auto; + + &--withHeader { + top: $euiHeaderHeightCompensation; + bottom: 0; + height: auto; + z-index: $euiZHeader - 1; + display: flex; + flex-direction: column; + + .euiPageSideBar--sticky { + top: 0; + } + } +} + +.guideBody--overflowHidden { + overflow: hidden; } .euiDataGridRowCell--favoriteFranchise { @@ -264,46 +258,30 @@ $guideZLevelHighest: $euiZLevel9 + 1000; @import 'guide_rule/index'; @include euiBreakpoint('xs', 's') { - .guidePage { - display: block; // Fixes IE + .guideSideNav { + width: 100%; } - .guideBody { - background: none; + .guideSideNav__content { + padding: 0; + border-top: $euiBorderThin; } +} - .guideSideNav { - position: static; - width: auto; - - // This is a temporary hack till we fix how classes pass into form controls - .euiFormControlLayout, - input[type='search'] { - max-width: 100%; - } - - .guideSideNav__content { - position: relative; - width: auto; - top: auto; - bottom: auto; - padding: 0; - overflow-y: hidden; - } +.guideHomePage__blockformCard { + min-width: 200px; + flex-basis: 100% !important; - .guideSideNav__identity { - position: relative; - width: auto; - } + // sass-lint:disable-block mixins-before-declarations + @include euiBreakpoint('s', 'm') { + flex-basis: 45% !important; // sass-lint:disable-line no-important + } - .guideSideNav__mobileToggle { - background: $euiColorPrimary; - color: $euiColorEmptyShade !important; - } + @include euiBreakpoint('l') { + flex-basis: 30% !important; // sass-lint:disable-line no-important } - .guidePageContent { - margin-left: 0; - width: 100%; + @include euiBreakpoint('xl') { + flex-basis: 22% !important; // sass-lint:disable-line no-important } } diff --git a/src-docs/src/components/guide_components_amsterdam.scss b/src-docs/src/components/guide_components_amsterdam.scss deleted file mode 100644 index af6938d7a60..00000000000 --- a/src-docs/src/components/guide_components_amsterdam.scss +++ /dev/null @@ -1,8 +0,0 @@ -.guide { - background: $euiPageBackgroundColor; -} - -.guidePageContent { - @include euiBottomShadowFlat; - border-left: none; -} diff --git a/src-docs/src/components/guide_locale_selector/guide_locale_selector.js b/src-docs/src/components/guide_locale_selector/guide_locale_selector.js index 6f850cccfcb..4b53fc35199 100644 --- a/src-docs/src/components/guide_locale_selector/guide_locale_selector.js +++ b/src-docs/src/components/guide_locale_selector/guide_locale_selector.js @@ -17,19 +17,20 @@ moment.defineLocale('en-xa', { // Reset default moment locale after using `defineLocale` moment.locale('en'); -import { EuiSwitch, EuiFormRow } from '../../../../src/components'; +import { EuiSwitch, EuiToolTip } from '../../../../src/components'; export const GuideLocaleSelector = ({ selectedLocale, onToggleLocale }) => { return ( - + onToggleLocale(selectedLocale === 'en' ? 'en-xa' : 'en') } /> - + ); }; diff --git a/src-docs/src/components/guide_page/guide_page.js b/src-docs/src/components/guide_page/guide_page.js index 0a742a476be..a3c11a4f417 100644 --- a/src-docs/src/components/guide_page/guide_page.js +++ b/src-docs/src/components/guide_page/guide_page.js @@ -1,15 +1,11 @@ import PropTypes from 'prop-types'; -import React, { Fragment } from 'react'; +import React from 'react'; import { Switch, Route, withRouter } from 'react-router-dom'; import { - EuiTitle, - EuiSpacer, - EuiFlexGroup, - EuiFlexItem, EuiBetaBadge, - EuiTab, - EuiTabs, - EuiHorizontalRule, + EuiPageHeader, + EuiPageContent, + EuiPageContentBody, } from '../../../../src/components'; const GuidePageComponent = ({ @@ -61,64 +57,61 @@ const GuidePageComponent = ({ const isPlaygroundView = location.pathname.includes('playground'); const renderTabs = () => { + if (tabs.length < 2) { + return undefined; + } + return tabs.map(({ id, handleClick, name }, index) => { let isSelected = false; if (id === 'playground') isSelected = isPlaygroundView; else if (id === 'guidelines') isSelected = isGuideLineView; else isSelected = !isGuideLineView && !isPlaygroundView; - return ( - { - if (handleClick) handleClick(); - }} - isSelected={isSelected} - key={index}> - {name} - - ); + return { + onClick: () => { + if (handleClick) handleClick(); + }, + isSelected, + key: index, + label: name, + }; }); }; return ( - -
- - - -

- {title} {betaBadge} -

-
-
- - - {tabs.length > 1 && renderTabs()} - - -
- - {tabs.length > 1 && } - - -
- - - {playground && ( - {playground} - )} - {guidelines && ( - {guidelines} - )} - -
{intro}
- <>{children} -
-
+ <> + + {title} {betaBadge} + + } + tabs={renderTabs()}> + {intro} + - {/* Give some space between the bottom of long content and the bottom of the screen */} - -
+ + +
+ + {playground && ( + {playground} + )} + {guidelines && ( + {guidelines} + )} + {children} + +
+
+
+ ); }; diff --git a/src-docs/src/components/guide_page/guide_page_chrome.js b/src-docs/src/components/guide_page/guide_page_chrome.js index 0674e84113b..0fa5a5aaa80 100644 --- a/src-docs/src/components/guide_page/guide_page_chrome.js +++ b/src-docs/src/components/guide_page/guide_page_chrome.js @@ -5,34 +5,14 @@ import { EuiFieldSearch, EuiFlexGroup, EuiFlexItem, - EuiButtonEmpty, EuiSideNav, - EuiSpacer, + EuiPageSideBar, EuiText, - EuiButtonIcon, - EuiPopover, - EuiPopoverTitle, } from '../../../../src/components'; -import { GuideLocaleSelector } from '../guide_locale_selector'; -import { GuideThemeSelector } from '../guide_theme_selector'; import { EuiHighlight } from '../../../../src/components/highlight'; import { EuiBadge } from '../../../../src/components/badge'; -const scrollTo = (position) => { - window.scrollTo({ top: position, behavior: 'smooth' }); -}; - -export function scrollToSelector(selector, attempts = 5) { - const element = document.querySelector(selector); - - if (element) { - scrollTo(element.offsetTop - 120); // Offset affords for the sticky contrast slider - } else if (attempts > 0) { - setTimeout(scrollToSelector.bind(null, selector, attempts - 1), 250); - } -} - export class GuidePageChrome extends Component { _isMounted = false; @@ -94,17 +74,6 @@ export class GuidePageChrome extends Component { }, 250); }; - onClickRoute = () => { - if (this._isMounted) - this.setState( - { - search: '', - isSideNavOpenOnMobile: false, - }, - this.scrollNavSectionIntoView - ); - }; - onButtonClick() { this.setState({ isPopoverOpen: !this.state.isPopoverOpen, @@ -117,54 +86,6 @@ export class GuidePageChrome extends Component { }); } - renderIdentity() { - const button = ( - - ); - return ( - - - - Elastic UI - - - - - - Docs options -
- - {location.host === 'localhost:8030' ? ( // eslint-disable-line no-restricted-globals - - ) : null} -
-
-
-
- ); - } - renderSubSections = (href, subSections = [], searchTerm = '') => { const subSectionsWithTitles = subSections.filter((item) => { if (!item.title) { @@ -259,7 +180,6 @@ export class GuidePageChrome extends Component { id: `${section.type}-${path}`, name: visibleName, href, - onClick: this.onClickRoute.bind(this), items: this.renderSubSections(href, sections, searchTerm), isSelected: item.path === this.props.currentRoute.path, forceOpen: !!(searchTerm && hasMatchingSubItem), @@ -306,24 +226,26 @@ export class GuidePageChrome extends Component { } return ( -
-
- {this.renderIdentity()} - - - -
+ + + -
-
- -
{sideNavContent}
-
+ + + {sideNavContent} + + + ); } } diff --git a/src-docs/src/components/guide_page/guide_page_header.tsx b/src-docs/src/components/guide_page/guide_page_header.tsx new file mode 100644 index 00000000000..d0ee296c2c0 --- /dev/null +++ b/src-docs/src/components/guide_page/guide_page_header.tsx @@ -0,0 +1,152 @@ +import React, { useState } from 'react'; + +import { + EuiHeaderLogo, + EuiHeader, + EuiHeaderSectionItemButton, +} from '../../../../src/components/header'; +import { EuiBadge } from '../../../../src/components/badge'; +import { EuiIcon } from '../../../../src/components/icon'; +import { EuiToolTip } from '../../../../src/components/tool_tip'; +import { EuiPopover } from '../../../../src/components/popover'; +import { useIsWithinBreakpoints } from '../../../../src/services/hooks'; +import { EuiButtonEmpty } from '../../../../src/components/button'; + +// @ts-ignore Not TS +import { CodeSandboxLink } from '../../components/codesandbox/link'; +import { + GuideThemeSelector, + GuideSketchLink, + GuideFigmaLink, +} from '../guide_theme_selector'; + +const pkg = require('../../../../package.json'); + +export type GuidePageHeaderProps = { + onToggleLocale: () => {}; + selectedLocale: string; +}; + +export const GuidePageHeader: React.FunctionComponent = ({ + onToggleLocale, + selectedLocale, +}) => { + const isMobileSize = useIsWithinBreakpoints(['xs', 's']); + + function renderLogo() { + return ( + + Elastic UI + + ); + } + + function renderGithub() { + const href = 'https://github.com/elastic/eui'; + const label = 'EUI GitHub repo'; + return isMobileSize ? ( + + {label} + + ) : ( + + + + + ); + } + + function renderCodeSandbox() { + const label = 'Codesandbox'; + return isMobileSize ? ( + + + {label} + + + ) : ( + + + + + + + ); + } + + const [mobilePopoverIsOpen, setMobilePopoverIsOpen] = useState(false); + + function renderMobileMenu() { + const button = ( + setMobilePopoverIsOpen((isOpen) => !isOpen)}> + + ); + + return ( + setMobilePopoverIsOpen(false)}> +
+ {renderGithub()} + + + {renderCodeSandbox()} +
+
+ ); + } + + const rightSideItems = isMobileSize + ? [ + , + renderMobileMenu(), + ] + : [ + , + renderGithub(), + , + , + renderCodeSandbox(), + ]; + + return ( + + v.{pkg.version} + , + ], + borders: 'none', + }, + { + items: rightSideItems, + borders: 'none', + }, + ]} + /> + ); +}; diff --git a/src-docs/src/components/guide_section/_guide_section.scss b/src-docs/src/components/guide_section/_guide_section.scss index 8784d0bc8d5..ee602ab2ac5 100644 --- a/src-docs/src/components/guide_section/_guide_section.scss +++ b/src-docs/src/components/guide_section/_guide_section.scss @@ -12,3 +12,9 @@ padding-left: $euiSizeXS; padding-right: $euiSizeXS; } + +.guideSectionExampleCode__link { + text-align: right; + padding-top: $euiSizeS; + padding-bottom: $euiSizeS; +} diff --git a/src-docs/src/components/guide_section/guide_section.js b/src-docs/src/components/guide_section/guide_section.js index 60160d0bb27..373e1351a67 100644 --- a/src-docs/src/components/guide_section/guide_section.js +++ b/src-docs/src/components/guide_section/guide_section.js @@ -29,6 +29,16 @@ import { cleanEuiImports } from '../../services'; import { extendedTypesInfo } from './guide_section_extends'; +const slugify = (str) => { + const parts = str + .toLowerCase() + .replace(/[-]+/g, ' ') + .replace(/[^\w^\s]+/g, '') + .replace(/ +/g, ' ') + .split(' '); + return parts.join('-'); +}; + export const markup = (text) => { const regex = /(#[a-zA-Z]+)|(`[^`]+`)/g; return text.split('\n').map((token) => { @@ -107,6 +117,12 @@ const nameToCodeClassMap = { html: 'html', }; +const tabDisplayNameMap = { + javascript: 'Demo JS', + html: 'Demo HTML', + snippet: 'Snippet', +}; + export class GuideSection extends Component { constructor(props) { super(props); @@ -124,18 +140,18 @@ export class GuideSection extends Component { } if (props.source) { - this.tabs.push( - { - name: 'javascript', - displayName: 'Demo JS', - isCode: true, - }, - { - name: 'html', - displayName: 'Demo HTML', - isCode: true, - } - ); + props.source.map((source) => { + this.tabs.push({ + name: + (source.displayName && slugify(source.displayName)) || + tabDisplayNameMap[source.type] || + 'tab', + displayName: + source.displayName || tabDisplayNameMap[source.type] || 'Tab', + isCode: source.type || true, + code: source.code, + }); + }); } if (hasSnippet) { @@ -183,16 +199,13 @@ export class GuideSection extends Component { }; onSelectedTabChanged = (selectedTab) => { - const { name } = selectedTab; + const { isCode, code } = selectedTab; let renderedCode = null; - if (name === 'html' || name === 'javascript') { - const { code } = this.props.source.find( - (sourceObject) => sourceObject.type === name - ); + if (isCode === 'html' || isCode === 'javascript') { renderedCode = code; - if (name === 'javascript') { + if (isCode === 'javascript') { renderedCode = renderedCode.default .replace( /(from )'(..\/)+src\/services(\/?';)/g, @@ -238,13 +251,13 @@ export class GuideSection extends Component { len = renderedCode.replace('\n\n\n', '\n\n').length; } renderedCode = cleanEuiImports(renderedCode); - } else if (name === 'html') { + } else if (isCode === 'html') { renderedCode = code.render(); } } - this.setState({ selectedTab, renderedCode }, () => { - if (name === 'javascript') { + this.setState({ selectedTab, renderedCode, code }, () => { + if (isCode === 'javascript') { requestAnimationFrame(() => { const pre = this.refs.javascript.querySelector('.euiCodeBlock__pre'); if (!pre) return; @@ -276,7 +289,11 @@ export class GuideSection extends Component { } renderSnippet() { - const { snippet } = this.props; + let { snippet } = this.props; + + if (this.props.source?.find((tab) => tab.type === 'snippet')) { + snippet = this.props.source?.find((tab) => tab.type === 'snippet').code; + } if (!snippet) { return; @@ -601,8 +618,10 @@ export class GuideSection extends Component { if (name === 'javascript') { return (
- {name === 'javascript' ? this.renderCodeSandBoxButton() : null} {euiCodeBlock} + {name === 'javascript' + ? this.renderCodeSandBoxButton(this.state.code) + : null}
); } @@ -622,7 +641,7 @@ export class GuideSection extends Component { if (this.state.selectedTab.isCode) { return ( - {this.renderCode(this.state.selectedTab.name)} + {this.renderCode(this.state.selectedTab.isCode)} ); } @@ -641,9 +660,15 @@ export class GuideSection extends Component { ); } - renderCodeSandBoxButton() { + renderCodeSandBoxButton(code) { + if (!code) { + return; + } + return ( - + Try out this demo on Code Sandbox diff --git a/src-docs/src/components/guide_section/guide_section_types.js b/src-docs/src/components/guide_section/guide_section_types.js index 238dbb1067f..ad0b93c736e 100644 --- a/src-docs/src/components/guide_section/guide_section_types.js +++ b/src-docs/src/components/guide_section/guide_section_types.js @@ -1,4 +1,5 @@ export const GuideSectionTypes = { JS: 'javascript', HTML: 'html', + SNIPPET: 'snippet', }; diff --git a/src-docs/src/components/guide_theme_selector/guide_figma_link.tsx b/src-docs/src/components/guide_theme_selector/guide_figma_link.tsx new file mode 100644 index 00000000000..ca85611ab53 --- /dev/null +++ b/src-docs/src/components/guide_theme_selector/guide_figma_link.tsx @@ -0,0 +1,60 @@ +/* eslint-disable no-restricted-globals */ +import React from 'react'; + +import { EuiButtonEmpty } from '../../../../src/components/button'; +import { useIsWithinBreakpoints } from '../../../../src/services/hooks/useIsWithinBreakpoints'; + +import { ThemeContext } from '../with_theme'; +import { EuiHeaderSectionItemButton } from '../../../../src/components/header'; +import { EuiToolTip } from '../../../../src/components/tool_tip'; +import { EuiIcon } from '../../../../src/components/icon'; +import logoFigma from '../../images/logo-figma.svg'; + +type GuideFigmaLinkProps = { + context?: any; +}; + +export const GuideFigmaLink: React.FunctionComponent = () => { + return ( + + {(context) => } + + ); +}; + +// @ts-ignore Context has no type +const GuideFigmaLinkComponent: React.FunctionComponent = ({ + context, +}) => { + const isMobileSize = useIsWithinBreakpoints(['xs', 's']); + + const isAmsterdam = context.theme.includes('amsterdam'); + + let href = 'https://www.figma.com/community/file/809845546262698150'; + let label = 'EUI Figma Design Library'; + + if (isAmsterdam) { + href = + 'https://www.figma.com/file/RzfYLj2xmH9K7gQtbSKygn/BETA-EUI-Amsterdam'; + label = `${label} (private beta)`; + } + + return isMobileSize ? ( + + {label} + + ) : ( + + + + + ); +}; diff --git a/src-docs/src/components/guide_theme_selector/guide_sketch_link.tsx b/src-docs/src/components/guide_theme_selector/guide_sketch_link.tsx new file mode 100644 index 00000000000..503c2dafe83 --- /dev/null +++ b/src-docs/src/components/guide_theme_selector/guide_sketch_link.tsx @@ -0,0 +1,56 @@ +/* eslint-disable no-restricted-globals */ +import React from 'react'; + +import { EuiButtonEmpty } from '../../../../src/components/button'; +import { useIsWithinBreakpoints } from '../../../../src/services/hooks/useIsWithinBreakpoints'; + +import { ThemeContext } from '../with_theme'; +import { EuiHeaderSectionItemButton } from '../../../../src/components/header'; +import { EuiToolTip } from '../../../../src/components/tool_tip'; +import { EuiIcon } from '../../../../src/components/icon'; + +type GuideSketchLinkProps = { + context?: any; +}; + +export const GuideSketchLink: React.FunctionComponent = () => { + return ( + + {(context) => } + + ); +}; + +// @ts-ignore Context has no type +const GuideSketchLinkComponent: React.FunctionComponent = ({ + context, +}) => { + const isMobileSize = useIsWithinBreakpoints(['xs', 's']); + + const href = + 'https://github.com/elastic/eui/releases/download/v8.0.0/eui_sketch_8.0.0.zip'; + const label = 'EUI Sketch Library (download)'; + + const isAmsterdam = context.theme.includes('amsterdam'); + + if (isAmsterdam) return <>; + + return isMobileSize ? ( + + {label} + + ) : ( + + + + + ); +}; diff --git a/src-docs/src/components/guide_theme_selector/guide_theme_selector.js b/src-docs/src/components/guide_theme_selector/guide_theme_selector.js deleted file mode 100644 index b1a6257117b..00000000000 --- a/src-docs/src/components/guide_theme_selector/guide_theme_selector.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; - -import { ThemeContext } from '../with_theme'; -import { EuiSelect, EuiFormRow } from '../../../../src/components'; -import { EUI_THEMES } from '../../../../src/themes'; - -export const GuideThemeSelector = () => { - return ( - - {(context) => } - - ); -}; - -const GuideThemeSelectorComponent = ({ context }) => { - return ( - - { - context.changeTheme(e.target.value); - }} - aria-label="Switch the theme" - /> - - ); -}; diff --git a/src-docs/src/components/guide_theme_selector/guide_theme_selector.tsx b/src-docs/src/components/guide_theme_selector/guide_theme_selector.tsx new file mode 100644 index 00000000000..8253e46205f --- /dev/null +++ b/src-docs/src/components/guide_theme_selector/guide_theme_selector.tsx @@ -0,0 +1,107 @@ +/* eslint-disable no-restricted-globals */ +import React, { useState } from 'react'; + +import { EuiButton } from '../../../../src/components/button'; +import { + EuiContextMenuPanel, + EuiContextMenuItem, +} from '../../../../src/components/context_menu'; +import { EuiPopover } from '../../../../src/components/popover'; +import { EuiHorizontalRule } from '../../../../src/components/horizontal_rule'; +import { useIsWithinBreakpoints } from '../../../../src/services/hooks/useIsWithinBreakpoints'; +import { EUI_THEME, EUI_THEMES } from '../../../../src/themes'; + +import { ThemeContext } from '../with_theme'; +// @ts-ignore Not TS +import { GuideLocaleSelector } from '../guide_locale_selector'; + +type GuideThemeSelectorProps = { + onToggleLocale: () => {}; + selectedLocale: string; + context?: any; +}; + +export const GuideThemeSelector: React.FunctionComponent = ({ + ...rest +}) => { + return ( + + {(context) => } + + ); +}; + +// @ts-ignore Context has no type +const GuideThemeSelectorComponent: React.FunctionComponent = ({ + context, + onToggleLocale, + selectedLocale, +}) => { + const isMobileSize = useIsWithinBreakpoints(['xs', 's']); + const [isPopoverOpen, setPopover] = useState(false); + + const onButtonClick = () => { + setPopover(!isPopoverOpen); + }; + + const closePopover = () => { + setPopover(false); + }; + + const currentTheme: EUI_THEME = + EUI_THEMES.find((theme) => theme.value === context.theme) || EUI_THEMES[0]; + + const getIconType = (value: EUI_THEME['value']) => { + return value === currentTheme.value ? 'check' : 'empty'; + }; + + const items = EUI_THEMES.map((theme) => { + return ( + { + closePopover(); + context.changeTheme(theme.value); + }}> + {theme.text} + + ); + }); + + const button = ( + + {isMobileSize ? 'Theme' : currentTheme.text} + + ); + + return ( + + + {location.host === 'localhost:8030' && ( + <> + +
+ +
+ + )} +
+ ); +}; diff --git a/src-docs/src/components/guide_theme_selector/index.js b/src-docs/src/components/guide_theme_selector/index.js deleted file mode 100644 index f295f9e31b7..00000000000 --- a/src-docs/src/components/guide_theme_selector/index.js +++ /dev/null @@ -1 +0,0 @@ -export { GuideThemeSelector } from './guide_theme_selector'; diff --git a/src-docs/src/components/guide_theme_selector/index.ts b/src-docs/src/components/guide_theme_selector/index.ts new file mode 100644 index 00000000000..6c24aabe99c --- /dev/null +++ b/src-docs/src/components/guide_theme_selector/index.ts @@ -0,0 +1,3 @@ +export { GuideThemeSelector } from './guide_theme_selector'; +export { GuideFigmaLink } from './guide_figma_link'; +export { GuideSketchLink } from './guide_sketch_link'; diff --git a/src-docs/src/components/scroll_to_hash.tsx b/src-docs/src/components/scroll_to_hash.tsx index 95adf3ed4aa..033fdfc36e4 100644 --- a/src-docs/src/components/scroll_to_hash.tsx +++ b/src-docs/src/components/scroll_to_hash.tsx @@ -5,8 +5,12 @@ const ScrollToHash: FunctionComponent = () => { const location = useLocation(); useEffect(() => { const element = document.getElementById(location.hash.replace('#', '')); + const headerOffset = 72; if (element) { - element.scrollIntoView({ behavior: 'smooth' }); + window.scrollTo({ + top: element.offsetTop - headerOffset, + behavior: 'smooth', + }); } else { window.scrollTo({ behavior: 'auto', diff --git a/src-docs/src/images/buttons.svg b/src-docs/src/images/buttons.svg index f37229916a8..297aded5395 100644 --- a/src-docs/src/images/buttons.svg +++ b/src-docs/src/images/buttons.svg @@ -1,21 +1,7 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + diff --git a/src-docs/src/images/cards.svg b/src-docs/src/images/cards.svg index 92ec14797df..a00f864af5a 100644 --- a/src-docs/src/images/cards.svg +++ b/src-docs/src/images/cards.svg @@ -1,28 +1,27 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src-docs/src/images/content.svg b/src-docs/src/images/content.svg new file mode 100644 index 00000000000..4cff9bd3993 --- /dev/null +++ b/src-docs/src/images/content.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src-docs/src/images/content_center.svg b/src-docs/src/images/content_center.svg new file mode 100644 index 00000000000..ee3b9ecab61 --- /dev/null +++ b/src-docs/src/images/content_center.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src-docs/src/images/flexgrid.svg b/src-docs/src/images/flexgrid.svg index 3da3d816c24..3463482f3a2 100644 --- a/src-docs/src/images/flexgrid.svg +++ b/src-docs/src/images/flexgrid.svg @@ -1,12 +1,10 @@ - - - - - - - - - - - + + + + + + + + + diff --git a/src-docs/src/images/forms.svg b/src-docs/src/images/forms.svg index 804c9c550db..8e21bacbd6a 100644 --- a/src-docs/src/images/forms.svg +++ b/src-docs/src/images/forms.svg @@ -1,30 +1,13 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/src-docs/src/images/home_illustration.svg b/src-docs/src/images/home_illustration.svg new file mode 100644 index 00000000000..8e973665a81 --- /dev/null +++ b/src-docs/src/images/home_illustration.svg @@ -0,0 +1,851 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src-docs/src/images/icons.svg b/src-docs/src/images/icons.svg index 9c2c1394acd..936cf01c463 100644 --- a/src-docs/src/images/icons.svg +++ b/src-docs/src/images/icons.svg @@ -1,6 +1,254 @@ - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src-docs/src/images/page.svg b/src-docs/src/images/page.svg index ba8d49aa604..f10327f7cc4 100644 --- a/src-docs/src/images/page.svg +++ b/src-docs/src/images/page.svg @@ -1,13 +1,24 @@ - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src-docs/src/images/side_nav.svg b/src-docs/src/images/side_nav.svg new file mode 100644 index 00000000000..b7baf5bcb2c --- /dev/null +++ b/src-docs/src/images/side_nav.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src-docs/src/images/single.svg b/src-docs/src/images/single.svg new file mode 100644 index 00000000000..923db47dc8b --- /dev/null +++ b/src-docs/src/images/single.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src-docs/src/images/tables.svg b/src-docs/src/images/tables.svg index 089f950ec03..fccc19b53fc 100644 --- a/src-docs/src/images/tables.svg +++ b/src-docs/src/images/tables.svg @@ -1,37 +1,32 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src-docs/src/images/text.svg b/src-docs/src/images/text.svg index 3d9c2d1431c..e9293fe88cb 100644 --- a/src-docs/src/images/text.svg +++ b/src-docs/src/images/text.svg @@ -1,12 +1,10 @@ - - - - - - - - - - - + + + + + + + + + diff --git a/src-docs/src/index.html b/src-docs/src/index.html index 2221e62eb46..4c5417fd557 100644 --- a/src-docs/src/index.html +++ b/src-docs/src/index.html @@ -6,6 +6,6 @@ -
+
diff --git a/src-docs/src/routes.js b/src-docs/src/routes.js index b19e7999f9c..14eb1bfa49e 100644 --- a/src-docs/src/routes.js +++ b/src-docs/src/routes.js @@ -151,6 +151,8 @@ import { OverlayMaskExample } from './views/overlay_mask/overlay_mask_example'; import { PageExample } from './views/page/page_example'; +import { PageHeaderExample } from './views/page_header/page_header_example'; + import { PaginationExample } from './views/pagination/pagination_example'; import { PanelExample } from './views/panel/panel_example'; @@ -335,6 +337,7 @@ const navigation = [ HorizontalRuleExample, ModalExample, PageExample, + PageHeaderExample, PanelExample, PopoverExample, ResizableContainerExample, @@ -393,7 +396,6 @@ const navigation = [ LoadingExample, ProgressExample, StatExample, - TextExample, TitleExample, ToastExample, diff --git a/src-docs/src/services/full_screen/full_screen.tsx b/src-docs/src/services/full_screen/full_screen.tsx index 67895ead2ef..7c0567a673d 100644 --- a/src-docs/src/services/full_screen/full_screen.tsx +++ b/src-docs/src/services/full_screen/full_screen.tsx @@ -46,7 +46,11 @@ export const GuideFullScreen: FunctionComponent<{ {buttonText} - {fullScreen && {children(setFullScreen)}} + {fullScreen && ( +
+ {children(setFullScreen)} +
+ )} ); }; diff --git a/src-docs/src/services/playground/playground.js b/src-docs/src/services/playground/playground.js index 01293d19d82..58eb52e47be 100644 --- a/src-docs/src/services/playground/playground.js +++ b/src-docs/src/services/playground/playground.js @@ -66,6 +66,7 @@ export default ({ config, setGhostBackground, playgroundClassName }) => {

{config.componentName}

+
- + <> + + -
+ + {(context) => { @@ -73,14 +79,14 @@ export class AppView extends Component { }} -
-
- + + + ); } render() { - return
{this.renderContent()}
; + return this.renderContent(); } onKeydown = (event) => { diff --git a/src-docs/src/views/collapsible_nav/collapsible_nav_all.tsx b/src-docs/src/views/collapsible_nav/collapsible_nav_all.tsx index b9237aa2f56..d498c1c6992 100644 --- a/src-docs/src/views/collapsible_nav/collapsible_nav_all.tsx +++ b/src-docs/src/views/collapsible_nav/collapsible_nav_all.tsx @@ -274,7 +274,7 @@ export default () => { ]} /> - + )} diff --git a/src-docs/src/views/guidelines/colors/_color_section.scss b/src-docs/src/views/guidelines/colors/_color_section.scss index de98603eca6..bb7da11c1e6 100644 --- a/src-docs/src/views/guidelines/colors/_color_section.scss +++ b/src-docs/src/views/guidelines/colors/_color_section.scss @@ -1,8 +1,8 @@ @include euiBreakpoint('m', 'l', 'xl') { .guideColorsPage__stickySlider { position: sticky; - top: 0; - z-index: $euiZLevel1; + top: $euiHeaderHeightCompensation; + z-index: $euiZLevel1 - 1; // Below header } } diff --git a/src-docs/src/views/guidelines/colors/color_section.js b/src-docs/src/views/guidelines/colors/color_section.js index f473aeccd1e..e1ee92e7ea2 100644 --- a/src-docs/src/views/guidelines/colors/color_section.js +++ b/src-docs/src/views/guidelines/colors/color_section.js @@ -9,6 +9,7 @@ import { EuiSpacer, EuiText, EuiFlexGrid, + EuiPanel, } from '../../../../../src/components'; import { getHexValueFromColorName, @@ -62,36 +63,33 @@ export const ColorSection = ({ - - - {showTextVariants && colorIsCore(color) && ( - - )} - - {colorsForContrast.map((color2) => { - if (colorIsCore(color) && colorIsCore(color2)) { - // i.e. don't render if both are core colors - return; - } - return ( + + + + {showTextVariants && colorIsCore(color) && ( - ); - })} - - + )} + {colorsForContrast.map((color2) => { + if (colorIsCore(color) && colorIsCore(color2)) { + // i.e. don't render if both are core colors + return; + } + return ( + + ); + })} + + + ); }; diff --git a/src-docs/src/views/guidelines/colors/contrast_slider.js b/src-docs/src/views/guidelines/colors/contrast_slider.js index 1b5f20a7d57..96538832773 100644 --- a/src-docs/src/views/guidelines/colors/contrast_slider.js +++ b/src-docs/src/views/guidelines/colors/contrast_slider.js @@ -9,6 +9,7 @@ import { EuiIcon, EuiSwitch, EuiSpacer, + EuiPanel, } from '../../../../../src/components'; import { ratingAAA, ratingAA18, ratingAA, ratingAll } from './_utilities'; @@ -94,43 +95,49 @@ export const ContrastSlider = ({ className="guideSection__emptyBox guideColorsPage__stickySlider" justifyContent="center" {...rest}> - - - { - setValue(e.currentTarget.value); - onChange(e.currentTarget.value, checked); - }} - showTicks - showValue - ticks={ticks} - valueAppend="+" - /> - - - - -
- - + + + { - setChecked(e.target.checked); - onChange(value, e.target.checked); + setValue(e.currentTarget.value); + onChange(e.currentTarget.value, checked); }} + showTicks + showValue + ticks={ticks} + valueAppend="+" + fullWidth /> -
-
+ + +
+ + + +
+ + { + setChecked(e.target.checked); + onChange(value, e.target.checked); + }} + /> +
+
+
); diff --git a/src-docs/src/views/guidelines/colors/core_palette.js b/src-docs/src/views/guidelines/colors/core_palette.js index a672d7a4428..c99a135de4a 100644 --- a/src-docs/src/views/guidelines/colors/core_palette.js +++ b/src-docs/src/views/guidelines/colors/core_palette.js @@ -7,9 +7,19 @@ import { EuiIcon, EuiCopy, EuiScreenReaderOnly, + EuiPanel, } from '../../../../../src/components'; import { rgbToHex } from '../../../../../src/services'; -import { scrollToSelector } from '../../../components/guide_page/guide_page_chrome'; + +export function scrollToSelector(selector, attempts = 5) { + const element = document.querySelector(selector); + + if (element) { + window.scrollTo({ top: element.offsetTop - 168, behavior: 'smooth' }); // Offset affords for the sticky contrast slider + } else if (attempts > 0) { + setTimeout(scrollToSelector.bind(null, selector, attempts - 1), 250); + } +} export const CorePalette = ({ theme, colors }) => { const palette = getSassVars(theme); @@ -58,14 +68,12 @@ export const CorePalette = ({ theme, colors }) => { } return ( - - {colors.map(function (color, index) { - return renderPaletteColor(palette, color, index); - })} - + + + {colors.map(function (color, index) { + return renderPaletteColor(palette, color, index); + })} + + ); }; diff --git a/src-docs/src/views/guidelines/colors/vis_palette.js b/src-docs/src/views/guidelines/colors/vis_palette.js index 8c6d2250a42..ffa5b27f347 100644 --- a/src-docs/src/views/guidelines/colors/vis_palette.js +++ b/src-docs/src/views/guidelines/colors/vis_palette.js @@ -8,6 +8,7 @@ import { EuiCopy, EuiTitle, EuiText, + EuiPanel, } from '../../../../../src/components'; import { rgbToHex } from '../../../../../src/services'; @@ -52,13 +53,12 @@ export const VisPalette = ({ variant }) => { } return ( - - {visColorKeys.map(function (color, index) { - return renderPaletteColor(visColors, color, index, variant); - })} - + + + {visColorKeys.map(function (color, index) { + return renderPaletteColor(visColors, color, index, variant); + })} + + ); }; diff --git a/src-docs/src/views/guidelines/index.scss b/src-docs/src/views/guidelines/index.scss index 7a38c15974a..6cb4f65a49e 100644 --- a/src-docs/src/views/guidelines/index.scss +++ b/src-docs/src/views/guidelines/index.scss @@ -318,12 +318,6 @@ } } -.guideSection__shadedBox { - background-color: $euiPageBackgroundColor; - padding: $euiSize; - border-radius: $euiBorderRadius; -} - .guideSection__emptyBox { background-color: $euiColorEmptyShade; } diff --git a/src-docs/src/views/header/header_elastic_pattern.js b/src-docs/src/views/header/header_elastic_pattern.js index 3073341095e..77e5757eb01 100644 --- a/src-docs/src/views/header/header_elastic_pattern.js +++ b/src-docs/src/views/header/header_elastic_pattern.js @@ -29,6 +29,7 @@ import { EuiTitle, EuiSelectableTemplateSitewide, EuiSelectableMessage, + EuiFlexGroup, } from '../../../../src/components'; export default ({ theme }) => { @@ -282,88 +283,91 @@ export default ({ theme }) => { {/* FocusTrap for Docs only */} {fullScreen && ( - - Elastic - , - deploymentMenu, - ], - borders: 'none', - }, - { - items: [ - {search}, - ], - borders: 'none', - }, - { - items: [ - {search}, - - setIsAlertFlyoutVisible(!isAlertFlyoutVisible) - }> - - , - userMenu, - ], - borders: 'none', - }, - ]} - /> - {}, - }, - { - text: 'Users', - }, - ], - borders: 'right', - }, - { - items: [ - - Share - Clone - { - setFullScreen(false); - document.body.classList.remove( - 'euiBody--headerIsFixed--double' - ); + + + Elastic + , + deploymentMenu, + ], + borders: 'none', + }, + { + items: [ + {search}, + ], + borders: 'none', + }, + { + items: [ + {search}, + + setIsAlertFlyoutVisible(!isAlertFlyoutVisible) + }> + + , + userMenu, + ], + borders: 'none', + }, + ]} + /> + {}, + }, + { + text: 'Users', + }, + ], + borders: 'right', + }, + { + items: [ + - Exit full screen - - , - ], - }, - ]} - /> + Share + Clone + { + setFullScreen(false); + document.body.classList.remove( + 'euiBody--headerIsFixed--double' + ); + }}> + Exit full screen + + , + ], + }, + ]} + /> - {isAlertFlyoutVisible ? headerAlerts : null} + {isAlertFlyoutVisible ? headerAlerts : null} - + + )} diff --git a/src-docs/src/views/header/header_sections.js b/src-docs/src/views/header/header_sections.js index 6725bd9b33b..95bffbfde6d 100644 --- a/src-docs/src/views/header/header_sections.js +++ b/src-docs/src/views/header/header_sections.js @@ -50,18 +50,19 @@ export default () => { ]; const renderSearch = ( - + ); const renderUser = ( - + ); const renderApps = ( diff --git a/src-docs/src/views/home/home_view.js b/src-docs/src/views/home/home_view.js index 387cf9ad292..a8383e4f58a 100644 --- a/src-docs/src/views/home/home_view.js +++ b/src-docs/src/views/home/home_view.js @@ -1,7 +1,5 @@ import React from 'react'; -import { Link } from 'react-router-dom'; - import imageIcons from '../../images/icons.svg'; import imageButtons from '../../images/buttons.svg'; import imageTables from '../../images/tables.svg'; @@ -11,236 +9,200 @@ import imageCards from '../../images/cards.svg'; import imagePages from '../../images/page.svg'; import imageText from '../../images/text.svg'; import imageCharts from '../../images/charts.svg'; -import logoFigma from '../../images/logo-figma.svg'; +import homeIllustration from '../../images/home_illustration.svg'; import { EuiCard, - EuiFlexGrid, EuiFlexGroup, EuiFlexItem, - EuiIcon, EuiLink, EuiSpacer, EuiText, EuiTitle, - EuiToolTip, - EuiScreenReaderOnly, + EuiPanel, + EuiImage, + EuiIcon, + EuiFlexGrid, + EuiPageContent, + EuiPageContentBody, } from '../../../../src/components'; - -import { CodeSandboxLink } from '../../components/codesandbox'; - -const pkg = require('../../../../package.json'); +import { Link } from 'react-router-dom'; export const HomeView = () => ( -
- - - -

Elastic UI framework

-
-
- - - - - - Elastic repo on GitHub - - + + +
+ + + + +

Elastic UI

+
+ + + +

The framework powering the Elastic Stack

+
+ + +

+ The Elastic UI framework (EUI) is a design library in use at + Elastic to build internal products that need to share our + aesthetics. It distributes UI React components and static + assets for use in building web layouts. +

+ + + + Getting started + + + + + What's new + + + + + Contributing + + + +
+
+ + + +
+
+ + + + } + layout="horizontal" + display="plain" + titleSize="xs" + title="Accessible to everyone" + description="Uses high contrast, color-blind safe palettes and tested with most + assistive technology." + /> - -

- Version:{' '} - - {pkg.version} - -

+ + } + layout="horizontal" + display="plain" + titleSize="xs" + title="Flexible and composable" + description="Configurable enough to meet the needs of a wide array of contexts while maintaining brand and low-level consistency." + /> - -
- - - - -

Libraries:

+ + } + layout="horizontal" + display="plain" + titleSize="xs" + title="Well documented and tested" + description="Code is friendly to the novice and expert alike." + /> - - - - Elastic UI Library on Figma - - - - +
+ + + + - - - - - Elastic UI Library for Sketch - - - + + - - - - - - - - Codesandbox - - + + + + + + + + + + + + + + + + + - - + + - - - - -

- The Elastic UI framework (EUI) is a design library in use at Elastic to - build internal products that need to share our aesthetics. It - distributes UI React components and static assets for use in building - web layouts. Alongside the React components is a SASS/CSS layer that can - be used independently on its own. If this is your first time using EUI - you might want to read up on{' '} - - how to consume EUI - {' '} - and{' '} - - Kibana plugin development - {' '} - in general. -

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Design goals

-
-
EUI is accessible to everyone.
-
- Uses high contrast, color-blind safe palettes and tested with most - assistive technology. -
-
EUI is themable.
-
- Theming involves changing fewer than a dozen lines of code. This means - strict variable usage. -
-
EUI is flexible and composable.
-
- Configurable enough to meet the needs of a wide array of contexts - while maintaining brand and low-level consistency. -
-
EUI is responsive.
-
Supports multiple window sizes from large desktop to mobile.
-
EUI is well documented and tested.
-
Code is friendly to the novice and expert alike.
-
EUI is playful.
-
Simple and consistent use of animation brings life.
-
-
-
+
+ + ); diff --git a/src-docs/src/views/page/_page_demo.tsx b/src-docs/src/views/page/_page_demo.tsx new file mode 100644 index 00000000000..8fb77f8d968 --- /dev/null +++ b/src-docs/src/views/page/_page_demo.tsx @@ -0,0 +1,102 @@ +import React, { + useState, + useEffect, + ReactNode, + FunctionComponent, +} from 'react'; +import { EuiImage } from '../../../../src/components/image'; +import { EuiButton } from '../../../../src/components/button'; +import { EuiFocusTrap } from '../../../../src/components/focus_trap'; +import { EuiSpacer } from '../../../../src/components/spacer'; +import { EuiSwitch } from '../../../../src/components/form'; +import { EuiTextAlign } from '../../../../src/components/text'; +import { useIsWithinBreakpoints } from '../../../../src/services/hooks'; + +import content from '../../images/content.svg'; +import contentCenter from '../../images/content_center.svg'; +import sideNav from '../../images/side_nav.svg'; +import single from '../../images/single.svg'; + +export const PageDemo: FunctionComponent<{ + children?: ( + button: typeof EuiButton, + Content: ReactNode, + SideNav: ReactNode, + showTemplate: boolean + ) => ReactNode; + centered?: boolean; +}> = ({ children, centered }) => { + const isMobileSize = useIsWithinBreakpoints(['xs', 's']); + const [showTemplate, setShowTemplate] = useState(true); + const [fullScreen, setFullScreen] = useState(false); + useEffect(() => { + if (fullScreen) { + document.body.classList.add('guideBody--overflowHidden'); + } + return () => { + document.body.classList.remove('guideBody--overflowHidden'); + }; + }, [fullScreen]); + + const Button = () => { + return fullScreen ? ( + setFullScreen(false)} fill iconType="minimize"> + Exit fullscreen + + ) : ( + setFullScreen(true)} fill iconType="fullScreen"> + Go fullscreen + + ); + }; + + const SideNav = () => ( + + ); + const Content = () => ( + <> + + {!centered && ( + <> + + + + )} + + ); + + return ( + <> + +
+ {children && children(Button, Content, SideNav, showTemplate)} +
+
+ + + setShowTemplate((showing) => !showing)} + /> + + + ); +}; diff --git a/src-docs/src/views/page/page.js b/src-docs/src/views/page/page.js index d6d4eca6e77..967ca093ee7 100644 --- a/src-docs/src/views/page/page.js +++ b/src-docs/src/views/page/page.js @@ -5,18 +5,17 @@ import { EuiPageBody, EuiPageContent, EuiPageContentBody, - EuiPageContentHeader, - EuiPageContentHeaderSection, EuiPageHeader, EuiPageSideBar, EuiTitle, EuiButton, + EuiSpacer, } from '../../../../src/components'; export default () => ( SideBar nav - + ( ]} /> - - - -

Content title

-
-
- - Content abilities - -
+ +

Content title

+
+ Content body
diff --git a/src-docs/src/views/page/page_centered_body.js b/src-docs/src/views/page/page_centered_body.js new file mode 100644 index 00000000000..7505c052f3f --- /dev/null +++ b/src-docs/src/views/page/page_centered_body.js @@ -0,0 +1,30 @@ +import React from 'react'; + +import { + EuiPage, + EuiPageContent, + EuiEmptyPrompt, + EuiPageSideBar, + EuiPageBody, +} from '../../../../src/components'; + +export default ({ button = <>, content, sideNav }) => { + return ( + + {sideNav} + + + + No spice} + body={content} + actions={button} + /> + + + + ); +}; diff --git a/src-docs/src/views/page/page_centered_body_template.js b/src-docs/src/views/page/page_centered_body_template.js new file mode 100644 index 00000000000..498136f4eab --- /dev/null +++ b/src-docs/src/views/page/page_centered_body_template.js @@ -0,0 +1,18 @@ +import React from 'react'; + +import { EuiPageTemplate, EuiEmptyPrompt } from '../../../../src/components'; + +export default ({ button = <>, content, sideNav }) => { + return ( + + No spice} + body={content} + actions={button} + /> + + ); +}; diff --git a/src-docs/src/views/page/page_centered_content.js b/src-docs/src/views/page/page_centered_content.js new file mode 100644 index 00000000000..aa4164dba0e --- /dev/null +++ b/src-docs/src/views/page/page_centered_content.js @@ -0,0 +1,34 @@ +import React from 'react'; + +import { + EuiPage, + EuiPageContent, + EuiEmptyPrompt, + EuiPageHeader, + EuiPageSideBar, + EuiPageBody, +} from '../../../../src/components'; + +export default ({ button = <>, content, sideNav }) => ( + + {sideNav} + + + + + + No spice} body={content} /> + + + +); diff --git a/src-docs/src/views/page/page_centered_content_template.js b/src-docs/src/views/page/page_centered_content_template.js new file mode 100644 index 00000000000..b84db173c3f --- /dev/null +++ b/src-docs/src/views/page/page_centered_content_template.js @@ -0,0 +1,17 @@ +import React from 'react'; + +import { EuiPageTemplate, EuiEmptyPrompt } from '../../../../src/components'; + +export default ({ button = <>, content, sideNav }) => ( + + No spice} body={content} /> + +); diff --git a/src-docs/src/views/page/page_content_center.js b/src-docs/src/views/page/page_content_center.js deleted file mode 100644 index df0281cf27a..00000000000 --- a/src-docs/src/views/page/page_content_center.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; - -import { - EuiPage, - EuiPageBody, - EuiPageContent, - EuiPageContentBody, - EuiPageContentHeader, - EuiPageContentHeaderSection, - EuiTitle, -} from '../../../../src/components'; - -export default () => ( - - - - - - -

Content title

-
-
-
- Content body -
-
-
-); diff --git a/src-docs/src/views/page/page_content_center_with_side_bar.js b/src-docs/src/views/page/page_content_center_with_side_bar.js deleted file mode 100644 index 27e97f9aa33..00000000000 --- a/src-docs/src/views/page/page_content_center_with_side_bar.js +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react'; - -import { - EuiPage, - EuiPageBody, - EuiPageContent, - EuiPageContentBody, - EuiPageContentHeader, - EuiPageContentHeaderSection, - EuiPageHeader, - EuiPageSideBar, - EuiTitle, -} from '../../../../src/components'; - -export default () => ( - - SideBar nav - {/* The EUI docs site already has a wrapping
tag, so we've changed this example to a
for accessibility. You likely don't need to copy the `component` prop for your own usage. */} - - - - - - -

Content title

-
-
- - Content abilities - -
- Content body -
-
- -); diff --git a/src-docs/src/views/page/page_content_only.js b/src-docs/src/views/page/page_content_only.js deleted file mode 100644 index 60a6964763e..00000000000 --- a/src-docs/src/views/page/page_content_only.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; - -import { - EuiPage, - EuiPageBody, - EuiPageContent, - EuiPageContentBody, - EuiPageContentHeader, - EuiPageContentHeaderSection, - EuiTitle, -} from '../../../../src/components'; - -export default () => ( - - - - - - -

Content title

-
-
-
- Content body -
-
-
-); diff --git a/src-docs/src/views/page/page_custom_content.js b/src-docs/src/views/page/page_custom_content.js new file mode 100644 index 00000000000..613f5aa46ff --- /dev/null +++ b/src-docs/src/views/page/page_custom_content.js @@ -0,0 +1,41 @@ +import React from 'react'; + +import { + EuiPage, + EuiPageHeader, + EuiButton, + EuiPageContentBody, + EuiPageBody, + EuiFlexGrid, + EuiFlexItem, + EuiPanel, +} from '../../../../src/components'; + +export default ({ button = <> }) => ( + + + Do something]} + paddingSize="l" + /> + + + + + + + + + + + + + + + + + + +); diff --git a/src-docs/src/views/page/page_custom_content_template.js b/src-docs/src/views/page/page_custom_content_template.js new file mode 100644 index 00000000000..b6ea7e40957 --- /dev/null +++ b/src-docs/src/views/page/page_custom_content_template.js @@ -0,0 +1,35 @@ +import React from 'react'; + +import { + EuiPageTemplate, + EuiButton, + EuiFlexGrid, + EuiFlexItem, + EuiPanel, +} from '../../../../src/components'; + +export default ({ button = <> }) => ( + Do something], + }}> + + + + + + + + + + + + + + + +); diff --git a/src-docs/src/views/page/page_example.js b/src-docs/src/views/page/page_example.js index a84baa69ba2..ebc2e720f0c 100644 --- a/src-docs/src/views/page/page_example.js +++ b/src-docs/src/views/page/page_example.js @@ -1,9 +1,11 @@ import React from 'react'; +import { Link } from 'react-router-dom'; import { renderToHtml } from '../../services'; import { GuideSectionTypes } from '../../components'; -import Playground from './playground'; +import { pageTemplateConfig } from './playground'; +import { PageDemo } from './_page_demo'; import { EuiCode, @@ -11,71 +13,117 @@ import { EuiPageBody, EuiPageContent, EuiPageContentBody, - EuiPageContentHeader, - EuiPageContentHeaderSection, EuiPageHeader, - EuiPageHeaderSection, EuiPageSideBar, EuiText, + EuiEmptyPrompt, + EuiPageTemplate, + EuiCallOut, EuiSpacer, } from '../../../../src/components'; -import Page from './page'; -const pageSource = require('!!raw-loader!./page'); -const pageHtml = renderToHtml(Page); +import PageNew from './page_new'; +const pageNewSource = require('!!raw-loader!./page_new'); +import PageTemplate from './page_template'; +const PageTemplateSource = require('!!raw-loader!./page_template'); +const PageTemplateHtml = renderToHtml(PageTemplate); -import PageSimple from './page_simple'; -const pageSimpleSource = require('!!raw-loader!./page_simple'); -const pageSimpleHtml = renderToHtml(PageSimple); +import PageRestricingWidth from './page_restricting_width'; +const PageRestricingWidthSource = require('!!raw-loader!./page_restricting_width'); +import PageRestricingWidthTemplate from './page_restricting_width_template'; +const PageRestricingWidthTemplateSource = require('!!raw-loader!./page_restricting_width_template'); +const PageRestricingWidthTemplateHtml = renderToHtml( + PageRestricingWidthTemplate +); + +import PageCenteredBody from './page_centered_body'; +const PageCenteredBodySource = require('!!raw-loader!./page_centered_body'); +import PageCenteredBodyTemplate from './page_centered_body_template'; +const PageCenteredBodyTemplateSource = require('!!raw-loader!./page_centered_body_template'); +const PageCenteredBodyTemplateHtml = renderToHtml(PageCenteredBodyTemplate); -import PageHeader from './page_header'; -const pageHeaderSource = require('!!raw-loader!./page_header'); -const pageHeaderHtml = renderToHtml(PageHeader); +import PageCenteredContent from './page_centered_content'; +const PageCenteredContentSource = require('!!raw-loader!./page_centered_content'); +import PageCenteredContentTemplate from './page_centered_content_template'; +const PageCenteredContentTemplateSource = require('!!raw-loader!./page_centered_content_template'); +const PageCenteredContentTemplateHtml = renderToHtml( + PageCenteredContentTemplate +); -import PageHeaderTabs from './page_header_tabs'; -const pageHeaderTabsSource = require('!!raw-loader!./page_header_tabs'); -const pageHeaderTabsHtml = renderToHtml(PageHeaderTabs); +import PageSimple from './page_simple'; +const PageSimpleSource = require('!!raw-loader!./page_simple'); +import PageSimpleTemplate from './page_simple_template'; +const PageSimpleTemplateSource = require('!!raw-loader!./page_simple_template'); +const PageSimpleTemplateHtml = renderToHtml(PageSimpleTemplate); -import PageHeaderCustom from './page_header_custom'; -const pageHeaderCustomSource = require('!!raw-loader!./page_header_custom'); -const pageHeaderCustomHtml = renderToHtml(PageHeaderCustom); +import PageSimpleCenteredBody from './page_simple_content_body'; +const PageSimpleCenteredBodySource = require('!!raw-loader!./page_simple_content_body'); +import PageSimpleCenteredBodyTemplate from './page_simple_content_body_template'; +const PageSimpleCenteredBodyTemplateSource = require('!!raw-loader!./page_simple_content_body_template'); +const PageSimpleCenteredBodyTemplateHtml = renderToHtml( + PageSimpleCenteredBodyTemplate +); -import PageContentOnly from './page_content_only'; -const pageContentOnlySource = require('!!raw-loader!./page_content_only'); -const pageContentOnlyHtml = renderToHtml(Page); +import PageSimpleEmptyContent from './page_simple_empty_content'; +const PageSimpleEmptyContentSource = require('!!raw-loader!./page_simple_empty_content'); +import PageSimpleEmptyContentTemplate from './page_simple_empty_content_template'; +const PageSimpleEmptyContentTemplateSource = require('!!raw-loader!./page_simple_empty_content_template'); +const PageSimpleEmptyContentTemplateHtml = renderToHtml( + PageSimpleEmptyContentTemplate +); -import PageContentCenter from './page_content_center'; -const pageContentCenterSource = require('!!raw-loader!./page_content_center'); -const pageContentCenterHtml = renderToHtml(Page); +import PageCustomContent from './page_custom_content'; +const PageCustomContentSource = require('!!raw-loader!./page_custom_content'); +import PageCustomContentTemplate from './page_custom_content_template'; +const PageCustomContentTemplateSource = require('!!raw-loader!./page_custom_content_template'); +const PageCustomContentTemplateHtml = renderToHtml(PageCustomContentTemplate); -import PageContentCenterWithSideBar from './page_content_center_with_side_bar'; -const PageContentCenterWithSideBarSource = require('!!raw-loader!./page_content_center_with_side_bar'); -const PageContentCenterWithSideBarHtml = renderToHtml(Page); +import PageLegacy from './page'; +const PageLegacySource = require('!!raw-loader!./page'); +const PageLegacyHtml = renderToHtml(PageLegacy); export const PageExample = { - playground: Playground, title: 'Page', + playground: [pageTemplateConfig], intro: ( - -

- Page layouts are modular and have the ability to add or remove - components as needed for the design. These examples are colored for - illustrative purposes only. -

+ <> + +

+ Page layouts are modular and fit together in a precise manner, though + certain parts can also be added or removed as needed. EUI provides + both the indivdual page components and an over-arching template for + easily creating some pre-defined layouts. +

+
-
+ +

+ You'll find the code for each in their own tab and if you go to + full screen, you can see how they would behave in a typical + applicaiton layout. +

+
+ ), sections: [ { - title: 'A full page layout with everything on', + title: 'A full page with everything', source: [ { type: GuideSectionTypes.JS, - code: pageSource, + code: PageTemplateSource, + displayName: 'Template JS', + }, + { + type: GuideSectionTypes.JS, + code: pageNewSource, + displayName: 'Components JS', }, { type: GuideSectionTypes.HTML, - code: pageHtml, + code: PageTemplateHtml, }, ], text: ( @@ -87,265 +135,480 @@ export const PageExample = {

  • - EuiPage provides the overall wrapper. + EuiPage and EuiPageBody provide + the overall wrapper with a column flex display.
  • - EuiPageHeader provides a title, description, - section for actions and possible tabs. + EuiPageSideBar provides a way to add side + navigation that can be made sticky to scroll + independent of the page content. See{' '} + + EuiSideNav + {' '} + for contents.
  • - EuiPageContent and its family of related - components provide the main content container. + + EuiPageHeader + {' '} + provides a title, description, section for actions and possible + tabs.
  • - EuiPageSideBar provides a way to add side - navigation. + EuiPageContent provides the main content + container and extends{' '} + + EuiPanel + + . +
  • +
  • + EuiPageContentBody wraps the content that comes + after the page header.

- By default, the entire page will always be 100% of the window's - width; to max out the typical width and center the page, set the{' '} - restrictWidth prop to true. - You can also pass an integer to this property to max out the width - at a custom pixel value or a string with a custom measurement. + Or you can use the provided EuiPageTemplate, which + is simply a shortcut for creating the different types of page layout + patterns described in these docs. It is somewhat opinionated, but + still has the ability to customize most of the inner components with + props like pageSideBarProps and{' '} + pageContentProps.

), props: { + EuiPageTemplate, EuiPage, EuiPageBody, + EuiPageSideBar, + EuiPageHeader, EuiPageContent, EuiPageContentBody, - EuiPageContentHeader, - EuiPageContentHeaderSection, - EuiPageHeader, - EuiPageHeaderSection, - EuiPageSideBar, }, demo: ( -
- -
+ + {(Button, Content, SideNav, showTemplate) => + showTemplate ? ( + } + content={} + sideNav={} + /> + ) : ( + } + content={} + sideNav={} + /> + ) + } + ), }, { - title: 'A simple page layout with a title', + title: 'Restricting page width', source: [ { type: GuideSectionTypes.JS, - code: pageSimpleSource, + code: PageRestricingWidthTemplateSource, + displayName: 'Template JS', + }, + { + type: GuideSectionTypes.JS, + code: PageRestricingWidthSource, + displayName: 'Components JS', }, { type: GuideSectionTypes.HTML, - code: pageSimpleHtml, + code: PageRestricingWidthTemplateHtml, }, ], text: ( -

- Most pages don’t have sidebars. A lot of our pages don’t - have extra abilities next to the title. Simply exclude those - components and everything will still line up. -

+ <> +

+ Most content does not scale well to the full width of the window. + You can restrict this to a typical width and center the page by + setting the restrictWidth prop to{' '} + true on EuiPageHeader and{' '} + EuiPageContent. You can also pass an integer to + this property to max out the width at a custom pixel value or a + string with a custom measurement. +

+ + The EuiPageTemplate allows setting this + property at the top level and defaults to{' '} + true. + + } + /> + ), demo: ( -
- -
+ + {(Button, Content, SideNav, showTemplate) => + showTemplate ? ( + } + content={} + sideNav={} + /> + ) : ( + } + content={} + sideNav={} + /> + ) + } + ), }, { - title: 'A simple page layout with content only', + title: 'Centered body', source: [ { type: GuideSectionTypes.JS, - code: pageContentOnlySource, + code: PageCenteredBodyTemplateSource, + displayName: 'Template JS', + }, + { + type: GuideSectionTypes.JS, + code: PageCenteredBodySource, + displayName: 'Components JS', }, { type: GuideSectionTypes.HTML, - code: pageContentOnlyHtml, + code: PageCenteredBodyTemplateHtml, }, ], - text:

We can further simplify pages by only showing the content.

, + text: ( + <> +

+ When the content for the page is minimal or in an empty/pre-setup + state, the page content can be centered vertically and horizontally. + We recommend then using the{' '} + + EuiEmptyPrompt + {' '} + for the content. +

+ + This layout can be achieved in EuiPageTemplate{' '} + by setting {'template="centeredBody"'}. + + } + /> + + ), demo: ( -
- -
+ + {(Button, Content, SideNav, showTemplate) => + showTemplate ? ( + } + content={} + sideNav={} + /> + ) : ( + } + content={} + sideNav={} + /> + ) + } + ), + props: { EuiPageTemplate, EuiEmptyPrompt }, }, { - title: 'A simple page layout with content centered', + title: 'Centered content', source: [ { type: GuideSectionTypes.JS, - code: pageContentCenterSource, + code: PageCenteredContentTemplateSource, + displayName: 'Template JS', + }, + { + type: GuideSectionTypes.JS, + code: PageCenteredContentSource, + displayName: 'Components JS', }, { type: GuideSectionTypes.HTML, - code: pageContentCenterHtml, + code: PageCenteredContentTemplateHtml, }, ], text: ( -

- The page content can be optionally centered either vertically or - horizontally. This is useful for various empty states. -

+ <> +

+ Similar to the previous example, you can create a centered panel to + emphasize incompleteness even with a page header. For this setup, we + recommend using setting EuiPageContent to use the{' '} + subdued color as to not have nested shadows. +

+ + This layout can be achieved in EuiPageTemplate{' '} + by setting {'template="centeredContent"'}. + + } + /> + ), demo: ( -
- -
+ + {(Button, Content, SideNav, showTemplate) => + showTemplate ? ( + } + content={} + sideNav={} + /> + ) : ( + } + content={} + sideNav={} + /> + ) + } + + ), + }, + { + title: 'A simple page with tabs', + source: [ + { + type: GuideSectionTypes.JS, + code: PageSimpleTemplateSource, + displayName: 'Template JS', + }, + { + type: GuideSectionTypes.JS, + code: PageSimpleSource, + displayName: 'Components JS', + }, + { + type: GuideSectionTypes.HTML, + code: PageSimpleTemplateHtml, + }, + ], + text: ( + <> +

+ When leaving off the EuiPageSideBar, we recommend a + slightly different configuration by pulling the page hader out of + the EuiPageContent and removing the shadow from{' '} + EuiPageContent. +

+ + This layout will automatically be achieved through{' '} + EuiPageTemplate by leaving{' '} + pageSideBar as undefined. + + } + /> + + ), + demo: ( + + {(Button, Content, SideNav, showTemplate) => + showTemplate ? ( + } content={} /> + ) : ( + } content={} /> + ) + } + ), }, { - title: 'A simple page layout with content centered in a full layout', + title: 'Simple layout with centered body', source: [ { type: GuideSectionTypes.JS, - code: PageContentCenterWithSideBarSource, + code: PageSimpleCenteredBodyTemplateSource, + displayName: 'Template JS', + }, + { + type: GuideSectionTypes.JS, + code: PageSimpleCenteredBodySource, + displayName: 'Components JS', }, { type: GuideSectionTypes.HTML, - code: PageContentCenterWithSideBarHtml, + code: PageSimpleCenteredBodyTemplateHtml, }, ], text: (

- Centering the content can happen regardless of layout configuration. - In this example, we’re centering within a complex sidebar - layout. + Similar to the version with a side bar, when the content for the page + is minimal or in an empty/pre-setup state, the page content can be + centered vertically and horizontally. We recommend then using the{' '} + + EuiEmptyPrompt + {' '} + for the content.

), demo: ( -
- -
+ + {(Button, Content, SideNav, showTemplate) => + showTemplate ? ( + } + content={} + /> + ) : ( + } + content={} + /> + ) + } + ), }, { - title: 'The page header in detail', + title: 'Simple layout with centered content', source: [ { type: GuideSectionTypes.JS, - code: pageHeaderSource, + code: PageSimpleEmptyContentTemplateSource, + displayName: 'Template JS', + }, + { + type: GuideSectionTypes.JS, + code: PageSimpleEmptyContentSource, + displayName: 'Components JS', }, { type: GuideSectionTypes.HTML, - code: pageHeaderHtml, + code: PageSimpleEmptyContentTemplateHtml, }, ], text: ( - <> -

- EuiPageHeader provides props for opinionated, - consistent formatting of your header. Any combination of{' '} - pageTitle, description,{' '} - tabs, or any children will - adjust the layout as needed. -

-

- An additional prop rightSideItems allows for a - simple array of nodes which will layout in a - flexbox row. This is commonly used for adding multiple buttons, of - which, at least one should be primary (or{' '} - {'fill="true"'}). These items are - also displayed in reverse order so that the first - and primary array item will be displayed on the far right. -

-

- You can further adjust the display of these content types with an - optional iconType placed to the left of the - title, alignItems for adjusting the vertical - alignment of the two sides, and responsiveOrder{' '} - to determine which content side to display first on smaller screens. -

- +

+ Also similar to the previous examples, you can create a centered panel + to emphasis incompleteness even with a page header. For this setup, + You will need to use nested EuiPageContent components + in order for the centering to work. +

), demo: ( -
- -
+ + {(Button, Content, SideNav, showTemplate) => + showTemplate ? ( + } + content={} + /> + ) : ( + } + content={} + /> + ) + } + ), - props: { EuiPageHeader }, - snippet: `Button 1, - Button 2 - ]} -/>`, }, { - title: 'Tabs in the page header', + title: 'A simple page layout with custom content', source: [ { type: GuideSectionTypes.JS, - code: pageHeaderTabsSource, + code: PageCustomContentTemplateSource, + displayName: 'Template JS', + }, + { + type: GuideSectionTypes.JS, + code: PageCustomContentSource, + displayName: 'Components JS', }, { type: GuideSectionTypes.HTML, - code: pageHeaderTabsHtml, + code: PageCustomContentTemplateHtml, }, ], text: ( <>

- When using supplying tabs without a{' '} - pageTitle, EuiPageHeader will - promote those tabs as if they are the page title. This means that - any description, or children{' '} - will sit below the tabs. + You can replace the inner parts of EuiPageBody with + your own content, with or without a page header. This allows you to + create dashboard style layouts with lots of panels. It is not + recommended, however, to use this setup when you also have side bar.

+ + This layout can be achieved in EuiPageTemplate{' '} + by setting {'template="empty"'}. + + } + /> ), demo: ( -
- -
+ + {(Button, Content, SideNav, showTemplate) => + showTemplate ? ( + } /> + ) : ( + } /> + ) + } + ), - props: { EuiPageHeader }, - snippet: ``, }, { - title: 'Customizing the page header', + title: 'Legacy layout', source: [ { type: GuideSectionTypes.JS, - code: pageHeaderCustomSource, + code: PageLegacySource, }, { type: GuideSectionTypes.HTML, - code: pageHeaderCustomHtml, + code: PageLegacyHtml, }, ], text: ( - <> -

- The page header content props mainly are helpful props to push - content into established Elastic page layout patterns. They are - completely optional and by nature, inflexible. If you need a layout - that does not match these patterns you can simply pass in your own{' '} - children utilizing the{' '} - EuiPageHeaderSection components. -

- +

+ In previous versions of EUI, we emulated page layouts where the{' '} + EuiPageContent had margins all around created by + padding on EuiPage. This layout is still achievable + but not through EuiPageTemplate. You must use the{' '} + EuiPage components manually as seen in this example. +

), demo: ( -
- +
+
), - props: { EuiPageHeader }, + props: { + EuiPage, + EuiPageBody, + EuiPageSideBar, + EuiPageHeader, + EuiPageContent, + EuiPageContentBody, + }, }, ], }; diff --git a/src-docs/src/views/page/page_new.js b/src-docs/src/views/page/page_new.js new file mode 100644 index 00000000000..aded8c5833d --- /dev/null +++ b/src-docs/src/views/page/page_new.js @@ -0,0 +1,34 @@ +import React from 'react'; + +import { + EuiPage, + EuiPageContent, + EuiPageContentBody, + EuiPageHeader, + EuiPageSideBar, + EuiPageBody, +} from '../../../../src/components'; + +export default ({ button = <>, content, sideNav }) => ( + + {sideNav} + + + + + {content} + + + +); diff --git a/src-docs/src/views/page/page_restricting_width.js b/src-docs/src/views/page/page_restricting_width.js new file mode 100644 index 00000000000..88a65719d5f --- /dev/null +++ b/src-docs/src/views/page/page_restricting_width.js @@ -0,0 +1,39 @@ +import React from 'react'; + +import { + EuiPage, + EuiPageContent, + EuiPageContentBody, + EuiPageHeader, + EuiPageSideBar, + EuiPageBody, +} from '../../../../src/components'; + +export default ({ button = <>, content, sideNav }) => { + return ( + + {sideNav} + + + + + + + {content} + + + + + ); +}; diff --git a/src-docs/src/views/page/page_restricting_width_template.js b/src-docs/src/views/page/page_restricting_width_template.js new file mode 100644 index 00000000000..ba6b440dc2c --- /dev/null +++ b/src-docs/src/views/page/page_restricting_width_template.js @@ -0,0 +1,19 @@ +import React from 'react'; + +import { EuiPageTemplate } from '../../../../src/components'; + +export default ({ button = <>, content, sideNav }) => { + return ( + + {content} + + ); +}; diff --git a/src-docs/src/views/page/page_simple.js b/src-docs/src/views/page/page_simple.js index f7c7b237d98..547e5ca043f 100644 --- a/src-docs/src/views/page/page_simple.js +++ b/src-docs/src/views/page/page_simple.js @@ -2,28 +2,25 @@ import React from 'react'; import { EuiPage, - EuiPageBody, EuiPageContent, - EuiPageContentBody, - EuiPageContentHeader, - EuiPageContentHeaderSection, EuiPageHeader, - EuiTitle, + EuiPageBody, + EuiPageContentBody, } from '../../../../src/components'; -export default () => ( - - - - - - - -

Content title

-
-
-
- Content body +export default ({ button = <>, content }) => ( + + + + + + {content} + diff --git a/src-docs/src/views/page/page_simple_content_body.js b/src-docs/src/views/page/page_simple_content_body.js new file mode 100644 index 00000000000..9ce36859663 --- /dev/null +++ b/src-docs/src/views/page/page_simple_content_body.js @@ -0,0 +1,27 @@ +import React from 'react'; + +import { + EuiPage, + EuiPageContent, + EuiEmptyPrompt, + EuiPageBody, +} from '../../../../src/components'; + +export default ({ button = <>, content }) => { + return ( + + + + No spice} + body={content} + actions={button} + /> + + + + ); +}; diff --git a/src-docs/src/views/page/page_simple_content_body_template.js b/src-docs/src/views/page/page_simple_content_body_template.js new file mode 100644 index 00000000000..902a08838fc --- /dev/null +++ b/src-docs/src/views/page/page_simple_content_body_template.js @@ -0,0 +1,17 @@ +import React from 'react'; + +import { EuiPageTemplate, EuiEmptyPrompt } from '../../../../src/components'; + +export default ({ button = <>, content }) => { + return ( + + No spice} + body={content} + actions={button} + /> + + ); +}; diff --git a/src-docs/src/views/page/page_simple_empty_content.js b/src-docs/src/views/page/page_simple_empty_content.js new file mode 100644 index 00000000000..d9ef831acb9 --- /dev/null +++ b/src-docs/src/views/page/page_simple_empty_content.js @@ -0,0 +1,36 @@ +import React from 'react'; + +import { + EuiPage, + EuiPageContent, + EuiEmptyPrompt, + EuiPageHeader, + EuiPageBody, +} from '../../../../src/components'; + +export default ({ button = <>, content }) => ( + + + + + + No spice} body={content} /> + + + + +); diff --git a/src-docs/src/views/page/page_simple_empty_content_template.js b/src-docs/src/views/page/page_simple_empty_content_template.js new file mode 100644 index 00000000000..03a0bedfc7d --- /dev/null +++ b/src-docs/src/views/page/page_simple_empty_content_template.js @@ -0,0 +1,16 @@ +import React from 'react'; + +import { EuiPageTemplate, EuiEmptyPrompt } from '../../../../src/components'; + +export default ({ button = <>, content }) => ( + + No spice} body={content} /> + +); diff --git a/src-docs/src/views/page/page_simple_template.js b/src-docs/src/views/page/page_simple_template.js new file mode 100644 index 00000000000..98e8203381f --- /dev/null +++ b/src-docs/src/views/page/page_simple_template.js @@ -0,0 +1,13 @@ +import React from 'react'; + +import { EuiPageTemplate } from '../../../../src/components'; + +export default ({ button = <>, content }) => ( + + {content} + +); diff --git a/src-docs/src/views/page/page_template.js b/src-docs/src/views/page/page_template.js new file mode 100644 index 00000000000..5c10e9704e9 --- /dev/null +++ b/src-docs/src/views/page/page_template.js @@ -0,0 +1,16 @@ +import React from 'react'; + +import { EuiPageTemplate } from '../../../../src/components'; + +export default ({ button = <>, content, sideNav }) => ( + + {content} + +); diff --git a/src-docs/src/views/page/playground.js b/src-docs/src/views/page/playground.js index 6be4412b5f9..5328e069bd0 100644 --- a/src-docs/src/views/page/playground.js +++ b/src-docs/src/views/page/playground.js @@ -1,98 +1,63 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ - -import React from 'react'; import { PropTypes } from 'react-view'; import { + EuiPageTemplate, EuiPageHeader, EuiButton, - EuiTabs, - EuiImage, } from '../../../../src/components/'; import { propUtilityForPlayground, - iconValidator, - simulateFunction, generateCustomProps, - createOptionalEnum, } from '../../services/playground'; -const tabs = `[ - { - label: 'Tab 1', - isSelected: true, - }, - { - label: 'Tab 2', - }, -]`; - -const rightSideItems = `[ - Button 1, - Button 2, -]`; - -// TODO: Try later to find build a toggle that allows switching between different types of content to pass -// const rightSideItems = -// '[]'; - -export default () => { - const docgenInfo = Array.isArray(EuiPageHeader.__docgenInfo) - ? EuiPageHeader.__docgenInfo[0] - : EuiPageHeader.__docgenInfo; +export const pageTemplateConfig = () => { + const docgenInfo = Array.isArray(EuiPageTemplate.__docgenInfo) + ? EuiPageTemplate.__docgenInfo[0] + : EuiPageTemplate.__docgenInfo; const propsToUse = propUtilityForPlayground(docgenInfo.props); - propsToUse.iconType = iconValidator(propsToUse.iconType); - - propsToUse.pageTitle = { - ...propsToUse.pageTitle, - type: PropTypes.String, - value: 'Page title', - }; - - propsToUse.description = { - ...propsToUse.description, - value: 'Example of a description.', - type: PropTypes.String, - }; - - propsToUse.rightSideItems = simulateFunction({ - ...propsToUse.rightSideItems, - custom: { - value: rightSideItems, - }, - }); - - propsToUse.tabs = simulateFunction({ - ...propsToUse.tabs, - custom: { - value: tabs, - }, - }); + // TODO: Follow up on how to allow passing an object to a prop + // propsToUse.pageHeader = simulateFunction({ + // ...propsToUse.pageHeader, + // custom: { + // value: '{ pageTitle: "Page title" }', + // }, + // }); propsToUse.children = { ...propsToUse.children, + value: 'Children', type: PropTypes.ReactNode, hidden: false, }; - propsToUse.alignItems = createOptionalEnum(propsToUse.alignItems); + propsToUse.pageSideBar = { + ...propsToUse.pageSideBar, + value: 'Side bar', + type: PropTypes.String, + hidden: false, + }; + + propsToUse.restrictWidth = { + ...propsToUse.restrictWidth, + type: PropTypes.String, + }; return { config: { - componentName: 'EuiPageHeader', + componentName: 'EuiPageTemplate', props: propsToUse, scope: { + EuiPageTemplate, EuiPageHeader, EuiButton, - EuiTabs, - EuiImage, }, imports: { '@elastic/eui': { - named: ['EuiPageHeader', 'EuiButton', 'EuiTabs', 'EuiImage'], + named: ['EuiPageTemplate', 'EuiPageHeader', 'EuiButton'], }, }, - customProps: generateCustomProps(['rightSideItems', 'tabs']), + customProps: generateCustomProps(['pageHeader']), }, + playgroundClassName: 'guideDemo__highlightLayout--playground', }; }; diff --git a/src-docs/src/views/page/page_header.js b/src-docs/src/views/page_header/page_header.js similarity index 100% rename from src-docs/src/views/page/page_header.js rename to src-docs/src/views/page_header/page_header.js diff --git a/src-docs/src/views/page/page_header_custom.js b/src-docs/src/views/page_header/page_header_custom.js similarity index 100% rename from src-docs/src/views/page/page_header_custom.js rename to src-docs/src/views/page_header/page_header_custom.js diff --git a/src-docs/src/views/page_header/page_header_example.js b/src-docs/src/views/page_header/page_header_example.js new file mode 100644 index 00000000000..08ac4e858b9 --- /dev/null +++ b/src-docs/src/views/page_header/page_header_example.js @@ -0,0 +1,160 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; + +import { renderToHtml } from '../../services'; + +import { GuideSectionTypes } from '../../components'; + +import { + EuiCode, + EuiPageHeader, + EuiPageHeaderSection, +} from '../../../../src/components'; + +import { pageHeaderConfig } from './playground'; + +import PageHeader from './page_header'; +const pageHeaderSource = require('!!raw-loader!./page_header'); +const pageHeaderHtml = renderToHtml(PageHeader); + +import PageHeaderTabs from './page_header_tabs'; +const pageHeaderTabsSource = require('!!raw-loader!./page_header_tabs'); +const pageHeaderTabsHtml = renderToHtml(PageHeaderTabs); + +import PageHeaderCustom from './page_header_custom'; +import { EuiText } from '../../../../src/components/text'; +const pageHeaderCustomSource = require('!!raw-loader!./page_header_custom'); +const pageHeaderCustomHtml = renderToHtml(PageHeaderCustom); + +export const PageHeaderExample = { + title: 'Page header', + playground: [pageHeaderConfig], + intro: ( + +

+ While the EuiPageHeader component can be placed + anywhere within your page layout, we recommend using it within the{' '} + + EuiPageTemplate + {' '} + component by passing the configuration props as its{' '} + pageHeader. +

+
+ ), + sections: [ + { + source: [ + { + type: GuideSectionTypes.JS, + code: pageHeaderSource, + }, + { + type: GuideSectionTypes.HTML, + code: pageHeaderHtml, + }, + ], + text: ( + <> +

+ EuiPageHeader provides props for opinionated, + consistent formatting of your header. Any combination of{' '} + pageTitle, description,{' '} + tabs, or any children will + adjust the layout as needed. +

+

+ An additional prop rightSideItems allows for a + simple array of nodes which will layout in a + flexbox row. This is commonly used for adding multiple buttons, of + which, at least one should be primary (or{' '} + {'fill="true"'}). These items are + also displayed in reverse order so that the first + and primary array item will be displayed on the far right. +

+

+ You can further adjust the display of these content types with an + optional iconType placed to the left of the + title, alignItems for adjusting the vertical + alignment of the two sides, and responsiveOrder{' '} + to determine which content side to display first on smaller screens. +

+ + ), + demo: , + props: { EuiPageHeader }, + snippet: `Button 1, + Button 2 +]} +/>`, + }, + { + title: 'Tabs in the page header', + source: [ + { + type: GuideSectionTypes.JS, + code: pageHeaderTabsSource, + }, + { + type: GuideSectionTypes.HTML, + code: pageHeaderTabsHtml, + }, + ], + text: ( + <> +

+ When supplying tabs without a{' '} + pageTitle, EuiPageHeader will + promote those tabs as if they are the page title. This means that + any description or children{' '} + will sit below the tabs. +

+ + ), + demo: , + props: { EuiPageHeader }, + snippet: ``, + }, + { + title: 'Customizing the page header', + source: [ + { + type: GuideSectionTypes.JS, + code: pageHeaderCustomSource, + }, + { + type: GuideSectionTypes.HTML, + code: pageHeaderCustomHtml, + }, + ], + text: ( + <> +

+ The page header content props are helpful props to push content into + established Elastic page layout patterns. They are completely + optional and by design, inflexible. If you need a layout that does + not match these patterns you can pass in your own{' '} + children utilizing the{' '} + EuiPageHeaderSection components. +

+ + ), + demo: , + props: { EuiPageHeader, EuiPageHeaderSection }, + }, + ], +}; diff --git a/src-docs/src/views/page/page_header_tabs.js b/src-docs/src/views/page_header/page_header_tabs.js similarity index 100% rename from src-docs/src/views/page/page_header_tabs.js rename to src-docs/src/views/page_header/page_header_tabs.js diff --git a/src-docs/src/views/page_header/playground.js b/src-docs/src/views/page_header/playground.js new file mode 100644 index 00000000000..4b89147ad21 --- /dev/null +++ b/src-docs/src/views/page_header/playground.js @@ -0,0 +1,99 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import React from 'react'; +import { PropTypes } from 'react-view'; +import { + EuiPageTemplate, + EuiPageHeader, + EuiButton, + EuiTabs, + EuiImage, +} from '../../../../src/components/'; +import { + propUtilityForPlayground, + iconValidator, + simulateFunction, + generateCustomProps, + createOptionalEnum, +} from '../../services/playground'; + +const tabs = `[ + { + label: 'Tab 1', + isSelected: true, + }, + { + label: 'Tab 2', + }, +]`; + +const rightSideItems = `[ + Button 1, + Button 2, +]`; + +// TODO: Try later to build a toggle that allows switching between different types of content to pass +// const rightSideItems = +// '[]'; + +export const pageHeaderConfig = () => { + const docgenInfo = Array.isArray(EuiPageHeader.__docgenInfo) + ? EuiPageHeader.__docgenInfo[0] + : EuiPageHeader.__docgenInfo; + const propsToUse = propUtilityForPlayground(docgenInfo.props); + + propsToUse.iconType = iconValidator(propsToUse.iconType); + + propsToUse.pageTitle = { + ...propsToUse.pageTitle, + type: PropTypes.String, + value: 'Page title', + }; + + propsToUse.description = { + ...propsToUse.description, + value: 'Example of a description.', + type: PropTypes.String, + }; + + propsToUse.rightSideItems = simulateFunction({ + ...propsToUse.rightSideItems, + custom: { + value: rightSideItems, + }, + }); + + propsToUse.tabs = simulateFunction({ + ...propsToUse.tabs, + custom: { + value: tabs, + }, + }); + + propsToUse.children = { + ...propsToUse.children, + type: PropTypes.ReactNode, + hidden: false, + }; + + propsToUse.alignItems = createOptionalEnum(propsToUse.alignItems); + + return { + config: { + componentName: 'EuiPageHeader', + props: propsToUse, + scope: { + EuiPageHeader, + EuiButton, + EuiTabs, + EuiImage, + }, + imports: { + '@elastic/eui': { + named: ['EuiPageHeader', 'EuiButton', 'EuiTabs', 'EuiImage'], + }, + }, + customProps: generateCustomProps(['rightSideItems', 'tabs']), + }, + }; +}; diff --git a/src-docs/src/views/tour/fullscreen.js b/src-docs/src/views/tour/fullscreen.js index 07e46c8083f..395676cca61 100644 --- a/src-docs/src/views/tour/fullscreen.js +++ b/src-docs/src/views/tour/fullscreen.js @@ -7,19 +7,10 @@ import { EuiButtonEmpty, EuiColorPicker, EuiColorPickerSwatch, - EuiPage, - EuiPageBody, - EuiPageHeader, - EuiPageContent, - EuiPageContentHeader, - EuiPageContentHeaderSection, - EuiPageContentBody, + EuiPageTemplate, EuiSpacer, EuiStat, - EuiTab, - EuiTabs, EuiTextArea, - EuiTitle, EuiTourStep, useEuiTour, } from '../../../../src/components'; @@ -174,55 +165,39 @@ export default () => { return ( {(setIsFullScreen) => ( - - - - setIsFullScreen(false)} - iconType="exit" - aria-label="Exit fullscreen demo"> - Exit fullscreen demo - , - ]} - /> - - - - -

A new feature to demo

-
-
-
- - - {tabs.map((tab, index) => ( - onTabClick(tab.id)} - isSelected={tab.id === selectedTabId} - key={index}> - {tab.name} - - ))} - - {tabs.map((tab, index) => ( - - {tab.id === selectedTabId && ( -
- {tab.content} -
- )} -
- ))} -
-
-
-
-
+ setIsFullScreen(false)} + iconType="exit"> + Exit fullscreen demo + , + ], + tabs: tabs.map((tab, index) => { + return { + key: index, + label: tab.name, + id: tab.id, + onClick: () => onTabClick(tab.id), + isSelected: tab.id === selectedTabId, + }; + }), + }}> + {tabs.map((tab, index) => ( + + {tab.id === selectedTabId && ( +
+ {tab.content} +
+ )} +
+ ))} +
)}
); diff --git a/src/components/breadcrumbs/_breadcrumbs.scss b/src/components/breadcrumbs/_breadcrumbs.scss index f8a574f8478..d344b02d303 100644 --- a/src/components/breadcrumbs/_breadcrumbs.scss +++ b/src/components/breadcrumbs/_breadcrumbs.scss @@ -9,6 +9,7 @@ display: flex; align-items: center; flex-wrap: wrap; + min-width: 0; // Ensure it shrinks if the window is narrow } .euiBreadcrumb { diff --git a/src/components/header/_header.scss b/src/components/header/_header.scss index 328d51f40c6..1d183512f12 100644 --- a/src/components/header/_header.scss +++ b/src/components/header/_header.scss @@ -5,13 +5,14 @@ height: $euiHeaderHeight + 1; // Add one for the border position: relative; - z-index: $euiZHeader; // ensure the shadow shows above content + z-index: $euiZHeader - 1; // ensure the shadow shows above content display: flex; justify-content: space-between; background: $euiHeaderBackgroundColor; border-bottom: 1px solid $euiHeaderBorderColor; &--fixed { + z-index: $euiZHeader; position: fixed; top: 0; left: 0; diff --git a/src/components/header/_header_logo.scss b/src/components/header/_header_logo.scss index 365a7441387..d311c89a1da 100644 --- a/src/components/header/_header_logo.scss +++ b/src/components/header/_header_logo.scss @@ -13,10 +13,6 @@ vertical-align: middle; white-space: nowrap; - &:hover { - background: $euiColorLightestShade; - } - &:focus, &:hover { text-decoration: none; diff --git a/src/components/header/_mixins.scss b/src/components/header/_mixins.scss index b693f759d2b..8973e76b195 100644 --- a/src/components/header/_mixins.scss +++ b/src/components/header/_mixins.scss @@ -21,12 +21,8 @@ .euiHeaderLogo, .euiHeaderLink, .euiHeaderSectionItem__button { - &:hover { - background: transparentize(lightOrDarkTheme($euiColorDarkShade, $euiColorLightestShade), .5); - } - &:focus { - background: shade($euiColorPrimary, 50%); + background-color: transparentize(lightOrDarkTheme($euiColorDarkShade, $euiColorLightestShade), .5); } } diff --git a/src/components/header/header_links/__snapshots__/header_links.test.tsx.snap b/src/components/header/header_links/__snapshots__/header_links.test.tsx.snap index aa42a20fce1..dc6733c233d 100644 --- a/src/components/header/header_links/__snapshots__/header_links.test.tsx.snap +++ b/src/components/header/header_links/__snapshots__/header_links.test.tsx.snap @@ -80,12 +80,20 @@ exports[`EuiHeaderLinks popover props is rendered 1`] = ` >
diff --git a/src/components/header/header_links/header_links.tsx b/src/components/header/header_links/header_links.tsx index 6899c3fa391..d2cea8054f5 100644 --- a/src/components/header/header_links/header_links.tsx +++ b/src/components/header/header_links/header_links.tsx @@ -38,7 +38,9 @@ import { EuiBreakpointSize } from '../../../services/breakpoint'; import { EuiHideFor, EuiShowFor } from '../../responsive'; type EuiHeaderLinksGutterSize = 'xs' | 's' | 'm' | 'l'; -type EuiHeaderLinksPopoverButtonProps = EuiHeaderSectionItemButtonProps & { +type EuiHeaderLinksPopoverButtonProps = Partial< + EuiHeaderSectionItemButtonProps +> & { iconType?: IconType; }; @@ -88,7 +90,9 @@ export const EuiHeaderLinks: FunctionComponent = ({ const [mobileMenuIsOpen, setMobileMenuIsOpen] = useState(false); - const onMenuButtonClick: MouseEventHandler = (e) => { + const onMenuButtonClick: MouseEventHandler< + HTMLButtonElement & HTMLAnchorElement + > = (e) => { _onClick && _onClick(e); setMobileMenuIsOpen(!mobileMenuIsOpen); }; diff --git a/src/components/header/header_section/__snapshots__/header_section_item_button.test.tsx.snap b/src/components/header/header_section/__snapshots__/header_section_item_button.test.tsx.snap index a8f3b53c283..0212f4288e3 100644 --- a/src/components/header/header_section/__snapshots__/header_section_item_button.test.tsx.snap +++ b/src/components/header/header_section/__snapshots__/header_section_item_button.test.tsx.snap @@ -3,58 +3,114 @@ exports[`EuiHeaderSectionItemButton is rendered 1`] = ` +`; + +exports[`EuiHeaderSectionItemButton renders a link 1`] = ` + + + + + `; exports[`EuiHeaderSectionItemButton renders children 1`] = ` `; exports[`EuiHeaderSectionItemButton renders notification as a badge 1`] = ` `; exports[`EuiHeaderSectionItemButton renders notification as a dot 1`] = ` `; exports[`EuiHeaderSectionItemButton renders notification color 1`] = ` `; diff --git a/src/components/header/header_section/_header_section_item.scss b/src/components/header/header_section/_header_section_item.scss index b54be94d58d..83b0c0b3610 100644 --- a/src/components/header/header_section/_header_section_item.scss +++ b/src/components/header/header_section/_header_section_item.scss @@ -17,18 +17,8 @@ .euiHeaderSectionItem__button { position: relative; // For positioning the notification - height: $euiHeaderChildSize; min-width: $euiHeaderChildSize; - text-align: center; - font-size: 0; // aligns icons better vertically - - &:hover { - background: $euiColorLightestShade; - } - - &:focus { - background: $euiFocusBackgroundColor; - } + height: $euiHeaderChildSize; } .euiHeaderSectionItem--borderLeft { diff --git a/src/components/header/header_section/header_section_item_button.test.tsx b/src/components/header/header_section/header_section_item_button.test.tsx index 1f273633359..fa8b19771da 100644 --- a/src/components/header/header_section/header_section_item_button.test.tsx +++ b/src/components/header/header_section/header_section_item_button.test.tsx @@ -40,6 +40,12 @@ describe('EuiHeaderSectionItemButton', () => { expect(component).toMatchSnapshot(); }); + test('renders a link', () => { + const component = render(); + + expect(component).toMatchSnapshot(); + }); + describe('renders notification', () => { test('as a badge', () => { const component = render(); diff --git a/src/components/header/header_section/header_section_item_button.tsx b/src/components/header/header_section/header_section_item_button.tsx index 9fe1035f13c..bc45d8a7839 100644 --- a/src/components/header/header_section/header_section_item_button.tsx +++ b/src/components/header/header_section/header_section_item_button.tsx @@ -17,82 +17,65 @@ * under the License. */ -import React, { ButtonHTMLAttributes, PropsWithChildren } from 'react'; +import React, { FunctionComponent } from 'react'; import classNames from 'classnames'; -import { CommonProps } from '../../common'; import { EuiNotificationBadgeProps, EuiNotificationBadge, } from '../../badge/notification_badge/badge_notification'; import { EuiIcon } from '../../icon'; +import { EuiButtonEmpty, EuiButtonEmptyProps } from '../../button'; -export type EuiHeaderSectionItemButtonProps = CommonProps & - ButtonHTMLAttributes & { - /** - * Inserts the node into a EuiBadgeNotification and places it appropriately against the button. - * Or pass `true` to render a simple dot - */ - notification?: EuiNotificationBadgeProps['children'] | boolean; - /** - * Changes the color of the notification background - */ - notificationColor?: EuiNotificationBadgeProps['color']; - }; +export type EuiHeaderSectionItemButtonProps = EuiButtonEmptyProps & { + /** + * Inserts the node into a EuiBadgeNotification and places it appropriately against the button. + * Or pass `true` to render a simple dot + */ + notification?: EuiNotificationBadgeProps['children'] | boolean; + /** + * Changes the color of the notification background + */ + notificationColor?: EuiNotificationBadgeProps['color']; +}; -export type EuiHeaderSectionItemButtonRef = HTMLButtonElement; +export const EuiHeaderSectionItemButton: FunctionComponent = ({ + children, + className, + notification, + notificationColor = 'accent', + ...rest +}) => { + const classes = classNames('euiHeaderSectionItem__button', className); -export const EuiHeaderSectionItemButton = React.forwardRef< - EuiHeaderSectionItemButtonRef, - PropsWithChildren ->( - ( - { - onClick, - children, - className, - notification, - notificationColor = 'accent', - ...rest - }, - ref - ) => { - const classes = classNames('euiHeaderSectionItem__button', className); - - let notificationBadge; - if (notification) { - if (notification === true) { - notificationBadge = ( - - ); - } else { - notificationBadge = ( - - {notification} - - ); - } + let notificationBadge; + if (notification) { + if (notification === true) { + notificationBadge = ( + + ); + } else { + notificationBadge = ( + + {notification} + + ); } - - return ( - - ); } -); + + return ( + + {children} + {notificationBadge} + + ); +}; EuiHeaderSectionItemButton.displayName = 'EuiHeaderSectionItemButton'; diff --git a/src/components/index.js b/src/components/index.js index 878c79da7f5..3b60758646a 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -256,6 +256,9 @@ export { EuiPageHeaderContent, EuiPageHeaderSection, EuiPageSideBar, + EuiPageSideBarProps, + EuiPageTemplate, + EuiPageTemplateProps, } from './page'; export { EuiPagination, EuiPaginationButton } from './pagination'; diff --git a/src/components/index.scss b/src/components/index.scss index 123870f0685..bc423974f10 100644 --- a/src/components/index.scss +++ b/src/components/index.scss @@ -45,9 +45,9 @@ @import 'mark/index'; @import 'modal/index'; @import 'overlay_mask/index'; -@import 'page/index'; @import 'pagination/index'; @import 'panel/index'; +@import 'page/index'; // Page needs to come after Panel for cascade specificity @import 'popover/index'; @import 'portal/index'; @import 'progress/index'; diff --git a/src/components/page/__snapshots__/page.test.tsx.snap b/src/components/page/__snapshots__/page.test.tsx.snap index 8d0b7edb54a..5e16efeea29 100644 --- a/src/components/page/__snapshots__/page.test.tsx.snap +++ b/src/components/page/__snapshots__/page.test.tsx.snap @@ -1,35 +1,65 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`EuiPage direction can be row 1`] = ` +
+`; + +exports[`EuiPage grow can be false 1`] = ` +
+`; + exports[`EuiPage is rendered 1`] = `
`; +exports[`EuiPage paddingSize l is rendered 1`] = ` +
+`; + +exports[`EuiPage paddingSize m is rendered 1`] = ` +
+`; + +exports[`EuiPage paddingSize none is rendered 1`] = ` +
+`; + +exports[`EuiPage paddingSize s is rendered 1`] = ` +
+`; + exports[`EuiPage restrict width can be set to a custom number 1`] = `
`; -exports[`EuiPage restrict width can be set to a custom value and measurement 1`] = ` +exports[`EuiPage restrict width can be set to a custom value and does not override custom style 1`] = `
`; exports[`EuiPage restrict width can be set to a default 1`] = `
`; diff --git a/src/components/page/__snapshots__/page_template.test.tsx.snap b/src/components/page/__snapshots__/page_template.test.tsx.snap new file mode 100644 index 00000000000..1cb6400ba1b --- /dev/null +++ b/src/components/page/__snapshots__/page_template.test.tsx.snap @@ -0,0 +1,785 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EuiPageTemplate is rendered 1`] = ` +
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate restrict width can be set to a custom number 1`] = ` +
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate restrict width can be turned off 1`] = ` +
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template centeredBody is rendered 1`] = ` +
+
+
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template centeredContent is rendered 1`] = ` +
+
+
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template default is rendered 1`] = ` +
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template empty is rendered 1`] = ` +
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template is rendered with pageBodyProps 1`] = ` +
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template is rendered with pageBodyProps 2`] = ` +
+
+
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template is rendered with pageBodyProps 3`] = ` +
+
+
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template is rendered with pageBodyProps 4`] = ` +
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template is rendered with pageContentBodyProps 1`] = ` +
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template is rendered with pageContentBodyProps 2`] = ` +
+
+
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template is rendered with pageContentBodyProps 3`] = ` +
+
+
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template is rendered with pageContentBodyProps 4`] = ` +
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template is rendered with pageContentProps 1`] = ` +
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template is rendered with pageContentProps 2`] = ` +
+
+
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template is rendered with pageContentProps 3`] = ` +
+
+
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template is rendered with pageContentProps 4`] = ` +
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template is rendered with pageHeader 1`] = ` +
+
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template is rendered with pageHeader 2`] = ` +
+
+
+
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template is rendered with pageHeader 3`] = ` +
+
+
+
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template is rendered with pageHeader 4`] = ` +
+
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template paddingSize is rendered 1`] = ` +
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template paddingSize is rendered 2`] = ` +
+
+
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template paddingSize is rendered 3`] = ` +
+
+
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template paddingSize is rendered 4`] = ` +
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template with pageSideBar is rendered 1`] = ` +
+
+ Side Bar +
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template with pageSideBar is rendered 2`] = ` +
+
+ Side Bar +
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template with pageSideBar is rendered 3`] = ` +
+
+ Side Bar +
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template with pageSideBar is rendered 4`] = ` +
+
+ Side Bar +
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template with pageSideBar is rendered with pageSideBarProps 1`] = ` +
+
+ Side Bar +
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template with pageSideBar is rendered with pageSideBarProps 2`] = ` +
+
+ Side Bar +
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template with pageSideBar is rendered with pageSideBarProps 3`] = ` +
+
+ Side Bar +
+
+
+
+
+
+
+`; + +exports[`EuiPageTemplate template with pageSideBar is rendered with pageSideBarProps 4`] = ` +
+
+ Side Bar +
+
+
+
+
+
+
+`; diff --git a/src/components/page/_index.scss b/src/components/page/_index.scss index 80bf4062857..6dd2f474b29 100644 --- a/src/components/page/_index.scss +++ b/src/components/page/_index.scss @@ -1,3 +1,5 @@ +@import 'mixins'; + @import 'page'; @import 'page_body/index'; @import 'page_content/index'; diff --git a/src/components/page/_mixins.scss b/src/components/page/_mixins.scss new file mode 100644 index 00000000000..db5e250ab70 --- /dev/null +++ b/src/components/page/_mixins.scss @@ -0,0 +1,11 @@ +@mixin euiPageRestrictWidth { + &--restrictWidth-default, + &--restrictWidth-custom { + margin-left: auto; + margin-right: auto; + } + + &--restrictWidth-default { + max-width: $euiPageDefaultMaxWidth; + } +} diff --git a/src/components/page/_page.scss b/src/components/page/_page.scss index 39634976c12..81c46bf68ec 100644 --- a/src/components/page/_page.scss +++ b/src/components/page/_page.scss @@ -1,15 +1,16 @@ .euiPage { + @include euiPageRestrictWidth; display: flex; background-color: $euiPageBackgroundColor; + flex-shrink: 0; // Ensures Safari doesn't shrink height beyond contents + max-width: 100%; // Ensures Firefox doesn't expand width beyond bounds - &--restrictWidth-default, - &--restrictWidth-custom { - margin-left: auto; - margin-right: auto; + &--grow { + flex-grow: 1; } - &--restrictWidth-default { - max-width: $euiPageDefaultMaxWidth; + &--column { + flex-direction: column; } @include euiBreakpoint('xs', 's') { @@ -21,5 +22,20 @@ @each $modifier, $amount in $euiPanelPaddingModifiers { .euiPage--#{$modifier} { padding: $amount; + + .euiPageSideBar { + min-width: $euiPageSidebarMinWidth; + margin-right: $amount; + + // sass-lint:disable-block mixins-before-declarations + @include euiBreakpoint('xs', 's') { + margin-right: 0; + margin-bottom: $amount; + } + } + + .euiPageHeader { + margin-bottom: $amount; + } } } diff --git a/src/components/page/_restrict_width.ts b/src/components/page/_restrict_width.ts new file mode 100644 index 00000000000..c9dccf25db5 --- /dev/null +++ b/src/components/page/_restrict_width.ts @@ -0,0 +1,59 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * The `restrictedWidth` property is the same for all EuiPage components. + * This is file contains the type specific to that prop and a helper + * function for creating the corresponding classNames and style tags + * based on the consumer's configuration + * + * @param {restrictWidth} boolean | number | string The prop value + * @param {style} CSSProperties An object of style attributes if provided + * @returns {{widthClassName: string, newStyle: CSSProperties}} Returns an object with keys for the class name to append to the component's class and the updated style props + */ + +import { CSSProperties } from 'react'; + +export type _EuiPageRestrictWidth = { + /** + * Sets the max-width of the page, + * set to `true` to use the default size of `1000px (1200 for Amsterdam)`, + * set to `false` to not restrict the width, + * set to a number for a custom width in px, + * set to a string for a custom width in custom measurement. + */ + restrictWidth?: boolean | number | string; +}; + +export function setPropsForRestrictedPageWidth( + restrictWidth: _EuiPageRestrictWidth['restrictWidth'], + style?: CSSProperties +): { widthClassName?: string; newStyle?: CSSProperties } { + let widthClassName; + let newStyle; + + if (restrictWidth === true) { + widthClassName = 'restrictWidth-default'; + } else if (restrictWidth !== false) { + widthClassName = 'restrictWidth-custom'; + newStyle = { ...style, maxWidth: restrictWidth }; + } + + return { widthClassName, newStyle }; +} diff --git a/src/components/page/index.ts b/src/components/page/index.ts index 942c6887b01..594f1b44a0e 100644 --- a/src/components/page/index.ts +++ b/src/components/page/index.ts @@ -42,3 +42,5 @@ export { } from './page_header'; export { EuiPageSideBar, EuiPageSideBarProps } from './page_side_bar'; + +export { EuiPageTemplate, EuiPageTemplateProps } from './page_template'; diff --git a/src/components/page/page.test.tsx b/src/components/page/page.test.tsx index 40afc0532f8..46305034d84 100644 --- a/src/components/page/page.test.tsx +++ b/src/components/page/page.test.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { render } from 'enzyme'; import { requiredProps } from '../../test/required_props'; -import { EuiPage } from './page'; +import { EuiPage, SIZES } from './page'; describe('EuiPage', () => { test('is rendered', () => { @@ -30,26 +30,48 @@ describe('EuiPage', () => { expect(component).toMatchSnapshot(); }); + describe('paddingSize', () => { + SIZES.forEach((size) => { + it(`${size} is rendered`, () => { + const component = render(); + + expect(component).toMatchSnapshot(); + }); + }); + }); + + describe('grow', () => { + test('can be false', () => { + const component = render(); + + expect(component).toMatchSnapshot(); + }); + }); + + describe('direction', () => { + test('can be row', () => { + const component = render(); + + expect(component).toMatchSnapshot(); + }); + }); + describe('restrict width', () => { test('can be set to a default', () => { - const component = render( - - ); + const component = render(); expect(component).toMatchSnapshot(); }); test('can be set to a custom number', () => { - const component = render( - - ); + const component = render(); expect(component).toMatchSnapshot(); }); - test('can be set to a custom value and measurement', () => { + test('can be set to a custom value and does not override custom style', () => { const component = render( - + ); expect(component).toMatchSnapshot(); diff --git a/src/components/page/page.tsx b/src/components/page/page.tsx index 33deab797a8..f4f87bfa7df 100644 --- a/src/components/page/page.tsx +++ b/src/components/page/page.tsx @@ -20,6 +20,10 @@ import React, { FunctionComponent, HTMLAttributes } from 'react'; import classNames from 'classnames'; import { CommonProps, keysOf } from '../common'; +import { + _EuiPageRestrictWidth, + setPropsForRestrictedPageWidth, +} from './_restrict_width'; const paddingSizeToClassNameMap = { none: null, @@ -28,24 +32,33 @@ const paddingSizeToClassNameMap = { l: 'euiPage--paddingLarge', }; +const directionToClassNameMap = { + row: null, + column: 'euiPage--column', +}; + export const SIZES = keysOf(paddingSizeToClassNameMap); +export const DIRECTIONS = keysOf(directionToClassNameMap); export interface EuiPageProps extends CommonProps, - HTMLAttributes { - /** - * Sets the max-width of the page, - * set to `true` to use the default size of `1000px (1200 for Amsterdam)`, - * set to `false` to not restrict the width, - * set to a number for a custom width in px, - * set to a string for a custom width in custom measurement. - */ - restrictWidth?: boolean | number | string; + HTMLAttributes, + _EuiPageRestrictWidth { /** * Adjust the padding. - * When using this setting it's best to be consistent throughout all similar usages. + * When using this setting it's best to be consistent throughout all similar usages */ paddingSize?: typeof SIZES[number]; + /** + * Adds `flex-grow: 1` to the whole page for stretching to fit vertically. + * Must be wrapped inside a flexbox, preferrably with `min-height: 100vh` + */ + grow?: boolean; + /** + * Changes the `flex-direction` property. + * Flip to `column` when not including a sidebar. + */ + direction?: 'row' | 'column'; } export const EuiPage: FunctionComponent = ({ @@ -54,22 +67,23 @@ export const EuiPage: FunctionComponent = ({ style, className, paddingSize = 'm', + grow = true, + direction = 'row', ...rest }) => { - let widthClassname; - let newStyle; - - if (restrictWidth === true) { - widthClassname = 'euiPage--restrictWidth-default'; - } else if (restrictWidth !== false) { - widthClassname = 'euiPage--restrictWidth-custom'; - newStyle = { ...style, maxWidth: restrictWidth }; - } + const { widthClassName, newStyle } = setPropsForRestrictedPageWidth( + restrictWidth, + style + ); const classes = classNames( 'euiPage', - widthClassname, paddingSizeToClassNameMap[paddingSize], + directionToClassNameMap[direction], + { + 'euiPage--grow': grow, + [`euiPage--${widthClassName}`]: widthClassName, + }, className ); diff --git a/src/components/page/page_body/__snapshots__/page_body.test.tsx.snap b/src/components/page/page_body/__snapshots__/page_body.test.tsx.snap index f09b9799df8..85c26daf68a 100644 --- a/src/components/page/page_body/__snapshots__/page_body.test.tsx.snap +++ b/src/components/page/page_body/__snapshots__/page_body.test.tsx.snap @@ -3,15 +3,27 @@ exports[`EuiPageBody is rendered 1`] = `
`; +exports[`EuiPageBody panelled also accepts panelProps 1`] = ` +
+`; + +exports[`EuiPageBody panelled can be set to true 1`] = ` +
+`; + exports[`EuiPageBody restrict width can be set to a custom number 1`] = `
@@ -20,7 +32,7 @@ exports[`EuiPageBody restrict width can be set to a custom number 1`] = ` exports[`EuiPageBody restrict width can be set to a custom value and measurement 1`] = `
@@ -29,7 +41,7 @@ exports[`EuiPageBody restrict width can be set to a custom value and measurement exports[`EuiPageBody restrict width can be set to a default 1`] = `
`; diff --git a/src/components/page/page_body/_page_body.scss b/src/components/page/page_body/_page_body.scss index ccc2c84d399..32814540500 100644 --- a/src/components/page/page_body/_page_body.scss +++ b/src/components/page/page_body/_page_body.scss @@ -1,19 +1,40 @@ .euiPageBody { + @include euiPageRestrictWidth; display: flex; flex-direction: column; align-items: stretch; flex: 1 1 100%; // Make sure that inner flex layouts don't get larger than this container max-width: 100%; + min-width: 0; - &--restrictWidth-default, - &--restrictWidth-custom { - margin-left: auto; - margin-right: auto; + // Assumes that in the default theme, the borders are touching the edge of the EuiPage so remove them. + &.euiPageBody--borderRadiusNone { // Nested for specificity + border-top-width: 0; + border-right-width: 0; + border-bottom-width: 0; } +} + +// Uses the same values as EuiPanel +@each $modifier, $amount in $euiPanelPaddingModifiers { + .euiPageBody--#{$modifier} { + padding: $amount; + + & > .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 { + border-bottom: $euiBorderThin; - &--restrictWidth-default { - max-width: $euiPageDefaultMaxWidth; + &:not(.euiPageHeader--tabsAtBottom) { + padding-bottom: $amount; + } + } } } diff --git a/src/components/page/page_body/page_body.test.tsx b/src/components/page/page_body/page_body.test.tsx index 997986a18b0..cebe69b5afe 100644 --- a/src/components/page/page_body/page_body.test.tsx +++ b/src/components/page/page_body/page_body.test.tsx @@ -30,6 +30,22 @@ describe('EuiPageBody', () => { expect(component).toMatchSnapshot(); }); + describe('panelled', () => { + test('can be set to true', () => { + const component = render(); + + expect(component).toMatchSnapshot(); + }); + + test('also accepts panelProps', () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + }); + describe('restrict width', () => { test('can be set to a default', () => { const component = render( diff --git a/src/components/page/page_body/page_body.tsx b/src/components/page/page_body/page_body.tsx index b2fcf443d96..34c657d9326 100644 --- a/src/components/page/page_body/page_body.tsx +++ b/src/components/page/page_body/page_body.tsx @@ -19,24 +19,43 @@ import React, { PropsWithChildren, ComponentType, ComponentProps } from 'react'; import classNames from 'classnames'; -import { CommonProps } from '../../common'; +import { CommonProps, keysOf } from '../../common'; +import { + _EuiPageRestrictWidth, + setPropsForRestrictedPageWidth, +} from '../_restrict_width'; +import { EuiPanel, EuiPanelProps } from '../../panel'; + +const paddingSizeToClassNameMap = { + none: null, + s: 'euiPageBody--paddingSmall', + m: 'euiPageBody--paddingMedium', + l: 'euiPageBody--paddingLarge', +}; + +export const PADDING_SIZES = keysOf(paddingSizeToClassNameMap); type ComponentTypes = keyof JSX.IntrinsicElements | ComponentType; export type EuiPageBodyProps = CommonProps & - ComponentProps & { - /** - * Sets the max-width of the page, - * set to `true` to use the default size, - * set to `false` to not restrict the width, - * set to a number for a custom width in px, - * set to a string for a custom width in custom measurement. - */ - restrictWidth?: boolean | number | string; + ComponentProps & + _EuiPageRestrictWidth & { /** * Sets the HTML element for `EuiPageBody`. */ component?: T; + /** + * Uses an EuiPanel as the main component instead of a plain div + */ + panelled?: boolean; + /** + * Extends any extra EuiPanel props if `panelled=true` + */ + panelProps?: Omit; + /** + * Adjusts the padding + */ + paddingSize?: typeof PADDING_SIZES[number]; }; export const EuiPageBody = ({ @@ -45,23 +64,45 @@ export const EuiPageBody = ({ style, className, component: Component = 'main' as T, + panelled, + panelProps, + paddingSize, + borderRadius = 'none', ...rest }: PropsWithChildren>) => { - let widthClassname; - let newStyle; + const { widthClassName, newStyle } = setPropsForRestrictedPageWidth( + restrictWidth, + style + ); - if (restrictWidth === true) { - widthClassname = 'euiPageBody--restrictWidth-default'; - } else if (restrictWidth !== false) { - widthClassname = 'euiPageBody--restrictWidth-custom'; - const value = - typeof restrictWidth === 'number' ? `${restrictWidth}px` : restrictWidth; - newStyle = { ...style, maxWidth: value }; - } + const nonBreakingDefaultPadding = panelled ? 'l' : 'none'; + paddingSize = paddingSize || nonBreakingDefaultPadding; - const classes = classNames('euiPageBody', widthClassname, className); + const borderRadiusClass = + borderRadius === 'none' ? 'euiPageBody--borderRadiusNone' : ''; - return ( + const classes = classNames( + 'euiPageBody', + borderRadiusClass, + // This may duplicate the padding styles from EuiPanel, but allows for some nested configurations in the CSS + paddingSizeToClassNameMap[paddingSize as typeof PADDING_SIZES[number]], + { + [`euiPageBody--${widthClassName}`]: widthClassName, + }, + className + ); + + return panelled ? ( + + {children} + + ) : ( {children} diff --git a/src/components/page/page_content/__snapshots__/page_content_body.test.tsx.snap b/src/components/page/page_content/__snapshots__/page_content_body.test.tsx.snap index eec1b04a98f..f4c765bc5fe 100644 --- a/src/components/page/page_content/__snapshots__/page_content_body.test.tsx.snap +++ b/src/components/page/page_content/__snapshots__/page_content_body.test.tsx.snap @@ -7,3 +7,47 @@ exports[`EuiPageContentBody is rendered 1`] = ` data-test-subj="test subject string" /> `; + +exports[`EuiPageContentBody paddingSize l is rendered 1`] = ` +
+`; + +exports[`EuiPageContentBody paddingSize m is rendered 1`] = ` +
+`; + +exports[`EuiPageContentBody paddingSize none is rendered 1`] = ` +
+`; + +exports[`EuiPageContentBody paddingSize s is rendered 1`] = ` +
+`; + +exports[`EuiPageContentBody restrict width can be set to a custom number 1`] = ` +
+`; + +exports[`EuiPageContentBody restrict width can be set to a custom value and does not override custom style 1`] = ` +
+`; + +exports[`EuiPageContentBody restrict width can be set to a default 1`] = ` +
+`; diff --git a/src/components/page/page_content/_page_content.scss b/src/components/page/page_content/_page_content.scss index e06f386de99..e9302bf3f4e 100644 --- a/src/components/page/page_content/_page_content.scss +++ b/src/components/page/page_content/_page_content.scss @@ -1,5 +1,13 @@ .euiPageContent { width: 100%; + min-width: 0; // Make sure that inner flex layouts don't get larger than this container + + // Assumes that in the default theme, the borders are touching the edge of the EuiPage so remove them. + &.euiPageContent--borderRadiusNone { // Nested for specificity + border-left-width: 0; + border-right-width: 0; + border-bottom-width: 0; + } &.euiPageContent--verticalCenter { align-self: center; @@ -15,16 +23,4 @@ margin-right: auto; flex-grow: 0; // Offsets the properties of .euiPanel within flexboxes } - - /** TEMPORARILY REMOVING - // At small screens, the content extends edge to edge, so remove the side borders and shadow - @include euiBreakpoint('xs', 's') { - .euiPanel:not(.euiPageContent--horizontalCenter) { // Override panel styles without the need for !important - // border-radius: 0; - // border-left: none; - // border-right: none; - // box-shadow: none; - } - } - **/ } diff --git a/src/components/page/page_content/_page_content_body.scss b/src/components/page/page_content/_page_content_body.scss index e69de29bb2d..77e9dbd01a6 100644 --- a/src/components/page/page_content/_page_content_body.scss +++ b/src/components/page/page_content/_page_content_body.scss @@ -0,0 +1,11 @@ +.euiPageContentBody { + @include euiPageRestrictWidth; +} + +// Uses the same values as EuiPanel +@each $modifier, $amount in $euiPanelPaddingModifiers { + .euiPageContentBody--#{$modifier} { + padding: $amount; + } +} + diff --git a/src/components/page/page_content/page_content.tsx b/src/components/page/page_content/page_content.tsx index 9169f6fc65b..8650547b8fa 100644 --- a/src/components/page/page_content/page_content.tsx +++ b/src/components/page/page_content/page_content.tsx @@ -53,12 +53,17 @@ export const EuiPageContent: FunctionComponent = ({ horizontalPosition, panelPaddingSize, paddingSize = 'l', + borderRadius, children, className, ...rest }) => { + const borderRadiusClass = + borderRadius === 'none' ? 'euiPageContent--borderRadiusNone' : ''; + const classes = classNames( 'euiPageContent', + borderRadiusClass, verticalPosition ? verticalPositionToClassNameMap[verticalPosition] : null, horizontalPosition ? horizontalPositionToClassNameMap[horizontalPosition] @@ -70,6 +75,7 @@ export const EuiPageContent: FunctionComponent = ({ {children} diff --git a/src/components/page/page_content/page_content_body.test.tsx b/src/components/page/page_content/page_content_body.test.tsx index 4db006bed64..e8348822e29 100644 --- a/src/components/page/page_content/page_content_body.test.tsx +++ b/src/components/page/page_content/page_content_body.test.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { render } from 'enzyme'; import { requiredProps } from '../../../test/required_props'; -import { EuiPageContentBody } from './page_content_body'; +import { EuiPageContentBody, PADDING_SIZES } from './page_content_body'; describe('EuiPageContentBody', () => { test('is rendered', () => { @@ -29,4 +29,36 @@ describe('EuiPageContentBody', () => { expect(component).toMatchSnapshot(); }); + + describe('paddingSize', () => { + PADDING_SIZES.forEach((size) => { + it(`${size} is rendered`, () => { + const component = render(); + + expect(component).toMatchSnapshot(); + }); + }); + }); + + describe('restrict width', () => { + test('can be set to a default', () => { + const component = render(); + + expect(component).toMatchSnapshot(); + }); + + test('can be set to a custom number', () => { + const component = render(); + + expect(component).toMatchSnapshot(); + }); + + test('can be set to a custom value and does not override custom style', () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + }); }); diff --git a/src/components/page/page_content/page_content_body.tsx b/src/components/page/page_content/page_content_body.tsx index f572c52cf43..a802e569cfc 100644 --- a/src/components/page/page_content/page_content_body.tsx +++ b/src/components/page/page_content/page_content_body.tsx @@ -19,21 +19,56 @@ import React, { FunctionComponent, HTMLAttributes } from 'react'; import classNames from 'classnames'; -import { CommonProps } from '../../common'; +import { CommonProps, keysOf } from '../../common'; +import { + _EuiPageRestrictWidth, + setPropsForRestrictedPageWidth, +} from '../_restrict_width'; + +const paddingSizeToClassNameMap = { + none: null, + s: 'euiPage--paddingSmall', + m: 'euiPage--paddingMedium', + l: 'euiPage--paddingLarge', +}; + +export const PADDING_SIZES = keysOf(paddingSizeToClassNameMap); export interface EuiPageContentBodyProps extends CommonProps, - HTMLAttributes {} + HTMLAttributes, + _EuiPageRestrictWidth { + /** + * Adjust the padding. + * When using this setting it's best to be consistent throughout all similar usages + */ + paddingSize?: typeof PADDING_SIZES[number]; +} export const EuiPageContentBody: FunctionComponent = ({ children, + restrictWidth = false, + paddingSize = 'none', + style, className, ...rest }) => { - const classes = classNames('euiPageContentBody', className); + const { widthClassName, newStyle } = setPropsForRestrictedPageWidth( + restrictWidth, + style + ); + + const classes = classNames( + 'euiPageContentBody', + paddingSizeToClassNameMap[paddingSize], + { + [`euiPage--${widthClassName}`]: widthClassName, + }, + className + ); return ( -
+
{children}
); diff --git a/src/components/page/page_header/__snapshots__/page_header.test.tsx.snap b/src/components/page/page_header/__snapshots__/page_header.test.tsx.snap index a30fad4cb45..702a47b5ef6 100644 --- a/src/components/page/page_header/__snapshots__/page_header.test.tsx.snap +++ b/src/components/page/page_header/__snapshots__/page_header.test.tsx.snap @@ -268,7 +268,7 @@ exports[`EuiPageHeader props page content props are passed down is rendered 1`] />
@@ -316,13 +316,13 @@ exports[`EuiPageHeader props responsive is rendered as reverse 1`] = ` exports[`EuiPageHeader props restrictWidth is rendered as custom 1`] = `
`; exports[`EuiPageHeader props restrictWidth is rendered as true 1`] = `
`; diff --git a/src/components/page/page_header/__snapshots__/page_header_content.test.tsx.snap b/src/components/page/page_header/__snapshots__/page_header_content.test.tsx.snap index 0b4ff499d20..05eaefebdcb 100644 --- a/src/components/page/page_header/__snapshots__/page_header_content.test.tsx.snap +++ b/src/components/page/page_header/__snapshots__/page_header_content.test.tsx.snap @@ -280,7 +280,7 @@ exports[`EuiPageHeaderContent props children is rendered even if content props a class="euiSpacer euiSpacer--l" />