- 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.
- 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
+
+
);
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
+
+
+
+ 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 (
+
+ 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.
+
+ 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.
+
- 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.
+
- 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.
+
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.
+ 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.
+