Skip to content

Commit

Permalink
[EuiTreeView] [EuiTreeViewNode] changed label PropType in tree_view t…
Browse files Browse the repository at this point in the history
…o ReactNode (#4352)

* changed label type from string to ReactNode

* Clean up node.label use; improve EuiI18n value interpolation of undefined values

Co-authored-by: Chandler Prall <[email protected]>
  • Loading branch information
Dishebh and chandlerprall authored Dec 15, 2020
1 parent 05b3de4 commit ecd065b
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 96 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,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**

Expand Down
3 changes: 2 additions & 1 deletion src/components/context/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> = ReactChild | ((values: T) => ReactChild);
Expand Down
9 changes: 9 additions & 0 deletions src/components/i18n/i18n_util.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
9 changes: 6 additions & 3 deletions src/components/i18n/i18n_util.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
200 changes: 108 additions & 92 deletions src/components/tree_view/tree_view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>('');
const treeIdGenerator = htmlIdGenerator('euiTreeView');
Expand All @@ -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;
Expand Down Expand Up @@ -279,101 +280,116 @@ export class EuiTreeView extends Component<EuiTreeViewProps, EuiTreeViewState> {
const buttonId = `${this.state.treeID}--${index}--node`;

return (
<EuiI18n
key={node.label + index}
token="euiTreeView.ariaLabel"
default="{nodeLabel} child of {ariaLabel}"
values={{
nodeLabel: node.label,
ariaLabel: hasAriaLabel(rest) ? rest['aria-label'] : '',
}}>
{(ariaLabel: string) => {
const label:
| { 'aria-label': string }
| { 'aria-labelledby': string } = hasAriaLabel(rest)
? {
'aria-label': ariaLabel,
}
: {
'aria-labelledby': `${buttonId} ${rest['aria-labelledby']}`,
};
<EuiInnerText
key={node.id + index}
fallback={typeof node.label === 'string' ? node.label : ''}>
{(ref, innerText) => (
<EuiI18n
key={node.id + index}
token="euiTreeView.ariaLabel"
default="{nodeLabel} child of {ariaLabel}"
values={{
nodeLabel: innerText,
ariaLabel: hasAriaLabel(rest) ? rest['aria-label'] : '',
}}>
{(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 (
<React.Fragment>
<li className={nodeClasses}>
<button
id={buttonId}
aria-controls={`euiNestedTreeView-${this.state.treeID}`}
aria-expanded={this.isNodeOpen(node)}
ref={(ref) => this.setButtonRef(ref, index)}
data-test-subj={`euiTreeViewButton-${this.state.treeID}`}
onKeyDown={(event: React.KeyboardEvent) =>
this.onKeyDown(event, node)
}
onClick={() => this.handleNodeClick(node)}
className={nodeButtonClasses}>
{showExpansionArrows && node.children ? (
<EuiIcon
className="euiTreeView__expansionArrow"
size={display === 'compressed' ? 's' : 'm'}
type={
this.isNodeOpen(node)
? 'arrowDown'
: 'arrowRight'
return (
<React.Fragment>
<li className={nodeClasses}>
<button
id={buttonId}
aria-controls={`euiNestedTreeView-${this.state.treeID}`}
aria-expanded={this.isNodeOpen(node)}
ref={(ref) => this.setButtonRef(ref, index)}
data-test-subj={`euiTreeViewButton-${this.state.treeID}`}
onKeyDown={(event: React.KeyboardEvent) =>
this.onKeyDown(event, node)
}
/>
) : null}
{node.icon && !node.useEmptyIcon ? (
<span className="euiTreeView__iconWrapper">
{this.isNodeOpen(node) && node.iconWhenExpanded
? node.iconWhenExpanded
: node.icon}
</span>
) : null}
{node.useEmptyIcon && !node.icon ? (
<span className="euiTreeView__iconPlaceholder" />
) : null}
<span className="euiTreeView__nodeLabel">
{node.label}
</span>
</button>
<div
id={`euiNestedTreeView-${this.state.treeID}`}
onKeyDown={(event: React.KeyboardEvent) =>
this.onChildrenKeydown(event, index)
}>
{node.children && this.isNodeOpen(node) ? (
<EuiTreeView
items={node.children}
display={display}
showExpansionArrows={showExpansionArrows}
expandByDefault={this.state.expandChildNodes}
{...label}
/>
) : null}
</div>
</li>
</React.Fragment>
);
}}
</EuiI18n>
onClick={() => this.handleNodeClick(node)}
className={nodeButtonClasses}>
{showExpansionArrows && node.children ? (
<EuiIcon
className="euiTreeView__expansionArrow"
size={display === 'compressed' ? 's' : 'm'}
type={
this.isNodeOpen(node)
? 'arrowDown'
: 'arrowRight'
}
/>
) : null}
{node.icon && !node.useEmptyIcon ? (
<span className="euiTreeView__iconWrapper">
{this.isNodeOpen(node) &&
node.iconWhenExpanded
? node.iconWhenExpanded
: node.icon}
</span>
) : null}
{node.useEmptyIcon && !node.icon ? (
<span className="euiTreeView__iconPlaceholder" />
) : null}
<span
ref={ref}
className="euiTreeView__nodeLabel">
{node.label}
</span>
</button>
<div
id={`euiNestedTreeView-${this.state.treeID}`}
onKeyDown={(event: React.KeyboardEvent) =>
this.onChildrenKeydown(event, index)
}>
{node.children && this.isNodeOpen(node) ? (
<EuiTreeView
items={node.children}
display={display}
showExpansionArrows={showExpansionArrows}
expandByDefault={
this.state.expandChildNodes
}
{...label}
/>
) : null}
</div>
</li>
</React.Fragment>
);
}}
</EuiI18n>
)}
</EuiInnerText>
);
})}
</ul>
Expand Down

0 comments on commit ecd065b

Please sign in to comment.