Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EuiTreeView] [EuiTreeViewNode] changed label PropType in tree_view to ReactNode #4352

Merged
merged 2 commits into from
Dec 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**

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