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

useInnerText; Fix EuiListGroupItem title attrs #2100

Merged
merged 18 commits into from
Jul 15, 2019
Merged
Show file tree
Hide file tree
Changes from 7 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
3 changes: 3 additions & 0 deletions src-docs/src/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ import { IconExample } from './views/icon/icon_example';

import { ImageExample } from './views/image/image_example';

import { InnerTextExample } from './views/inner_text/inner_text_example';

import { KeyPadMenuExample } from './views/key_pad_menu/key_pad_menu_example';

import { LinkExample } from './views/link/link_example';
Expand Down Expand Up @@ -371,6 +373,7 @@ const navigation = [
ErrorBoundaryExample,
FocusTrapExample,
HighlightExample,
InnerTextExample,
I18nExample,
IsColorDarkExample,
MutationObserverExample,
Expand Down
42 changes: 42 additions & 0 deletions src-docs/src/views/inner_text/inner_text.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';

import { EuiInnerText } from '../../../../src/components/inner_text';

import {
EuiBadge,
EuiCode,
EuiHighlight,
EuiIcon,
EuiSpacer,
} from '../../../../src/components';

export default () => (
<React.Fragment>
<EuiInnerText>
{(ref, innerText) => (
<React.Fragment>
<span ref={ref} title={innerText}>
Simple string content
</span>
<EuiIcon type="sortRight" />
<EuiCode>{innerText}</EuiCode>
</React.Fragment>
)}
</EuiInnerText>

<EuiSpacer />

<EuiInnerText>
{(ref, innerText) => (
<React.Fragment>
<span ref={ref} title={innerText}>
<EuiHighlight search="content">EuiHighlight content </EuiHighlight>
<EuiBadge>with EuiBadge</EuiBadge>
</span>
<EuiIcon type="sortRight" />
<EuiCode>{innerText}</EuiCode>
</React.Fragment>
)}
</EuiInnerText>
</React.Fragment>
);
86 changes: 86 additions & 0 deletions src-docs/src/views/inner_text/inner_text_example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import React from 'react';

import { renderToHtml } from '../../services';

import { GuideSectionTypes } from '../../components';

import {
EuiCode,
EuiCodeBlock,
EuiSpacer,
EuiText,
EuiTitle,
} from '../../../../src/components';

import InnerText from './inner_text';
const innerTextSource = require('!!raw-loader!./inner_text');
const innerTextHtml = renderToHtml(InnerText);
const innerTextSnippet = `// useInnerText
const [ref, innerText] = useInnerText();
<span ref={ref} title={innerText}>
Content
</span>

// EuiInnerText
thompsongl marked this conversation as resolved.
Show resolved Hide resolved
<EuiInnerText>
{(ref, innerText) => (
<span ref={ref} title={innerText}>
Content
</span>
)}
</EuiInnerText>`;

export const InnerTextExample = {
title: 'Inner Text',
intro: (
<React.Fragment>
<EuiText>
<p>
For instances where accessing the text content of a component that may
be wrapped or interspersed with other components, two utilities are
available:
</p>
<ul>
<li>
<EuiCode>useInnerText</EuiCode> - A custom React hook, usable in
function components
</li>
<li>
<EuiCode>EuiInnerText</EuiCode> - A higher order{' '}
<EuiCode>useInnerText</EuiCode> component for use in class
components
</li>
</ul>
<p>
Both utilities make available a <EuiCode>ref</EuiCode> reference to
add to the target DOM element, and the resulting{' '}
<EuiCode>innerText</EuiCode> value to use as needed.
</p>
<EuiTitle>
<h2>Usage</h2>
</EuiTitle>
</EuiText>
<EuiCodeBlock language="js" paddingSize="m">
{innerTextSnippet}
</EuiCodeBlock>
<EuiSpacer />
</React.Fragment>
),
sections: [
{
title: 'Rendered',
source: [
{
type: GuideSectionTypes.JS,
code: innerTextSource,
},
{
type: GuideSectionTypes.HTML,
code: innerTextHtml,
},
],
demo: <InnerText />,
snippet: innerTextSnippet,
},
],
};
27 changes: 27 additions & 0 deletions src-docs/src/views/list_group/list_group_example.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ import ListGroupLinkActions from './list_group_link_actions';
const listGroupLinkActionsSource = require('!!raw-loader!./list_group_link_actions');
const listGroupLinkActionsHtml = renderToHtml(ListGroupLinkActions);

import ListGroupExtra from './list_group_extra';
const listGroupExtraSource = require('!!raw-loader!./list_group_extra');
const listGroupExtraHtml = renderToHtml(ListGroupExtra);

export const ListGroupExample = {
title: 'List Group',
sections: [
Expand Down Expand Up @@ -95,5 +99,28 @@ export const ListGroupExample = {
),
demo: <ListGroupLinkActions />,
},
{
title: 'Text truncation and wrapping ',
thompsongl marked this conversation as resolved.
Show resolved Hide resolved
source: [
{
type: GuideSectionTypes.JS,
code: listGroupExtraSource,
},
{
type: GuideSectionTypes.HTML,
code: listGroupExtraHtml,
},
],
text: (
<p>
By default, truncation occurs for long list items. In such cases a{' '}
<EuiCode>title</EuiCode> attribute with a value matching the text
content of the item is added for readability and accessibility. If{' '}
<EuiCode>showToolTip</EuiCode> or <EuiCode>wrapLines</EuiCode> are
used, the attribute will not be added.
</p>
),
demo: <ListGroupExtra />,
},
],
};
55 changes: 55 additions & 0 deletions src-docs/src/views/list_group/list_group_extra.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, { Fragment } from 'react';

