mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
NestedFolders: Style tweaks (#68532)
* Center align cell items * Change empty folder message + style * Use new Text components in TypeCell * Use new components in Move and Delete modals * fix test * Change spinner in DescendantsCount to the same font size as the text to prevent layout shift
This commit is contained in:
parent
dc656ecd8a
commit
e17f676a98
@ -4,17 +4,13 @@ import React from 'react';
|
|||||||
import { stylesFactory } from '../../themes';
|
import { stylesFactory } from '../../themes';
|
||||||
import { Icon } from '../Icon/Icon';
|
import { Icon } from '../Icon/Icon';
|
||||||
|
|
||||||
const getStyles = stylesFactory((size: number, inline: boolean) => {
|
const getStyles = stylesFactory((size: number | string, inline: boolean) => {
|
||||||
return {
|
return css([
|
||||||
wrapper: css`
|
{
|
||||||
font-size: ${size}px;
|
fontSize: typeof size === 'string' ? size : `${size}px`,
|
||||||
${inline
|
},
|
||||||
? css`
|
inline && { display: 'inline-block' },
|
||||||
display: inline-block;
|
]);
|
||||||
`
|
|
||||||
: ''}
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
@ -22,7 +18,7 @@ export type Props = {
|
|||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
iconClassName?: string;
|
iconClassName?: string;
|
||||||
inline?: boolean;
|
inline?: boolean;
|
||||||
size?: number;
|
size?: number | string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,7 +27,7 @@ export type Props = {
|
|||||||
export const Spinner = ({ className, inline = false, iconClassName, style, size = 16 }: Props) => {
|
export const Spinner = ({ className, inline = false, iconClassName, style, size = 16 }: Props) => {
|
||||||
const styles = getStyles(size, inline);
|
const styles = getStyles(size, inline);
|
||||||
return (
|
return (
|
||||||
<div data-testid="Spinner" style={style} className={cx(styles.wrapper, className)}>
|
<div data-testid="Spinner" style={style} className={cx(styles, className)}>
|
||||||
<Icon className={cx('fa-spin', iconClassName)} name="fa fa-spinner" />
|
<Icon className={cx('fa-spin', iconClassName)} name="fa fa-spinner" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -3,6 +3,7 @@ import { sortBy } from 'lodash';
|
|||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import { Space } from '@grafana/experimental';
|
||||||
import { config } from '@grafana/runtime';
|
import { config } from '@grafana/runtime';
|
||||||
import { Button, useStyles2 } from '@grafana/ui';
|
import { Button, useStyles2 } from '@grafana/ui';
|
||||||
import { SlideDown } from 'app/core/components/Animations/SlideDown';
|
import { SlideDown } from 'app/core/components/Animations/SlideDown';
|
||||||
@ -143,6 +144,7 @@ export const Permissions = ({
|
|||||||
$all: false,
|
$all: false,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Space v={2} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{canSetPermissions && (
|
{canSetPermissions && (
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { css } from '@emotion/css';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { Space } from '@grafana/experimental';
|
||||||
import { ConfirmModal, useStyles2 } from '@grafana/ui';
|
import { ConfirmModal } from '@grafana/ui';
|
||||||
|
import { P } from '@grafana/ui/src/unstable';
|
||||||
|
|
||||||
import { DashboardTreeSelection } from '../../types';
|
import { DashboardTreeSelection } from '../../types';
|
||||||
|
|
||||||
@ -16,8 +16,6 @@ export interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const DeleteModal = ({ onConfirm, onDismiss, selectedItems, ...props }: Props) => {
|
export const DeleteModal = ({ onConfirm, onDismiss, selectedItems, ...props }: Props) => {
|
||||||
const styles = useStyles2(getStyles);
|
|
||||||
|
|
||||||
const onDelete = () => {
|
const onDelete = () => {
|
||||||
onConfirm();
|
onConfirm();
|
||||||
onDismiss();
|
onDismiss();
|
||||||
@ -26,10 +24,11 @@ export const DeleteModal = ({ onConfirm, onDismiss, selectedItems, ...props }: P
|
|||||||
return (
|
return (
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
body={
|
body={
|
||||||
<div className={styles.modalBody}>
|
<>
|
||||||
This action will delete the following content:
|
<P>This action will delete the following content:</P>
|
||||||
<DescendantCount selectedItems={selectedItems} />
|
<DescendantCount selectedItems={selectedItems} />
|
||||||
</div>
|
<Space v={2} />
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
confirmationText="Delete"
|
confirmationText="Delete"
|
||||||
confirmText="Delete"
|
confirmText="Delete"
|
||||||
@ -40,9 +39,3 @@ export const DeleteModal = ({ onConfirm, onDismiss, selectedItems, ...props }: P
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getStyles = (theme: GrafanaTheme2) => ({
|
|
||||||
modalBody: css({
|
|
||||||
...theme.typography.body,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { css } from '@emotion/css';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { Alert, Spinner, useTheme2 } from '@grafana/ui';
|
||||||
import { Alert, Spinner, useStyles2 } from '@grafana/ui';
|
import { P } from '@grafana/ui/src/unstable';
|
||||||
|
|
||||||
import { useGetAffectedItemsQuery } from '../../api/browseDashboardsAPI';
|
import { useGetAffectedItemsQuery } from '../../api/browseDashboardsAPI';
|
||||||
import { DashboardTreeSelection } from '../../types';
|
import { DashboardTreeSelection } from '../../types';
|
||||||
@ -14,24 +13,17 @@ export interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const DescendantCount = ({ selectedItems }: Props) => {
|
export const DescendantCount = ({ selectedItems }: Props) => {
|
||||||
const styles = useStyles2(getStyles);
|
const theme = useTheme2();
|
||||||
const { data, isFetching, isLoading, error } = useGetAffectedItemsQuery(selectedItems);
|
const { data, isFetching, isLoading, error } = useGetAffectedItemsQuery(selectedItems);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.breakdown}>
|
<>
|
||||||
<>
|
{data && (
|
||||||
{data && buildBreakdownString(data.folder, data.dashboard, data.libraryPanel, data.alertRule)}
|
<P color="secondary">{buildBreakdownString(data.folder, data.dashboard, data.libraryPanel, data.alertRule)}</P>
|
||||||
{(isFetching || isLoading) && <Spinner size={12} />}
|
)}
|
||||||
{error && <Alert severity="error" title="Unable to retrieve descendant information" />}
|
|
||||||
</>
|
{(isFetching || isLoading) && <Spinner size={theme.typography.body.fontSize} />}
|
||||||
</div>
|
{error && <Alert severity="error" title="Unable to retrieve descendant information" />}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getStyles = (theme: GrafanaTheme2) => ({
|
|
||||||
breakdown: css({
|
|
||||||
...theme.typography.bodySmall,
|
|
||||||
color: theme.colors.text.secondary,
|
|
||||||
marginBottom: theme.spacing(2),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
import { Space } from '@grafana/experimental';
|
||||||
import { Alert, Button, Field, Modal } from '@grafana/ui';
|
import { Alert, Button, Field, Modal } from '@grafana/ui';
|
||||||
|
import { P } from '@grafana/ui/src/unstable';
|
||||||
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
|
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
|
||||||
|
|
||||||
import { DashboardTreeSelection } from '../../types';
|
import { DashboardTreeSelection } from '../../types';
|
||||||
@ -28,11 +30,17 @@ export const MoveModal = ({ onConfirm, onDismiss, selectedItems, ...props }: Pro
|
|||||||
return (
|
return (
|
||||||
<Modal title="Move" onDismiss={onDismiss} {...props}>
|
<Modal title="Move" onDismiss={onDismiss} {...props}>
|
||||||
{selectedFolders.length > 0 && <Alert severity="warning" title="Moving this item may change its permissions." />}
|
{selectedFolders.length > 0 && <Alert severity="warning" title="Moving this item may change its permissions." />}
|
||||||
This action will move the following content:
|
|
||||||
|
<P>This action will move the following content:</P>
|
||||||
|
|
||||||
<DescendantCount selectedItems={selectedItems} />
|
<DescendantCount selectedItems={selectedItems} />
|
||||||
|
|
||||||
|
<Space v={3} />
|
||||||
|
|
||||||
<Field label="Folder name">
|
<Field label="Folder name">
|
||||||
<FolderPicker allowEmpty onChange={({ uid }) => setMoveTarget(uid)} />
|
<FolderPicker allowEmpty onChange={({ uid }) => setMoveTarget(uid)} />
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Modal.ButtonRow>
|
<Modal.ButtonRow>
|
||||||
<Button onClick={onDismiss} variant="secondary" fill="outline">
|
<Button onClick={onDismiss} variant="secondary" fill="outline">
|
||||||
Cancel
|
Cancel
|
||||||
|
@ -112,7 +112,7 @@ describe('browse-dashboards DashboardsTree', () => {
|
|||||||
onAllSelectionChange={noop}
|
onAllSelectionChange={noop}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
expect(screen.queryByText('Empty folder')).toBeInTheDocument();
|
expect(screen.queryByText('No items')).toBeInTheDocument();
|
||||||
expect(screen.queryByText(emptyFolderIndicator.item.kind)).not.toBeInTheDocument();
|
expect(screen.queryByText(emptyFolderIndicator.item.kind)).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -204,9 +204,9 @@ const getStyles = (theme: GrafanaTheme2) => {
|
|||||||
|
|
||||||
cell: css({
|
cell: css({
|
||||||
padding: theme.spacing(1),
|
padding: theme.spacing(1),
|
||||||
whiteSpace: 'nowrap',
|
overflow: 'hidden', // Required so flex children can do text-overflow: ellipsis
|
||||||
overflow: 'hidden',
|
display: 'flex',
|
||||||
textOverflow: 'ellipsis',
|
alignItems: 'center',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
link: css({
|
link: css({
|
||||||
|
@ -5,6 +5,7 @@ import { CellProps } from 'react-table';
|
|||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { IconButton, Link, useStyles2 } from '@grafana/ui';
|
import { IconButton, Link, useStyles2 } from '@grafana/ui';
|
||||||
import { getSvgSize } from '@grafana/ui/src/components/Icon/utils';
|
import { getSvgSize } from '@grafana/ui/src/components/Icon/utils';
|
||||||
|
import { Span, TextModifier } from '@grafana/ui/src/unstable';
|
||||||
|
|
||||||
import { DashboardsTreeItem } from '../types';
|
import { DashboardsTreeItem } from '../types';
|
||||||
|
|
||||||
@ -23,7 +24,9 @@ export function NameCell({ row: { original: data }, onFolderClick }: NameCellPro
|
|||||||
<>
|
<>
|
||||||
<Indent level={level} />
|
<Indent level={level} />
|
||||||
<span className={styles.folderButtonSpacer} />
|
<span className={styles.folderButtonSpacer} />
|
||||||
<em>Empty folder</em>
|
<em>
|
||||||
|
<TextModifier color="secondary">No items</TextModifier>
|
||||||
|
</em>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -45,13 +48,15 @@ export function NameCell({ row: { original: data }, onFolderClick }: NameCellPro
|
|||||||
<span className={styles.folderButtonSpacer} />
|
<span className={styles.folderButtonSpacer} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{item.url ? (
|
<Span variant="body" truncate>
|
||||||
<Link href={item.url} className={styles.link}>
|
{item.url ? (
|
||||||
{item.title}
|
<Link href={item.url} className={styles.link}>
|
||||||
</Link>
|
{item.title}
|
||||||
) : (
|
</Link>
|
||||||
item.title
|
) : (
|
||||||
)}
|
item.title
|
||||||
|
)}
|
||||||
|
</Span>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,45 +1,35 @@
|
|||||||
import { css } from '@emotion/css';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { CellProps } from 'react-table';
|
import { CellProps } from 'react-table';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { Icon } from '@grafana/ui';
|
||||||
import { Icon, useStyles2 } from '@grafana/ui';
|
import { TextModifier } from '@grafana/ui/src/unstable';
|
||||||
import { getIconForKind } from 'app/features/search/service/utils';
|
import { getIconForKind } from 'app/features/search/service/utils';
|
||||||
|
|
||||||
import { DashboardsTreeItem } from '../types';
|
import { DashboardsTreeItem } from '../types';
|
||||||
|
|
||||||
export function TypeCell({ row: { original: data } }: CellProps<DashboardsTreeItem, unknown>) {
|
export function TypeCell({ row: { original: data } }: CellProps<DashboardsTreeItem, unknown>) {
|
||||||
const styles = useStyles2(getStyles);
|
|
||||||
const iconName = getIconForKind(data.item.kind);
|
const iconName = getIconForKind(data.item.kind);
|
||||||
|
|
||||||
switch (data.item.kind) {
|
switch (data.item.kind) {
|
||||||
case 'dashboard':
|
case 'dashboard':
|
||||||
return (
|
return (
|
||||||
<span className={styles.text}>
|
<TextModifier color="secondary">
|
||||||
<Icon name={iconName} /> Dashboard
|
<Icon name={iconName} /> Dashboard
|
||||||
</span>
|
</TextModifier>
|
||||||
);
|
);
|
||||||
case 'folder':
|
case 'folder':
|
||||||
return (
|
return (
|
||||||
<span className={styles.text}>
|
<TextModifier color="secondary">
|
||||||
<Icon name={iconName} /> Folder
|
<Icon name={iconName} /> Folder
|
||||||
</span>
|
</TextModifier>
|
||||||
);
|
);
|
||||||
case 'panel':
|
case 'panel':
|
||||||
return (
|
return (
|
||||||
<span className={styles.text}>
|
<TextModifier color="secondary">
|
||||||
<Icon name={iconName} /> Panel
|
<Icon name={iconName} /> Panel
|
||||||
</span>
|
</TextModifier>
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStyles(theme: GrafanaTheme2) {
|
|
||||||
return {
|
|
||||||
text: css({
|
|
||||||
color: theme.colors.text.secondary,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user