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 { Icon } from '../Icon/Icon';
|
||||
|
||||
const getStyles = stylesFactory((size: number, inline: boolean) => {
|
||||
return {
|
||||
wrapper: css`
|
||||
font-size: ${size}px;
|
||||
${inline
|
||||
? css`
|
||||
display: inline-block;
|
||||
`
|
||||
: ''}
|
||||
`,
|
||||
};
|
||||
const getStyles = stylesFactory((size: number | string, inline: boolean) => {
|
||||
return css([
|
||||
{
|
||||
fontSize: typeof size === 'string' ? size : `${size}px`,
|
||||
},
|
||||
inline && { display: 'inline-block' },
|
||||
]);
|
||||
});
|
||||
|
||||
export type Props = {
|
||||
@ -22,7 +18,7 @@ export type Props = {
|
||||
style?: React.CSSProperties;
|
||||
iconClassName?: string;
|
||||
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) => {
|
||||
const styles = getStyles(size, inline);
|
||||
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" />
|
||||
</div>
|
||||
);
|
||||
|
@ -3,6 +3,7 @@ import { sortBy } from 'lodash';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Space } from '@grafana/experimental';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { Button, useStyles2 } from '@grafana/ui';
|
||||
import { SlideDown } from 'app/core/components/Animations/SlideDown';
|
||||
@ -143,6 +144,7 @@ export const Permissions = ({
|
||||
$all: false,
|
||||
}}
|
||||
/>
|
||||
<Space v={2} />
|
||||
</>
|
||||
)}
|
||||
{canSetPermissions && (
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { ConfirmModal, useStyles2 } from '@grafana/ui';
|
||||
import { Space } from '@grafana/experimental';
|
||||
import { ConfirmModal } from '@grafana/ui';
|
||||
import { P } from '@grafana/ui/src/unstable';
|
||||
|
||||
import { DashboardTreeSelection } from '../../types';
|
||||
|
||||
@ -16,8 +16,6 @@ export interface Props {
|
||||
}
|
||||
|
||||
export const DeleteModal = ({ onConfirm, onDismiss, selectedItems, ...props }: Props) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const onDelete = () => {
|
||||
onConfirm();
|
||||
onDismiss();
|
||||
@ -26,10 +24,11 @@ export const DeleteModal = ({ onConfirm, onDismiss, selectedItems, ...props }: P
|
||||
return (
|
||||
<ConfirmModal
|
||||
body={
|
||||
<div className={styles.modalBody}>
|
||||
This action will delete the following content:
|
||||
<>
|
||||
<P>This action will delete the following content:</P>
|
||||
<DescendantCount selectedItems={selectedItems} />
|
||||
</div>
|
||||
<Space v={2} />
|
||||
</>
|
||||
}
|
||||
confirmationText="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 { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Alert, Spinner, useStyles2 } from '@grafana/ui';
|
||||
import { Alert, Spinner, useTheme2 } from '@grafana/ui';
|
||||
import { P } from '@grafana/ui/src/unstable';
|
||||
|
||||
import { useGetAffectedItemsQuery } from '../../api/browseDashboardsAPI';
|
||||
import { DashboardTreeSelection } from '../../types';
|
||||
@ -14,24 +13,17 @@ export interface Props {
|
||||
}
|
||||
|
||||
export const DescendantCount = ({ selectedItems }: Props) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
const theme = useTheme2();
|
||||
const { data, isFetching, isLoading, error } = useGetAffectedItemsQuery(selectedItems);
|
||||
|
||||
return (
|
||||
<div className={styles.breakdown}>
|
||||
<>
|
||||
{data && buildBreakdownString(data.folder, data.dashboard, data.libraryPanel, data.alertRule)}
|
||||
{(isFetching || isLoading) && <Spinner size={12} />}
|
||||
{error && <Alert severity="error" title="Unable to retrieve descendant information" />}
|
||||
</>
|
||||
</div>
|
||||
<>
|
||||
{data && (
|
||||
<P color="secondary">{buildBreakdownString(data.folder, data.dashboard, data.libraryPanel, data.alertRule)}</P>
|
||||
)}
|
||||
|
||||
{(isFetching || isLoading) && <Spinner size={theme.typography.body.fontSize} />}
|
||||
{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 { Space } from '@grafana/experimental';
|
||||
import { Alert, Button, Field, Modal } from '@grafana/ui';
|
||||
import { P } from '@grafana/ui/src/unstable';
|
||||
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
|
||||
|
||||
import { DashboardTreeSelection } from '../../types';
|
||||
@ -28,11 +30,17 @@ export const MoveModal = ({ onConfirm, onDismiss, selectedItems, ...props }: Pro
|
||||
return (
|
||||
<Modal title="Move" onDismiss={onDismiss} {...props}>
|
||||
{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} />
|
||||
|
||||
<Space v={3} />
|
||||
|
||||
<Field label="Folder name">
|
||||
<FolderPicker allowEmpty onChange={({ uid }) => setMoveTarget(uid)} />
|
||||
</Field>
|
||||
|
||||
<Modal.ButtonRow>
|
||||
<Button onClick={onDismiss} variant="secondary" fill="outline">
|
||||
Cancel
|
||||
|
@ -112,7 +112,7 @@ describe('browse-dashboards DashboardsTree', () => {
|
||||
onAllSelectionChange={noop}
|
||||
/>
|
||||
);
|
||||
expect(screen.queryByText('Empty folder')).toBeInTheDocument();
|
||||
expect(screen.queryByText('No items')).toBeInTheDocument();
|
||||
expect(screen.queryByText(emptyFolderIndicator.item.kind)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -204,9 +204,9 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
|
||||
cell: css({
|
||||
padding: theme.spacing(1),
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden', // Required so flex children can do text-overflow: ellipsis
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}),
|
||||
|
||||
link: css({
|
||||
|
@ -5,6 +5,7 @@ import { CellProps } from 'react-table';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { IconButton, Link, useStyles2 } from '@grafana/ui';
|
||||
import { getSvgSize } from '@grafana/ui/src/components/Icon/utils';
|
||||
import { Span, TextModifier } from '@grafana/ui/src/unstable';
|
||||
|
||||
import { DashboardsTreeItem } from '../types';
|
||||
|
||||
@ -23,7 +24,9 @@ export function NameCell({ row: { original: data }, onFolderClick }: NameCellPro
|
||||
<>
|
||||
<Indent level={level} />
|
||||
<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} />
|
||||
)}
|
||||
|
||||
{item.url ? (
|
||||
<Link href={item.url} className={styles.link}>
|
||||
{item.title}
|
||||
</Link>
|
||||
) : (
|
||||
item.title
|
||||
)}
|
||||
<Span variant="body" truncate>
|
||||
{item.url ? (
|
||||
<Link href={item.url} className={styles.link}>
|
||||
{item.title}
|
||||
</Link>
|
||||
) : (
|
||||
item.title
|
||||
)}
|
||||
</Span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,45 +1,35 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
import { CellProps } from 'react-table';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Icon, useStyles2 } from '@grafana/ui';
|
||||
import { Icon } from '@grafana/ui';
|
||||
import { TextModifier } from '@grafana/ui/src/unstable';
|
||||
import { getIconForKind } from 'app/features/search/service/utils';
|
||||
|
||||
import { DashboardsTreeItem } from '../types';
|
||||
|
||||
export function TypeCell({ row: { original: data } }: CellProps<DashboardsTreeItem, unknown>) {
|
||||
const styles = useStyles2(getStyles);
|
||||
const iconName = getIconForKind(data.item.kind);
|
||||
|
||||
switch (data.item.kind) {
|
||||
case 'dashboard':
|
||||
return (
|
||||
<span className={styles.text}>
|
||||
<TextModifier color="secondary">
|
||||
<Icon name={iconName} /> Dashboard
|
||||
</span>
|
||||
</TextModifier>
|
||||
);
|
||||
case 'folder':
|
||||
return (
|
||||
<span className={styles.text}>
|
||||
<TextModifier color="secondary">
|
||||
<Icon name={iconName} /> Folder
|
||||
</span>
|
||||
</TextModifier>
|
||||
);
|
||||
case 'panel':
|
||||
return (
|
||||
<span className={styles.text}>
|
||||
<TextModifier color="secondary">
|
||||
<Icon name={iconName} /> Panel
|
||||
</span>
|
||||
</TextModifier>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getStyles(theme: GrafanaTheme2) {
|
||||
return {
|
||||
text: css({
|
||||
color: theme.colors.text.secondary,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user