mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Search: Improving search look and feel (#23854)
* Search: Improving search look and feel * Fixed issue with tag filter beeing cramped and wrapping tags * Minor tag polish * fixed type
This commit is contained in:
parent
76b184b93c
commit
6f02b51561
@ -236,7 +236,6 @@ export interface GrafanaTheme extends GrafanaThemeCommons {
|
|||||||
formSwitchBgHover: string;
|
formSwitchBgHover: string;
|
||||||
formSwitchBgDisabled: string;
|
formSwitchBgDisabled: string;
|
||||||
formSwitchDot: string;
|
formSwitchDot: string;
|
||||||
formCheckboxBg: string;
|
|
||||||
formCheckboxBgChecked: string;
|
formCheckboxBgChecked: string;
|
||||||
formCheckboxBgCheckedHover: string;
|
formCheckboxBgCheckedHover: string;
|
||||||
formCheckboxCheckmark: string;
|
formCheckboxCheckmark: string;
|
||||||
|
@ -50,7 +50,7 @@ export const getCheckboxStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
* */
|
* */
|
||||||
&:checked + span {
|
&:checked + span {
|
||||||
background: blue;
|
background: blue;
|
||||||
background: ${theme.colors.formCheckboxBgChecked};
|
background: ${theme.colors.formInputBg};
|
||||||
border: none;
|
border: none;
|
||||||
&:hover {
|
&:hover {
|
||||||
background: ${theme.colors.formCheckboxBgCheckedHover};
|
background: ${theme.colors.formCheckboxBgCheckedHover};
|
||||||
@ -74,7 +74,7 @@ export const getCheckboxStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
height: ${checkboxSize};
|
height: ${checkboxSize};
|
||||||
border-radius: ${theme.border.radius.sm};
|
border-radius: ${theme.border.radius.sm};
|
||||||
margin-right: ${theme.spacing.formSpacingBase}px;
|
margin-right: ${theme.spacing.formSpacingBase}px;
|
||||||
background: ${theme.colors.formCheckboxBg};
|
background: ${theme.colors.formInputBg};
|
||||||
border: 1px solid ${theme.colors.formInputBorder};
|
border: 1px solid ${theme.colors.formInputBorder};
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 1px;
|
top: 1px;
|
||||||
|
@ -17,7 +17,7 @@ export interface RadioButtonProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getRadioButtonStyles = stylesFactory((theme: GrafanaTheme, size: RadioButtonSize, fullWidth?: boolean) => {
|
const getRadioButtonStyles = stylesFactory((theme: GrafanaTheme, size: RadioButtonSize, fullWidth?: boolean) => {
|
||||||
const { fontSize, height } = getPropertiesForButtonSize({
|
const { fontSize, height, padding } = getPropertiesForButtonSize({
|
||||||
theme,
|
theme,
|
||||||
size,
|
size,
|
||||||
hasIcon: false,
|
hasIcon: false,
|
||||||
@ -25,7 +25,6 @@ const getRadioButtonStyles = stylesFactory((theme: GrafanaTheme, size: RadioButt
|
|||||||
variant: 'secondary',
|
variant: 'secondary',
|
||||||
});
|
});
|
||||||
|
|
||||||
const horizontalPadding = theme.spacing[size] ?? theme.spacing.md;
|
|
||||||
const c = theme.palette;
|
const c = theme.palette;
|
||||||
const textColor = theme.colors.textSemiWeak;
|
const textColor = theme.colors.textSemiWeak;
|
||||||
const textColorHover = theme.colors.text;
|
const textColorHover = theme.colors.text;
|
||||||
@ -75,7 +74,7 @@ const getRadioButtonStyles = stylesFactory((theme: GrafanaTheme, size: RadioButt
|
|||||||
// Deduct border from line-height for perfect vertical centering on windows and linux
|
// Deduct border from line-height for perfect vertical centering on windows and linux
|
||||||
line-height: ${height - 2}px;
|
line-height: ${height - 2}px;
|
||||||
color: ${textColor};
|
color: ${textColor};
|
||||||
padding: 0 ${horizontalPadding};
|
padding: ${padding};
|
||||||
margin-left: -1px;
|
margin-left: -1px;
|
||||||
border-radius: ${theme.border.radius.sm};
|
border-radius: ${theme.border.radius.sm};
|
||||||
border: ${border};
|
border: ${border};
|
||||||
|
@ -45,11 +45,10 @@ const getTagStyles = (theme: GrafanaTheme, name: string, colorIndex?: number) =>
|
|||||||
line-height: ${theme.typography.lineHeight.xs};
|
line-height: ${theme.typography.lineHeight.xs};
|
||||||
vertical-align: baseline;
|
vertical-align: baseline;
|
||||||
background-color: ${colors.color};
|
background-color: ${colors.color};
|
||||||
color: ${theme.palette.white};
|
color: ${theme.colors.textStrong};
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
padding: 3px 6px;
|
padding: 3px 6px;
|
||||||
border: 1px solid ${colors.borderColor};
|
|
||||||
border-radius: ${theme.border.radius.md};
|
border-radius: ${theme.border.radius.md};
|
||||||
|
|
||||||
:hover {
|
:hover {
|
||||||
|
@ -27,12 +27,9 @@ const getStyles = () => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
padding: 10px;
|
|
||||||
`,
|
`,
|
||||||
tag: css`
|
tag: css`
|
||||||
margin-left: 6px;
|
margin-left: 6px;
|
||||||
font-size: 11px;
|
|
||||||
padding: 2px 6px;
|
|
||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -81,7 +81,6 @@ const form = {
|
|||||||
formSwitchBgActiveHover: basicColors.blue80,
|
formSwitchBgActiveHover: basicColors.blue80,
|
||||||
formSwitchBgDisabled: basicColors.gray25,
|
formSwitchBgDisabled: basicColors.gray25,
|
||||||
formSwitchDot: basicColors.gray15,
|
formSwitchDot: basicColors.gray15,
|
||||||
formCheckboxBg: basicColors.dark5,
|
|
||||||
formCheckboxBgChecked: basicColors.blue95,
|
formCheckboxBgChecked: basicColors.blue95,
|
||||||
formCheckboxBgCheckedHover: basicColors.blue80,
|
formCheckboxBgCheckedHover: basicColors.blue80,
|
||||||
formCheckboxCheckmark: basicColors.gray25,
|
formCheckboxCheckmark: basicColors.gray25,
|
||||||
|
@ -80,7 +80,6 @@ const form = {
|
|||||||
formSwitchBgActiveHover: basicColors.blue80,
|
formSwitchBgActiveHover: basicColors.blue80,
|
||||||
formSwitchBgDisabled: basicColors.gray4,
|
formSwitchBgDisabled: basicColors.gray4,
|
||||||
formSwitchDot: basicColors.white,
|
formSwitchDot: basicColors.white,
|
||||||
formCheckboxBg: basicColors.white,
|
|
||||||
formCheckboxBgChecked: basicColors.blue77,
|
formCheckboxBgChecked: basicColors.blue77,
|
||||||
formCheckboxBgCheckedHover: basicColors.blue80,
|
formCheckboxBgCheckedHover: basicColors.blue80,
|
||||||
formCheckboxCheckmark: basicColors.white,
|
formCheckboxCheckmark: basicColors.white,
|
||||||
|
@ -15,11 +15,12 @@ export class TagBadge extends React.Component<Props, any> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { label, removeIcon, count } = this.props;
|
const { label, removeIcon, count } = this.props;
|
||||||
const { color, borderColor } = getTagColorsFromName(label);
|
const { color } = getTagColorsFromName(label);
|
||||||
|
|
||||||
const tagStyle = {
|
const tagStyle = {
|
||||||
backgroundColor: color,
|
backgroundColor: color,
|
||||||
borderColor: borderColor,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const countLabel = count !== 0 && <span className="tag-count-label">{`(${count})`}</span>;
|
const countLabel = count !== 0 && <span className="tag-count-label">{`(${count})`}</span>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -107,17 +107,11 @@ const getStyles = stylesFactory(() => {
|
|||||||
return {
|
return {
|
||||||
tagFilter: css`
|
tagFilter: css`
|
||||||
min-width: 180px;
|
min-width: 180px;
|
||||||
line-height: 22px;
|
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|
||||||
.label-tag {
|
.label-tag {
|
||||||
margin-left: 6px;
|
margin-left: 6px;
|
||||||
font-size: 11px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
.fa.fa-remove {
|
|
||||||
margin-right: 3px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
|
@ -14,7 +14,7 @@ export const TagOption = (props: ExtendedOptionProps) => {
|
|||||||
const { data, className, label } = props;
|
const { data, className, label } = props;
|
||||||
return (
|
return (
|
||||||
<components.Option {...props}>
|
<components.Option {...props}>
|
||||||
<div className={`tag-filter-option btn btn-link ${className || ''}`}>
|
<div className={`tag-filter-option ${className || ''}`}>
|
||||||
<TagBadge label={label} removeIcon={false} count={data.count} />
|
<TagBadge label={label} removeIcon={false} count={data.count} />
|
||||||
</div>
|
</div>
|
||||||
</components.Option>
|
</components.Option>
|
||||||
|
@ -44,7 +44,7 @@ export const ActionRow: FC<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.actionRow}>
|
<div className={styles.actionRow}>
|
||||||
<HorizontalGroup spacing="md" width="100%">
|
<HorizontalGroup spacing="md">
|
||||||
{!hideLayout ? <RadioButtonGroup options={layoutOptions} onChange={onLayoutChange} value={layout} /> : null}
|
{!hideLayout ? <RadioButtonGroup options={layoutOptions} onChange={onLayoutChange} value={layout} /> : null}
|
||||||
<SortPicker onChange={onSortChange} value={query.sort} />
|
<SortPicker onChange={onSortChange} value={query.sort} />
|
||||||
</HorizontalGroup>
|
</HorizontalGroup>
|
||||||
|
@ -192,10 +192,6 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
return {
|
return {
|
||||||
container: css`
|
container: css`
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
.results-container {
|
|
||||||
padding: 5px 0 0;
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
searchField: css`
|
searchField: css`
|
||||||
height: auto;
|
height: auto;
|
||||||
|
@ -22,6 +22,7 @@ const getStyles = stylesFactory(() => ({
|
|||||||
// Vertically align absolutely positioned checkbox element
|
// Vertically align absolutely positioned checkbox element
|
||||||
wrapper: css`
|
wrapper: css`
|
||||||
height: 21px;
|
height: 21px;
|
||||||
|
margin-right: 12px;
|
||||||
& > label {
|
& > label {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow, mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
import { Tag } from '@grafana/ui';
|
import { Tag } from '@grafana/ui';
|
||||||
import { SearchItem, Props } from './SearchItem';
|
import { SearchItem, Props } from './SearchItem';
|
||||||
import { DashboardSearchItemType } from '../types';
|
import { DashboardSearchItemType } from '../types';
|
||||||
@ -17,7 +17,7 @@ const data = {
|
|||||||
checked: false,
|
checked: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const setup = (propOverrides?: Partial<Props>, renderMethod = shallow) => {
|
const setup = (propOverrides?: Partial<Props>) => {
|
||||||
const props: Props = {
|
const props: Props = {
|
||||||
item: data,
|
item: data,
|
||||||
onTagSelected: jest.fn(),
|
onTagSelected: jest.fn(),
|
||||||
@ -28,7 +28,7 @@ const setup = (propOverrides?: Partial<Props>, renderMethod = shallow) => {
|
|||||||
|
|
||||||
Object.assign(props, propOverrides);
|
Object.assign(props, propOverrides);
|
||||||
|
|
||||||
const wrapper = renderMethod(<SearchItem {...props} />);
|
const wrapper = mount(<SearchItem {...props} />);
|
||||||
const instance = wrapper.instance();
|
const instance = wrapper.instance();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -39,14 +39,14 @@ const setup = (propOverrides?: Partial<Props>, renderMethod = shallow) => {
|
|||||||
|
|
||||||
describe('SearchItem', () => {
|
describe('SearchItem', () => {
|
||||||
it('should render the item', () => {
|
it('should render the item', () => {
|
||||||
const { wrapper } = setup();
|
const { wrapper } = setup({});
|
||||||
expect(wrapper.find({ 'aria-label': 'Dashboard search item Test 1' })).toHaveLength(1);
|
expect(wrapper.find({ 'aria-label': 'Dashboard search item Test 1' })).toHaveLength(1);
|
||||||
expect(wrapper.findWhere(comp => comp.type() === 'div' && comp.text() === 'Test 1')).toHaveLength(1);
|
expect(wrapper.findWhere(comp => comp.type() === 'div' && comp.text() === 'Test 1')).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render item's tags", () => {
|
it("should render item's tags", () => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const { wrapper } = setup({}, mount);
|
const { wrapper } = setup({});
|
||||||
expect(wrapper.find(Tag)).toHaveLength(2);
|
expect(wrapper.find(Tag)).toHaveLength(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import React, { FC, useCallback, useRef, useEffect, CSSProperties } from 'react';
|
import React, { FC, useCallback, CSSProperties } from 'react';
|
||||||
import { css, cx } from 'emotion';
|
import { css, cx } from 'emotion';
|
||||||
import { GrafanaTheme } from '@grafana/data';
|
import { GrafanaTheme } from '@grafana/data';
|
||||||
import { e2e } from '@grafana/e2e';
|
import { e2e } from '@grafana/e2e';
|
||||||
import { Icon, useTheme, TagList, styleMixins, stylesFactory } from '@grafana/ui';
|
import { useTheme, TagList, styleMixins, stylesFactory } from '@grafana/ui';
|
||||||
import { updateLocation } from 'app/core/reducers/location';
|
|
||||||
import { DashboardSectionItem, OnToggleChecked } from '../types';
|
import { DashboardSectionItem, OnToggleChecked } from '../types';
|
||||||
import { SearchCheckbox } from './SearchCheckbox';
|
import { SearchCheckbox } from './SearchCheckbox';
|
||||||
|
import { SEARCH_ITEM_HEIGHT, SEARCH_ITEM_MARGIN } from '../constants';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
item: DashboardSectionItem;
|
item: DashboardSectionItem;
|
||||||
@ -20,30 +20,6 @@ const { selectors } = e2e.pages.Dashboards;
|
|||||||
export const SearchItem: FC<Props> = ({ item, editable, onToggleChecked, onTagSelected, style }) => {
|
export const SearchItem: FC<Props> = ({ item, editable, onToggleChecked, onTagSelected, style }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const styles = getResultsItemStyles(theme);
|
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>) => {
|
const tagSelected = useCallback((tag: string, event: React.MouseEvent<HTMLElement>) => {
|
||||||
onTagSelected(tag);
|
onTagSelected(tag);
|
||||||
@ -60,23 +36,21 @@ export const SearchItem: FC<Props> = ({ item, editable, onToggleChecked, onTagSe
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li
|
<div
|
||||||
style={style}
|
style={style}
|
||||||
aria-label={selectors.dashboards(item.title)}
|
aria-label={selectors.dashboards(item.title)}
|
||||||
className={cx(styles.wrapper, { [styles.selected]: item.selected })}
|
className={cx(styles.wrapper, { [styles.selected]: item.selected })}
|
||||||
>
|
>
|
||||||
<SearchCheckbox editable={editable} checked={item.checked} onClick={toggleItem} />
|
<SearchCheckbox editable={editable} checked={item.checked} onClick={toggleItem} />
|
||||||
|
|
||||||
<a href={item.url} className={styles.link}>
|
<a href={item.url} className={styles.link}>
|
||||||
<Icon className={styles.icon} name="apps" size="lg" />
|
<div className={styles.body}>
|
||||||
<div className={styles.body} onClick={onItemClick}>
|
|
||||||
<span>{item.title}</span>
|
<span>{item.title}</span>
|
||||||
<span className={styles.folderTitle}>{item.folderTitle}</span>
|
<span className={styles.folderTitle}>{item.folderTitle}</span>
|
||||||
</div>
|
</div>
|
||||||
<span ref={inputEl}>
|
|
||||||
<TagList tags={item.tags} onClick={tagSelected} className={styles.tags} />
|
|
||||||
</span>
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
<TagList tags={item.tags} onClick={tagSelected} className={styles.tags} />
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -85,8 +59,9 @@ const getResultsItemStyles = stylesFactory((theme: GrafanaTheme) => ({
|
|||||||
${styleMixins.listItem(theme)};
|
${styleMixins.listItem(theme)};
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 ${theme.spacing.sm};
|
height: ${SEARCH_ITEM_HEIGHT}px;
|
||||||
min-height: 37px;
|
margin-bottom: ${SEARCH_ITEM_MARGIN}px;
|
||||||
|
padding: 0 ${theme.spacing.md};
|
||||||
|
|
||||||
:hover {
|
:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -101,7 +76,6 @@ const getResultsItemStyles = stylesFactory((theme: GrafanaTheme) => ({
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 0 10px;
|
|
||||||
`,
|
`,
|
||||||
folderTitle: css`
|
folderTitle: css`
|
||||||
color: ${theme.colors.textWeak};
|
color: ${theme.colors.textWeak};
|
||||||
@ -114,6 +88,7 @@ const getResultsItemStyles = stylesFactory((theme: GrafanaTheme) => ({
|
|||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
`,
|
`,
|
||||||
tags: css`
|
tags: css`
|
||||||
|
flex-grow: 0;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
@media only screen and (max-width: ${theme.breakpoints.md}) {
|
@media only screen and (max-width: ${theme.breakpoints.md}) {
|
||||||
display: none;
|
display: none;
|
||||||
@ -122,6 +97,7 @@ const getResultsItemStyles = stylesFactory((theme: GrafanaTheme) => ({
|
|||||||
link: css`
|
link: css`
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
flex-shrink: 0;
|
||||||
|
flex-grow: 1;
|
||||||
`,
|
`,
|
||||||
}));
|
}));
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { css, cx } from 'emotion';
|
import { css } from 'emotion';
|
||||||
import { FixedSizeList } from 'react-window';
|
import { FixedSizeList } from 'react-window';
|
||||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||||
import { GrafanaTheme } from '@grafana/data';
|
import { GrafanaTheme } from '@grafana/data';
|
||||||
import { stylesFactory, useTheme, Spinner } from '@grafana/ui';
|
import { stylesFactory, useTheme, Spinner } from '@grafana/ui';
|
||||||
import { DashboardSection, OnToggleChecked, SearchLayout } from '../types';
|
import { DashboardSection, OnToggleChecked, SearchLayout } from '../types';
|
||||||
import { getVisibleItems } from '../utils';
|
import { getVisibleItems } from '../utils';
|
||||||
import { ITEM_HEIGHT } from '../constants';
|
import { SEARCH_ITEM_HEIGHT, SEARCH_ITEM_MARGIN } from '../constants';
|
||||||
import { SearchItem } from './SearchItem';
|
import { SearchItem } from './SearchItem';
|
||||||
import { SectionHeader } from './SectionHeader';
|
import { SectionHeader } from './SectionHeader';
|
||||||
|
|
||||||
@ -35,18 +35,18 @@ export const SearchResults: FC<Props> = ({
|
|||||||
|
|
||||||
const renderFolders = () => {
|
const renderFolders = () => {
|
||||||
return (
|
return (
|
||||||
<ul className={styles.wrapper}>
|
<div className={styles.wrapper}>
|
||||||
{results.map(section => {
|
{results.map(section => {
|
||||||
return (
|
return (
|
||||||
<li aria-label="Search section" className={styles.section} key={section.title}>
|
<div aria-label="Search section" className={styles.section} key={section.title}>
|
||||||
<SectionHeader onSectionClick={onToggleSection} {...{ onToggleChecked, editable, section }} />
|
<SectionHeader onSectionClick={onToggleSection} {...{ onToggleChecked, editable, section }} />
|
||||||
<ul aria-label="Search items">
|
<div aria-label="Search items" className={styles.sectionItems}>
|
||||||
{section.expanded && section.items.map(item => <SearchItem key={item.id} {...itemProps} item={item} />)}
|
{section.expanded && section.items.map(item => <SearchItem key={item.id} {...itemProps} item={item} />)}
|
||||||
</ul>
|
</div>
|
||||||
</li>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ul>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -54,24 +54,26 @@ export const SearchResults: FC<Props> = ({
|
|||||||
|
|
||||||
const renderDashboards = () => {
|
const renderDashboards = () => {
|
||||||
return (
|
return (
|
||||||
<AutoSizer disableWidth>
|
<div className={styles.listModeWrapper}>
|
||||||
{({ height }) => (
|
<AutoSizer disableWidth>
|
||||||
<FixedSizeList
|
{({ height }) => (
|
||||||
aria-label="Search items"
|
<FixedSizeList
|
||||||
className={styles.wrapper}
|
aria-label="Search items"
|
||||||
innerElementType="ul"
|
className={styles.wrapper}
|
||||||
itemSize={ITEM_HEIGHT}
|
innerElementType="ul"
|
||||||
height={height}
|
itemSize={SEARCH_ITEM_HEIGHT + SEARCH_ITEM_MARGIN}
|
||||||
itemCount={items.length}
|
height={height}
|
||||||
width="100%"
|
itemCount={items.length}
|
||||||
>
|
width="100%"
|
||||||
{({ index, style }) => {
|
>
|
||||||
const item = items[index];
|
{({ index, style }) => {
|
||||||
return <SearchItem key={item.id} {...itemProps} item={item} style={style} />;
|
const item = items[index];
|
||||||
}}
|
return <SearchItem key={item.id} {...itemProps} item={item} style={style} />;
|
||||||
</FixedSizeList>
|
}}
|
||||||
)}
|
</FixedSizeList>
|
||||||
</AutoSizer>
|
)}
|
||||||
|
</AutoSizer>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -80,24 +82,28 @@ export const SearchResults: FC<Props> = ({
|
|||||||
} else if (!results || !results.length) {
|
} else if (!results || !results.length) {
|
||||||
return <h6>No dashboards matching your query were found.</h6>;
|
return <h6>No dashboards matching your query were found.</h6>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cx('results-container', styles.resultsContainer)}>
|
<div className={styles.resultsContainer}>{layout !== SearchLayout.List ? renderFolders() : renderDashboards()}</div>
|
||||||
{layout !== SearchLayout.List ? renderFolders() : renderDashboards()}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSectionStyles = stylesFactory((theme: GrafanaTheme) => {
|
const getSectionStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
const { xs, sm, md } = theme.spacing;
|
const { md } = theme.spacing;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
wrapper: css`
|
wrapper: css`
|
||||||
list-style: none;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
`,
|
`,
|
||||||
section: css`
|
section: css`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
background: ${theme.colors.panelBg};
|
background: ${theme.colors.panelBg};
|
||||||
border-bottom: solid 1px ${theme.isLight ? theme.palette.gray95 : theme.palette.gray25};
|
border-bottom: solid 1px ${theme.colors.border2};
|
||||||
padding: 0px ${xs} ${xs};
|
`,
|
||||||
margin-bottom: 3px;
|
sectionItems: css`
|
||||||
|
margin: 0 24px 0 32px;
|
||||||
`,
|
`,
|
||||||
spinner: css`
|
spinner: css`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -106,14 +112,18 @@ const getSectionStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
min-height: 100px;
|
min-height: 100px;
|
||||||
`,
|
`,
|
||||||
resultsContainer: css`
|
resultsContainer: css`
|
||||||
padding: ${sm};
|
|
||||||
position: relative;
|
position: relative;
|
||||||
flex-grow: 10;
|
flex-grow: 10;
|
||||||
margin-bottom: ${md};
|
margin-bottom: ${md};
|
||||||
background: ${theme.palette.gray10};
|
background: ${theme.colors.bg1};
|
||||||
border: 1px solid ${theme.palette.gray15};
|
border: 1px solid ${theme.colors.border1};
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
`,
|
`,
|
||||||
|
listModeWrapper: css`
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
padding: ${md};
|
||||||
|
`,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -19,7 +19,7 @@ export const SectionHeader: FC<SectionHeaderProps> = ({
|
|||||||
editable = false,
|
editable = false,
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const styles = getSectionHeaderStyles(theme, section.selected);
|
const styles = getSectionHeaderStyles(theme, section.selected, editable);
|
||||||
|
|
||||||
const onSectionExpand = () => {
|
const onSectionExpand = () => {
|
||||||
onSectionClick(section);
|
onSectionClick(section);
|
||||||
@ -39,7 +39,10 @@ export const SectionHeader: FC<SectionHeaderProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className={styles.wrapper} onClick={onSectionExpand}>
|
<div className={styles.wrapper} onClick={onSectionExpand}>
|
||||||
<SearchCheckbox editable={editable} checked={section.checked} onClick={onSectionChecked} />
|
<SearchCheckbox editable={editable} checked={section.checked} onClick={onSectionChecked} />
|
||||||
<Icon className={styles.icon} name={section.icon as IconName} />
|
|
||||||
|
<div className={styles.icon}>
|
||||||
|
<Icon name={section.icon as IconName} />
|
||||||
|
</div>
|
||||||
|
|
||||||
<span className={styles.text}>{section.title}</span>
|
<span className={styles.text}>{section.title}</span>
|
||||||
{section.url && (
|
{section.url && (
|
||||||
@ -52,15 +55,15 @@ export const SectionHeader: FC<SectionHeaderProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSectionHeaderStyles = stylesFactory((theme: GrafanaTheme, selected = false) => {
|
const getSectionHeaderStyles = stylesFactory((theme: GrafanaTheme, selected = false, editable: boolean) => {
|
||||||
const { sm, xs } = theme.spacing;
|
const { sm } = theme.spacing;
|
||||||
return {
|
return {
|
||||||
wrapper: cx(
|
wrapper: cx(
|
||||||
css`
|
css`
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: ${theme.typography.size.base};
|
font-size: ${theme.typography.size.base};
|
||||||
padding: ${sm} ${xs} ${xs};
|
padding: 12px;
|
||||||
color: ${theme.colors.textWeak};
|
color: ${theme.colors.textWeak};
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
@ -78,7 +81,7 @@ const getSectionHeaderStyles = stylesFactory((theme: GrafanaTheme, selected = fa
|
|||||||
{ selected }
|
{ selected }
|
||||||
),
|
),
|
||||||
icon: css`
|
icon: css`
|
||||||
width: 43px;
|
padding: 0 ${sm} 0 ${editable ? 0 : sm};
|
||||||
`,
|
`,
|
||||||
text: css`
|
text: css`
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
export const NO_ID_SECTIONS = ['Recent', 'Starred'];
|
export const NO_ID_SECTIONS = ['Recent', 'Starred'];
|
||||||
// Height of the search result item
|
// Height of the search result item
|
||||||
export const ITEM_HEIGHT = 40;
|
export const SEARCH_ITEM_HEIGHT = 48;
|
||||||
|
export const SEARCH_ITEM_MARGIN = 4;
|
||||||
export const DEFAULT_SORT = { label: 'A-Z', value: 'alpha-asc' };
|
export const DEFAULT_SORT = { label: 'A-Z', value: 'alpha-asc' };
|
||||||
|
@ -20,9 +20,14 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
font-size: 13px;
|
font-size: 12px;
|
||||||
padding: 0px 6px;
|
padding: 0px 6px;
|
||||||
border: 1px solid lighten($purple, 10%);
|
line-height: 20px;
|
||||||
|
height: 20px;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.icon-tag {
|
.icon-tag {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -46,30 +46,30 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tag-filter {
|
.tag-filter {
|
||||||
line-height: 22px;
|
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|
||||||
.label-tag {
|
.label-tag {
|
||||||
margin-left: 6px;
|
margin-left: 6px;
|
||||||
font-size: 11px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
.fa.fa-remove {
|
.fa.fa-remove {
|
||||||
margin-right: 3px;
|
margin-right: 3px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.tag-filter-option {
|
.tag-filter-option {
|
||||||
position: relative;
|
position: relative;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: block;
|
display: block;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
cursor: pointer;
|
||||||
|
padding: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
.tag-count-label {
|
.tag-count-label {
|
||||||
margin-left: 3px;
|
margin-left: 3px;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-filter-values {
|
.tag-filter-values {
|
||||||
|
Loading…
Reference in New Issue
Block a user