import {
EuiListGroup,
EuiListGroupItem,
EuiSpacer,
} from '../../../../src/components';

export default () => (
<Fragment>
<EuiListGroup>
thompsongl marked this conversation as resolved.
Show resolved Hide resolved
<EuiListGroupItem label="We use defaults" />

<EuiListGroupItem
label={
<span>
A very, very long item that <strong>will surely</strong> force
truncation
</span>
}
/>
</EuiListGroup>

<EuiSpacer />

<EuiListGroup>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these other two can be combined into one group even though it will still show a tooltip on the wrapped lines one.

  • First item
  • Second item
  • Third very, very long item that will surely force truncation
  • Fourth very, very long item with wrapping enabled that will not force truncation

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TY! Just one more thing, I promise...

Can you add two snippets for this example?

<EuiListGroup showToolTips>...</EuiListGroup>
<EuiListGroupItem
  wrapText
  label={label}
/>

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thompsongl Did you see my last comment on here? ^^

<EuiListGroupItem wrapText label="We wrap lines" />

<EuiListGroupItem
wrapText
label={
<span>
A very, very long item that <strong>will surely</strong> force
wrapping
</span>
}
/>
</EuiListGroup>

<EuiSpacer />

<EuiListGroup showToolTips>
<EuiListGroupItem label="We use tooltips" />

<EuiListGroupItem
label={
<span>
A very, very long item that <strong>will surely</strong> force
truncation
</span>
}
/>
</EuiListGroup>
</Fragment>
);
6 changes: 5 additions & 1 deletion src/components/filter_group/filter_button.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { COLORS, ICON_SIDES, EuiButtonEmpty } from '../button/button_empty';

import { IconPropType } from '../icon';

import { useInnerText } from '../inner_text';

export const EuiFilterButton = ({
children,
className,
Expand Down Expand Up @@ -53,12 +55,14 @@ export const EuiFilterButton = ({
dataText = children;
}

const [ref, innerText] = useInnerText();
const buttonContents = (
<Fragment>
<span
ref={ref}
className="euiFilterButton__textShift"
data-text={dataText}
thompsongl marked this conversation as resolved.
Show resolved Hide resolved
title={dataText}>
title={dataText || innerText}>
{children}
</span>

Expand Down
2 changes: 2 additions & 0 deletions src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ export { ICON_TYPES, EuiIcon } from './icon';

export { EuiImage } from './image';

export { useInnerText, EuiInnerText } from './inner_text';

export { EuiI18n, EuiI18nNumber } from './i18n';

export {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`EuiInnerText is rendered 1`] = `
<span>
Test
</span>
`;
1 change: 1 addition & 0 deletions src/components/inner_text/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { useInnerText, EuiInnerText } from './inner_text';
43 changes: 43 additions & 0 deletions src/components/inner_text/inner_text.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';
import {
render,
// mount
} from 'enzyme';
import {
// findTestSubject,
requiredProps,
} from '../../test';

import { EuiInnerText } from './inner_text';

describe('EuiInnerText', () => {
test('is rendered', () => {
const component = render(
<EuiInnerText {...requiredProps}>
{(ref, innerText) => (
<span ref={ref} title={innerText}>
Test
</span>
)}
</EuiInnerText>
);

expect(component).toMatchSnapshot();
});

// test('uses innerText', () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Working on getting these types of tests to run

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still stumped. I'm not actually sure if Enzyme can handle it.

// const text = 'Test';
// const component = mount(
// <EuiInnerText {...requiredProps}>
// {(ref, innerText) => (
// <span ref={ref} title={innerText} data-test-subj="span">
// {text}
// </span>
// )}
// </EuiInnerText>
// );
//
// const span = findTestSubject(component, 'span');
// expect(span.props().title).toBe(text);
// });
});
30 changes: 30 additions & 0 deletions src/components/inner_text/inner_text.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {
FunctionComponent,
ReactElement,
Ref,
RefObject,
useEffect,
useRef,
useState,
} from 'react';

export function useInnerText(
innerTextFallback?: string
): [RefObject<HTMLElement>, string | undefined] {
const ref = useRef<HTMLElement>(null);
const [innerText, setInnerText] = useState(innerTextFallback);
useEffect(() => {
if (ref && ref.current) {
setInnerText(ref.current.innerText);
}
}, [ref.current]);

return [ref, innerText];
}

export const EuiInnerText: FunctionComponent<{
children: (ref?: Ref<HTMLElement>, innerText?: string) => ReactElement;
}> = ({ children }) => {
const [ref, innerText] = useInnerText();
return children(ref, innerText);
};
11 changes: 9 additions & 2 deletions src/components/list_group/list_group_item.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import classNames from 'classnames';
import { EuiButtonIcon } from '../button';
import { IconPropType, EuiIcon } from '../icon';
import { EuiToolTip } from '../tool_tip';
import { useInnerText } from '../inner_text';

const sizeToClassNameMap = {
xs: 'euiListGroupItem--xSmall',
Expand Down Expand Up @@ -79,12 +80,18 @@ export const EuiListGroupItem = ({
}

// Only add the label as the title attribute if it's possibly truncated
const labelContent = (
// Also ensure the value of the title attribute is a string
const [ref, innerText] = useInnerText();
const shouldRenderTitle = !wrapText && !showToolTip;
const labelContent = shouldRenderTitle ? (
<span
ref={ref}
className="euiListGroupItem__label"
title={wrapText ? undefined : label}>
title={typeof label === 'string' ? label : innerText}>
thompsongl marked this conversation as resolved.
Show resolved Hide resolved
{label}
</span>
) : (
<span className="euiListGroupItem__label">{label}</span>
);

// Handle the variety of interaction behavior
Expand Down