mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Search: use Card component (#29892)
* Search: Use Card component for search items * Search: Set min height of search item * Search: Adjust item height * Search: Move tags to the right * Search: Center item content * Search: Align tags * Search: Adjust Card spacing * Search: Adjust dimensions
This commit is contained in:
parent
77ef9abeed
commit
0fceca5f73
@ -114,12 +114,14 @@ export const Card: CardInterface = ({
|
|||||||
<CardInner href={href}>
|
<CardInner href={href}>
|
||||||
{figure}
|
{figure}
|
||||||
<div className={styles.inner}>
|
<div className={styles.inner}>
|
||||||
<div className={styles.heading} role="heading">
|
<div className={styles.info}>
|
||||||
{heading}
|
<div className={styles.heading} role="heading">
|
||||||
|
{heading}
|
||||||
|
{tags}
|
||||||
|
</div>
|
||||||
|
{meta}
|
||||||
|
{description && <p className={styles.description}>{description}</p>}
|
||||||
</div>
|
</div>
|
||||||
{meta}
|
|
||||||
{tags}
|
|
||||||
{description && <p className={styles.description}>{description}</p>}
|
|
||||||
{hasActions && (
|
{hasActions && (
|
||||||
<div className={styles.actionRow}>
|
<div className={styles.actionRow}>
|
||||||
{actions}
|
{actions}
|
||||||
@ -178,20 +180,37 @@ export const getContainerStyles = stylesFactory((theme: GrafanaTheme, disabled =
|
|||||||
export const getCardStyles = stylesFactory((theme: GrafanaTheme) => {
|
export const getCardStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
return {
|
return {
|
||||||
inner: css`
|
inner: css`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
flex-wrap: wrap;
|
||||||
`,
|
`,
|
||||||
heading: css`
|
heading: css`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
font-size: ${theme.typography.size.md};
|
font-size: ${theme.typography.size.md};
|
||||||
line-height: ${theme.typography.lineHeight.xs};
|
line-height: ${theme.typography.lineHeight.xs};
|
||||||
`,
|
`,
|
||||||
|
info: css`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
`,
|
||||||
metadata: css`
|
metadata: css`
|
||||||
|
width: 100%;
|
||||||
font-size: ${theme.typography.size.sm};
|
font-size: ${theme.typography.size.sm};
|
||||||
color: ${theme.colors.textSemiWeak};
|
color: ${theme.colors.textSemiWeak};
|
||||||
margin: ${theme.spacing.sm} 0 0;
|
margin: ${theme.spacing.xs} 0 0;
|
||||||
line-height: ${theme.typography.lineHeight.xs};
|
line-height: ${theme.typography.lineHeight.xs};
|
||||||
`,
|
`,
|
||||||
description: css`
|
description: css`
|
||||||
|
width: 100%;
|
||||||
margin: ${theme.spacing.sm} 0 0;
|
margin: ${theme.spacing.sm} 0 0;
|
||||||
color: ${theme.colors.textSemiWeak};
|
color: ${theme.colors.textSemiWeak};
|
||||||
line-height: ${theme.typography.lineHeight.md};
|
line-height: ${theme.typography.lineHeight.md};
|
||||||
@ -202,6 +221,10 @@ export const getCardStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
& > * {
|
& > * {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
actionRow: css`
|
actionRow: css`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -233,7 +256,7 @@ export const getCardStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
`,
|
`,
|
||||||
tagList: css`
|
tagList: css`
|
||||||
margin-top: ${theme.spacing.sm};
|
max-width: 50%;
|
||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -248,8 +271,21 @@ const Tags: FC<ChildProps> = ({ children, styles }) => {
|
|||||||
};
|
};
|
||||||
Tags.displayName = 'Tags';
|
Tags.displayName = 'Tags';
|
||||||
|
|
||||||
const Figure: FC<ChildProps> = ({ children, styles }) => {
|
const Figure: FC<ChildProps & { align?: 'top' | 'center' }> = ({ children, styles, align = 'top' }) => {
|
||||||
return <div className={styles?.media}>{children}</div>;
|
return (
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
styles?.media,
|
||||||
|
align === 'center' &&
|
||||||
|
css`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Figure.displayName = 'Figure';
|
Figure.displayName = 'Figure';
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { FC, memo } from 'react';
|
import React, { FC, memo } from 'react';
|
||||||
import { cx, css } from 'emotion';
|
import { css, cx } from 'emotion';
|
||||||
import { OnTagClick, Tag } from './Tag';
|
import { OnTagClick, Tag } from './Tag';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@ -29,11 +29,11 @@ const getStyles = () => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: -6px;
|
||||||
|
justify-content: flex-end;
|
||||||
`,
|
`,
|
||||||
tag: css`
|
tag: css`
|
||||||
&:not(:first-child) {
|
margin: 0 0 6px 6px;
|
||||||
margin-left: 6px;
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -19,12 +19,14 @@ export const SearchCheckbox: FC<Props> = memo(({ onClick, checked = false, edita
|
|||||||
});
|
});
|
||||||
|
|
||||||
const getStyles = stylesFactory(() => ({
|
const getStyles = stylesFactory(() => ({
|
||||||
// Vertically align absolutely positioned checkbox element
|
|
||||||
wrapper: css`
|
wrapper: css`
|
||||||
height: 21px;
|
height: 21px;
|
||||||
margin-right: 12px;
|
|
||||||
& > label {
|
& > label {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
|
& > input {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
}));
|
}));
|
||||||
|
@ -1,26 +1,23 @@
|
|||||||
import React, { FC, useCallback, CSSProperties } from 'react';
|
import React, { FC, useCallback } from 'react';
|
||||||
import { css, cx } from 'emotion';
|
import { css } from 'emotion';
|
||||||
import { GrafanaTheme } from '@grafana/data';
|
|
||||||
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
|
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
|
||||||
import { useTheme, TagList, styleMixins, stylesFactory } from '@grafana/ui';
|
import { TagList, Card, useStyles } from '@grafana/ui';
|
||||||
|
import { GrafanaTheme } from '@grafana/data';
|
||||||
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';
|
import { SEARCH_ITEM_HEIGHT } from '../constants';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
item: DashboardSectionItem;
|
item: DashboardSectionItem;
|
||||||
editable?: boolean;
|
editable?: boolean;
|
||||||
onTagSelected: (name: string) => any;
|
onTagSelected: (name: string) => any;
|
||||||
onToggleChecked?: OnToggleChecked;
|
onToggleChecked?: OnToggleChecked;
|
||||||
style?: CSSProperties;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectors = e2eSelectors.pages.Dashboards;
|
const selectors = e2eSelectors.pages.Dashboards;
|
||||||
|
|
||||||
export const SearchItem: FC<Props> = ({ item, editable, onToggleChecked, onTagSelected, style }) => {
|
export const SearchItem: FC<Props> = ({ item, editable, onToggleChecked, onTagSelected }) => {
|
||||||
const theme = useTheme();
|
const styles = useStyles(getStyles);
|
||||||
const styles = getResultsItemStyles(theme);
|
|
||||||
|
|
||||||
const tagSelected = useCallback((tag: string, event: React.MouseEvent<HTMLElement>) => {
|
const tagSelected = useCallback((tag: string, event: React.MouseEvent<HTMLElement>) => {
|
||||||
onTagSelected(tag);
|
onTagSelected(tag);
|
||||||
}, []);
|
}, []);
|
||||||
@ -36,71 +33,28 @@ export const SearchItem: FC<Props> = ({ item, editable, onToggleChecked, onTagSe
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<Card
|
||||||
style={style}
|
|
||||||
aria-label={selectors.dashboards(item.title)}
|
aria-label={selectors.dashboards(item.title)}
|
||||||
className={cx(styles.wrapper, { [styles.selected]: item.selected })}
|
heading={item.title}
|
||||||
|
href={item.url}
|
||||||
|
style={{ minHeight: SEARCH_ITEM_HEIGHT }}
|
||||||
|
className={styles.container}
|
||||||
>
|
>
|
||||||
<SearchCheckbox editable={editable} checked={item.checked} onClick={toggleItem} />
|
<Card.Figure align={'center'}>
|
||||||
|
<SearchCheckbox editable={editable} checked={item.checked} onClick={toggleItem} />
|
||||||
<a href={item.url} className={styles.link}>
|
</Card.Figure>
|
||||||
<div className={styles.body}>
|
{item.folderTitle && <Card.Meta>{item.folderTitle}</Card.Meta>}
|
||||||
<span>{item.title}</span>
|
<Card.Tags>
|
||||||
<span className={styles.folderTitle}>{item.folderTitle}</span>
|
<TagList tags={item.tags} onClick={tagSelected} />
|
||||||
</div>
|
</Card.Tags>
|
||||||
</a>
|
</Card>
|
||||||
<TagList tags={item.tags} onClick={tagSelected} className={styles.tags} />
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getResultsItemStyles = stylesFactory((theme: GrafanaTheme) => ({
|
const getStyles = (theme: GrafanaTheme) => {
|
||||||
wrapper: css`
|
return {
|
||||||
${styleMixins.listItem(theme)};
|
container: css`
|
||||||
display: flex;
|
padding: ${theme.spacing.sm} ${theme.spacing.md};
|
||||||
align-items: center;
|
`,
|
||||||
height: ${SEARCH_ITEM_HEIGHT}px;
|
};
|
||||||
margin-bottom: ${SEARCH_ITEM_MARGIN}px;
|
};
|
||||||
padding: 0 ${theme.spacing.md};
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: ${SEARCH_ITEM_MARGIN * 2}px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
selected: css`
|
|
||||||
${styleMixins.listItemSelected(theme)};
|
|
||||||
`,
|
|
||||||
body: css`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
flex: 1 1 auto;
|
|
||||||
overflow: hidden;
|
|
||||||
`,
|
|
||||||
folderTitle: css`
|
|
||||||
color: ${theme.colors.textWeak};
|
|
||||||
font-size: ${theme.typography.size.sm};
|
|
||||||
line-height: ${theme.typography.lineHeight.sm};
|
|
||||||
`,
|
|
||||||
icon: css`
|
|
||||||
margin-left: 10px;
|
|
||||||
`,
|
|
||||||
tags: css`
|
|
||||||
flex-grow: 0;
|
|
||||||
justify-content: flex-end;
|
|
||||||
@media only screen and (max-width: ${theme.breakpoints.md}) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
link: css`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
flex-grow: 1;
|
|
||||||
height: 100%;
|
|
||||||
`,
|
|
||||||
}));
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
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 SEARCH_ITEM_HEIGHT = 48;
|
export const SEARCH_ITEM_HEIGHT = 62;
|
||||||
export const SEARCH_ITEM_MARGIN = 4;
|
export const SEARCH_ITEM_MARGIN = 8;
|
||||||
export const DEFAULT_SORT = { label: 'A-Z', value: 'alpha-asc' };
|
export const DEFAULT_SORT = { label: 'A-Z', value: 'alpha-asc' };
|
||||||
export const SECTION_STORAGE_KEY = 'search.sections';
|
export const SECTION_STORAGE_KEY = 'search.sections';
|
||||||
export const GENERAL_FOLDER_ID = 0;
|
export const GENERAL_FOLDER_ID = 0;
|
||||||
|
Loading…
Reference in New Issue
Block a user