diff --git a/changelogs/CHANGELOG_2024.md b/changelogs/CHANGELOG_2024.md index b3ef1aa7920..c296edeff6a 100644 --- a/changelogs/CHANGELOG_2024.md +++ b/changelogs/CHANGELOG_2024.md @@ -1,3 +1,28 @@ +## [`v93.0.0`](https://github.com/elastic/eui/releases/v93.0.0) + +**Bug fixes** + +- Fixed `EuiTextTruncate` component to clean up timer from side effect on unmount ([#7495](https://github.com/elastic/eui/pull/7495)) + +**Breaking changes** + +- Removed deprecated `anchorClassName` prop from `EuiPopover`. Use `className` instead ([#7488](https://github.com/elastic/eui/pull/7488)) +- Removed deprecated `buttonRef` prop from `EuiPopover`. Use `popoverRef` instead ([#7488](https://github.com/elastic/eui/pull/7488)) +- Removed deprecated `toolTipTitle` and `toolTipPosition` props from `EuiContextMenuItem`. Use `toolTipProps.title` and `toolTipProps.position` instead ([#7489](https://github.com/elastic/eui/pull/7489)) +- Removed deprecated internal `setSelection` ref method from `EuiInMemoryTable` and `EuiBasicTable`. Use the new controlled `selection.selected` prop API instead. ([#7491](https://github.com/elastic/eui/pull/7491)) +- `EuiTourStep`'s `className` and `style` props now apply to the anchoring element instead of to the popover panel, to match `EuiPopover` behavior. ([#7497](https://github.com/elastic/eui/pull/7497)) + - Convert your existing usages to `panelClassName` and `panelStyle` respectively instead. + +**Performance** + +- Improved the amount of recomputed styles being generated by `EuiCode` and `EuiCodeBlock` ([#7486](https://github.com/elastic/eui/pull/7486)) + +**CSS-in-JS conversions** + +- Converted `EuiSearchBar` to Emotion ([#7490](https://github.com/elastic/eui/pull/7490)) +- Converted `EuiEmptyPrompt` to Emotion ([#7494](https://github.com/elastic/eui/pull/7494)) +- Added `euiBorderColor` and `useEuiBorderColorCSS` style utilities ([#7494](https://github.com/elastic/eui/pull/7494)) + ## [`v92.2.1`](https://github.com/elastic/eui/releases/v92.2.1) **Bug fixes** diff --git a/changelogs/upcoming/7486.md b/changelogs/upcoming/7486.md deleted file mode 100644 index c7a8b913065..00000000000 --- a/changelogs/upcoming/7486.md +++ /dev/null @@ -1,3 +0,0 @@ -**Performance** - -- Improved the amount of recomputed styles being generated by `EuiCode` and `EuiCodeBlock` diff --git a/changelogs/upcoming/7488.md b/changelogs/upcoming/7488.md deleted file mode 100644 index f5daa51b6ea..00000000000 --- a/changelogs/upcoming/7488.md +++ /dev/null @@ -1,4 +0,0 @@ -**Breaking changes** - -- Removed deprecated `anchorClassName` prop from `EuiPopover`. Use `className` instead -- Removed deprecated `buttonRef` prop from `EuiPopover`. Use `popoverRef` instead diff --git a/changelogs/upcoming/7489.md b/changelogs/upcoming/7489.md deleted file mode 100644 index 7239dc5212d..00000000000 --- a/changelogs/upcoming/7489.md +++ /dev/null @@ -1,3 +0,0 @@ -**Breaking changes** - -- Removed deprecated `toolTipTitle` and `toolTipPosition` props from `EuiContextMenuItem`. Use `toolTipProps.title` and `toolTilProps.position` instead diff --git a/changelogs/upcoming/7495.md b/changelogs/upcoming/7495.md deleted file mode 100644 index edfe954e183..00000000000 --- a/changelogs/upcoming/7495.md +++ /dev/null @@ -1,3 +0,0 @@ -**Bug fixes** - -- Fixed `EuiTextTruncate` component to clean up timer from side effect on unmount \ No newline at end of file diff --git a/changelogs/upcoming/7496.md b/changelogs/upcoming/7496.md new file mode 100644 index 00000000000..fcb4b3e8f93 --- /dev/null +++ b/changelogs/upcoming/7496.md @@ -0,0 +1,5 @@ +- Updated `EuiHighlight` to accept an array of `search` strings, which allows highlighting multiple, separate words within its children. This new type and behavior *only* works if `highlightAll` is also set to true. + +**Bug fixes** + +- Fixed `EuiHighlight` to not parse `search` strings as regexes diff --git a/changelogs/upcoming/7502.md b/changelogs/upcoming/7502.md new file mode 100644 index 00000000000..f4ff0ca7b86 --- /dev/null +++ b/changelogs/upcoming/7502.md @@ -0,0 +1 @@ +- Updated `EuiSuperDatePicker` with a new `canRoundRelativeUnits` prop, which defaults to true (current behavior). To preserve displaying the unit that users select for relative time, set this to false. diff --git a/i18ntokens.json b/i18ntokens.json index 18311b8b370..26bbf5073e1 100644 --- a/i18ntokens.json +++ b/i18ntokens.json @@ -41,14 +41,14 @@ "highlighting": "string", "loc": { "start": { - "line": 664, + "line": 657, "column": 10, - "index": 17148 + "index": 16964 }, "end": { - "line": 668, + "line": 661, "column": 12, - "index": 17351 + "index": 17167 } }, "filepath": "src/components/basic_table/basic_table.tsx" @@ -59,14 +59,14 @@ "highlighting": "string", "loc": { "start": { - "line": 677, + "line": 670, "column": 12, - "index": 17548 + "index": 17364 }, "end": { - "line": 681, + "line": 674, "column": 14, - "index": 17827 + "index": 17643 } }, "filepath": "src/components/basic_table/basic_table.tsx" @@ -77,14 +77,14 @@ "highlighting": "string", "loc": { "start": { - "line": 685, + "line": 678, "column": 12, - "index": 17899 + "index": 17715 }, "end": { - "line": 689, + "line": 682, "column": 14, - "index": 18139 + "index": 17955 } }, "filepath": "src/components/basic_table/basic_table.tsx" @@ -95,14 +95,14 @@ "highlighting": "string", "loc": { "start": { - "line": 694, + "line": 687, "column": 10, - "index": 18215 + "index": 18031 }, "end": { - "line": 698, + "line": 691, "column": 12, - "index": 18399 + "index": 18215 } }, "filepath": "src/components/basic_table/basic_table.tsx" @@ -113,14 +113,14 @@ "highlighting": "string", "loc": { "start": { - "line": 740, + "line": 733, "column": 6, - "index": 19397 + "index": 19213 }, "end": { - "line": 740, + "line": 733, "column": 77, - "index": 19468 + "index": 19284 } }, "filepath": "src/components/basic_table/basic_table.tsx" @@ -131,14 +131,14 @@ "highlighting": "string", "loc": { "start": { - "line": 1136, + "line": 1129, "column": 8, - "index": 31279 + "index": 31095 }, "end": { - "line": 1136, + "line": 1129, "column": 79, - "index": 31350 + "index": 31166 } }, "filepath": "src/components/basic_table/basic_table.tsx" @@ -149,14 +149,14 @@ "highlighting": "string", "loc": { "start": { - "line": 1361, + "line": 1354, "column": 8, - "index": 37925 + "index": 37741 }, "end": { - "line": 1365, + "line": 1358, "column": 9, - "index": 38084 + "index": 37900 } }, "filepath": "src/components/basic_table/basic_table.tsx" @@ -437,14 +437,14 @@ "highlighting": "string", "loc": { "start": { - "line": 55, + "line": 56, "column": 49, - "index": 1750 + "index": 1816 }, "end": { - "line": 61, + "line": 62, "column": 3, - "index": 1905 + "index": 1971 } }, "filepath": "src/components/code/code_block_full_screen.tsx" @@ -455,14 +455,14 @@ "highlighting": "string", "loc": { "start": { - "line": 55, + "line": 56, "column": 49, - "index": 1750 + "index": 1816 }, "end": { - "line": 61, + "line": 62, "column": 3, - "index": 1905 + "index": 1971 } }, "filepath": "src/components/code/code_block_full_screen.tsx" @@ -5729,14 +5729,14 @@ "highlighting": "string", "loc": { "start": { - "line": 695, + "line": 682, "column": 16, - "index": 20476 + "index": 20150 }, "end": { - "line": 698, + "line": 685, "column": 18, - "index": 20670 + "index": 20344 } }, "filepath": "src/components/popover/popover.tsx" @@ -6767,6 +6767,60 @@ }, "filepath": "src/components/toast/toast.tsx" }, + { + "token": "euiTourFooter.endTour", + "defString": "End tour", + "highlighting": "string", + "loc": { + "start": { + "line": 95, + "column": 14, + "index": 2888 + }, + "end": { + "line": 102, + "column": 15, + "index": 3156 + } + }, + "filepath": "src/components/tour/_tour_footer.tsx" + }, + { + "token": "euiTourFooter.skipTour", + "defString": "Skip tour", + "highlighting": "string", + "loc": { + "start": { + "line": 95, + "column": 14, + "index": 2888 + }, + "end": { + "line": 102, + "column": 15, + "index": 3156 + } + }, + "filepath": "src/components/tour/_tour_footer.tsx" + }, + { + "token": "euiTourFooter.closeTour", + "defString": "Close tour", + "highlighting": "string", + "loc": { + "start": { + "line": 95, + "column": 14, + "index": 2888 + }, + "end": { + "line": 102, + "column": 15, + "index": 3156 + } + }, + "filepath": "src/components/tour/_tour_footer.tsx" + }, { "token": "euiTourStepIndicator.isActive", "defString": "active", @@ -6839,60 +6893,6 @@ }, "filepath": "src/components/tour/tour_step_indicator.tsx" }, - { - "token": "euiTourStep.endTour", - "defString": "End tour", - "highlighting": "string", - "loc": { - "start": { - "line": 256, - "column": 10, - "index": 6776 - }, - "end": { - "line": 263, - "column": 11, - "index": 7010 - } - }, - "filepath": "src/components/tour/tour_step.tsx" - }, - { - "token": "euiTourStep.skipTour", - "defString": "Skip tour", - "highlighting": "string", - "loc": { - "start": { - "line": 256, - "column": 10, - "index": 6776 - }, - "end": { - "line": 263, - "column": 11, - "index": 7010 - } - }, - "filepath": "src/components/tour/tour_step.tsx" - }, - { - "token": "euiTourStep.closeTour", - "defString": "Close tour", - "highlighting": "string", - "loc": { - "start": { - "line": 256, - "column": 10, - "index": 6776 - }, - "end": { - "line": 263, - "column": 11, - "index": 7010 - } - }, - "filepath": "src/components/tour/tour_step.tsx" - }, { "token": "euiTreeView.listNavigationInstructions", "defString": "You can quickly navigate this list using arrow keys.", diff --git a/i18ntokens_changelog.json b/i18ntokens_changelog.json index 4ec98e7f7fc..37142bb6810 100644 --- a/i18ntokens_changelog.json +++ b/i18ntokens_changelog.json @@ -1,4 +1,36 @@ [ + { + "version": "93.0.0", + "changes": [ + { + "token": "euiTourStep.endTour", + "changeType": "deleted" + }, + { + "token": "euiTourStep.skipTour", + "changeType": "deleted" + }, + { + "token": "euiTourStep.closeTour", + "changeType": "deleted" + }, + { + "token": "euiTourFooter.endTour", + "changeType": "added", + "value": "End tour" + }, + { + "token": "euiTourFooter.skipTour", + "changeType": "added", + "value": "Skip tour" + }, + { + "token": "euiTourFooter.closeTour", + "changeType": "added", + "value": "Close tour" + } + ] + }, { "version": "92.1.0", "changes": [ diff --git a/package.json b/package.json index e6416149d9c..532c32ece3c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@elastic/eui", "description": "Elastic UI Component Library", - "version": "92.2.1", + "version": "93.0.0", "license": "SEE LICENSE IN LICENSE.txt", "main": "lib", "module": "es", diff --git a/scripts/eslint-plugin/forward_ref_display_name.js b/scripts/eslint-plugin/forward_ref_display_name.js index c7e9b703fde..8b2ff085110 100644 --- a/scripts/eslint-plugin/forward_ref_display_name.js +++ b/scripts/eslint-plugin/forward_ref_display_name.js @@ -2,11 +2,12 @@ module.exports = { meta: { type: 'problem', docs: { - description: 'Enforce display name to forwardRef components', + description: + 'Enforce display name on components wrapped in forwardRef & memo', }, }, - create: function(context) { - const forwardRefUsages = []; + create: function (context) { + const usagesToCheck = []; const displayNameUsages = []; return { VariableDeclarator(node) { @@ -16,14 +17,20 @@ module.exports = { node.init.callee.type === 'MemberExpression' ) { if ( - node.init.callee.property && - node.init.callee.property.name === 'forwardRef' + node.init.callee.property?.name === 'forwardRef' || + node.init.callee.property?.name === 'memo' ) { - forwardRefUsages.push(node.id); + usagesToCheck.push({ + id: node.id, + type: node.init.callee.property.name, + }); } } - if (node.init.callee && node.init.callee.name === 'forwardRef') { - forwardRefUsages.push(node.id); + if ( + node.init.callee?.name === 'forwardRef' || + node.init.callee?.name === 'memo' + ) { + usagesToCheck.push({ id: node.id, type: node.init.callee.name }); } } }, @@ -38,11 +45,11 @@ module.exports = { } }, 'Program:exit'() { - forwardRefUsages.forEach(identifier => { - if (!isDisplayNameUsed(identifier)) { + usagesToCheck.forEach(({ id, type }) => { + if (!isDisplayNameUsed(id)) { context.report({ - node: identifier, - message: 'Forward ref components must use a display name', + node: id, + message: `Components wrapped in React.${type} must set a manual displayName`, }); } }); @@ -50,7 +57,7 @@ module.exports = { }; function isDisplayNameUsed(identifier) { const node = displayNameUsages.find( - displayName => displayName.name === identifier.name + (displayName) => displayName.name === identifier.name ); return !!node; } diff --git a/scripts/eslint-plugin/forward_ref_display_name.test.js b/scripts/eslint-plugin/forward_ref_display_name.test.js index ca8403b263e..c9f1ff8e950 100644 --- a/scripts/eslint-plugin/forward_ref_display_name.test.js +++ b/scripts/eslint-plugin/forward_ref_display_name.test.js @@ -8,6 +8,9 @@ const ruleTester = new RuleTester({ const valid = [ `const Component = React.forwardRef(() => {}) Component.displayName = "EuiBadgeGroup" +`, + `const Component = React.memo(() => {}) + Component.displayName = "EuiHighlight" `, ]; @@ -16,7 +19,17 @@ const invalid = [ code: 'const Component = React.forwardRef(() => {})', errors: [ { - message: 'Forward ref components must use a display name', + message: + 'Components wrapped in React.forwardRef must set a manual displayName', + }, + ], + }, + { + code: 'const Component = React.memo(() => {})', + errors: [ + { + message: + 'Components wrapped in React.memo must set a manual displayName', }, ], }, diff --git a/scripts/jest/setup/throw_on_console_error.js b/scripts/jest/setup/throw_on_console_error.js index b0c208cd5b8..534a7e36a8b 100644 --- a/scripts/jest/setup/throw_on_console_error.js +++ b/scripts/jest/setup/throw_on_console_error.js @@ -37,18 +37,5 @@ console.error = (message, ...rest) => { return; } - // Print React validateDOMNesting warning as a console.warn instead - // of throwing an error. - // TODO: Remove when edge-case DOM nesting is fixed in all components - if ( - typeof message === 'string' && - message.startsWith( - 'Warning: validateDOMNesting(...): %s cannot appear as a child of <%s>' - ) - ) { - console.warn(message, ...rest); - return; - } - throw new Error(format(message, ...rest)); }; diff --git a/src-docs/src/components/guide_page/versions.json b/src-docs/src/components/guide_page/versions.json index a94f6f4ae09..d685571e72a 100644 --- a/src-docs/src/components/guide_page/versions.json +++ b/src-docs/src/components/guide_page/versions.json @@ -1,5 +1,6 @@ { "euiVersions": [ + "93.0.0", "92.2.0", "92.1.1", "92.1.0", diff --git a/src-docs/src/views/highlight_and_mark/highlight.js b/src-docs/src/views/highlight_and_mark/highlight.js deleted file mode 100644 index 0e4e4713d0e..00000000000 --- a/src-docs/src/views/highlight_and_mark/highlight.js +++ /dev/null @@ -1,45 +0,0 @@ -import React, { Fragment, useState } from 'react'; - -import { - EuiHighlight, - EuiFieldSearch, - EuiFormRow, - EuiSpacer, - EuiSwitch, -} from '../../../../src/components'; - -export default () => { - const [searchValue, setSearchValue] = useState('jumped over'); - const [isHighlightAll, setHighlightAll] = useState(false); - - const onSearchChange = (e) => { - setSearchValue(e.target.value); - }; - const changeHighlightAll = (e) => { - setHighlightAll(e.target.checked); - }; - - return ( - - - { - onSearchChange(e); - }} - /> - - - - changeHighlightAll(e)} - /> - - - The quick brown fox jumped over the lazy dog - - - ); -}; diff --git a/src-docs/src/views/highlight_and_mark/highlight.tsx b/src-docs/src/views/highlight_and_mark/highlight.tsx new file mode 100644 index 00000000000..50b06f78fcc --- /dev/null +++ b/src-docs/src/views/highlight_and_mark/highlight.tsx @@ -0,0 +1,64 @@ +import React, { useState, useMemo } from 'react'; + +import { + EuiHighlight, + EuiFieldSearch, + EuiFormRow, + EuiSpacer, + EuiSwitch, + EuiFlexGroup, +} from '../../../../src/components'; + +export default () => { + const [searchInput, setSearchInput] = useState('jumped over'); + const [isHighlightAll, setHighlightAll] = useState(false); + const [searchMultiple, setSearchMultiple] = useState(false); + const [caseSensitive, setCaseSensitive] = useState(false); + + const searchValues = useMemo(() => { + return searchMultiple && isHighlightAll + ? searchInput.split(' ') + : searchInput; + }, [searchMultiple, searchInput, isHighlightAll]); + + return ( + <> + + setCaseSensitive(e.target.checked)} + /> + setHighlightAll(e.target.checked)} + /> + {isHighlightAll && ( + setSearchMultiple(e.target.checked)} + /> + )} + + + + + setSearchInput(e.target.value)} + /> + + + + + The quick brown fox jumped over the lazy dog + + + ); +}; diff --git a/src-docs/src/views/highlight_and_mark/mark.js b/src-docs/src/views/highlight_and_mark/mark.tsx similarity index 70% rename from src-docs/src/views/highlight_and_mark/mark.js rename to src-docs/src/views/highlight_and_mark/mark.tsx index 0f3d72a1bd9..9d903a9f049 100644 --- a/src-docs/src/views/highlight_and_mark/mark.js +++ b/src-docs/src/views/highlight_and_mark/mark.tsx @@ -1,11 +1,11 @@ -import React, { Fragment } from 'react'; +import React from 'react'; import { EuiMark } from '../../../../src/components'; export default () => { return ( - + <> The quick brown fox jumped over the lazy dog - + ); }; diff --git a/src-docs/src/views/highlight_and_mark/playground.js b/src-docs/src/views/highlight_and_mark/playground.js index 2589f2861e5..0601930da83 100644 --- a/src-docs/src/views/highlight_and_mark/playground.js +++ b/src-docs/src/views/highlight_and_mark/playground.js @@ -15,7 +15,11 @@ export const highlightConfig = () => { value: 'The quick brown fox jumped over the lazy dog', }; - propsToUse.search.value = 'quick'; + propsToUse.search = { + ...propsToUse.search, + type: PropTypes.String, + value: 'quick', + }; return { config: { diff --git a/src-docs/src/views/super_date_picker/playground.js b/src-docs/src/views/super_date_picker/playground.js index 681e5423076..610014ac214 100644 --- a/src-docs/src/views/super_date_picker/playground.js +++ b/src-docs/src/views/super_date_picker/playground.js @@ -37,6 +37,13 @@ export const superDatePickerConfig = () => { value: true, }; + propsToUse.canRoundRelativeUnits = { + ...propsToUse.canRoundRelativeUnits, + type: PropTypes.Boolean, + defaultValue: true, + value: true, + }; + propsToUse.locale = { ...propsToUse.locale, type: PropTypes.String, diff --git a/src-docs/src/views/theme/color/_contrast_js.tsx b/src-docs/src/views/theme/color/_contrast_js.tsx index e2bcb001eb8..ca98bc014b7 100644 --- a/src-docs/src/views/theme/color/_contrast_js.tsx +++ b/src-docs/src/views/theme/color/_contrast_js.tsx @@ -56,7 +56,12 @@ export const ColorSectionJS: FunctionComponent = ({ }} > - + {showTextVariants && colorIsCore(colorValue) && ( )} diff --git a/src-docs/src/views/theme/color/_contrast_sass.tsx b/src-docs/src/views/theme/color/_contrast_sass.tsx index cb3836195f0..ade9ce9dde9 100644 --- a/src-docs/src/views/theme/color/_contrast_sass.tsx +++ b/src-docs/src/views/theme/color/_contrast_sass.tsx @@ -83,7 +83,12 @@ export const ColorSectionSass: FunctionComponent = ({ style={{ background: matchPanelColor ? palette[color] : undefined }} > - + {showTextVariants && colorIsCore(color) && ( )} diff --git a/src-docs/src/views/theme/color/_contrast_slider.tsx b/src-docs/src/views/theme/color/_contrast_slider.tsx index d3d959dfaa3..71cdd587c4c 100644 --- a/src-docs/src/views/theme/color/_contrast_slider.tsx +++ b/src-docs/src/views/theme/color/_contrast_slider.tsx @@ -21,7 +21,7 @@ import { ratingAll, } from './_contrast_utilities'; -type ContrastSlider = EuiFlexGroupProps & { +type ContrastSlider = Omit & { contrastValue: EuiRangeProps['value']; showTextVariants: boolean; onChange?: (value: number | string, checked: boolean) => void; diff --git a/src-docs/src/views/theme/color/contrast.tsx b/src-docs/src/views/theme/color/contrast.tsx index b4b3efa0da3..e9a96a4c286 100644 --- a/src-docs/src/views/theme/color/contrast.tsx +++ b/src-docs/src/views/theme/color/contrast.tsx @@ -1,4 +1,4 @@ -import React, { useState, useContext } from 'react'; +import React, { useState, useContext, useCallback } from 'react'; import { ThemeContext } from '../../../components/with_theme'; import { @@ -28,7 +28,7 @@ import { _EuiThemeColorsMode } from '../../../../../src/global_styling/variables import { BACKGROUND_COLORS, _EuiBackgroundColor, - useEuiBackgroundColor, + euiBackgroundColor, } from '../../../../../src/global_styling'; import { BUTTON_COLORS, @@ -48,23 +48,22 @@ export const contrastSections = [ const background_colors = BACKGROUND_COLORS.filter( (color) => color !== 'transparent' ); +const backgroundButtons = [ + 'container', + // 'hover', Commenting out for now since contrast can't be calculated on transparent values + 'button', +].map((m) => { + return { + id: m, + label: m, + }; +}); export default () => { const euiTheme = useEuiTheme(); const [showTextVariants, setShowTextVariants] = useState(true); const [contrastValue, setContrastValue] = useState(4.5); - const backgroundButtons = [ - 'container', - // 'hover', Commenting out for now since contrast can't be calculated on transparent values - 'button', - ].map((m) => { - return { - id: m, - label: m, - }; - }); - const [backgroundColors, setBackgroundColors] = useState(background_colors); const [backgroundFunction, setBackgroundFunction] = useState( @@ -74,7 +73,7 @@ export default () => { backgroundButtons[0].id ); - const switchBackgroundColors = (id: string) => { + const switchBackgroundColors = useCallback((id: string) => { switch (id) { case 'container': setBackgroundSelected(id); @@ -92,7 +91,7 @@ export default () => { setBackgroundFunction('euiButtonColor(color)'); break; } - }; + }, []); const showSass = useContext(ThemeContext).themeLanguage.includes('sass'); @@ -127,12 +126,8 @@ export default () => { , - toggleChecked: React.SetStateAction - ) => { - setContrastValue(sliderValue); + onChange={(sliderValue, toggleChecked) => { + setContrastValue(Number(sliderValue)); setShowTextVariants(toggleChecked); }} /> @@ -301,7 +296,9 @@ export default () => { { , showThemeLanguageToggle: true, - description: ( - <> + intro: ( +

