From adb7eb2d4685f29f271f67a5f18b0463d4a7b22e Mon Sep 17 00:00:00 2001 From: Caroline Horn <549577+cchaos@users.noreply.github.com> Date: Mon, 9 Mar 2020 16:19:34 -0400 Subject: [PATCH 01/12] [Feature] Added `EuiCollapsibleNav` component (#2977) * Setting up file structure * Added EuiFlyout to render, moved to left, and added docking * mock euioverlaymask * Better docs for EuiCollapsibleNav * Cleanup css * Adding responsive behavior * No longer using EuiFlyout directly * added a `close` button --- src-docs/src/routes.js | 3 + .../views/collapsible_nav/collapsible_nav.tsx | 36 ++++++ .../collapsible_nav_example.js | 69 ++++++++++++ .../collapsible_nav.test.tsx.snap | 100 +++++++++++++++++ .../collapsible_nav/_collapsible_nav.scss | 52 +++++++++ src/components/collapsible_nav/_index.scss | 2 + .../collapsible_nav/_variables.scss | 2 + .../collapsible_nav/collapsible_nav.test.tsx | 27 +++++ .../collapsible_nav/collapsible_nav.tsx | 104 ++++++++++++++++++ src/components/collapsible_nav/index.ts | 1 + src/components/flyout/_flyout.scss | 8 +- src/components/index.js | 2 + src/components/index.scss | 1 + 13 files changed, 405 insertions(+), 2 deletions(-) create mode 100644 src-docs/src/views/collapsible_nav/collapsible_nav.tsx create mode 100644 src-docs/src/views/collapsible_nav/collapsible_nav_example.js create mode 100644 src/components/collapsible_nav/__snapshots__/collapsible_nav.test.tsx.snap create mode 100644 src/components/collapsible_nav/_collapsible_nav.scss create mode 100644 src/components/collapsible_nav/_index.scss create mode 100644 src/components/collapsible_nav/_variables.scss create mode 100644 src/components/collapsible_nav/collapsible_nav.test.tsx create mode 100644 src/components/collapsible_nav/collapsible_nav.tsx create mode 100644 src/components/collapsible_nav/index.ts diff --git a/src-docs/src/routes.js b/src-docs/src/routes.js index 7cff19e5a28..377a0806a94 100644 --- a/src-docs/src/routes.js +++ b/src-docs/src/routes.js @@ -63,6 +63,8 @@ import { CodeEditorExample } from './views/code_editor/code_editor_example'; import { CodeExample } from './views/code/code_example'; +import { CollapsibleNavExample } from './views/collapsible_nav/collapsible_nav_example'; + import { ColorPickerExample } from './views/color_picker/color_picker_example'; import { ComboBoxExample } from './views/combo_box/combo_box_example'; @@ -321,6 +323,7 @@ const navigation = [ items: [ BreadcrumbsExample, ButtonExample, + CollapsibleNavExample, ContextMenuExample, ControlBarExample, FacetExample, diff --git a/src-docs/src/views/collapsible_nav/collapsible_nav.tsx b/src-docs/src/views/collapsible_nav/collapsible_nav.tsx new file mode 100644 index 00000000000..287eae6e58e --- /dev/null +++ b/src-docs/src/views/collapsible_nav/collapsible_nav.tsx @@ -0,0 +1,36 @@ +import React, { useState } from 'react'; + +import { EuiCollapsibleNav } from '../../../../src/components/collapsible_nav'; +import { EuiButton, EuiButtonToggle } from '../../../../src/components/button'; +import { EuiTitle } from '../../../../src/components/title'; +import { EuiSpacer } from '../../../../src/components/spacer'; + +export default () => { + const [navIsOpen, setNavIsOpen] = useState(false); + const [navIsDocked, setNavIsDocked] = useState(false); + + return ( + <> + setNavIsOpen(!navIsOpen)}>Toggle nav + {navIsOpen && ( + setNavIsOpen(false)}> +
+ +

I am some nav

+
+ + { + setNavIsDocked(!navIsDocked); + }} + /> +
+
+ )} + + ); +}; diff --git a/src-docs/src/views/collapsible_nav/collapsible_nav_example.js b/src-docs/src/views/collapsible_nav/collapsible_nav_example.js new file mode 100644 index 00000000000..12a225742cd --- /dev/null +++ b/src-docs/src/views/collapsible_nav/collapsible_nav_example.js @@ -0,0 +1,69 @@ +import React from 'react'; +import { Link } from 'react-router'; + +import { renderToHtml } from '../../services'; + +import { GuideSectionTypes } from '../../components'; + +import { + EuiCode, + EuiCollapsibleNav, + EuiText, + EuiSpacer, + EuiCallOut, +} from '../../../../src/components'; + +import CollapsibleNav from './collapsible_nav'; +const collapsibleNavSource = require('!!raw-loader!./collapsible_nav'); +const collapsibleNavHtml = renderToHtml(CollapsibleNav); + +export const CollapsibleNavExample = { + title: 'Collapsible nav', + intro: ( + +

+ This is a high level component that creates a flyout-style navigational + pane. It is the next evolution of{' '} + + EuiNavDrawer + {' '} + which will be deprecated in the coming months. +

+ +
+ ), + sections: [ + { + source: [ + { + type: GuideSectionTypes.JS, + code: collapsibleNavSource, + }, + { + type: GuideSectionTypes.HTML, + code: collapsibleNavHtml, + }, + ], + text: ( + <> +

+ EuiCollapsibleNav is a similar implementation to{' '} + + EuiFlyout + + ; the visibility of which must be maintained by the consuming + application. An extra feature that it provides is the ability to{' '} + dock the flyout. This affixes the flyout to the + window and pushes the body content by adding left side padding. +

+ + + ), + props: { EuiCollapsibleNav }, + demo: , + }, + ], +}; diff --git a/src/components/collapsible_nav/__snapshots__/collapsible_nav.test.tsx.snap b/src/components/collapsible_nav/__snapshots__/collapsible_nav.test.tsx.snap new file mode 100644 index 00000000000..1af87fb7989 --- /dev/null +++ b/src/components/collapsible_nav/__snapshots__/collapsible_nav.test.tsx.snap @@ -0,0 +1,100 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EuiCollapsibleNav can be docked 1`] = ` +
+
+
+
+ +
+
+
+`; + +exports[`EuiCollapsibleNav is rendered 1`] = ` +Array [ +
, +
+
+
+
+ +
+
+
, +] +`; diff --git a/src/components/collapsible_nav/_collapsible_nav.scss b/src/components/collapsible_nav/_collapsible_nav.scss new file mode 100644 index 00000000000..7d11ac4fff7 --- /dev/null +++ b/src/components/collapsible_nav/_collapsible_nav.scss @@ -0,0 +1,52 @@ +// Extends euiFlyout +@use '../flyout/flyout'; + +.euiCollapsibleNav { + @extend %eui-flyout; + right: auto; + left: 0; + width: $euiCollapsibleNavWidth; + max-width: 80vw; + + &:not(.euiCollapsibleNav--isDocked) { + animation: euiCollapsibleNavIn $euiAnimSpeedNormal $euiAnimSlightResistance; + } +} + +.euiCollapsibleNav__closeButton { + position: absolute; + right: 0; + top: $euiSize; + margin-right: -25%; +} + +@include euiBreakpoint('l', 'xl') { + // The addition of this class is handled through JS as well + // but adding under the breakpoint mixin is an additional fail-safe + .euiCollapsibleNav.euiCollapsibleNav--isDocked { + @include euiBottomShadowMedium; + + .euiCollapsibleNav__closeButton { + display: none; + } + } + + .euiBody--collapsibleNavIsDocked { + // Shrink the content from the left so it's no longer overlapped by the nav drawer (ALWAYS) + padding-left: $euiCollapsibleNavWidth !important; // sass-lint:disable-line no-important + transition: padding $euiAnimSpeedFast $euiAnimSlightResistance; + } +} + +// Specific keyframes so in comes in from the left +@keyframes euiCollapsibleNavIn { + 0% { + opacity: 0; + transform: translateX(-100%); + } + + 75% { + opacity: 1; + transform: translateX(0%); + } +} diff --git a/src/components/collapsible_nav/_index.scss b/src/components/collapsible_nav/_index.scss new file mode 100644 index 00000000000..53427be8960 --- /dev/null +++ b/src/components/collapsible_nav/_index.scss @@ -0,0 +1,2 @@ +@import 'variables'; +@import 'collapsible_nav'; diff --git a/src/components/collapsible_nav/_variables.scss b/src/components/collapsible_nav/_variables.scss new file mode 100644 index 00000000000..2378031a40f --- /dev/null +++ b/src/components/collapsible_nav/_variables.scss @@ -0,0 +1,2 @@ +// Sizing +$euiCollapsibleNavWidth: $euiSize * 20; // ~ 320px diff --git a/src/components/collapsible_nav/collapsible_nav.test.tsx b/src/components/collapsible_nav/collapsible_nav.test.tsx new file mode 100644 index 00000000000..38dd806e1fe --- /dev/null +++ b/src/components/collapsible_nav/collapsible_nav.test.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../test/required_props'; + +import { EuiCollapsibleNav } from './collapsible_nav'; + +jest.mock('../overlay_mask', () => ({ + EuiOverlayMask: (props: any) =>
, +})); + +describe('EuiCollapsibleNav', () => { + test('is rendered', () => { + const component = render( + {}} {...requiredProps} /> + ); + + expect(component).toMatchSnapshot(); + }); + + test('can be docked', () => { + const component = render( + {}} /> + ); + + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/components/collapsible_nav/collapsible_nav.tsx b/src/components/collapsible_nav/collapsible_nav.tsx new file mode 100644 index 00000000000..3ea6117be18 --- /dev/null +++ b/src/components/collapsible_nav/collapsible_nav.tsx @@ -0,0 +1,104 @@ +import React, { + FunctionComponent, + ReactNode, + useEffect, + useState, + Fragment, + HTMLAttributes, +} from 'react'; +import classNames from 'classnames'; +import { throttle } from '../color_picker/utils'; +import { EuiWindowEvent, keyCodes } from '../../services'; +import { EuiFocusTrap } from '../focus_trap'; +import { EuiOverlayMask } from '../overlay_mask'; +import { CommonProps } from '../common'; +import { EuiButtonEmpty } from '../button'; + +export type EuiCollapsibleNavProps = CommonProps & + HTMLAttributes & { + children?: ReactNode; + /** + * Keep navigation flyout visible and push `` content via padding + */ + docked?: boolean; + onClose: () => void; + }; + +export const EuiCollapsibleNav: FunctionComponent = ({ + children, + className, + docked = false, + onClose, + ...rest +}) => { + const [windowIsLargeEnoughToDock, setWindowIsLargeEnoughToDock] = useState( + window.innerWidth >= 992 + ); + const isDocked = docked && windowIsLargeEnoughToDock; + + const functionToCallOnWindowResize = throttle(() => { + if (window.innerWidth < 992) { + setWindowIsLargeEnoughToDock(false); + } else { + setWindowIsLargeEnoughToDock(true); + } + // reacts every 50ms to resize changes and always gets the final update + }, 50); + + // Watch for docked status and appropriately add/remove body classes and resize handlers + useEffect(() => { + if (docked) { + document.body.classList.add('euiBody--collapsibleNavIsDocked'); + window.addEventListener('resize', functionToCallOnWindowResize); + } + return () => { + document.body.classList.remove('euiBody--collapsibleNavIsDocked'); + window.removeEventListener('resize', functionToCallOnWindowResize); + }; + }, [docked, functionToCallOnWindowResize]); + + const onKeyDown = (event: KeyboardEvent) => { + if (event.keyCode === keyCodes.ESCAPE) { + event.preventDefault(); + collapse(); + } + }; + + const collapse = () => { + if (!isDocked) { + onClose(); + } + }; + + const classes = classNames( + 'euiCollapsibleNav', + { 'euiCollapsibleNav--isDocked': isDocked }, + className + ); + + let optionalOverlay; + if (!isDocked) { + optionalOverlay = ; + } + + return ( + + + {optionalOverlay} + {/* Trap focus only when isDocked={false} */} + + + + + ); +}; diff --git a/src/components/collapsible_nav/index.ts b/src/components/collapsible_nav/index.ts new file mode 100644 index 00000000000..4954e5ade02 --- /dev/null +++ b/src/components/collapsible_nav/index.ts @@ -0,0 +1 @@ +export { EuiCollapsibleNav } from './collapsible_nav'; diff --git a/src/components/flyout/_flyout.scss b/src/components/flyout/_flyout.scss index def8aec630b..2333723e298 100644 --- a/src/components/flyout/_flyout.scss +++ b/src/components/flyout/_flyout.scss @@ -1,4 +1,4 @@ -.euiFlyout { +%eui-flyout { border-left: $euiBorderThin; // The mixin augments the above // sass-lint:disable mixins-before-declarations @@ -10,12 +10,16 @@ height: 100%; z-index: $euiZModal; background: $euiColorEmptyShade; - animation: euiFlyout $euiAnimSpeedNormal $euiAnimSlightResistance; display: flex; flex-direction: column; align-items: stretch; } +.euiFlyout { + @extend %eui-flyout; + animation: euiFlyout $euiAnimSpeedNormal $euiAnimSlightResistance; +} + // The actual size of the X button in pixels is a bit fuzzy because of all the // button padding so there is some pixel pushing here. .euiFlyout__closeButton { diff --git a/src/components/index.js b/src/components/index.js index 9efdf29620a..c3a9351a285 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -37,6 +37,8 @@ export { EuiCode, EuiCodeBlock, EuiCodeBlockImpl } from './code'; export { EuiCodeEditor } from './code_editor'; +export { EuiCollapsibleNav } from './collapsible_nav'; + export { EuiColorPicker, EuiColorPickerSwatch, diff --git a/src/components/index.scss b/src/components/index.scss index cc4f1615102..dce5687119d 100644 --- a/src/components/index.scss +++ b/src/components/index.scss @@ -14,6 +14,7 @@ @import 'card/index'; @import 'code/index'; @import 'code_editor/index'; +@import 'collapsible_nav/index'; @import 'color_picker/index'; @import 'combo_box/index'; @import 'context_menu/index'; From 6d7a3b852320273522bda1b3b390d8e4c7cc51f5 Mon Sep 17 00:00:00 2001 From: Caroline Horn <549577+cchaos@users.noreply.github.com> Date: Mon, 9 Mar 2020 18:46:28 -0400 Subject: [PATCH 02/12] [New Nav Feature] Added `ghost` colored EuiListGroupItem (#3018) * Added color=ghost to list group item And fixed class names for list group * Added `color` prop to EuiListGroup Fixed color on disabled list group items * Fixing hover colors for each list item color * ghost example * Increase the height of large items too Fixes the hidden underline in focus state * Fixing demo to not apply black bg to list item * Snaps --- .../views/list_group/list_group_example.js | 3 +- .../list_group/list_group_item_color.tsx | 31 ++++-- src/components/accordion/_accordion.scss | 6 ++ .../__snapshots__/list_group.test.tsx.snap | 101 ++++++++++++++++-- .../list_group_item.test.tsx.snap | 17 +++ .../list_group/_list_group_item.scss | 22 +++- src/components/list_group/_variables.scss | 10 +- src/components/list_group/list_group.test.tsx | 8 ++ src/components/list_group/list_group.tsx | 11 +- src/components/list_group/list_group_item.tsx | 5 +- .../__snapshots__/nav_drawer.test.js.snap | 16 +-- 11 files changed, 189 insertions(+), 41 deletions(-) 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 7e704b16cfc..d4642cb9066 100644 --- a/src-docs/src/views/list_group/list_group_example.js +++ b/src-docs/src/views/list_group/list_group_example.js @@ -191,7 +191,8 @@ export const ListGroupExample = { anchor, or span. You can enforce a different color of primary,{' '} text, or subdued with the{' '} - color prop. + color prop. Or provide the prop directly to{' '} + EuiListGroup.

They also accept options for text size;{' '} 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 index 883bd524338..46c58e3d680 100644 --- a/src-docs/src/views/list_group/list_group_item_color.tsx +++ b/src-docs/src/views/list_group/list_group_item_color.tsx @@ -4,20 +4,29 @@ import { EuiListGroupItem, EuiListGroup, } from '../../../../src/components/list_group'; +import { EuiSpacer } from '../../../../src/components/spacer'; export default () => ( - - + <> + + - {}} - label="Primary (s)" - color="primary" - size="s" - /> + {}} + label="Primary (s)" + color="primary" + size="s" + /> - + - - + + + + + + + + + ); diff --git a/src/components/accordion/_accordion.scss b/src/components/accordion/_accordion.scss index 61a36b1ddc9..14533c8fadd 100644 --- a/src/components/accordion/_accordion.scss +++ b/src/components/accordion/_accordion.scss @@ -28,12 +28,18 @@ // Puts the arrow on the right flex-direction: row-reverse; justify-content: space-between; + + .euiAccordion__iconWrapper { + margin-left: $euiSizeS; + margin-right: $euiSizeXS; + } } .euiAccordion__iconWrapper { @include size($euiSize); border-radius: $euiBorderRadius; margin-right: $euiSizeS; + margin-left: $euiSizeXS; flex-shrink: 0; // Nested to override EuiIcon 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 72252c29443..c8a941b009b 100644 --- a/src/components/list_group/__snapshots__/list_group.test.tsx.snap +++ b/src/components/list_group/__snapshots__/list_group.test.tsx.snap @@ -3,14 +3,14 @@ exports[`EuiListGroup is rendered 1`] = `

    `; exports[`EuiListGroup is rendered with listItems 1`] = `
    • `; +exports[`EuiListGroup is rendered with listItems and color 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`] = `
            `; @@ -113,38 +192,38 @@ 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 e110e1cebbe..c43facbadcc 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,23 @@ exports[`EuiListGroupItem is rendered 1`] = ` `; +exports[`EuiListGroupItem props color ghost is rendered 1`] = ` +
                      • + + + Label + + +
                      • +`; + exports[`EuiListGroupItem props color inherit is rendered 1`] = `
                      • { expect(component).toMatchSnapshot(); }); + test('is rendered with listItems and color', () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + describe('props', () => { test('bordered is rendered', () => { const component = render(); diff --git a/src/components/list_group/list_group.tsx b/src/components/list_group/list_group.tsx index b79a1d388de..f2d70bd458a 100644 --- a/src/components/list_group/list_group.tsx +++ b/src/components/list_group/list_group.tsx @@ -7,8 +7,8 @@ import { CommonProps } from '../common'; type GutterSize = 'none' | 's' | 'm'; const gutterSizeToClassNameMap: { [size in GutterSize]: string } = { none: '', - s: 'euiListGroup--gutterS', - m: 'euiListGroup--gutterM', + s: 'euiListGroup--gutterSmall', + m: 'euiListGroup--gutterMedium', }; export const GUTTER_SIZES = Object.keys( gutterSizeToClassNameMap @@ -36,6 +36,11 @@ export type EuiListGroupProps = CommonProps & */ listItems?: EuiListGroupItemProps[]; + /** + * Change the colors of all `listItems` at once + */ + color?: EuiListGroupItemProps['color']; + /** * Sets the max-width of the page, * set to `true` to use the default size, @@ -68,6 +73,7 @@ export const EuiListGroup: FunctionComponent = ({ wrapText = false, maxWidth = true, showToolTips = false, + color, ariaLabelledby, ...rest }) => { @@ -105,6 +111,7 @@ export const EuiListGroup: FunctionComponent = ({ key={`title-${index}`} showToolTip={showToolTips} wrapText={wrapText} + color={color} {...item} />, ]; diff --git a/src/components/list_group/list_group_item.tsx b/src/components/list_group/list_group_item.tsx index 0801ae8043e..adfb3c0cfdb 100644 --- a/src/components/list_group/list_group_item.tsx +++ b/src/components/list_group/list_group_item.tsx @@ -25,12 +25,13 @@ const sizeToClassNameMap: { [size in ItemSize]: string } = { }; export const SIZES = Object.keys(sizeToClassNameMap) as ItemSize[]; -type Color = 'inherit' | 'primary' | 'text' | 'subdued'; +type Color = 'inherit' | 'primary' | 'text' | 'subdued' | 'ghost'; const colorToClassNameMap: { [color in Color]: string } = { inherit: '', primary: 'euiListGroupItem--primary', text: 'euiListGroupItem--text', subdued: 'euiListGroupItem--subdued', + ghost: 'euiListGroupItem--ghost', }; export const COLORS = Object.keys(colorToClassNameMap) as Color[]; @@ -48,7 +49,7 @@ export type EuiListGroupItemProps = CommonProps & size?: ItemSize; /** * By default the item will inherit the color of its wrapper (button/link/span), - * otherwise pass on of the acceptable options + * otherwise pass one of the acceptable options */ color?: Color; 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 b8b6c6384b4..4c0ce67796d 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" >