Skip to content

Commit

Permalink
update usage of selectable
Browse files Browse the repository at this point in the history
  • Loading branch information
shahzad31 committed Sep 22, 2020
1 parent a0ca1e1 commit a5c8775
Show file tree
Hide file tree
Showing 4 changed files with 263 additions and 108 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Then(`it displays top pages in the suggestion popover`, () => {
listOfUrls.should('have.length', 5);

const actualUrlsText = [
'http://opbeans-node:3000/dashboardPage views: 17Page load duration: 109 ms Select',
'http://opbeans-node:3000/dashboardPage views: 17Page load duration: 109 ms ',
'http://opbeans-node:3000/ordersPage views: 14Page load duration: 72 ms',
];

Expand All @@ -41,7 +41,7 @@ When(`a user enters a query in url search field`, () => {
cy.get('kbnLoadingIndicator').should('not.be.visible');

cy.get('[data-cy=csmUrlFilter]').within(() => {
cy.get('input.euiSelectableTemplateSitewide__search').type('cus');
cy.get('input.euiSelectableSearch').type('cus');
});

cy.get('kbnLoadingIndicator').should('not.be.visible');
Expand All @@ -55,7 +55,7 @@ Then(`it should filter results based on query`, () => {
listOfUrls.should('have.length', 1);

const actualUrlsText = [
'http://opbeans-node:3000/customersPage views: 10Page load duration: 76 ms Select',
'http://opbeans-node:3000/customersPage views: 10Page load duration: 76 ms ',
];

cy.get('li.euiSelectableListItem')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React, { ReactNode } from 'react';
import classNames from 'classnames';
import { EuiHighlight, EuiSelectableOption } from '@elastic/eui';
import styled from 'styled-components';
import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';

const StyledSpan = styled.span`
color: ${euiLightVars.euiColorSecondaryText};
font-weight: 500;
:not(:last-of-type)::after {
content: '•';
margin: 0 4px;
}
`;

const StyledListSpan = styled.span`
display: block;
margin-top: 4px;
font-size: 12px;
`;
export type UrlOption<T = { [key: string]: any }> = {
meta?: string[];
} & EuiSelectableOption<T>;

export const formatOptions = (options: EuiSelectableOption[]) => {
return options.map((item: EuiSelectableOption) => ({
title: item.label,
...item,
className: classNames(
'euiSelectableTemplateSitewide__listItem',
item.className
),
}));
};

export function selectableRenderOptions(
option: UrlOption,
searchValue: string
) {
return (
<>
<EuiHighlight
className="euiSelectableTemplateSitewide__listItemTitle"
search={searchValue}
>
{option.label}
</EuiHighlight>
{renderOptionMeta(option.meta)}
</>
);
}

function renderOptionMeta(meta?: string[]): ReactNode {
if (!meta || meta.length < 1) return;
return (
<StyledListSpan>
{meta.map((item: string) => (
<StyledSpan key={item}>{item}</StyledSpan>
))}
</StyledListSpan>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React, { FormEvent, useRef, useState } from 'react';
import {
EuiButtonEmpty,
EuiFlexGroup,
EuiFlexItem,
EuiLoadingSpinner,
EuiPopover,
EuiPopoverTitle,
EuiSelectable,
EuiSelectableMessage,
} from '@elastic/eui';
import {
formatOptions,
selectableRenderOptions,
UrlOption,
} from './RenderOption';
import { I18LABELS } from '../../translations';

interface Props {
data: {
items: UrlOption[];
total?: number;
};
loading: boolean;
onInputChange: (e: FormEvent<HTMLInputElement>) => void;
onTermChange: () => void;
onChange: (updatedOptions: UrlOption[]) => void;
searchValue: string;
onClose: () => void;
}

export function SelectableUrlList({
data,
loading,
onInputChange,
onTermChange,
onChange,
searchValue,
onClose,
}: Props) {
const [popoverIsOpen, setPopoverIsOpen] = useState(false);
const [popoverRef, setPopoverRef] = useState<HTMLElement | null>(null);
const [searchRef, setSearchRef] = useState<HTMLInputElement | null>(null);

const titleRef = useRef();

const searchOnFocus = (e: React.FocusEvent<HTMLInputElement>) => {
setPopoverIsOpen(true);
};

const onSearchInput = (e: React.FormEvent<HTMLInputElement>) => {
onInputChange(e);
setPopoverIsOpen(true);
};

const searchOnBlur = (e: React.FocusEvent<HTMLInputElement>) => {
if (
!popoverRef?.contains(e.relatedTarget as HTMLElement) &&
!popoverRef?.contains(titleRef.current as HTMLElement)
) {
setPopoverIsOpen(false);
}
};

const formattedOptions = formatOptions(data.items ?? []);

const closePopover = () => {
setPopoverIsOpen(false);
onClose();
if (searchRef) {
searchRef.blur();
}
};

const loadingMessage = (
<EuiSelectableMessage style={{ minHeight: 300 }}>
<EuiLoadingSpinner size="l" />
<br />
<p>Loading results</p>
</EuiSelectableMessage>
);

const emptyMessage = (
<EuiSelectableMessage style={{ minHeight: 300 }}>
<p>No results available</p>
</EuiSelectableMessage>
);

const titleText = searchValue
? I18LABELS.getSearchResultsLabel(data?.total ?? 0)
: I18LABELS.topPages;

function PopOverTitle() {
return (
<EuiPopoverTitle>
<EuiFlexGroup ref={titleRef}>
<EuiFlexItem style={{ justifyContent: 'center' }}>
{loading ? <EuiLoadingSpinner /> : titleText}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
size="s"
disabled={!searchValue}
onClick={() => {
onTermChange();
setPopoverIsOpen(false);
}}
>
{I18LABELS.matchThisQuery}
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPopoverTitle>
);
}

return (
<EuiSelectable
searchable
onChange={onChange}
isLoading={loading}
options={formattedOptions}
renderOption={selectableRenderOptions}
singleSelection={false}
searchProps={{
placeholder: I18LABELS.searchByUrl,
isClearable: true,
onFocus: searchOnFocus,
onBlur: searchOnBlur,
onInput: onSearchInput,
inputRef: setSearchRef,
}}
listProps={{
rowHeight: 68,
showIcons: true,
}}
loadingMessage={loadingMessage}
emptyMessage={emptyMessage}
noMatchesMessage={emptyMessage}
>
{(list, search) => (
<EuiPopover
panelPaddingSize="none"
isOpen={popoverIsOpen}
display={'block'}
panelRef={setPopoverRef}
button={search}
closePopover={closePopover}
>
<div style={{ width: 600, maxWidth: '100%' }}>
<PopOverTitle />
{list}
</div>
</EuiPopover>
)}
</EuiSelectable>
);
}
Loading

0 comments on commit a5c8775

Please sign in to comment.