Elastic UI builds with a very limited palette. It uses a core set of three colors with a green / orange / red qualitative set and combined @@ -46,7 +46,7 @@ export const colorsInfo = { their evaluated value but by their{' '} purpose.

- +
), }; diff --git a/src/components/basic_table/basic_table.test.tsx b/src/components/basic_table/basic_table.test.tsx index 3828a0c34ce..bf9513474ff 100644 --- a/src/components/basic_table/basic_table.test.tsx +++ b/src/components/basic_table/basic_table.test.tsx @@ -7,7 +7,6 @@ */ import React from 'react'; -import { act } from '@testing-library/react'; import { render, screen } from '../../test/rtl'; import { requiredProps } from '../../test'; import { shouldRenderCustomStyles } from '../../test/internal'; @@ -463,34 +462,6 @@ describe('EuiBasicTable', () => { expect(onSelectionChange).toHaveBeenCalledWith([]); expect(container.querySelectorAll('[checked]')).toHaveLength(0); }); - - // TODO: Delete this test once deprecated API is removed - test('deprecated setSelection ref API', () => { - const props = { - items: basicItems, - columns: basicColumns, - itemId: 'id', - selection: { - onSelectionChange: () => {}, - }, - }; - - let classRef: EuiBasicTable | null; - render( - { - classRef = ref; - }} - /> - ); - expect(getCheckboxAt(1).checked).toBeFalsy(); - - act(() => { - classRef!.setSelection([basicItems[0]]); - }); - expect(getCheckboxAt(1).checked).toBeTruthy(); - }); }); test('footers', () => { diff --git a/src/components/basic_table/basic_table.tsx b/src/components/basic_table/basic_table.tsx index c917a890afb..18ef806dbf6 100644 --- a/src/components/basic_table/basic_table.tsx +++ b/src/components/basic_table/basic_table.tsx @@ -417,13 +417,6 @@ export class EuiBasicTable extends Component< } } - /** - * @deprecated Use `selection.selected` instead to declaratively control table selection - */ - setSelection(newSelection: T[]) { - this.changeSelection(newSelection); - } - buildCriteria(props: EuiBasicTableProps): Criteria { const criteria: Criteria = {}; if (hasPagination(props)) { diff --git a/src/components/basic_table/in_memory_table.tsx b/src/components/basic_table/in_memory_table.tsx index 833cb8e1de8..a1c63ff2d25 100644 --- a/src/components/basic_table/in_memory_table.tsx +++ b/src/components/basic_table/in_memory_table.tsx @@ -303,7 +303,6 @@ export class EuiInMemoryTable extends Component< tableLayout: 'fixed', searchFormat: 'eql', }; - tableRef: React.RefObject; static getDerivedStateFromProps( nextProps: EuiInMemoryTableProps, @@ -424,14 +423,6 @@ export class EuiInMemoryTable extends Component< allowNeutralSort: allowNeutralSort !== false, showPerPageOptions, }; - - this.tableRef = React.createRef(); - } - - setSelection(newSelection: T[]) { - if (this.tableRef.current) { - this.tableRef.current.setSelection(newSelection); - } } onTableChange = ({ page, sort }: Criteria) => { @@ -749,7 +740,6 @@ export class EuiInMemoryTable extends Component< const table = ( // @ts-ignore complex relationship between pagination's existence and criteria, the code logic ensures this is correctly maintained `; @@ -14,7 +14,7 @@ exports[`EuiBeacon props color accent is rendered 1`] = ` aria-label="aria-label" class="euiBeacon testClass1 testClass2 emotion-euiBeacon-accent-euiTestCss" data-test-subj="test subject string" - style="height: 12px; width: 12px;" + style="block-size: 12px; inline-size: 12px;" /> `; @@ -23,7 +23,7 @@ exports[`EuiBeacon props color danger is rendered 1`] = ` aria-label="aria-label" class="euiBeacon testClass1 testClass2 emotion-euiBeacon-danger-euiTestCss" data-test-subj="test subject string" - style="height: 12px; width: 12px;" + style="block-size: 12px; inline-size: 12px;" /> `; @@ -32,7 +32,7 @@ exports[`EuiBeacon props color primary is rendered 1`] = ` aria-label="aria-label" class="euiBeacon testClass1 testClass2 emotion-euiBeacon-primary-euiTestCss" data-test-subj="test subject string" - style="height: 12px; width: 12px;" + style="block-size: 12px; inline-size: 12px;" /> `; @@ -41,7 +41,7 @@ exports[`EuiBeacon props color subdued is rendered 1`] = ` aria-label="aria-label" class="euiBeacon testClass1 testClass2 emotion-euiBeacon-subdued-euiTestCss" data-test-subj="test subject string" - style="height: 12px; width: 12px;" + style="block-size: 12px; inline-size: 12px;" /> `; @@ -50,7 +50,7 @@ exports[`EuiBeacon props color success is rendered 1`] = ` aria-label="aria-label" class="euiBeacon testClass1 testClass2 emotion-euiBeacon-success-euiTestCss" data-test-subj="test subject string" - style="height: 12px; width: 12px;" + style="block-size: 12px; inline-size: 12px;" /> `; @@ -59,7 +59,7 @@ exports[`EuiBeacon props color warning is rendered 1`] = ` aria-label="aria-label" class="euiBeacon testClass1 testClass2 emotion-euiBeacon-warning-euiTestCss" data-test-subj="test subject string" - style="height: 12px; width: 12px;" + style="block-size: 12px; inline-size: 12px;" /> `; @@ -68,6 +68,6 @@ exports[`EuiBeacon props size accepts size 1`] = ` aria-label="aria-label" class="euiBeacon testClass1 testClass2 emotion-euiBeacon-success-euiTestCss" data-test-subj="test subject string" - style="height: 14px; width: 14px;" + style="block-size: 14px; inline-size: 14px;" /> `; diff --git a/src/components/beacon/beacon.tsx b/src/components/beacon/beacon.tsx index b0070e00217..acea33586f2 100644 --- a/src/components/beacon/beacon.tsx +++ b/src/components/beacon/beacon.tsx @@ -6,13 +6,15 @@ * Side Public License, v 1. */ -import React, { FunctionComponent, HTMLAttributes } from 'react'; +import React, { FunctionComponent, HTMLAttributes, useMemo } from 'react'; import { CommonProps } from '../common'; import classNames from 'classnames'; -import { euiBeaconStyles } from './beacon.styles'; +import { logicalStyles } from '../../global_styling'; import { useEuiTheme } from '../../services'; +import { euiBeaconStyles } from './beacon.styles'; + export const COLORS = [ 'subdued', 'primary', @@ -51,11 +53,15 @@ export const EuiBeacon: FunctionComponent = ({ const styles = euiBeaconStyles(euiTheme); const cssStyles = [styles.euiBeacon, styles[color]]; - const beaconStyle = { - ...style, - height: size, - width: size, - }; + const beaconStyle = useMemo( + () => + logicalStyles({ + ...style, + height: size, + width: size, + }), + [style, size] + ); return (
diff --git a/src/components/comment_list/comment_event.styles.ts b/src/components/comment_list/comment_event.styles.ts index 42530c43101..8ef8471d794 100644 --- a/src/components/comment_list/comment_event.styles.ts +++ b/src/components/comment_list/comment_event.styles.ts @@ -7,42 +7,9 @@ */ import { css } from '@emotion/react'; -import { UseEuiTheme, tintOrShade } from '../../services'; +import { UseEuiTheme } from '../../services'; import { logicalCSS } from '../../global_styling'; -export const euiCommentEventBorderColors = ({ - euiTheme, - colorMode, -}: UseEuiTheme) => { - const ratio = 0.6; - return { - warning: css` - border-color: ${tintOrShade(euiTheme.colors.warning, 0.4, colorMode)}; - `, - accent: css` - border-color: ${tintOrShade(euiTheme.colors.accent, ratio, colorMode)}; - `, - primary: css` - border-color: ${tintOrShade(euiTheme.colors.primary, ratio, colorMode)}; - `, - success: css` - border-color: ${tintOrShade(euiTheme.colors.success, ratio, colorMode)}; - `, - danger: css` - border-color: ${tintOrShade(euiTheme.colors.danger, ratio, colorMode)}; - `, - subdued: css` - border-color: ${euiTheme.border.color}; - `, - transparent: css` - border-color: ${euiTheme.border.color}; - `, - plain: css` - border-color: ${euiTheme.border.color}; - `, - }; -}; - export const euiCommentEventStyles = (euiThemeContext: UseEuiTheme) => { const { euiTheme } = euiThemeContext; return { diff --git a/src/components/comment_list/comment_event.tsx b/src/components/comment_list/comment_event.tsx index 4747e3910c9..7e1a56af706 100644 --- a/src/components/comment_list/comment_event.tsx +++ b/src/components/comment_list/comment_event.tsx @@ -10,13 +10,13 @@ import React, { FunctionComponent, ReactNode, useMemo } from 'react'; import classNames from 'classnames'; import { useEuiTheme } from '../../services'; +import { useEuiBorderColorCSS } from '../../global_styling'; import { CommonProps } from '../common'; import { IconType } from '../icon'; import { EuiPanel, EuiPanelProps } from '../panel'; import { EuiAvatar } from '../avatar'; import { - euiCommentEventBorderColors, euiCommentEventStyles, euiCommentEventHeaderStyles, euiCommentEventBodyStyles, @@ -119,7 +119,7 @@ export const EuiCommentEvent: FunctionComponent = ({ * Styles */ const euiTheme = useEuiTheme(); - const borderStyles = euiCommentEventBorderColors(euiTheme); + const borderStyles = useEuiBorderColorCSS(); const styles = euiCommentEventStyles(euiTheme); const cssStyles = [ diff --git a/src/components/datagrid/body/cell/data_grid_cell.tsx b/src/components/datagrid/body/cell/data_grid_cell.tsx index 58e59bcd437..357edb444ff 100644 --- a/src/components/datagrid/body/cell/data_grid_cell.tsx +++ b/src/components/datagrid/body/cell/data_grid_cell.tsx @@ -147,6 +147,7 @@ const EuiDataGridCellContent: FunctionComponent< ); } ); +EuiDataGridCellContent.displayName = 'EuiDataGridCellContent'; export class EuiDataGridCell extends Component< EuiDataGridCellProps, diff --git a/src/components/date_picker/super_date_picker/date_popover/date_popover_button.tsx b/src/components/date_picker/super_date_picker/date_popover/date_popover_button.tsx index 536e1e178fb..e238d0df208 100644 --- a/src/components/date_picker/super_date_picker/date_popover/date_popover_button.tsx +++ b/src/components/date_picker/super_date_picker/date_popover/date_popover_button.tsx @@ -38,6 +38,7 @@ export interface EuiDatePopoverButtonProps { onPopoverClose: EuiPopoverProps['closePopover']; onPopoverToggle: MouseEventHandler; position: 'start' | 'end'; + canRoundRelativeUnits?: boolean; roundUp?: boolean; timeFormat: string; value: string; @@ -56,6 +57,7 @@ export const EuiDatePopoverButton: FunctionComponent< needsUpdating, value, buttonProps, + canRoundRelativeUnits, roundUp, onChange, locale, @@ -82,12 +84,11 @@ export const EuiDatePopoverButton: FunctionComponent< }, ]); - const formattedValue = useFormatTimeString( - value, - dateFormat, + const formattedValue = useFormatTimeString(value, dateFormat, { roundUp, - locale - ); + locale, + canRoundRelativeUnits, + }); let title = formattedValue; const invalidTitle = useEuiI18n( @@ -133,6 +134,7 @@ export const EuiDatePopoverButton: FunctionComponent< void; + canRoundRelativeUnits?: boolean; roundUp?: boolean; dateFormat: string; timeFormat: string; @@ -41,6 +42,7 @@ export const EuiDatePopoverContent: FunctionComponent< EuiDatePopoverContentProps > = ({ value, + canRoundRelativeUnits = true, roundUp = false, onChange, dateFormat, @@ -108,7 +110,9 @@ export const EuiDatePopoverContent: FunctionComponent< { ).toBe(false); }); }); + +describe('useFormatTimeString', () => { + it('it takes a time string and formats it into a humanized date', () => { + expect( + renderHook(() => useFormatTimeString('now-3s', dateFormat)).result.current + ).toEqual('~ a few seconds ago'); + expect( + renderHook(() => useFormatTimeString('now+1m', dateFormat)).result.current + ).toEqual('~ in a minute'); + expect( + renderHook(() => useFormatTimeString('now+100w', dateFormat)).result + .current + ).toEqual('~ in 2 years'); + }); + + it("always parses the 'now' string as-is", () => { + expect( + renderHook(() => useFormatTimeString('now', dateFormat)).result.current + ).toEqual('now'); + }); + + describe('options', () => { + test('locale', () => { + expect( + renderHook(() => + useFormatTimeString('now+15m', dateFormat, { locale: 'ja' }) + ).result.current + ).toBe('~ 15分後'); + }); + + describe('canRoundRelativeUnits', () => { + const option = { canRoundRelativeUnits: false }; + + it("allows skipping moment.fromNow()'s default rounding", () => { + expect( + renderHook(() => useFormatTimeString('now-3s', dateFormat, option)) + .result.current + ).toEqual('3 seconds ago'); + expect( + renderHook(() => useFormatTimeString('now+1m', dateFormat, option)) + .result.current + ).toEqual('in a minute'); + expect( + renderHook(() => useFormatTimeString('now+100w', dateFormat, option)) + .result.current + ).toEqual('in 100 weeks'); + }); + }); + }); +}); diff --git a/src/components/date_picker/super_date_picker/pretty_duration.tsx b/src/components/date_picker/super_date_picker/pretty_duration.tsx index e64d1b9f568..3f6f36d2ef0 100644 --- a/src/components/date_picker/super_date_picker/pretty_duration.tsx +++ b/src/components/date_picker/super_date_picker/pretty_duration.tsx @@ -8,7 +8,7 @@ import React from 'react'; import dateMath from '@elastic/datemath'; -import moment, { LocaleSpecifier } from 'moment'; // eslint-disable-line import/named +import moment, { LocaleSpecifier, RelativeTimeKey } from 'moment'; // eslint-disable-line import/named import { useEuiI18n } from '../../i18n'; import { getDateMode, DATE_MODES } from './date_modes'; import { parseRelativeParts } from './relative_utils'; @@ -146,9 +146,18 @@ const ISO_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; export const useFormatTimeString = ( timeString: string, dateFormat: string, - roundUp = false, - locale: LocaleSpecifier = 'en' + options?: { + locale?: LocaleSpecifier; + roundUp?: boolean; + canRoundRelativeUnits?: boolean; + } ): string => { + const { + locale = 'en', + roundUp = false, + canRoundRelativeUnits = true, + } = options || {}; + // i18n'd strings const nowDisplay = useEuiI18n('euiPrettyDuration.now', 'now'); const invalidDateDisplay = useEuiI18n( @@ -171,7 +180,27 @@ export const useFormatTimeString = ( } if (moment.isMoment(tryParse)) { - return `~ ${tryParse.locale(locale).fromNow()}`; + if (canRoundRelativeUnits) { + return `~ ${tryParse.locale(locale).fromNow()}`; + } else { + // To force a specific unit to be used, we need to skip moment.fromNow() + // entirely and write our own custom moment formatted output. + const { count, unit: _unit } = parseRelativeParts(timeString); + const isFuture = _unit.endsWith('+'); + const unit = isFuture ? _unit.slice(0, -1) : _unit; // We want just the unit letter without the trailing + + + // @see https://momentjs.com/docs/#/customization/relative-time/ + const relativeUnitKey = ( + count === 1 ? unit : unit + unit + ) as RelativeTimeKey; + + // @see https://momentjs.com/docs/#/i18n/locale-data/ + return moment.localeData().pastFuture( + isFuture ? count : count * -1, + moment.localeData().relativeTime(count, false, relativeUnitKey, false) + // Booleans don't seem to actually matter for output, .pastFuture() handles that + ); + } } return timeString; @@ -246,7 +275,7 @@ export const usePrettyDuration = ({ * If it's none of the above, display basic fallback copy */ const displayFrom = useFormatTimeString(timeFrom, dateFormat); - const displayTo = useFormatTimeString(timeTo, dateFormat, true); + const displayTo = useFormatTimeString(timeTo, dateFormat, { roundUp: true }); const fallbackDuration = useEuiI18n( 'euiPrettyDuration.fallbackDuration', '{displayFrom} to {displayTo}', diff --git a/src/components/date_picker/super_date_picker/super_date_picker.test.tsx b/src/components/date_picker/super_date_picker/super_date_picker.test.tsx index 1cdd87f109e..03f6f151647 100644 --- a/src/components/date_picker/super_date_picker/super_date_picker.test.tsx +++ b/src/components/date_picker/super_date_picker/super_date_picker.test.tsx @@ -32,6 +32,12 @@ const findInternalInstance = ( }; describe('EuiSuperDatePicker', () => { + // RTL doesn't automatically clean up portals/datepicker popovers between tests + afterEach(() => { + const portals = document.querySelectorAll('[data-euiportal]'); + portals.forEach((portal) => portal.parentNode?.removeChild(portal)); + }); + shouldRenderCustomStyles(, { skip: { style: true }, }); @@ -369,5 +375,59 @@ describe('EuiSuperDatePicker', () => { }); }); }); + + describe('canRoundRelativeUnits', () => { + const props = { + onTimeChange: noop, + start: 'now-300m', + end: 'now', + }; + + it('defaults to true, which will round relative units up to the next largest unit', () => { + const { getByTestSubject } = render( + + ); + fireEvent.click(getByTestSubject('superDatePickerShowDatesButton')); + + const startButton = getByTestSubject( + 'superDatePickerstartDatePopoverButton' + ); + expect(startButton).toHaveTextContent('~ 5 hours ago'); + + const countInput = getByTestSubject( + 'superDatePickerRelativeDateInputNumber' + ); + expect(countInput).toHaveValue(5); + + const unitSelect = getByTestSubject( + 'superDatePickerRelativeDateInputUnitSelector' + ); + expect(unitSelect).toHaveValue('h'); + + fireEvent.change(countInput, { target: { value: 300 } }); + fireEvent.change(unitSelect, { target: { value: 'd' } }); + expect(startButton).toHaveTextContent('~ 10 months ago'); + }); + + it('when false, allows preserving the unit set in the start/end time timestamp', () => { + const { getByTestSubject } = render( + + ); + fireEvent.click(getByTestSubject('superDatePickerShowDatesButton')); + + const startButton = getByTestSubject( + 'superDatePickerstartDatePopoverButton' + ); + expect(startButton).toHaveTextContent('300 minutes ago'); + + const unitSelect = getByTestSubject( + 'superDatePickerRelativeDateInputUnitSelector' + ); + expect(unitSelect).toHaveValue('m'); + + fireEvent.change(unitSelect, { target: { value: 'd' } }); + expect(startButton).toHaveTextContent('300 days ago'); + }); + }); }); }); diff --git a/src/components/date_picker/super_date_picker/super_date_picker.tsx b/src/components/date_picker/super_date_picker/super_date_picker.tsx index 5ae0048edb6..31817cd7113 100644 --- a/src/components/date_picker/super_date_picker/super_date_picker.tsx +++ b/src/components/date_picker/super_date_picker/super_date_picker.tsx @@ -188,6 +188,15 @@ export type EuiSuperDatePickerProps = CommonProps & { * Props passed to the update button #EuiSuperUpdateButtonProps */ updateButtonProps?: EuiSuperUpdateButtonProps; + + /** + * By default, relative units will be rounded up to next largest unit of time + * (for example, 90 minutes will become ~ 2 hours). + * + * If you do not want this behavior and instead wish to keep the exact units + * input by the user, set this flag to `false`. + */ + canRoundRelativeUnits?: boolean; }; type EuiSuperDatePickerInternalProps = EuiSuperDatePickerProps & { @@ -250,6 +259,7 @@ export class EuiSuperDatePickerInternal extends Component< recentlyUsedRanges: [], refreshInterval: 1000, showUpdateButton: true, + canRoundRelativeUnits: true, start: 'now-15m', timeFormat: 'HH:mm', width: 'restricted', @@ -483,6 +493,7 @@ export class EuiSuperDatePickerInternal extends Component< isQuickSelectOnly, showUpdateButton, commonlyUsedRanges, + canRoundRelativeUnits, timeOptions, dateFormat, refreshInterval, @@ -579,6 +590,7 @@ export class EuiSuperDatePickerInternal extends Component< utcOffset={utcOffset} timeFormat={timeFormat} locale={locale || contextLocale} + canRoundRelativeUnits={canRoundRelativeUnits} isOpen={this.state.isStartDatePopoverOpen} onPopoverToggle={this.onStartDatePopoverToggle} onPopoverClose={this.onStartDatePopoverClose} @@ -599,6 +611,7 @@ export class EuiSuperDatePickerInternal extends Component< utcOffset={utcOffset} timeFormat={timeFormat} locale={locale || contextLocale} + canRoundRelativeUnits={canRoundRelativeUnits} roundUp isOpen={this.state.isEndDatePopoverOpen} onPopoverToggle={this.onEndDatePopoverToggle} diff --git a/src/components/empty_prompt/__snapshots__/empty_prompt.test.tsx.snap b/src/components/empty_prompt/__snapshots__/empty_prompt.test.tsx.snap index 8682a97dbf0..b0316cdf1ab 100644 --- a/src/components/empty_prompt/__snapshots__/empty_prompt.test.tsx.snap +++ b/src/components/empty_prompt/__snapshots__/empty_prompt.test.tsx.snap @@ -3,14 +3,14 @@ exports[`EuiEmptyPrompt is rendered 1`] = `
+

