diff --git a/src-docs/src/views/accordion/accordion_disabled.tsx b/src-docs/src/views/accordion/accordion_disabled.tsx new file mode 100644 index 00000000000..11c4fa20266 --- /dev/null +++ b/src-docs/src/views/accordion/accordion_disabled.tsx @@ -0,0 +1,41 @@ +import React, { useState } from 'react'; + +import { + EuiAccordion, + EuiFieldPassword, + EuiFormRow, + EuiPanel, + useGeneratedHtmlId, +} from '../../../../src'; + +export default () => { + const [passwordValue, setPasswordValue] = useState(''); + const disabledAccordionId = useGeneratedHtmlId({ + prefix: 'disabledAccordion', + }); + + const isInvalid = passwordValue === ''; + + return ( + + + + setPasswordValue(v.currentTarget.value)} + /> + + + + ); +}; diff --git a/src-docs/src/views/accordion/accordion_example.js b/src-docs/src/views/accordion/accordion_example.js index ff28c277b0a..b48f115c2cd 100644 --- a/src-docs/src/views/accordion/accordion_example.js +++ b/src-docs/src/views/accordion/accordion_example.js @@ -89,6 +89,9 @@ const accordionIsLoadingSnippet = [ import AccordionButtonElement from './accordion_buttonElement'; const accordionButtonElementSource = require('!!raw-loader!./accordion_buttonElement'); +import AccordionDisabled from './accordion_disabled'; +const accordionDisabledSource = require('!!raw-loader!./accordion_disabled'); + export const AccordionExample = { title: 'Accordion', intro: ( @@ -314,6 +317,53 @@ export const AccordionExample = { snippet: accordionIsLoadingSnippet, demo: , }, + { + title: 'Disabled state', + source: [ + { + type: GuideSectionTypes.JS, + code: accordionDisabledSource, + }, + ], + text: ( + <> +

+ In some cases you might want to prevent the user from closing the{' '} + EuiAccordion. For example, if a form field is + displaying an error, opening the accordion and preventing its + closure until the error has been addressed will help the user find + and fix the error. +

+

+ The isDisabled prop will disable interaction on + the button and the arrow. Use it in conjunction with{' '} + initialIsOpen to achieve the desired effect. +

+ + When disabling the interaction of accordions based on user + input, it's advisable to ensure there is further + explanation through the use of help or error text. + + } + /> + + ), + demo: , + props: { EuiAccordion }, + snippet: ` + + +`, + }, { title: 'When content changes dynamically', source: [ diff --git a/src-docs/src/views/accordion/playground.js b/src-docs/src/views/accordion/playground.js index 260c231db53..4dfc963ce87 100644 --- a/src-docs/src/views/accordion/playground.js +++ b/src-docs/src/views/accordion/playground.js @@ -21,6 +21,11 @@ export const accordionConfig = () => { type: PropTypes.String, }; + propsToUse.buttonElement = { + ...propsToUse.buttonElement, + defaultValue: 'button', + }; + propsToUse.id = { ...propsToUse.id, value: htmlIdGenerator('generated')(), diff --git a/src/components/accordion/__snapshots__/accordion.test.tsx.snap b/src/components/accordion/__snapshots__/accordion.test.tsx.snap index e3f1e27bef9..c837f863ea6 100644 --- a/src/components/accordion/__snapshots__/accordion.test.tsx.snap +++ b/src/components/accordion/__snapshots__/accordion.test.tsx.snap @@ -8,7 +8,7 @@ exports[`EuiAccordion behavior closes when clicked twice 1`] = ` class="euiAccordion__triggerWrapper emotion-euiAccordion__triggerWrapper" > + + +
+
+
+

+ You cannot see me. +

+
+
+
+ +`; + exports[`EuiAccordion behavior opens when clicked once 1`] = `
`; +exports[`EuiAccordion isDisabled is rendered 1`] = ` +
+
+ + +
+
+
+
+
+
+
+`; + exports[`EuiAccordion props arrowDisplay none is rendered 1`] = `
{ text-decoration: underline; } `, + + // Triggering button needs separate `disabled` key because the element that renders may not support `:disabled`; + // Hover pseudo selector for specificity + disabled: css` + &, + &:hover { + cursor: not-allowed; + color: ${euiTheme.colors.disabledText}; + text-decoration: none; + } + `, }; }; diff --git a/src/components/accordion/accordion.test.tsx b/src/components/accordion/accordion.test.tsx index d8be9c9a74a..6cc19cf896c 100644 --- a/src/components/accordion/accordion.test.tsx +++ b/src/components/accordion/accordion.test.tsx @@ -188,6 +188,14 @@ describe('EuiAccordion', () => { }); }); + describe('isDisabled', () => { + it('is rendered', () => { + const component = render(); + + expect(component).toMatchSnapshot(); + }); + }); + describe('behavior', () => { it('opens when clicked once', () => { const component = mount( @@ -201,6 +209,18 @@ describe('EuiAccordion', () => { expect(component.render()).toMatchSnapshot(); }); + it('does not open when isDisabled', () => { + const component = mount( + +

You cannot see me.

+
+ ); + + component.find('button').at(0).simulate('click'); + + expect(component.render()).toMatchSnapshot(); + }); + it('opens when div is clicked if element is a div', () => { const component = mount( diff --git a/src/components/accordion/accordion.tsx b/src/components/accordion/accordion.tsx index a6aaef40aa5..7b12d2bd34d 100644 --- a/src/components/accordion/accordion.tsx +++ b/src/components/accordion/accordion.tsx @@ -110,6 +110,10 @@ export type EuiAccordionProps = CommonProps & * Choose whether the loading message replaces the content. Customize the message by passing a node */ isLoadingMessage?: boolean | ReactNode; + /** + * Disable the open/close interaction and visually subdues the trigger + */ + isDisabled?: boolean; }; export class EuiAccordionClass extends Component< @@ -121,6 +125,7 @@ export class EuiAccordionClass extends Component< paddingSize: 'none' as const, arrowDisplay: 'left' as const, isLoading: false, + isDisabled: false, isLoadingMessage: false, element: 'div' as const, buttonElement: 'button' as const, @@ -206,6 +211,7 @@ export class EuiAccordionClass extends Component< forceState, isLoading, isLoadingMessage, + isDisabled, buttonProps, buttonElement: _ButtonElement = 'button', arrowProps, @@ -263,7 +269,10 @@ export class EuiAccordionClass extends Component< // Emotion styles const buttonStyles = euiAccordionButtonStyles(theme); - const cssButtonStyles = [buttonStyles.euiAccordion__button]; + const cssButtonStyles = [ + buttonStyles.euiAccordion__button, + isDisabled && buttonStyles.disabled, + ]; const childrenStyles = euiAccordionChildrenStyles(theme); const cssChildrenStyles = [ @@ -313,6 +322,7 @@ export class EuiAccordionClass extends Component< aria-expanded={isOpen} aria-labelledby={buttonId} tabIndex={buttonElementIsFocusable ? -1 : 0} + isDisabled={isDisabled} /> ); } @@ -361,8 +371,9 @@ export class EuiAccordionClass extends Component< css={cssButtonStyles} aria-controls={id} aria-expanded={isOpen} - onClick={this.onToggle} + onClick={isDisabled ? undefined : this.onToggle} type={ButtonElement === 'button' ? 'button' : undefined} + disabled={ButtonElement === 'button' ? isDisabled : undefined} > {buttonContent} diff --git a/upcoming_changelogs/6095.md b/upcoming_changelogs/6095.md new file mode 100644 index 00000000000..b6b4bc593b0 --- /dev/null +++ b/upcoming_changelogs/6095.md @@ -0,0 +1 @@ +- Added `isDisabled` prop to `EuiAccordion`