diff --git a/src-docs/src/components/guide_section/guide_section.tsx b/src-docs/src/components/guide_section/guide_section.tsx index 6a1ea6407a1..2e479d14013 100644 --- a/src-docs/src/components/guide_section/guide_section.tsx +++ b/src-docs/src/components/guide_section/guide_section.tsx @@ -185,6 +185,7 @@ export const GuideSection: FunctionComponent = ({ config, setGhostBackground, playgroundClassName, + playgroundCssStyles, playgroundPanelProps, } = playground(); @@ -192,6 +193,7 @@ export const GuideSection: FunctionComponent = ({ config, setGhostBackground, playgroundClassName, + playgroundCssStyles, playgroundPanelProps, playgroundToggle: renderPlaygroundToggle(), tabs: renderTabs(), diff --git a/src-docs/src/services/playground/playground.js b/src-docs/src/services/playground/playground.js index 2f12d0d623e..4422d135181 100644 --- a/src-docs/src/services/playground/playground.js +++ b/src-docs/src/services/playground/playground.js @@ -9,7 +9,8 @@ import { EuiFlyoutBody, EuiFlyoutHeader, EuiPanel, -} from '../../../../src/components'; + useEuiTheme, +} from '../../../../src'; import Knobs from './knobs'; import { GuideSectionPropsDescription } from '../../components/guide_section/guide_section_parts/guide_section_props_description'; @@ -17,6 +18,7 @@ export default ({ config, setGhostBackground, playgroundClassName, + playgroundCssStyles, playgroundPanelProps, }) => { const getSnippet = (code) => { @@ -50,6 +52,7 @@ export default ({ }; const Playground = () => { + const euiTheme = useEuiTheme(); const [isGhost, setGhost] = useState(false); const params = useView(config); @@ -87,6 +90,7 @@ export default ({ diff --git a/src-docs/src/views/_index.scss b/src-docs/src/views/_index.scss index d2d7e586ba0..1648b306b84 100644 --- a/src-docs/src/views/_index.scss +++ b/src-docs/src/views/_index.scss @@ -7,7 +7,6 @@ $guideDemoHighlightColor: transparentize($euiColorPrimary, .9); @import './date_picker/date_picker'; @import './elastic_charts/index'; @import './empty_prompt/empty_prompt'; -@import './flex/flex'; @import './horizontal_rule/horizontal_rule'; @import './page_template/page'; @import './notification_event/notification_event'; diff --git a/src-docs/src/views/flex/_flex.scss b/src-docs/src/views/flex/_flex.scss deleted file mode 100644 index ef069da0157..00000000000 --- a/src-docs/src/views/flex/_flex.scss +++ /dev/null @@ -1,5 +0,0 @@ -.guideDemo__highlightGrid .euiFlexItem, -.guideDemo__highlightGridWrap .euiFlexItem div { - background: $guideDemoHighlightColor; - padding: $euiSize; -} diff --git a/src-docs/src/views/flex/component_span.tsx b/src-docs/src/views/flex/component_span.tsx index 878ee751e71..078a000f685 100644 --- a/src-docs/src/views/flex/component_span.tsx +++ b/src-docs/src/views/flex/component_span.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { EuiFlexGroup, EuiFlexItem } from '../../../../src/components/flex'; +import { EuiFlexGroup, EuiFlexItem } from '../../../../src/components'; export default () => ( diff --git a/src/components/flex/__snapshots__/flex_grid.test.tsx.snap b/src/components/flex/__snapshots__/flex_grid.test.tsx.snap index 87089d939cc..d27f5ddcaa9 100644 --- a/src/components/flex/__snapshots__/flex_grid.test.tsx.snap +++ b/src/components/flex/__snapshots__/flex_grid.test.tsx.snap @@ -3,7 +3,7 @@ exports[`EuiFlexGrid is rendered 1`] = `

@@ -12,80 +12,74 @@ exports[`EuiFlexGrid is rendered 1`] = `

`; -exports[`EuiFlexGrid props columns 0 is rendered 1`] = ` -
-`; - exports[`EuiFlexGrid props columns 1 is rendered 1`] = `
`; exports[`EuiFlexGrid props columns 2 is rendered 1`] = `
`; exports[`EuiFlexGrid props columns 3 is rendered 1`] = `
`; exports[`EuiFlexGrid props columns 4 is rendered 1`] = `
`; exports[`EuiFlexGrid props direction column is rendered 1`] = `
`; exports[`EuiFlexGrid props direction row is rendered 1`] = `
`; exports[`EuiFlexGrid props gutterSize l is rendered 1`] = `
`; exports[`EuiFlexGrid props gutterSize m is rendered 1`] = `
`; exports[`EuiFlexGrid props gutterSize none is rendered 1`] = `
`; exports[`EuiFlexGrid props gutterSize s is rendered 1`] = `
`; exports[`EuiFlexGrid props gutterSize xl is rendered 1`] = `
`; exports[`EuiFlexGrid props responsive is rendered 1`] = `
`; diff --git a/src/components/flex/__snapshots__/flex_group.test.tsx.snap b/src/components/flex/__snapshots__/flex_group.test.tsx.snap index 5d0a51f350b..26f6bfb2313 100644 --- a/src/components/flex/__snapshots__/flex_group.test.tsx.snap +++ b/src/components/flex/__snapshots__/flex_group.test.tsx.snap @@ -3,7 +3,7 @@ exports[`EuiFlexGroup is rendered 1`] = `

@@ -14,162 +14,162 @@ exports[`EuiFlexGroup is rendered 1`] = ` exports[`EuiFlexGroup props alignItems baseline is rendered 1`] = `
`; exports[`EuiFlexGroup props alignItems center is rendered 1`] = `
`; exports[`EuiFlexGroup props alignItems flexEnd is rendered 1`] = `
`; exports[`EuiFlexGroup props alignItems flexStart is rendered 1`] = `
`; exports[`EuiFlexGroup props alignItems stretch is rendered 1`] = `
`; exports[`EuiFlexGroup props component div is rendered 1`] = `
`; exports[`EuiFlexGroup props component span is rendered 1`] = ` `; exports[`EuiFlexGroup props direction column is rendered 1`] = `
`; exports[`EuiFlexGroup props direction columnReverse is rendered 1`] = `
`; exports[`EuiFlexGroup props direction row is rendered 1`] = `
`; exports[`EuiFlexGroup props direction rowReverse is rendered 1`] = `
`; exports[`EuiFlexGroup props gutterSize l is rendered 1`] = `
`; exports[`EuiFlexGroup props gutterSize m is rendered 1`] = `
`; exports[`EuiFlexGroup props gutterSize none is rendered 1`] = `
`; exports[`EuiFlexGroup props gutterSize s is rendered 1`] = `
`; exports[`EuiFlexGroup props gutterSize xl is rendered 1`] = `
`; exports[`EuiFlexGroup props gutterSize xs is rendered 1`] = `
`; exports[`EuiFlexGroup props justifyContent center is rendered 1`] = `
`; exports[`EuiFlexGroup props justifyContent flexEnd is rendered 1`] = `
`; exports[`EuiFlexGroup props justifyContent flexStart is rendered 1`] = `
`; exports[`EuiFlexGroup props justifyContent spaceAround is rendered 1`] = `
`; exports[`EuiFlexGroup props justifyContent spaceBetween is rendered 1`] = `
`; exports[`EuiFlexGroup props justifyContent spaceEvenly is rendered 1`] = `
`; exports[`EuiFlexGroup props responsive false is rendered 1`] = `
`; exports[`EuiFlexGroup props responsive true is rendered 1`] = `
`; exports[`EuiFlexGroup props wrap false is rendered 1`] = `
`; exports[`EuiFlexGroup props wrap true is rendered 1`] = `
`; diff --git a/src/components/flex/__snapshots__/flex_item.test.tsx.snap b/src/components/flex/__snapshots__/flex_item.test.tsx.snap index 39afc51a3a3..95313d84673 100644 --- a/src/components/flex/__snapshots__/flex_item.test.tsx.snap +++ b/src/components/flex/__snapshots__/flex_item.test.tsx.snap @@ -1,81 +1,105 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`EuiFlexItem component 1`] = ` + +`; + +exports[`EuiFlexItem grow 0 is rendered 1`] = ` +
+`; + exports[`EuiFlexItem grow 1 is rendered 1`] = `
`; exports[`EuiFlexItem grow 2 is rendered 1`] = `
`; exports[`EuiFlexItem grow 3 is rendered 1`] = `
`; exports[`EuiFlexItem grow 4 is rendered 1`] = `
`; exports[`EuiFlexItem grow 5 is rendered 1`] = `
`; exports[`EuiFlexItem grow 6 is rendered 1`] = `
`; exports[`EuiFlexItem grow 7 is rendered 1`] = `
`; exports[`EuiFlexItem grow 8 is rendered 1`] = `
`; exports[`EuiFlexItem grow 9 is rendered 1`] = `
`; exports[`EuiFlexItem grow 10 is rendered 1`] = `
`; exports[`EuiFlexItem grow false is rendered 1`] = `
+`; + +exports[`EuiFlexItem grow null is rendered 1`] = ` +
`; exports[`EuiFlexItem grow true is rendered 1`] = `
+`; + +exports[`EuiFlexItem grow undefined is rendered 1`] = ` +
`; exports[`EuiFlexItem is rendered 1`] = `
`; diff --git a/src/components/flex/_flex_grid.scss b/src/components/flex/_flex_grid.scss deleted file mode 100644 index 60be52d4899..00000000000 --- a/src/components/flex/_flex_grid.scss +++ /dev/null @@ -1,99 +0,0 @@ -.euiFlexGrid { - display: flex; - flex-wrap: wrap; - margin-bottom: 0; - - > .euiFlexItem { - flex-grow: 0; - - &.euiFlexItem--flexGrowZero { - // sass-lint:disable-block no-important - flex-grow: 0 !important; - flex-basis: auto !important; - } - } -} - -/** - * 1. For vertical layouts we use columns instead of flex - */ -.euiFlexGrid--directionColumn { - display: block; /* 1 */ - column-gap: 0; // The "gap" comes from the margin around the item - - > .euiFlexItem { - display: inline-block; /* 1 */ - line-height: initial; // Ensures the item itself doesn't impose any height - } -} - -$gutterTypes: ( - // We're using calc which requires the px unit - gutterNone: 0px, // sass-lint:disable-line zero-unit - gutterSmall: $euiSizeS, - gutterMedium: $euiSize, - gutterLarge: $euiSizeL, - gutterXLarge: $euiSizeXL, -); - -$fractions: ( - fourths: ( - percentage: 25%, - count: 4, - ), - thirds: ( - percentage: 33.3%, - count: 3, - ), - halves: ( - percentage: 50%, - count: 2, - ), - single: ( - percentage: 100%, - count: 1, - ), -); - -@each $gutterName, $gutterSize in $gutterTypes { - $halfGutterSize: $gutterSize * .5; - - /** - * Uncouple the gutter margin from the column widths to support cases where we use a FlexGrid - * without columns. - */ - .euiFlexGrid--#{$gutterName} { - margin: -$halfGutterSize; - align-items: stretch; - - > .euiFlexItem { - margin: $halfGutterSize; - } - } - - @each $fraction, $map in $fractions { - .euiFlexGrid--#{$gutterName}.euiFlexGrid--#{$fraction} { - > .euiFlexItem { - flex-basis: calc(#{map-get($map, 'percentage')} - #{$gutterSize}); - } - - &.euiFlexGrid--directionColumn { /* 1 */ - column-count: map-get($map, 'count'); - - > .euiFlexItem { - width: calc(100% - #{$gutterSize}); - } - } - } - } -} - - -@include euiBreakpoint('xs', 's') { - .euiFlexGrid.euiFlexGrid--responsive { - // sass-lint:disable-block no-important - margin-left: 0 !important; - margin-right: 0 !important; - column-count: 1 !important; - } -} diff --git a/src/components/flex/_flex_group.scss b/src/components/flex/_flex_group.scss deleted file mode 100644 index 878c2a5b786..00000000000 --- a/src/components/flex/_flex_group.scss +++ /dev/null @@ -1,100 +0,0 @@ -.euiFlexGroup { - display: flex; - align-items: stretch; - flex-grow: 1; // Grow nested flex-groups by default - - .euiFlexItem { - flex-grow: 1; - flex-basis: 0%; - } -} - -$gutterTypes: ( - gutterExtraSmall: $euiSizeXS, - gutterSmall: $euiSizeS, - gutterMedium: $euiSize, - gutterLarge: $euiSizeL, - gutterExtraLarge: $euiSizeXXL, -); - -// Gutter Sizes -@each $gutterName, $gutterSize in $gutterTypes { - $halfGutterSize: $gutterSize * .5; - - .euiFlexGroup--#{$gutterName} { - margin: -$halfGutterSize; - - & > .euiFlexItem { - margin: $halfGutterSize; - } - } -} - -// Justify the grid -.euiFlexGroup--justifyContentSpaceEvenly { - justify-content: space-evenly; -} - -.euiFlexGroup--justifyContentSpaceBetween { - justify-content: space-between; -} - -.euiFlexGroup--justifyContentSpaceAround { - justify-content: space-around; -} - -.euiFlexGroup--justifyContentCenter { - justify-content: center; -} - -.euiFlexGroup--justifyContentFlexEnd { - justify-content: flex-end; -} - -// Align Items -.euiFlexGroup--alignItemsFlexStart { - align-items: flex-start; -} - -.euiFlexGroup--alignItemsCenter { - align-items: center; -} - -.euiFlexGroup--alignItemsFlexEnd { - align-items: flex-end; -} - -.euiFlexGroup--alignItemsBaseline { - align-items: baseline; -} - -// Direction - -.euiFlexGroup--directionRow { - flex-direction: row; -} - -.euiFlexGroup--directionRowReverse { - flex-direction: row-reverse; -} - -.euiFlexGroup--directionColumn { - flex-direction: column; -} - -.euiFlexGroup--directionColumnReverse { - flex-direction: column-reverse; -} - -// Wrap -.euiFlexGroup--wrap { - flex-wrap: wrap; -} - -@include euiBreakpoint('xs', 's') { - .euiFlexGroup--responsive { - flex-wrap: wrap; - margin-left: 0; - margin-right: 0; - } -} diff --git a/src/components/flex/_flex_item.scss b/src/components/flex/_flex_item.scss deleted file mode 100644 index c2b6ffdf6dc..00000000000 --- a/src/components/flex/_flex_item.scss +++ /dev/null @@ -1,35 +0,0 @@ -/** - * 1. Allow EuiPanels to expand to fill the item. - */ -.euiFlexItem { - display: flex; /* 1 */ - flex-direction: column; /* 1 */ - - /* - * 1. We need the extra specificity here to override the FlexGroup > FlexItem styles. - * 2. FlexItem can be manually set to not grow if needed. - */ - &.euiFlexItem--flexGrowZero { /* 1 */ - flex-grow: 0; /* 2 */ - flex-basis: auto; /* 2 */ - } - - @for $i from 1 through 10 { - &.euiFlexItem--flexGrow#{$i} { - flex-grow: $i; - } - } -} - -// On mobile we force them to stack and act the same. -@include euiBreakpoint('xs', 's') { - .euiFlexGroup--responsive > .euiFlexItem, - .euiFlexGrid--responsive > .euiFlexItem { - // sass-lint:disable-block no-important - width: 100% !important; - flex-basis: 100% !important; - margin-left: 0 !important; - margin-right: 0 !important; - margin-bottom: $euiSize !important; - } -} diff --git a/src/components/flex/_index.scss b/src/components/flex/_index.scss deleted file mode 100644 index 42ce128e9cf..00000000000 --- a/src/components/flex/_index.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import 'flex_group'; -@import 'flex_grid'; -@import 'flex_item'; diff --git a/src/components/flex/flex_grid.styles.ts b/src/components/flex/flex_grid.styles.ts new file mode 100644 index 00000000000..b41c6b589bc --- /dev/null +++ b/src/components/flex/flex_grid.styles.ts @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { css } from '@emotion/react'; +import { euiBreakpoint } from '../../global_styling'; +import { UseEuiTheme } from '../../services'; + +// Note: the only way to get column direction working with `display: grid` +// the same way `display: flex` works is to manually set `grid-template-rows`, +// calculated based on the number of children in the grid +export const euiFlexGridStyles = ( + euiThemeContext: UseEuiTheme, + gridTemplateRows = 0 +) => { + const { euiTheme } = euiThemeContext; + return { + euiFlexGrid: css` + display: grid; + `, + responsive: css` + ${euiBreakpoint(euiThemeContext, ['xs', 's'])} { + grid-template-columns: repeat(1, 1fr); + grid-auto-flow: row; + } + `, + direction: { + row: css``, + column: css` + grid-auto-flow: column; + grid-template-rows: repeat(${gridTemplateRows}, 1fr); + `, + }, + columnCount: { + '1': css` + grid-template-columns: repeat(1, 1fr); + `, + '2': css` + grid-template-columns: repeat(2, 1fr); + `, + '3': css` + grid-template-columns: repeat(3, 1fr); + `, + '4': css` + grid-template-columns: repeat(4, 1fr); + `, + }, + gutterSizes: { + none: css``, + s: css` + gap: ${euiTheme.size.s}; + `, + m: css` + gap: ${euiTheme.size.m}; + `, + l: css` + gap: ${euiTheme.size.l}; + `, + xl: css` + gap: ${euiTheme.size.xl}; + `, + }, + }; +}; diff --git a/src/components/flex/flex_grid.test.tsx b/src/components/flex/flex_grid.test.tsx index 9ff23f711e3..f987d90fc68 100644 --- a/src/components/flex/flex_grid.test.tsx +++ b/src/components/flex/flex_grid.test.tsx @@ -9,10 +9,13 @@ import React from 'react'; import { render } from 'enzyme'; import { requiredProps } from '../../test/required_props'; +import { shouldRenderCustomStyles } from '../../test/internal'; -import { EuiFlexGrid, GUTTER_SIZES, COLUMNS, DIRECTIONS } from './flex_grid'; +import { EuiFlexGrid, GUTTER_SIZES, DIRECTIONS } from './flex_grid'; describe('EuiFlexGrid', () => { + shouldRenderCustomStyles(); + test('is rendered', () => { const component = render( @@ -35,7 +38,7 @@ describe('EuiFlexGrid', () => { }); describe('columns', () => { - COLUMNS.forEach((value) => { + ([1, 2, 3, 4] as const).forEach((value) => { test(`${value} is rendered`, () => { const component = render(); diff --git a/src/components/flex/flex_grid.tsx b/src/components/flex/flex_grid.tsx index d0c94d9f06b..7866d3ee0dd 100644 --- a/src/components/flex/flex_grid.tsx +++ b/src/components/flex/flex_grid.tsx @@ -6,13 +6,23 @@ * Side Public License, v 1. */ -import React, { HTMLAttributes, ReactNode, FunctionComponent } from 'react'; +import React, { + HTMLAttributes, + ReactNode, + FunctionComponent, + ElementType, +} from 'react'; import classNames from 'classnames'; -import { CommonProps, keysOf } from '../common'; +import { CommonProps } from '../common'; -export type FlexGridGutterSize = keyof typeof gutterSizeToClassNameMap; -export type FlexGridColumns = 0 | 1 | 2 | 3 | 4; -export type FlexGridDirection = keyof typeof directionToClassNameMap; +import { useEuiTheme } from '../../services'; +import { euiFlexGridStyles } from './flex_grid.styles'; + +export const DIRECTIONS = ['row', 'column'] as const; +export type FlexGridDirection = typeof DIRECTIONS[number]; + +export const GUTTER_SIZES = ['none', 's', 'm', 'l', 'xl'] as const; +export type FlexGridGutterSize = typeof GUTTER_SIZES[number]; export interface EuiFlexGridProps { /** @@ -20,13 +30,12 @@ export interface EuiFlexGridProps { */ children?: ReactNode; /** - * Number of columns `1-4`, pass `0` for normal display + * Number of columns. Accepts `1-4` */ - columns?: FlexGridColumns; + columns?: 1 | 2 | 3 | 4; // Leave this as inline so the props table correctly parses it /** * Flex layouts default to left-right then top-down (`row`). * Change this prop to `column` to create a top-down then left-right display. - * Only works with column count of `1-4`. */ direction?: FlexGridDirection; /** @@ -34,47 +43,17 @@ export interface EuiFlexGridProps { */ gutterSize?: FlexGridGutterSize; /** - * Force each item to be display block on smaller screens + * Will display each item at full-width on smaller screens */ responsive?: boolean; /** * The tag to render + * @default div */ - component?: keyof JSX.IntrinsicElements; + component?: ElementType; } -const directionToClassNameMap = { - row: null, - column: 'euiFlexGrid--directionColumn', -}; - -export const DIRECTIONS = keysOf(directionToClassNameMap); - -const gutterSizeToClassNameMap = { - none: 'euiFlexGrid--gutterNone', - s: 'euiFlexGrid--gutterSmall', - m: 'euiFlexGrid--gutterMedium', - l: 'euiFlexGrid--gutterLarge', - xl: 'euiFlexGrid--gutterXLarge', -}; - -export const GUTTER_SIZES: FlexGridGutterSize[] = keysOf( - gutterSizeToClassNameMap -); - -const columnsToClassNameMap = { - 0: 'euiFlexGrid--wrap', - 1: 'euiFlexGrid--single', - 2: 'euiFlexGrid--halves', - 3: 'euiFlexGrid--thirds', - 4: 'euiFlexGrid--fourths', -}; - -export const COLUMNS = Object.keys( - columnsToClassNameMap -).map((columns: string) => parseInt(columns, 10)) as FlexGridColumns[]; - export const EuiFlexGrid: FunctionComponent< CommonProps & HTMLAttributes & EuiFlexGridProps > = ({ @@ -83,24 +62,29 @@ export const EuiFlexGrid: FunctionComponent< gutterSize = 'l', direction = 'row', responsive = true, - columns = 0, + columns = 1, component: Component = 'div', ...rest }) => { - const classes = classNames( - 'euiFlexGrid', - gutterSize ? gutterSizeToClassNameMap[gutterSize] : undefined, - columns != null ? columnsToClassNameMap[columns] : undefined, - direction ? directionToClassNameMap[direction] : undefined, - { - 'euiFlexGrid--responsive': responsive, - }, - className - ); + const gridTemplateRows = + direction === 'column' + ? Math.ceil(React.Children.count(children) / columns) + : 0; + + const euiTheme = useEuiTheme(); + const styles = euiFlexGridStyles(euiTheme, gridTemplateRows); + const cssStyles = [ + styles.euiFlexGrid, + styles.gutterSizes[gutterSize], + styles.direction[direction], + styles.columnCount[columns], + responsive && styles.responsive, + ]; + + const classes = classNames('euiFlexGrid', className); return ( - // @ts-ignore difficult to verify `rest` applies to `Component` - + {children} ); diff --git a/src/components/flex/flex_group.styles.ts b/src/components/flex/flex_group.styles.ts new file mode 100644 index 00000000000..b30bfb6f83e --- /dev/null +++ b/src/components/flex/flex_group.styles.ts @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { css } from '@emotion/react'; +import { euiBreakpoint, logicalCSS } from '../../global_styling'; +import { UseEuiTheme } from '../../services'; + +export const euiFlexGroupStyles = (euiThemeContext: UseEuiTheme) => { + const { euiTheme } = euiThemeContext; + return { + euiFlexGroup: css` + display: flex; + align-items: stretch; + flex-grow: 1; // Grow nested flex-groups by default + `, + responsive: css` + ${euiBreakpoint(euiThemeContext, ['xs', 's'])} { + flex-wrap: wrap; + + .euiFlexItem { + ${logicalCSS('width', '100%')} + flex-basis: 100%; + } + } + `, + wrap: css` + flex-wrap: wrap; + `, + gutterSizes: { + none: css``, + xs: css` + gap: ${euiTheme.size.xs}; + `, + s: css` + gap: ${euiTheme.size.s}; + `, + m: css` + gap: ${euiTheme.size.m}; + `, + l: css` + gap: ${euiTheme.size.l}; + `, + xl: css` + gap: ${euiTheme.size.xxl}; + `, + }, + justifyContent: { + flexStart: css` + justify-content: flex-start; + `, + flexEnd: css` + justify-content: flex-end; + `, + spaceEvenly: css` + justify-content: space-evenly; + `, + spaceBetween: css` + justify-content: space-between; + `, + spaceAround: css` + justify-content: space-around; + `, + center: css` + justify-content: center; + `, + }, + alignItems: { + stretch: css` + align-items: stretch; + `, + flexStart: css` + align-items: flex-start; + `, + flexEnd: css` + align-items: flex-end; + `, + center: css` + align-items: center; + `, + baseline: css` + align-items: baseline; + `, + }, + direction: { + row: css` + flex-direction: row; + `, + rowReverse: css` + flex-direction: row-reverse; + `, + column: css` + flex-direction: column; + `, + columnReverse: css` + flex-direction: column-reverse; + `, + }, + }; +}; diff --git a/src/components/flex/flex_group.test.tsx b/src/components/flex/flex_group.test.tsx index 2ce6a7a274d..4f78099e5e0 100644 --- a/src/components/flex/flex_group.test.tsx +++ b/src/components/flex/flex_group.test.tsx @@ -13,6 +13,7 @@ import { startThrowingReactWarnings, stopThrowingReactWarnings, } from '../../test'; +import { shouldRenderCustomStyles } from '../../test/internal'; import { EuiFlexGroup, @@ -26,6 +27,8 @@ beforeAll(startThrowingReactWarnings); afterAll(stopThrowingReactWarnings); describe('EuiFlexGroup', () => { + shouldRenderCustomStyles(); + test('is rendered', () => { const component = render( diff --git a/src/components/flex/flex_group.tsx b/src/components/flex/flex_group.tsx index 6e8289659ba..7c450f4fae2 100644 --- a/src/components/flex/flex_group.tsx +++ b/src/components/flex/flex_group.tsx @@ -8,13 +8,47 @@ import React, { HTMLAttributes, Ref, forwardRef } from 'react'; import classNames from 'classnames'; -import { CommonProps, keysOf } from '../common'; +import { CommonProps } from '../common'; -export type FlexGroupAlignItems = keyof typeof alignItemsToClassNameMap; -export type FlexGroupComponentType = 'div' | 'span'; -export type FlexGroupDirection = keyof typeof directionToClassNameMap; -export type FlexGroupGutterSize = keyof typeof gutterSizeToClassNameMap; -export type FlexGroupJustifyContent = keyof typeof justifyContentToClassNameMap; +import { useEuiTheme } from '../../services'; +import { euiFlexGroupStyles } from './flex_group.styles'; + +export const GUTTER_SIZES = ['none', 'xs', 's', 'm', 'l', 'xl'] as const; +export type EuiFlexGroupGutterSize = typeof GUTTER_SIZES[number]; + +export const ALIGN_ITEMS = [ + 'stretch', + 'flexStart', + 'flexEnd', + 'center', + 'baseline', +] as const; +export type FlexGroupAlignItems = typeof ALIGN_ITEMS[number]; + +export const JUSTIFY_CONTENTS = [ + 'flexStart', + 'flexEnd', + 'center', + 'spaceBetween', + 'spaceAround', + 'spaceEvenly', +] as const; +type FlexGroupJustifyContent = typeof JUSTIFY_CONTENTS[number]; + +export const DIRECTIONS = [ + 'row', + 'rowReverse', + 'column', + 'columnReverse', +] as const; +type FlexGroupDirection = typeof DIRECTIONS[number]; + +type FlexGroupComponentType = 'div' | 'span'; +const isValidElement = ( + component: string +): component is FlexGroupComponentType => { + return ['div', 'span'].includes(component); +}; export interface EuiFlexGroupProps extends CommonProps, @@ -22,60 +56,12 @@ export interface EuiFlexGroupProps alignItems?: FlexGroupAlignItems; component?: FlexGroupComponentType; direction?: FlexGroupDirection; - gutterSize?: FlexGroupGutterSize; + gutterSize?: EuiFlexGroupGutterSize; justifyContent?: FlexGroupJustifyContent; responsive?: boolean; wrap?: boolean; } -const gutterSizeToClassNameMap = { - none: null, - xs: 'euiFlexGroup--gutterExtraSmall', - s: 'euiFlexGroup--gutterSmall', - m: 'euiFlexGroup--gutterMedium', - l: 'euiFlexGroup--gutterLarge', - xl: 'euiFlexGroup--gutterExtraLarge', -}; - -export const GUTTER_SIZES = keysOf(gutterSizeToClassNameMap); -export type EuiFlexGroupGutterSize = keyof typeof gutterSizeToClassNameMap; - -const alignItemsToClassNameMap = { - stretch: null, - flexStart: 'euiFlexGroup--alignItemsFlexStart', - flexEnd: 'euiFlexGroup--alignItemsFlexEnd', - center: 'euiFlexGroup--alignItemsCenter', - baseline: 'euiFlexGroup--alignItemsBaseline', -}; - -export const ALIGN_ITEMS = keysOf(alignItemsToClassNameMap); - -const justifyContentToClassNameMap = { - flexStart: null, - flexEnd: 'euiFlexGroup--justifyContentFlexEnd', - center: 'euiFlexGroup--justifyContentCenter', - spaceBetween: 'euiFlexGroup--justifyContentSpaceBetween', - spaceAround: 'euiFlexGroup--justifyContentSpaceAround', - spaceEvenly: 'euiFlexGroup--justifyContentSpaceEvenly', -}; - -export const JUSTIFY_CONTENTS = keysOf(justifyContentToClassNameMap); - -const directionToClassNameMap = { - row: 'euiFlexGroup--directionRow', - rowReverse: 'euiFlexGroup--directionRowReverse', - column: 'euiFlexGroup--directionColumn', - columnReverse: 'euiFlexGroup--directionColumnReverse', -}; - -export const DIRECTIONS = keysOf(directionToClassNameMap); - -const isValidElement = ( - component: string -): component is FlexGroupComponentType => { - return ['div', 'span'].includes(component); -}; - export const EuiFlexGroup = forwardRef< HTMLDivElement | HTMLSpanElement, EuiFlexGroupProps @@ -95,18 +81,19 @@ export const EuiFlexGroup = forwardRef< }, ref: Ref | Ref ) => { - const classes = classNames( - 'euiFlexGroup', - gutterSizeToClassNameMap[gutterSize as FlexGroupGutterSize], - alignItemsToClassNameMap[alignItems as FlexGroupAlignItems], - justifyContentToClassNameMap[justifyContent as FlexGroupJustifyContent], - directionToClassNameMap[direction as FlexGroupDirection], - { - 'euiFlexGroup--responsive': responsive, - 'euiFlexGroup--wrap': wrap, - }, - className - ); + const euiTheme = useEuiTheme(); + const styles = euiFlexGroupStyles(euiTheme); + const cssStyles = [ + styles.euiFlexGroup, + responsive && !direction.includes('column') && styles.responsive, + wrap && styles.wrap, + styles.gutterSizes[gutterSize], + styles.justifyContent[justifyContent], + styles.alignItems[alignItems], + styles.direction[direction], + ]; + + const classes = classNames('euiFlexGroup', className); if (!isValidElement(component)) { throw new Error( @@ -116,17 +103,19 @@ export const EuiFlexGroup = forwardRef< return component === 'span' ? ( } - {...(rest as HTMLAttributes)} + {...rest} > {children} ) : (
} - {...(rest as HTMLAttributes)} + {...rest} > {children}
diff --git a/src/components/flex/flex_item.styles.ts b/src/components/flex/flex_item.styles.ts new file mode 100644 index 00000000000..ffa2ae5e370 --- /dev/null +++ b/src/components/flex/flex_item.styles.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { css } from '@emotion/react'; + +export const euiFlexItemStyles = () => { + return { + // 1. Allow EuiPanels to expand to fill the item. + euiFlexItem: css` + display: flex; /* 1 */ + flex-direction: column; /* 1 */ + `, + growZero: css` + flex-grow: 0; + flex-basis: auto; + `, + grow: css` + flex-basis: 0%; + `, + growSizes: { + '1': css` + flex-grow: 1; + `, + '2': css` + flex-grow: 2; + `, + '3': css` + flex-grow: 3; + `, + '4': css` + flex-grow: 4; + `, + '5': css` + flex-grow: 5; + `, + '6': css` + flex-grow: 6; + `, + '7': css` + flex-grow: 7; + `, + '8': css` + flex-grow: 8; + `, + '9': css` + flex-grow: 9; + `, + '10': css` + flex-grow: 10; + `, + }, + }; +}; diff --git a/src/components/flex/flex_item.test.tsx b/src/components/flex/flex_item.test.tsx index 26d53e01122..88d1f47ac29 100644 --- a/src/components/flex/flex_item.test.tsx +++ b/src/components/flex/flex_item.test.tsx @@ -13,21 +13,30 @@ import { startThrowingReactWarnings, stopThrowingReactWarnings, } from '../../test'; +import { shouldRenderCustomStyles } from '../../test/internal'; -import { EuiFlexItem, GROW_SIZES } from './flex_item'; +import { EuiFlexItem, VALID_GROW_VALUES } from './flex_item'; beforeAll(startThrowingReactWarnings); afterAll(stopThrowingReactWarnings); describe('EuiFlexItem', () => { + shouldRenderCustomStyles(); + test('is rendered', () => { const component = render(); expect(component).toMatchSnapshot(); }); + test('component', () => { + const component = render(); + + expect(component).toMatchSnapshot(); + }); + describe('grow', () => { - GROW_SIZES.concat([true, false]).forEach((value) => { + VALID_GROW_VALUES.forEach((value) => { test(`${value} is rendered`, () => { const component = render(); diff --git a/src/components/flex/flex_item.tsx b/src/components/flex/flex_item.tsx index b507155f7b8..4734ae973d8 100644 --- a/src/components/flex/flex_item.tsx +++ b/src/components/flex/flex_item.tsx @@ -6,32 +6,20 @@ * Side Public License, v 1. */ -import React, { HTMLAttributes, FunctionComponent } from 'react'; +import React, { HTMLAttributes, FunctionComponent, ElementType } from 'react'; import classNames from 'classnames'; import { CommonProps } from '../common'; -export type FlexItemGrowSize = - | 1 - | 2 - | 3 - | 4 - | 5 - | 6 - | 7 - | 8 - | 9 - | 10 - | true - | false - | null; +import { euiFlexItemStyles } from './flex_item.styles'; export interface EuiFlexItemProps { - grow?: FlexItemGrowSize; - component?: keyof JSX.IntrinsicElements; + grow?: boolean | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | null; // Leave this as an inline string enum so the props table properly parses it + /** + * @default div + */ + component?: ElementType; } -export const GROW_SIZES: FlexItemGrowSize[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - export const EuiFlexItem: FunctionComponent< CommonProps & HTMLAttributes & @@ -45,30 +33,46 @@ export const EuiFlexItem: FunctionComponent< }) => { validateGrowValue(grow); - const classes = classNames( - 'euiFlexItem', - { - 'euiFlexItem--flexGrowZero': !grow, - [`euiFlexItem--flexGrow${grow}`]: - typeof grow === 'number' ? GROW_SIZES.indexOf(grow) >= 0 : undefined, - }, - className - ); + const styles = euiFlexItemStyles(); + const cssStyles = [ + styles.euiFlexItem, + !grow ? styles.growZero : styles.grow, + grow && + (typeof grow === 'number' + ? styles.growSizes[grow] + : styles.growSizes['1']), + ]; + + const classes = classNames('euiFlexItem', className); return ( - // @ts-ignore difficult to verify `rest` applies to `Component` - + {children} ); }; +export const VALID_GROW_VALUES = [ + null, + undefined, + true, + false, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, +] as const; function validateGrowValue(value: EuiFlexItemProps['grow']) { - const validValues = [null, undefined, true, false, ...GROW_SIZES]; - - if (validValues.indexOf(value) === -1) { + if (VALID_GROW_VALUES.indexOf(value) === -1) { throw new Error( - `Prop \`grow\` passed to \`EuiFlexItem\` must be a boolean or an integer between 1 and 10, received \`${value}\`` + `Prop \`grow\` passed to \`EuiFlexItem\` must be a boolean or an integer between 0 and 10, received \`${value}\`` ); } } diff --git a/src/components/header/header_alert/__snapshots__/header_alert.test.tsx.snap b/src/components/header/header_alert/__snapshots__/header_alert.test.tsx.snap index dfeac591855..91ceedcefc1 100644 --- a/src/components/header/header_alert/__snapshots__/header_alert.test.tsx.snap +++ b/src/components/header/header_alert/__snapshots__/header_alert.test.tsx.snap @@ -8,10 +8,10 @@ exports[`EuiHeaderAlert is rendered 1`] = ` data-test-subj="test subject string" >
@@ -28,10 +28,10 @@ exports[`EuiHealth props color #000000 is rendered 1`] = ` class="euiHealth emotion-euiHealth-s" >
@@ -50,10 +50,10 @@ exports[`EuiHealth props color accent is rendered 1`] = ` class="euiHealth emotion-euiHealth-s" >
@@ -72,10 +72,10 @@ exports[`EuiHealth props color danger is rendered 1`] = ` class="euiHealth emotion-euiHealth-s" >
@@ -94,10 +94,10 @@ exports[`EuiHealth props color default is rendered 1`] = ` class="euiHealth emotion-euiHealth-s" >
@@ -116,10 +116,10 @@ exports[`EuiHealth props color ghost is rendered 1`] = ` class="euiHealth emotion-euiHealth-s" >
@@ -138,10 +138,10 @@ exports[`EuiHealth props color inherit is rendered 1`] = ` class="euiHealth emotion-euiHealth-s" >
@@ -160,10 +160,10 @@ exports[`EuiHealth props color primary is rendered 1`] = ` class="euiHealth emotion-euiHealth-s" >
@@ -182,10 +182,10 @@ exports[`EuiHealth props color subdued is rendered 1`] = ` class="euiHealth emotion-euiHealth-s" >
@@ -204,10 +204,10 @@ exports[`EuiHealth props color success is rendered 1`] = ` class="euiHealth emotion-euiHealth-s" >
@@ -226,10 +226,10 @@ exports[`EuiHealth props color text is rendered 1`] = ` class="euiHealth emotion-euiHealth-s" >
@@ -248,10 +248,10 @@ exports[`EuiHealth props color warning is rendered 1`] = ` class="euiHealth emotion-euiHealth-s" >
@@ -270,10 +270,10 @@ exports[`EuiHealth props textSize inherit is rendered 1`] = ` class="euiHealth emotion-euiHealth-inherit" >
@@ -292,10 +292,10 @@ exports[`EuiHealth props textSize m is rendered 1`] = ` class="euiHealth emotion-euiHealth-m" >
@@ -314,10 +314,10 @@ exports[`EuiHealth props textSize s is rendered 1`] = ` class="euiHealth emotion-euiHealth-s" >
@@ -336,10 +336,10 @@ exports[`EuiHealth props textSize xs is rendered 1`] = ` class="euiHealth emotion-euiHealth-xs" >
diff --git a/src/components/index.scss b/src/components/index.scss index 3316643d97a..42e032fbcce 100644 --- a/src/components/index.scss +++ b/src/components/index.scss @@ -14,7 +14,6 @@ @import 'drag_and_drop/index'; @import 'empty_prompt/index'; @import 'filter_group/index'; -@import 'flex/index'; @import 'form/index'; @import 'header/index'; @import 'key_pad_menu/index'; diff --git a/src/components/page/__snapshots__/page_template.test.tsx.snap b/src/components/page/__snapshots__/page_template.test.tsx.snap index 640e71714dc..966d9c0d929 100644 --- a/src/components/page/__snapshots__/page_template.test.tsx.snap +++ b/src/components/page/__snapshots__/page_template.test.tsx.snap @@ -16,10 +16,10 @@ exports[`EuiPageTemplate_Deprecated fullHeight is rendered with noscroll 1`] = ` class="euiPageContentBody euiPageContentBody--paddingLarge euiPageContentBody--restrictWidth-default eui-fullHeight" >
@@ -44,10 +44,10 @@ exports[`EuiPageTemplate_Deprecated fullHeight is rendered with true 1`] = ` class="euiPageContentBody euiPageContentBody--paddingLarge euiPageContentBody--restrictWidth-default eui-fullHeight" >
@@ -244,10 +244,10 @@ exports[`EuiPageTemplate_Deprecated template centeredBody is rendered with pageH class="euiPageHeaderContent emotion-euiPageHeaderContent-border-l" >
@@ -520,10 +520,10 @@ exports[`EuiPageTemplate_Deprecated template centeredContent is rendered with pa style="max-width:1200px" >
@@ -777,10 +777,10 @@ exports[`EuiPageTemplate_Deprecated template default is rendered with pageHeader style="max-width:1200px" >
@@ -1026,10 +1026,10 @@ exports[`EuiPageTemplate_Deprecated template empty is rendered with pageHeader 1 class="euiPageHeaderContent emotion-euiPageHeaderContent-border-l" >
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 0e3dd0999c5..814cb118f90 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 @@ -26,10 +26,10 @@ exports[`EuiPageHeader props alignItems bottom is rendered 1`] = ` class="euiPageHeaderContent emotion-euiPageHeaderContent" >