diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a12bf1d427..e4b18e24370 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 99758b562e6..7e704b16cfc 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 00000000000..883bd524338 --- /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 129681e17db..e3e31b1df89 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 c2660d17137..c0c5e7198ac 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 fbd22fd489e..72252c29443 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`] = `
    `; + +exports[`EuiListGroup is rendered with listItems 1`] = ` +
      +
    • + +
      + + Label with iconType + + +
    • +
    • + + + Custom extra action + + + +
    • +
    • + +
    • +
    • + + + Link with href + + +
    • +
    +`; + +exports[`EuiListGroup props bordered is rendered 1`] = ` +
      +`; + +exports[`EuiListGroup props flush is rendered 1`] = ` +
        +`; + +exports[`EuiListGroup props gutter size m is rendered 1`] = ` +
          +`; + +exports[`EuiListGroup props gutter size none is rendered 1`] = ` +
            +`; + +exports[`EuiListGroup props gutter size s is rendered 1`] = ` +
              +`; + +exports[`EuiListGroup props maxWidth as a number is rendered 1`] = ` +
                +`; + +exports[`EuiListGroup props maxWidth as a string is rendered 1`] = ` +
                  +`; + +exports[`EuiListGroup props maxWidth as true is rendered 1`] = ` +
                    +`; + +exports[`EuiListGroup props showToolTips is rendered 1`] = ` +
                      +`; + +exports[`EuiListGroup props wrapText is rendered 1`] = ` +
                        +`; 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 623e6ad1ce6..e110e1cebbe 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 @@ -17,6 +17,74 @@ exports[`EuiListGroupItem is rendered 1`] = ` `; +exports[`EuiListGroupItem props color inherit is rendered 1`] = ` +
                      • + + + Label + + +
                      • +`; + +exports[`EuiListGroupItem props color primary is rendered 1`] = ` +
                      • + + + Label + + +
                      • +`; + +exports[`EuiListGroupItem props color subdued is rendered 1`] = ` +
                      • + + + Label + + +
                      • +`; + +exports[`EuiListGroupItem props color text is rendered 1`] = ` +
                      • + + + Label + + +
                      • +`; + exports[`EuiListGroupItem props extraAction is rendered 1`] = `
                      • { + console.log('Visualize clicked', e); + }, + }, + { + label: 'Link with href', + href: '#', + }, +]; describe('EuiListGroup', () => { test('is rendered', () => { @@ -10,4 +35,66 @@ describe('EuiListGroup', () => { expect(component).toMatchSnapshot(); }); + + test('is rendered with listItems', () => { + const component = render(); + + expect(component).toMatchSnapshot(); + }); + + describe('props', () => { + test('bordered is rendered', () => { + const component = render(); + + expect(component).toMatchSnapshot(); + }); + + test('flush is rendered', () => { + const component = render(); + + expect(component).toMatchSnapshot(); + }); + + test('showToolTips is rendered', () => { + const component = render(); + + expect(component).toMatchSnapshot(); + }); + + test('wrapText is rendered', () => { + const component = render(); + + expect(component).toMatchSnapshot(); + }); + + describe('gutter size', () => { + GUTTER_SIZES.forEach(gutter => { + test(`${gutter} is rendered`, () => { + const component = render(); + + expect(component).toMatchSnapshot(); + }); + }); + }); + + describe('maxWidth', () => { + test('as true is rendered', () => { + const component = render(); + + expect(component).toMatchSnapshot(); + }); + + test('as a number is rendered', () => { + const component = render(); + + expect(component).toMatchSnapshot(); + }); + + test('as a string is rendered', () => { + const component = render(); + + expect(component).toMatchSnapshot(); + }); + }); + }); }); diff --git a/src/components/list_group/list_group.tsx b/src/components/list_group/list_group.tsx index 1d6354f8867..b79a1d388de 100644 --- a/src/components/list_group/list_group.tsx +++ b/src/components/list_group/list_group.tsx @@ -4,6 +4,16 @@ import classNames from 'classnames'; import { EuiListGroupItem, EuiListGroupItemProps } from './list_group_item'; import { CommonProps } from '../common'; +type GutterSize = 'none' | 's' | 'm'; +const gutterSizeToClassNameMap: { [size in GutterSize]: string } = { + none: '', + s: 'euiListGroup--gutterS', + m: 'euiListGroup--gutterM', +}; +export const GUTTER_SIZES = Object.keys( + gutterSizeToClassNameMap +) as GutterSize[]; + export type EuiListGroupProps = CommonProps & HTMLAttributes & { /** @@ -16,6 +26,11 @@ export type EuiListGroupProps = CommonProps & */ flush?: boolean; + /** + * Spacing between list items + */ + gutterSize?: GutterSize; + /** * Items to display in this group. See #EuiListGroupItem */ @@ -36,7 +51,7 @@ export type EuiListGroupProps = CommonProps & showToolTips?: boolean; /** - * Allow link text to wrap + * Allow link text to wrap vs truncated */ wrapText?: boolean; ariaLabelledby?: string; @@ -49,6 +64,7 @@ export const EuiListGroup: FunctionComponent = ({ style, flush = false, bordered = false, + gutterSize = 's', wrapText = false, maxWidth = true, showToolTips = false, @@ -76,6 +92,7 @@ export const EuiListGroup: FunctionComponent = ({ 'euiListGroup-flush': flush, 'euiListGroup-bordered': bordered, }, + gutterSizeToClassNameMap[gutterSize], widthClassName, className ); diff --git a/src/components/list_group/list_group_item.test.tsx b/src/components/list_group/list_group_item.test.tsx index 6582f7303b2..40a903f4b8f 100644 --- a/src/components/list_group/list_group_item.test.tsx +++ b/src/components/list_group/list_group_item.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { render } from 'enzyme'; -import { EuiListGroupItem, SIZES } from './list_group_item'; +import { EuiListGroupItem, SIZES, COLORS } from './list_group_item'; describe('EuiListGroupItem', () => { test('is rendered', () => { @@ -23,6 +23,18 @@ describe('EuiListGroupItem', () => { }); }); + describe('color', () => { + COLORS.forEach(color => { + test(`${color} is rendered`, () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + }); + }); + describe('isActive', () => { test('is rendered', () => { const component = render(); diff --git a/src/components/list_group/list_group_item.tsx b/src/components/list_group/list_group_item.tsx index 40f318cdd5c..0801ae8043e 100644 --- a/src/components/list_group/list_group_item.tsx +++ b/src/components/list_group/list_group_item.tsx @@ -17,16 +17,23 @@ import { useInnerText } from '../inner_text'; import { ExclusiveUnion, CommonProps } from '../common'; type ItemSize = 'xs' | 's' | 'm' | 'l'; - const sizeToClassNameMap: { [size in ItemSize]: string } = { xs: 'euiListGroupItem--xSmall', s: 'euiListGroupItem--small', m: 'euiListGroupItem--medium', l: 'euiListGroupItem--large', }; - export const SIZES = Object.keys(sizeToClassNameMap) as ItemSize[]; +type Color = 'inherit' | 'primary' | 'text' | 'subdued'; +const colorToClassNameMap: { [color in Color]: string } = { + inherit: '', + primary: 'euiListGroupItem--primary', + text: 'euiListGroupItem--text', + subdued: 'euiListGroupItem--subdued', +}; +export const COLORS = Object.keys(colorToClassNameMap) as Color[]; + export type EuiListGroupItemProps = CommonProps & ExclusiveUnion< ExclusiveUnion< @@ -39,6 +46,11 @@ export type EuiListGroupItemProps = CommonProps & * Size of the label text */ size?: ItemSize; + /** + * By default the item will inherit the color of its wrapper (button/link/span), + * otherwise pass on of the acceptable options + */ + color?: Color; /** * Content to be displayed in the list item @@ -114,6 +126,7 @@ export const EuiListGroupItem: FunctionComponent = ({ extraAction, onClick, size = 'm', + color = 'inherit', showToolTip = false, wrapText, buttonRef, @@ -122,6 +135,7 @@ export const EuiListGroupItem: FunctionComponent = ({ const classes = classNames( 'euiListGroupItem', sizeToClassNameMap[size], + colorToClassNameMap[color], { 'euiListGroupItem-isActive': isActive, 'euiListGroupItem-isDisabled': isDisabled, @@ -192,9 +206,9 @@ export const EuiListGroupItem: FunctionComponent = ({ if (href && !isDisabled) { itemContent = ( ['onClick']} - className="euiListGroupItem__button" {...rest as AnchorHTMLAttributes}> {iconNode} {labelContent} diff --git a/src/components/nav_drawer/__snapshots__/nav_drawer.test.js.snap b/src/components/nav_drawer/__snapshots__/nav_drawer.test.js.snap index 01827dde7fa..b8b6c6384b4 100644 --- a/src/components/nav_drawer/__snapshots__/nav_drawer.test.js.snap +++ b/src/components/nav_drawer/__snapshots__/nav_drawer.test.js.snap @@ -15,7 +15,7 @@ exports[`EuiNavDrawer is rendered 1`] = ` id="navDrawerMenu" >