+ Title +

+
-

- Title -

-
-
-

- Body -

-
-
-
- Actions -
+

+ Body +

+
+
+
+ Actions
@@ -52,22 +48,15 @@ exports[`EuiEmptyPrompt is rendered 1`] = ` exports[`EuiEmptyPrompt props actions renders alone 1`] = `
-
-
- actions -
+ actions
@@ -75,33 +64,26 @@ exports[`EuiEmptyPrompt props actions renders alone 1`] = ` exports[`EuiEmptyPrompt props actions renders an array 1`] = `
+ class="euiFlexItem emotion-euiFlexItem-growZero" + > + action1 +
-
- action1 -
-
- action2 -
+ action2
@@ -111,22 +93,18 @@ exports[`EuiEmptyPrompt props actions renders an array 1`] = ` exports[`EuiEmptyPrompt props body renders alone 1`] = `
-
- body -
+ body
@@ -135,165 +113,129 @@ exports[`EuiEmptyPrompt props body renders alone 1`] = ` exports[`EuiEmptyPrompt props color accent is rendered 1`] = `
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
`; exports[`EuiEmptyPrompt props color danger is rendered 1`] = `
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
`; exports[`EuiEmptyPrompt props color plain is rendered 1`] = `
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
`; exports[`EuiEmptyPrompt props color primary is rendered 1`] = `
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
`; exports[`EuiEmptyPrompt props color subdued is rendered 1`] = `
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
`; exports[`EuiEmptyPrompt props color success is rendered 1`] = `
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
`; exports[`EuiEmptyPrompt props color transparent is rendered 1`] = `
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
`; exports[`EuiEmptyPrompt props color warning is rendered 1`] = `
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
`; exports[`EuiEmptyPrompt props footer renders alone 1`] = `
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
@@ -302,38 +244,34 @@ exports[`EuiEmptyPrompt props footer renders alone 1`] = ` exports[`EuiEmptyPrompt props icon renders alone 1`] = `
Custom icon
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
`; exports[`EuiEmptyPrompt props iconType renders alone 1`] = `
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
`; exports[`EuiEmptyPrompt props iconType renders with iconColor 1`] = `
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
`; exports[`EuiEmptyPrompt props layout renders alone 1`] = `
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-horizontal" + />
`; exports[`EuiEmptyPrompt props paddingSize l is rendered 1`] = `
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
`; exports[`EuiEmptyPrompt props paddingSize m is rendered 1`] = `
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
`; exports[`EuiEmptyPrompt props paddingSize none is rendered 1`] = `
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
`; exports[`EuiEmptyPrompt props paddingSize s is rendered 1`] = `
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
`; exports[`EuiEmptyPrompt props styles are rendered 1`] = `
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
`; exports[`EuiEmptyPrompt props title renders alone 1`] = `
-
- title -
+ title
diff --git a/src/components/empty_prompt/_empty_prompt.scss b/src/components/empty_prompt/_empty_prompt.scss deleted file mode 100644 index 188da8b0365..00000000000 --- a/src/components/empty_prompt/_empty_prompt.scss +++ /dev/null @@ -1,128 +0,0 @@ -.euiEmptyPrompt { - text-align: center; - margin: auto; - - @include euiBreakpoint('l', 'xl') { - max-width: max-content; // the width becomes as wide as necessary to contain all of its contents - } - - .euiEmptyPrompt__icon { - // Consumers should use an EuiImage (recommended) with the horizontal layout - // But they can use for example an img or other react node - > * { - flex-shrink: 1; - max-width: convertToRem(360px); - } - } - - // Footer background colors - @each $modifier, $color in $euiPanelBackgroundColorModifiers { - @if ($modifier == 'transparent') { - &.euiPanel--#{$modifier} { - .euiEmptyPrompt__footer { - background-color: $euiPageBackgroundColor; - } - - &:not(.euiPanel--hasBorder) .euiEmptyPrompt__footer { - border-radius: $euiBorderRadius; - } - - &.euiPanel--hasBorder .euiEmptyPrompt__footer { - border-radius: 0 0 $euiBorderRadius $euiBorderRadius; - } - } - } @else if ($modifier == 'plain') { - &.euiPanel--#{$modifier} .euiEmptyPrompt__footer { - background-color: $euiPageBackgroundColor; - border-radius: 0 0 $euiBorderRadius $euiBorderRadius; - } - } @else if ($modifier == 'subdued') { - &.euiPanel--#{$modifier} .euiEmptyPrompt__footer { - border-top: $euiBorderThin; - border-radius: 0 0 $euiBorderRadius $euiBorderRadius; - } - } @else { - &.euiPanel--#{$modifier} .euiEmptyPrompt__footer { - border-top: 1px solid lightOrDarkTheme(darken($color, 10%), lighten($color, 6%)); - border-radius: 0 0 $euiBorderRadius $euiBorderRadius; - } - } - } -} - -$euiEmptyPromptContentMaxWidth: 36em; - -.euiEmptyPrompt--vertical { - .euiEmptyPrompt__main { - display: flex; - flex-direction: column; - justify-content: center; - } - - .euiEmptyPrompt__contentInner { - max-width: $euiEmptyPromptContentMaxWidth; - margin: auto; - } - - .euiEmptyPrompt__icon { - margin-bottom: $euiSize; - } -} - -.euiEmptyPrompt--horizontal { - @include euiBreakpoint('l', 'xl') { - justify-content: flex-start; - text-align: left; - } - - .euiEmptyPrompt__main { - display: flex; - flex-direction: column; - align-items: center; - - @include euiBreakpoint('l', 'xl') { - flex-direction: row-reverse; - } - } - - .euiEmptyPrompt__icon { - display: flex; - align-items: center; - justify-content: center; - - @include euiBreakpoint('l', 'xl') { - min-width: 40%; - max-width: 50%; - } - } - - .euiEmptyPrompt__content { - max-width: $euiEmptyPromptContentMaxWidth; - - @include euiBreakpoint('l', 'xl') { - padding: $euiSizeL 0; - } - } - - .euiEmptyPrompt__actions { - @include euiBreakpoint('l', 'xl') { - justify-content: flex-start; - } - } -} - -// Padding -@each $modifier, $amount in $euiPanelPaddingModifiers { - .euiEmptyPrompt--#{$modifier} { - .euiEmptyPrompt__main, - .euiEmptyPrompt__footer { - padding: $amount; - } - - &.euiEmptyPrompt--horizontal { - .euiEmptyPrompt__main { - gap: $amount; - } - } - } -} diff --git a/src/components/empty_prompt/_index.scss b/src/components/empty_prompt/_index.scss deleted file mode 100644 index 7b7dacf4a1d..00000000000 --- a/src/components/empty_prompt/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'empty_prompt'; diff --git a/src/components/empty_prompt/empty_prompt.styles.ts b/src/components/empty_prompt/empty_prompt.styles.ts new file mode 100644 index 00000000000..81a18d02ef9 --- /dev/null +++ b/src/components/empty_prompt/empty_prompt.styles.ts @@ -0,0 +1,169 @@ +/* + * 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, + euiPaddingSize, + euiBorderColor, + logicalCSS, + mathWithUnits, +} from '../../global_styling'; +import { UseEuiTheme } from '../../services'; + +export const euiEmptyPromptStyles = (euiThemeContext: UseEuiTheme) => { + const { euiTheme } = euiThemeContext; + + const iconMaxWidth = mathWithUnits(euiTheme.size.l, (x) => x * 15); + + const generatePaddingStyles = (property = 'padding') => ({ + none: null, + s: css` + ${property}: ${euiPaddingSize(euiThemeContext, 's')} + `, + m: css` + ${property}: ${euiPaddingSize(euiThemeContext, 'm')} + `, + l: css` + ${property}: ${euiPaddingSize(euiThemeContext, 'l')} + `, + }); + + const generateFooterBorder = (color: Parameters[1]) => + `${euiTheme.border.width.thin} solid ${euiBorderColor( + euiThemeContext, + color + )}`; + + return { + euiEmptyPrompt: css` + text-align: center; + margin: auto; + + ${euiBreakpoint(euiThemeContext, ['l', 'xl'])} { + /* the width becomes as wide as necessary to contain all of its contents */ + ${logicalCSS('max-width', 'max-content')} + } + `, + vertical: css``, + horizontal: css` + ${euiBreakpoint(euiThemeContext, ['l', 'xl'])} { + justify-content: flex-start; + text-align: start; + } + `, + main: { + euiEmptyPrompt__main: css` + display: flex; + flex-direction: column; + `, + vertical: css` + justify-content: center; + `, + horizontal: css` + align-items: center; + + ${euiBreakpoint(euiThemeContext, ['l', 'xl'])} { + flex-direction: row-reverse; + } + `, + ...generatePaddingStyles(), + horizontalPadding: generatePaddingStyles('gap'), + }, + content: { + euiEmptyPrompt__content: css` + ${logicalCSS('max-width', '36em')} + `, + vertical: css` + margin: auto; + `, + horizontal: css` + ${euiBreakpoint(euiThemeContext, ['l', 'xl'])} { + padding-block: ${euiTheme.size.l}; + padding-inline: 0; + } + `, + }, + icon: { + euiEmptyPrompt__icon: css` + ${logicalCSS('max-width', iconMaxWidth)} + margin: auto; + + /* Consumers should use an EuiImage (recommended) with the horizontal layout + * But they can use for example an img or other react node */ + & > * { + flex-shrink: 1; + ${logicalCSS('max-width', '100%')} + } + `, + vertical: css` + ${logicalCSS('margin-bottom', euiTheme.size.base)} + `, + horizontal: css` + ${euiBreakpoint(euiThemeContext, ['l', 'xl'])} { + ${logicalCSS('min-width', '40%')} + ${logicalCSS('max-width', '50%')} + + /* I'm not totally sure why setting a percentage max width on the wrapper and a static + max-width on the underlying image/icon makes sense, but this ports over the previous Sass + styles as-is to avoid UI changes/regressions 🤷 */ + /* Note: The extra && selector specificity is there to override euiImageWrapper's CSS */ + && > * { + ${logicalCSS('max-width', iconMaxWidth)} + } + } + `, + }, + actions: { + euiEmptyPrompt__actions: css``, + vertical: css``, + horizontal: css` + ${euiBreakpoint(euiThemeContext, ['l', 'xl'])} { + justify-content: flex-start; + } + `, + }, + footer: { + euiEmptyPrompt__footer: css` + /* Round bottom corners only */ + border-end-end-radius: inherit; + border-end-start-radius: inherit; + `, + roundedBorders: css` + /* Round all corners */ + border-radius: inherit; + `, + // Colors + transparent: css` + background-color: ${euiTheme.colors.body}; + `, + plain: css` + background-color: ${euiTheme.colors.body}; + `, + subdued: css` + ${logicalCSS('border-top', generateFooterBorder('subdued'))} + `, + primary: css` + ${logicalCSS('border-top', generateFooterBorder('primary'))} + `, + accent: css` + ${logicalCSS('border-top', generateFooterBorder('accent'))} + `, + danger: css` + ${logicalCSS('border-top', generateFooterBorder('danger'))} + `, + warning: css` + ${logicalCSS('border-top', generateFooterBorder('warning'))} + `, + success: css` + ${logicalCSS('border-top', generateFooterBorder('success'))} + `, + ...generatePaddingStyles(), + }, + }; +}; diff --git a/src/components/empty_prompt/empty_prompt.test.tsx b/src/components/empty_prompt/empty_prompt.test.tsx index e63ecf36f18..dd87ba4166e 100644 --- a/src/components/empty_prompt/empty_prompt.test.tsx +++ b/src/components/empty_prompt/empty_prompt.test.tsx @@ -8,11 +8,15 @@ import React from 'react'; import { requiredProps } from '../../test'; +import { shouldRenderCustomStyles } from '../../test/internal'; import { render } from '../../test/rtl'; -import { EuiEmptyPrompt, PADDING_SIZES } from './empty_prompt'; + import { COLORS } from '../panel/panel'; +import { EuiEmptyPrompt, PADDING_SIZES } from './empty_prompt'; describe('EuiEmptyPrompt', () => { + shouldRenderCustomStyles(); + test('is rendered', () => { const { container } = render( = ({ footer, ...rest }) => { - const isVerticalLayout = layout === 'vertical'; + const classes = classNames('euiEmptyPrompt', className); + const euiTheme = useEuiTheme(); + const styles = useMemo(() => euiEmptyPromptStyles(euiTheme), [euiTheme]); + const cssStyles = [styles.euiEmptyPrompt, styles[layout]]; + const mainStyles = [ + styles.main.euiEmptyPrompt__main, + styles.main[layout], + styles.main[paddingSize], + layout === 'horizontal' && styles.main.horizontalPadding[paddingSize], + ]; + const contentStyles = [ + styles.content.euiEmptyPrompt__content, + styles.content[layout], + ]; + // Default the iconColor to `subdued`, // otherwise try to match the iconColor with the panel color unless iconColor is specified const iconColor = _iconColor ?? (isNamedColor(color) ? color : 'subdued'); + const iconNode = useMemo(() => { + if (!iconType && !icon) return null; + + const iconStyles = [styles.icon.euiEmptyPrompt__icon, styles.icon[layout]]; + return ( +
+ {iconType ? ( + + ) : ( + icon + )} +
+ ); + }, [icon, iconType, iconColor, layout, styles.icon]); - const iconNode = iconType ? ( - - ) : ( - icon - ); - - let titleNode; - let bodyNode; - if (body || title) { - if (title) { - titleNode = {title}; - } - - if (body) { - bodyNode = ( - <> - {title && } - {body} - - ); - } - } - - let actionsNode; - if (actions) { - let actionsRow; + const actionsNode = useMemo(() => { + if (!actions) return null; if (Array.isArray(actions)) { - actionsRow = ( + const actionStyles = [ + styles.actions.euiEmptyPrompt__actions, + styles.actions[layout], + ]; + return ( {actions.map((action, index) => ( @@ -145,50 +154,46 @@ export const EuiEmptyPrompt: FunctionComponent = ({ ); } else { - actionsRow = actions; + return actions; } - - actionsNode = ( - <> - - {actionsRow} - + }, [actions, layout, styles.actions]); + + const footerNode = useMemo(() => { + if (!footer) return null; + const footerStyles = [ + styles.footer.euiEmptyPrompt__footer, + styles.footer[paddingSize], + styles.footer[color], + color === 'transparent' && !hasBorder && styles.footer.roundedBorders, + ]; + return ( +
+ {footer} +
); - } - - const contentNodes = ( - <> - {titleNode} - {bodyNode} - {actionsNode} - - ); - - const classes = classNames( - 'euiEmptyPrompt', - [`euiEmptyPrompt--${layout}`], - paddingSizeToClassNameMap[paddingSize], - className - ); - - const panelProps: _EuiPanelDivlike = { - className: classes, - color: color, - paddingSize: 'none', - hasBorder: hasBorder, - grow: false, - ...rest, - }; + }, [footer, paddingSize, color, hasBorder, styles.footer]); return ( - -
- {iconNode &&
{iconNode}
} -
-
{contentNodes}
+ +
+ {iconNode} +
+ {title && {title}} + {title && body && } + {body && {body}} + {actionsNode && (body || title) && } + {actionsNode}
- {footer &&
{footer}
} + {footerNode}
); }; diff --git a/src/components/highlight/__snapshots__/highlight.test.tsx.snap b/src/components/highlight/__snapshots__/highlight.test.tsx.snap index 6c76deac806..dce6cc67640 100644 --- a/src/components/highlight/__snapshots__/highlight.test.tsx.snap +++ b/src/components/highlight/__snapshots__/highlight.test.tsx.snap @@ -1,84 +1,16 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`EuiHighlight behavior loose matching matches strings with different casing 1`] = ` - - different - - case - - match - -`; - -exports[`EuiHighlight behavior matching applies to all matches 1`] = ` - - - match - - - - match - - - - match - - -`; - -exports[`EuiHighlight behavior matching hasScreenReaderHelpText can be false 1`] = ` - - - match - - - - match - - - - match - - -`; - -exports[`EuiHighlight behavior matching only applies to first match 1`] = ` - - - match - - match match - -`; - -exports[`EuiHighlight behavior strict matching doesn't match strings with different casing 1`] = ` - - different case match - -`; - exports[`EuiHighlight is rendered 1`] = ` - value + + va + + lue `; diff --git a/src/components/highlight/_highlight_all.tsx b/src/components/highlight/_highlight_all.tsx new file mode 100644 index 00000000000..13ae97c601e --- /dev/null +++ b/src/components/highlight/_highlight_all.tsx @@ -0,0 +1,102 @@ +/* + * 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 React, { useMemo, FunctionComponent } from 'react'; +import escapeRegExp from 'lodash/escapeRegExp'; + +import type { _SharedSubcomponentProps } from './highlight'; + +/** + * Internal subcomponent with logic for highlighting all occurrences + * of a search value within a subject + * + * Uses regex rather than indexOf/while loops for easier dev maintainability + */ +export const HighlightAll: FunctionComponent<_SharedSubcomponentProps> = ({ + searchSubject, + searchValue: _searchValue, + isStrict, + highlightComponent: HighlightComponent = 'mark', +}) => { + const searchValue = useMemo(() => { + return Array.isArray(_searchValue) + ? _searchValue.map(escapeRegExp).join('|') + : escapeRegExp(_searchValue); + }, [_searchValue]); + + const chunks = useMemo(() => { + const regex = new RegExp(searchValue, isStrict ? 'g' : 'gi'); + const matches = [...searchSubject.matchAll(regex)].map((match) => ({ + start: match.index || 0, + end: (match.index || 0) + match[0].length, + })); + + return fillInChunks(matches, searchSubject.length); + }, [searchValue, searchSubject, isStrict]); + + return ( + <> + {chunks.map((chunk) => { + const { end, highlight, start } = chunk; + const value = searchSubject.substring(start, end); + + return highlight ? ( + {value} + ) : ( + value + ); + })} + + ); +}; + +/** + * Chunk utility + */ + +interface EuiHighlightChunk { + /** + * Start of the chunk + */ + start: number; + /** + * End of the chunk + */ + end: number; + /** + * Whether to highlight chunk or not + */ + highlight?: boolean; +} +const fillInChunks = ( + chunksToHighlight: EuiHighlightChunk[], + totalLength: number +) => { + const allChunks: EuiHighlightChunk[] = []; + const append = (start: number, end: number, highlight: boolean) => { + if (end - start > 0) { + allChunks.push({ + start, + end, + highlight, + }); + } + }; + if (chunksToHighlight.length === 0) { + append(0, totalLength, false); + } else { + let lastIndex = 0; + chunksToHighlight.forEach((chunk) => { + append(lastIndex, chunk.start, false); + append(chunk.start, chunk.end, true); + lastIndex = chunk.end; + }); + append(lastIndex, totalLength, false); + } + return allChunks; +}; diff --git a/src/components/highlight/_highlight_first.tsx b/src/components/highlight/_highlight_first.tsx new file mode 100644 index 00000000000..2b9bbb06c03 --- /dev/null +++ b/src/components/highlight/_highlight_first.tsx @@ -0,0 +1,59 @@ +/* + * 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 React, { FunctionComponent } from 'react'; + +import { _SharedSubcomponentProps } from './highlight'; + +/** + * Internal subcomponent with logic for highlighting only the first occurrence + * of a search value within a subject + * + * Uses indexOf for performance (which does matter for, e.g. EuiSelectable searching) + */ +export const HighlightFirst: FunctionComponent<_SharedSubcomponentProps> = ({ + searchSubject, + searchValue, + isStrict, + highlightComponent: HighlightComponent = 'mark', +}) => { + if (Array.isArray(searchValue)) { + throw new Error( + 'Cannot parse multiple search strings without `highlightAll` enabled' + ); + } + + const normalizedSearchSubject = isStrict + ? searchSubject + : searchSubject.toLowerCase(); + const normalizedSearchValue = isStrict + ? searchValue + : searchValue.toLowerCase(); + + const indexOfMatch = normalizedSearchSubject.indexOf(normalizedSearchValue); + if (indexOfMatch === -1) { + return <>{searchSubject}; + } + + const preMatch = searchSubject.substring(0, indexOfMatch); + const match = searchSubject.substring( + indexOfMatch, + indexOfMatch + searchValue.length + ); + const postMatch = searchSubject.substring(indexOfMatch + searchValue.length); + + return ( + // Note: React 16/17 will render empty strings in the DOM. The + // `|| undefined` prevents this & keeps snapshots the same for all versions + <> + {preMatch || undefined} + {match} + {postMatch || undefined} + + ); +}; diff --git a/src/components/highlight/highlight.test.tsx b/src/components/highlight/highlight.test.tsx index 41f8512fc36..8cdc9bd0670 100644 --- a/src/components/highlight/highlight.test.tsx +++ b/src/components/highlight/highlight.test.tsx @@ -15,7 +15,7 @@ import { EuiHighlight } from './highlight'; describe('EuiHighlight', () => { test('is rendered', () => { const { container } = render( - + value ); @@ -30,7 +30,8 @@ describe('EuiHighlight', () => { match match match ); - expect(container.firstChild).toMatchSnapshot(); + expect(container.querySelectorAll('mark')).toHaveLength(1); + expect(container.querySelector('mark')).toHaveTextContent('match'); }); test('applies to all matches', () => { @@ -40,21 +41,32 @@ describe('EuiHighlight', () => { ); - expect(container.firstChild).toMatchSnapshot(); + expect(container.querySelectorAll('mark')).toHaveLength(3); }); - test('hasScreenReaderHelpText can be false', () => { - const { container } = render( - - match match match - - ); + describe('array of search strings', () => { + it('returns results for each word in the array', () => { + const { container } = render( + + The quick brown fox jumped over the lazy dog + + ); - expect(container.firstChild).toMatchSnapshot(); + const results = container.querySelectorAll('mark'); + expect(results).toHaveLength(2); + expect(results[0]).toHaveTextContent('fox'); + expect(results[1]).toHaveTextContent('dog'); + }); + + it('throws an error if `highlightAll` is not set', () => { + expect(() => + render( + + The quick brown fox jumped over the lazy dog + + ) + ).toThrow(); + }); }); }); @@ -64,7 +76,7 @@ describe('EuiHighlight', () => { different case match ); - expect(container.firstChild).toMatchSnapshot(); + expect(container.querySelector('mark')).toBeInTheDocument(); }); }); @@ -76,8 +88,30 @@ describe('EuiHighlight', () => { ); - expect(container.firstChild).toMatchSnapshot(); + expect(container.querySelector('mark')).not.toBeInTheDocument(); }); }); + + it('does not parse regex characters', () => { + const { container } = render( + + match match match + + ); + + expect(container.querySelector('mark')).not.toBeInTheDocument(); + }); + }); + + test('hasScreenReaderHelpText can be false', () => { + const { container } = render( + + match match match + + ); + + expect(container.querySelector('mark')!.className).not.toContain( + 'hasScreenReaderHelpText' + ); }); }); diff --git a/src/components/highlight/highlight.tsx b/src/components/highlight/highlight.tsx index b939deeef68..f8f12e699f0 100644 --- a/src/components/highlight/highlight.tsx +++ b/src/components/highlight/highlight.tsx @@ -6,29 +6,21 @@ * Side Public License, v 1. */ -import React, { Fragment, HTMLAttributes, FunctionComponent } from 'react'; +import React, { + HTMLAttributes, + FunctionComponent, + ElementType, + useMemo, +} from 'react'; + import { CommonProps } from '../common'; import { EuiMark, EuiMarkProps } from '../mark'; -interface EuiHighlightChunk { - /** - * Start of the chunk - */ - start: number; - /** - * End of the chunk - */ - end: number; - /** - * Whether to highlight chunk or not - */ - highlight?: boolean; -} - -type EuiMarkPropHelpText = Pick; +import { HighlightAll } from './_highlight_all'; +import { HighlightFirst } from './_highlight_first'; export type EuiHighlightProps = HTMLAttributes & - EuiMarkPropHelpText & + Pick & CommonProps & { /** * string to highlight as this component's content @@ -36,9 +28,12 @@ export type EuiHighlightProps = HTMLAttributes & children: string; /** - * What to search for + * What to search for. + * + * Allows passing an array of strings (searching by multiple separate + * words or phrases) **only** if `highlightAll` is also set to `true`. */ - search: string; + search: string | string[]; /** * Should the search be strict or not @@ -51,123 +46,6 @@ export type EuiHighlightProps = HTMLAttributes & highlightAll?: boolean; }; -const highlight = ( - searchSubject: string, - searchValue: string, - isStrict: boolean, - highlightAll: boolean, - hasScreenReaderHelpText: boolean -) => { - if (!searchValue) { - return searchSubject; - } - - if (!searchSubject) { - return null; - } - - if (highlightAll) { - const chunks = getHightlightWords(searchSubject, searchValue, isStrict); - return ( - - {chunks.map((chunk) => { - const { end, highlight, start } = chunk; - const value = searchSubject.substring(start, end); - if (highlight) { - return ( - - {value} - - ); - } - return value; - })} - - ); - } - - const normalizedSearchSubject: string = isStrict - ? searchSubject - : searchSubject.toLowerCase(); - const normalizedSearchValue: string = isStrict - ? searchValue - : searchValue.toLowerCase(); - - const indexOfMatch: number = normalizedSearchSubject.indexOf( - normalizedSearchValue - ); - if (indexOfMatch === -1) { - return searchSubject; - } - - const preMatch: string = searchSubject.substring(0, indexOfMatch); - const match: string = searchSubject.substring( - indexOfMatch, - indexOfMatch + searchValue.length - ); - const postMatch: string = searchSubject.substring( - indexOfMatch + searchValue.length - ); - - return ( - - {preMatch || undefined} - - {match} - - {postMatch || undefined} - - ); -}; - -const getHightlightWords = ( - searchSubject: string, - searchValue: string, - isStrict: boolean -) => { - const regex = new RegExp(searchValue, isStrict ? 'g' : 'gi'); - const matches = []; - let match; - while ((match = regex.exec(searchSubject)) !== null) { - matches.push({ - start: match.index, - end: (match.index || 0) + match[0].length, - }); - } - return fillInChunks(matches, searchSubject.length); -}; - -const fillInChunks = ( - chunksToHighlight: EuiHighlightChunk[], - totalLength: number -) => { - const allChunks: EuiHighlightChunk[] = []; - const append = (start: number, end: number, highlight: boolean) => { - if (end - start > 0) { - allChunks.push({ - start, - end, - highlight, - }); - } - }; - if (chunksToHighlight.length === 0) { - append(0, totalLength, false); - } else { - let lastIndex = 0; - chunksToHighlight.forEach((chunk) => { - append(lastIndex, chunk.start, false); - append(chunk.start, chunk.end, true); - lastIndex = chunk.end; - }); - append(lastIndex, totalLength, false); - } - return allChunks; -}; - export const EuiHighlight: FunctionComponent = ({ children, className, @@ -177,15 +55,48 @@ export const EuiHighlight: FunctionComponent = ({ hasScreenReaderHelpText = true, ...rest }) => { + const hasSearch = search && search.length > 0; + + const HighlightComponent = useMemo(() => { + const Component: FunctionComponent<{ children: string }> = ({ + children, + }) => ( + + {children} + + ); + Component.displayName = '_HighlightComponent'; + return Component; + }, [hasScreenReaderHelpText]); + return ( - {highlight( - children, - search, - strict, - highlightAll, - hasScreenReaderHelpText + {children && hasSearch ? ( + highlightAll ? ( + + ) : ( + + ) + ) : ( + children )} ); }; + +export type _SharedSubcomponentProps = { + searchValue: EuiHighlightProps['search']; + searchSubject: EuiHighlightProps['children']; + isStrict: EuiHighlightProps['strict']; + highlightComponent?: ElementType; +}; diff --git a/src/components/index.scss b/src/components/index.scss index 0cba396fc05..251ea2bdb7d 100644 --- a/src/components/index.scss +++ b/src/components/index.scss @@ -4,11 +4,9 @@ @import 'combo_box/index'; @import 'date_picker/index'; @import 'datagrid/index'; -@import 'empty_prompt/index'; @import 'form/index'; @import 'markdown_editor/index'; @import 'tree_view/index'; @import 'side_nav/index'; -@import 'search_bar/index'; @import 'selectable/index'; @import 'table/index'; diff --git a/src/components/page_template/empty_prompt/__snapshots__/page_empty_prompt.test.tsx.snap b/src/components/page_template/empty_prompt/__snapshots__/page_empty_prompt.test.tsx.snap index 60e931b2974..e9932078add 100644 --- a/src/components/page_template/empty_prompt/__snapshots__/page_empty_prompt.test.tsx.snap +++ b/src/components/page_template/empty_prompt/__snapshots__/page_empty_prompt.test.tsx.snap @@ -8,13 +8,13 @@ exports[`_EuiPageEmptyPrompt EuiEmptyPromptProps is rendered 1`] = ` class="emotion-euiPageSection__content-l-center" >
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-horizontal" + />
@@ -47,18 +43,14 @@ exports[`_EuiPageEmptyPrompt EuiPageSectionProps is rendered 1`] = ` class="emotion-euiPageSection__content-l-center" >
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
@@ -74,19 +66,15 @@ exports[`_EuiPageEmptyPrompt is rendered 1`] = ` >
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
@@ -101,18 +89,14 @@ exports[`_EuiPageEmptyPrompt paddingSize l is rendered 1`] = ` class="emotion-euiPageSection__content-l-center" >
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
@@ -127,18 +111,14 @@ exports[`_EuiPageEmptyPrompt paddingSize m is rendered 1`] = ` class="emotion-euiPageSection__content-m-center" >
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
@@ -153,18 +133,14 @@ exports[`_EuiPageEmptyPrompt paddingSize none is rendered 1`] = ` class="emotion-euiPageSection__content-center" >
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
@@ -179,18 +155,14 @@ exports[`_EuiPageEmptyPrompt paddingSize s is rendered 1`] = ` class="emotion-euiPageSection__content-s-center" >
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
@@ -205,18 +177,14 @@ exports[`_EuiPageEmptyPrompt paddingSize xl is rendered 1`] = ` class="emotion-euiPageSection__content-xl-center" >
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
@@ -231,18 +199,14 @@ exports[`_EuiPageEmptyPrompt paddingSize xs is rendered 1`] = ` class="emotion-euiPageSection__content-xs-center" >
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
@@ -257,18 +221,14 @@ exports[`_EuiPageEmptyPrompt panelled is false and color is defined, then the pr class="emotion-euiPageSection__content-l-center" >
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
@@ -283,18 +243,14 @@ exports[`_EuiPageEmptyPrompt panelled is false and color is not defined, then th class="emotion-euiPageSection__content-l-center" >
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
@@ -309,18 +265,14 @@ exports[`_EuiPageEmptyPrompt panelled is true and color is defined, then the pro class="emotion-euiPageSection__content-l-center" >
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
@@ -335,18 +287,14 @@ exports[`_EuiPageEmptyPrompt panelled is true and color is not defined, then the class="emotion-euiPageSection__content-l-center" >
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
@@ -362,18 +310,14 @@ exports[`_EuiPageEmptyPrompt restrict width can be set to a custom number 1`] = style="max-width: 1024px;" >
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
@@ -389,18 +333,14 @@ exports[`_EuiPageEmptyPrompt restrict width can be set to a custom value and mea style="max-width: 24rem;" >
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
@@ -416,18 +356,14 @@ exports[`_EuiPageEmptyPrompt restrict width can be set to a default 1`] = ` style="max-width: 1200px;" >
-
-
+ class="euiEmptyPrompt__content emotion-euiEmptyPrompt__content-vertical" + />
diff --git a/src/components/search_bar/__snapshots__/search_bar.test.tsx.snap b/src/components/search_bar/__snapshots__/search_bar.test.tsx.snap index 98f6fb63890..2b176ab4d9e 100644 --- a/src/components/search_bar/__snapshots__/search_bar.test.tsx.snap +++ b/src/components/search_bar/__snapshots__/search_bar.test.tsx.snap @@ -5,7 +5,7 @@ exports[`SearchBar render - box 1`] = ` class="euiFlexGroup emotion-euiFlexGroup-responsive-wrap-m-flexStart-center-row" >
{ + const { maxWidth } = euiFormVariables(euiThemeContext); + return css` + ${logicalCSS( + 'min-width', + mathWithUnits(maxWidth, (x) => x / 2) + )} + `; +}; + +export const euiSearchBar__filtersHolder = (euiThemeContext: UseEuiTheme) => { + const { euiTheme } = euiThemeContext; + return css` + ${euiBreakpoint(euiThemeContext, ['m', 'l', 'xl'])} { + /* Helps with flex-wrapping */ + ${logicalCSS('max-width', `calc(100% - ${euiTheme.size.base})`)} + } + `; +}; diff --git a/src/components/search_bar/search_bar.test.tsx b/src/components/search_bar/search_bar.test.tsx index 1b5953b9545..77dc9a151d4 100644 --- a/src/components/search_bar/search_bar.test.tsx +++ b/src/components/search_bar/search_bar.test.tsx @@ -7,8 +7,7 @@ */ import React, { useState } from 'react'; -import { act } from '@testing-library/react'; -import { mount } from 'enzyme'; +import { fireEvent, act } from '@testing-library/react'; import { render } from '../../test/rtl'; import { requiredProps } from '../../test'; @@ -92,7 +91,7 @@ describe('SearchBar', () => { test('calls onChange callback when the query is modified', () => { const onChange = jest.fn(); - const component = mount( + const { getByTestSubject } = render( { /> ); - component.find('input[data-test-subj="searchbar"]').simulate('keyup', { + fireEvent.keyUp(getByTestSubject('searchbar'), { key: keys.ENTER, target: { value: 'status:inactive' }, }); @@ -114,7 +113,7 @@ describe('SearchBar', () => { describe('hint', () => { test('renders a hint below the search bar on focus', () => { - const component = mount( + const { getByTestSubject, queryByTestSubject } = render( { /> ); - const getHint = () => component.find('[data-test-subj="myHint"]'); - - let hint = getHint(); - expect(hint.length).toBe(0); + expect(queryByTestSubject('myHint')).toBeNull(); act(() => { - component.find('input[data-test-subj="searchbar"]').simulate('focus'); + fireEvent.focus(getByTestSubject('searchbar')); }); - component.update(); - hint = getHint(); - expect(hint.length).toBe(1); - expect(hint.text()).toBe('Hello from hint'); + expect(queryByTestSubject('myHint')).toBeInTheDocument(); + expect(queryByTestSubject('myHint')).toHaveTextContent('Hello from hint'); }); test('control the visibility of the hint', () => { @@ -164,28 +158,22 @@ describe('SearchBar', () => { ); }; - const component = mount(); - const getHint = () => component.find('[data-test-subj="myHint"]'); + const { getByTestSubject, queryByTestSubject } = render(); - let hint = getHint(); - expect(hint.length).toBe(0); + expect(queryByTestSubject('myHint')).toBeNull(); // Not visible on focus as it is controlled act(() => { - component.find('input[data-test-subj="searchbar"]').simulate('focus'); + fireEvent.focus(getByTestSubject('searchbar')); }); - component.update(); - hint = getHint(); - expect(hint.length).toBe(0); // Not visible on focus as it is controlled + expect(queryByTestSubject('myHint')).toBeNull(); // Not visible on focus as it is controlled act(() => { - component.find('[data-test-subj="showHintBtn"]').simulate('click'); + fireEvent.click(getByTestSubject('showHintBtn')); }); - component.update(); - hint = getHint(); - expect(hint.length).toBe(1); - expect(hint.text()).toBe('Hello from hint'); + expect(queryByTestSubject('myHint')).toBeInTheDocument(); + expect(queryByTestSubject('myHint')).toHaveTextContent('Hello from hint'); }); }); }); diff --git a/src/components/search_bar/search_bar.tsx b/src/components/search_bar/search_bar.tsx index 783440d90f0..046e8eaeb0b 100644 --- a/src/components/search_bar/search_bar.tsx +++ b/src/components/search_bar/search_bar.tsx @@ -8,7 +8,7 @@ import React, { Component, ReactElement } from 'react'; -import { htmlIdGenerator } from '../../services/accessibility'; +import { RenderWithEuiTheme, htmlIdGenerator } from '../../services'; import { isString } from '../../services/predicate'; import { EuiFlexGroup, EuiFlexItem } from '../flex'; import { EuiSearchBox } from './search_box'; @@ -18,6 +18,10 @@ import { CommonProps } from '../common'; import { EuiFieldSearchProps } from '../form/field_search'; import { EuiInputPopoverProps } from '../popover'; +import { + euiSearchBar__searchHolder, + euiSearchBar__filtersHolder, +} from './search_bar.styles'; export { Query, AST as Ast } from './query'; export type QueryType = Query | string; @@ -259,48 +263,58 @@ export class EuiSearchBar extends Component { const toolsLeftEl = this.renderTools(toolsLeft); - const filtersBar = !filters ? undefined : ( - - - - ); - const toolsRightEl = this.renderTools(toolsRight); const isHintVisible = hint?.popoverProps?.isOpen ?? isHintVisibleState; return ( - - {toolsLeftEl} - - { - this.setState({ isHintVisible: isVisible }); - }, - id: this.hintId, - ...hint, - } - : undefined - } - /> - - {filtersBar} - {toolsRightEl} - + + {(euiTheme) => ( + + {toolsLeftEl} + + { + this.setState({ isHintVisible: isVisible }); + }, + id: this.hintId, + ...hint, + } + : undefined + } + /> + + {filters && ( + + + + )} + {toolsRightEl} + + )} + ); } } diff --git a/src/components/table/__snapshots__/table_footer.test.tsx.snap b/src/components/table/__snapshots__/table_footer.test.tsx.snap index 2ef311c3160..0fc2a8a0b0f 100644 --- a/src/components/table/__snapshots__/table_footer.test.tsx.snap +++ b/src/components/table/__snapshots__/table_footer.test.tsx.snap @@ -1,15 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`EuiTableFooter is rendered 1`] = ` -
+ - children + - +
+ children +
`; diff --git a/src/components/table/__snapshots__/table_footer_cell.test.tsx.snap b/src/components/table/__snapshots__/table_footer_cell.test.tsx.snap index 8b98900d655..7381fc37986 100644 --- a/src/components/table/__snapshots__/table_footer_cell.test.tsx.snap +++ b/src/components/table/__snapshots__/table_footer_cell.test.tsx.snap @@ -1,129 +1,177 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`EuiTableFooterCell align defaults to left 1`] = ` - -
- -
- + + + + + + +
+
+ +
+
`; exports[`EuiTableFooterCell align renders center when specified 1`] = ` - -
- -
- + + + + + + +
+
+ +
+
`; exports[`EuiTableFooterCell align renders right when specified 1`] = ` - -
- -
- + + + + + + +
+
+ +
+
`; exports[`EuiTableFooterCell is rendered 1`] = ` - -
- - children - -
- + + + + + + +
+
+ + children + +
+
`; exports[`EuiTableFooterCell width and style accepts style attribute 1`] = ` - -
- - Test - -
- + + + + + + +
+
+ + Test + +
+
`; exports[`EuiTableFooterCell width and style accepts width attribute 1`] = ` - -
- - Test - -
- + + + + + + +
+
+ + Test + +
+
`; exports[`EuiTableFooterCell width and style accepts width attribute as number 1`] = ` - -
- - Test - -
- + + + + + + +
+
+ + Test + +
+
`; exports[`EuiTableFooterCell width and style resolves style and width attribute 1`] = ` - -
- - Test - -
- + + + + + + +
+
+ + Test + +
+
`; diff --git a/src/components/table/__snapshots__/table_header.test.tsx.snap b/src/components/table/__snapshots__/table_header.test.tsx.snap index 518627c1afe..a23dfae2dd2 100644 --- a/src/components/table/__snapshots__/table_header.test.tsx.snap +++ b/src/components/table/__snapshots__/table_header.test.tsx.snap @@ -1,25 +1,29 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`EuiTableHeader is rendered 1`] = ` - - - - children - - - + + + + + + +
+ children +
`; exports[`EuiTableHeader is rendered without 1`] = ` - - - - children - - - + + + + + + +
+ children +
`; diff --git a/src/components/table/__snapshots__/table_header_cell.test.tsx.snap b/src/components/table/__snapshots__/table_header_cell.test.tsx.snap index 92ee353d553..e60c0af7ec8 100644 --- a/src/components/table/__snapshots__/table_header_cell.test.tsx.snap +++ b/src/components/table/__snapshots__/table_header_cell.test.tsx.snap @@ -1,270 +1,348 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`align defaults to left 1`] = ` - - - - - + + + + + + +
+ + + +
`; exports[`align renders center when specified 1`] = ` - - - - - + + + + + + +
+ + + +
`; exports[`align renders right when specified 1`] = ` - - - - - + + + + + + +
+ + + +
`; exports[`renders EuiTableHeaderCell 1`] = ` - - - - children - - - + + + + + + +
+ + + children + + +
`; exports[`renders td when children is null/undefined 1`] = ` - - - - - + + + + + + +
+ + + +
`; exports[`sorting does not render a button with readOnly 1`] = ` - - - - Test - - - - + + + + + + +
+ + + Test + + + +
`; exports[`sorting is rendered with isSortAscending 1`] = ` - - - - Test - - - - + + + + + + +
+ + + Test + + + +
`; exports[`sorting is rendered with isSorted 1`] = ` - - - - Test - - - - + + + + + + +
+ + + Test + + + +
`; exports[`sorting renders a button with onSort 1`] = ` - - - + + + + + `; exports[`width and style accepts style attribute 1`] = ` - - - - Test - - - + + + + + + +
+ + + Test + + +
`; exports[`width and style accepts width attribute 1`] = ` - - - - Test - - - + + + + + + +
+ + + Test + + +
`; exports[`width and style accepts width attribute as number 1`] = ` - - - - Test - - - + + + + + + +
+ + + Test + + +
`; exports[`width and style resolves style and width attribute 1`] = ` - - - - Test - - - + + + + + + +
+ + + Test + + +
`; diff --git a/src/components/table/__snapshots__/table_header_cell_checkbox.test.tsx.snap b/src/components/table/__snapshots__/table_header_cell_checkbox.test.tsx.snap index fa187eb71df..ecb526bbd67 100644 --- a/src/components/table/__snapshots__/table_header_cell_checkbox.test.tsx.snap +++ b/src/components/table/__snapshots__/table_header_cell_checkbox.test.tsx.snap @@ -1,70 +1,100 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`EuiTableHeaderCellCheckbox is rendered 1`] = ` - -
- + + + + + + +
+
+
`; exports[`EuiTableHeaderCellCheckbox width and style accepts style attribute 1`] = ` - -
- Test -
- + + + + + + +
+
+ Test +
+
`; exports[`EuiTableHeaderCellCheckbox width and style accepts width attribute 1`] = ` - -
- Test -
- + + + + + + +
+
+ Test +
+
`; exports[`EuiTableHeaderCellCheckbox width and style accepts width attribute as number 1`] = ` - -
- Test -
- + + + + + + +
+
+ Test +
+
`; exports[`EuiTableHeaderCellCheckbox width and style resolves style and width attribute 1`] = ` - -
- Test -
- + + + + + + +
+
+ Test +
+
`; diff --git a/src/components/table/__snapshots__/table_row.test.tsx.snap b/src/components/table/__snapshots__/table_row.test.tsx.snap index 2cd1f5c5c29..2cf34108c6f 100644 --- a/src/components/table/__snapshots__/table_row.test.tsx.snap +++ b/src/components/table/__snapshots__/table_row.test.tsx.snap @@ -1,41 +1,49 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`isSelected renders true when specified 1`] = ` - - -
+ + - -
- - + +
+ +
+ + + + `; exports[`renders EuiTableRow 1`] = ` - - -
+ + - - hi - -
- - +
+ + hi + +
+ + + + `; diff --git a/src/components/table/__snapshots__/table_row_cell.test.tsx.snap b/src/components/table/__snapshots__/table_row_cell.test.tsx.snap index 794bc452819..863bc72681c 100644 --- a/src/components/table/__snapshots__/table_row_cell.test.tsx.snap +++ b/src/components/table/__snapshots__/table_row_cell.test.tsx.snap @@ -1,251 +1,353 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`align defaults to left 1`] = ` - -
- -
- + + + + + + +
+
+ +
+
`; exports[`align renders center when specified 1`] = ` - -
- -
- + + + + + + +
+
+ +
+
`; exports[`align renders right when specified 1`] = ` - -
- -
- + + + + + + +
+
+ +
+
`; exports[`children's className merges new classnames into existing ones 1`] = ` - -
-
-
- + + + + + + +
+
+
+
+
`; exports[`renders EuiTableRowCell 1`] = ` - -
- - children - -
- + + + + + + +
+
+ + children + +
+
`; exports[`textOnly defaults to true 1`] = ` - -
- -
- + + + + + + +
+
+ +
+
`; exports[`textOnly is rendered when specified 1`] = ` - -
- + + + + + + +
+
+
`; exports[`truncateText defaults to false 1`] = ` - -
- -
- + + + + + + +
+
+ +
+
`; exports[`truncateText renders lines configuration 1`] = ` - -
- -
- + + + + + + +
+
+ +
+
`; exports[`truncateText renders true 1`] = ` - -
- -
- + + + + + + +
+
+ +
+
`; exports[`valign defaults to middle 1`] = ` - -
- -
- + + + + + + +
+
+ +
+
`; exports[`valign renders bottom when specified 1`] = ` - -
- -
- + + + + + + +
+
+ +
+
`; exports[`valign renders top when specified 1`] = ` - -
- -
- + + + + + + +
+
+ +
+
`; exports[`width and style accepts style attribute 1`] = ` - -
- - Test - -
- + + + + + + +
+
+ + Test + +
+
`; exports[`width and style accepts width attribute 1`] = ` - -
- - Test - -
- + + + + + + +
+
+ + Test + +
+
`; exports[`width and style accepts width attribute as number 1`] = ` - -
- - Test - -
- + + + + + + +
+
+ + Test + +
+
`; exports[`width and style resolves style and width attribute 1`] = ` - -
- - Test - -
- + + + + + + +
+
+ + Test + +
+
`; diff --git a/src/components/table/__snapshots__/table_row_cell_checkbox.test.tsx.snap b/src/components/table/__snapshots__/table_row_cell_checkbox.test.tsx.snap index 504e19fe842..f81ba97cd20 100644 --- a/src/components/table/__snapshots__/table_row_cell_checkbox.test.tsx.snap +++ b/src/components/table/__snapshots__/table_row_cell_checkbox.test.tsx.snap @@ -1,13 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`EuiTableRowCellCheckbox is rendered 1`] = ` - -
- + + + + + + +
+
+
`; diff --git a/src/components/table/table_footer.test.tsx b/src/components/table/table_footer.test.tsx index 22a7f395b5d..8f0318754d3 100644 --- a/src/components/table/table_footer.test.tsx +++ b/src/components/table/table_footer.test.tsx @@ -15,9 +15,13 @@ import { EuiTableFooter } from './table_footer'; describe('EuiTableFooter', () => { test('is rendered', () => { const { container } = render( - children + + + + +
children
); - expect(container).toMatchSnapshot(); + expect(container.firstChild).toMatchSnapshot(); }); }); diff --git a/src/components/table/table_footer_cell.test.tsx b/src/components/table/table_footer_cell.test.tsx index fd0d3d960db..b3581d2406a 100644 --- a/src/components/table/table_footer_cell.test.tsx +++ b/src/components/table/table_footer_cell.test.tsx @@ -12,9 +12,18 @@ import { render } from '../../test/rtl'; import { EuiTableFooterCell } from './table_footer_cell'; -import { RIGHT_ALIGNMENT, CENTER_ALIGNMENT } from '../../services'; +import { CENTER_ALIGNMENT, RIGHT_ALIGNMENT } from '../../services'; import { WARNING_MESSAGE } from './utils'; +const renderInTableFooter = (cell: React.ReactElement) => + render( + + + {cell} + +
+ ); + describe('EuiTableFooterCell', () => { const _consoleWarn = console.warn; beforeAll(() => { @@ -29,7 +38,7 @@ describe('EuiTableFooterCell', () => { }); test('is rendered', () => { - const { container } = render( + const { container } = renderInTableFooter( children ); @@ -38,13 +47,13 @@ describe('EuiTableFooterCell', () => { describe('align', () => { test('defaults to left', () => { - const { container } = render(); + const { container } = renderInTableFooter(); expect(container.firstChild).toMatchSnapshot(); }); test('renders right when specified', () => { - const { container } = render( + const { container } = renderInTableFooter( ); @@ -52,7 +61,7 @@ describe('EuiTableFooterCell', () => { }); test('renders center when specified', () => { - const { container } = render( + const { container } = renderInTableFooter( ); @@ -62,7 +71,7 @@ describe('EuiTableFooterCell', () => { describe('width and style', () => { test('accepts style attribute', () => { - const { container } = render( + const { container } = renderInTableFooter( Test ); @@ -70,7 +79,7 @@ describe('EuiTableFooterCell', () => { }); test('accepts width attribute', () => { - const { container } = render( + const { container } = renderInTableFooter( Test ); @@ -78,7 +87,7 @@ describe('EuiTableFooterCell', () => { }); test('accepts width attribute as number', () => { - const { container } = render( + const { container } = renderInTableFooter( Test ); @@ -86,7 +95,7 @@ describe('EuiTableFooterCell', () => { }); test('resolves style and width attribute', () => { - const { container } = render( + const { container } = renderInTableFooter( Test diff --git a/src/components/table/table_header.test.tsx b/src/components/table/table_header.test.tsx index 99184636b97..82704768e15 100644 --- a/src/components/table/table_header.test.tsx +++ b/src/components/table/table_header.test.tsx @@ -15,20 +15,24 @@ import { EuiTableHeader } from './table_header'; describe('EuiTableHeader', () => { test('is rendered', () => { const { container } = render( - - children - + + + + +
children
); expect(container.firstChild).toMatchSnapshot(); }); test('is rendered without ', () => { const { container } = render( - - - children - - + + + + + + +
children
); expect(container.firstChild).toMatchSnapshot(); }); diff --git a/src/components/table/table_header_cell.test.tsx b/src/components/table/table_header_cell.test.tsx index ba5cb666770..78c960b07dc 100644 --- a/src/components/table/table_header_cell.test.tsx +++ b/src/components/table/table_header_cell.test.tsx @@ -15,8 +15,17 @@ import { EuiTableHeaderCell } from './table_header_cell'; import { RIGHT_ALIGNMENT, CENTER_ALIGNMENT } from '../../services'; import { WARNING_MESSAGE } from './utils'; +const renderInTableHeader = (cell: React.ReactElement) => + render( + + + {cell} + +
+ ); + test('renders EuiTableHeaderCell', () => { - const { container } = render( + const { container } = renderInTableHeader( children ); @@ -24,20 +33,22 @@ test('renders EuiTableHeaderCell', () => { }); test('renders td when children is null/undefined', () => { - const { container } = render(); + const { container } = renderInTableHeader( + + ); expect(container.firstChild).toMatchSnapshot(); }); describe('align', () => { test('defaults to left', () => { - const { container } = render(); + const { container } = renderInTableHeader(); expect(container.firstChild).toMatchSnapshot(); }); test('renders right when specified', () => { - const { container } = render( + const { container } = renderInTableHeader( ); @@ -45,7 +56,7 @@ describe('align', () => { }); test('renders center when specified', () => { - const { container } = render( + const { container } = renderInTableHeader( ); @@ -55,7 +66,7 @@ describe('align', () => { describe('sorting', () => { test('is rendered with isSorted', () => { - const { container } = render( + const { container } = renderInTableHeader( Test ); @@ -63,7 +74,7 @@ describe('sorting', () => { }); test('is rendered with isSortAscending', () => { - const { container } = render( + const { container } = renderInTableHeader( Test @@ -73,7 +84,7 @@ describe('sorting', () => { }); test('renders a button with onSort', () => { - const { container } = render( + const { container } = renderInTableHeader( {}}> Test @@ -83,7 +94,7 @@ describe('sorting', () => { }); test('does not render a button with readOnly', () => { - const { container } = render( + const { container } = renderInTableHeader( {}}> Test @@ -107,7 +118,7 @@ describe('width and style', () => { }); test('accepts style attribute', () => { - const { container } = render( + const { container } = renderInTableHeader( Test ); @@ -115,7 +126,7 @@ describe('width and style', () => { }); test('accepts width attribute', () => { - const { container } = render( + const { container } = renderInTableHeader( Test ); @@ -123,7 +134,7 @@ describe('width and style', () => { }); test('accepts width attribute as number', () => { - const { container } = render( + const { container } = renderInTableHeader( Test ); @@ -131,7 +142,7 @@ describe('width and style', () => { }); test('resolves style and width attribute', () => { - const { container } = render( + const { container } = renderInTableHeader( Test diff --git a/src/components/table/table_header_cell_checkbox.test.tsx b/src/components/table/table_header_cell_checkbox.test.tsx index 30f06c38fb5..e1a5a4486c1 100644 --- a/src/components/table/table_header_cell_checkbox.test.tsx +++ b/src/components/table/table_header_cell_checkbox.test.tsx @@ -13,6 +13,15 @@ import { render } from '../../test/rtl'; import { EuiTableHeaderCellCheckbox } from './table_header_cell_checkbox'; import { WARNING_MESSAGE } from './utils'; +const renderInTableHeader = (cell: React.ReactElement) => + render( + + + {cell} + +
+ ); + describe('EuiTableHeaderCellCheckbox', () => { const _consoleWarn = console.warn; beforeAll(() => { @@ -27,7 +36,7 @@ describe('EuiTableHeaderCellCheckbox', () => { }); test('is rendered', () => { - const { container } = render( + const { container } = renderInTableHeader( ); @@ -36,7 +45,7 @@ describe('EuiTableHeaderCellCheckbox', () => { describe('width and style', () => { test('accepts style attribute', () => { - const { container } = render( + const { container } = renderInTableHeader( Test @@ -46,7 +55,7 @@ describe('EuiTableHeaderCellCheckbox', () => { }); test('accepts width attribute', () => { - const { container } = render( + const { container } = renderInTableHeader( Test @@ -56,7 +65,7 @@ describe('EuiTableHeaderCellCheckbox', () => { }); test('accepts width attribute as number', () => { - const { container } = render( + const { container } = renderInTableHeader( Test @@ -66,7 +75,7 @@ describe('EuiTableHeaderCellCheckbox', () => { }); test('resolves style and width attribute', () => { - const { container } = render( + const { container } = renderInTableHeader( Test diff --git a/src/components/table/table_row.test.tsx b/src/components/table/table_row.test.tsx index ba7e68e029b..945df026f60 100644 --- a/src/components/table/table_row.test.tsx +++ b/src/components/table/table_row.test.tsx @@ -14,8 +14,15 @@ import { EuiTableRow } from './table_row'; import { EuiTableRowCell } from './table_row_cell'; +const renderInTable = (row: React.ReactElement) => + render( + + {row} +
+ ); + test('renders EuiTableRow', () => { - const { container } = render( + const { container } = renderInTable( hi @@ -26,7 +33,7 @@ test('renders EuiTableRow', () => { describe('isSelected', () => { test('renders true when specified', () => { - const { container } = render( + const { container } = renderInTable( diff --git a/src/components/table/table_row_cell.test.tsx b/src/components/table/table_row_cell.test.tsx index 1a3ef10ba43..b2f5a315a44 100644 --- a/src/components/table/table_row_cell.test.tsx +++ b/src/components/table/table_row_cell.test.tsx @@ -12,11 +12,20 @@ import { render } from '../../test/rtl'; import { EuiTableRowCell } from './table_row_cell'; -import { RIGHT_ALIGNMENT, CENTER_ALIGNMENT } from '../../services/alignment'; +import { CENTER_ALIGNMENT, RIGHT_ALIGNMENT } from '../../services/alignment'; import { WARNING_MESSAGE } from './utils'; +const renderInTableRow = (cell: React.ReactElement) => + render( + + + {cell} + +
+ ); + test('renders EuiTableRowCell', () => { - const { container } = render( + const { container } = renderInTableRow( children ); @@ -25,19 +34,23 @@ test('renders EuiTableRowCell', () => { describe('align', () => { test('defaults to left', () => { - const { container } = render(); + const { container } = renderInTableRow(); expect(container.firstChild).toMatchSnapshot(); }); test('renders right when specified', () => { - const { container } = render(); + const { container } = renderInTableRow( + + ); expect(container.firstChild).toMatchSnapshot(); }); test('renders center when specified', () => { - const { container } = render(); + const { container } = renderInTableRow( + + ); expect(container.firstChild).toMatchSnapshot(); }); @@ -45,19 +58,19 @@ describe('align', () => { describe('valign', () => { test('defaults to middle', () => { - const { container } = render(); + const { container } = renderInTableRow(); expect(container.firstChild).toMatchSnapshot(); }); test('renders top when specified', () => { - const { container } = render(); + const { container } = renderInTableRow(); expect(container.firstChild).toMatchSnapshot(); }); test('renders bottom when specified', () => { - const { container } = render(); + const { container } = renderInTableRow(); expect(container.firstChild).toMatchSnapshot(); }); @@ -65,13 +78,15 @@ describe('valign', () => { describe('textOnly', () => { test('defaults to true', () => { - const { container } = render(); + const { container } = renderInTableRow(); expect(container.firstChild).toMatchSnapshot(); }); test('is rendered when specified', () => { - const { container } = render(); + const { container } = renderInTableRow( + + ); expect(container.firstChild).toMatchSnapshot(); }); @@ -79,13 +94,15 @@ describe('textOnly', () => { describe('truncateText', () => { it('defaults to false', () => { - const { container } = render(); + const { container } = renderInTableRow(); expect(container.firstChild).toMatchSnapshot(); }); it('renders true', () => { - const { container } = render(); + const { container } = renderInTableRow( + + ); expect( container.querySelector('.euiTableCellContent--truncateText') @@ -94,7 +111,7 @@ describe('truncateText', () => { }); test('renders lines configuration', () => { - const { container } = render( + const { container } = renderInTableRow( ); @@ -110,7 +127,7 @@ describe('truncateText', () => { describe("children's className", () => { test('merges new classnames into existing ones', () => { - const { container } = render( + const { container } = renderInTableRow(
@@ -134,7 +151,7 @@ describe('width and style', () => { }); test('accepts style attribute', () => { - const { container } = render( + const { container } = renderInTableRow( Test ); @@ -142,7 +159,7 @@ describe('width and style', () => { }); test('accepts width attribute', () => { - const { container } = render( + const { container } = renderInTableRow( Test ); @@ -150,7 +167,7 @@ describe('width and style', () => { }); test('accepts width attribute as number', () => { - const { container } = render( + const { container } = renderInTableRow( Test ); @@ -158,7 +175,7 @@ describe('width and style', () => { }); test('resolves style and width attribute', () => { - const { container } = render( + const { container } = renderInTableRow( Test diff --git a/src/components/table/table_row_cell_checkbox.test.tsx b/src/components/table/table_row_cell_checkbox.test.tsx index 1f343495025..ac19e4c8263 100644 --- a/src/components/table/table_row_cell_checkbox.test.tsx +++ b/src/components/table/table_row_cell_checkbox.test.tsx @@ -15,7 +15,13 @@ import { EuiTableRowCellCheckbox } from './table_row_cell_checkbox'; describe('EuiTableRowCellCheckbox', () => { test('is rendered', () => { const { container } = render( - + + + + + + +
); expect(container.firstChild).toMatchSnapshot(); diff --git a/src/components/tour/__snapshots__/tour_step.test.tsx.snap b/src/components/tour/__snapshots__/tour_step.test.tsx.snap index 4ba2a0953aa..0aa353e4849 100644 --- a/src/components/tour/__snapshots__/tour_step.test.tsx.snap +++ b/src/components/tour/__snapshots__/tour_step.test.tsx.snap @@ -1,565 +1,99 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`EuiTourStep accepts an array of buttons in the footerAction prop 1`] = ` -
- - Test - -
-
+exports[`EuiTourStep renders 1`] = ` + +
-
-
-`; - -exports[`EuiTourStep can be closed 1`] = ` -
- - Test - -
-`; - -exports[`EuiTourStep can change the minWidth and maxWidth 1`] = ` -
- - Test - -
-
- -
-
-`; - -exports[`EuiTourStep can have subtitle 1`] = ` -
- - Test - -
-
-