2025-04-01 10:38:02 +09:00

137 lines
3.8 KiB
TypeScript

import { css } from '@emotion/css';
import Skeleton from 'react-loading-skeleton';
import { GrafanaTheme2 } from '@grafana/data';
import { reportInteraction } from '@grafana/runtime';
import { Icon, IconButton, Link, Spinner, useStyles2, Text } from '@grafana/ui';
import { getSvgSize } from '@grafana/ui/src/components/Icon/utils';
import { t } from 'app/core/internationalization';
import { getIconForItem } from 'app/features/search/service/utils';
import { Indent } from '../../../core/components/Indent/Indent';
import { useChildrenByParentUIDState } from '../state';
import { DashboardsTreeCellProps } from '../types';
import { makeRowID } from './utils';
const CHEVRON_SIZE = 'md';
const ICON_SIZE = 'sm';
type NameCellProps = DashboardsTreeCellProps & {
onFolderClick: (uid: string, newOpenState: boolean) => void;
};
export function NameCell({ row: { original: data }, onFolderClick, treeID }: NameCellProps) {
const styles = useStyles2(getStyles);
const { item, level, isOpen } = data;
const childrenByParentUID = useChildrenByParentUIDState();
const isLoading = isOpen && !childrenByParentUID[item.uid];
const iconName = getIconForItem(data.item, isOpen);
if (item.kind === 'ui') {
return (
<>
<Indent
level={level}
spacing={{
xs: 1,
md: 3,
}}
/>
<span className={styles.folderButtonSpacer} />
{item.uiKind === 'empty-folder' ? (
<em className={styles.emptyText}>
<Text variant="body" color="secondary" truncate>
No items
</Text>
</em>
) : (
<Skeleton width={200} />
)}
</>
);
}
return (
<>
<Indent
level={level}
spacing={{
xs: 1,
md: 3,
}}
/>
{item.kind === 'folder' ? (
<IconButton
size={CHEVRON_SIZE}
className={styles.chevron}
onClick={() => {
onFolderClick(item.uid, !isOpen);
}}
name={isOpen ? 'angle-down' : 'angle-right'}
aria-label={
isOpen
? t('browse-dashboards.dashboards-tree.collapse-folder-button', 'Collapse folder {{title}}', {
title: item.title,
})
: t('browse-dashboards.dashboards-tree.expand-folder-button', 'Expand folder {{title}}', {
title: item.title,
})
}
/>
) : (
<span className={styles.folderButtonSpacer} />
)}
<div className={styles.iconNameContainer}>
{isLoading ? <Spinner size={ICON_SIZE} /> : <Icon size={ICON_SIZE} name={iconName} />}
<Text variant="body" truncate id={treeID && makeRowID(treeID, item)}>
{item.url ? (
<Link
onClick={() => {
reportInteraction('manage_dashboards_result_clicked');
}}
href={item.url}
className={styles.link}
>
{item.title}
</Link>
) : (
item.title
)}
</Text>
</div>
</>
);
}
const getStyles = (theme: GrafanaTheme2) => {
return {
chevron: css({
marginRight: theme.spacing(1),
width: getSvgSize(CHEVRON_SIZE),
}),
emptyText: css({
// needed for text to truncate correctly
overflow: 'hidden',
}),
// Should be the same size as the <IconButton /> so Dashboard name is aligned to Folder name siblings
folderButtonSpacer: css({
paddingLeft: `calc(${getSvgSize(CHEVRON_SIZE)}px + ${theme.spacing(1)})`,
}),
iconNameContainer: css({
alignItems: 'center',
display: 'flex',
gap: theme.spacing(1),
overflow: 'hidden',
}),
link: css({
'&:hover': {
textDecoration: 'underline',
},
}),
};
};