diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a7cb401785..208f453bb15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ - Added `EuiOverlayMask` directly to `EuiModal` ([#4480](https://github.com/elastic/eui/pull/4480)) - Added `paddingSize` prop to `EuiFlyout` ([#4448](https://github.com/elastic/eui/pull/4448)) - Added `size='l'` prop to `EuiTabs` ([#4501](https://github.com/elastic/eui/pull/4501)) +- Added content-specific props (`pageTitle`, `description`, `tabs`, `rightSideItems`) to `EuiPageHeader` by creating a new `EuiPageHeaderContent` component ([#4451](https://github.com/elastic/eui/pull/4451)) +- Added `isActive` parameter to the `useIsWithinBreakpoints` hook ([#4451](https://github.com/elastic/eui/pull/4451)) **Bug fixes** @@ -12,6 +14,11 @@ - Fixed `EuiCodeBlock` focus-state if content overflows [#4463](https://github.com/elastic/eui/pull/4463) - Fixed issues in `EuiDataGrid` around unnecessary scroll bars and container heights not updating ([#4468](https://github.com/elastic/eui/pull/4468)) +**Theme: Amsterdam** + +- Increased `EuiPage`'s default `restrictWidth` size to `1200px` (extra large breakpoint) ([#4451](https://github.com/elastic/eui/pull/4451)) +- Reduced size of `euiBottomShadowSmall` by one layer ([#4451](https://github.com/elastic/eui/pull/4451)) + ## [`31.5.0`](https://github.com/elastic/eui/tree/v31.5.0) - Added `isLoading` prop and added `EuiOverlayMask` directly to `EuiConfirmModal` ([#4421](https://github.com/elastic/eui/pull/4421)) diff --git a/src-docs/src/components/guide_components.scss b/src-docs/src/components/guide_components.scss index 5047858a5e6..73cbebe954d 100644 --- a/src-docs/src/components/guide_components.scss +++ b/src-docs/src/components/guide_components.scss @@ -96,7 +96,7 @@ $guideZLevelHighest: $euiZLevel9 + 1000; min-height: 100vh; background-color: $euiColorEmptyShade; border-left: $euiBorderThin; - max-width: 1000px; + max-width: $euiPageDefaultMaxWidth; margin-left: 240px; } @@ -105,11 +105,16 @@ $guideZLevelHighest: $euiZLevel9 + 1000; min-height: 460px; } - div { + > div, + > div > div { background: transparentize($euiColorPrimary, .9); } } +.guideDemo__highlightLayout--single { + background: transparentize($euiColorPrimary, .9); +} + .guideDemo__highlightSpacer { .euiSpacer { background: transparentize($euiColorPrimary, .9); diff --git a/src-docs/src/services/playground/_playground_compiler.scss b/src-docs/src/services/playground/_playground_compiler.scss index 79c32707828..0771394014b 100644 --- a/src-docs/src/services/playground/_playground_compiler.scss +++ b/src-docs/src/services/playground/_playground_compiler.scss @@ -11,4 +11,4 @@ @if (lightness($euiTextColor) < 50) { background: $euiColorDarkestShade; } -} \ No newline at end of file +} diff --git a/src-docs/src/services/playground/createOptionalEnum.js b/src-docs/src/services/playground/createOptionalEnum.js index ab4a9d91032..64a6db09b56 100644 --- a/src-docs/src/services/playground/createOptionalEnum.js +++ b/src-docs/src/services/playground/createOptionalEnum.js @@ -2,7 +2,7 @@ export const createOptionalEnum = (prop = { options: {} }) => { const newProp = { ...prop, options: { - none: '-- No value selected --', + none: '', ...prop.options, }, defaultValue: 'none', diff --git a/src-docs/src/services/playground/iconValidator.js b/src-docs/src/services/playground/iconValidator.js index 336f9180dfd..3bfd7b30d66 100644 --- a/src-docs/src/services/playground/iconValidator.js +++ b/src-docs/src/services/playground/iconValidator.js @@ -1,8 +1,9 @@ import { iconTypes } from '../../views/icon/icons'; +import { iconTypes as logoTypes } from '../../views/icon/logos'; import { mapOptions } from './mapOptions'; import { PropTypes } from 'react-view'; -const iconOptions = mapOptions(iconTypes); +const iconOptions = mapOptions(iconTypes.concat(logoTypes)); export const iconValidator = (prop = { custom: {} }) => { const newProp = { diff --git a/src-docs/src/services/playground/index.js b/src-docs/src/services/playground/index.js index a426fce24f6..72420fda6b0 100644 --- a/src-docs/src/services/playground/index.js +++ b/src-docs/src/services/playground/index.js @@ -6,3 +6,4 @@ export { iconValidator } from './iconValidator'; export { createOptionalEnum } from './createOptionalEnum'; export { dummyFunction } from './dummyFunction'; export { simulateFunction } from './simulateFunction'; +export { generateAst, generateCustomProps } from './utils'; diff --git a/src-docs/src/services/playground/knobs.js b/src-docs/src/services/playground/knobs.js index 3094bf950cb..614a03a386f 100644 --- a/src-docs/src/services/playground/knobs.js +++ b/src-docs/src/services/playground/knobs.js @@ -214,6 +214,7 @@ const Knob = ({ isInvalid={error && error.length > 0} compressed fullWidth + hasNoInitialSelection={!valueKey && !defaultValue} /> ); @@ -241,11 +242,11 @@ const Knob = ({ { const value = e.target.checked; - set(value ? value : undefined); + set(value ? custom.value ?? e.target.checked : undefined); }} compressed /> diff --git a/src-docs/src/services/playground/playground.js b/src-docs/src/services/playground/playground.js index 503154b23be..01293d19d82 100644 --- a/src-docs/src/services/playground/playground.js +++ b/src-docs/src/services/playground/playground.js @@ -26,7 +26,15 @@ export default ({ config, setGhostBackground, playgroundClassName }) => { newCode = newCode.replace(/(\);)$/m, ''); } - return format(newCode.trim(), ' '.repeat(4)); + let formatted; + // TODO: Replace `html-format` with something better. + // Notably, something more jsx-friendly + try { + formatted = format(newCode.trim(), ' '.repeat(4)); + } catch { + formatted = newCode.trim(); + } + return formatted; }; const Playground = () => { diff --git a/src-docs/src/services/playground/utils.js b/src-docs/src/services/playground/utils.js new file mode 100644 index 00000000000..a0fe26d599e --- /dev/null +++ b/src-docs/src/services/playground/utils.js @@ -0,0 +1,16 @@ +import template from '@babel/template'; + +export const generateAst = (value) => { + return template.ast(String(value), { plugins: ['jsx'] }).expression; +}; + +export const generateCustomProps = (props) => { + return props.reduce((obj, item) => { + return { + ...obj, + [item]: { + generate: generateAst, + }, + }; + }, {}); +}; diff --git a/src-docs/src/views/icon/logos.js b/src-docs/src/views/icon/logos.js index b433169d531..d0d42f58846 100644 --- a/src-docs/src/views/icon/logos.js +++ b/src-docs/src/views/icon/logos.js @@ -20,7 +20,7 @@ import { EuiCopy, } from '../../../../src/components'; -const iconTypes = [ +export const iconTypes = [ 'logoAppSearch', 'logoBeats', 'logoBusinessAnalytics', diff --git a/src-docs/src/views/page/page.js b/src-docs/src/views/page/page.js index 3fe78d2f491..d6d4eca6e77 100644 --- a/src-docs/src/views/page/page.js +++ b/src-docs/src/views/page/page.js @@ -8,23 +8,23 @@ import { EuiPageContentHeader, EuiPageContentHeaderSection, EuiPageHeader, - EuiPageHeaderSection, EuiPageSideBar, EuiTitle, + EuiButton, } from '../../../../src/components'; export default () => ( SideBar nav - - - -

Page title

-
-
- Page abilities -
+ Add something, + Do something, + ]} + /> 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 index 2856fd5b733..27e97f9aa33 100644 --- 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 @@ -8,7 +8,6 @@ import { EuiPageContentHeader, EuiPageContentHeaderSection, EuiPageHeader, - EuiPageHeaderSection, EuiPageSideBar, EuiTitle, } from '../../../../src/components'; @@ -18,14 +17,11 @@ 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. */} - - - -

