Skip to content

Commit

Permalink
[EuiSelectable] isVirtualized and data (#5521)
Browse files Browse the repository at this point in the history
* isVirtualized and labelProps

* update docs

* CL

* review feedback

* update list types

* labelProps -> data

* Update docs with virtualization toggle and hide overflow of selectable item

Co-authored-by: cchaos <caroline.horn@elastic.co>
  • Loading branch information
thompsongl and cchaos authored Jan 11, 2022
1 parent d4f7bfb commit d26becf
Show file tree
Hide file tree
Showing 12 changed files with 484 additions and 50 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## [`main`](https://github.com/elastic/eui/tree/main)

- Added virtulized rendering option to `EuiSelectableList` with `isVirtualized` ([#5521](https://github.com/elastic/eui/pull/5521))
- Added expanded option properties to `EuiSelectableOption` with `data` ([#5521](https://github.com/elastic/eui/pull/5521))

**Breaking changes**

- Changed `EuiSearchBar` to preserve phrases with leading and trailing spaces, instead of dropping surrounding whitespace ([#5514](https://github.com/elastic/eui/pull/5514))
Expand Down
35 changes: 26 additions & 9 deletions src-docs/src/views/selectable/selectable_custom_render.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { createDataStore } from '../tables/data_store';

export default () => {
const [useCustomContent, setUseCustomContent] = useState(false);
const [isVirtualized, setIsVirtualized] = useState(true);

const countries = createDataStore().countries.map((country) => {
return {
Expand All @@ -20,6 +21,9 @@ export default () => {
prepend: country.flag,
append: <EuiBadge>{country.code}</EuiBadge>,
showIcons: false,
data: {
secondaryContent: 'I am secondary content, I am!',
},
};
});

Expand All @@ -38,49 +42,62 @@ export default () => {
setUseCustomContent(e.currentTarget.checked);
};

const onVirtualized = (e) => {
setIsVirtualized(e.currentTarget.checked);
};

const renderCountryOption = (option, searchValue) => {
return (
<>
<EuiHighlight search={searchValue}>{option.label}</EuiHighlight>
<br />
<EuiTextColor color="subdued">
{/* <br /> */}
<EuiTextColor style={{ display: 'block' }} color="subdued">
<small>
<EuiHighlight search={searchValue}>
I am secondary content, I am!
{option.secondaryContent}
</EuiHighlight>
</small>
</EuiTextColor>
</>
);
};

let listProps = {
isVirtualized,
};

let customProps;
if (useCustomContent) {
customProps = {
height: 240,
renderOption: renderCountryOption,
listProps: {
rowHeight: 50,
showIcons: false,
},
};
listProps = {
rowHeight: 50,
isVirtualized,
};
}

return (
<>
<EuiSwitch
label="Virtualized"
checked={isVirtualized}
onChange={onVirtualized}
/>{' '}
&emsp;
<EuiSwitch
label="Custom content"
checked={useCustomContent}
onChange={onCustom}
/>

<EuiSpacer />

<EuiSelectable
aria-label="Selectable example with custom list items"
searchable
options={options}
onChange={onChange}
listProps={listProps}
{...customProps}
>
{(list, search) => (
Expand Down
30 changes: 23 additions & 7 deletions src-docs/src/views/selectable/selectable_example.js
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,20 @@ export const SelectableExample = {
similar to a title. Add one of these by setting the{' '}
<EuiCode>option.isGroupLabel</EuiCode> to true.{' '}
</p>
<h3>Row height and virtualization</h3>
<p>
When virtualization is on,{' '}
<strong>every row must be the same height</strong> in order for the
list to know how to scroll to the selected or highlighted option. It
applies the <EuiCode>listProps.rowHeight</EuiCode> (in pixels)
directly to each option hiding any overflow.
</p>
<p>
If <EuiCode>listProps.isVirtualized</EuiCode> is set to{' '}
<EuiCode>false</EuiCode>, each row will fit its contents and removes
all scrolling. Therefore, we recommend having a large enough
container to accomodate all optons.
</p>
<h3>Custom content</h3>
<p>
While it is best to stick to the{' '}
Expand All @@ -357,15 +371,17 @@ export const SelectableExample = {
<EuiCode>searchValue</EuiCode> to use for highlighting.
</p>
<p>
In order for the list to know how to scroll to the selected or
highlighted option, it must also know the height of the rows. It
applies this pixel height directly to options. If your custom
content is taller than the default of <EuiCode>32px</EuiCode> tall,
you will need to recalculate this height and apply it via{' '}
<EuiCode>listProps.rowHeight</EuiCode>.
To provide data that can be used by the{' '}
<EuiCode>renderOption</EuiCode> function that does not match the
standard option API, use <EuiCode>option.data</EuiCode> which will
make custom data available in the <EuiCode>option</EuiCode>{' '}
parameter. See the <EuiCode>secondaryContent</EuiCode> configuration
in the following example.
</p>
<p>
<strong>Every row must be the same height.</strong>
Also, if your custom content is taller than the default{' '}
<EuiCode>listProps.rowHeight</EuiCode> of <EuiCode>32px</EuiCode>{' '}
tall, you will need to pass in a custom value to this prop.
</p>
</Fragment>
),
Expand Down
102 changes: 102 additions & 0 deletions src/components/selectable/__snapshots__/selectable.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,107 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`EuiSelectable custom options with data 1`] = `
<div
class="euiSelectable"
>
<div
class="euiSelectableList"
>
<div
data-eui="EuiAutoSizer"
>
<div
class="euiSelectableList__list"
style="position:relative;height:96px;width:600px;overflow:auto;-webkit-overflow-scrolling:touch;will-change:transform;direction:ltr"
>
<ul
style="height:96px;width:100%"
>
<li
aria-posinset="1"
aria-selected="false"
aria-setsize="3"
class="euiSelectableListItem"
id="generated-id_listbox_option-0"
role="option"
style="position:absolute;left:0;top:0;height:32px;width:100%"
title="Titan"
>
<span
class="euiSelectableListItem__content"
>
<span
class="euiSelectableListItem__icon"
data-euiicon-type="empty"
/>
<span
class="euiSelectableListItem__text"
>
<span>
VI: Titan
</span>
</span>
</span>
</li>
<li
aria-posinset="2"
aria-selected="false"
aria-setsize="3"
class="euiSelectableListItem"
id="generated-id_listbox_option-1"
role="option"
style="position:absolute;left:0;top:32px;height:32px;width:100%"
title="Enceladus"
>
<span
class="euiSelectableListItem__content"
>
<span
class="euiSelectableListItem__icon"
data-euiicon-type="empty"
/>
<span
class="euiSelectableListItem__text"
>
<span>
II: Enceladus
</span>
</span>
</span>
</li>
<li
aria-posinset="3"
aria-selected="false"
aria-setsize="3"
class="euiSelectableListItem"
id="generated-id_listbox_option-2"
role="option"
style="position:absolute;left:0;top:64px;height:32px;width:100%"
title="Pandora is one of Saturn's moons, named for a Titaness of Greek mythology"
>
<span
class="euiSelectableListItem__content"
>
<span
class="euiSelectableListItem__icon"
data-euiicon-type="empty"
/>
<span
class="euiSelectableListItem__text"
>
<span>
XVII: Pandora is one of Saturn's moons, named for a Titaness of Greek mythology
</span>
</span>
</span>
</li>
</ul>
</div>
</div>
</div>
</div>
`;

exports[`EuiSelectable is rendered 1`] = `
<div
class="euiSelectable testClass1 testClass2"
Expand Down
43 changes: 43 additions & 0 deletions src/components/selectable/selectable.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -208,5 +208,48 @@ describe('EuiSelectable', () => {
(component.find('EuiSelectableList').props() as any).visibleOptions
).toEqual(options);
});

test('with data', () => {
type WithData = {
numeral?: string;
};
const options = [
{
label: 'Titan',
data: {
numeral: 'VI',
},
},
{
label: 'Enceladus',
data: {
numeral: 'II',
},
},
{
label:
"Pandora is one of Saturn's moons, named for a Titaness of Greek mythology",
data: {
numeral: 'XVII',
},
},
];
const component = render(
<EuiSelectable<WithData>
options={options}
renderOption={(option) => {
return (
<span>
{option.numeral}: {option.label}
</span>
);
}}
>
{(list) => list}
</EuiSelectable>
);

expect(component).toMatchSnapshot();
});
});
});
23 changes: 21 additions & 2 deletions src/components/selectable/selectable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ import classNames from 'classnames';
import { CommonProps, ExclusiveUnion } from '../common';
import { EuiSelectableSearch } from './selectable_search';
import { EuiSelectableMessage } from './selectable_message';
import { EuiSelectableList } from './selectable_list';
import {
EuiSelectableList,
EuiSelectableOptionsListVirtualizedProps,
} from './selectable_list';
import { EuiLoadingSpinner } from '../loading';
import { EuiSpacer } from '../spacer';
import { getMatchingOptions } from './matching_options';
Expand Down Expand Up @@ -434,8 +437,23 @@ export class EuiSelectable<T = {}> extends Component<
const {
'aria-label': listAriaLabel,
'aria-describedby': listAriaDescribedby,
isVirtualized,
rowHeight,
...cleanedListProps
} = listProps || unknownAccessibleName;
} = (listProps || unknownAccessibleName) as typeof listProps &
typeof unknownAccessibleName;

let virtualizedProps: EuiSelectableOptionsListVirtualizedProps;

if (isVirtualized === false) {
virtualizedProps = {
isVirtualized,
};
} else if (rowHeight != null) {
virtualizedProps = {
rowHeight,
};
}

const classes = classNames(
'euiSelectable',
Expand Down Expand Up @@ -629,6 +647,7 @@ export class EuiSelectable<T = {}> extends Component<
? listAccessibleName
: searchable && { 'aria-label': placeholderName })}
{...cleanedListProps}
{...virtualizedProps}
/>
)}
</EuiI18n>
Expand Down
Loading

0 comments on commit d26becf

Please sign in to comment.