diff --git a/CHANGELOG.md b/CHANGELOG.md index de2eb5ed089..ce69ef48da0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ ## [`master`](https://github.com/elastic/eui/tree/master) +- Added `minWidth` prop to `EuiButton` ([4056](https://github.com/elastic/eui/pull/4056)) +- Added `isSelected` prop to easily turn `EuiButton`, `EuiButtonEmpty`, and `EuiButtonIcon` into toggle buttons ([4056](https://github.com/elastic/eui/pull/4056)) +- Updated `EuiButtonGroup` props and render for better accessibility ([4056](https://github.com/elastic/eui/pull/4056)) + +**Breaking changes** + +- Removed `EuiToggle` and `EuiButtonToggle` in favor of `aria-pressed` ([4056](https://github.com/elastic/eui/pull/4056)) +- Updated `legend` and `idSelected` props of `EuiButtonGroup` to be required ([4056](https://github.com/elastic/eui/pull/4056)) + **Theme: Amsterdam** - Tightened `line-height` for some `EuiTitle` sizes ([4133](https://github.com/elastic/eui/pull/4133)) diff --git a/packages/eslint-plugin/rules/href_or_on_click.js b/packages/eslint-plugin/rules/href_or_on_click.js index e0533fa9455..e0960ae1f65 100644 --- a/packages/eslint-plugin/rules/href_or_on_click.js +++ b/packages/eslint-plugin/rules/href_or_on_click.js @@ -1,4 +1,4 @@ -const componentNames = ['EuiButton', 'EuiButtonEmpty', 'EuiLink']; +const componentNames = ['EuiButton', 'EuiButtonEmpty', 'EuiLink', 'EuiBadge']; module.exports = { meta: { @@ -24,9 +24,7 @@ module.exports = { if (hasHref && hasOnClick) { context.report( node, - `<${ - node.name.name - }> accepts either \`href\` or \`onClick\`, not both.` + `<${node.name.name}> accepts either \`href\` or \`onClick\`, not both.` ); } }, diff --git a/src-docs/src/routes.js b/src-docs/src/routes.js index 2e361642dd5..0261f3558a3 100644 --- a/src-docs/src/routes.js +++ b/src-docs/src/routes.js @@ -204,8 +204,6 @@ import { ToastExample } from './views/toast/toast_example'; import { ToolTipExample } from './views/tool_tip/tool_tip_example'; -import { ToggleExample } from './views/toggle/toggle_example'; - import { TourExample } from './views/tour/tour_example'; import { WindowEventExample } from './views/window_event/window_event_example'; @@ -468,7 +466,6 @@ const navigation = [ ResizeObserverExample, ResponsiveExample, TextDiffExample, - ToggleExample, WindowEventExample, ].map((example) => createExample(example)), }, diff --git a/src-docs/src/views/button/button_example.js b/src-docs/src/views/button/button_example.js index e557bd1db61..b8a754a86ba 100644 --- a/src-docs/src/views/button/button_example.js +++ b/src-docs/src/views/button/button_example.js @@ -1,9 +1,7 @@ import React from 'react'; - import { Link } from 'react-router-dom'; import { renderToHtml } from '../../services'; - import { GuideSectionTypes } from '../../components'; import { @@ -12,12 +10,14 @@ import { EuiButtonIcon, EuiCode, EuiButtonGroup, - EuiButtonToggle, EuiCallOut, EuiText, } from '../../../../src/components'; + +import { EuiButtonGroupOptionProps } from '!!prop-loader!../../../../src/components/button/button_group/button_group'; + import Guidelines from './guidelines'; -import buttonConfig from './playground'; +import Playground from './playground'; import Button from './button'; const buttonSource = require('!!raw-loader!./button'); @@ -82,30 +82,60 @@ const buttonLoadingSnippet = ` import ButtonToggle from './button_toggle'; const buttonToggleSource = require('!!raw-loader!./button_toggle'); const buttonToggleHtml = renderToHtml(ButtonToggle); -const buttonToggleSnippet = ` + {toggleOn ? onLabel : offLabel} + +`, + ``; + fill={toggleOn} + onClick={onToggleChange} + > + +`, + ` + +`, +]; import ButtonGroup from './button_group'; const buttonGroupSource = require('!!raw-loader!./button_group'); const buttonGroupHtml = renderToHtml(ButtonGroup); const buttonGroupSnippet = [ ` {}} />`, ` {}} />`, ]; @@ -292,26 +322,43 @@ export const ButtonExample = { }, ], text: ( -
+ <>

- This is a specialized component that combines{' '} - EuiButton and EuiToggle to create - a button with an on/off state. You can pass all the same parameters - to it as you can to EuiButton. The main difference - is that, it does not accept any children, but a{' '} - label prop instead. This is for the handling of - accessibility with the EuiToggle. + You can create a toggle style button with any button type like the + standard EuiButton, EuiButtonEmpty + , or EuiButtonIcon. Use state management to handle + the visual differences for on and off. Though there are two{' '} + exclusive situations to consider.

-

- The EuiButtonToggle does not have any inherit - visual state differences. These you must apply in your - implementation. -

-
+
    +
  1. + If your button changes its readable text, via + children or aria-label, then there is no + additional accessibility concern. +
  2. +
  3. + If your button only changes the visual{' '} + appearance, you must add aria-pressed passing a + boolean for the on and off states. All EUI button types provide a + helper prop for this called isSelected. +
  4. +