Page title

-
-
- Page abilities -
+ diff --git a/src-docs/src/views/page/page_example.js b/src-docs/src/views/page/page_example.js index fbb6c94d5ef..a84baa69ba2 100644 --- a/src-docs/src/views/page/page_example.js +++ b/src-docs/src/views/page/page_example.js @@ -3,6 +3,7 @@ import React from 'react'; import { renderToHtml } from '../../services'; import { GuideSectionTypes } from '../../components'; +import Playground from './playground'; import { EuiCode, @@ -27,6 +28,18 @@ import PageSimple from './page_simple'; const pageSimpleSource = require('!!raw-loader!./page_simple'); const pageSimpleHtml = renderToHtml(PageSimple); +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'; +const pageHeaderCustomSource = require('!!raw-loader!./page_header_custom'); +const pageHeaderCustomHtml = renderToHtml(PageHeaderCustom); + import PageContentOnly from './page_content_only'; const pageContentOnlySource = require('!!raw-loader!./page_content_only'); const pageContentOnlyHtml = renderToHtml(Page); @@ -40,6 +53,7 @@ const PageContentCenterWithSideBarSource = require('!!raw-loader!./page_content_ const PageContentCenterWithSideBarHtml = renderToHtml(Page); export const PageExample = { + playground: Playground, title: 'Page', intro: ( @@ -53,7 +67,7 @@ export const PageExample = { ), sections: [ { - title: 'Page with everything on', + title: 'A full page layout with everything on', source: [ { type: GuideSectionTypes.JS, @@ -66,6 +80,28 @@ export const PageExample = { ], text: (
+

+ EUI provides a family of components using the{' '} + EuiPage prefix that work together to build + consistent page layouts that work responsively. +

+
    +
  • + EuiPage provides the overall wrapper. +
  • +
  • + EuiPageHeader provides a title, description, + section for actions and possible tabs. +
  • +
  • + EuiPageContent and its family of related + components provide the main content container. +
  • +
  • + EuiPageSideBar provides a way to add side + navigation. +
  • +

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{' '} @@ -93,7 +129,7 @@ export const PageExample = { ), }, { - title: 'Simple page with title', + title: 'A simple page layout with a title', source: [ { type: GuideSectionTypes.JS, @@ -118,7 +154,7 @@ export const PageExample = { ), }, { - title: 'Page with content only', + title: 'A simple page layout with content only', source: [ { type: GuideSectionTypes.JS, @@ -137,7 +173,7 @@ export const PageExample = { ), }, { - title: 'Page content centered', + title: 'A simple page layout with content centered', source: [ { type: GuideSectionTypes.JS, @@ -161,7 +197,7 @@ export const PageExample = { ), }, { - title: 'Page content centered in a full layout', + title: 'A simple page layout with content centered in a full layout', source: [ { type: GuideSectionTypes.JS, @@ -185,5 +221,131 @@ export const PageExample = {

), }, + { + title: 'The page header in detail', + 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 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. +

+ + ), + 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 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. +

+ + ), + demo: ( +
+ +
+ ), + props: { EuiPageHeader }, + }, ], }; diff --git a/src-docs/src/views/page/page_header.js b/src-docs/src/views/page/page_header.js new file mode 100644 index 00000000000..239c9a704f0 --- /dev/null +++ b/src-docs/src/views/page/page_header.js @@ -0,0 +1,37 @@ +import React from 'react'; + +import { + EuiPageHeader, + EuiCode, + EuiText, + EuiButton, +} from '../../../../src/components'; + +export default () => ( + Add something, + Do something, + ]}> + +

+ This custom content (children), on the other hand, exists below the + content above including below the right side content and therefore will + stretch beneath them. Unless you set the alignItems{' '} + prop to something other than top. +

+
+
+); diff --git a/src-docs/src/views/page/page_header_custom.js b/src-docs/src/views/page/page_header_custom.js new file mode 100644 index 00000000000..f1642afe595 --- /dev/null +++ b/src-docs/src/views/page/page_header_custom.js @@ -0,0 +1,18 @@ +import React from 'react'; + +import { + EuiPageHeader, + EuiPageHeaderSection, + EuiTitle, +} from '../../../../src/components'; + +export default () => ( + + + +

Page title

+
+
+ Page abilities +
+); diff --git a/src-docs/src/views/page/page_header_tabs.js b/src-docs/src/views/page/page_header_tabs.js new file mode 100644 index 00000000000..6c92df0e8ac --- /dev/null +++ b/src-docs/src/views/page/page_header_tabs.js @@ -0,0 +1,35 @@ +import React from 'react'; + +import { + EuiPageHeader, + EuiText, + EuiButton, + EuiCode, +} from '../../../../src/components'; + +export default () => ( + Add something, + Do something, + ]}> + +

+ This custom content (children), on the other hand, exists below the + content above including below the right side content and therefore will + stretch beneath them. Unless you set the alignItems{' '} + prop to something other than top. +

+
+
+); diff --git a/src-docs/src/views/page/page_simple.js b/src-docs/src/views/page/page_simple.js index d473522c675..f7c7b237d98 100644 --- a/src-docs/src/views/page/page_simple.js +++ b/src-docs/src/views/page/page_simple.js @@ -8,20 +8,13 @@ import { EuiPageContentHeader, EuiPageContentHeaderSection, EuiPageHeader, - EuiPageHeaderSection, EuiTitle, } from '../../../../src/components'; export default () => ( - - - -

Page title

-
-
-
+ diff --git a/src-docs/src/views/page/playground.js b/src-docs/src/views/page/playground.js new file mode 100644 index 00000000000..6be4412b5f9 --- /dev/null +++ b/src-docs/src/views/page/playground.js @@ -0,0 +1,98 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import React from 'react'; +import { PropTypes } from 'react-view'; +import { + 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; + 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 b79159e15d4..07e46c8083f 100644 --- a/src-docs/src/views/tour/fullscreen.js +++ b/src-docs/src/views/tour/fullscreen.js @@ -10,7 +10,6 @@ import { EuiPage, EuiPageBody, EuiPageHeader, - EuiPageHeaderSection, EuiPageContent, EuiPageContentHeader, EuiPageContentHeaderSection, @@ -178,22 +177,18 @@ export default () => { - - - -

My app

-
-
- + setIsFullScreen(false)} iconType="exit" aria-label="Exit fullscreen demo"> Exit fullscreen demo - - -
+ , + ]} + /> diff --git a/src/components/flex/flex_group.tsx b/src/components/flex/flex_group.tsx index 72c1fa8f516..e3ae0de1cdd 100644 --- a/src/components/flex/flex_group.tsx +++ b/src/components/flex/flex_group.tsx @@ -27,7 +27,9 @@ export type FlexGroupDirection = keyof typeof directionToClassNameMap; export type FlexGroupGutterSize = keyof typeof gutterSizeToClassNameMap; export type FlexGroupJustifyContent = keyof typeof justifyContentToClassNameMap; -export interface EuiFlexGroupProps { +export interface EuiFlexGroupProps + extends CommonProps, + HTMLAttributes { alignItems?: FlexGroupAlignItems; component?: FlexGroupComponentType; direction?: FlexGroupDirection; @@ -87,9 +89,7 @@ const isValidElement = ( export const EuiFlexGroup = forwardRef< HTMLDivElement | HTMLSpanElement, - CommonProps & - HTMLAttributes & - EuiFlexGroupProps + EuiFlexGroupProps >( ( { diff --git a/src/components/flex/index.ts b/src/components/flex/index.ts index 5ea60b7ceaa..f9804e43cb5 100644 --- a/src/components/flex/index.ts +++ b/src/components/flex/index.ts @@ -17,7 +17,11 @@ * under the License. */ -export { EuiFlexGroup, EuiFlexGroupGutterSize } from './flex_group'; +export { + EuiFlexGroup, + EuiFlexGroupProps, + EuiFlexGroupGutterSize, +} from './flex_group'; export { EuiFlexGrid } from './flex_grid'; diff --git a/src/components/index.js b/src/components/index.js index a57b1fe2235..b2af052ad9f 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -253,6 +253,7 @@ export { EuiPageContentHeader, EuiPageContentHeaderSection, EuiPageHeader, + EuiPageHeaderContent, EuiPageHeaderSection, EuiPageSideBar, } from './page'; diff --git a/src/components/page/_page.scss b/src/components/page/_page.scss index 973a1ee53b1..39634976c12 100644 --- a/src/components/page/_page.scss +++ b/src/components/page/_page.scss @@ -9,7 +9,7 @@ } &--restrictWidth-default { - max-width: 1000px; + max-width: $euiPageDefaultMaxWidth; } @include euiBreakpoint('xs', 's') { diff --git a/src/components/page/index.ts b/src/components/page/index.ts index 7cfb2d64ef3..942c6887b01 100644 --- a/src/components/page/index.ts +++ b/src/components/page/index.ts @@ -34,6 +34,8 @@ export { export { EuiPageHeader, + EuiPageHeaderContent, + EuiPageHeaderContentProps, EuiPageHeaderProps, EuiPageHeaderSection, EuiPageHeaderSectionProps, diff --git a/src/components/page/page.tsx b/src/components/page/page.tsx index 89668dd07e7..33deab797a8 100644 --- a/src/components/page/page.tsx +++ b/src/components/page/page.tsx @@ -35,7 +35,7 @@ export interface EuiPageProps HTMLAttributes { /** * Sets the max-width of the page, - * set to `true` to use the default size of `1000px`, + * 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. @@ -43,7 +43,7 @@ export interface EuiPageProps restrictWidth?: boolean | number | string; /** * Adjust the padding. - * When using this setting it's best to be consistent throught all similar usages. + * When using this setting it's best to be consistent throughout all similar usages. */ paddingSize?: typeof SIZES[number]; } diff --git a/src/components/page/page_body/_page_body.scss b/src/components/page/page_body/_page_body.scss index 990f0fcd1ab..ccc2c84d399 100644 --- a/src/components/page/page_body/_page_body.scss +++ b/src/components/page/page_body/_page_body.scss @@ -13,7 +13,7 @@ } &--restrictWidth-default { - max-width: 1000px; + max-width: $euiPageDefaultMaxWidth; } } diff --git a/src/components/page/page_content/page_content.tsx b/src/components/page/page_content/page_content.tsx index c707a6639c4..9169f6fc65b 100644 --- a/src/components/page/page_content/page_content.tsx +++ b/src/components/page/page_content/page_content.tsx @@ -40,6 +40,9 @@ const horizontalPositionToClassNameMap: { export type EuiPageContentProps = CommonProps & EuiPanelProps & { + /** + * **DEPRECATED: use `paddingSize` instead.** + */ panelPaddingSize?: PanelPaddingSize; verticalPosition?: EuiPageContentVerticalPositions; horizontalPosition?: EuiPageContentHorizontalPositions; @@ -48,7 +51,8 @@ export type EuiPageContentProps = CommonProps & export const EuiPageContent: FunctionComponent = ({ verticalPosition, horizontalPosition, - panelPaddingSize = 'l', + panelPaddingSize, + paddingSize = 'l', children, className, ...rest @@ -63,7 +67,10 @@ export const EuiPageContent: FunctionComponent = ({ ); 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 44bb0884549..a30fad4cb45 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 @@ -1,9 +1,328 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`EuiPageHeader is rendered 1`] = ` -
+ Anything + +`; + +exports[`EuiPageHeader props alignItems bottom is rendered 1`] = ` +
+
+
+
+

+ Page title +

+
+
+
+
+ +
+
+ +
+
+
+
+
+
+`; + +exports[`EuiPageHeader props alignItems center is rendered 1`] = ` +
+
+
+
+

+ Page title +

+
+
+
+
+ +
+
+ +
+
+
+
+
+
+`; + +exports[`EuiPageHeader props alignItems stretch is rendered 1`] = ` +
+
+
+
+

+ Page title +

+
+
+
+
+ +
+
+ +
+
+
+
+
+
+`; + +exports[`EuiPageHeader props alignItems top is rendered 1`] = ` +
+
+
+
+

+ Page title +

+
+
+
+
+ +
+
+ +
+
+
+
+
+
+`; + +exports[`EuiPageHeader props page content props are passed down is rendered 1`] = ` +
+
+
+
+

+ + Page title +

+
+
+

+ Description +

+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+

+ Anything +

+
+
+ + +
+
+
+
+`; + +exports[`EuiPageHeader props responsive is rendered as false 1`] = ` +
+`; + +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 new file mode 100644 index 00000000000..0b4ff499d20 --- /dev/null +++ b/src/components/page/page_header/__snapshots__/page_header_content.test.tsx.snap @@ -0,0 +1,597 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EuiPageHeaderContent is rendered 1`] = ` +
+
+
+
+
+`; + +exports[`EuiPageHeaderContent props alignItems bottom is rendered 1`] = ` +
+
+
+

+ Page title +

+
+
+
+
+ +
+
+ +
+
+
+
+
+`; + +exports[`EuiPageHeaderContent props alignItems center is rendered 1`] = ` +
+
+
+

+ Page title +

+
+
+
+
+ +
+
+ +
+
+
+
+
+`; + +exports[`EuiPageHeaderContent props alignItems stretch is rendered 1`] = ` +
+
+
+

+ Page title +

+
+
+
+
+ +
+
+ +
+
+
+
+
+`; + +exports[`EuiPageHeaderContent props alignItems top is rendered 1`] = ` +
+
+
+

+ Page title +

+
+
+
+
+ +
+
+ +
+
+
+
+
+`; + +exports[`EuiPageHeaderContent props children is rendered 1`] = ` +
+
+
+
+
+
+

+ Anything +

+
+
+`; + +exports[`EuiPageHeaderContent props children is rendered 2`] = ` +
+
+
+
+
+
+ Child +
+
+`; + +exports[`EuiPageHeaderContent props children is rendered even if content props are passed 1`] = ` +
+
+
+

+ Page title +

+
+
+
+
+ +
+
+ +
+
+
+
+
+
+ Child +
+
+ + +
+
+
+`; + +exports[`EuiPageHeaderContent props description is rendered 1`] = ` +
+
+
+
+

+ Description +

+
+
+
+
+`; + +exports[`EuiPageHeaderContent props pageTitle is rendered 1`] = ` +
+
+
+

+ Page title +

+
+
+
+`; + +exports[`EuiPageHeaderContent props pageTitle is rendered with icon 1`] = ` +
+
+
+

+ + Page title +

+
+
+
+`; + +exports[`EuiPageHeaderContent props pageTitle is rendered with icon and iconProps 1`] = ` +
+
+
+

+ + Page title +

+
+
+
+`; + +exports[`EuiPageHeaderContent props responsive is rendered as false 1`] = ` +
+
+
+
+
+`; + +exports[`EuiPageHeaderContent props responsive is rendered as reverse 1`] = ` +
+
+
+
+
+`; + +exports[`EuiPageHeaderContent props rightSideItems is rendered 1`] = ` +
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+`; + +exports[`EuiPageHeaderContent props rightSideItems is rendered with rightSideGroupProps 1`] = ` +
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+`; + +exports[`EuiPageHeaderContent props tabs is rendered 1`] = ` +
+
+
+
+ + +
+
+
+
+`; + +exports[`EuiPageHeaderContent props tabs is rendered with tabsProps 1`] = ` +
+
+
+
+ + +
+
+
+
+`; diff --git a/src/components/page/page_header/_index.scss b/src/components/page/page_header/_index.scss index df5354044ba..600ba67cd57 100644 --- a/src/components/page/page_header/_index.scss +++ b/src/components/page/page_header/_index.scss @@ -1,2 +1,3 @@ @import 'page_header'; +@import 'page_header_content'; @import 'page_header_section'; diff --git a/src/components/page/page_header/_page_header.scss b/src/components/page/page_header/_page_header.scss index 1661035bd92..d7342edc9b7 100644 --- a/src/components/page/page_header/_page_header.scss +++ b/src/components/page/page_header/_page_header.scss @@ -1,9 +1,36 @@ .euiPageHeader { + width: 100%; margin-bottom: $euiSize; display: flex; flex-direction: row; justify-content: space-between; align-items: center; + + &--restrictWidth-default, + &--restrictWidth-custom { + margin-left: auto; + margin-right: auto; + } + + &--restrictWidth-default { + max-width: $euiPageDefaultMaxWidth; + } +} + +.euiPageHeader--top { + align-items: flex-start; +} + +.euiPageHeader--bottom { + align-items: flex-end; +} + +.euiPageHeader--stretch { + align-items: stretch; +} + +.euiPageHeader--tabsAtBottom { + padding-bottom: 0; } @include euiBreakpoint('xs', 's') { @@ -12,7 +39,15 @@ margin-bottom: 0; } + .euiPageHeader--tabsAtBottom { + padding-bottom: 0; + } + .euiPageHeader--responsive { flex-direction: column; } + + .euiPageHeader--responsiveReverse { + flex-direction: column-reverse; + } } diff --git a/src/components/page/page_header/_page_header_content.scss b/src/components/page/page_header/_page_header_content.scss new file mode 100644 index 00000000000..d9138bbb141 --- /dev/null +++ b/src/components/page/page_header/_page_header_content.scss @@ -0,0 +1,15 @@ +.euiPageHeader .euiPageHeaderContent { + width: 100%; +} + +.euiPageHeaderContent__titleIcon { + top: -$euiSizeXS; + position: relative; + margin-right: $euiSize; +} + +@include euiBreakpoint('m', 'l', 'xl') { + .euiPageHeaderContent__rightSideItems { + flex-direction: row-reverse; + } +} diff --git a/src/components/page/page_header/_page_header_section.scss b/src/components/page/page_header/_page_header_section.scss index f9df787fad6..dff7fb0a3d2 100644 --- a/src/components/page/page_header/_page_header_section.scss +++ b/src/components/page/page_header/_page_header_section.scss @@ -1,15 +1,25 @@ -.euiPageHeaderSection { - & + & { - margin-left: $euiSizeXL; - } +.euiPageHeaderSection:not(:first-of-type) { + margin-left: $euiSizeXL; } @include euiBreakpoint('xs', 's') { .euiPageHeader--responsive .euiPageHeaderSection { width: 100%; - + .euiPageHeaderSection { + &:not(:first-of-type) { + margin-left: 0; + margin-top: $euiSize; + } + } + + .euiPageHeader--responsiveReverse .euiPageHeaderSection { + width: 100%; + + &:not(:first-of-type) { margin-left: 0; + } + + &:not(:last-of-type) { margin-top: $euiSize; } } diff --git a/src/components/page/page_header/index.ts b/src/components/page/page_header/index.ts index 8a1d0654951..bba450a1733 100644 --- a/src/components/page/page_header/index.ts +++ b/src/components/page/page_header/index.ts @@ -18,6 +18,10 @@ */ export { EuiPageHeader, EuiPageHeaderProps } from './page_header'; +export { + EuiPageHeaderContent, + EuiPageHeaderContentProps, +} from './page_header_content'; export { EuiPageHeaderSection, EuiPageHeaderSectionProps, diff --git a/src/components/page/page_header/page_header.test.tsx b/src/components/page/page_header/page_header.test.tsx index 2fe9aa62fd7..1149311aeff 100644 --- a/src/components/page/page_header/page_header.test.tsx +++ b/src/components/page/page_header/page_header.test.tsx @@ -21,12 +21,96 @@ import React from 'react'; import { render } from 'enzyme'; import { requiredProps } from '../../../test/required_props'; -import { EuiPageHeader } from './page_header'; +import { EuiPageHeader, EuiPageHeaderProps } from './page_header'; +import { ALIGN_ITEMS } from './page_header_content'; + +export const tabs: EuiPageHeaderProps['tabs'] = [ + { + label: 'Tab 1', + isSelected: true, + }, + { + label: 'Tab 2', + }, +]; + +export const rightSideItems: EuiPageHeaderProps['rightSideItems'] = [ + , + , +]; describe('EuiPageHeader', () => { test('is rendered', () => { - const component = render(); + const component = render( + Anything + ); expect(component).toMatchSnapshot(); }); + + describe('props', () => { + describe('page content props are passed down', () => { + test('is rendered', () => { + const component = render( + +

Anything

+
+ ); + + expect(component).toMatchSnapshot(); + }); + }); + + describe('alignItems', () => { + ALIGN_ITEMS.forEach((alignment) => { + it(`${alignment} is rendered`, () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + }); + }); + + describe('responsive', () => { + test('is rendered as false', () => { + const component = render(); + + expect(component).toMatchSnapshot(); + }); + + test('is rendered as reverse', () => { + const component = render(); + + expect(component).toMatchSnapshot(); + }); + }); + + describe('restrictWidth', () => { + test('is rendered as true', () => { + const component = render(); + + expect(component).toMatchSnapshot(); + }); + + test('is rendered as custom', () => { + const component = render(); + + expect(component).toMatchSnapshot(); + }); + }); + }); }); diff --git a/src/components/page/page_header/page_header.tsx b/src/components/page/page_header/page_header.tsx index bf682c0e622..8df58ceff1c 100644 --- a/src/components/page/page_header/page_header.tsx +++ b/src/components/page/page_header/page_header.tsx @@ -20,34 +20,90 @@ import React, { FunctionComponent, HTMLAttributes } from 'react'; import classNames from 'classnames'; import { CommonProps } from '../../common'; +import { + EuiPageHeaderContent, + EuiPageHeaderContentProps, +} from './page_header_content'; -export interface EuiPageHeaderProps - extends CommonProps, - HTMLAttributes { - /** - * Set to false if you don't want the children to stack - * at small screen sizes. - */ - responsive?: boolean; -} +export type EuiPageHeaderProps = CommonProps & + HTMLAttributes & + EuiPageHeaderContentProps & { + /** + * 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 const EuiPageHeader: FunctionComponent = ({ - children, className, + restrictWidth = false, + style, + + // Page header content shared props: + alignItems, responsive = true, + children, + + // Page header content only props: + pageTitle, + iconType, + iconProps, + tabs, + tabsProps, + description, + rightSideItems, + rightSideGroupProps, ...rest }) => { + let widthClassname; + let newStyle; + + if (restrictWidth === true) { + widthClassname = 'euiPageHeader--restrictWidth-default'; + } else if (restrictWidth !== false) { + widthClassname = 'euiPageHeader--restrictWidth-custom'; + newStyle = { ...style, maxWidth: restrictWidth }; + } + const classes = classNames( 'euiPageHeader', { - 'euiPageHeader--responsive': responsive, + 'euiPageHeader--responsive': responsive === true, + 'euiPageHeader--responsiveReverse': responsive === 'reverse', + 'euiPageHeader--tabsAtBottom': pageTitle && tabs, }, + `euiPageHeader--${alignItems ?? 'center'}`, + widthClassname, className ); + if (!pageTitle && !tabs && !description && !rightSideItems) { + return ( +
+ {children} +
+ ); + } + return ( -
- {children} -
+
+ + {children} + +
); }; diff --git a/src/components/page/page_header/page_header_content.test.tsx b/src/components/page/page_header/page_header_content.test.tsx new file mode 100644 index 00000000000..d704dd2af06 --- /dev/null +++ b/src/components/page/page_header/page_header_content.test.tsx @@ -0,0 +1,197 @@ +/* + * 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. + */ + +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../../test/required_props'; + +import { + ALIGN_ITEMS, + EuiPageHeaderContent, + EuiPageHeaderContentProps, +} from './page_header_content'; + +const tabs: EuiPageHeaderContentProps['tabs'] = [ + { + label: 'Tab 1', + isSelected: true, + }, + { + label: 'Tab 2', + }, +]; + +const rightSideItems: EuiPageHeaderContentProps['rightSideItems'] = [ + , + , +]; + +describe('EuiPageHeaderContent', () => { + test('is rendered', () => { + const component = render(); + + expect(component).toMatchSnapshot(); + }); + + describe('props', () => { + describe('pageTitle', () => { + test('is rendered', () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + + test('is rendered with icon', () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + + test('is rendered with icon and iconProps', () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + }); + + describe('tabs', () => { + test('is rendered', () => { + const component = render(); + + expect(component).toMatchSnapshot(); + }); + + test('is rendered with tabsProps', () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + }); + + describe('children', () => { + test('is rendered', () => { + const component = render( + +

Anything

+
+ ); + + expect(component).toMatchSnapshot(); + }); + }); + + describe('description', () => { + test('is rendered', () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + }); + + describe('rightSideItems', () => { + test('is rendered', () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + + test('is rendered with rightSideGroupProps', () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + }); + + describe('children', () => { + test('is rendered', () => { + const component = render( + Child + ); + + expect(component).toMatchSnapshot(); + }); + + test('is rendered even if content props are passed', () => { + const component = render( + + Child + + ); + + expect(component).toMatchSnapshot(); + }); + }); + + describe('alignItems', () => { + ALIGN_ITEMS.forEach((alignment) => { + it(`${alignment} is rendered`, () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + }); + }); + + describe('responsive', () => { + test('is rendered as false', () => { + const component = render(); + + expect(component).toMatchSnapshot(); + }); + + test('is rendered as reverse', () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/src/components/page/page_header/page_header_content.tsx b/src/components/page/page_header/page_header_content.tsx new file mode 100644 index 00000000000..a82df74d0bf --- /dev/null +++ b/src/components/page/page_header/page_header_content.tsx @@ -0,0 +1,298 @@ +/* + * 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. + */ + +import React, { FunctionComponent, ReactNode, HTMLAttributes } from 'react'; +import classNames from 'classnames'; +import { CommonProps } from '../../common'; +import { EuiIcon, EuiIconProps, IconType } from '../../icon'; +import { EuiTab, EuiTabs, EuiTabsProps } from '../../tabs'; +import { Props as EuiTabProps } from '../../tabs/tab'; +import { EuiFlexGroup, EuiFlexItem, EuiFlexGroupProps } from '../../flex'; +import { EuiSpacer } from '../../spacer'; +import { EuiTitle } from '../../title'; +import { EuiText } from '../../text'; +import { useIsWithinBreakpoints } from '../../../services/hooks'; + +export const ALIGN_ITEMS = ['top', 'bottom', 'center', 'stretch'] as const; + +// Gets all the tab props including the button or link props +type Tab = EuiTabProps & { + /** + * Visible text of the tab + */ + label: string; +}; + +export type EuiPageHeaderContentTitle = { + /** + * Wrapped in an `H1` so choose appropriately. + * A simple string is best + */ + pageTitle?: ReactNode; + /** + * Optional icon to place to the left of the title + */ + iconType?: IconType; + /** + * Additional EuiIcon props to apply to the optional icon + */ + iconProps?: Partial>; +}; + +export type EuiPageHeaderContentTabs = { + /** + * In-app navigation presented as large borderless tabs. + * Accepts an array of `EuiTab` objects; + * HELP: This is evaluating to `any[]` in the props table + */ + tabs?: Tab[]; + /** + * Any extras to apply to the outer tabs container. + * Extends `EuiTabs` + */ + tabsProps?: Omit; +}; + +/** + * The left side can either be a title with optional description and/or icon; + * Or a list of tabs, + * Or a custom node + */ +type EuiPageHeaderContentLeft = EuiPageHeaderContentTitle & + EuiPageHeaderContentTabs & { + /** + * Position is dependent on existing with a `pageTitle` or `tabs` + * Automatically get wrapped in a single paragraph tag inside an EuiText block + */ + description?: string | ReactNode; + }; + +export type EuiPageHeaderContentProps = CommonProps & + HTMLAttributes & + EuiPageHeaderContentLeft & { + /** + * Set to false if you don't want the children to stack at small screen sizes. + * Set to `reverse` to display the right side content first for the sack of hierarchy (like global time) + */ + responsive?: boolean | 'reverse'; + /** + * Vertical alignment of the left and right side content; + * Default is `middle` for custom content, but `top` for when `pageTitle` or `tabs` are included + */ + alignItems?: typeof ALIGN_ITEMS[number]; + /** + * Pass custom an array of content to this side usually up to 3 buttons. + * The first button should be primary, usually with `fill` and will be visually displayed as the last item, + * but first in the tab order + */ + rightSideItems?: ReactNode[]; + /** + * Additional EuiFlexGroup props to pass to the container of the `rightSideItems` + */ + rightSideGroupProps?: Partial; + /** + * Custom children will be rendered before the `tabs` unless no `pageTitle` is present, then it will be the last item + */ + children?: ReactNode; + }; + +export const EuiPageHeaderContent: FunctionComponent = ({ + className, + pageTitle, + iconType, + iconProps, + tabs, + tabsProps, + description, + alignItems = 'top', + responsive = true, + rightSideItems, + rightSideGroupProps, + children, + ...rest +}) => { + const isResponsiveBreakpoint = useIsWithinBreakpoints( + ['xs', 's'], + !!responsive + ); + + const classes = classNames('euiPageHeaderContent'); + + let descriptionNode; + if (description) { + descriptionNode = ( + <> + {(pageTitle || tabs) && } + +

{description}

+
+ + ); + } + + let pageTitleNode; + if (pageTitle) { + const icon = iconType ? ( + + ) : undefined; + + pageTitleNode = ( + +

+ {icon} + {pageTitle} +

+
+ ); + } + + let tabsNode; + if (tabs) { + const renderTabs = () => { + return tabs.map((tab, index) => { + const { label, ...tabRest } = tab; + return ( + + {label} + + ); + }); + }; + + tabsNode = ( + <> + {pageTitleNode && } + + {renderTabs()} + + + ); + } + + const childrenNode = children && ( + <> + + {children} + + ); + + let bottomContentNode; + if (childrenNode || (tabsNode && pageTitleNode)) { + bottomContentNode = ( +
+ {childrenNode} + {pageTitleNode && tabsNode} +
+ ); + } + + /** + * The left side order depends on if a `pageTitle` was supplied. + * If not, but there are `tabs`, then the tabs become the page title + */ + let leftSideOrder; + if (tabsNode && !pageTitleNode) { + leftSideOrder = ( + <> + {tabsNode} + {descriptionNode} + + ); + } else { + leftSideOrder = ( + <> + {pageTitleNode} + {descriptionNode} + + ); + } + + let rightSideFlexItem; + if (rightSideItems && rightSideItems.length) { + const wrapWithFlex = () => { + return rightSideItems.map((item, index) => { + return ( + + {item} + + ); + }); + }; + + rightSideFlexItem = ( + + + {wrapWithFlex()} + + + ); + } + + return alignItems === 'top' || isResponsiveBreakpoint ? ( +
+ + {isResponsiveBreakpoint && responsive === 'reverse' ? ( + <> + {rightSideFlexItem} + {leftSideOrder} + + ) : ( + <> + {leftSideOrder} + {rightSideFlexItem} + + )} + + {bottomContentNode} +
+ ) : ( +
+ + + {leftSideOrder} + {bottomContentNode} + + {rightSideFlexItem} + +
+ ); +}; diff --git a/src/global_styling/mixins/_responsive.scss b/src/global_styling/mixins/_responsive.scss index 910c26d27a2..6747bde4c76 100644 --- a/src/global_styling/mixins/_responsive.scss +++ b/src/global_styling/mixins/_responsive.scss @@ -3,8 +3,6 @@ // sass-lint:disable quotes, no-warn, indentation -@import '../variables/responsive'; - // A sem-complicated mixin for breakpoints, that takes any number of // named breakpoints that exists in $euiBreakpoints. diff --git a/src/global_styling/variables/_index.scss b/src/global_styling/variables/_index.scss index 61d74dfcec3..ce7d1d67fcc 100644 --- a/src/global_styling/variables/_index.scss +++ b/src/global_styling/variables/_index.scss @@ -12,6 +12,7 @@ @import 'animations'; @import 'typography'; @import 'borders'; +@import 'responsive'; @import 'shadows'; @import 'states'; @import 'z_index'; @@ -19,5 +20,6 @@ @import 'buttons'; @import 'form'; @import 'header'; +@import 'page'; @import 'panel'; @import 'tool_tip'; diff --git a/src/global_styling/variables/_page.scss b/src/global_styling/variables/_page.scss new file mode 100644 index 00000000000..870b260aa83 --- /dev/null +++ b/src/global_styling/variables/_page.scss @@ -0,0 +1 @@ +$euiPageDefaultMaxWidth: 1000px !default; diff --git a/src/services/hooks/useIsWithinBreakpoints.ts b/src/services/hooks/useIsWithinBreakpoints.ts index 0bdef3562fb..17ffa46a17d 100644 --- a/src/services/hooks/useIsWithinBreakpoints.ts +++ b/src/services/hooks/useIsWithinBreakpoints.ts @@ -27,9 +27,13 @@ import { isWithinBreakpoints, EuiBreakpointSize } from '../breakpoint'; * falls within any of the named breakpoints. * * @param {EuiBreakpointSize[]} sizes An array of named breakpoints + * @param {boolean} isActive Manages whether the resize handler should be active * @returns {boolean} Returns `true` if current breakpoint name is included in `sizes` */ -export function useIsWithinBreakpoints(sizes: EuiBreakpointSize[]) { +export function useIsWithinBreakpoints( + sizes: EuiBreakpointSize[], + isActive: boolean = true +) { const [isWithinBreakpointsValue, setIsWithinBreakpointsValue] = useState< boolean >(false); @@ -41,12 +45,14 @@ export function useIsWithinBreakpoints(sizes: EuiBreakpointSize[]) { ); } - window.addEventListener('resize', handleResize); - - handleResize(); + if (isActive) { + window.removeEventListener('resize', handleResize); + window.addEventListener('resize', handleResize); + handleResize(); + } return () => window.removeEventListener('resize', handleResize); - }, [sizes]); + }, [sizes, isActive]); return isWithinBreakpointsValue; } diff --git a/src/themes/eui-amsterdam/global_styling/mixins/_shadow.scss b/src/themes/eui-amsterdam/global_styling/mixins/_shadow.scss index 49d46e2e0e4..f50c8a74c28 100644 --- a/src/themes/eui-amsterdam/global_styling/mixins/_shadow.scss +++ b/src/themes/eui-amsterdam/global_styling/mixins/_shadow.scss @@ -17,8 +17,7 @@ box-shadow: 0 .7px 1.4px rgba($color, shadowOpacity(.07)), 0 1.9px 4px rgba($color, shadowOpacity(.05)), - 0 4.5px 10px rgba($color, shadowOpacity(.05)), - 0 15px 32px rgba($color, shadowOpacity(.04)); + 0 4.5px 10px rgba($color, shadowOpacity(.05)); @if ($opacity > 0) { @warn 'The $opacity variable of euiBottomShadowSmall() will be depricated in a future version of EUI.'; @@ -77,9 +76,9 @@ ) { @if ($reverse) { box-shadow: - 0 2.7px -9px rgba($color, shadowOpacity(.13)), - 0 9.4px -24px rgba($color, shadowOpacity(.09)), - 0 21.8px -43px rgba($color, shadowOpacity(.08)); + 0 -2.7px 9px rgba($color, shadowOpacity(.13)), + 0 -9.4px 24px rgba($color, shadowOpacity(.09)), + 0 -21.8px 43px rgba($color, shadowOpacity(.08)); } @else { box-shadow: 0 2.7px 9px rgba($color, shadowOpacity(.13)), diff --git a/src/themes/eui-amsterdam/global_styling/variables/_index.scss b/src/themes/eui-amsterdam/global_styling/variables/_index.scss index b3d6b1d5f8c..2b1628872ce 100644 --- a/src/themes/eui-amsterdam/global_styling/variables/_index.scss +++ b/src/themes/eui-amsterdam/global_styling/variables/_index.scss @@ -7,6 +7,7 @@ @import 'flyout'; @import 'form'; @import 'header'; +@import 'page'; @import 'panel'; @import 'steps'; @import 'typography'; diff --git a/src/themes/eui-amsterdam/global_styling/variables/_page.scss b/src/themes/eui-amsterdam/global_styling/variables/_page.scss new file mode 100644 index 00000000000..318cae55681 --- /dev/null +++ b/src/themes/eui-amsterdam/global_styling/variables/_page.scss @@ -0,0 +1 @@ +$euiPageDefaultMaxWidth: map-get($euiBreakpoints, 'xl');