From 4a27d0925428d03d1f5aa1ddf49307e044300806 Mon Sep 17 00:00:00 2001 From: Ryan Keairns Date: Thu, 5 Mar 2020 08:25:23 -0600 Subject: [PATCH 1/9] Add showOnFocus prop to EuiScreenReader Only & Add EuiSkipLink (#2976) * adds showOnFocus prop * add EuiSkipLink component * address feedback, add fixedToTop prop * addressing feedback * use EuiButton * Updated some props to be enums and extended EuiButton type and added tests * add snippets, as one does * remove tabIndex from fixed example * remove portal from docs example Co-authored-by: Caroline Horn <549577+cchaos@users.noreply.github.com> --- CHANGELOG.md | 3 + .../accessibility/accessibility_example.js | 105 ++++++++++++++---- .../accessibility/keyboard_accessible.js | 60 +++++----- src-docs/src/views/accessibility/props.tsx | 6 + .../src/views/accessibility/screen_reader.tsx | 47 +++++++- src-docs/src/views/accessibility/skip_link.js | 61 ++++++++++ .../__snapshots__/screen_reader.test.tsx.snap | 9 ++ .../__snapshots__/skip_link.test.tsx.snap | 68 ++++++++++++ src/components/accessibility/_index.scss | 1 + .../accessibility/_screen_reader.scss | 3 +- src/components/accessibility/_skip_link.scss | 20 ++++ src/components/accessibility/index.ts | 1 + .../accessibility/screen_reader.test.tsx | 10 ++ .../accessibility/screen_reader.tsx | 15 ++- .../accessibility/skip_link.test.tsx | 27 +++++ src/components/accessibility/skip_link.tsx | 68 ++++++++++++ src/components/index.js | 6 +- 17 files changed, 455 insertions(+), 55 deletions(-) create mode 100644 src-docs/src/views/accessibility/props.tsx create mode 100644 src-docs/src/views/accessibility/skip_link.js create mode 100644 src/components/accessibility/__snapshots__/skip_link.test.tsx.snap create mode 100644 src/components/accessibility/_skip_link.scss create mode 100644 src/components/accessibility/skip_link.test.tsx create mode 100644 src/components/accessibility/skip_link.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index e720bef827b1..58097844fabc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## [`master`](https://github.com/elastic/eui/tree/master) +- Added `showOnFocus` prop to `EuiScreenReaderOnly` to force display on keyboard focus ([#2976](https://github.com/elastic/eui/pull/2976)) +- Added `EuiSkipLink` component ([#2976](https://github.com/elastic/eui/pull/2976)) + **Bug Fixes** - Fixed `EuiDataGrid`'s sort popover to behave properly on mobile screens ([#2979](https://github.com/elastic/eui/pull/2979)) diff --git a/src-docs/src/views/accessibility/accessibility_example.js b/src-docs/src/views/accessibility/accessibility_example.js index 140c87e7237b..0986f1020a04 100644 --- a/src-docs/src/views/accessibility/accessibility_example.js +++ b/src-docs/src/views/accessibility/accessibility_example.js @@ -5,26 +5,56 @@ import { renderToHtml } from '../../services'; import { GuideSectionTypes } from '../../components'; import { + EuiCallOut, EuiCode, EuiLink, EuiKeyboardAccessible, - EuiScreenReaderOnly, + EuiSkipLink, } from '../../../../src/components'; import KeyboardAccessible from './keyboard_accessible'; import ScreenReaderOnly from './screen_reader'; +import SkipLink from './skip_link'; const keyboardAccessibleSource = require('!!raw-loader!./keyboard_accessible'); const keyboardAccessibleHtml = renderToHtml(KeyboardAccessible); +const keyboardAccessibleSnippet = ` + +`; const screenReaderOnlyHtml = renderToHtml(ScreenReaderOnly); const screenReaderOnlySource = require('!!raw-loader!./screen_reader'); +const screenReaderOnlySnippet = [ + ` + + +`, + ` + + +`, +]; + +const skipLinkHtml = renderToHtml(SkipLink); +const skipLinkSource = require('!!raw-loader!./skip_link'); +const skipLinkSnippet = [ + ` + Skip to content + +`, + ` + Skip to main content + +`, +]; + +import { ScreenReaderOnlyDocsComponent } from './props'; export const AccessibilityExample = { title: 'Accessibility', sections: [ { - title: 'KeyboardAccessible', + title: 'Keyboard accessible', source: [ { type: GuideSectionTypes.JS, @@ -37,17 +67,18 @@ export const AccessibilityExample = { ], text: (

- You can make interactive elements keyboard-accessible with this - component. This is necessary for non-button elements and{' '} - a tags without + You can make interactive elements keyboard-accessible with the{' '} + EuiKeyboardAccessible component. This is necessary + for non-button elements and a tags without{' '} href attributes.

), props: { EuiKeyboardAccessible }, + snippet: keyboardAccessibleSnippet, demo: , }, { - title: 'ScreenReaderOnly', + title: 'Screen reader only', source: [ { type: GuideSectionTypes.JS, @@ -61,24 +92,58 @@ export const AccessibilityExample = { text: (

- This class can be useful to add accessibility to older designs that - are still in use, but it shouldn’t be a permanent solution. - See{' '} - { - - http://webaim.org/techniques/css/invisiblecontent/ - - }{' '} - for more information. -

-

- Use a screenreader to verify that there is a second paragraph in - this example: + Use the EuiScreenReaderOnly component to visually + hide elements while still allowing them to be read by screen + readers. In certain cases, you may want to use the{' '} + showOnFocus prop to display screen reader-only + content when in focus.

+ +

+ "In most cases, if content (particularly content that + provides functionality or interactivity) is important enough to + provide to screen reader users, it should probably be made + available to all users."{' '} + + Learn more about invisible content + +

+
), - props: { EuiScreenReaderOnly }, + props: { + EuiScreenReaderOnly: ScreenReaderOnlyDocsComponent, + }, + snippet: screenReaderOnlySnippet, demo: , }, + { + title: 'Skip link', + source: [ + { + type: GuideSectionTypes.JS, + code: skipLinkSource, + }, + { + type: GuideSectionTypes.HTML, + code: skipLinkHtml, + }, + ], + text: ( +

+ The EuiSkipLink component allows users to bypass + navigation, or ornamental elements, and quickly reach the main content + of the page. +

+ ), + props: { EuiSkipLink }, + snippet: skipLinkSnippet, + demo: , + }, ], }; diff --git a/src-docs/src/views/accessibility/keyboard_accessible.js b/src-docs/src/views/accessibility/keyboard_accessible.js index 3f68055265e5..3d08c0ac5984 100644 --- a/src-docs/src/views/accessibility/keyboard_accessible.js +++ b/src-docs/src/views/accessibility/keyboard_accessible.js @@ -1,6 +1,7 @@ import React from 'react'; import { EuiKeyboardAccessible } from '../../../../src/components'; +import { EuiText } from '../../../../src/components/text'; // For custom components, we just need to make sure they delegate props to their rendered root // element, e.g. onClick, tabIndex, and role. @@ -10,33 +11,42 @@ const CustomComponent = ({ children, ...rest }) => ( export default () => (
- -
window.alert('Div clicked')}>Click this div
-
+ + +
window.alert('Div clicked')}>Click this div
+
- - window.alert('Anchor tag clicked')}> - Click this anchor tag - - + + window.alert('Anchor tag clicked')}> + Click this anchor tag + + - - window.alert('Custom component clicked')}> - Click this custom component - - + + window.alert('Custom component clicked')}> + Click this custom component + + - -
window.alert('Outer EuiKeyboardAccessible clicked')}> - This EuiKeyboardAccessible contains another EuiKeyboardAccessible  - - window.alert('Inner EuiKeyboardAccessible clicked')}> - Clicking this inner one should call both onClick handlers - - -
-
+ +
window.alert('Outer EuiKeyboardAccessible clicked')}> + This EuiKeyboardAccessible contains another + EuiKeyboardAccessible  + + + window.alert('Inner EuiKeyboardAccessible clicked') + }> + Clicking this inner one should call both onClick handlers + + +
+
+
); diff --git a/src-docs/src/views/accessibility/props.tsx b/src-docs/src/views/accessibility/props.tsx new file mode 100644 index 000000000000..29e44b11cc84 --- /dev/null +++ b/src-docs/src/views/accessibility/props.tsx @@ -0,0 +1,6 @@ +import React, { FunctionComponent } from 'react'; +import { EuiScreenReaderOnlyProps } from '../../../../src/components/accessibility/screen_reader'; + +export const ScreenReaderOnlyDocsComponent: FunctionComponent< + EuiScreenReaderOnlyProps +> = () =>
; diff --git a/src-docs/src/views/accessibility/screen_reader.tsx b/src-docs/src/views/accessibility/screen_reader.tsx index c5d74bc5b316..dc43ab1e0c11 100644 --- a/src-docs/src/views/accessibility/screen_reader.tsx +++ b/src-docs/src/views/accessibility/screen_reader.tsx @@ -1,16 +1,51 @@ import React from 'react'; import { EuiScreenReaderOnly } from '../../../../src/components/accessibility/screen_reader'; +import { EuiCallOut } from '../../../../src/components/call_out'; +import { EuiText } from '../../../../src/components/text'; +import { EuiTitle } from '../../../../src/components/title'; +import { EuiLink } from '../../../../src/components/link'; export default () => (
-

This is the first paragraph. It is visible to all.

- + + +

Visually hide content

+

- This is the second paragraph. It is hidden for sighted users but visible - to screen readers. + + Use a screenreader to verify that there is a second paragraph in this + example: +

-
-

This is the third paragraph. It is visible to all.

+

This is the first paragraph. It is visible to all.

+ +

+ This is the second paragraph. It is hidden for sighted users but + visible to screen readers. +

+
+

This is the third paragraph. It is visible to all.

+ +

Show on focus

+
+

+ + Tab through this section with your keyboard to display a ‘Skip + navigation’ link: + +

+

+ This link is visible to all on focus:{' '} + + Skip navigation + +

+ +
); diff --git a/src-docs/src/views/accessibility/skip_link.js b/src-docs/src/views/accessibility/skip_link.js new file mode 100644 index 000000000000..15cf70e03c19 --- /dev/null +++ b/src-docs/src/views/accessibility/skip_link.js @@ -0,0 +1,61 @@ +import React, { Fragment, useState } from 'react'; + +import { EuiSkipLink } from '../../../../src/components/accessibility/skip_link'; +import { EuiCallOut } from '../../../../src/components/call_out'; +import { EuiText } from '../../../../src/components/text'; +import { EuiSpacer } from '../../../../src/components/spacer'; +import { EuiSwitch } from '../../../../src/components/form/switch'; + +export default () => { + const [isFixed, setFixed] = useState(false); + + return ( + + + {isFixed ? ( +

+ + Tab through this section and a fixed{' '} + Skip to main content link will appear atop this + page. + +

+ ) : ( +

+ + Tab through this section and a Skip to content{' '} + link will appear below. + +

+ )} +
+ + setFixed(e.target.checked)} + /> + + {isFixed ? ( + + + Skip to main content + + + + ) : ( + + Skip to content + + )} +
+ ); +}; diff --git a/src/components/accessibility/__snapshots__/screen_reader.test.tsx.snap b/src/components/accessibility/__snapshots__/screen_reader.test.tsx.snap index e218d61cb91c..39b7d8e177a8 100644 --- a/src/components/accessibility/__snapshots__/screen_reader.test.tsx.snap +++ b/src/components/accessibility/__snapshots__/screen_reader.test.tsx.snap @@ -15,3 +15,12 @@ exports[`EuiScreenReaderOnly adds an accessibility class to a child element when This paragraph is not visibile to sighted users but will be read by screenreaders.

`; + +exports[`EuiScreenReaderOnly will show on focus 1`] = ` + + Link + +`; diff --git a/src/components/accessibility/__snapshots__/skip_link.test.tsx.snap b/src/components/accessibility/__snapshots__/skip_link.test.tsx.snap new file mode 100644 index 000000000000..685870e4087a --- /dev/null +++ b/src/components/accessibility/__snapshots__/skip_link.test.tsx.snap @@ -0,0 +1,68 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EuiSkipLink is rendered 1`] = ` + + + + + +`; + +exports[`EuiSkipLink position absolute is rendered 1`] = ` + + + + + +`; + +exports[`EuiSkipLink position fixed is rendered 1`] = ` + + + + + +`; + +exports[`EuiSkipLink position static is rendered 1`] = ` + + + + + +`; diff --git a/src/components/accessibility/_index.scss b/src/components/accessibility/_index.scss index 98294afdf558..b8b7ffe9a8f1 100644 --- a/src/components/accessibility/_index.scss +++ b/src/components/accessibility/_index.scss @@ -1 +1,2 @@ @import 'screen_reader'; +@import 'skip_link'; diff --git a/src/components/accessibility/_screen_reader.scss b/src/components/accessibility/_screen_reader.scss index 398beafda858..6cc8a8e64071 100644 --- a/src/components/accessibility/_screen_reader.scss +++ b/src/components/accessibility/_screen_reader.scss @@ -1,3 +1,4 @@ -.euiScreenReaderOnly { +.euiScreenReaderOnly, +.euiScreenReaderOnly--showOnFocus:not(:focus) { @include euiScreenReaderOnly; } diff --git a/src/components/accessibility/_skip_link.scss b/src/components/accessibility/_skip_link.scss new file mode 100644 index 000000000000..38aa7fd814d6 --- /dev/null +++ b/src/components/accessibility/_skip_link.scss @@ -0,0 +1,20 @@ +.euiSkipLink { + transition: none !important; // sass-lint:disable-line no-important + + &:focus { + animation: none !important; // sass-lint:disable-line no-important + } + + // Set positions on focus only as to no override screenReaderOnly position + // When positioned absolutely, consumers still need to tell it WHERE (top,left,etc...) + &.euiSkipLink--absolute:focus { + position: absolute; + } + + &.euiSkipLink--fixed:focus { + position: fixed; + top: $euiSizeXS; + left: $euiSizeXS; + z-index: $euiZHeader + 1; + } +} diff --git a/src/components/accessibility/index.ts b/src/components/accessibility/index.ts index d63ef5aa8008..06e7f202092d 100644 --- a/src/components/accessibility/index.ts +++ b/src/components/accessibility/index.ts @@ -1,2 +1,3 @@ export { EuiKeyboardAccessible } from './keyboard_accessible'; export { EuiScreenReaderOnly } from './screen_reader'; +export { EuiSkipLink } from './skip_link'; diff --git a/src/components/accessibility/screen_reader.test.tsx b/src/components/accessibility/screen_reader.test.tsx index 5b6c06e59afb..db021b454ac2 100644 --- a/src/components/accessibility/screen_reader.test.tsx +++ b/src/components/accessibility/screen_reader.test.tsx @@ -30,4 +30,14 @@ describe('EuiScreenReaderOnly', () => { expect($paragraph).toMatchSnapshot(); }); }); + + test('will show on focus', () => { + const component = render( + + Link + + ); + + expect(component).toMatchSnapshot(); + }); }); diff --git a/src/components/accessibility/screen_reader.tsx b/src/components/accessibility/screen_reader.tsx index 437d8d5a5a0e..55485b541e19 100644 --- a/src/components/accessibility/screen_reader.tsx +++ b/src/components/accessibility/screen_reader.tsx @@ -3,12 +3,23 @@ import classNames from 'classnames'; export interface EuiScreenReaderOnlyProps { children: ReactElement; + + /** + * For keyboard navigation, force content to display visually upon focus. + */ + showOnFocus?: boolean; } export const EuiScreenReaderOnly: FunctionComponent< EuiScreenReaderOnlyProps -> = ({ children }) => { - const classes = classNames('euiScreenReaderOnly', children.props.className); +> = ({ children, showOnFocus }) => { + const classes = classNames( + { + euiScreenReaderOnly: !showOnFocus, + 'euiScreenReaderOnly--showOnFocus': showOnFocus, + }, + children.props.className + ); const props = { ...children.props, diff --git a/src/components/accessibility/skip_link.test.tsx b/src/components/accessibility/skip_link.test.tsx new file mode 100644 index 000000000000..e7a0589d50c3 --- /dev/null +++ b/src/components/accessibility/skip_link.test.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../test'; + +import { EuiSkipLink, POSITIONS } from './skip_link'; + +describe('EuiSkipLink', () => { + test('is rendered', () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + + describe('position', () => { + POSITIONS.forEach(position => { + test(`${position} is rendered`, () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/src/components/accessibility/skip_link.tsx b/src/components/accessibility/skip_link.tsx new file mode 100644 index 000000000000..a87e2880b9c7 --- /dev/null +++ b/src/components/accessibility/skip_link.tsx @@ -0,0 +1,68 @@ +import React, { FunctionComponent, Ref } from 'react'; +import classNames from 'classnames'; +import { EuiButton, EuiButtonProps } from '../button/button'; +import { EuiScreenReaderOnly } from '../accessibility/screen_reader'; +import { PropsForAnchor, PropsForButton, ExclusiveUnion } from '../common'; + +type Positions = 'static' | 'fixed' | 'absolute'; +export const POSITIONS = ['static', 'fixed', 'absolute'] as Positions[]; + +export interface EuiSkipLinkProps extends EuiButtonProps { + /** + * If true, the link will be fixed to the top left of the viewport + */ + position?: Positions; + + /** + * Typically an anchor id (e.g. `a11yMainContent`), the value provided + * will be prepended with a hash `#` and used as the link `href` + */ + destinationId: string; + + tabIndex?: number; +} + +type propsForAnchor = PropsForAnchor< + EuiSkipLinkProps, + { + buttonRef?: Ref; + } +>; + +type propsForButton = PropsForButton< + EuiSkipLinkProps, + { + buttonRef?: Ref; + } +>; + +export type Props = ExclusiveUnion; + +export const EuiSkipLink: FunctionComponent = ({ + destinationId, + tabIndex, + position = 'static', + children, + className, + ...rest +}) => { + const classes = classNames( + 'euiSkipLink', + [`euiSkipLink--${position}`], + className + ); + + return ( + + + {children} + + + ); +}; diff --git a/src/components/index.js b/src/components/index.js index f4cb7cf66528..764036c894d5 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -4,7 +4,11 @@ export { EuiAspectRatio } from './aspect_ratio'; export { EuiAvatar } from './avatar'; -export { EuiKeyboardAccessible, EuiScreenReaderOnly } from './accessibility'; +export { + EuiKeyboardAccessible, + EuiScreenReaderOnly, + EuiSkipLink, +} from './accessibility'; export { EuiBadge, EuiBetaBadge, EuiNotificationBadge } from './badge'; From 1360731ab19acbf1b44b63a52df8d44042446690 Mon Sep 17 00:00:00 2001 From: "dave.snider@gmail.com" Date: Thu, 5 Mar 2020 06:54:20 -0800 Subject: [PATCH 2/9] use React.RefCallback instead of custom implementation (#2929) Updates our code to use React's RefCallback type. --- CHANGELOG.md | 4 +++ package.json | 9 +++--- src/components/combo_box/combo_box.tsx | 4 +-- .../combo_box_input/combo_box_input.tsx | 8 ++++-- .../combo_box_option.tsx | 7 +++-- .../combo_box_options_list.tsx | 8 ++++-- src/components/combo_box/types.ts | 5 ---- src/components/common.ts | 4 --- src/components/facet/facet_button.tsx | 3 +- src/components/popover/popover.tsx | 5 ++-- yarn.lock | 28 +++++++++---------- 11 files changed, 46 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58097844fabc..6ee05e2f75a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ - Fixed `EuiDataGrid`'s sort popover to behave properly on mobile screens ([#2979](https://github.com/elastic/eui/pull/2979)) - Fixed `EuiButton` and other textual components' disabled contrast ([#2874](https://github.com/elastic/eui/pull/2874)) +**Breaking changes** + +- Updated `@types/react` and `@types/react-dom` to utilize React.RefCallback type instead of custom implementation ([#2929](https://github.com/elastic/eui/pull/2929)) + **Theme: Amsterdam** - Buttons have a new visual style ([#2874](https://github.com/elastic/eui/pull/2874)) diff --git a/package.json b/package.json index 2d7126241137..c12f48cb84c5 100644 --- a/package.json +++ b/package.json @@ -88,8 +88,9 @@ "@types/classnames": "^2.2.6", "@types/highlight.js": "^9.12.3", "@types/jest": "^24.0.6", - "@types/react": "^16.9.11", - "@types/react-dom": "^16.9.4", + "@types/react": "^16.9.23", + "@types/react-dom": "^16.9.5", + "@types/react-input-autosize": "^2.0.1", "@types/react-is": "^16.7.1", "@types/resize-observer-browser": "^0.1.1", "@types/tabbable": "^3.1.0", @@ -197,8 +198,8 @@ }, "peerDependencies": { "@elastic/datemath": "^5.0.2", - "@types/react": "^16.9.11", - "@types/react-dom": "^16.9.4", + "@types/react": "^16.9.23", + "@types/react-dom": "^16.9.5", "moment": "^2.13.0", "prop-types": "^15.5.0", "react": "^16.12", diff --git a/src/components/combo_box/combo_box.tsx b/src/components/combo_box/combo_box.tsx index cd29bea80b8d..ef3c5f9905a1 100644 --- a/src/components/combo_box/combo_box.tsx +++ b/src/components/combo_box/combo_box.tsx @@ -7,8 +7,9 @@ import React, { Component, FocusEventHandler, - KeyboardEventHandler, HTMLAttributes, + KeyboardEventHandler, + RefCallback, } from 'react'; import classNames from 'classnames'; @@ -34,7 +35,6 @@ import { EuiComboBoxOptionsListProps } from './combo_box_options_list/combo_box_ import { UpdatePositionHandler, OptionHandler, - RefCallback, RefInstance, EuiComboBoxOptionOption, EuiComboBoxOptionsListPosition, diff --git a/src/components/combo_box/combo_box_input/combo_box_input.tsx b/src/components/combo_box/combo_box_input/combo_box_input.tsx index de91822d9e49..b5e605dbe822 100644 --- a/src/components/combo_box/combo_box_input/combo_box_input.tsx +++ b/src/components/combo_box/combo_box_input/combo_box_input.tsx @@ -1,4 +1,9 @@ -import React, { Component, FocusEventHandler, ChangeEventHandler } from 'react'; +import React, { + ChangeEventHandler, + Component, + FocusEventHandler, + RefCallback, +} from 'react'; import classNames from 'classnames'; import AutosizeInput from 'react-input-autosize'; @@ -11,7 +16,6 @@ import { EuiComboBoxOptionOption, EuiComboBoxSingleSelectionShape, OptionHandler, - RefCallback, UpdatePositionHandler, } from '../types'; import { CommonProps } from '../../common'; diff --git a/src/components/combo_box/combo_box_options_list/combo_box_option.tsx b/src/components/combo_box/combo_box_options_list/combo_box_option.tsx index 3ee02230ee80..382dbbab372a 100644 --- a/src/components/combo_box/combo_box_options_list/combo_box_option.tsx +++ b/src/components/combo_box/combo_box_options_list/combo_box_option.tsx @@ -1,13 +1,14 @@ import React, { Component, - ReactNode, - KeyboardEventHandler, HTMLAttributes, + KeyboardEventHandler, + ReactNode, + RefCallback, } from 'react'; import classNames from 'classnames'; import { ENTER, SPACE } from '../../../services/key_codes'; -import { EuiComboBoxOptionOption, OptionHandler, RefCallback } from '../types'; +import { EuiComboBoxOptionOption, OptionHandler } from '../types'; import { CommonProps } from '../../common'; export interface EuiComboBoxOptionProps diff --git a/src/components/combo_box/combo_box_options_list/combo_box_options_list.tsx b/src/components/combo_box/combo_box_options_list/combo_box_options_list.tsx index 5530df3027b3..04967ce18b06 100644 --- a/src/components/combo_box/combo_box_options_list/combo_box_options_list.tsx +++ b/src/components/combo_box/combo_box_options_list/combo_box_options_list.tsx @@ -1,4 +1,9 @@ -import React, { Component, ReactNode, ComponentProps } from 'react'; +import React, { + Component, + ComponentProps, + ReactNode, + RefCallback, +} from 'react'; import classNames from 'classnames'; import { List, ListProps } from 'react-virtualized'; // eslint-disable-line import/named @@ -20,7 +25,6 @@ import { EuiComboBoxOptionsListPosition, EuiComboBoxSingleSelectionShape, OptionHandler, - RefCallback, RefInstance, UpdatePositionHandler, } from '../types'; diff --git a/src/components/combo_box/types.ts b/src/components/combo_box/types.ts index e0c3f1c7c4c7..445007de1c76 100644 --- a/src/components/combo_box/types.ts +++ b/src/components/combo_box/types.ts @@ -17,11 +17,6 @@ export type UpdatePositionHandler = ( ) => void; export type OptionHandler = (option: EuiComboBoxOptionOption) => void; -// See https://github.com/DefinitelyTyped/DefinitelyTyped/pull/42482/files -export type RefCallback = { - bivarianceHack(instance: T | null): void; -}['bivarianceHack']; - export type RefInstance = T | null; export type EuiComboBoxOptionsListPosition = 'top' | 'bottom'; diff --git a/src/components/common.ts b/src/components/common.ts index a0ed67e1b980..6e61a578fa77 100644 --- a/src/components/common.ts +++ b/src/components/common.ts @@ -15,10 +15,6 @@ export interface CommonProps { export type NoArgCallback = () => T; -export type RefCallback = ( - element: Element -) => void; - // utility types: /** diff --git a/src/components/facet/facet_button.tsx b/src/components/facet/facet_button.tsx index 795c2c72d0bd..68a603411b94 100644 --- a/src/components/facet/facet_button.tsx +++ b/src/components/facet/facet_button.tsx @@ -3,10 +3,11 @@ import React, { HTMLAttributes, MouseEventHandler, ReactNode, + RefCallback, } from 'react'; import classNames from 'classnames'; -import { CommonProps, RefCallback } from '../common'; +import { CommonProps } from '../common'; import { EuiNotificationBadge } from '../badge'; diff --git a/src/components/popover/popover.tsx b/src/components/popover/popover.tsx index 27761afd48d0..b3be36b6e64c 100644 --- a/src/components/popover/popover.tsx +++ b/src/components/popover/popover.tsx @@ -4,11 +4,12 @@ import React, { HTMLAttributes, ReactNode, Ref, + RefCallback, } from 'react'; import classNames from 'classnames'; import tabbable from 'tabbable'; -import { CommonProps, NoArgCallback, RefCallback } from '../common'; +import { CommonProps, NoArgCallback } from '../common'; import { FocusTarget, EuiFocusTrap } from '../focus_trap'; import { Props as ReactFocusLockProps } from 'react-focus-lock'; // eslint-disable-line import/named @@ -66,7 +67,7 @@ export interface EuiPopoverProps { button: NonNullable; - buttonRef?: RefCallback; + buttonRef?: RefCallback; closePopover: NoArgCallback; diff --git a/yarn.lock b/yarn.lock index 8da965e0c096..f78886da08fa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1248,9 +1248,9 @@ integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== "@types/prop-types@*": - version "15.5.9" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.5.9.tgz#f2d14df87b0739041bc53a7d75e3d77d726a3ec0" - integrity sha512-Nha5b+jmBI271jdTMwrHiNXM+DvThjHOfyZtMX9kj/c/LUj2xiLHsG/1L3tJ8DjAoQN48cHwUwtqBotjyXaSdQ== + version "15.7.3" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" + integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== "@types/q@^1.5.1": version "1.5.2" @@ -1264,10 +1264,10 @@ dependencies: "@types/react" "*" -"@types/react-dom@^16.9.4": - version "16.9.4" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.4.tgz#0b58df09a60961dcb77f62d4f1832427513420df" - integrity sha512-fya9xteU/n90tda0s+FtN5Ym4tbgxpq/hb/Af24dvs6uYnYn+fspaxw5USlw0R8apDNwxsqumdRoCoKitckQqw== +"@types/react-dom@^16.9.5": + version "16.9.5" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.5.tgz#5de610b04a35d07ffd8f44edad93a71032d9aaa7" + integrity sha512-BX6RQ8s9D+2/gDhxrj8OW+YD4R+8hj7FEM/OJHGNR0KipE1h1mSsf39YeyC81qafkq+N3rU3h3RFbLSwE5VqUg== dependencies: "@types/react" "*" @@ -1293,10 +1293,10 @@ "@types/prop-types" "*" "@types/react" "*" -"@types/react@*", "@types/react@^16.9.11": - version "16.9.13" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.13.tgz#b3ea5dd443f4a680599e2abba8cc66f5e1ce0059" - integrity sha512-LikzRslbiufJYHyzbHSW0GrAiff8QYLMBFeZmSxzCYGXKxi8m/1PHX+rsVOwhr7mJNq+VIu2Dhf7U6mjFERK6w== +"@types/react@*", "@types/react@^16.9.23": + version "16.9.23" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.23.tgz#1a66c6d468ba11a8943ad958a8cb3e737568271c" + integrity sha512-SsGVT4E7L2wLN3tPYLiF20hmZTPGuzaayVunfgXzUn1x4uHVsKH6QDJQ/TdpHqwsTLd4CwrmQ2vOgxN7gE24gw== dependencies: "@types/prop-types" "*" csstype "^2.2.0" @@ -4184,9 +4184,9 @@ cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": cssom "0.3.x" csstype@^2.2.0: - version "2.5.6" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.5.6.tgz#2ae1db2319642d8b80a668d2d025c6196071e788" - integrity sha512-tKPyhy0FmfYD2KQYXD5GzkvAYLYj96cMLXr648CKGd3wBe0QqoPipImjGiLze9c8leJK8J3n7ap90tpk3E6HGQ== + version "2.6.9" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.9.tgz#05141d0cd557a56b8891394c1911c40c8a98d098" + integrity sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q== csstype@^2.6.7: version "2.6.7" From c954597def0d657868442b7aca8956c418230a1b Mon Sep 17 00:00:00 2001 From: Caroline Horn <549577+cchaos@users.noreply.github.com> Date: Thu, 5 Mar 2020 10:58:39 -0500 Subject: [PATCH 3/9] [New] EuiBadgeGroup component (#2942) --- CHANGELOG.md | 1 + src-docs/src/views/badge/badge_example.js | 18 +++++- src-docs/src/views/badge/badge_truncate.js | 62 +++++++++---------- .../views/suggest/_global_filter_group.scss | 6 +- .../src/views/suggest/global_filter_bar.js | 35 ++--------- .../src/views/suggest/global_filter_item.js | 1 + src-docs/src/views/suggest/saved_queries.js | 3 +- src/components/badge/_index.scss | 1 + .../__snapshots__/badge_group.test.tsx.snap | 46 ++++++++++++++ .../badge/badge_group/_badge_group.scss | 23 +++++++ src/components/badge/badge_group/_index.scss | 1 + .../badge/badge_group/badge_group.test.tsx | 28 +++++++++ .../badge/badge_group/badge_group.tsx | 48 ++++++++++++++ src/components/badge/badge_group/index.ts | 1 + src/components/badge/index.ts | 2 + src/components/index.js | 7 ++- 16 files changed, 213 insertions(+), 70 deletions(-) create mode 100644 src/components/badge/badge_group/__snapshots__/badge_group.test.tsx.snap create mode 100644 src/components/badge/badge_group/_badge_group.scss create mode 100644 src/components/badge/badge_group/_index.scss create mode 100644 src/components/badge/badge_group/badge_group.test.tsx create mode 100644 src/components/badge/badge_group/badge_group.tsx create mode 100644 src/components/badge/badge_group/index.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ee05e2f75a0..a9aab2db999e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - Added `showOnFocus` prop to `EuiScreenReaderOnly` to force display on keyboard focus ([#2976](https://github.com/elastic/eui/pull/2976)) - Added `EuiSkipLink` component ([#2976](https://github.com/elastic/eui/pull/2976)) +- Created `EuiBadgeGroup` component ([#2921](https://github.com/elastic/eui/pull/2921)) **Bug Fixes** diff --git a/src-docs/src/views/badge/badge_example.js b/src-docs/src/views/badge/badge_example.js index 3568874491d3..39d9b972990e 100644 --- a/src-docs/src/views/badge/badge_example.js +++ b/src-docs/src/views/badge/badge_example.js @@ -11,10 +11,12 @@ import { EuiCode, EuiBetaBadge, EuiNotificationBadge, + EuiBadgeGroup, EuiCallOut, } from '../../../../src/components'; import Badge from './badge'; + const badgeSource = require('!!raw-loader!./badge'); const badgeHtml = renderToHtml(Badge); const badgeSnippet = [ @@ -72,6 +74,12 @@ const badgeButtonSnippet = [ import BadgeTruncate from './badge_truncate'; const badgeTruncateSource = require('!!raw-loader!./badge_truncate'); const badgeTruncateHtml = renderToHtml(BadgeTruncate); +const badgeTruncateSnippet = [ + ` + + +`, +]; import BetaBadge from './beta_badge'; const betaBadgeSource = require('!!raw-loader!./beta_badge'); @@ -170,7 +178,7 @@ export const BadgeExample = { demo: , }, { - title: 'Badge truncation', + title: 'Badge groups and truncation', source: [ { type: GuideSectionTypes.JS, @@ -193,9 +201,15 @@ export const BadgeExample = { to the title attribute of the element to provide default browser tooltips with the full badge text.

+

+ To ensure proper wrapping, truncation and spacing of multiple + badges, it is advisable to wrap them in a{' '} + EuiBadgeGroup +

), demo: , + snippet: badgeTruncateSnippet, }, { title: 'Beta badge type', @@ -233,7 +247,7 @@ export const BadgeExample = {

), - props: { EuiBetaBadge }, + props: { EuiBetaBadge, EuiBadgeGroup }, snippet: betaBadgeSnippet, demo: , }, diff --git a/src-docs/src/views/badge/badge_truncate.js b/src-docs/src/views/badge/badge_truncate.js index 9f26a97dd252..2759cd0e6c56 100644 --- a/src-docs/src/views/badge/badge_truncate.js +++ b/src-docs/src/views/badge/badge_truncate.js @@ -1,41 +1,35 @@ import React from 'react'; -import { EuiBadge, EuiPanel, EuiSpacer } from '../../../../src/components'; +import { EuiBadge, EuiPanel, EuiBadgeGroup } from '../../../../src/components'; export default () => ( - Badge with simple text being truncated - - - - Badge with icon being truncated - - - - {}} onClickAriaLabel="Click this badge to..."> - Badge with onClick being truncated - - - - - {}} - iconOnClickAriaLabel="Click this icon to..."> - Badge with iconOnClick being truncated - - - - - {}} - onClickAriaLabel="Click this badge to..." - iconOnClick={() => {}} - iconOnClickAriaLabel="Click this icon to..."> - Badge with both onClicks being truncated - + + Badge with simple text being truncated + + Badge with icon being truncated + + {}} onClickAriaLabel="Click this badge to..."> + Badge with onClick being truncated + + + {}} + iconOnClickAriaLabel="Click this icon to..."> + Badge with iconOnClick being truncated + + + {}} + onClickAriaLabel="Click this badge to..." + iconOnClick={() => {}} + iconOnClickAriaLabel="Click this icon to..."> + Badge with both onClicks being truncated + + ); diff --git a/src-docs/src/views/suggest/_global_filter_group.scss b/src-docs/src/views/suggest/_global_filter_group.scss index 82908a215aa0..3187c9551fbf 100644 --- a/src-docs/src/views/suggest/_global_filter_group.scss +++ b/src-docs/src/views/suggest/_global_filter_group.scss @@ -2,7 +2,7 @@ @import 'saved_queries'; .globalFilterGroup__filterBar { - margin-top: $euiSizeM; + margin-top: $euiSizeXS; } // sass-lint:disable quotes @@ -20,9 +20,9 @@ .globalFilterGroup__filterFlexItem { overflow: hidden; - padding-bottom: 2px; // Allow the shadows of the pills to show + padding: $euiSizeS; } .globalFilterBar__flexItem { max-width: calc(100% - #{$euiSizeXS}); // Width minus margin around each flex itm -} \ No newline at end of file +} diff --git a/src-docs/src/views/suggest/global_filter_bar.js b/src-docs/src/views/suggest/global_filter_bar.js index 5bd19d2e0771..aefa66cb3dd5 100644 --- a/src-docs/src/views/suggest/global_filter_bar.js +++ b/src-docs/src/views/suggest/global_filter_bar.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { EuiFlexGroup, EuiFlexItem } from '../../../../src/components'; +import { EuiBadgeGroup } from '../../../../src/components'; import GlobalFilterAdd from './global_filter_add'; import { GlobalFilterItem } from './global_filter_item'; @@ -12,45 +12,22 @@ export const GlobalFilterBar = ({ filters, className, ...rest }) => { const pinnedFilters = filters .filter(filter => filter.isPinned) .map(filter => { - return ( - - - - ); + return ; }); const unpinnedFilters = filters .filter(filter => !filter.isPinned) .map(filter => { - return ( - - - - ); + return ; }); return ( - + {/* Show pinned filters first and in a specific group */} {pinnedFilters} {unpinnedFilters} - - - - - + + ); }; diff --git a/src-docs/src/views/suggest/global_filter_item.js b/src-docs/src/views/suggest/global_filter_item.js index a575f10bccd9..87eb2ac55ed0 100644 --- a/src-docs/src/views/suggest/global_filter_item.js +++ b/src-docs/src/views/suggest/global_filter_item.js @@ -100,6 +100,7 @@ export class GlobalFilterItem extends Component { title={title} iconOnClick={this.deleteFilter} iconOnClickAriaLabel={'Delete filter'} + color="hollow" iconType="cross" iconSide="right" onClick={this.togglePopover} diff --git a/src-docs/src/views/suggest/saved_queries.js b/src-docs/src/views/suggest/saved_queries.js index 353ec1f925ce..8f7e04b70221 100644 --- a/src-docs/src/views/suggest/saved_queries.js +++ b/src-docs/src/views/suggest/saved_queries.js @@ -62,7 +62,8 @@ export default class extends Component { }, { id: 'filter1', - field: '@tags.keyword', + field: + 'Filter with a very long title to test if the badge will properly get truncated in the separate set of filter badges that are not quite as long but man does it really need to be long', operator: 'IS', value: 'value', isDisabled: true, diff --git a/src/components/badge/_index.scss b/src/components/badge/_index.scss index a567f7a42de2..a271275b2dc8 100644 --- a/src/components/badge/_index.scss +++ b/src/components/badge/_index.scss @@ -1,3 +1,4 @@ @import 'badge'; +@import 'badge_group/index'; @import 'beta_badge/index'; @import 'notification_badge/index'; diff --git a/src/components/badge/badge_group/__snapshots__/badge_group.test.tsx.snap b/src/components/badge/badge_group/__snapshots__/badge_group.test.tsx.snap new file mode 100644 index 000000000000..57ba7cbeb02e --- /dev/null +++ b/src/components/badge/badge_group/__snapshots__/badge_group.test.tsx.snap @@ -0,0 +1,46 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EuiBadgeGroup gutterSize none is rendered 1`] = ` +
+`; + +exports[`EuiBadgeGroup gutterSize s is rendered 1`] = ` +
+`; + +exports[`EuiBadgeGroup gutterSize xs is rendered 1`] = ` +
+`; + +exports[`EuiBadgeGroup is rendered 1`] = ` +
+ + + + + Content + + + + +
+`; diff --git a/src/components/badge/badge_group/_badge_group.scss b/src/components/badge/badge_group/_badge_group.scss new file mode 100644 index 000000000000..a1a0b0ec5d3c --- /dev/null +++ b/src/components/badge/badge_group/_badge_group.scss @@ -0,0 +1,23 @@ +$euiBadgeGroupGutterTypes: ( + gutterExtraSmall: $euiSizeXS, + gutterSmall: $euiSizeS, +); + +.euiBadgeGroup__item { + display: inline-block; + max-width: 100%; +} + +// Gutter Sizes +@each $gutterName, $gutterSize in $euiBadgeGroupGutterTypes { + $halfGutterSize: $gutterSize * .5; + + .euiBadgeGroup--#{$gutterName} { + margin: -$halfGutterSize; + + & > .euiBadgeGroup__item { + margin: $halfGutterSize; + max-width: calc(100% - #{$gutterSize}); + } + } +} diff --git a/src/components/badge/badge_group/_index.scss b/src/components/badge/badge_group/_index.scss new file mode 100644 index 000000000000..21500c4a8cf7 --- /dev/null +++ b/src/components/badge/badge_group/_index.scss @@ -0,0 +1 @@ +@import './badge_group'; diff --git a/src/components/badge/badge_group/badge_group.test.tsx b/src/components/badge/badge_group/badge_group.test.tsx new file mode 100644 index 000000000000..895f94e08b96 --- /dev/null +++ b/src/components/badge/badge_group/badge_group.test.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../../test/required_props'; + +import { EuiBadge } from '../badge'; +import { EuiBadgeGroup, GUTTER_SIZES } from './badge_group'; + +describe('EuiBadgeGroup', () => { + test('is rendered', () => { + const component = render( + + Content + + ); + + expect(component).toMatchSnapshot(); + }); + + describe('gutterSize', () => { + GUTTER_SIZES.forEach(size => { + it(`${size} is rendered`, () => { + const component = render(); + + expect(component).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/src/components/badge/badge_group/badge_group.tsx b/src/components/badge/badge_group/badge_group.tsx new file mode 100644 index 000000000000..ce3b97565c08 --- /dev/null +++ b/src/components/badge/badge_group/badge_group.tsx @@ -0,0 +1,48 @@ +import React, { HTMLAttributes, Ref, ReactNode } from 'react'; +import classNames from 'classnames'; +import { CommonProps, keysOf } from '../../common'; + +const gutterSizeToClassNameMap = { + none: null, + xs: 'euiBadgeGroup--gutterExtraSmall', + s: 'euiBadgeGroup--gutterSmall', +}; + +export const GUTTER_SIZES = keysOf(gutterSizeToClassNameMap); +type BadgeGroupGutterSize = keyof typeof gutterSizeToClassNameMap; + +export interface EuiBadgeGroupProps { + /** + * Space between badges + */ + gutterSize?: BadgeGroupGutterSize; + /** + * Should be a list of EuiBadge's but can also be any other element + * Will apply an extra class to add spacing + */ + children?: ReactNode; +} + +export const EuiBadgeGroup = React.forwardRef< + HTMLDivElement, + CommonProps & HTMLAttributes & EuiBadgeGroupProps +>( + ( + { children, className, gutterSize = 'xs', ...rest }, + ref: Ref + ) => { + const classes = classNames( + 'euiBadgeGroup', + gutterSizeToClassNameMap[gutterSize as BadgeGroupGutterSize], + className + ); + + return ( +
+ {React.Children.map(children, (child: ReactNode) => ( + {child} + ))} +
+ ); + } +); diff --git a/src/components/badge/badge_group/index.ts b/src/components/badge/badge_group/index.ts new file mode 100644 index 000000000000..347af71bc336 --- /dev/null +++ b/src/components/badge/badge_group/index.ts @@ -0,0 +1 @@ +export { EuiBadgeGroup, EuiBadgeGroupProps } from './badge_group'; diff --git a/src/components/badge/index.ts b/src/components/badge/index.ts index 6ab2cc152c30..125b867860dd 100644 --- a/src/components/badge/index.ts +++ b/src/components/badge/index.ts @@ -3,3 +3,5 @@ export { EuiBadge, EuiBadgeProps } from './badge'; export { EuiBetaBadge } from './beta_badge'; export { EuiNotificationBadge } from './notification_badge'; + +export { EuiBadgeGroup, EuiBadgeGroupProps } from './badge_group'; diff --git a/src/components/index.js b/src/components/index.js index 764036c894d5..78214b35586a 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -10,7 +10,12 @@ export { EuiSkipLink, } from './accessibility'; -export { EuiBadge, EuiBetaBadge, EuiNotificationBadge } from './badge'; +export { + EuiBadge, + EuiBetaBadge, + EuiNotificationBadge, + EuiBadgeGroup, +} from './badge'; export { EuiBottomBar } from './bottom_bar'; From 7cc113fcbcdeb9c51ccd783f4971e5ae77349823 Mon Sep 17 00:00:00 2001 From: Caroline Horn <549577+cchaos@users.noreply.github.com> Date: Thu, 5 Mar 2020 11:38:07 -0500 Subject: [PATCH 4/9] [EuiHeader] Added `sections` and `position` props (#2928) --- CHANGELOG.md | 1 + src-docs/src/views/header/header_example.js | 108 ++++++++++++++++- src-docs/src/views/header/header_position.js | 38 ++++++ src-docs/src/views/header/header_sections.js | 82 +++++++++++++ src-docs/src/views/header/props.tsx | 7 ++ .../header/__snapshots__/header.test.tsx.snap | 88 +++++++++++++- src/components/header/_header.scss | 13 ++ src/components/header/_header_logo.scss | 4 + src/components/header/header.test.tsx | 80 ++++++++++++ src/components/header/header.tsx | 114 +++++++++++++++++- .../header_section/_header_section_item.scss | 10 +- .../header_section/header_section_item.tsx | 14 +-- src/components/header/header_section/index.ts | 5 +- src/components/header/index.ts | 2 +- .../list_group_item.test.tsx.snap | 2 +- .../list_group/list_group_item.test.tsx | 2 +- 16 files changed, 543 insertions(+), 27 deletions(-) create mode 100644 src-docs/src/views/header/header_position.js create mode 100644 src-docs/src/views/header/header_sections.js create mode 100644 src-docs/src/views/header/props.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index a9aab2db999e..5a12bf1d4276 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - Added `showOnFocus` prop to `EuiScreenReaderOnly` to force display on keyboard focus ([#2976](https://github.com/elastic/eui/pull/2976)) - Added `EuiSkipLink` component ([#2976](https://github.com/elastic/eui/pull/2976)) - Created `EuiBadgeGroup` component ([#2921](https://github.com/elastic/eui/pull/2921)) +- Added `sections` and `position` props to `EuiHeader` ([#2928](https://github.com/elastic/eui/pull/2928)) **Bug Fixes** diff --git a/src-docs/src/views/header/header_example.js b/src-docs/src/views/header/header_example.js index 32e8ad7eca89..72b503e9988f 100644 --- a/src-docs/src/views/header/header_example.js +++ b/src-docs/src/views/header/header_example.js @@ -1,4 +1,5 @@ import React from 'react'; +import { Link } from 'react-router'; import { renderToHtml } from '../../services'; @@ -17,10 +18,20 @@ import { EuiHeaderLink, } from '../../../../src/components'; +import { EuiHeaderSectionsProp } from './props'; + import Header from './header'; const headerSource = require('!!raw-loader!./header'); const headerHtml = renderToHtml(Header); +import HeaderSections from './header_sections'; +const headerSectionsSource = require('!!raw-loader!./header_sections'); +const headerSectionsHtml = renderToHtml(HeaderSections); + +import HeaderPosition from './header_position'; +const headerPositionSource = require('!!raw-loader!./header_position'); +const headerPositionHtml = renderToHtml(HeaderPosition); + import HeaderAlert from './header_alert'; const headerAlertSource = require('!!raw-loader!./header_alert'); const headerAlertHtml = renderToHtml(HeaderAlert); @@ -45,10 +56,27 @@ const headerSnippet = ` `; +const headerSectionsSnippet = ``; + const headerLinksSnippet = ` @@ -78,7 +106,11 @@ export const HeaderExample = { code: headerHtml, }, ], - text:

