diff --git a/CHANGELOG.md b/CHANGELOG.md index 04e1478dfad..4352baa7deb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ - Limited the links allowed in `EuiMarkdownEditor` to http, https, or starting with a forward slash ([#4362](https://github.com/elastic/eui/pull/4362)) - Aligned components with an `href` prop to React's practice of disallowing `javascript:` protocols ([#4362](https://github.com/elastic/eui/pull/4362)) - Fixed form submit bug in `EuiButtonGroup` by adding an optional `type` prop for `EuiButtonGroupOption` ([#4368](https://github.com/elastic/eui/pull/4368)) +- Changed `label` type from `string` to `ReactNode` in `EuiTreeViewNode` ([#4352](https://github.com/elastic/eui/pull/4352)) **Theme: Amsterdam** diff --git a/src/components/context/context.tsx b/src/components/context/context.tsx index dd0ffa57169..3170d2ba7ab 100644 --- a/src/components/context/context.tsx +++ b/src/components/context/context.tsx @@ -20,7 +20,8 @@ import React, { createContext, ReactChild, ReactNode } from 'react'; export interface RenderableValues { - [key: string]: ReactChild; + // undefined values are ignored, but including support here improves usability + [key: string]: ReactChild | undefined; } export type Renderable = ReactChild | ((values: T) => ReactChild); diff --git a/src/components/i18n/i18n_util.test.tsx b/src/components/i18n/i18n_util.test.tsx index 91ca90d7ee5..5d52534d1e3 100644 --- a/src/components/i18n/i18n_util.test.tsx +++ b/src/components/i18n/i18n_util.test.tsx @@ -35,6 +35,15 @@ describe('i18n_util', () => { ).toEqual('Hello, John'); }); + it('ignores `undefined` values and still returns a string', () => { + expect( + processStringToChildren('{greeting}, {name}', { + greeting: 'Hello', + name: undefined, + }) + ).toEqual('Hello, '); + }); + describe('escape characters', () => { it('backslash escapes opening and closing braces', () => { expect( diff --git a/src/components/i18n/i18n_util.tsx b/src/components/i18n/i18n_util.tsx index bc85c1557b9..f668c724e3c 100644 --- a/src/components/i18n/i18n_util.tsx +++ b/src/components/i18n/i18n_util.tsx @@ -22,12 +22,15 @@ import { isBoolean, isString, isNumber, -} from '../../services/predicate/lodash_predicates'; + isUndefined, +} from '../../services/predicate'; import { isElement } from 'react-is'; import { RenderableValues } from '../context/context'; -function isPrimitive(value: ReactChild) { - return isBoolean(value) || isString(value) || isNumber(value); +function isPrimitive(value: ReactChild | undefined) { + return ( + isBoolean(value) || isString(value) || isNumber(value) || isUndefined(value) + ); } type Child = string | { propName: string } | ReactChild | undefined; diff --git a/src/components/tree_view/tree_view.tsx b/src/components/tree_view/tree_view.tsx index 10dbccde057..4c4a066cbb8 100644 --- a/src/components/tree_view/tree_view.tsx +++ b/src/components/tree_view/tree_view.tsx @@ -25,6 +25,7 @@ import { EuiIcon } from '../icon'; import { EuiScreenReaderOnly } from '../accessibility'; import { EuiText } from '../text'; import { keys, htmlIdGenerator } from '../../services'; +import { EuiInnerText } from '../inner_text'; const EuiTreeViewContext = createContext(''); const treeIdGenerator = htmlIdGenerator('euiTreeView'); @@ -41,7 +42,7 @@ export interface Node { children?: Node[]; /** The readable label for the item */ - label: string; + label: React.ReactNode; /** A unique ID */ id: string; @@ -279,101 +280,116 @@ export class EuiTreeView extends Component { const buttonId = `${this.state.treeID}--${index}--node`; return ( - - {(ariaLabel: string) => { - const label: - | { 'aria-label': string } - | { 'aria-labelledby': string } = hasAriaLabel(rest) - ? { - 'aria-label': ariaLabel, - } - : { - 'aria-labelledby': `${buttonId} ${rest['aria-labelledby']}`, - }; + + {(ref, innerText) => ( + + {(ariaLabel: string) => { + const label: + | { 'aria-label': string } + | { 'aria-labelledby': string } = hasAriaLabel(rest) + ? { + 'aria-label': ariaLabel, + } + : { + 'aria-labelledby': `${buttonId} ${rest['aria-labelledby']}`, + }; - const nodeClasses = classNames( - 'euiTreeView__node', - display ? displayToClassNameMap[display] : null, - { 'euiTreeView__node--expanded': this.isNodeOpen(node) } - ); + const nodeClasses = classNames( + 'euiTreeView__node', + display ? displayToClassNameMap[display] : null, + { + 'euiTreeView__node--expanded': this.isNodeOpen( + node + ), + } + ); - const nodeButtonClasses = classNames( - 'euiTreeView__nodeInner', - showExpansionArrows && node.children - ? 'euiTreeView__nodeInner--withArrows' - : null, - this.state.activeItem === node.id - ? 'euiTreeView__node--active' - : null, - node.className ? node.className : null - ); + const nodeButtonClasses = classNames( + 'euiTreeView__nodeInner', + showExpansionArrows && node.children + ? 'euiTreeView__nodeInner--withArrows' + : null, + this.state.activeItem === node.id + ? 'euiTreeView__node--active' + : null, + node.className ? node.className : null + ); - return ( - -
  • - -
    - this.onChildrenKeydown(event, index) - }> - {node.children && this.isNodeOpen(node) ? ( - - ) : null} -
    -
  • -
    - ); - }} -
    + onClick={() => this.handleNodeClick(node)} + className={nodeButtonClasses}> + {showExpansionArrows && node.children ? ( + + ) : null} + {node.icon && !node.useEmptyIcon ? ( + + {this.isNodeOpen(node) && + node.iconWhenExpanded + ? node.iconWhenExpanded + : node.icon} + + ) : null} + {node.useEmptyIcon && !node.icon ? ( + + ) : null} + + {node.label} + + +
    + this.onChildrenKeydown(event, index) + }> + {node.children && this.isNodeOpen(node) ? ( + + ) : null} +
    + + + ); + }} +
    + )} + ); })}