diff --git a/CHANGELOG.md b/CHANGELOG.md index 67df1dd1ca3..43a68571e89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ - Added `placeholder` prop to `EuiMarkdownEditor` ([#5151](https://github.com/elastic/eui/pull/5151)) - Added `.eui-textNumber` utility class to apply `tnum` font-feature setting ([#5078](https://github.com/elastic/eui/pull/5078)) +- Changed `EuiPageHeader`'s tab implementation to use size `xl` when only content ([#5135](https://github.com/elastic/eui/pull/5135)) +- Added `pageTitleProps` prop to `EuiPageHeader` to pass through props to the `EuiTitle` ([#5135](https://github.com/elastic/eui/pull/5135)) +- Added screen-reader only `

` to `EuiPageHeader` when tabs exist without a `pageTitle` ([#5135](https://github.com/elastic/eui/pull/5135)) +- Added `bottomBorder` prop and `xl` `size` to `EuiTabs` ([#5135](https://github.com/elastic/eui/pull/5135)) +- Added `prepend` and `append` props to `EuiTab` ([#5135](https://github.com/elastic/eui/pull/5135)) +- Refactored styles of `EuiTabs` ([#5135](https://github.com/elastic/eui/pull/5135)) +- Removed Sass variables for `EuiTabs` font size (`$euiTabFontSize, $euiTabFontSizeS, $euiTabFontSizeL`) ([#5135](https://github.com/elastic/eui/pull/5135)) +- Extended all `EuiTabProps` for each `EuiTabbedContentTab` ([#5135](https://github.com/elastic/eui/pull/5135)) + +**Theme: Amsterdam** + +- Deprecated `display` prop of `EuiTabs` in favor of unified styles and `bottomBorder` ([#5135](https://github.com/elastic/eui/pull/5135)) ## [`37.6.2`](https://github.com/elastic/eui/tree/v37.6.2) diff --git a/src-docs/src/components/guide_section/_guide_section.scss b/src-docs/src/components/guide_section/_guide_section.scss index 022f0ae64db..1346f16cccd 100644 --- a/src-docs/src/components/guide_section/_guide_section.scss +++ b/src-docs/src/components/guide_section/_guide_section.scss @@ -27,7 +27,7 @@ } } -.guideSectionTabs__tab { +.guideSectionTabs__tab > * { font-weight: $euiFontWeightMedium !important; // sass-lint:disable-line no-important } diff --git a/src-docs/src/views/page_header/page_header.js b/src-docs/src/views/page_header/page_header.js deleted file mode 100644 index 1d6c026f950..00000000000 --- a/src-docs/src/views/page_header/page_header.js +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; - -import { - EuiPageHeader, - EuiCode, - EuiText, - EuiButton, -} from '../../../../src/components'; - -export default () => ( - Add something, - Do something, - ]} - > - -

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

-
-
-); diff --git a/src-docs/src/views/page_header/page_header.tsx b/src-docs/src/views/page_header/page_header.tsx new file mode 100644 index 00000000000..b20247d11e5 --- /dev/null +++ b/src-docs/src/views/page_header/page_header.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { EuiPageHeader, EuiButton } from '../../../../src/components'; + +export default () => ( + Add something, + Do something, + ]} + /> +); diff --git a/src-docs/src/views/page_header/page_header_custom.js b/src-docs/src/views/page_header/page_header_custom.tsx similarity index 100% rename from src-docs/src/views/page_header/page_header_custom.js rename to src-docs/src/views/page_header/page_header_custom.tsx diff --git a/src-docs/src/views/page_header/page_header_example.js b/src-docs/src/views/page_header/page_header_example.js index df27da36805..bdd87823076 100644 --- a/src-docs/src/views/page_header/page_header_example.js +++ b/src-docs/src/views/page_header/page_header_example.js @@ -16,6 +16,8 @@ const pageHeaderSource = require('!!raw-loader!./page_header'); import PageHeaderTabs from './page_header_tabs'; const pageHeaderTabsSource = require('!!raw-loader!./page_header_tabs'); +import PageHeaderTabsTitle from './page_header_tabs_title'; +const pageHeaderTabsTitleSource = require('!!raw-loader!./page_header_tabs_title'); import PageHeaderCustom from './page_header_custom'; import { EuiText } from '../../../../src/components/text'; @@ -76,10 +78,6 @@ export const PageHeaderExample = { props: { EuiPageHeader }, snippet: `Button 1, @@ -89,6 +87,42 @@ export const PageHeaderExample = { }, { title: 'Tabs in the page header', + source: [ + { + type: GuideSectionTypes.JS, + code: pageHeaderTabsTitleSource, + }, + ], + text: ( + <> +

+ A set of tabs can be displayed inside the page + header by passing an array of{' '} + + EuiTab + {' '} + objects using the label key for the tab content. + When displaying tabs it is recommended to also apply the{' '} + bottomBorder prop (on the{' '} + EuiPageHeader component not through{' '} + tabsProps) to create separation of the header and + content. You'll still need to manage the page content and + selected tab in your own instance. +

+ + ), + demo: , + props: { EuiPageHeader }, + snippet: ``, + }, + { source: [ { type: GuideSectionTypes.JS, @@ -102,7 +136,8 @@ export const PageHeaderExample = { pageTitle, EuiPageHeader will promote those tabs as if they are the page title. This means that any description or children{' '} - will sit below the tabs. + will sit below the tabs and should be describing + the currently selected tab.

), @@ -113,6 +148,7 @@ export const PageHeaderExample = { { label:"Tab 1", isSelected: true }, { label:"Tab 2" } ]} + bottomBorder description="Example of a description." />`, }, diff --git a/src-docs/src/views/page_header/page_header_tabs.js b/src-docs/src/views/page_header/page_header_tabs.js deleted file mode 100644 index 439018f814f..00000000000 --- a/src-docs/src/views/page_header/page_header_tabs.js +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; - -import { - EuiPageHeader, - EuiText, - EuiButton, - EuiCode, -} from '../../../../src/components'; - -export default () => ( - Add something, - Do something, - ]} - > - -

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

-
-
-); diff --git a/src-docs/src/views/page_header/page_header_tabs.tsx b/src-docs/src/views/page_header/page_header_tabs.tsx new file mode 100644 index 00000000000..9398ac7ef4f --- /dev/null +++ b/src-docs/src/views/page_header/page_header_tabs.tsx @@ -0,0 +1,19 @@ +import React from 'react'; + +import { EuiPageHeader } from '../../../../src/components'; + +export default () => ( + +); diff --git a/src-docs/src/views/page_header/page_header_tabs_title.tsx b/src-docs/src/views/page_header/page_header_tabs_title.tsx new file mode 100644 index 00000000000..35b44e08bcd --- /dev/null +++ b/src-docs/src/views/page_header/page_header_tabs_title.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +import { EuiPageHeader, EuiButton } from '../../../../src/components'; + +export default () => ( + Add something, + Do something, + ]} + bottomBorder + /> +); diff --git a/src-docs/src/views/page_header/playground.js b/src-docs/src/views/page_header/playground.js index 4b89147ad21..6dd833b84de 100644 --- a/src-docs/src/views/page_header/playground.js +++ b/src-docs/src/views/page_header/playground.js @@ -56,6 +56,16 @@ export const pageHeaderConfig = () => { type: PropTypes.String, }; + propsToUse.alignItems = { + ...propsToUse.alignItems, + value: 'top', + }; + + propsToUse.restrictWidth = { + ...propsToUse.restrictWidth, + type: PropTypes.Number, + }; + propsToUse.rightSideItems = simulateFunction({ ...propsToUse.rightSideItems, custom: { diff --git a/src-docs/src/views/tabs/controlled.js b/src-docs/src/views/tabs/controlled.tsx similarity index 50% rename from src-docs/src/views/tabs/controlled.js rename to src-docs/src/views/tabs/controlled.tsx index d3e8b254f32..d4ff9e0ef80 100644 --- a/src-docs/src/views/tabs/controlled.js +++ b/src-docs/src/views/tabs/controlled.tsx @@ -4,27 +4,27 @@ import { EuiButton, EuiIcon, EuiTabbedContent, - EuiTitle, EuiText, EuiSpacer, + EuiTabbedContentProps, + EuiTabbedContentTab, } from '../../../../src/components'; -const tabs = [ +const tabs: EuiTabbedContentProps['tabs'] = [ { id: 'cobalt', name: 'Cobalt', content: ( - -

Cobalt

-
- Cobalt is a chemical element with symbol Co and atomic number 27. Like - nickel, cobalt is found in the Earth’s crust only in chemically - combined form, save for small deposits found in alloys of natural - meteoric iron. The free element, produced by reductive smelting, is a - hard, lustrous, silver-gray metal. +

+ Cobalt is a chemical element with symbol Co and atomic number 27. + Like nickel, cobalt is found in the Earth’s crust only in + chemically combined form, save for small deposits found in alloys of + natural meteoric iron. The free element, produced by reductive + smelting, is a hard, lustrous, silver-gray metal. +

), @@ -35,35 +35,29 @@ const tabs = [ content: ( - -

Dextrose

-
- Intravenous sugar solution, also known as dextrose solution, is a - mixture of dextrose (glucose) and water. It is used to treat low blood - sugar or water loss without electrolyte loss. +

+ Intravenous sugar solution, also known as dextrose solution, is a + mixture of dextrose (glucose) and water. It is used to treat low + blood sugar or water loss without electrolyte loss. +

), }, { id: 'hydrogen', - name: ( - - -  Hydrogen - - ), + name: 'Hydrogen', + prepend: , content: ( - -

Hydrogen

-
- Hydrogen is a chemical element with symbol H and atomic number 1. With - a standard atomic weight of 1.008, hydrogen is the lightest element on - the periodic table +

+ Hydrogen is a chemical element with symbol H and atomic number 1. + With a standard atomic weight of 1.008, hydrogen is the lightest + element on the periodic table +

), @@ -74,14 +68,13 @@ const tabs = [ content: ( - -

Monosodium Glutamate

-
- Monosodium glutamate (MSG, also known as sodium glutamate) is the - sodium salt of glutamic acid, one of the most abundant naturally - occurring non-essential amino acids. Monosodium glutamate is found - naturally in tomatoes, cheese and other foods. +

+ Monosodium glutamate (MSG, also known as sodium glutamate) is the + sodium salt of glutamic acid, one of the most abundant naturally + occurring non-essential amino acids. Monosodium glutamate is found + naturally in tomatoes, cheese and other foods. +

), @@ -91,7 +84,7 @@ const tabs = [ export default () => { const [selectedTab, setSelectedTab] = useState(tabs[1]); - const onTabClick = (selectedTab) => { + const onTabClick = (selectedTab: EuiTabbedContentTab) => { setSelectedTab(selectedTab); }; diff --git a/src-docs/src/views/tabs/playground.js b/src-docs/src/views/tabs/playground.js index 81d35018584..6e9f59e18a9 100644 --- a/src-docs/src/views/tabs/playground.js +++ b/src-docs/src/views/tabs/playground.js @@ -54,7 +54,8 @@ export const tabsConfig = () => { const propsToUse = propUtilityForPlayground(docgenInfo.props); propsToUse.children = { - value: 'Tab 1Tab 2', + value: + 'Tab 1Tab 2Tab 3Tab link', type: PropTypes.ReactNode, hidden: false, }; diff --git a/src-docs/src/views/tabs/tabbed_content.js b/src-docs/src/views/tabs/tabbed_content.js deleted file mode 100644 index 22e7af71bd2..00000000000 --- a/src-docs/src/views/tabs/tabbed_content.js +++ /dev/null @@ -1,101 +0,0 @@ -import React, { Fragment } from 'react'; - -import { - EuiIcon, - EuiTabbedContent, - EuiTitle, - EuiText, - EuiSpacer, -} from '../../../../src/components'; - -export default () => { - const tabs = [ - { - id: 'cobalt--id', - name: 'Cobalt', - content: ( - - - -

Cobalt

-
- - Cobalt is a chemical element with symbol Co and atomic number 27. - Like nickel, cobalt is found in the Earth’s crust only in - chemically combined form, save for small deposits found in alloys of - natural meteoric iron. The free element, produced by reductive - smelting, is a hard, lustrous, silver-gray metal. - -
- ), - }, - { - id: 'dextrose--id', - name: 'Dextrose', - content: ( - - - -

Dextrose

-
- - Intravenous sugar solution, also known as dextrose solution, is a - mixture of dextrose (glucose) and water. It is used to treat low - blood sugar or water loss without electrolyte loss. - -
- ), - }, - { - id: 'hydrogen--id', - name: ( - - -  Hydrogen - - ), - content: ( - - - -

Hydrogen

-
- - Hydrogen is a chemical element with symbol H and atomic number 1. - With a standard atomic weight of 1.008, hydrogen is the lightest - element on the periodic table - -
- ), - }, - { - id: 'monosodium_glutammate--id', - name: 'Monosodium Glutamate', - content: ( - - - -

Monosodium Glutamate

-
- - Monosodium glutamate (MSG, also known as sodium glutamate) is the - sodium salt of glutamic acid, one of the most abundant naturally - occurring non-essential amino acids. Monosodium glutamate is found - naturally in tomatoes, cheese and other foods. - -
- ), - }, - ]; - - return ( - { - console.log('clicked tab', tab); - }} - /> - ); -}; diff --git a/src-docs/src/views/tabs/tabbed_content.tsx b/src-docs/src/views/tabs/tabbed_content.tsx new file mode 100644 index 00000000000..3f25da02c5a --- /dev/null +++ b/src-docs/src/views/tabs/tabbed_content.tsx @@ -0,0 +1,92 @@ +import React, { Fragment } from 'react'; + +import { + EuiIcon, + EuiTabbedContent, + EuiText, + EuiSpacer, +} from '../../../../src/components'; + +export default () => { + const tabs = [ + { + id: 'cobalt--id', + name: 'Cobalt', + content: ( + + + +

+ Cobalt is a chemical element with symbol Co and atomic number 27. + Like nickel, cobalt is found in the Earth’s crust only in + chemically combined form, save for small deposits found in alloys + of natural meteoric iron. The free element, produced by reductive + smelting, is a hard, lustrous, silver-gray metal. +

+
+
+ ), + }, + { + id: 'dextrose--id', + name: 'Dextrose', + content: ( + + + +

+ Intravenous sugar solution, also known as dextrose solution, is a + mixture of dextrose (glucose) and water. It is used to treat low + blood sugar or water loss without electrolyte loss. +

+
+
+ ), + }, + { + id: 'hydrogen--id', + name: 'Hydrogen', + prepend: , + content: ( + + + +

+ Hydrogen is a chemical element with symbol H and atomic number 1. + With a standard atomic weight of 1.008, hydrogen is the lightest + element on the periodic table +

+
+
+ ), + }, + { + id: 'monosodium_glutammate--id', + name: 'Monosodium Glutamate', + content: ( + + + +

+ Monosodium glutamate (MSG, also known as sodium glutamate) is the + sodium salt of glutamic acid, one of the most abundant naturally + occurring non-essential amino acids. Monosodium glutamate is found + naturally in tomatoes, cheese and other foods. +

+
+
+ ), + }, + ]; + + return ( + { + console.log('clicked tab', tab); + }} + /> + ); +}; diff --git a/src-docs/src/views/tabs/tabs.js b/src-docs/src/views/tabs/tabs.js deleted file mode 100644 index dbbef8a4577..00000000000 --- a/src-docs/src/views/tabs/tabs.js +++ /dev/null @@ -1,88 +0,0 @@ -import React, { useState, Fragment } from 'react'; - -import { - EuiIcon, - EuiTabs, - EuiTab, - EuiSpacer, - EuiTitle, -} from '../../../../src/components'; - -const tabs = [ - { - id: 'cobalt', - name: 'Cobalt', - disabled: false, - }, - { - id: 'dextrose', - name: 'Dextrose', - disabled: false, - }, - { - id: 'hydrogen', - name: ( - - -  Hydrogen - - ), - disabled: true, - }, - { - id: 'monosodium_glutammate', - name: 'Monosodium Glutamate', - disabled: false, - }, - { - id: 'elastic_link', - name: 'Elastic Website', - disabled: false, - href: 'https://www.elastic.co/', - }, -]; - -export default () => { - const [selectedTabId, setSelectedTabId] = useState('cobalt'); - - const onSelectedTabChanged = (id) => { - setSelectedTabId(id); - }; - - const renderTabs = () => { - return tabs.map((tab, index) => ( - onSelectedTabChanged(tab.id)} - isSelected={tab.id === selectedTabId} - disabled={tab.disabled} - key={index} - > - {tab.name} - - )); - }; - - return ( - - - Small - - {renderTabs()} - - - - Medium (default) - - - {renderTabs()} - - - - Large - - - {renderTabs()} - - ); -}; diff --git a/src-docs/src/views/tabs/tabs.tsx b/src-docs/src/views/tabs/tabs.tsx new file mode 100644 index 00000000000..d4655a7bd6d --- /dev/null +++ b/src-docs/src/views/tabs/tabs.tsx @@ -0,0 +1,122 @@ +import React, { useState, Fragment, useMemo } from 'react'; + +import { + EuiIcon, + EuiTabs, + EuiTab, + EuiSpacer, + EuiText, + EuiNotificationBadge, +} from '../../../../src/components'; + +const tabs = [ + { + id: 'cobalt--id', + name: 'Cobalt', + content: ( + + + +

+ Cobalt is a chemical element with symbol Co and atomic number 27. + Like nickel, cobalt is found in the Earth’s crust only in + chemically combined form, save for small deposits found in alloys of + natural meteoric iron. The free element, produced by reductive + smelting, is a hard, lustrous, silver-gray metal. +

+
+
+ ), + }, + { + id: 'dextrose--id', + name: 'Dextrose', + content: ( + + + +

+ Intravenous sugar solution, also known as dextrose solution, is a + mixture of dextrose (glucose) and water. It is used to treat low + blood sugar or water loss without electrolyte loss. +

+
+
+ ), + }, + { + id: 'hydrogen--id', + disabled: true, + name: 'Hydrogen', + prepend: , + content: ( + + + +

+ Hydrogen is a chemical element with symbol H and atomic number 1. + With a standard atomic weight of 1.008, hydrogen is the lightest + element on the periodic table +

+
+
+ ), + }, + { + id: 'monosodium_glutammate--id', + name: 'Monosodium Glutamate', + append: ( + + 10 + + ), + href: '#/navigation/tabs#monosodium', + content: ( + + + +

+ Monosodium glutamate (MSG, also known as sodium glutamate) is the + sodium salt of glutamic acid, one of the most abundant naturally + occurring non-essential amino acids. Monosodium glutamate is found + naturally in tomatoes, cheese and other foods. +

+
+
+ ), + }, +]; + +export default () => { + const [selectedTabId, setSelectedTabId] = useState('cobalt--id'); + const selectedTabContent = useMemo(() => { + return tabs.find((obj) => obj.id === selectedTabId)?.content; + }, [selectedTabId]); + + const onSelectedTabChanged = (id: string) => { + setSelectedTabId(id); + }; + + const renderTabs = () => { + return tabs.map((tab, index) => ( + onSelectedTabChanged(tab.id)} + isSelected={tab.id === selectedTabId} + disabled={tab.disabled} + prepend={tab.prepend} + append={tab.append} + > + {tab.name} + + )); + }; + + return ( + <> + {renderTabs()} + {selectedTabContent} + + ); +}; diff --git a/src-docs/src/views/tabs/tabs_condensed.js b/src-docs/src/views/tabs/tabs_condensed.js deleted file mode 100644 index c45f10789c5..00000000000 --- a/src-docs/src/views/tabs/tabs_condensed.js +++ /dev/null @@ -1,54 +0,0 @@ -import React, { useState } from 'react'; - -import { EuiIcon, EuiTabs, EuiTab } from '../../../../src/components'; - -const tabs = [ - { - id: 'cobalt', - name: 'Cobalt', - disabled: false, - }, - { - id: 'dextrose', - name: 'Dextrose', - disabled: false, - }, - { - id: 'hydrogen', - name: ( - - -  Hydrogen - - ), - disabled: true, - }, - { - id: 'monosodium_glutammate', - name: 'Monosodium Glutamate', - disabled: false, - }, -]; - -export default () => { - const [selectedTabId, setSelectedTabId] = useState('cobalt'); - - const onSelectedTabChanged = (id) => { - setSelectedTabId(id); - }; - - const renderTabs = () => { - return tabs.map((tab, index) => ( - onSelectedTabChanged(tab.id)} - isSelected={tab.id === selectedTabId} - disabled={tab.disabled} - key={index} - > - {tab.name} - - )); - }; - - return {renderTabs()}; -}; diff --git a/src-docs/src/views/tabs/tabs_example.js b/src-docs/src/views/tabs/tabs_example.js index 5aff01fc18f..cf0bc9498a5 100644 --- a/src-docs/src/views/tabs/tabs_example.js +++ b/src-docs/src/views/tabs/tabs_example.js @@ -1,8 +1,7 @@ +/* eslint-disable import/no-unresolved */ import React from 'react'; import { Link } from 'react-router-dom'; -import { renderToHtml } from '../../services'; - import { GuideSectionTypes } from '../../components'; import { @@ -10,33 +9,42 @@ import { EuiTabs, EuiTab, EuiTabbedContent, + EuiText, + EuiLink, } from '../../../../src/components'; + import { tabsConfig } from './playground'; import Tabs from './tabs'; const tabsSource = require('!!raw-loader!./tabs'); -const tabsHtml = renderToHtml(Tabs); -import TabsCondensed from './tabs_condensed'; -const tabsCondensedSource = require('!!raw-loader!./tabs_condensed'); -const tabsCondensedHtml = renderToHtml(TabsCondensed); +import TabsSmall from './tabs_small'; +const tabsSmallSource = require('!!raw-loader!./tabs_small'); + +import TabsFlyout from './tabs_flyout'; +const tabsFlyoutSource = require('!!raw-loader!./tabs_flyout'); import TabbedContent from './tabbed_content'; const tabbedContentSource = require('!!raw-loader!./tabbed_content'); -const tabbedContentHtml = renderToHtml(TabbedContent); import Controlled from './controlled'; const controlledSource = require('!!raw-loader!./controlled'); -const controlledHtml = renderToHtml(Controlled); -const controlledSnippet = ` -`; export const TabsExample = { title: 'Tabs', + intro: ( + +

+ Use tabs to organize in-page navigation, segmenting + mutually-exclusive content under a single organizational principle. For + more guideline usage see{' '} + + NNG's article "Tabs, Used Right" + + . +

+
+ ), sections: [ { source: [ @@ -44,68 +52,93 @@ export const TabsExample = { type: GuideSectionTypes.JS, code: tabsSource, }, - { - type: GuideSectionTypes.HTML, - code: tabsHtml, - }, ], text: ( -

- EuiTabs allow a size prop. In - general you should always use the default (medium) size. The small - size is best for when placing inside popovers or other small - containers. Reserve using the large size for when using as primary - page navigation, like inside of{' '} - - EuiPageHeader - - . -

+ <> +

+ EuiTabs is a wrapping component that requires{' '} + EuiTab components as direct children. You control + the displayed contents and current state through props on EuiTab + like isSelected and onClick. +

+

+ Use the prepend and append tab + props to add content before and after the tab label respectively. +

+ ), props: { EuiTabs, EuiTab, }, demo: , - playground: tabsConfig, snippet: [ ` Example 1 - Example 2 -`, - ` + EuiPageHeader + + . +

+

+ You can also use the expand prop to evenly + stretch each tab horizontally. +

+ + ), + props: { + EuiTabs, + EuiTab, + }, + demo: , + snippet: ` + Example 1 + Example 2 +`, + }, + { + title: 'Bottom border', + source: [ { - type: GuideSectionTypes.HTML, - code: tabsCondensedHtml, + type: GuideSectionTypes.JS, + code: tabsFlyoutSource, }, ], text: (

- EuiTabs allow a display prop. In - general you should always use the default display. However, it is - acceptable to use the alternative condensed display - in situations where it is desirable to display a bolder, more compact - and borderless tab interface (for use as primary navigation within - your application or to establish a higher level hierarchy of tabs). + The bottomBorder helps to separate the tab group + from the content below and is on by default. However, some components + like flyouts already provide borders + which can act as the divider. In this case you can turn the border off + with {'bottomBorder={false}'}.

), props: { EuiTabs, }, - demo: , - snippet: ` + demo: , + snippet: ` Example 1 Example 2 `, @@ -117,10 +150,6 @@ export const TabsExample = { type: GuideSectionTypes.JS, code: tabbedContentSource, }, - { - type: GuideSectionTypes.HTML, - code: tabbedContentHtml, - }, ], text: (

@@ -156,10 +185,6 @@ export const TabsExample = { type: GuideSectionTypes.JS, code: controlledSource, }, - { - type: GuideSectionTypes.HTML, - code: controlledHtml, - }, ], text: (

@@ -172,7 +197,11 @@ export const TabsExample = { props: { EuiTabbedContent, }, - snippet: controlledSnippet, + snippet: ``, demo: , }, ], diff --git a/src-docs/src/views/tabs/tabs_flyout.tsx b/src-docs/src/views/tabs/tabs_flyout.tsx new file mode 100644 index 00000000000..11d858bc945 --- /dev/null +++ b/src-docs/src/views/tabs/tabs_flyout.tsx @@ -0,0 +1,109 @@ +import React, { useState } from 'react'; + +import { + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiSpacer, + EuiTab, + EuiTabs, + EuiText, + EuiTitle, +} from '../../../../src/components'; + +export default () => { + const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); + const [selectedTabId, setSelectedTabId] = useState('1'); + + const tabs = [ + { + id: '1', + name: 'Tab 1', + }, + { + id: '2', + name: 'Tab 2', + }, + ]; + + const closeFlyout = () => setIsFlyoutVisible(false); + + const showFlyout = () => setIsFlyoutVisible(true); + + const onSelectedTabChanged = (id: string) => setSelectedTabId(id); + + const renderTabs = tabs.map((tab, index) => ( + onSelectedTabChanged(tab.id)} + isSelected={tab.id === selectedTabId} + key={index} + > + {tab.name} + + )); + + let flyout; + + if (isFlyoutVisible) { + flyout = ( + + + +

Flyout header

+ + + +

+ Put navigation items in the header, and cross tab actions in a + footer. +

+
+ + + {renderTabs} + + + + +

Tab {selectedTabId} content

+
+
+ + + + + Close + + + + + Save + + + + + + ); + } + + return ( +
+ Show tabs flyout header + + {flyout} +
+ ); +}; diff --git a/src-docs/src/views/tabs/tabs_small.tsx b/src-docs/src/views/tabs/tabs_small.tsx new file mode 100644 index 00000000000..d59712e1aab --- /dev/null +++ b/src-docs/src/views/tabs/tabs_small.tsx @@ -0,0 +1,84 @@ +import React, { useState } from 'react'; + +import { + EuiIcon, + EuiTabs, + EuiTab, + EuiButton, + EuiPopover, + EuiSpacer, +} from '../../../../src/components'; + +const tabs = [ + { + id: 'cobalt', + name: 'Cobalt', + disabled: false, + }, + { + id: 'dextrose', + name: 'Dextrose', + disabled: false, + }, + { + id: 'hydrogen', + name: 'Hydrogen', + prepend: , + disabled: true, + }, +]; + +export default () => { + const [isPopoverOpen, setPopover] = useState(false); + const [selectedTabId, setSelectedTabId] = useState('cobalt'); + + const onSelectedTabChanged = (id: string) => { + setSelectedTabId(id); + }; + + const renderTabs = () => { + return tabs.map((tab, index) => ( + onSelectedTabChanged(tab.id)} + isSelected={tab.id === selectedTabId} + disabled={tab.disabled} + prepend={tab.prepend} + > + {tab.name} + + )); + }; + + const onButtonClick = () => { + setPopover(!isPopoverOpen); + }; + + const closePopover = () => { + setPopover(false); + }; + + const button = ( + + Click to show small tabs in a popover + + ); + + return ( + +
+ + {renderTabs()} + + A spacer is usually needed between the tabs and + the content. Try to use the same size as the surrounding padding. +
+
+ ); +}; diff --git a/src/components/page/__snapshots__/page_template.test.tsx.snap b/src/components/page/__snapshots__/page_template.test.tsx.snap index dc5426efefa..b35638fcf15 100644 --- a/src/components/page/__snapshots__/page_template.test.tsx.snap +++ b/src/components/page/__snapshots__/page_template.test.tsx.snap @@ -475,7 +475,7 @@ exports[`EuiPageTemplate template centeredContent is rendered with pageHeader 1` >
@@ -719,7 +719,7 @@ exports[`EuiPageTemplate template default is rendered with pageHeader 1`] = ` >
diff --git a/src/components/page/page_body/_page_body.scss b/src/components/page/page_body/_page_body.scss index dc762800fb8..43e798be497 100644 --- a/src/components/page/page_body/_page_body.scss +++ b/src/components/page/page_body/_page_body.scss @@ -31,10 +31,15 @@ // We want to add some extra separation between it and the content body border-bottom: $euiBorderThin; - &:not(.euiPageHeader--tabsAtBottom) { + &:not(.euiPageHeader--tabsAtBottom):not(.euiPageHeader--onlyTabs) { padding-bottom: $amount; } } + + // When only tabs, remove all padding + & > .euiPageHeader.euiPageHeader--onlyTabs { + padding-top: 0; + } } } diff --git a/src/components/page/page_header/__snapshots__/page_header.test.tsx.snap b/src/components/page/page_header/__snapshots__/page_header.test.tsx.snap index 702a47b5ef6..4acd61718aa 100644 --- a/src/components/page/page_header/__snapshots__/page_header.test.tsx.snap +++ b/src/components/page/page_header/__snapshots__/page_header.test.tsx.snap @@ -208,7 +208,9 @@ exports[`EuiPageHeader props page content props are passed down is rendered 1`] class="euiFlexItem" >

@@ -316,13 +318,13 @@ exports[`EuiPageHeader props responsive is rendered as reverse 1`] = ` exports[`EuiPageHeader props restrictWidth is rendered as custom 1`] = `
`; exports[`EuiPageHeader props restrictWidth is rendered as true 1`] = `
`; diff --git a/src/components/page/page_header/__snapshots__/page_header_content.test.tsx.snap b/src/components/page/page_header/__snapshots__/page_header_content.test.tsx.snap index 05eaefebdcb..5520d7379c8 100644 --- a/src/components/page/page_header/__snapshots__/page_header_content.test.tsx.snap +++ b/src/components/page/page_header/__snapshots__/page_header_content.test.tsx.snap @@ -7,7 +7,7 @@ exports[`EuiPageHeaderContent is rendered 1`] = ` data-test-subj="test subject string" >
+`; + +exports[`EuiTab props disabled is rendered 1`] = ` +`; + +exports[`EuiTab props onClick renders button 1`] = ` ); }; diff --git a/src/components/tabs/tabbed_content/__snapshots__/tabbed_content.test.tsx.snap b/src/components/tabs/tabbed_content/__snapshots__/tabbed_content.test.tsx.snap index cd9bf61bcc7..59f11cf45da 100644 --- a/src/components/tabs/tabbed_content/__snapshots__/tabbed_content.test.tsx.snap +++ b/src/components/tabs/tabbed_content/__snapshots__/tabbed_content.test.tsx.snap @@ -3,7 +3,7 @@ exports[`EuiTabbedContent behavior when selected tab state isn't controlled by the owner, select the first tab by default 1`] = `
updated Kibana content

, @@ -71,6 +83,7 @@ exports[`EuiTabbedContent behavior when uncontrolled, the selected tab should up "name": Kibana , + "prepend": "prepend", }, ] } @@ -80,7 +93,7 @@ exports[`EuiTabbedContent behavior when uncontrolled, the selected tab should up onFocus={[Function]} >
@@ -109,17 +122,20 @@ exports[`EuiTabbedContent behavior when uncontrolled, the selected tab should up
@@ -158,7 +184,7 @@ exports[`EuiTabbedContent is rendered with required props and tabs 1`] = ` data-test-subj="test subject string" >
Kibana, 'data-test-subj': 'kibanaTab', + className: 'kibanaTab', content:

Kibana content

, + prepend: 'prepend', + append: 'append', }; const tabs = [elasticsearchTab, kibanaTab]; diff --git a/src/components/tabs/tabbed_content/tabbed_content.tsx b/src/components/tabs/tabbed_content/tabbed_content.tsx index d242c83db31..601b1cbd30c 100644 --- a/src/components/tabs/tabbed_content/tabbed_content.tsx +++ b/src/components/tabs/tabbed_content/tabbed_content.tsx @@ -11,7 +11,7 @@ import React, { Component, createRef, HTMLAttributes, ReactNode } from 'react'; import { htmlIdGenerator } from '../../../services'; import { EuiTabs, EuiTabsDisplaySizes, EuiTabsSizes } from '../tabs'; -import { EuiTab } from '../tab'; +import { EuiTab, EuiTabProps } from '../tab'; import { CommonProps } from '../../common'; /** @@ -19,7 +19,7 @@ import { CommonProps } from '../../common'; */ export const AUTOFOCUS = ['initial', 'selected'] as const; -export interface EuiTabbedContentTab { +export interface EuiTabbedContentTab extends EuiTabProps { id: string; name: ReactNode; content: ReactNode; diff --git a/src/components/tabs/tabs.tsx b/src/components/tabs/tabs.tsx index 79ad29d36f0..18c1070e4f8 100644 --- a/src/components/tabs/tabs.tsx +++ b/src/components/tabs/tabs.tsx @@ -28,6 +28,7 @@ const sizeToClassNameMap = { s: 'euiTabs--small', m: null, l: 'euiTabs--large', + xl: 'euiTabs--xlarge', }; export const SIZES = keysOf(sizeToClassNameMap); @@ -41,7 +42,8 @@ export type EuiTabsProps = CommonProps & */ children?: ReactNode; /** - * Choose `default` or alternative `condensed` display styles + * **DEPRECATED IN AMSTERDAM** + * Choose `default` or alternative `condensed` display styles. */ display?: EuiTabsDisplaySizes; /** @@ -49,6 +51,14 @@ export type EuiTabsProps = CommonProps & * horizontal space */ expand?: boolean; + /** + * Adds a bottom border to separate it from the content after + */ + bottomBorder?: boolean; + /** + * Sizes affect both font size and overall size. + * Only use the `xl` size when displayed as page titles. + */ size?: EuiTabsSizes; }; @@ -60,18 +70,25 @@ export const EuiTabs = forwardRef>( children, className, display = 'default', + bottomBorder = true, expand = false, size = 'm', ...rest }: PropsWithChildren, ref ) => { + /** + * Temporary force of bottom border based on `display` + */ + bottomBorder = display === 'condensed' ? false : bottomBorder; + const classes = classNames( 'euiTabs', - displayToClassNameMap[display], sizeToClassNameMap[size], + displayToClassNameMap[display], { 'euiTabs--expand': expand, + 'euiTabs--bottomBorder': bottomBorder, }, className ); diff --git a/src/themes/eui-amsterdam/overrides/_notification_badge.scss b/src/themes/eui-amsterdam/overrides/_notification_badge.scss index 1c9ec46c7e5..bce8d42bc8b 100644 --- a/src/themes/eui-amsterdam/overrides/_notification_badge.scss +++ b/src/themes/eui-amsterdam/overrides/_notification_badge.scss @@ -1,3 +1,4 @@ .euiNotificationBadge { + @include euiNumberFormat; border-radius: $euiBorderRadiusSmall; } diff --git a/src/themes/eui-amsterdam/overrides/_tabs.scss b/src/themes/eui-amsterdam/overrides/_tabs.scss index e2ff5ab166f..623ef2abdab 100644 --- a/src/themes/eui-amsterdam/overrides/_tabs.scss +++ b/src/themes/eui-amsterdam/overrides/_tabs.scss @@ -1,15 +1,80 @@ // Fixed heights ensure proper alignment with pixel grid -.euiTab { - font-weight: $euiFontWeightMedium; - height: $euiSizeXXL + $euiSizeS; -} +// MEDIUM +.euiTab, +.euiTabs--condensed .euiTab { + padding: 0 $euiSizeXS; + + .euiTab__content { + @include euiTitle('xxs'); + line-height: $euiSizeXXL; + } -.euiTabs--small .euiTab { - height: $euiSizeXXL; + & + .euiTab { + margin-left: $euiSize; + } + + &:focus { + background-color: transparent; + } } -.euiTabs--large .euiTab { - @include fontSize($euiTabFontSizeL); - height: $euiSizeXXL + $euiSizeS; +.euiTabs, +.euiTabs--condensed.euiTabs { + // SMALL + &--small .euiTab { + padding: 0 $euiSizeXS; + + .euiTab__content { + @include euiTitle('xxxs'); + line-height: $euiSizeXL; + } + + & + .euiTab { + margin-left: $euiSizeM; + } + } + + // LARGE + &--large .euiTab { + padding: 0 $euiSizeXS; + + .euiTab__content { + @include euiTitle('xs'); + line-height: $euiSizeXXL + $euiSizeS; + } + + & + .euiTab { + margin-left: $euiSizeL; + } + } + + // X-LARGE + &--xlarge .euiTab { + padding: 0 $euiSizeXS; + + .euiTab__content { + @include fontSize($euiSize + $euiSizeXS); + line-height: $euiSizeXXL + $euiSizeS; + } + + & + .euiTab { + margin-left: $euiSizeXL; + } + } + + // SELECTED + .euiTab-isSelected { + color: $euiColorPrimaryText; + + &:hover, + &:focus { + text-decoration: underline; + cursor: pointer; + } + + &:focus-visible { + box-shadow: none; + } + } }