The header is made up of several individual components.

, + text: ( +

+ The header is made up of many individual components. +

+ ), props: { EuiHeader, EuiHeaderBreadcrumbs, @@ -86,12 +118,80 @@ export const HeaderExample = { EuiHeaderSectionItem, EuiHeaderSectionItemButton, EuiHeaderLogo, + EuiHeaderSectionsProp, }, snippet: headerSnippet, demo:
, }, { - title: 'Links', + title: 'Sections', + source: [ + { + type: GuideSectionTypes.JS, + code: headerSectionsSource, + }, + { + type: GuideSectionTypes.HTML, + code: headerSectionsHtml, + }, + ], + text: ( + <> +

+ Alternatively, you can pass an array objects to the{' '} + sections props that takes a key of{' '} + items (array of children to wrap in an{' '} + EuiHeaderSectionItem) and/or{' '} + breadcrumbs (array of{' '} + breadcrumb objects). Each + item in the array will be wrapped in an{' '} + EuiHeaderSection. +

+

+ Note: Passing sections and{' '} + children will disregard the{' '} + children as it is not easily interpreted at what + location the children should be placed. +

+ + ), + props: { + EuiHeader, + EuiHeaderSectionsProp, + EuiHeaderSection, + EuiHeaderSectionItem, + }, + snippet: headerSectionsSnippet, + demo: , + }, + { + title: 'Fixed header', + source: [ + { + type: GuideSectionTypes.JS, + code: headerPositionSource, + }, + { + type: GuideSectionTypes.HTML, + code: headerPositionHtml, + }, + ], + text: ( + <> +

+ Most consumer need a header that does not scroll way with the page + contents. You can apply this display by changing{' '} + position to fixed. It will + also add the appropriate padding to the window body by applying a + class. +

+ + ), + snippet: '', + demo: , + }, + { + title: 'Header links', source: [ { type: GuideSectionTypes.JS, @@ -117,7 +217,7 @@ export const HeaderExample = { demo: , }, { - title: 'Display header alerts', + title: 'Alerts in the header', source: [ { type: GuideSectionTypes.JS, diff --git a/src-docs/src/views/header/header_position.js b/src-docs/src/views/header/header_position.js new file mode 100644 index 000000000000..9e9d1de9fe96 --- /dev/null +++ b/src-docs/src/views/header/header_position.js @@ -0,0 +1,38 @@ +import React, { useState } from 'react'; + +import { + EuiHeader, + EuiHeaderLogo, + EuiSwitch, +} from '../../../../src/components'; + +export default () => { + const [position, setPosition] = useState('static'); + + const sections = [ + { + items: [ + , + ], + borders: 'none', + }, + { + items: [ +
+ setPosition(e.target.checked ? 'fixed' : 'static')} + /> +
, + ], + borders: 'none', + }, + ]; + + return ; +}; diff --git a/src-docs/src/views/header/header_sections.js b/src-docs/src/views/header/header_sections.js new file mode 100644 index 000000000000..dbe68843a00b --- /dev/null +++ b/src-docs/src/views/header/header_sections.js @@ -0,0 +1,82 @@ +import React from 'react'; + +import { + EuiHeader, + EuiFieldSearch, + EuiHeaderLogo, +} from '../../../../src/components'; + +import HeaderAppMenu from './header_app_menu'; +import HeaderUserMenu from './header_user_menu'; +import HeaderSpacesMenu from './header_spaces_menu'; + +export default () => { + const renderLogo = ( + + ); + + const breadcrumbs = [ + { + text: 'Management', + href: '#', + onClick: e => { + e.preventDefault(); + console.log('You clicked management'); + }, + 'data-test-subj': 'breadcrumbsAnimals', + className: 'customClass', + }, + { + text: 'Truncation test is here for a really long item', + href: '#', + onClick: e => { + e.preventDefault(); + console.log('You clicked truncation test'); + }, + }, + { + text: 'hidden', + href: '#', + onClick: e => { + e.preventDefault(); + console.log('You clicked hidden'); + }, + }, + { + text: 'Users', + href: '#', + onClick: e => { + e.preventDefault(); + console.log('You clicked users'); + }, + }, + { + text: 'Create', + }, + ]; + + const renderSearch = ( + + ); + + const sections = [ + { + items: [renderLogo, ], + borders: 'right', + breadcrumbs: breadcrumbs, + }, + { + items: [renderSearch,
], + borders: 'none', + }, + { + items: [, ], + }, + ]; + + return ; +}; diff --git a/src-docs/src/views/header/props.tsx b/src-docs/src/views/header/props.tsx new file mode 100644 index 000000000000..4259e7d9842b --- /dev/null +++ b/src-docs/src/views/header/props.tsx @@ -0,0 +1,7 @@ +import React, { FunctionComponent } from 'react'; + +import { EuiHeaderSections } from '../../../../src/components/header'; + +export const EuiHeaderSectionsProp: FunctionComponent< + EuiHeaderSections +> = () =>
; diff --git a/src/components/header/__snapshots__/header.test.tsx.snap b/src/components/header/__snapshots__/header.test.tsx.snap index 2ac2a381dee3..3654b8e49a9a 100644 --- a/src/components/header/__snapshots__/header.test.tsx.snap +++ b/src/components/header/__snapshots__/header.test.tsx.snap @@ -3,17 +3,101 @@ exports[`EuiHeader is rendered 1`] = `
`; exports[`EuiHeader renders children 1`] = `
Hello!
`; + +exports[`EuiHeader renders in fixed position 1`] = ` +
+ + Hello! + +
+`; + +exports[`EuiHeader sections render breadcrumbs and props 1`] = ` +
+ +
+`; + +exports[`EuiHeader sections render simple items and borders 1`] = ` +
+
+
+ Item 1 +
+
+ Item 2 +
+
+
+
+ Item A +
+
+ Item B +
+
+
+`; + +exports[`EuiHeader throws a warning if both children and sections were passed 1`] = ` +
+
+
+ Item 1 +
+
+ Item 2 +
+
+
+`; diff --git a/src/components/header/_header.scss b/src/components/header/_header.scss index ca9c05b1145e..389fb60f9495 100644 --- a/src/components/header/_header.scss +++ b/src/components/header/_header.scss @@ -6,6 +6,19 @@ position: relative; z-index: $euiZHeader; // ensure the shadow shows above content display: flex; + justify-content: space-between; background: $euiHeaderBackgroundColor; border-bottom: $euiBorderThin; + + &--fixed { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: $euiZLevel7; + } +} + +.euiBody--headerIsFixed { + padding-top: $euiHeaderChildSize + $euiSizeS; // Extra padding to accound for the shadow } diff --git a/src/components/header/_header_logo.scss b/src/components/header/_header_logo.scss index 1ac01250f810..b8687ef6639d 100644 --- a/src/components/header/_header_logo.scss +++ b/src/components/header/_header_logo.scss @@ -12,6 +12,10 @@ vertical-align: middle; white-space: nowrap; + &:hover { + background: $euiColorLightestShade; + } + &:focus, &:hover { text-decoration: none; diff --git a/src/components/header/header.test.tsx b/src/components/header/header.test.tsx index 5a36ac03da4c..8be9d11b7068 100644 --- a/src/components/header/header.test.tsx +++ b/src/components/header/header.test.tsx @@ -20,4 +20,84 @@ describe('EuiHeader', () => { expect(component).toMatchSnapshot(); }); + + test('renders in fixed position', () => { + const component = render( + + Hello! + + ); + + expect(component).toMatchSnapshot(); + }); + + describe('sections', () => { + test('render simple items and borders', () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + + test('render breadcrumbs and props', () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + }); + + describe('throws a warning', () => { + const oldConsoleError = console.warn; + let consoleStub: jest.Mock; + + beforeEach(() => { + // We don't use jest.spyOn() here, because EUI's tests apply a global + // console.error() override that throws an exception. For these + // tests, we just want to know if console.error() was called. + console.warn = consoleStub = jest.fn(); + }); + + afterEach(() => { + console.warn = oldConsoleError; + }); + + test('if both children and sections were passed', () => { + const component = render( + + Child + + ); + + expect(consoleStub).toBeCalled(); + expect(consoleStub.mock.calls[0][0]).toMatch( + 'cannot accept both `children` and `sections`' + ); + expect(component).toMatchSnapshot(); + }); + }); }); diff --git a/src/components/header/header.tsx b/src/components/header/header.tsx index 1c59a263e1d4..2da6d3eeb3c4 100644 --- a/src/components/header/header.tsx +++ b/src/components/header/header.tsx @@ -1,20 +1,124 @@ -import React, { FunctionComponent, HTMLAttributes } from 'react'; +import React, { FunctionComponent, HTMLAttributes, useEffect } from 'react'; import classNames from 'classnames'; - import { CommonProps } from '../common'; -export type EuiHeaderProps = CommonProps & HTMLAttributes; +import { + EuiHeaderSectionItem, + EuiHeaderSectionItemProps, + EuiHeaderSection, +} from './header_section'; +import { EuiHeaderBreadcrumbs } from './header_breadcrumbs'; +import { Breadcrumb, EuiBreadcrumbsProps } from '../breadcrumbs'; + +type EuiHeaderSectionItemType = EuiHeaderSectionItemProps['children']; +type EuiHeaderSectionBorderType = EuiHeaderSectionItemProps['border']; + +export interface EuiHeaderSections { + /** + * An arry of items that will be wrapped in a #EuiHeaderSectionItem + */ + items?: EuiHeaderSectionItemType[]; + /** + * Apply the passed border side to each #EuiHeaderSectionItem + */ + borders?: EuiHeaderSectionBorderType; + /** + * Breadcrumbs in the header cannot be wrapped in a #EuiHeaderSection in order for truncation to work. + * Simply pass the array of Breadcrumb objects + */ + breadcrumbs?: Breadcrumb[]; + /** + * Other props to pass to #EuiHeaderBreadcrumbs + */ + breadcrumbProps?: Omit; +} + +function createHeaderSection( + sections: EuiHeaderSectionItemType[], + border?: EuiHeaderSectionBorderType +) { + return sections.map((section, index) => { + return ( + + {section} + + ); + }); +} + +export type EuiHeaderProps = CommonProps & + HTMLAttributes & { + /** + * An array of objects to wrap in a #EuiHeaderSection. + * Each section is spaced using `space-between`. + * See #EuiHeaderSectionsProp for object details. + * This prop disregards the prop `children` if both are passed. + */ + sections?: EuiHeaderSections[]; + /** + * Helper that positions the header against the window body and + * adds the correct amount of top padding to the window when in `fixed` mode + */ + position?: 'static' | 'fixed'; + }; export const EuiHeader: FunctionComponent = ({ children, className, + sections, + position = 'static', ...rest }) => { - const classes = classNames('euiHeader', className); + const classes = classNames('euiHeader', `euiHeader--${position}`, className); + + useEffect(() => { + if (position === 'fixed') { + document.body.classList.add('euiBody--headerIsFixed'); + } + return () => { + document.body.classList.remove('euiBody--headerIsFixed'); + }; + }, [position]); + + let contents; + if (sections) { + if (children) { + // In case both children and sections are passed, warn in the console that the children will be disregarded + console.warn( + 'EuiHeader cannot accept both `children` and `sections`. It will disregard the `children`.' + ); + } + + contents = sections.map((section, index) => { + const content = []; + if (section.items) { + // Items get wrapped in EuiHeaderSection and each item in a EuiHeaderSectionItem + content.push( + + {createHeaderSection(section.items, section.borders)} + + ); + } + if (section.breadcrumbs) { + content.push( + // Breadcrumbs are separate and cannot be contained in a EuiHeaderSection + // in order for truncation to work + + ); + } + return content; + }); + } else { + contents = children; + } return (
- {children} + {contents}
); }; diff --git a/src/components/header/header_section/_header_section_item.scss b/src/components/header/header_section/_header_section_item.scss index 098b6a98667b..ac58b1774c14 100644 --- a/src/components/header/header_section/_header_section_item.scss +++ b/src/components/header/header_section/_header_section_item.scss @@ -2,10 +2,8 @@ .euiHeaderSectionItem { position: relative; - - &:hover { - background: $euiColorLightestShade; - } + display: flex; + align-items: center; &:after { position: absolute; @@ -23,6 +21,10 @@ text-align: center; font-size: 0; // aligns icons better vertically + &:hover { + background: $euiColorLightestShade; + } + &:focus { background: $euiFocusBackgroundColor; } diff --git a/src/components/header/header_section/header_section_item.tsx b/src/components/header/header_section/header_section_item.tsx index 4bcdff3937da..2989c03c3212 100644 --- a/src/components/header/header_section/header_section_item.tsx +++ b/src/components/header/header_section/header_section_item.tsx @@ -1,4 +1,4 @@ -import React, { FunctionComponent } from 'react'; +import React, { FunctionComponent, ReactNode } from 'react'; import classNames from 'classnames'; import { CommonProps } from '../../common'; @@ -11,16 +11,14 @@ const borderToClassNameMap: { [border in Border]: string | undefined } = { none: undefined, }; -type Props = CommonProps & { +export type EuiHeaderSectionItemProps = CommonProps & { border?: Border; + children?: ReactNode; }; -export const EuiHeaderSectionItem: FunctionComponent = ({ - border = 'left', - children, - className, - ...rest -}) => { +export const EuiHeaderSectionItem: FunctionComponent< + EuiHeaderSectionItemProps +> = ({ border = 'left', children, className, ...rest }) => { const classes = classNames( 'euiHeaderSectionItem', borderToClassNameMap[border], diff --git a/src/components/header/header_section/index.ts b/src/components/header/header_section/index.ts index 472a50057a32..2d032f7b0886 100644 --- a/src/components/header/header_section/index.ts +++ b/src/components/header/header_section/index.ts @@ -1,5 +1,8 @@ export { EuiHeaderSection } from './header_section'; -export { EuiHeaderSectionItem } from './header_section_item'; +export { + EuiHeaderSectionItem, + EuiHeaderSectionItemProps, +} from './header_section_item'; export { EuiHeaderSectionItemButton } from './header_section_item_button'; diff --git a/src/components/header/index.ts b/src/components/header/index.ts index 731612722e68..029216ab1b49 100644 --- a/src/components/header/index.ts +++ b/src/components/header/index.ts @@ -1,4 +1,4 @@ -export { EuiHeader, EuiHeaderProps } from './header'; +export { EuiHeader, EuiHeaderProps, EuiHeaderSections } from './header'; export { EuiHeaderAlert, EuiHeaderAlertProps } from './header_alert'; diff --git a/src/components/list_group/__snapshots__/list_group_item.test.tsx.snap b/src/components/list_group/__snapshots__/list_group_item.test.tsx.snap index cc4c07759322..623e6ad1ce6a 100644 --- a/src/components/list_group/__snapshots__/list_group_item.test.tsx.snap +++ b/src/components/list_group/__snapshots__/list_group_item.test.tsx.snap @@ -294,7 +294,7 @@ exports[`EuiListGroupItem renders a disabled button even if provided an href 2`] `; -exports[`EuiListGroupItem throws an warning if both iconType and icon are provided but still renders 1`] = ` +exports[`EuiListGroupItem throws a warning if both iconType and icon are provided but still renders 1`] = `
  • diff --git a/src/components/list_group/list_group_item.test.tsx b/src/components/list_group/list_group_item.test.tsx index ef4e9264c32b..6582f7303b2c 100644 --- a/src/components/list_group/list_group_item.test.tsx +++ b/src/components/list_group/list_group_item.test.tsx @@ -140,7 +140,7 @@ describe('EuiListGroupItem', () => { expect(component).toMatchSnapshot(); }); - describe('throws an warning', () => { + describe('throws a warning', () => { const oldConsoleError = console.warn; let consoleStub: jest.Mock; From 1343725e16fcdb02314a9dc22c124e756afef112 Mon Sep 17 00:00:00 2001 From: Caroline Horn <549577+cchaos@users.noreply.github.com> Date: Thu, 5 Mar 2020 12:02:31 -0500 Subject: [PATCH 5/9] [EuiListGroup] and [Items] Adds `gutterSize`, `color` (#2980) --- CHANGELOG.md | 2 + .../views/list_group/list_group_example.js | 117 ++++++++++-- .../list_group/list_group_item_color.tsx | 23 +++ .../list_group/list_group_link_actions.js | 171 +++++++----------- .../src/views/list_group/list_group_links.js | 73 +------- .../__snapshots__/list_group.test.tsx.snap | 143 ++++++++++++++- .../list_group_item.test.tsx.snap | 68 +++++++ src/components/list_group/_index.scss | 3 +- src/components/list_group/_list_group.scss | 15 +- .../list_group/_list_group_item.scss | 31 ++-- src/components/list_group/_variables.scss | 17 ++ src/components/list_group/list_group.test.tsx | 89 ++++++++- src/components/list_group/list_group.tsx | 19 +- .../list_group/list_group_item.test.tsx | 14 +- src/components/list_group/list_group_item.tsx | 20 +- .../__snapshots__/nav_drawer.test.js.snap | 16 +- 16 files changed, 595 insertions(+), 226 deletions(-) create mode 100644 src-docs/src/views/list_group/list_group_item_color.tsx create mode 100644 src/components/list_group/_variables.scss diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a12bf1d4276..e4b18e243707 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ - Added `EuiSkipLink` component ([#2976](https://github.com/elastic/eui/pull/2976)) - Created `EuiBadgeGroup` component ([#2921](https://github.com/elastic/eui/pull/2921)) - Added `sections` and `position` props to `EuiHeader` ([#2928](https://github.com/elastic/eui/pull/2928)) +- Added `gutterSize` prop to `EuiListGroup` ([#2980](https://github.com/elastic/eui/pull/2980)) +- Added `color` prop to `EuiListGroupItem` and updated size style ([#2980](https://github.com/elastic/eui/pull/2980)) **Bug Fixes** diff --git a/src-docs/src/views/list_group/list_group_example.js b/src-docs/src/views/list_group/list_group_example.js index 99758b562e66..7e704b16cfc6 100644 --- a/src-docs/src/views/list_group/list_group_example.js +++ b/src-docs/src/views/list_group/list_group_example.js @@ -26,8 +26,12 @@ import ListGroupExtra from './list_group_extra'; const listGroupExtraSource = require('!!raw-loader!./list_group_extra'); const listGroupExtraHtml = renderToHtml(ListGroupExtra); +import ListGroupItemColor from './list_group_item_color'; +const listGroupItemColorSource = require('!!raw-loader!./list_group_item_color'); +const listGroupItemColorHtml = renderToHtml(ListGroupItemColor); + export const ListGroupExample = { - title: 'List Group', + title: 'List group', sections: [ { source: [ @@ -41,15 +45,24 @@ export const ListGroupExample = { }, ], text: ( -

    - The ListGroup component is used to present   - ListGroupItems in a neatly formatted list. Use the -  flush and bordered{' '} - properties for full-width and bordered presentations, respectively. -

    + <> +

    + The EuiListGroup component is used to present{' '} + EuiListGroupItems in a neatly formatted list. Use + the flush and bordered{' '} + properties for full-width and bordered presentations, respectively. +

    +

    + Adjust the gutterSize prop to increase or + decrease the spacing between items. +

    + ), props: { EuiListGroup, EuiListGroupItem }, demo: , + snippet: ` + +`, }, { title: 'List of links', @@ -64,16 +77,36 @@ export const ListGroupExample = { }, ], text: ( -

    - Present ListGroupItems as links by providing an -  href value and change their appearance with - the size, isActive, and - isDisabled properties. As done in this example, the -  ListGroup component can also accept an array - of items via the listItems property. -

    + <> +

    + Display EuiListGroupItems as links by providing an{' '} + href value and change their state with the{' '} + isActive and isDisabled{' '} + properties. +

    +

    + As is done in this example, the EuiListGroup{' '} + component can also accept an array of items via the{' '} + listItems property. +

    + ), demo: , + snippet: ``, }, { title: 'Secondary link actions', @@ -91,13 +124,24 @@ export const ListGroupExample = {

    The extraAction property adds a secondary icon button to any list item. It accepts several properties of its own, - including color, onClick,   + including color, onClick,{' '} iconType, and alwaysShow, and can be used for actions such as pinning, favoriting, or deleting an item.

    ), demo: , + snippet: ``, }, { title: 'Text wrapping and tooltips', @@ -120,6 +164,47 @@ export const ListGroupExample = {

    ), demo: , + snippet: ` + +`, + }, + { + title: 'List item color and size', + source: [ + { + type: GuideSectionTypes.JS, + code: listGroupItemColorSource, + }, + { + type: GuideSectionTypes.HTML, + code: listGroupItemColorHtml, + }, + ], + text: ( + <> +

    + EuiListGroupItems will inherit the color from their + element type whether it is a button,{' '} + anchor, or span. You can + enforce a different color of primary,{' '} + text, or subdued with the{' '} + color prop. +

    +

    + They also accept options for text size;{' '} + xs | s | m | l. +

    + + ), + demo: , + snippet: ``, }, ], }; diff --git a/src-docs/src/views/list_group/list_group_item_color.tsx b/src-docs/src/views/list_group/list_group_item_color.tsx new file mode 100644 index 000000000000..883bd5243389 --- /dev/null +++ b/src-docs/src/views/list_group/list_group_item_color.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +import { + EuiListGroupItem, + EuiListGroup, +} from '../../../../src/components/list_group'; + +export default () => ( + + + + {}} + label="Primary (s)" + color="primary" + size="s" + /> + + + + + +); diff --git a/src-docs/src/views/list_group/list_group_link_actions.js b/src-docs/src/views/list_group/list_group_link_actions.js index 129681e17dbd..e3e31b1df89f 100644 --- a/src-docs/src/views/list_group/list_group_link_actions.js +++ b/src-docs/src/views/list_group/list_group_link_actions.js @@ -1,24 +1,14 @@ -import React, { Component, Fragment } from 'react'; +import React, { Component } from 'react'; -import { - EuiListGroup, - EuiListGroupItem, - EuiSpacer, - EuiSwitch, - EuiCode, - EuiFlexGroup, - EuiFlexItem, -} from '../../../../src/components'; +import { EuiListGroup, EuiListGroupItem } from '../../../../src/components'; export default class extends Component { constructor(props) { super(props); this.state = { - flushWidth: false, - showBorder: false, favorite1: undefined, - favorite2: undefined, + favorite2: 'link2', favorite3: undefined, }; } @@ -65,106 +55,71 @@ export default class extends Component { }; render() { - const { - flushWidth, - showBorder, - favorite1, - favorite2, - favorite3, - } = this.state; + const { favorite1, favorite2, favorite3 } = this.state; return ( - - - - - Show as flush - - } - checked={this.state.flushWidth} - onChange={this.toggleFlushWidth} - /> - - - - Show as bordered - - } - checked={this.state.showBorder} - onChange={this.toggleBorder} - /> - - + + window.alert('Button clicked')} + isActive + extraAction={{ + color: 'subdued', + onClick: this.link1Clicked, + iconType: favorite1 === 'link1' ? 'pinFilled' : 'pin', + iconSize: 's', + 'aria-label': 'Favorite link1', + alwaysShow: favorite1 === 'link1', + }} + /> - + window.alert('Button clicked')} + label="EUI button link" + extraAction={{ + color: 'subdued', + onClick: this.link2Clicked, + iconType: favorite2 === 'link2' ? 'pinFilled' : 'pin', + iconSize: 's', + 'aria-label': 'Favorite link2', + alwaysShow: favorite2 === 'link2', + }} + /> - - window.alert('Button clicked')} - isActive - extraAction={{ - color: 'subdued', - onClick: this.link1Clicked, - iconType: favorite1 === 'link1' ? 'pinFilled' : 'pin', - iconSize: 's', - 'aria-label': 'Favorite link1', - alwaysShow: favorite1 === 'link1', - }} - /> + window.alert('Button clicked')} + iconType="broom" + label="EUI button link" + extraAction={{ + color: 'subdued', + onClick: this.link3Clicked, + iconType: favorite3 === 'link3' ? 'pinFilled' : 'pin', + iconSize: 's', + 'aria-label': 'Favorite link3', + alwaysShow: favorite3 === 'link3', + isDisabled: true, + }} + /> - window.alert('Button clicked')} - label="EUI button link" - extraAction={{ - color: 'subdued', - onClick: this.link2Clicked, - iconType: favorite2 === 'link2' ? 'pinFilled' : 'pin', - iconSize: 's', - 'aria-label': 'Favorite link2', - alwaysShow: favorite2 === 'link2', - }} - /> - - window.alert('Button clicked')} - iconType="broom" - label="EUI button link" - extraAction={{ - color: 'subdued', - onClick: this.link3Clicked, - iconType: favorite3 === 'link3' ? 'pinFilled' : 'pin', - iconSize: 's', - 'aria-label': 'Favorite link3', - alwaysShow: favorite3 === 'link3', - isDisabled: true, - }} - /> - - window.alert('Action clicked'), - iconType: 'pin', - iconSize: 's', - 'aria-label': 'Favorite link4', - }} - /> - - + window.alert('Action clicked'), + iconType: 'pin', + iconSize: 's', + 'aria-label': 'Favorite link4', + }} + /> +
    ); } } diff --git a/src-docs/src/views/list_group/list_group_links.js b/src-docs/src/views/list_group/list_group_links.js index c2660d171371..c0c5e7198acc 100644 --- a/src-docs/src/views/list_group/list_group_links.js +++ b/src-docs/src/views/list_group/list_group_links.js @@ -1,13 +1,6 @@ -import React, { Component, Fragment } from 'react'; +import React from 'react'; -import { - EuiListGroup, - EuiSpacer, - EuiSwitch, - EuiCode, - EuiFlexGroup, - EuiFlexItem, -} from '../../../../src/components'; +import { EuiListGroup } from '../../../../src/components'; const myContent = [ { @@ -44,62 +37,6 @@ const myContent = [ }, ]; -export default class extends Component { - constructor(props) { - super(props); - - this.state = { - flushWidth: false, - showBorder: false, - }; - } - - toggleFlushWidth = () => { - this.setState(prevState => ({ flushWidth: !prevState.flushWidth })); - }; - - toggleBorder = () => { - this.setState(prevState => ({ showBorder: !prevState.showBorder })); - }; - - render() { - const { flushWidth, showBorder } = this.state; - - return ( - - - - - Show as flush - - } - checked={this.state.flushWidth} - onChange={this.toggleFlushWidth} - /> - - - - Show as bordered - - } - checked={this.state.showBorder} - onChange={this.toggleBorder} - /> - - - - - - - - ); - } -} +export default () => { + return ; +}; diff --git a/src/components/list_group/__snapshots__/list_group.test.tsx.snap b/src/components/list_group/__snapshots__/list_group.test.tsx.snap index fbd22fd489eb..72252c294432 100644 --- a/src/components/list_group/__snapshots__/list_group.test.tsx.snap +++ b/src/components/list_group/__snapshots__/list_group.test.tsx.snap @@ -3,7 +3,148 @@ exports[`EuiListGroup is rendered 1`] = `