diff --git a/CHANGELOG.md b/CHANGELOG.md index cb5ad3d36ff..925d6b618fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ - Added default prompt text to `aria-describedby` for `EuiFilePicker` ([#2919](https://github.com/elastic/eui/pull/2919)) - Added SASS variables for text variants of the primary palette `$euiColorPrimaryText`, `$euiColorSecondaryText`, etc... Updated components to use these new variables. ([#2873](https://github.com/elastic/eui/pull/2873)) - Updated SASS mixin `makeHighContrastColor()` to default `$background: $euiPageBackgroundColor` and `$ratio: 4.5`. Created `makeGraphicContrastColor()` for graphic specific contrast levels of 3.0. ([#2873](https://github.com/elastic/eui/pull/2873)) +- Added `arrowDisplay` prop to `EuiAccordion` for changing side or hiding completely ([#2914](https://github.com/elastic/eui/pull/2914)) +- Added `prepend` and `append` ability to `EuiFieldSearch` ([#2914](https://github.com/elastic/eui/pull/2914)) +- Added `notification` and `notificationColor` props to `EuiHeaderSectionItemButton` ([#2914](https://github.com/elastic/eui/pull/2914)) - Added `folderCheck`, `folderExclamation`, `push`, `quote`, `reporter` and `users` icons ([#2935](https://github.com/elastic/eui/pull/2935)) - Updated `folderClosed` and `folderOpen` to match new additions and sit better on the pixel grid ([#2935](https://github.com/elastic/eui/pull/2935)) @@ -12,6 +15,7 @@ - Fixed `EuiTitle` not rendering child classes ([#2926](https://github.com/elastic/eui/pull/2926)) - Added TypeScript definition for `EuiCodeEditor`'s accepting `react-ace` props ([#2926](https://github.com/elastic/eui/pull/2926)) +- Extended `div` element in `EuiFlyout` type ([#2914](https://github.com/elastic/eui/pull/2914)) **Theme: Amsterdam** diff --git a/src-docs/src/views/accordion/accordion_arrow.js b/src-docs/src/views/accordion/accordion_arrow.js new file mode 100644 index 00000000000..0b14cba00cd --- /dev/null +++ b/src-docs/src/views/accordion/accordion_arrow.js @@ -0,0 +1,50 @@ +import React from 'react'; + +import { + EuiAccordion, + EuiText, + EuiCode, + EuiSpacer, +} from '../../../../src/components'; + +export default () => ( +
+ + +

+ Any content inside of EuiAccordion will appear + here. +

+
+
+ + + +

+ Any content inside of EuiAccordion will appear + here. +

+
+
+ + + +

+ Any content inside of EuiAccordion will appear + here. +

+
+
+
+); diff --git a/src-docs/src/views/accordion/accordion_example.js b/src-docs/src/views/accordion/accordion_example.js index a4252a360b8..8a75230bf06 100644 --- a/src-docs/src/views/accordion/accordion_example.js +++ b/src-docs/src/views/accordion/accordion_example.js @@ -22,6 +22,18 @@ const accordionSnippet = ` `; +import AccordionArrow from './accordion_arrow'; +const accordionArrowSource = require('!!raw-loader!./accordion_arrow'); +const accordionArrowHtml = renderToHtml(AccordionArrow); +const accordionArrowSnippet = ` + + +`; + import AccordionMultiple from './accordion_multiple'; const accordionMultipleSource = require('!!raw-loader!./accordion_multiple'); const accordionMultipleHtml = renderToHtml(AccordionMultiple); @@ -142,6 +154,32 @@ export const AccordionExample = { snippet: accordionSnippet, demo: , }, + { + title: 'Arrow display', + source: [ + { + type: GuideSectionTypes.JS, + code: accordionArrowSource, + }, + { + type: GuideSectionTypes.HTML, + code: accordionArrowHtml, + }, + ], + text: ( +
+

+ The arrow helps indicate the current state of the accordion (open or + not) and points to the main triggering button text. If you must hide + or change the side in which the arrow appears, use{' '} + arrowDisplay: 'right' or{' '} + 'none' +

+
+ ), + snippet: accordionArrowSnippet, + demo: , + }, { title: 'Multiple accordions', source: [ diff --git a/src-docs/src/views/form_controls/display_toggles.js b/src-docs/src/views/form_controls/display_toggles.js index c126cd96f56..d0f2e2c7a51 100644 --- a/src-docs/src/views/form_controls/display_toggles.js +++ b/src-docs/src/views/form_controls/display_toggles.js @@ -84,7 +84,11 @@ export class DisplayToggles extends Component { }>
- + {(canDisabled || canIsDisabled) && ( @@ -74,7 +75,7 @@ export default class extends Component { - + diff --git a/src-docs/src/views/header/header_updates.js b/src-docs/src/views/header/header_updates.js index 526a03c3a62..14dfc87bc03 100755 --- a/src-docs/src/views/header/header_updates.js +++ b/src-docs/src/views/header/header_updates.js @@ -8,7 +8,6 @@ import { EuiFlyoutBody, EuiFlyoutHeader, EuiTitle, - EuiNotificationBadge, EuiLink, EuiFlyoutFooter, EuiFlexGroup, @@ -121,15 +120,12 @@ export default class extends Component { aria-controls="headerNewsFeed" aria-expanded={this.state.isFlyoutVisible} aria-haspopup="true" - aria-label="News feed" - onClick={this.showFlyout}> + aria-label={`News feed: ${ + this.state.showBadge ? 'Updates available' : 'No updates' + }`} + onClick={this.showFlyout} + notification={this.state.showBadge && '•'}> - - {this.state.showBadge ? ( - - ▪ - - ) : null} ); diff --git a/src/components/accordion/__snapshots__/accordion.test.tsx.snap b/src/components/accordion/__snapshots__/accordion.test.tsx.snap index 492bcd03be8..4f49249582f 100644 --- a/src/components/accordion/__snapshots__/accordion.test.tsx.snap +++ b/src/components/accordion/__snapshots__/accordion.test.tsx.snap @@ -2,7 +2,8 @@ exports[`EuiAccordion behavior closes when clicked twice 1`] = ` @@ -13,7 +14,7 @@ exports[`EuiAccordion behavior closes when clicked twice 1`] = ` className="euiAccordion__triggerWrapper" > +
+
+
+
+

+ You can see me. +

+
+
+
+ +`; + +exports[`EuiAccordion props arrowDisplay right is rendered 1`] = ` +
+
+ +
+
+
+
+

+ You can see me. +

+
+
+
+
+`; + exports[`EuiAccordion props buttonContent is rendered 1`] = `
{ expect(component).toMatchSnapshot(); }); }); + + describe('arrowDisplay', () => { + it('right is rendered', () => { + const component = render( + +

You can see me.

+
+ ); + + expect(component).toMatchSnapshot(); + }); + + it('none is rendered', () => { + const component = render( + +

You can see me.

+
+ ); + + expect(component).toMatchSnapshot(); + }); + }); }); describe('behavior', () => { diff --git a/src/components/accordion/accordion.tsx b/src/components/accordion/accordion.tsx index 6d6df43a694..54a052ad921 100644 --- a/src/components/accordion/accordion.tsx +++ b/src/components/accordion/accordion.tsx @@ -52,6 +52,11 @@ export type EuiAccordionProps = HTMLAttributes & * The padding around the exposed accordion content. */ paddingSize?: EuiAccordionSize; + /** + * Placement of the arrow indicator, or 'none' to hide it. + * Placing on the `right` doesn't work with `extraAction` and so it will be ignored + */ + arrowDisplay?: 'left' | 'right' | 'none'; }; export class EuiAccordion extends Component< @@ -61,6 +66,7 @@ export class EuiAccordion extends Component< static defaultProps = { initialIsOpen: false, paddingSize: 'none', + arrowDisplay: 'left', }; childContent: HTMLDivElement | null = null; @@ -128,6 +134,7 @@ export class EuiAccordion extends Component< extraAction, paddingSize, initialIsOpen, + arrowDisplay, ...rest } = this.props; @@ -143,13 +150,26 @@ export class EuiAccordion extends Component< ? classNames(paddingSizeToClassNameMap[paddingSize]) : undefined; - const buttonClasses = classNames('euiAccordion__button', buttonClassName); + const buttonClasses = classNames( + 'euiAccordion__button', + { + euiAccordion__buttonReverse: !extraAction && arrowDisplay === 'right', + }, + buttonClassName + ); const iconClasses = classNames('euiAccordion__icon', { 'euiAccordion__icon-isOpen': this.state.isOpen, }); - const icon = ; + let icon; + if (arrowDisplay !== 'none') { + icon = ( + + + + ); + } let optionalAction = null; @@ -168,7 +188,7 @@ export class EuiAccordion extends Component< onClick={this.onToggle} className={buttonClasses} type="button"> - {icon} + {icon} {buttonContent} diff --git a/src/components/color_picker/color_picker.tsx b/src/components/color_picker/color_picker.tsx index 901ec8588fe..825816d9171 100644 --- a/src/components/color_picker/color_picker.tsx +++ b/src/components/color_picker/color_picker.tsx @@ -77,11 +77,13 @@ export interface EuiColorPickerProps /** * Creates an input group with element(s) coming before input. It only shows when the `display` is set to `default`. + * `string` | `ReactElement` or an array of these */ prepend?: EuiFormControlLayoutProps['prepend']; /** * Creates an input group with element(s) coming after input. It only shows when the `display` is set to `default`. + * `string` | `ReactElement` or an array of these */ append?: EuiFormControlLayoutProps['append']; } diff --git a/src/components/flyout/flyout.test.tsx b/src/components/flyout/flyout.test.tsx index 76c9d84436f..eb32c45275f 100644 --- a/src/components/flyout/flyout.test.tsx +++ b/src/components/flyout/flyout.test.tsx @@ -46,6 +46,12 @@ describe('EuiFlyout', () => { expect(label).toBe('Closes specific flyout'); }); }); + + test('accepts div props', () => { + const component = render( {}} id="imaflyout" />); + + expect(component).toMatchSnapshot(); + }); }); describe('size', () => { diff --git a/src/components/flyout/flyout.tsx b/src/components/flyout/flyout.tsx index ea1d90d73b3..2efa0afc718 100644 --- a/src/components/flyout/flyout.tsx +++ b/src/components/flyout/flyout.tsx @@ -1,4 +1,9 @@ -import React, { Component, CSSProperties, Fragment } from 'react'; +import React, { + Component, + CSSProperties, + Fragment, + HTMLAttributes, +} from 'react'; import classnames from 'classnames'; import { keyCodes, EuiWindowEvent } from '../../services'; @@ -16,7 +21,9 @@ const sizeToClassNameMap: { [size in EuiFlyoutSize]: string } = { l: 'euiFlyout--large', }; -export interface EuiFlyoutProps extends CommonProps { +export interface EuiFlyoutProps + extends CommonProps, + HTMLAttributes { onClose: () => void; size?: EuiFlyoutSize; /** diff --git a/src/components/form/field_number/field_number.tsx b/src/components/form/field_number/field_number.tsx index a9ee1e21895..a5ceefc4280 100644 --- a/src/components/form/field_number/field_number.tsx +++ b/src/components/form/field_number/field_number.tsx @@ -25,12 +25,14 @@ export type EuiFieldNumberProps = InputHTMLAttributes & inputRef?: Ref; /** - * Creates an input group with element(s) coming before input + * Creates an input group with element(s) coming before input. + * `string` | `ReactElement` or an array of these */ prepend?: EuiFormControlLayoutProps['prepend']; /** - * Creates an input group with element(s) coming after input + * Creates an input group with element(s) coming after input. + * `string` | `ReactElement` or an array of these */ append?: EuiFormControlLayoutProps['append']; diff --git a/src/components/form/field_search/__snapshots__/field_search.test.tsx.snap b/src/components/form/field_search/__snapshots__/field_search.test.tsx.snap index 6d3eb2848ea..60591378e8d 100644 --- a/src/components/form/field_search/__snapshots__/field_search.test.tsx.snap +++ b/src/components/form/field_search/__snapshots__/field_search.test.tsx.snap @@ -23,6 +23,23 @@ exports[`EuiFieldSearch is rendered 1`] = ` `; +exports[`EuiFieldSearch props append is rendered 1`] = ` + + + + + +`; + exports[`EuiFieldSearch props fullWidth is rendered 1`] = ` `; + +exports[`EuiFieldSearch props prepend is rendered 1`] = ` + + + + + +`; diff --git a/src/components/form/field_search/field_search.test.tsx b/src/components/form/field_search/field_search.test.tsx index c44ec4a02aa..68715328fe8 100644 --- a/src/components/form/field_search/field_search.test.tsx +++ b/src/components/form/field_search/field_search.test.tsx @@ -45,5 +45,17 @@ describe('EuiFieldSearch', () => { expect(component).toMatchSnapshot(); }); + + test('prepend is rendered', () => { + const component = render(); + + expect(component).toMatchSnapshot(); + }); + + test('append is rendered', () => { + const component = render(); + + expect(component).toMatchSnapshot(); + }); }); }); diff --git a/src/components/form/field_search/field_search.tsx b/src/components/form/field_search/field_search.tsx index b88728504b1..2047cf46aa1 100644 --- a/src/components/form/field_search/field_search.tsx +++ b/src/components/form/field_search/field_search.tsx @@ -4,7 +4,10 @@ import { Browser } from '../../../services/browser'; import { ENTER } from '../../../services/key_codes'; import { CommonProps } from '../../common'; -import { EuiFormControlLayout } from '../form_control_layout'; +import { + EuiFormControlLayout, + EuiFormControlLayoutProps, +} from '../form_control_layout'; import { EuiValidatableControl } from '../validatable_control'; @@ -37,6 +40,17 @@ export interface EuiFieldSearchProps * Shows a button that quickly clears any input */ isClearable?: boolean; + /** + * Creates an input group with element(s) coming before input + * `string` | `ReactElement` or an array of these + */ + prepend?: EuiFormControlLayoutProps['prepend']; + + /** + * Creates an input group with element(s) coming after input. + * `string` | `ReactElement` or an array of these + */ + append?: EuiFormControlLayoutProps['append']; } export class EuiFieldSearch extends Component { @@ -154,6 +168,8 @@ export class EuiFieldSearch extends Component { compressed, onSearch, isClearable, + append, + prepend, ...rest } = this.props; @@ -163,6 +179,7 @@ export class EuiFieldSearch extends Component { 'euiFieldSearch--fullWidth': fullWidth, 'euiFieldSearch--compressed': compressed, 'euiFieldSearch-isLoading': isLoading, + 'euiFieldText--inGroup': prepend || append, }, className ); @@ -177,7 +194,9 @@ export class EuiFieldSearch extends Component { ? { onClick: this.onClear } : undefined } - compressed={compressed}> + compressed={compressed} + append={append} + prepend={prepend}> & inputRef?: Ref; /** - * Creates an input group with element(s) coming before input + * Creates an input group with element(s) coming before input. + * `string` | `ReactElement` or an array of these */ prepend?: EuiFormControlLayoutProps['prepend']; /** - * Creates an input group with element(s) coming after input + * Creates an input group with element(s) coming after input. + * `string` | `ReactElement` or an array of these */ append?: EuiFormControlLayoutProps['append']; diff --git a/src/components/form/form_control_layout/form_control_layout.tsx b/src/components/form/form_control_layout/form_control_layout.tsx index 8a01f0c6073..b53629c5840 100644 --- a/src/components/form/form_control_layout/form_control_layout.tsx +++ b/src/components/form/form_control_layout/form_control_layout.tsx @@ -22,11 +22,13 @@ type PrependAppendType = StringOrReactElement | StringOrReactElement[]; export type EuiFormControlLayoutProps = CommonProps & HTMLAttributes & { /** - * Creates an input group with element(s) coming before children + * Creates an input group with element(s) coming before children. + * `string` | `ReactElement` or an array of these */ prepend?: PrependAppendType; /** - * Creates an input group with element(s) coming after children + * Creates an input group with element(s) coming after children. + * `string` | `ReactElement` or an array of these */ append?: PrependAppendType; children?: ReactNode; diff --git a/src/components/form/range/dual_range.tsx b/src/components/form/range/dual_range.tsx index 7edba3ac3b1..7296e7dbffb 100644 --- a/src/components/form/range/dual_range.tsx +++ b/src/components/form/range/dual_range.tsx @@ -45,14 +45,37 @@ export interface EuiDualRangeProps ) => void; fullWidth?: boolean; isInvalid?: boolean; + /** + * Create colored indicators for certain intervals + */ levels?: EuiRangeLevel[]; + /** + * Shows static min/max labels on the sides of the range slider + */ showLabels?: boolean; + /** + * Pass `true` to displays an extra input control for direct manipulation. + * Pass `'inputWithPopover'` to only show the input but show the range in a dropdown. + */ showInput?: EuiRangeProps['showInput']; + /** + * Modifies the number of tick marks and at what interval + */ tickInterval?: number; + /** + * Specified ticks at specified values + */ ticks?: EuiRangeTick[]; - append?: EuiFormControlLayoutProps['append']; + /** + * Creates an input group with element(s) coming before input. Will only show if `showInput = inputWithPopver`. + * `string` | `ReactElement` or an array of these + */ prepend?: EuiFormControlLayoutProps['prepend']; - + /** + * Creates an input group with element(s) coming after input. Will only show if `showInput = inputWithPopver`. + * `string` | `ReactElement` or an array of these + */ + append?: EuiFormControlLayoutProps['append']; /** * Intended to be uses with aria attributes. Some attributes may be overwritten. */ diff --git a/src/components/form/select/select.tsx b/src/components/form/select/select.tsx index 6eb141b48a6..301e070cf86 100644 --- a/src/components/form/select/select.tsx +++ b/src/components/form/select/select.tsx @@ -37,11 +37,13 @@ export type EuiSelectProps = SelectHTMLAttributes & compressed?: boolean; /** - * Creates an input group with element(s) coming before select + * Creates an input group with element(s) coming before select. + * `string` | `ReactElement` or an array of these */ prepend?: EuiFormControlLayoutProps['prepend']; /** - * Creates an input group with element(s) coming after select + * Creates an input group with element(s) coming after select. + * `string` | `ReactElement` or an array of these */ append?: EuiFormControlLayoutProps['append']; }; diff --git a/src/components/header/header_section/__snapshots__/header_section_item_button.test.tsx.snap b/src/components/header/header_section/__snapshots__/header_section_item_button.test.tsx.snap index 407fafc9b2f..a92f0259a33 100644 --- a/src/components/header/header_section/__snapshots__/header_section_item_button.test.tsx.snap +++ b/src/components/header/header_section/__snapshots__/header_section_item_button.test.tsx.snap @@ -19,3 +19,29 @@ exports[`EuiHeaderSectionItemButton renders children 1`] = ` `; + +exports[`EuiHeaderSectionItemButton renders notification children 1`] = ` + +`; + +exports[`EuiHeaderSectionItemButton renders notification color 1`] = ` + +`; diff --git a/src/components/header/header_section/_header_section_item.scss b/src/components/header/header_section/_header_section_item.scss index b694e4f9e57..098b6a98667 100644 --- a/src/components/header/header_section/_header_section_item.scss +++ b/src/components/header/header_section/_header_section_item.scss @@ -43,7 +43,11 @@ } } -.euiHeaderNotification { +// SET FOR DEPRECATION: 2/21/20 +// The `euiHeaderNotification` class was needed to be manually applied +// Now notifications can be automatically added to the buttons via props +.euiHeaderNotification, +.euiHeaderSectionItemButton__notification { position: absolute; top: 9%; right: 9%; @@ -64,7 +68,8 @@ } // On small screens just show a small dot indicating there are notifications - .euiHeaderNotification { + .euiHeaderNotification, + .euiHeaderSectionItemButton__notification { @include size($euiSizeS); top: 20%; min-width: 0; diff --git a/src/components/header/header_section/header_section_item_button.test.tsx b/src/components/header/header_section/header_section_item_button.test.tsx index 817519bf34f..1481ebf62e3 100644 --- a/src/components/header/header_section/header_section_item_button.test.tsx +++ b/src/components/header/header_section/header_section_item_button.test.tsx @@ -21,6 +21,25 @@ describe('EuiHeaderSectionItemButton', () => { expect(component).toMatchSnapshot(); }); + describe('renders notification', () => { + test('children', () => { + const component = render(); + + expect(component).toMatchSnapshot(); + }); + + test('color', () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + }); + describe('onClick', () => { test("isn't called upon instantiation", () => { const onClickHandler = jest.fn(); diff --git a/src/components/header/header_section/header_section_item_button.tsx b/src/components/header/header_section/header_section_item_button.tsx index 413c288a479..648553fc859 100644 --- a/src/components/header/header_section/header_section_item_button.tsx +++ b/src/components/header/header_section/header_section_item_button.tsx @@ -2,20 +2,48 @@ import React, { ButtonHTMLAttributes, FunctionComponent } from 'react'; import classNames from 'classnames'; import { CommonProps } from '../../common'; +import { + EuiNotificationBadgeProps, + EuiNotificationBadge, +} from '../../badge/notification_badge/badge_notification'; -type Props = CommonProps & ButtonHTMLAttributes; +type Props = CommonProps & + ButtonHTMLAttributes & { + /** + * Inserts the node into a EuiBadgeNotification and places it appropriately against the button + */ + notification?: EuiNotificationBadgeProps['children']; + /** + * Changes the color of the notification background + */ + notificationColor?: EuiNotificationBadgeProps['color']; + }; export const EuiHeaderSectionItemButton: FunctionComponent = ({ onClick, children, className, + notification, + notificationColor, ...rest }) => { const classes = classNames('euiHeaderSectionItem__button', className); + let notificationBadge; + if (notification) { + notificationBadge = ( + + {notification} + + ); + } + return ( ); };