Skip to content
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**

Expand Down
18 changes: 16 additions & 2 deletions src-docs/src/views/badge/badge_example.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -72,6 +74,12 @@ const badgeButtonSnippet = [
import BadgeTruncate from './badge_truncate';
const badgeTruncateSource = require('!!raw-loader!./badge_truncate');
const badgeTruncateHtml = renderToHtml(BadgeTruncate);
const badgeTruncateSnippet = [
`<EuiBadgeGroup gutterSize="s">
<EuiBadge />
<EuiBadge />
</EuiBadgeGroup>`,
];

import BetaBadge from './beta_badge';
const betaBadgeSource = require('!!raw-loader!./beta_badge');
Expand Down Expand Up @@ -170,7 +178,7 @@ export const BadgeExample = {
demo: <BadgeButton />,
},
{
title: 'Badge truncation',
title: 'Badge groups and truncation',
source: [
{
type: GuideSectionTypes.JS,
Expand All @@ -193,9 +201,15 @@ export const BadgeExample = {
to the <EuiCode>title</EuiCode> attribute of the element to provide
default browser tooltips with the full badge text.
</p>
<p>
To ensure proper wrapping, truncation and spacing of multiple
badges, it is advisable to wrap them in a{' '}
<EuiCode>EuiBadgeGroup</EuiCode>
</p>
</Fragment>
),
demo: <BadgeTruncate />,
snippet: badgeTruncateSnippet,
},
{
title: 'Beta badge type',
Expand Down Expand Up @@ -233,7 +247,7 @@ export const BadgeExample = {
</p>
</div>
),
props: { EuiBetaBadge },
props: { EuiBetaBadge, EuiBadgeGroup },
snippet: betaBadgeSnippet,
demo: <BetaBadge />,
},
Expand Down
62 changes: 28 additions & 34 deletions src-docs/src/views/badge/badge_truncate.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,35 @@
import React from 'react';

import { EuiBadge, EuiPanel, EuiSpacer } from '../../../../src/components';
import { EuiBadge, EuiPanel, EuiBadgeGroup } from '../../../../src/components';

export default () => (
<EuiPanel style={{ maxWidth: 200 }}>
<EuiBadge>Badge with simple text being truncated</EuiBadge>

<EuiSpacer size="s" />

<EuiBadge iconType="clock">Badge with icon being truncated</EuiBadge>

<EuiSpacer size="s" />

<EuiBadge onClick={() => {}} onClickAriaLabel="Click this badge to...">
Badge with onClick being truncated
</EuiBadge>

<EuiSpacer size="s" />

<EuiBadge
iconType="cross"
iconSide="right"
iconOnClick={() => {}}
iconOnClickAriaLabel="Click this icon to...">
Badge with iconOnClick being truncated
</EuiBadge>

<EuiSpacer size="s" />

<EuiBadge
iconType="cross"
iconSide="right"
onClick={() => {}}
onClickAriaLabel="Click this badge to..."
iconOnClick={() => {}}
iconOnClickAriaLabel="Click this icon to...">
Badge with both onClicks being truncated
</EuiBadge>
<EuiBadgeGroup gutterSize="s">
<EuiBadge>Badge with simple text being truncated</EuiBadge>

<EuiBadge iconType="clock">Badge with icon being truncated</EuiBadge>

<EuiBadge onClick={() => {}} onClickAriaLabel="Click this badge to...">
Badge with onClick being truncated
</EuiBadge>

<EuiBadge
iconType="cross"
iconSide="right"
iconOnClick={() => {}}
iconOnClickAriaLabel="Click this icon to...">
Badge with iconOnClick being truncated
</EuiBadge>

<EuiBadge
iconType="cross"
iconSide="right"
onClick={() => {}}
onClickAriaLabel="Click this badge to..."
iconOnClick={() => {}}
iconOnClickAriaLabel="Click this icon to...">
Badge with both onClicks being truncated
</EuiBadge>
</EuiBadgeGroup>
</EuiPanel>
);
6 changes: 3 additions & 3 deletions src-docs/src/views/suggest/_global_filter_group.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
@import 'saved_queries';

.globalFilterGroup__filterBar {
margin-top: $euiSizeM;
margin-top: $euiSizeXS;
}

// sass-lint:disable quotes
Expand All @@ -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
}
}
35 changes: 6 additions & 29 deletions src-docs/src/views/suggest/global_filter_bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -12,45 +12,22 @@ export const GlobalFilterBar = ({ filters, className, ...rest }) => {
const pinnedFilters = filters
.filter(filter => filter.isPinned)
.map(filter => {
return (
<EuiFlexItem
key={filter.id}
grow={false}
className="globalFilterBar__flexItem">
<GlobalFilterItem {...filter} />
</EuiFlexItem>
);
return <GlobalFilterItem key={filter.id} {...filter} />;
});

const unpinnedFilters = filters
.filter(filter => !filter.isPinned)
.map(filter => {
return (
<EuiFlexItem
key={filter.id}
grow={false}
className="globalFilterBar__flexItem">
<GlobalFilterItem {...filter} />
</EuiFlexItem>
);
return <GlobalFilterItem key={filter.id} {...filter} />;
});

return (
<EuiFlexGroup
className={classes}
wrap={true}
responsive={false}
gutterSize="xs"
alignItems="center"
{...rest}>
<EuiBadgeGroup className={classes} {...rest}>
{/* Show pinned filters first and in a specific group */}
{pinnedFilters}
{unpinnedFilters}

<EuiFlexItem grow={false}>
<GlobalFilterAdd />
</EuiFlexItem>
</EuiFlexGroup>
<GlobalFilterAdd />
</EuiBadgeGroup>
);
};

Expand Down
1 change: 1 addition & 0 deletions src-docs/src/views/suggest/global_filter_item.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
3 changes: 2 additions & 1 deletion src-docs/src/views/suggest/saved_queries.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/components/badge/_index.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@import 'badge';
@import 'badge_group/index';
@import 'beta_badge/index';
@import 'notification_badge/index';
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`EuiBadgeGroup gutterSize none is rendered 1`] = `
<div
class="euiBadgeGroup"
/>
`;

