grafana/public/app/features/search/components/SearchItem.tsx
Alex Khomenko fb8a555f19
Search/virtualize list (#23710)
* Search: Add FixedSizeList for result items

* Search: Move SectionHeader to a separate file

* Search: Add useListHeight hook

* Search: Fix horizontal scrollbar

* Search: Remove custom scrollbar

* Search: Do not fetch dashboard folder on route change

* Search: Update tests

* Search: Remove extra checkbox renders

* Search: Move wrapper ref outside search results

* Search: Fix param type

* Search: Fix merge conflicts

* Search: Virtualize dashboard list

* Search: Update layout

* Search: Pass wrapper to search results

* Search: Update dashboard redirect

* Search: Remove unused css

* Search: Revert config

* Search: Use AutoSizer

* Search: Remove redundant appEvents call

* Search: Use List layout in folder view
2020-04-24 11:08:06 +03:00

128 lines
3.5 KiB
TypeScript

import React, { FC, useCallback, useRef, useEffect, CSSProperties } from 'react';
import { css, cx } from 'emotion';
import { GrafanaTheme } from '@grafana/data';
import { e2e } from '@grafana/e2e';
import { Icon, useTheme, TagList, styleMixins, stylesFactory } from '@grafana/ui';
import { updateLocation } from 'app/core/reducers/location';
import { DashboardSectionItem, OnToggleChecked } from '../types';
import { SearchCheckbox } from './SearchCheckbox';
export interface Props {
item: DashboardSectionItem;
editable?: boolean;
onTagSelected: (name: string) => any;
onToggleChecked?: OnToggleChecked;
style?: CSSProperties;
}
const { selectors } = e2e.pages.Dashboards;
export const SearchItem: FC<Props> = ({ item, editable, onToggleChecked, onTagSelected, style }) => {
const theme = useTheme();
const styles = getResultsItemStyles(theme);
const inputEl = useRef<HTMLInputElement>(null);
useEffect(() => {
const preventDef = (event: MouseEvent) => {
// manually prevent default on TagList click, as doing it via normal onClick doesn't work inside angular
event.preventDefault();
};
if (inputEl.current) {
inputEl.current.addEventListener('click', preventDef);
}
return () => {
inputEl.current!.removeEventListener('click', preventDef);
};
}, []);
const onItemClick = () => {
//Check if one string can be found in the other
if (window.location.pathname.includes(item.url) || item.url.includes(window.location.pathname)) {
updateLocation({
query: { search: null },
partial: true,
});
}
};
const tagSelected = useCallback((tag: string, event: React.MouseEvent<HTMLElement>) => {
onTagSelected(tag);
}, []);
const toggleItem = useCallback(
(event: React.MouseEvent) => {
event.preventDefault();
if (onToggleChecked) {
onToggleChecked(item);
}
},
[item]
);
return (
<li
style={style}
aria-label={selectors.dashboards(item.title)}
className={cx(styles.wrapper, { [styles.selected]: item.selected })}
>
<SearchCheckbox editable={editable} checked={item.checked} onClick={toggleItem} />
<a href={item.url} className={styles.link}>
<Icon className={styles.icon} name="apps" size="lg" />
<div className={styles.body} onClick={onItemClick}>
<span>{item.title}</span>
<span className={styles.folderTitle}>{item.folderTitle}</span>
</div>
<span ref={inputEl}>
<TagList tags={item.tags} onClick={tagSelected} className={styles.tags} />
</span>
</a>
</li>
);
};
const getResultsItemStyles = stylesFactory((theme: GrafanaTheme) => ({
wrapper: css`
${styleMixins.listItem(theme)};
display: flex;
align-items: center;
padding: 0 ${theme.spacing.sm};
min-height: 37px;
:hover {
cursor: pointer;
}
`,
selected: css`
${styleMixins.listItemSelected(theme)};
`,
body: css`
display: flex;
flex-direction: column;
justify-content: center;
flex: 1 1 auto;
overflow: hidden;
padding: 0 10px;
`,
folderTitle: css`
color: ${theme.colors.textWeak};
font-size: ${theme.typography.size.xs};
line-height: ${theme.typography.lineHeight.xs};
position: relative;
top: -1px;
`,
icon: css`
margin-left: 10px;
`,
tags: css`
justify-content: flex-end;
@media only screen and (max-width: ${theme.breakpoints.md}) {
display: none;
}
`,
link: css`
display: flex;
align-items: center;
width: 100%;
`,
}));