+ + Do not add aria-pressed or{' '} + isSelected if you also change the readable + text. + + } + /> + ), demo: , snippet: buttonToggleSnippet, - props: { EuiButtonToggle }, + props: { EuiButton, EuiButtonIcon }, }, { title: 'Button groups', @@ -328,19 +375,11 @@ export const ButtonExample = { text: (

- EuiButtonGroups are handled similarly to the way - checkbox and radio groups are handled but made to look like buttons. - They group multiple EuiButtonToggles and utilize - the type="single" or{' '} + EuiButtonGroups utilize the{' '} + type="single" or{' '} "multi" prop to determine - whether multiple or only single selections are allowed per group. -

-

- Stylistically, all button groups are the size of small buttons, do - not stretch to fill the container, and typically should only be{' '} - color="text" (default) or{' '} - "primary". If you're - just displaying a group of icons, add the prop{' '} + whether multiple or only single selections are allowed per group. If + you're just displaying a group of icons, add the prop{' '} isIconOnly.

In order for groups to be properly read as groups with a title, - add the legend prop. This is only for - accessibility, however, so it will be visibly hidden. + the legend prop is required. + This is only for accessibility, however, so it will be visibly + hidden. } /> @@ -358,7 +398,7 @@ export const ButtonExample = { ), demo: , snippet: buttonGroupSnippet, - props: { EuiButtonGroup }, + props: { EuiButtonGroup, EuiButtonGroupOptionProps }, }, { title: 'Ghost', @@ -390,5 +430,5 @@ export const ButtonExample = { }, ], guidelines: , - playground: buttonConfig, + playground: Playground, }; diff --git a/src-docs/src/views/button/button_ghost.js b/src-docs/src/views/button/button_ghost.js index 8b7be264463..eb163b85020 100644 --- a/src-docs/src/views/button/button_ghost.js +++ b/src-docs/src/views/button/button_ghost.js @@ -6,15 +6,14 @@ import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, - EuiButtonToggle, EuiPanel, } from '../../../../src/components'; export default () => { const [toggle0On, setToggle0On] = useState(false); - const onToggle0Change = (e) => { - setToggle0On(e.target.checked); + const onToggle0Change = () => { + setToggle0On((isOn) => !isOn); }; return ( @@ -71,12 +70,13 @@ export default () => { - + onClick={onToggle0Change}> + Toggle me + diff --git a/src-docs/src/views/button/button_group.js b/src-docs/src/views/button/button_group.js index f54514806b8..1d35a5980c1 100644 --- a/src-docs/src/views/button/button_group.js +++ b/src-docs/src/views/button/button_group.js @@ -4,10 +4,10 @@ import { EuiButtonGroup, EuiSpacer, EuiTitle, + EuiPanel, } from '../../../../src/components'; import { htmlIdGenerator } from '../../../../src/services'; -import { EuiPanel } from '../../../../src/components/panel/panel'; const idPrefix = htmlIdGenerator()(); const idPrefix2 = htmlIdGenerator()(); @@ -89,6 +89,7 @@ export default () => { id: `${idPrefix3}2`, label: 'Align right', iconType: 'editorAlignRight', + isDisabled: true, }, ]; @@ -104,6 +105,7 @@ export default () => { label: 'Italic', name: 'italic', iconType: 'editorItalic', + isDisabled: true, }, { id: `${idPrefix3}5`, @@ -201,7 +203,6 @@ export default () => { onChangeMulti(id)} @@ -215,7 +216,6 @@ export default () => { onChangeDisabled(id)} @@ -230,7 +230,6 @@ export default () => { onChangeIcons(id)} @@ -269,7 +268,6 @@ export default () => { { const [toggle0On, setToggle0On] = useState(false); - const [toggle1On, setToggle1On] = useState(false); - const toggle2On = false; - const toggle3On = true; - const [toggle4On, setToggle4On] = useState(true); - - const onToggle0Change = (e) => { - setToggle0On(e.target.checked); - }; - - const onToggle1Change = (e) => { - setToggle1On(e.target.checked); - }; - - const onToggle4Change = (e) => { - setToggle4On(e.target.checked); - }; + const [toggle1On, setToggle1On] = useState(true); + const [toggle2On, setToggle2On] = useState(true); + const [toggle3On, setToggle3On] = useState(false); return ( -
- -   - + <> + +

Changing content

+
+ + { + setToggle0On((isOn) => !isOn); + }}> + {toggle0On ? 'Hey there good lookin' : 'Toggle me'} +   - { + setToggle1On((isOn) => !isOn); + }} /> -

Disabled

+

Changing visual appearance

- + fill={toggle2On} + iconType={toggle2On ? 'starFilledSpace' : 'starPlusEmpty'} + onClick={() => { + setToggle2On((isOn) => !isOn); + }}> + Toggle me +   - { + setToggle3On((isOn) => !isOn); + }} /> -
+ ); }; diff --git a/src-docs/src/views/button/playground.js b/src-docs/src/views/button/playground.js index bb61586582e..98891eda414 100644 --- a/src-docs/src/views/button/playground.js +++ b/src-docs/src/views/button/playground.js @@ -20,6 +20,11 @@ export default () => { hidden: true, }; + propsToUse.minWidth = { + ...propsToUse.minWidth, + type: PropTypes.Number, + }; + const setGhostBackground = { color: 'ghost', }; diff --git a/src-docs/src/views/collapsible_nav/collapsible_nav.tsx b/src-docs/src/views/collapsible_nav/collapsible_nav.tsx index cfe4cdc50a8..bec9da37bea 100644 --- a/src-docs/src/views/collapsible_nav/collapsible_nav.tsx +++ b/src-docs/src/views/collapsible_nav/collapsible_nav.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { EuiCollapsibleNav } from '../../../../src/components/collapsible_nav'; -import { EuiButton, EuiButtonToggle } from '../../../../src/components/button'; +import { EuiButton } from '../../../../src/components/button'; import { EuiTitle } from '../../../../src/components/title'; import { EuiSpacer } from '../../../../src/components/spacer'; import { EuiText } from '../../../../src/components/text'; @@ -27,13 +27,12 @@ export default () => {

I am some nav

- { + { setNavIsDocked(!navIsDocked); - }} - /> + }}> + Docked: {navIsDocked ? 'on' : 'off'} +
diff --git a/src-docs/src/views/markdown_editor/markdown_editor.js b/src-docs/src/views/markdown_editor/markdown_editor.js index 6b39ab149b8..13fffa52f41 100644 --- a/src-docs/src/views/markdown_editor/markdown_editor.js +++ b/src-docs/src/views/markdown_editor/markdown_editor.js @@ -4,7 +4,7 @@ import { EuiMarkdownEditor, EuiSpacer, EuiCodeBlock, - EuiButtonToggle, + EuiButton, } from '../../../../src/components'; const initialContent = `## Hello world! @@ -59,14 +59,13 @@ export default () => { />
- setIsAstShowing(!isAstShowing)} - isSelected={isAstShowing} - /> + onClick={() => setIsAstShowing(!isAstShowing)} + fill={isAstShowing}> + {isAstShowing ? 'Hide editor AST' : 'Show editor AST'} +
{isAstShowing && {ast}} diff --git a/src-docs/src/views/markdown_editor/markdown_editor_errors.js b/src-docs/src/views/markdown_editor/markdown_editor_errors.js index 68c33fa1c45..2c44fce6eb9 100644 --- a/src-docs/src/views/markdown_editor/markdown_editor_errors.js +++ b/src-docs/src/views/markdown_editor/markdown_editor_errors.js @@ -6,7 +6,7 @@ import { EuiMarkdownEditor, EuiSpacer, EuiCodeBlock, - EuiButtonToggle, + EuiButton, EuiFormErrorText, } from '../../../../src/components'; @@ -53,14 +53,13 @@ export default () => {
- setIsAstShowing(!isAstShowing)} - isSelected={isAstShowing} - /> + onClick={() => setIsAstShowing(!isAstShowing)} + fill={isAstShowing}> + {isAstShowing ? 'Hide editor AST' : 'Show editor AST'} +
{isAstShowing && {ast}} diff --git a/src-docs/src/views/markdown_editor/markdown_editor_with_plugins.js b/src-docs/src/views/markdown_editor/markdown_editor_with_plugins.js index 574a4d5b1d7..dc6aa29ba8b 100644 --- a/src-docs/src/views/markdown_editor/markdown_editor_with_plugins.js +++ b/src-docs/src/views/markdown_editor/markdown_editor_with_plugins.js @@ -17,7 +17,6 @@ import { EuiMarkdownFormat, EuiSpacer, EuiCodeBlock, - EuiButtonToggle, EuiModalHeader, EuiModalHeaderTitle, EuiModalBody, @@ -295,14 +294,13 @@ export default () => { />
- setIsAstShowing(!isAstShowing)} - isSelected={isAstShowing} - /> + onClick={() => setIsAstShowing(!isAstShowing)} + fill={isAstShowing}> + {isAstShowing ? 'Hide editor AST' : 'Show editor AST'} +
{isAstShowing && {ast}} diff --git a/src-docs/src/views/toggle/playground.js b/src-docs/src/views/toggle/playground.js deleted file mode 100644 index 978d46cbd47..00000000000 --- a/src-docs/src/views/toggle/playground.js +++ /dev/null @@ -1,48 +0,0 @@ -import { PropTypes } from 'react-view'; -import { EuiToggle } from '../../../../src/components/'; -import { - propUtilityForPlayground, - dummyFunction, - simulateFunction, -} from '../../services/playground'; - -export const toggleConfig = () => { - const docgenInfo = Array.isArray(EuiToggle.__docgenInfo) - ? EuiToggle.__docgenInfo[0] - : EuiToggle.__docgenInfo; - const propsToUse = propUtilityForPlayground(docgenInfo.props); - - propsToUse.label.value = 'Is toggle on?'; - propsToUse.checked.stateful = true; - propsToUse.children = { - ...propsToUse.children, - type: PropTypes.ReactNode, - value: "{checked ? 'On' : 'Off'}", - hidden: false, - }; - - propsToUse.value = { - ...propsToUse.value, - type: PropTypes.String, - }; - - propsToUse.onChange = simulateFunction(propsToUse.onChange); - - return { - config: { - componentName: 'EuiToggle', - props: propsToUse, - scope: { - EuiToggle, - }, - imports: { - '@elastic/eui': { - named: ['EuiToggle'], - }, - }, - customProps: { - onChange: dummyFunction, - }, - }, - }; -}; diff --git a/src-docs/src/views/toggle/toggle.js b/src-docs/src/views/toggle/toggle.js deleted file mode 100644 index 04375eac8db..00000000000 --- a/src-docs/src/views/toggle/toggle.js +++ /dev/null @@ -1,19 +0,0 @@ -import React, { useState } from 'react'; - -import { EuiToggle } from '../../../../src/components'; - -export default function () { - const [toggleOn, setToggleValue] = useState(false); - - const onToggleChange = (e) => { - setToggleValue(e.target.checked); - }; - - return ( -
- onToggleChange(e)} label="Is toggle on?"> - {toggleOn ? 'On' : 'Off'} - -
- ); -} diff --git a/src-docs/src/views/toggle/toggle_example.js b/src-docs/src/views/toggle/toggle_example.js deleted file mode 100644 index fee1d3a06a6..00000000000 --- a/src-docs/src/views/toggle/toggle_example.js +++ /dev/null @@ -1,76 +0,0 @@ -import React from 'react'; - -import { Link } from 'react-router-dom'; - -import { renderToHtml } from '../../services'; - -import { GuideSectionTypes } from '../../components'; - -import { EuiCode, EuiToggle, EuiCallOut } from '../../../../src/components'; - -import { toggleConfig } from './playground'; - -import Toggle from './toggle'; -const toggleSource = require('!!raw-loader!./toggle'); -const toggleHtml = renderToHtml(Toggle); -const toggleSnippet = [ - ` - {toggleOn ? 'On' : 'Off'} -`, -]; - -export const ToggleExample = { - title: 'Toggle', - sections: [ - { - source: [ - { - type: GuideSectionTypes.JS, - code: toggleSource, - }, - { - type: GuideSectionTypes.HTML, - code: toggleHtml, - }, - ], - text: ( -
-

- The EuiToggle component is a very simplified - utility for creating toggle-able elements. There is only an on/off - (checked/unchecked) state. All this creates is a visibly hidden - input (checkbox or radio) overtop of the children provided. -

-

- By default, the children will be wrapped in a block element. To - change the display you can simply use one of the{' '} - utility classes{' '} - like .eui-displayInlineBlock. -

- - This utility is just a helper component and comes with no - inherit styles including no :hover or{' '} - :focus states. If you use this utility - directly, be sure to add these states. Otherwise, you may just - want to utilize the{' '} - EuiButtonToggle component. - - } - /> -
- ), - components: { EuiToggle }, - snippet: toggleSnippet, - demo: , - props: { EuiToggle }, - }, - ], - playground: toggleConfig, -}; diff --git a/src/components/accessibility/__snapshots__/skip_link.test.tsx.snap b/src/components/accessibility/__snapshots__/skip_link.test.tsx.snap index 5a12b3e9c5c..2f03a9a93e3 100644 --- a/src/components/accessibility/__snapshots__/skip_link.test.tsx.snap +++ b/src/components/accessibility/__snapshots__/skip_link.test.tsx.snap @@ -3,7 +3,7 @@ exports[`EuiSkipLink is rendered 1`] = ` @@ -38,7 +38,7 @@ exports[`EuiSkipLink props onClick is rendered 1`] = ` exports[`EuiSkipLink props position absolute is rendered 1`] = ` @@ -54,7 +54,7 @@ exports[`EuiSkipLink props position absolute is rendered 1`] = ` exports[`EuiSkipLink props position fixed is rendered 1`] = ` @@ -87,7 +87,7 @@ exports[`EuiSkipLink props position static is rendered 1`] = ` exports[`EuiSkipLink props tabIndex is rendered 1`] = ` `; +exports[`EuiButton props isSelected is rendered as false 1`] = ` + +`; + +exports[`EuiButton props isSelected is rendered as true 1`] = ` + +`; + +exports[`EuiButton props minWidth is rendered 1`] = ` + +`; + exports[`EuiButton props size m is rendered 1`] = ` `; +exports[`EuiButtonEmpty props isSelected is rendered as false 1`] = ` + +`; + +exports[`EuiButtonEmpty props isSelected is rendered as true 1`] = ` + +`; + exports[`EuiButtonEmpty props size l is rendered 1`] = ` diff --git a/src/components/button/button_group/__snapshots__/button_group.test.tsx.snap b/src/components/button/button_group/__snapshots__/button_group.test.tsx.snap index c895ae057d4..0db6d7464d8 100644 --- a/src/components/button/button_group/__snapshots__/button_group.test.tsx.snap +++ b/src/components/button/button_group/__snapshots__/button_group.test.tsx.snap @@ -1,1663 +1,2270 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`EuiButtonGroup is rendered 1`] = ` +exports[`EuiButtonGroup button props buttonSize compressed is rendered for multi 1`] = `
+ + test + +
+ + + +
+
+`; + +exports[`EuiButtonGroup button props buttonSize compressed is rendered for single 1`] = ` +
+ + test + +
+ + + +
+
+`; + +exports[`EuiButtonGroup button props buttonSize m is rendered for multi 1`] = ` +
+ + test + +
+ + + +
+
+`; + +exports[`EuiButtonGroup button props buttonSize m is rendered for single 1`] = ` +
+ + test + +
+ + + +
+
+`; + +exports[`EuiButtonGroup button props buttonSize s is rendered for multi 1`] = ` +
+ + test + +
+ + + +
+
+`; + +exports[`EuiButtonGroup button props buttonSize s is rendered for single 1`] = ` +
+ + test + +
+ + + +
+
+`; + +exports[`EuiButtonGroup button props color danger is rendered for multi 1`] = ` +
+ + test + +
+ + + +
+
+`; + +exports[`EuiButtonGroup button props color danger is rendered for single 1`] = ` +
+ + test + +
+ + + +
+
+`; + +exports[`EuiButtonGroup button props color ghost is rendered for multi 1`] = ` +
+ + test +
+ class="euiButtonGroup__buttons" + > + + + +
`; -exports[`EuiButtonGroup props buttonSize compressed is rendered 1`] = ` +exports[`EuiButtonGroup button props color ghost is rendered for single 1`] = `
+ + test +
-
- - -
-
- - -
-
- - -
+ + +
`; -exports[`EuiButtonGroup props buttonSize m is rendered 1`] = ` +exports[`EuiButtonGroup button props color primary is rendered for multi 1`] = `
+ + test +
-
- - -
-
- - -
-
- - -
+ + +
`; -exports[`EuiButtonGroup props buttonSize s is rendered 1`] = ` +exports[`EuiButtonGroup button props color primary is rendered for single 1`] = `
+ + test +
-
- - -
-
- - -
-
- - -
+ + +
`; -exports[`EuiButtonGroup props color danger is rendered 1`] = ` +exports[`EuiButtonGroup button props color secondary is rendered for multi 1`] = `
+ + test +
-
- - -
-
- - -
-
- - -
+ + + +
+
+`; + +exports[`EuiButtonGroup button props color secondary is rendered for single 1`] = ` +
+ + test + +
+ + +
`; -exports[`EuiButtonGroup props color ghost is rendered 1`] = ` +exports[`EuiButtonGroup button props color text is rendered for multi 1`] = `
+ + test +
-
- - -
-
- - -
-
- - -
+ + + +
+
+`; + +exports[`EuiButtonGroup button props color text is rendered for single 1`] = ` +
+ + test + +
+ + + +
+
+`; + +exports[`EuiButtonGroup button props color warning is rendered for multi 1`] = ` +
+ + test + +
+ + +
`; -exports[`EuiButtonGroup props color primary is rendered 1`] = ` +exports[`EuiButtonGroup button props color warning is rendered for single 1`] = `
-
-
- - -
-
- - -
-
- - -
-
-
-`; - -exports[`EuiButtonGroup props color secondary is rendered 1`] = ` -
+ test +
-
- - -
-
- - -
-
- - -
+ + +
`; -exports[`EuiButtonGroup props color text is rendered 1`] = ` +exports[`EuiButtonGroup button props isDisabled is rendered for multi 1`] = `
-
-
- - -
-
- - -
-
- - -
-
-
-`; - -exports[`EuiButtonGroup props color warning is rendered 1`] = ` -
+ test +
-
- - -
-
- - -
-
- - -
+ + +
`; -exports[`EuiButtonGroup props idSelected is rendered 1`] = ` +exports[`EuiButtonGroup button props isDisabled is rendered for single 1`] = `
-
-
- - -
-
- - -
-
- - -
-
-
-`; - -exports[`EuiButtonGroup props isDisabled is rendered 1`] = ` -
+ test +
-
- - -
-
- - -
-
- - -
+ + +
`; -exports[`EuiButtonGroup props isFullWidth is rendered 1`] = ` +exports[`EuiButtonGroup button props isFullWidth is rendered for multi 1`] = `
+ + test +
-
- - -
-
- - -
-
- - -
+ + +
`; -exports[`EuiButtonGroup props isIconOnly is rendered 1`] = ` +exports[`EuiButtonGroup button props isFullWidth is rendered for single 1`] = `
+ + test +
-
- - -
-
+ +
-
+ + -
+ +
`; -exports[`EuiButtonGroup props legend is rendered 1`] = ` +exports[`EuiButtonGroup button props isIconOnly is rendered for multi 1`] = `
- legend + test
-
- - -
-
- - -
-
- - -
+ + +
`; -exports[`EuiButtonGroup props name is rendered 1`] = ` +exports[`EuiButtonGroup button props isIconOnly is rendered for single 1`] = `
+ + test +
-
- - -
-
- - -
-
- - -
+ + +
`; -exports[`EuiButtonGroup props options are rendered 1`] = ` +exports[`EuiButtonGroup button props selection idSelected is rendered for single 1`] = `
+ + test +
-
- - -
-
- - -
-
- - -
+ + +
`; -exports[`EuiButtonGroup props options can pass down data-test-subj 1`] = ` +exports[`EuiButtonGroup button props selection idToSelectedMap is rendered for multi 1`] = `
+ + test +
-
- - -
+ + +
`; -exports[`EuiButtonGroup props type of multi idToSelectedMap is rendered 1`] = ` +exports[`EuiButtonGroup type multi is rendered 1`] = `
+ + test +
-
- - -
-
- - -
-
- - -
+ + +
`; -exports[`EuiButtonGroup props type of multi is rendered 1`] = ` +exports[`EuiButtonGroup type single is rendered 1`] = `
+ + test +
-
- - -
-
- - -
-
- - -
+ + +
`; diff --git a/src/components/button/button_group/_button_group.scss b/src/components/button/button_group/_button_group.scss index 6251cf8fec0..1047b94e9ee 100644 --- a/src/components/button/button_group/_button_group.scss +++ b/src/components/button/button_group/_button_group.scss @@ -1,138 +1,55 @@ .euiButtonGroup { - max-width: 100%; - display: flex; -} - -.euiButtonGroup__fieldset { display: inline-block; max-width: 100%; - - &--fullWidth { - display: block; - } } .euiButtonGroup--fullWidth { - .euiButtonGroup__toggle { - flex: 1; - } -} - -.euiButtonGroup__toggle { - margin-left: -1px; - z-index: 1; - - // DO NOT Transform - // sass-lint:disable-block no-important - transition: none !important; - transform: none !important; - animation: none !important; - - &[class*='checked'] { - z-index: 2; // Raise it above the simply bordered versions for crisper lines + display: block; - // add a slight divider if two selected items are next to each other - + [class*='checked'] { - box-shadow: -1px 0 0 transparentize($euiColorEmptyShade, .9); - } - } - - .euiButtonGroup__button { - border-radius: 0; + .euiButtonGroup__buttons { width: 100%; - // DO NOT Transform - // sass-lint:disable-block no-important - transition: none !important; - transform: none !important; - animation: none !important; - - // always the same border color unless it's selected - &:not([class*='fill']) { - border-color: $euiButtonToggleBorderColor; - } - - // don't colorize the shadows - &:enabled { - @include euiSlightShadow; + .euiButtonGroupButton { + flex: 1; } } +} - &:first-child { - margin-left: 0; - - .euiButtonGroup__button { - border-top-left-radius: $euiBorderRadius; - border-bottom-left-radius: $euiBorderRadius; - } - } +.euiButtonGroup__buttons { + @include euiSlightShadow; + border-radius: $euiBorderRadius + 1px; // Simply for the box-shadow + max-width: 100%; + display: flex; + overflow: hidden; + transition: all $euiAnimSpeedNormal ease-in-out; - &:last-child .euiButtonGroup__button { - border-top-right-radius: $euiBorderRadius; - border-bottom-right-radius: $euiBorderRadius; + &:hover, + &:active, + &:focus-within { + @include euiSlightShadowHover; } - } -@include euiBreakpoint('xs', 's') { - .euiButtonGroup__fieldset { - display: block; - } - - .euiButtonGroup__toggle { - flex: 1; - min-width: 0; - - .euiButtonGroup__button { - min-width: 0; - } +.euiButtonGroup--isDisabled .euiButtonGroup__buttons { + &:hover, + &:active, + &:focus-within { + box-shadow: none !important; // sass-lint:disable-line no-important } } .euiButtonGroup--compressed { - border: 1px solid $euiFormBorderColor; - border-radius: $euiFormControlCompressedBorderRadius; - background-color: $euiFormBackgroundColor; - - .euiButtonGroup__button { - height: $euiFormControlCompressedHeight - 2px; - // sass-lint:disable-block no-important - box-shadow: none !important; - font-size: $euiFontSizeS; - min-width: 0; - border: none; - border-radius: $euiBorderRadius; - // Offset the background color from the border by 2px - // by clipping background to before the padding starts - padding: 2px; - background-clip: content-box; - - &:not(.euiButtonGroup__button--selected):not(:disabled) { - color: $euiColorDarkShade; - } - - .euiButton__content { - padding-left: $euiSizeS; - padding-right: $euiSizeS; - } - } - - .euiButtonGroup__toggle { - flex: 1; - min-width: 0; - } - - .euiButtonToggle__input:enabled:hover + .euiButtonGroup__button, - .euiButtonToggle__input:enabled:focus + .euiButtonGroup__button { - background-color: transparentize($euiFormInputGroupLabelBackground, .5); - } - - .euiButtonToggle__input:enabled:focus + .euiButtonGroup__button { - outline: 2px solid $euiFocusRingColor; - } - - .euiButtonGroup__button--selected { - font-weight: $euiFontWeightSemiBold; - background-color: $euiFormInputGroupLabelBackground; + .euiButtonGroup__buttons { + box-shadow: none !important; // sass-lint:disable-line no-important + border-radius: $euiFormControlCompressedBorderRadius; + background-color: $euiFormBackgroundColor; + height: $euiFormControlCompressedHeight; + border: 1px solid $euiFormBorderColor; + overflow: visible; + } + + .euiButtonGroupButton { + // Add 2 to the border radius to account for the background-clip + border-radius: $euiFormControlCompressedBorderRadius + 2; } } diff --git a/src/components/button/button_group/_button_group_button.scss b/src/components/button/button_group/_button_group_button.scss new file mode 100644 index 00000000000..cd6433f7752 --- /dev/null +++ b/src/components/button/button_group/_button_group_button.scss @@ -0,0 +1,215 @@ +.euiButtonGroupButton { + @include euiButtonBase; + @include euiFont; + @include euiFontSize; + + // sass-lint:disable-block indentation + transition: background-color $euiAnimSpeedNormal ease-in-out, + border-color $euiAnimSpeedNormal ease-in-out, + color $euiAnimSpeedNormal ease-in-out; + + // Allow button to shrink and truncate + min-width: 0; + flex-shrink: 1; + flex-grow: 0; + + .euiButton__content { + padding: 0 $euiSizeM; + } + + &-isIconOnly .euiButton__content { + padding: 0 $euiSizeS; + } + + .euiButton__text { + text-overflow: ellipsis; + overflow: hidden; + } + + &.euiButtonGroupButton--small { + height: $euiButtonHeightSmall; + line-height: $euiButtonHeightSmall; // prevents descenders from getting cut off + } + + &:not([class*='isDisabled']) { + &:hover, + &:focus, + &:focus-within { + background-color: transparentize($euiColorPrimary, .9); + text-decoration: underline; + } + } + + &.euiButtonGroupButton-isDisabled { + @include euiButtonContentDisabled; + color: $euiButtonColorDisabledText; + + &.euiButtonGroupButton-isSelected { + // Only increase the contrast of background color to text to 2.0 for disabled + color: makeHighContrastColor($euiButtonColorDisabled, $euiButtonColorDisabled, 2); + } + } +} + +.euiButtonGroupButton__textShift { + @include euiTextShift; +} + +/** + * Medium and Small sizing (regular button style) + */ + +// sass-lint:disable nesting-depth +.euiButtonGroup--medium, +.euiButtonGroup--small { + .euiButtonGroupButton { + border: $euiBorderThin; + + &:not(:first-child) { + margin-left: -1px; + } + + &:first-child { + border-radius: $euiBorderRadius 0 0 $euiBorderRadius; + } + + &:last-child { + border-radius: 0 $euiBorderRadius $euiBorderRadius 0; + } + } + + .euiButtonGroupButton-isDisabled { + &.euiButtonGroupButton-isSelected { + background-color: $euiButtonColorDisabled; + border-color: $euiButtonColorDisabled; + + &:hover, + &:focus, + &:focus-within { + background-color: $euiButtonColorDisabled; + border-color: $euiButtonColorDisabled; + } + } + } + + @each $name, $color in $euiButtonTypes { + .euiButtonGroupButton--#{$name}:not([class*='isDisabled']) { + @if ($name == 'ghost') { + // Ghost is unique and ALWAYS sits against a dark background. + color: $color; + } @else if ($name == 'text') { + // The default color is lighter than the normal text color, make the it the text color + color: $euiTextColor; + } @else { + // Other colors need to check their contrast against the page background color. + color: makeHighContrastColor($color, $euiPageBackgroundColor); + } + + &.euiButtonGroupButton-isSelected { + background-color: $color; + border-color: $color; + + // The function makes that hexes safe for theming + $fillTextColor: chooseLightOrDarkText($color, $euiColorGhost, $euiColorInk); + + color: $fillTextColor; + + &:hover, + &:focus, + &:focus-within { + background-color: darken($color, 5%); + border-color: darken($color, 5%); + } + } + + &:hover, + &:focus, + &:focus-within { + background-color: transparentize($color, .9); + } + } + } + + // Fix ghost/disabled look specifically + .euiButtonGroupButton.euiButtonGroupButton-isDisabled.euiButtonGroupButton--ghost { + &, + &:hover, + &:focus, + &:focus-within { + color: $euiButtonColorGhostDisabled; + } + + .euiButtonGroup--isDisabled & { + border-color: $euiButtonColorGhostDisabled; + } + + &.euiButtonGroupButton-isSelected { + background-color: $euiButtonColorGhostDisabled; + color: makeHighContrastColor($euiButtonColorGhostDisabled, $euiButtonColorGhostDisabled, 2); + } + } + + .euiButtonGroupButton-isSelected { + z-index: 0; + } + + .euiButtonGroupButton-isSelected + .euiButtonGroupButton-isSelected { + box-shadow: -1px 0 0 transparentize($euiColorEmptyShade, .9); + } +} + +/** + * Compressed (form style) + */ + +.euiButtonGroup--compressed { + .euiButtonGroupButton { + height: $euiFormControlCompressedHeight - 2px; + line-height: $euiFormControlCompressedHeight - 2px; // prevents descenders from getting cut off + font-size: $euiFontSizeS; + border-radius: $euiBorderRadius; + // Offset the background color from the border by 2px + // by clipping background to before the padding starts + padding: 2px; + background-clip: content-box; + + .euiButton__content { + padding-left: $euiSizeS; + padding-right: $euiSizeS; + } + + &.euiButtonGroupButton-isSelected { + font-weight: $euiFontWeightSemiBold; + background-color: $euiFormInputGroupLabelBackground; + } + + &:not([class*='isDisabled']) { + color: $euiColorDarkShade; + + &:hover, + &:focus, + &:focus-within { + background-color: transparentize($euiFormInputGroupLabelBackground, .5); + } + + &:focus, + &:focus-within { + outline: 2px solid $euiFocusRingColor; + } + } + } + + @each $name, $color in $euiButtonTypes { + .euiButtonGroupButton--#{$name}:not([class*='isDisabled']) { + &.euiButtonGroupButton-isSelected { + @if ($name == 'text') { + // The default color is lighter than the normal text color, make the it the text color + color: $euiTextColor; + } @else { + // Other colors need to check their contrast against the page background color. + color: makeHighContrastColor($color, $euiPageBackgroundColor); + } + } + } + } +} diff --git a/src/components/button/button_group/_index.scss b/src/components/button/button_group/_index.scss index 78cfad351a8..dd9e2fa377b 100644 --- a/src/components/button/button_group/_index.scss +++ b/src/components/button/button_group/_index.scss @@ -1 +1,2 @@ @import 'button_group'; +@import 'button_group_button'; diff --git a/src/components/button/button_group/button_group.test.tsx b/src/components/button/button_group/button_group.test.tsx index 46ffcba1a43..0bfa03159f4 100644 --- a/src/components/button/button_group/button_group.test.tsx +++ b/src/components/button/button_group/button_group.test.tsx @@ -19,73 +19,84 @@ import React from 'react'; import { render } from 'enzyme'; -import { requiredProps } from '../../../test'; +import { requiredProps as commonProps } from '../../../test'; -import { EuiButtonGroup, GroupButtonSize } from './button_group'; +import { EuiButtonGroup, EuiButtonGroupProps } from './button_group'; import { COLORS } from '../button'; -const SIZES: GroupButtonSize[] = ['s', 'm', 'compressed']; +const SIZES: Array = [ + 's', + 'm', + 'compressed', +]; const options = [ { id: 'button00', label: 'Option one', + iconType: 'bolt', + ...commonProps, }, { id: 'button01', label: 'Option two', + iconType: 'bolt', }, { id: 'button02', label: 'Option three', + iconType: 'bolt', + isDisabled: true, }, ]; -describe('EuiButtonGroup', () => { - test('is rendered', () => { - const component = render( - {}} {...requiredProps} /> - ); - - expect(component).toMatchSnapshot(); - }); - - describe('props', () => { - describe('options', () => { - it('are rendered', () => { - const component = render( - {}} options={options} /> - ); +const requiredSingleProps: EuiButtonGroupProps = { + type: 'single', + legend: 'test', + onChange: () => {}, + options, + name: 'test', + idSelected: '', +}; + +const requiredMultiProps: EuiButtonGroupProps = { + type: 'multi', + legend: 'test', + onChange: () => {}, + options, +}; - expect(component).toMatchSnapshot(); - }); - - it('can pass down data-test-subj', () => { - const options2 = [ - { - id: 'button00', - label: 'Option one', - 'data-test-subj': 'test', - }, - ]; +describe('EuiButtonGroup', () => { + describe('type', () => { + test('single is rendered', () => { + const component = render( + + ); - const component = render( - {}} options={options2} /> - ); + expect(component).toMatchSnapshot(); + }); + test('multi is rendered', () => { + const component = render( + + ); - expect(component).toMatchSnapshot(); - }); + expect(component).toMatchSnapshot(); }); + }); + describe('button props', () => { describe('buttonSize', () => { SIZES.forEach((size) => { - test(`${size} is rendered`, () => { + test(`${size} is rendered for single`, () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + test(`${size} is rendered for multi`, () => { const component = render( - {}} - buttonSize={size} - options={options} - /> + ); expect(component).toMatchSnapshot(); @@ -94,105 +105,89 @@ describe('EuiButtonGroup', () => { }); describe('isDisabled', () => { - it('is rendered', () => { + it('is rendered for single', () => { const component = render( - {}} isDisabled options={options} /> + ); expect(component).toMatchSnapshot(); }); - }); - - describe('isFullWidth', () => { - it('is rendered', () => { + it('is rendered for multi', () => { const component = render( - {}} isFullWidth options={options} /> + ); expect(component).toMatchSnapshot(); }); }); - describe('isIconOnly', () => { - it('is rendered', () => { + describe('isFullWidth', () => { + it('is rendered for single', () => { const component = render( - {}} isIconOnly options={options} /> + ); expect(component).toMatchSnapshot(); }); - }); - - describe('color', () => { - COLORS.forEach((color) => { - test(`${color} is rendered`, () => { - const component = render( - {}} - color={color} - options={options} - /> - ); + it('is rendered for multi', () => { + const component = render( + + ); - expect(component).toMatchSnapshot(); - }); + expect(component).toMatchSnapshot(); }); }); - describe('legend', () => { - it('is rendered', () => { + describe('isIconOnly', () => { + it('is rendered for single', () => { const component = render( - {}} - legend="legend" - options={options} - /> + ); expect(component).toMatchSnapshot(); }); - }); - - describe('name', () => { - it('is rendered', () => { + it('is rendered for multi', () => { const component = render( - {}} name="name" options={options} /> + ); expect(component).toMatchSnapshot(); }); }); - describe('idSelected', () => { - it('is rendered', () => { - const component = render( - {}} - idSelected="button00" - options={options} - /> - ); + describe('color', () => { + COLORS.forEach((color) => { + test(`${color} is rendered for single`, () => { + const component = render( + + ); - expect(component).toMatchSnapshot(); + expect(component).toMatchSnapshot(); + }); + test(`${color} is rendered for multi`, () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); }); }); - describe('type of multi', () => { - it('is rendered', () => { + describe('selection', () => { + it('idSelected is rendered for single', () => { const component = render( - {}} type="multi" options={options} /> + ); expect(component).toMatchSnapshot(); }); - it('idToSelectedMap is rendered', () => { + it('idToSelectedMap is rendered for multi', () => { const component = render( {}} - type="multi" - idToSelectedMap={{ button00: true, button01: true }} - options={options} + {...requiredMultiProps} + idToSelectedMap={{ [options[0].id]: true, [options[1].id]: true }} /> ); diff --git a/src/components/button/button_group/button_group.tsx b/src/components/button/button_group/button_group.tsx index 63750ccf3ce..59d9f5c95f9 100644 --- a/src/components/button/button_group/button_group.tsx +++ b/src/components/button/button_group/button_group.tsx @@ -17,148 +17,178 @@ * under the License. */ -import React, { ReactNode, FunctionComponent, HTMLAttributes } from 'react'; import classNames from 'classnames'; - +import React, { FunctionComponent, HTMLAttributes, ReactNode } from 'react'; import { EuiScreenReaderOnly } from '../../accessibility'; -import { ToggleType } from '../../toggle'; - -import { EuiButtonToggle } from '../button_toggle'; +import { EuiButtonGroupButton } from './button_group_button'; +import { colorToClassNameMap, ButtonColor } from '../button'; +import { EuiButtonContentProps } from '../button_content'; import { CommonProps } from '../../common'; +import { htmlIdGenerator } from '../../../services'; -import { ButtonColor } from '../button'; -import { ButtonContentIconSide } from '../button_content'; -import { IconType } from '../../icon'; - -export interface EuiButtonGroupIdToSelectedMap { - [id: string]: boolean; -} - -export type GroupButtonSize = 's' | 'm' | 'compressed'; - -export interface EuiButtonGroupOption extends CommonProps { +export interface EuiButtonGroupOptionProps + extends EuiButtonContentProps, + CommonProps { + /** + * Each option must have a unique `id` for maintaining selection + */ id: string; + /** + * Each option must have a `label` even for icons which will be applied as the `aria-label` + */ label: ReactNode; - name?: string; isDisabled?: boolean; + /** + * The value of the radio input. + */ value?: any; - iconSide?: ButtonContentIconSide; - iconType?: IconType; } -export interface EuiButtonGroupProps extends CommonProps { - options?: EuiButtonGroupOption[]; - onChange: (id: string, value?: any) => void; +export type EuiButtonGroupProps = CommonProps & { /** * Typical sizing is `s`. Medium `m` size should be reserved for major features. * `compressed` is meant to be used alongside and within compressed forms. */ - buttonSize?: GroupButtonSize; + buttonSize?: 's' | 'm' | 'compressed'; isDisabled?: boolean; + /** + * Expands the whole group to the full width of the container. + * Each button gets equal widths no matter the content + */ isFullWidth?: boolean; + /** + * Hides the label to only show the `iconType` provided by the `option` + */ isIconOnly?: boolean; - idSelected?: string; - legend?: string; + /** + * A hidden group title (required for accessibility) + */ + legend: string; + /** + * Compressed styles don't support `ghost` color (Color will be changed to "text") + */ color?: ButtonColor; - name?: string; - type?: ToggleType; - idToSelectedMap?: EuiButtonGroupIdToSelectedMap; -} + /** + * Actual type is `'single' | 'multi'`. + * Determines how the selection of the group should be handled. + * With `'single'` only one option can be selected at a time (similar to radio group). + * With `'multi'` multiple options selected (similar to checkbox group). + */ + type?: 'single' | 'multi'; + /** + * An array of #EuiButtonGroupOptionProps + */ + options: EuiButtonGroupOptionProps[]; +} & ( + | { + /** + * Default for `type` is single so it can also be excluded + */ + type?: 'single'; + /** + * The `name` attribute for radio inputs; + * Defaults to a random string + */ + name?: string; + /** + * Styles the selected option to look selected (usually with `fill`) + * Required by and only used in `type='single'`. + */ + idSelected: string; + /** + * Single: Returns the `id` of the clicked option and the `value` + */ + onChange: (id: string, value?: any) => void; + idToSelectedMap?: never; + } + | { + type: 'multi'; + /** + * A map of `id`s as keys with the selected boolean values. + * Required by and only used in `type='multi'`. + */ + idToSelectedMap?: { [id: string]: boolean }; + /** + * Multi: Returns the `id` of the clicked option + */ + onChange: (id: string) => void; + idSelected?: never; + name?: never; + } + ); -type Props = Omit, 'onChange'> & +type Props = Omit, 'onChange'> & EuiButtonGroupProps; +const groupSizeToClassNameMap = { + s: '--small', + m: '--medium', + compressed: '--compressed', +}; + export const EuiButtonGroup: FunctionComponent = ({ className, buttonSize = 's', color = 'text', - idSelected, + idSelected = '', idToSelectedMap = {}, - isDisabled, - isFullWidth, - isIconOnly, - name, + isDisabled = false, + isFullWidth = false, + isIconOnly = false, legend, + name, onChange, options = [], type = 'single', - 'data-test-subj': dataTestSubj, ...rest }) => { + // Compressed style can't support `ghost` color because it's more like a form field than a button + const badColorCombo = buttonSize === 'compressed' && color === 'ghost'; + const resolvedColor = badColorCombo ? 'text' : color; + if (badColorCombo) { + console.warn( + 'EuiButtonGroup of compressed size does not support the ghost color. It will render as text instead.' + ); + } + const classes = classNames( 'euiButtonGroup', - [`euiButtonGroup--${buttonSize}`], + `euiButtonGroup${groupSizeToClassNameMap[buttonSize]}`, + `euiButtonGroup${colorToClassNameMap[resolvedColor]}`, { 'euiButtonGroup--fullWidth': isFullWidth, + 'euiButtonGroup--isDisabled': isDisabled, }, className ); - const fieldsetClasses = classNames('euiButtonGroup__fieldset', { - 'euiButtonGroup__fieldset--fullWidth': isFullWidth, - }); + const typeIsSingle = type === 'single'; + const nameIfSingle = name || htmlIdGenerator()(); - let legendNode; - if (legend) { - legendNode = ( + return ( +
{legend} - ); - } - - return ( -
- {legendNode} -
+
{options.map((option, index) => { - const { - id, - name: optionName, - value, - isDisabled: optionDisabled, - className, - ...rest - } = option; - - let isSelectedState; - if (type === 'multi') { - isSelectedState = idToSelectedMap[id] || false; - } else { - isSelectedState = id === idSelected; - } - - let fill; - if (buttonSize !== 'compressed') { - fill = isSelectedState; - } - const buttonClasses = classNames( - 'euiButtonGroup__button', - { - 'euiButtonGroup__button--selected': isSelectedState, - }, - className - ); - return ( - onChange(id, value)} - size={buttonSize === 'compressed' ? 's' : buttonSize} - type={type} - data-test-subj={dataTestSubj} - {...rest} + onChange={onChange} /> ); })} diff --git a/src/components/button/button_group/button_group_button.tsx b/src/components/button/button_group/button_group_button.tsx new file mode 100644 index 00000000000..dea959ed661 --- /dev/null +++ b/src/components/button/button_group/button_group_button.tsx @@ -0,0 +1,144 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import classNames from 'classnames'; +import React, { FunctionComponent } from 'react'; +import { EuiButtonDisplay } from '../button'; +import { EuiButtonGroupOptionProps, EuiButtonGroupProps } from './button_group'; +import { useInnerText } from '../../inner_text'; + +type Props = EuiButtonGroupOptionProps & { + /** + * Element to display based on single or multi + */ + type: 'button' | 'label'; + /** + * Styles the selected button to look selected (usually with `fill`) + */ + isSelected?: boolean; + /** + * Name of the whole group for 'single'. + */ + name?: string; + /** + * The value of the radio input for 'single'. + */ + value?: string; + /** + * Inherit from EuiButtonGroup + */ + color: EuiButtonGroupProps['color']; + /** + * Inherit from EuiButtonGroup + */ + size: EuiButtonGroupProps['buttonSize']; + /** + * Inherit from EuiButtonGroup + */ + isIconOnly: EuiButtonGroupProps['isIconOnly']; + /** + * Inherit from EuiButtonGroup + */ + onChange: EuiButtonGroupProps['onChange']; +}; + +export const EuiButtonGroupButton: FunctionComponent = ({ + className, + id, + isDisabled, + isIconOnly, + isSelected = false, + label, + name, + onChange, + size, + value, + type = 'button', + ...rest +}) => { + // Force element to be a button if disabled + const element = isDisabled ? 'button' : type; + + let elementProps = {}; + let singleInput; + if (element === 'label') { + elementProps = { + ...elementProps, + htmlFor: id, + onClick: () => onChange(id, value), + }; + singleInput = ( + onChange(id, value)} + /> + ); + } else { + elementProps = { + ...elementProps, + id, + isSelected, + onClick: () => onChange(id), + }; + } + + const buttonClasses = classNames( + { + 'euiButtonGroupButton-isSelected': isSelected, + 'euiButtonGroupButton-isIconOnly': isIconOnly, + }, + className + ); + + /** + * Because the selected buttons also increase their text weight to 'bold', + * we don't want the whole button size to shift when selected, so we determine + * the base width of the button via the `euiTextShift()` method in SASS. + */ + const [buttonTextRef, innerText] = useInnerText(); + + return ( + + {singleInput} + {label} + + ); +}; diff --git a/src/components/button/button_group/index.ts b/src/components/button/button_group/index.ts index 5e8f193e409..e1f27153750 100644 --- a/src/components/button/button_group/index.ts +++ b/src/components/button/button_group/index.ts @@ -19,6 +19,6 @@ export { EuiButtonGroup, - EuiButtonGroupOption, + EuiButtonGroupOptionProps, EuiButtonGroupProps, } from './button_group'; diff --git a/src/components/button/button_icon/__snapshots__/button_icon.test.tsx.snap b/src/components/button/button_icon/__snapshots__/button_icon.test.tsx.snap index 6cae9bb61a3..78e6dc06502 100644 --- a/src/components/button/button_icon/__snapshots__/button_icon.test.tsx.snap +++ b/src/components/button/button_icon/__snapshots__/button_icon.test.tsx.snap @@ -200,3 +200,33 @@ exports[`EuiButtonIcon props isDisabled renders a button even when href is defin /> `; + +exports[`EuiButtonIcon props isSelected is rendered as false 1`] = ` + +`; + +exports[`EuiButtonIcon props isSelected is rendered as true 1`] = ` + +`; diff --git a/src/components/button/button_icon/button_icon.test.tsx b/src/components/button/button_icon/button_icon.test.tsx index f8efe7d3ee2..b8d16e05f2c 100644 --- a/src/components/button/button_icon/button_icon.test.tsx +++ b/src/components/button/button_icon/button_icon.test.tsx @@ -78,6 +78,28 @@ describe('EuiButtonIcon', () => { }); }); + describe('isSelected', () => { + it('is rendered as true', () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + + it('is rendered as false', () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + }); + describe('href', () => { it('secures the rel attribute when the target is _blank', () => { const component = render( diff --git a/src/components/button/button_icon/button_icon.tsx b/src/components/button/button_icon/button_icon.tsx index f05cf593e7e..7a30fa9c57c 100644 --- a/src/components/button/button_icon/button_icon.tsx +++ b/src/components/button/button_icon/button_icon.tsx @@ -57,6 +57,11 @@ export interface EuiButtonIconProps extends CommonProps { isDisabled?: boolean; size?: ButtonSize; iconSize?: IconSize; + /** + * Applies the boolean state as the `aria-pressed` property to create a toggle button. + * *Only use when the readable text does not change between states.* + */ + isSelected?: boolean; } type EuiButtonIconPropsForAnchor = { @@ -107,6 +112,7 @@ export const EuiButtonIcon: FunctionComponent = ({ target, rel, buttonRef, + isSelected, ...rest }) => { const ariaHidden = rest['aria-hidden']; @@ -163,6 +169,7 @@ export const EuiButtonIcon: FunctionComponent = ({ tabIndex={isAriaHidden ? -1 : undefined} disabled={isDisabled} className={classes} + aria-pressed={isSelected} type={type as typeof buttonType} ref={buttonRef as Ref} {...(rest as ButtonHTMLAttributes)}> diff --git a/src/components/button/button_toggle/__snapshots__/button_toggle.test.tsx.snap b/src/components/button/button_toggle/__snapshots__/button_toggle.test.tsx.snap deleted file mode 100644 index 5293306c543..00000000000 --- a/src/components/button/button_toggle/__snapshots__/button_toggle.test.tsx.snap +++ /dev/null @@ -1,31 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`EuiButtonToggle is rendered 1`] = ` -
- - -
-`; diff --git a/src/components/button/button_toggle/_button_toggle.scss b/src/components/button/button_toggle/_button_toggle.scss deleted file mode 100644 index d6d33ea6745..00000000000 --- a/src/components/button/button_toggle/_button_toggle.scss +++ /dev/null @@ -1,74 +0,0 @@ - -.euiButtonToggle__wrapper { - display: inline-block; - - // Transform the whole thing so that the button doesn't - // steal pointer events from input - &:not(.euiButtonToggle--isDisabled) { - transition: transform $euiAnimSpeedNormal $euiAnimSlightBounce; - - &:hover { - transform: translateY(-1px); - } - - &:focus { - animation: euiButtonActive $euiAnimSpeedNormal $euiAnimSlightBounce; - } - - &:active { - transform: translateY(1px); - } - } -} - -.euiButtonToggle { - @include euiButtonToggleStates { - text-decoration: underline; - } - - &.euiButtonToggle--isIconOnly { - min-width: 0; - - .euiButton__content { - padding: 0 $euiSizeS; - } - - .euiButton__text:empty { - display: none; - } - } - - &.euiButtonToggle--isEmpty { - border-color: transparent; - background-color: transparent; - box-shadow: none; - } -} - -// Modifier naming and colors. -$euiButtonToggleTypes: ( - primary: $euiColorPrimary, - secondary: $euiColorSecondary, - warning: $euiColorWarning, - danger: $euiColorDanger, - ghost: $euiColorGhost, // Ghost is special, and does not care about theming. - text: $euiColorDarkShade, // Reserved for special use cases -); - -// Add hover states based on input state -@each $name, $color in $euiButtonToggleTypes { - .euiButtonToggle[class*='#{$name}'] { - @include euiButtonToggleStates { - background-color: transparentize($color, .9); - } - - // Not using the full button class here as it's targeting a different component, - // instead we know what the modifier is, so just look for that somewhere in the classNames - &[class*='fill'] { - @include euiButtonToggleStates { - background-color: darken($color, 5%); - border-color: darken($color, 5%); - } - } - } -} diff --git a/src/components/button/button_toggle/_index.scss b/src/components/button/button_toggle/_index.scss deleted file mode 100644 index 9e43663b603..00000000000 --- a/src/components/button/button_toggle/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import 'mixins'; -@import 'button_toggle'; diff --git a/src/components/button/button_toggle/_mixins.scss b/src/components/button/button_toggle/_mixins.scss deleted file mode 100644 index 6419334aa60..00000000000 --- a/src/components/button/button_toggle/_mixins.scss +++ /dev/null @@ -1,12 +0,0 @@ -// BUTTON TOGGLES -// Pointer events and interactions are handled by the input (checkbox/radio) -// and not the button, this mixin helps to target that element for states -@mixin euiButtonToggleStates { - @at-root { - .euiButtonToggle__input:enabled:hover + #{&}, - .euiButtonToggle__input:enabled:focus + #{&}, - .euiButtonToggle__input:enabled:active + #{&} { - @content; - } - } -} diff --git a/src/components/button/button_toggle/button_toggle.test.tsx b/src/components/button/button_toggle/button_toggle.test.tsx deleted file mode 100644 index 1111772511f..00000000000 --- a/src/components/button/button_toggle/button_toggle.test.tsx +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import { render } from 'enzyme'; -import { requiredProps } from '../../../test'; - -import { EuiButtonToggle } from './button_toggle'; - -describe('EuiButtonToggle', () => { - test('is rendered', () => { - const component = render( - - ); - - expect(component).toMatchSnapshot(); - }); -}); diff --git a/src/components/button/button_toggle/button_toggle.tsx b/src/components/button/button_toggle/button_toggle.tsx deleted file mode 100644 index cf086c90730..00000000000 --- a/src/components/button/button_toggle/button_toggle.tsx +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React, { - AnchorHTMLAttributes, - ButtonHTMLAttributes, - ChangeEventHandler, - FunctionComponent, - MouseEventHandler, - ReactNode, -} from 'react'; -import classNames from 'classnames'; -import { CommonProps, ExclusiveUnion } from '../../common'; - -import { EuiToggle, ToggleType } from '../../toggle'; -import { EuiButton, EuiButtonProps } from '../button'; -import { useRenderToText } from '../../inner_text/render_to_text'; - -export interface EuiButtonToggleProps extends EuiButtonProps, CommonProps { - /** - * Simulates a `EuiButtonEmpty` - */ - isEmpty?: boolean; - - /** - * Hides the label from the button content and only displays the icon - */ - isIconOnly?: boolean; - - /** - * Initial state of the toggle - */ - isSelected?: boolean; - - /** - * Button label, which is also passed to `EuiToggle` as the input's label - */ - label: ReactNode; - - /** - * Classnames to add to `EuiToggle` instead of the `EuiButton` - */ - toggleClassName?: string; - - /** - * Is the button a single action or part of a group (multi)? - * Used primarily for `EuiButtonGroup` - */ - type?: ToggleType; - - onChange?: ChangeEventHandler; -} - -type EuiButtonTogglePropsForAnchor = EuiButtonToggleProps & - Omit, 'name' | 'href' | 'onClick'> & { - href?: string; - name?: string; - onClick?: MouseEventHandler; - }; - -type EuiButtonTogglePropsForButtonToggle = EuiButtonToggleProps & - Omit< - ButtonHTMLAttributes, - 'name' | 'onClick' | 'value' - > & { - onClick?: MouseEventHandler; - name?: string; - value?: string; - }; - -type Props = ExclusiveUnion< - EuiButtonTogglePropsForAnchor, - EuiButtonTogglePropsForButtonToggle ->; - -export const EuiButtonToggle: FunctionComponent = ({ - className, - color = 'primary', - isDisabled, - isEmpty, - isIconOnly, - isSelected, - label, - name, - onChange, - toggleClassName, - type, - value, - 'data-test-subj': dataTestSubj, - ...rest -}) => { - const classes = classNames( - 'euiButtonToggle', - { - 'euiButtonToggle--isIconOnly': isIconOnly, - 'euiButtonToggle--isEmpty': isEmpty, - }, - className - ); - - const wrapperClasses = classNames( - 'euiButtonToggle__wrapper', - { - 'euiButtonToggle--isDisabled': isDisabled, - }, - toggleClassName - ); - - const buttonContent = isIconOnly ? '' : label; - const labelText = useRenderToText( - label, - typeof label === 'string' ? label : '' - ); - - return ( - - )}> - {buttonContent} - - - ); -}; diff --git a/src/components/button/button_toggle/index.ts b/src/components/button/button_toggle/index.ts deleted file mode 100644 index 6bd321eefb0..00000000000 --- a/src/components/button/button_toggle/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { EuiButtonToggle, EuiButtonToggleProps } from './button_toggle'; diff --git a/src/components/button/index.ts b/src/components/button/index.ts index 68189c28425..0298b137e35 100644 --- a/src/components/button/index.ts +++ b/src/components/button/index.ts @@ -39,10 +39,8 @@ export { EuiButtonIconPropsForButton, } from './button_icon'; -export { EuiButtonToggle, EuiButtonToggleProps } from './button_toggle'; - export { EuiButtonGroup, - EuiButtonGroupOption, + EuiButtonGroupOptionProps, EuiButtonGroupProps, } from './button_group'; diff --git a/src/components/control_bar/__snapshots__/control_bar.test.tsx.snap b/src/components/control_bar/__snapshots__/control_bar.test.tsx.snap index 1008506cfd0..47388bb5679 100644 --- a/src/components/control_bar/__snapshots__/control_bar.test.tsx.snap +++ b/src/components/control_bar/__snapshots__/control_bar.test.tsx.snap @@ -300,6 +300,7 @@ exports[`EuiControlBar props leftOffset is rendered 1`] = ` size="s" > ( - -
-`; - -exports[`EuiToggle props checked is rendered 1`] = ` -
- -
-`; - -exports[`EuiToggle props isDisabled is rendered 1`] = ` -
- -
-`; - -exports[`EuiToggle props onChange is rendered 1`] = ` -
- -
-`; - -exports[`EuiToggle props type is rendered 1`] = ` -
- -
-`; diff --git a/src/components/toggle/_index.scss b/src/components/toggle/_index.scss deleted file mode 100644 index a7f5cb8c186..00000000000 --- a/src/components/toggle/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'toggle'; diff --git a/src/components/toggle/_toggle.scss b/src/components/toggle/_toggle.scss deleted file mode 100644 index ae37badaa33..00000000000 --- a/src/components/toggle/_toggle.scss +++ /dev/null @@ -1,12 +0,0 @@ -.euiToggle { - position: relative; - - .euiToggle__input { - @include euiHiddenSelectableInput; - z-index: 1; // Increase z-index to ensure the input receives the pointer events - - &:disabled { - cursor: not-allowed; - } - } -} diff --git a/src/components/toggle/index.ts b/src/components/toggle/index.ts deleted file mode 100644 index ca8545c5b54..00000000000 --- a/src/components/toggle/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { EuiToggle, ToggleType, TYPES as TOGGLE_TYPES } from './toggle'; diff --git a/src/components/toggle/toggle.test.tsx b/src/components/toggle/toggle.test.tsx deleted file mode 100644 index e67948cb987..00000000000 --- a/src/components/toggle/toggle.test.tsx +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import { render } from 'enzyme'; -import { requiredProps } from '../../test/required_props'; - -import { EuiToggle } from './toggle'; - -describe('EuiToggle', () => { - test('is rendered', () => { - const component = render( - - ); - - expect(component).toMatchSnapshot(); - }); - - describe('props', () => { - test('isDisabled is rendered', () => { - const component = render(); - - expect(component).toMatchSnapshot(); - }); - - test('onChange is rendered', () => { - const component = render( - - ); - - expect(component).toMatchSnapshot(); - }); - - test('type is rendered', () => { - const component = render( - - ); - - expect(component).toMatchSnapshot(); - }); - - test('checked is rendered', () => { - const component = render( - - ); - - expect(component).toMatchSnapshot(); - }); - }); -}); diff --git a/src/components/toggle/toggle.tsx b/src/components/toggle/toggle.tsx deleted file mode 100644 index 00766e027e5..00000000000 --- a/src/components/toggle/toggle.tsx +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React, { ChangeEventHandler, HTMLAttributes, SFC } from 'react'; -import classNames from 'classnames'; -import { CommonProps } from '../common'; - -const typeToInputTypeMap = { - single: 'radio', - multi: 'checkbox', -}; - -export const TYPES = Object.keys(typeToInputTypeMap); - -export type ToggleType = keyof typeof typeToInputTypeMap; - -export type EuiToggleProps = Omit, 'onChange'> & - CommonProps & { - id?: string; - /** - * Initial state of the toggle - */ - checked?: boolean; - /** - * For handling the onChange event of the input - */ - onChange?: ChangeEventHandler; - isDisabled?: boolean; - name?: string; - /** - * Determines the input type based on multiple or single item(s) - */ - type?: ToggleType; - /** - * What would typically be the input's label. Required for accessibility. - */ - label: string; - /** - * Additional classNames for the input itself - */ - inputClassName?: string; - value?: string | number; - }; - -export const EuiToggle: SFC = ({ - id, - className, - checked, - children, - inputClassName, - isDisabled, - label, - name, - onChange, - title, - type, - value, - 'data-test-subj': dataTestSubj, - ...rest -}) => { - const classes = classNames( - 'euiToggle', - { 'euiToggle--checked': checked }, - className - ); - - const inputClasses = classNames('euiToggle__input', inputClassName); - - return ( -
- - - {children} -
- ); -}; - -EuiToggle.defaultProps = { - type: 'multi', -}; diff --git a/src/global_styling/mixins/_button.scss b/src/global_styling/mixins/_button.scss index f5069625e8a..5c176de596c 100644 --- a/src/global_styling/mixins/_button.scss +++ b/src/global_styling/mixins/_button.scss @@ -16,7 +16,7 @@ @mixin euiButtonFocus { transition: all $euiAnimSpeedNormal ease-in-out; - &:hover:not(:disabled) { + &:hover:not([class*='isDisabled']) { transform: translateY(-1px); } @@ -24,7 +24,7 @@ animation: euiButtonActive $euiAnimSpeedNormal $euiAnimSlightBounce; } - &:active:not(:disabled) { + &:active:not([class*='isDisabled']) { transform: translateY(1px); } } @@ -43,7 +43,7 @@ // focus states should come after all default styles @include euiButtonFocus; - &:hover:not(:disabled), + &:hover:not([class*='isDisabled']), &:focus { text-decoration: underline; } diff --git a/src/global_styling/variables/_buttons.scss b/src/global_styling/variables/_buttons.scss index 79c5810dd20..df81cccad48 100644 --- a/src/global_styling/variables/_buttons.scss +++ b/src/global_styling/variables/_buttons.scss @@ -5,7 +5,6 @@ $euiButtonColorDisabled: tintOrShade($euiTextColor, 70%, 70%) !default; // Only increase the contrast of background color to text to 2.0 for disabled $euiButtonColorDisabledText: makeHighContrastColor($euiButtonColorDisabled, $euiPageBackgroundColor, 2) !default; $euiButtonColorGhostDisabled: lightOrDarkTheme($euiColorDarkShade, $euiColorLightShade) !default; -$euiButtonToggleBorderColor: $euiColorLightShade !default; // Modifier naming and colors. $euiButtonTypes: ( diff --git a/src/themes/eui-amsterdam/global_styling/mixins/_button.scss b/src/themes/eui-amsterdam/global_styling/mixins/_button.scss index 33479a57304..cc269d1c748 100644 --- a/src/themes/eui-amsterdam/global_styling/mixins/_button.scss +++ b/src/themes/eui-amsterdam/global_styling/mixins/_button.scss @@ -11,7 +11,7 @@ // focus states should come after all default styles @include euiButtonFocus; - &:hover:not(:disabled), + &:hover:not([class*='isDisabled']), &:focus { text-decoration: underline; } @@ -28,7 +28,7 @@ // But still use transparency background-color: transparentize($color, $transparency); - &:enabled { + &:not([class*='isDisabled']) { &:hover, &:focus { background-color: transparentize($color, lightOrDarkTheme(.9, .65)); diff --git a/src/themes/eui-amsterdam/overrides/_button.scss b/src/themes/eui-amsterdam/overrides/_button.scss index d4d22be0452..618f8f21c2e 100644 --- a/src/themes/eui-amsterdam/overrides/_button.scss +++ b/src/themes/eui-amsterdam/overrides/_button.scss @@ -9,7 +9,7 @@ // Added exclusion of the `ghost` type of button // so as not to override those specific styles from default theme // And the only style that needs to change is the non-filled version - &:disabled:not(.euiButton--ghost):not(.euiButton--fill) { + &.euiButton-isDisabled:not(.euiButton--ghost):not(.euiButton--fill) { $backgroundColorSimulated: mix($euiPageBackgroundColor, $euiButtonColorDisabled, 70%); background-color: transparentize($euiButtonColorDisabled, .7); color: makeHighContrastColor($euiButtonColorDisabled, $backgroundColorSimulated, 2); @@ -34,7 +34,7 @@ color: makeHighContrastColor($color, $backgroundColorSimulated); } - &.euiButton--fill:enabled { + &.euiButton--fill:not([class*='isDisabled']) { &:hover, &:focus { background-color: darken($color, 10%); @@ -44,7 +44,7 @@ } // Fix ghost/disabled look specifically -.euiButton:disabled.euiButton--ghost:not(.euiButton--fill) { +.euiButton.euiButton-isDisabled.euiButton--ghost:not(.euiButton--fill) { &, &:hover, &:focus { diff --git a/src/themes/eui-amsterdam/overrides/_button_group.scss b/src/themes/eui-amsterdam/overrides/_button_group.scss index 7c1ae611abe..33cf0d61501 100644 --- a/src/themes/eui-amsterdam/overrides/_button_group.scss +++ b/src/themes/eui-amsterdam/overrides/_button_group.scss @@ -1,14 +1,49 @@ -.euiButtonGroup--compressed { - .euiButtonGroup__button { - &:not(.euiButtonGroup__button--selected):not(:disabled) { - background-color: transparentize($euiColorLightShade, .6); +.euiButtonGroup__buttons { + box-shadow: none !important; // sass-lint:disable-line no-important +} + +.euiButtonGroup--medium, +.euiButtonGroup--small { + .euiButtonGroupButton { + border: none !important; // sass-lint:disable-line no-important + } + + .euiButtonGroupButton-isDisabled:not(.euiButtonGroupButton--ghost):not(.euiButtonGroupButton-isSelected) { + $backgroundColorSimulated: mix($euiPageBackgroundColor, $euiButtonColorDisabled, 70%); + background-color: transparentize($euiButtonColorDisabled, .7); + color: makeHighContrastColor($euiButtonColorDisabled, $backgroundColorSimulated, 2); + } + + // Change the hollow (bordered) buttons to have a transparent background + // and no border + @each $name, $color in $euiButtonTypes { + .euiButtonGroupButton--#{$name} { + @include euiButtonDefaultStyle($color); + + @if ($name == 'ghost') { + // Ghost is unique and ALWAYS sits against a dark background. + $backgroundColorSimulated: mix($euiColorInk, $color, 70%); + color: makeHighContrastColor($color, $backgroundColorSimulated); + } } } - .euiButtonToggle__input:enabled:hover + .euiButtonGroup__button, - .euiButtonToggle__input:enabled:focus + .euiButtonGroup__button, - .euiButtonGroup__button--selected { - background-color: $euiColorDarkShade; - color: $euiColorLightestShade; + .euiButtonGroupButton-isDisabled.euiButtonGroupButton--ghost:not(.euiButtonGroupButton-isSelected) { + &, + &:hover, + &:focus { + background-color: transparentize($euiButtonColorGhostDisabled, .7); + } } } + +.euiButtonGroup--small .euiButtonGroup__buttons { + // Use a moderately smaller radius on small buttons + // so that they don't appear completely rounded + border-radius: $euiBorderRadius * .667; +} + +.euiButtonGroup--compressed .euiButtonGroupButton { + // Add 1 to the border radius to account for the background-clip + border-radius: $euiFormControlCompressedBorderRadius + 1; +}