exports[`EuiBadgeGroup gutterSize s is rendered 1`] = `
<div
class="euiBadgeGroup euiBadgeGroup--gutterSmall"
/>
`;

exports[`EuiBadgeGroup gutterSize xs is rendered 1`] = `
<div
class="euiBadgeGroup euiBadgeGroup--gutterExtraSmall"
/>
`;

exports[`EuiBadgeGroup is rendered 1`] = `
<div
aria-label="aria-label"
class="euiBadgeGroup euiBadgeGroup--gutterExtraSmall testClass1 testClass2"
data-test-subj="test subject string"
>
<span
class="euiBadgeGroup__item"
>
<span
class="euiBadge euiBadge--iconLeft"
style="background-color:#d3dae6;color:#000"
>
<span
class="euiBadge__content"
>
<span
class="euiBadge__text"
>
Content
</span>
</span>
</span>
</span>
</div>
`;
23 changes: 23 additions & 0 deletions src/components/badge/badge_group/_badge_group.scss
Original file line number Diff line number Diff line change
@@ -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});
}
}
}
1 change: 1 addition & 0 deletions src/components/badge/badge_group/_index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import './badge_group';
28 changes: 28 additions & 0 deletions src/components/badge/badge_group/badge_group.test.tsx
Original file line number Diff line number Diff line change
@@ -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(
<EuiBadgeGroup {...requiredProps}>
<EuiBadge>Content</EuiBadge>
</EuiBadgeGroup>
);

expect(component).toMatchSnapshot();
});

describe('gutterSize', () => {
GUTTER_SIZES.forEach(size => {
it(`${size} is rendered`, () => {
const component = render(<EuiBadgeGroup gutterSize={size} />);

expect(component).toMatchSnapshot();
});
});
});
});
48 changes: 48 additions & 0 deletions src/components/badge/badge_group/badge_group.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement> & EuiBadgeGroupProps
>(
(
{ children, className, gutterSize = 'xs', ...rest },
ref: Ref<HTMLDivElement>
) => {
const classes = classNames(
'euiBadgeGroup',
gutterSizeToClassNameMap[gutterSize as BadgeGroupGutterSize],
className
);

return (
<div className={classes} ref={ref} {...rest}>
{React.Children.map(children, (child: ReactNode) => (
<span className="euiBadgeGroup__item">{child}</span>
))}
</div>
);
}
);
1 change: 1 addition & 0 deletions src/components/badge/badge_group/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { EuiBadgeGroup, EuiBadgeGroupProps } from './badge_group';
Loading