setNavIsOpen(false)}
+ />
+)}`,
+ },
+ {
+ title: 'Collapsible nav group',
+ source: [
+ {
+ type: GuideSectionTypes.JS,
+ code: collapsibleNavGroupSource,
+ },
+ {
+ type: GuideSectionTypes.HTML,
+ code: collapsibleNavGroupHtml,
+ },
+ ],
+ text: (
+ <>
+
+ An EuiCollapsibleNavGroup adds some basic borders
+ and background color of none ,{' '}
+ light , or dark . Give each
+ seaction a heading by providing an optional title {' '}
+ and iconType . Make the section collapsible (
+ accordion style) with{' '}
+ isCollapsible=true .
+
+
+ When in isCollapsible mode, a{' '}
+ title and{' '}
+ initialIsOpen:boolean is required.
+
+ >
+ ),
+ props: {
+ EuiCollapsibleNavGroup,
+ },
+ demo: ,
+ snippet: ` `,
},
],
};
diff --git a/src-docs/src/views/collapsible_nav/collapsible_nav_group.tsx b/src-docs/src/views/collapsible_nav/collapsible_nav_group.tsx
new file mode 100644
index 00000000000..89463b9a46d
--- /dev/null
+++ b/src-docs/src/views/collapsible_nav/collapsible_nav_group.tsx
@@ -0,0 +1,55 @@
+import React from 'react';
+
+import { EuiCollapsibleNavGroup } from '../../../../src/components/collapsible_nav';
+import { EuiText } from '../../../../src/components/text';
+import { EuiCode } from '../../../../src/components/code';
+
+export default () => (
+ <>
+
+
+ This is a basic group without any modifications
+
+
+
+
+
+ This is a nice group with a heading supplied via{' '}
+ title and iconType .
+
+
+
+
+
+
+ This group is collapsible and set with{' '}
+ initialIsOpen . It has a heading that is the
+ collapsing button via title and{' '}
+ iconType .
+
+
+
+
+
+
+ This is a dark collapsible group
+ that is initally set to closed,{' '}
+ iconSize="xxl" and{' '}
+ titleSize="s" .
+
+
+
+ >
+);
diff --git a/src/components/collapsible_nav/_index.scss b/src/components/collapsible_nav/_index.scss
index 53427be8960..fa7acfa01df 100644
--- a/src/components/collapsible_nav/_index.scss
+++ b/src/components/collapsible_nav/_index.scss
@@ -1,2 +1,4 @@
@import 'variables';
+
+@import 'collapsible_nav_group/index';
@import 'collapsible_nav';
diff --git a/src/components/collapsible_nav/_variables.scss b/src/components/collapsible_nav/_variables.scss
index 2378031a40f..f883cd5f8a2 100644
--- a/src/components/collapsible_nav/_variables.scss
+++ b/src/components/collapsible_nav/_variables.scss
@@ -1,2 +1,14 @@
// Sizing
$euiCollapsibleNavWidth: $euiSize * 20; // ~ 320px
+
+$euiCollapsibleNavGroupLightBackgroundColor: $euiPageBackgroundColor;
+
+$euiCollapsibleNavGroupDarkBackgroundColor: lightOrDarkTheme(
+ shade($euiColorDarkestShade, 20%),
+ shade($euiColorLightestShade, 50%),
+);
+
+$euiCollapsibleNavGroupDarkHighContrastColor: makeGraphicContrastColor(
+ $euiColorPrimary,
+ $euiCollapsibleNavGroupDarkBackgroundColor
+);
diff --git a/src/components/collapsible_nav/collapsible_nav_group/__snapshots__/collapsible_nav_group.test.tsx.snap b/src/components/collapsible_nav/collapsible_nav_group/__snapshots__/collapsible_nav_group.test.tsx.snap
new file mode 100644
index 00000000000..082ad42be5d
--- /dev/null
+++ b/src/components/collapsible_nav/collapsible_nav_group/__snapshots__/collapsible_nav_group.test.tsx.snap
@@ -0,0 +1,252 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`EuiCollapsibleNavGroup is rendered 1`] = `
+
+`;
+
+exports[`EuiCollapsibleNavGroup props background dark is rendered 1`] = `
+
+`;
+
+exports[`EuiCollapsibleNavGroup props background light is rendered 1`] = `
+
+`;
+
+exports[`EuiCollapsibleNavGroup props background none is rendered 1`] = `
+
+`;
+
+exports[`EuiCollapsibleNavGroup props iconSize is rendered 1`] = `
+
+`;
+
+exports[`EuiCollapsibleNavGroup props iconType is rendered 1`] = `
+
+`;
+
+exports[`EuiCollapsibleNavGroup props title is rendered 1`] = `
+
+`;
+
+exports[`EuiCollapsibleNavGroup props titleElement can change the rendered element to h2 1`] = `
+
+`;
+
+exports[`EuiCollapsibleNavGroup props titleSize can be larger 1`] = `
+
+`;
+
+exports[`EuiCollapsibleNavGroup throws a warning if iconType is passed without a title 1`] = `
+
+`;
+
+exports[`EuiCollapsibleNavGroup when isCollapsible is true will render an accordion 1`] = `
+
+`;
diff --git a/src/components/collapsible_nav/collapsible_nav_group/_collapsible_nav_group.scss b/src/components/collapsible_nav/collapsible_nav_group/_collapsible_nav_group.scss
new file mode 100644
index 00000000000..b301243a8e3
--- /dev/null
+++ b/src/components/collapsible_nav/collapsible_nav_group/_collapsible_nav_group.scss
@@ -0,0 +1,47 @@
+.euiCollapsibleNavGroup {
+ &:not(:first-child) {
+ border-top: $euiBorderThin;
+ }
+}
+
+.euiCollapsibleNavGroup--light {
+ background-color: $euiCollapsibleNavGroupLightBackgroundColor;
+}
+
+.euiCollapsibleNavGroup--dark {
+ background-color: $euiCollapsibleNavGroupDarkBackgroundColor;
+ color: $euiColorGhost;
+
+ // Forcing better contrast of focus state on EuiAccordion toggle icon
+ .euiCollapsibleNavGroup__heading:focus .euiAccordion__iconWrapper {
+ color: $euiCollapsibleNavGroupDarkHighContrastColor;
+ animation-name: euiCollapsibleNavGroupDarkFocusRingAnimate !important; // sass-lint:disable-line no-important
+ }
+
+ .euiCollapsibleNavGroup__title {
+ color: inherit;
+ }
+}
+
+.euiCollapsibleNavGroup__heading {
+ padding: $euiSize;
+ font-weight: $euiFontWeightSemiBold;
+}
+
+.euiCollapsibleNavGroup__children {
+ padding: $euiSizeS;
+}
+
+.euiCollapsibleNavGroup--withHeading .euiCollapsibleNavGroup__children {
+ padding-top: 0;
+}
+
+@keyframes euiCollapsibleNavGroupDarkFocusRingAnimate {
+ 0% {
+ box-shadow: 0 0 0 $euiFocusRingAnimStartSize $euiFocusRingAnimStartColor;
+ }
+
+ 100% {
+ box-shadow: 0 0 0 $euiFocusRingSize $euiCollapsibleNavGroupDarkHighContrastColor;
+ }
+}
diff --git a/src/components/collapsible_nav/collapsible_nav_group/_index.scss b/src/components/collapsible_nav/collapsible_nav_group/_index.scss
new file mode 100644
index 00000000000..667ebab6881
--- /dev/null
+++ b/src/components/collapsible_nav/collapsible_nav_group/_index.scss
@@ -0,0 +1 @@
+@import 'collapsible_nav_group';
diff --git a/src/components/collapsible_nav/collapsible_nav_group/collapsible_nav_group.test.tsx b/src/components/collapsible_nav/collapsible_nav_group/collapsible_nav_group.test.tsx
new file mode 100644
index 00000000000..3a37ffed510
--- /dev/null
+++ b/src/components/collapsible_nav/collapsible_nav_group/collapsible_nav_group.test.tsx
@@ -0,0 +1,117 @@
+import React from 'react';
+import { render } from 'enzyme';
+import { requiredProps } from '../../../test/required_props';
+
+import { EuiCollapsibleNavGroup, BACKGROUNDS } from './collapsible_nav_group';
+
+describe('EuiCollapsibleNavGroup', () => {
+ test('is rendered', () => {
+ const component = render(
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+
+ describe('props', () => {
+ test('title is rendered', () => {
+ const component = render(
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+
+ test('iconType is rendered', () => {
+ const component = render(
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+
+ test('iconSize is rendered', () => {
+ const component = render(
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+
+ describe('background', () => {
+ BACKGROUNDS.forEach(color => {
+ test(`${color} is rendered`, () => {
+ const component = render(
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+ });
+ });
+
+ test('titleElement can change the rendered element to h2', () => {
+ const component = render(
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+
+ test('titleSize can be larger', () => {
+ const component = render(
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+ });
+
+ describe('when isCollapsible is true', () => {
+ test('will render an accordion', () => {
+ 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 iconType is passed without a title', () => {
+ const component = render(
+
+ );
+
+ expect(consoleStub).toBeCalled();
+ expect(consoleStub.mock.calls[0][0]).toMatch(
+ 'not render an icon without `title`'
+ );
+ expect(component).toMatchSnapshot();
+ });
+ });
+});
diff --git a/src/components/collapsible_nav/collapsible_nav_group/collapsible_nav_group.tsx b/src/components/collapsible_nav/collapsible_nav_group/collapsible_nav_group.tsx
new file mode 100644
index 00000000000..836244a83bc
--- /dev/null
+++ b/src/components/collapsible_nav/collapsible_nav_group/collapsible_nav_group.tsx
@@ -0,0 +1,161 @@
+import React, { FunctionComponent, ReactNode, useState } from 'react';
+import classNames from 'classnames';
+import { CommonProps, ExclusiveUnion } from '../../common';
+import { htmlIdGenerator } from '../../../services';
+
+import { EuiAccordion, EuiAccordionProps } from '../../accordion';
+import { EuiIcon, IconType, IconSize } from '../../icon';
+import { EuiFlexGroup, EuiFlexItem } from '../../flex';
+import { EuiTitle, EuiTitleProps, EuiTitleSize } from '../../title';
+
+type Background = 'none' | 'light' | 'dark';
+const backgroundToClassNameMap: { [color in Background]: string } = {
+ none: '',
+ light: 'euiCollapsibleNavGroup--light',
+ dark: 'euiCollapsibleNavGroup--dark',
+};
+export const BACKGROUNDS = Object.keys(
+ backgroundToClassNameMap
+) as Background[];
+
+export interface EuiCollapsibleNavGroupInterface extends CommonProps {
+ children?: ReactNode;
+ /**
+ * Sits left of the `title` and only when `title` is present
+ */
+ iconType?: IconType;
+ /**
+ * Change the size of the icon in the `title`
+ */
+ iconSize?: IconSize;
+ /**
+ * Optionally provide an id, otherwise one will be created
+ */
+ id?: string;
+ /**
+ * Adds a background color to the entire group,
+ * applying the correct text color to the `title` only
+ */
+ background?: Background;
+ /**
+ * Determines the title's heading element
+ */
+ titleElement?: 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'span';
+ /**
+ * Title sizing equivelant to EuiTitle, but only `s` and smaller
+ */
+ titleSize?: Omit;
+}
+
+type GroupAsAccordion = EuiCollapsibleNavGroupInterface &
+ Omit & {
+ /**
+ * If `true`, wraps children in the body of an accordion,
+ * requiring the prop `title` to be used as the button
+ */
+ isCollapsible: true;
+ /**
+ * The title gets wrapped in the appropriate heading level
+ * with the option to add an iconType
+ */
+ title: ReactNode;
+ };
+
+type GroupAsDiv = EuiCollapsibleNavGroupInterface & {
+ /**
+ * When `false`, simply renders a div without any accordion functionality
+ */
+ isCollapsible?: false;
+ /**
+ * The title gets wrapped in the appropriate heading level
+ * with the option to add an iconType
+ */
+ title?: ReactNode;
+};
+
+export type EuiCollapsibleNavGroupProps = ExclusiveUnion<
+ GroupAsAccordion,
+ GroupAsDiv
+>;
+
+export const EuiCollapsibleNavGroup: FunctionComponent<
+ EuiCollapsibleNavGroupProps
+> = ({
+ className,
+ children,
+ id,
+ title,
+ iconType,
+ iconSize = 'l',
+ background = 'none',
+ isCollapsible = false,
+ titleElement = 'h3',
+ titleSize = 'xxs',
+ ...rest
+}) => {
+ const [groupID] = useState(id || htmlIdGenerator()());
+ const classes = classNames(
+ 'euiCollapsibleNavGroup',
+ backgroundToClassNameMap[background],
+ {
+ 'euiCollapsibleNavGroup--withHeading': title,
+ },
+ className
+ );
+
+ // Warn if consumer passes an iconType without a title
+ if (iconType && !title) {
+ console.warn(
+ 'EuiCollapsibleNavGroup will not render an icon without `title`.'
+ );
+ }
+
+ const content = (
+ {children}
+ );
+
+ const headingClasses = 'euiCollapsibleNavGroup__heading';
+
+ const TitleElement = titleElement;
+ const titleContent = title ? (
+
+ {iconType && (
+
+
+
+ )}
+
+
+
+
+ {title}
+
+
+
+
+ ) : (
+ undefined
+ );
+
+ if (isCollapsible && title) {
+ return (
+
+ {content}
+
+ );
+ } else {
+ return (
+
+ {titleContent &&
{titleContent}
}
+ {content}
+
+ );
+ }
+};
diff --git a/src/components/collapsible_nav/collapsible_nav_group/index.ts b/src/components/collapsible_nav/collapsible_nav_group/index.ts
new file mode 100644
index 00000000000..3ded0215793
--- /dev/null
+++ b/src/components/collapsible_nav/collapsible_nav_group/index.ts
@@ -0,0 +1,4 @@
+export {
+ EuiCollapsibleNavGroup,
+ EuiCollapsibleNavGroupProps,
+} from './collapsible_nav_group';
diff --git a/src/components/collapsible_nav/index.ts b/src/components/collapsible_nav/index.ts
index 4954e5ade02..846e72f558f 100644
--- a/src/components/collapsible_nav/index.ts
+++ b/src/components/collapsible_nav/index.ts
@@ -1 +1,6 @@
-export { EuiCollapsibleNav } from './collapsible_nav';
+export {
+ EuiCollapsibleNavGroup,
+ EuiCollapsibleNavGroupProps,
+} from './collapsible_nav_group';
+
+export { EuiCollapsibleNav, EuiCollapsibleNavProps } from './collapsible_nav';
diff --git a/src/components/index.js b/src/components/index.js
index 62f71dcd23e..00b11be0cdc 100644
--- a/src/components/index.js
+++ b/src/components/index.js
@@ -37,7 +37,7 @@ export { EuiCode, EuiCodeBlock, EuiCodeBlockImpl } from './code';
export { EuiCodeEditor } from './code_editor';
-export { EuiCollapsibleNav } from './collapsible_nav';
+export { EuiCollapsibleNavGroup, EuiCollapsibleNav } from './collapsible_nav';
export {
EuiColorPicker,
diff --git a/src/global_styling/utility/_animations.scss b/src/global_styling/utility/_animations.scss
index f5bedf92568..c0cb6e7365a 100644
--- a/src/global_styling/utility/_animations.scss
+++ b/src/global_styling/utility/_animations.scss
@@ -28,7 +28,7 @@
@keyframes focusRingAnimate {
0% {
- box-shadow: 0 0 0 6px $euiFocusRingAnimStartColor;
+ box-shadow: 0 0 0 $euiFocusRingAnimStartSize $euiFocusRingAnimStartColor;
}
100% {
@@ -38,7 +38,7 @@
@keyframes focusRingAnimateLarge {
0% {
- box-shadow: 0 0 0 10px $euiFocusRingAnimStartColor;
+ box-shadow: 0 0 0 $euiFocusRingAnimStartSizeLarge $euiFocusRingAnimStartColor;
}
100% {
diff --git a/src/global_styling/variables/_states.scss b/src/global_styling/variables/_states.scss
index 42f750130a7..5152c3eef2e 100644
--- a/src/global_styling/variables/_states.scss
+++ b/src/global_styling/variables/_states.scss
@@ -1,6 +1,8 @@
// Colors
$euiFocusRingColor: rgba($euiColorPrimary, .3) !default;
$euiFocusRingAnimStartColor: rgba($euiColorPrimary, 0) !default;
+$euiFocusRingAnimStartSize: 6px !default;
+$euiFocusRingAnimStartSizeLarge: 10px !default;
// Sizing
$euiFocusRingSizeLarge: $euiSizeXS !default;