Chore/PluginList: Consolidate PluginListItemRow & PluginListItemCard (#40271)
* Chore/PluginList: Consolidate PluginListItemRow & PluginListItemCard
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
@@ -12,26 +12,28 @@ interface Props {
|
||||
}
|
||||
|
||||
export const PluginList = ({ plugins, displayMode }: Props) => {
|
||||
const styles = useStyles2((theme) => getStyles(theme, displayMode));
|
||||
const isList = displayMode === PluginListDisplayMode.List;
|
||||
const styles = useStyles2(getStyles);
|
||||
const location = useLocation();
|
||||
|
||||
return (
|
||||
<div className={styles.container} data-testid="plugin-list">
|
||||
<div className={cx(styles.container, { [styles.list]: isList })} data-testid="plugin-list">
|
||||
{plugins.map((plugin) => (
|
||||
<PluginListItem key={plugin.id} plugin={plugin} pathName={location.pathname} />
|
||||
<PluginListItem key={plugin.id} plugin={plugin} pathName={location.pathname} displayMode={displayMode} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2, display: PluginListDisplayMode) => {
|
||||
const isList = display === PluginListDisplayMode.List;
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
container: css`
|
||||
display: grid;
|
||||
grid-template-columns: ${isList ? '1fr' : 'repeat(auto-fill, minmax(288px, 1fr))'};
|
||||
grid-gap: ${theme.spacing(3)};
|
||||
grid-template-columns: repeat(auto-fill, minmax(288px, 1fr));
|
||||
gap: ${theme.spacing(3)};
|
||||
`,
|
||||
list: css`
|
||||
grid-template-columns: 1fr;
|
||||
`,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { PluginErrorCode, PluginSignatureStatus, PluginType } from '@grafana/data';
|
||||
import { PluginListItem } from './PluginListItem';
|
||||
import { CatalogPlugin, PluginListDisplayMode } from '../types';
|
||||
|
||||
describe('PluginListItem', () => {
|
||||
const plugin: CatalogPlugin = {
|
||||
description: 'The test plugin',
|
||||
downloads: 5,
|
||||
id: 'test-plugin',
|
||||
info: {
|
||||
logos: {
|
||||
small: 'https://grafana.com/api/plugins/test-plugin/versions/0.0.10/logos/small',
|
||||
large: 'https://grafana.com/api/plugins/test-plugin/versions/0.0.10/logos/large',
|
||||
},
|
||||
},
|
||||
name: 'Testing Plugin',
|
||||
orgName: 'Test',
|
||||
popularity: 0,
|
||||
signature: PluginSignatureStatus.valid,
|
||||
publishedAt: '2020-09-01',
|
||||
updatedAt: '2021-06-28',
|
||||
version: '1.0.0',
|
||||
hasUpdate: false,
|
||||
isInstalled: false,
|
||||
isCore: false,
|
||||
isDev: false,
|
||||
isEnterprise: false,
|
||||
isDisabled: false,
|
||||
};
|
||||
|
||||
/** As Grid */
|
||||
it('renders a card with link, image, name, orgName and badges', () => {
|
||||
render(<PluginListItem plugin={plugin} pathName="/plugins" />);
|
||||
|
||||
expect(screen.getByRole('link')).toHaveAttribute('href', '/plugins/test-plugin?page=overview');
|
||||
|
||||
const logo = screen.getByRole('img');
|
||||
expect(logo).toHaveAttribute('src', plugin.info.logos.small);
|
||||
|
||||
expect(screen.getByRole('heading', { name: /testing plugin/i })).toBeVisible();
|
||||
expect(screen.getByText(`By ${plugin.orgName}`)).toBeVisible();
|
||||
expect(screen.getByText(/signed/i)).toBeVisible();
|
||||
expect(screen.queryByLabelText(/icon/i)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders a datasource plugin with correct icon', () => {
|
||||
const datasourcePlugin = { ...plugin, type: PluginType.datasource };
|
||||
render(<PluginListItem plugin={datasourcePlugin} pathName="" />);
|
||||
|
||||
expect(screen.getByLabelText(/datasource plugin icon/i)).toBeVisible();
|
||||
});
|
||||
|
||||
it('renders a panel plugin with correct icon', () => {
|
||||
const panelPlugin = { ...plugin, type: PluginType.panel };
|
||||
render(<PluginListItem plugin={panelPlugin} pathName="" />);
|
||||
|
||||
expect(screen.getByLabelText(/panel plugin icon/i)).toBeVisible();
|
||||
});
|
||||
|
||||
it('renders an app plugin with correct icon', () => {
|
||||
const appPlugin = { ...plugin, type: PluginType.app };
|
||||
render(<PluginListItem plugin={appPlugin} pathName="" />);
|
||||
|
||||
expect(screen.getByLabelText(/app plugin icon/i)).toBeVisible();
|
||||
});
|
||||
|
||||
it('renders a disabled plugin with a badge to indicate its error', () => {
|
||||
const pluginWithError = { ...plugin, isDisabled: true, error: PluginErrorCode.modifiedSignature };
|
||||
render(<PluginListItem plugin={pluginWithError} pathName="" />);
|
||||
|
||||
expect(screen.getByText(/disabled/i)).toBeVisible();
|
||||
});
|
||||
|
||||
/** As List */
|
||||
it('renders a row with link, image, name, orgName and badges', () => {
|
||||
render(<PluginListItem plugin={plugin} pathName="/plugins" displayMode={PluginListDisplayMode.List} />);
|
||||
|
||||
expect(screen.getByRole('link')).toHaveAttribute('href', '/plugins/test-plugin?page=overview');
|
||||
|
||||
const logo = screen.getByRole('img');
|
||||
expect(logo).toHaveAttribute('src', plugin.info.logos.small);
|
||||
|
||||
expect(screen.getByRole('heading', { name: /testing plugin/i })).toBeVisible();
|
||||
expect(screen.getByText(`By ${plugin.orgName}`)).toBeVisible();
|
||||
expect(screen.getByText(/signed/i)).toBeVisible();
|
||||
expect(screen.queryByLabelText(/icon/i)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders a datasource plugin with correct icon', () => {
|
||||
const datasourcePlugin = { ...plugin, type: PluginType.datasource };
|
||||
render(<PluginListItem plugin={datasourcePlugin} pathName="" displayMode={PluginListDisplayMode.List} />);
|
||||
|
||||
expect(screen.getByLabelText(/datasource plugin icon/i)).toBeVisible();
|
||||
});
|
||||
|
||||
it('renders a panel plugin with correct icon', () => {
|
||||
const panelPlugin = { ...plugin, type: PluginType.panel };
|
||||
render(<PluginListItem plugin={panelPlugin} pathName="" displayMode={PluginListDisplayMode.List} />);
|
||||
|
||||
expect(screen.getByLabelText(/panel plugin icon/i)).toBeVisible();
|
||||
});
|
||||
|
||||
it('renders an app plugin with correct icon', () => {
|
||||
const appPlugin = { ...plugin, type: PluginType.app };
|
||||
render(<PluginListItem plugin={appPlugin} pathName="" displayMode={PluginListDisplayMode.List} />);
|
||||
|
||||
expect(screen.getByLabelText(/app plugin icon/i)).toBeVisible();
|
||||
});
|
||||
|
||||
it('renders a disabled plugin with a badge to indicate its error', () => {
|
||||
const pluginWithError = { ...plugin, isDisabled: true, error: PluginErrorCode.modifiedSignature };
|
||||
render(<PluginListItem plugin={pluginWithError} pathName="" displayMode={PluginListDisplayMode.List} />);
|
||||
|
||||
expect(screen.getByText(/disabled/i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -1,78 +1,102 @@
|
||||
import React from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { useDisplayMode } from '../state/hooks';
|
||||
import { CatalogPlugin, PluginListDisplayMode } from '../types';
|
||||
import { PluginListItemRow } from './PluginListItemRow';
|
||||
import { PluginListItemCard } from './PluginListItemCard';
|
||||
import { CatalogPlugin, PluginIconName, PluginListDisplayMode, PluginTabIds } from '../types';
|
||||
import { PluginListItemBadges } from './PluginListItemBadges';
|
||||
import { PluginLogo } from './PluginLogo';
|
||||
import { Icon, useStyles2 } from '@grafana/ui';
|
||||
|
||||
export const LOGO_SIZE = '48px';
|
||||
|
||||
type Props = {
|
||||
plugin: CatalogPlugin;
|
||||
pathName: string;
|
||||
displayMode?: PluginListDisplayMode;
|
||||
};
|
||||
|
||||
export function PluginListItem({ plugin, pathName }: Props) {
|
||||
const { displayMode } = useDisplayMode();
|
||||
export function PluginListItem({ plugin, pathName, displayMode = PluginListDisplayMode.Grid }: Props) {
|
||||
const styles = useStyles2(getStyles);
|
||||
const isList = displayMode === PluginListDisplayMode.List;
|
||||
|
||||
if (isList) {
|
||||
return <PluginListItemRow plugin={plugin} pathName={pathName} />;
|
||||
}
|
||||
|
||||
return <PluginListItemCard plugin={plugin} pathName={pathName} />;
|
||||
return (
|
||||
<a
|
||||
href={`${pathName}/${plugin.id}?page=${PluginTabIds.OVERVIEW}`}
|
||||
className={cx(styles.container, { [styles.list]: isList })}
|
||||
>
|
||||
<PluginLogo src={plugin.info.logos.small} className={styles.pluginLogo} height={LOGO_SIZE} alt="" />
|
||||
<h2 className={cx(styles.name, 'plugin-name')}>{plugin.name}</h2>
|
||||
<div className={cx(styles.content, 'plugin-content')}>
|
||||
<p>By {plugin.orgName}</p>
|
||||
<PluginListItemBadges plugin={plugin} />
|
||||
</div>
|
||||
<div className={styles.pluginType}>
|
||||
{plugin.type && <Icon name={PluginIconName[plugin.type]} aria-label={`${plugin.type} plugin icon`} />}
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
// Styles shared between the different type of list items
|
||||
export const getStyles = (theme: GrafanaTheme2, displayMode: PluginListDisplayMode) => {
|
||||
const isRow = displayMode === PluginListDisplayMode.List;
|
||||
const isCard = displayMode === PluginListDisplayMode.Grid;
|
||||
|
||||
export const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
cardContainer: css`
|
||||
margin-bottom: 0;
|
||||
padding: ${theme.spacing()};
|
||||
`,
|
||||
headerWrap: css`
|
||||
container: css`
|
||||
display: grid;
|
||||
grid-template-columns: ${LOGO_SIZE} 1fr ${theme.spacing(3)};
|
||||
grid-gap: ${theme.spacing(2)};
|
||||
width: 100%;
|
||||
${isCard &&
|
||||
css`
|
||||
align-items: center;
|
||||
`}
|
||||
grid-template-rows: auto;
|
||||
gap: ${theme.spacing(2)};
|
||||
grid-auto-flow: row;
|
||||
background: ${theme.colors.background.secondary};
|
||||
border-radius: ${theme.shape.borderRadius()};
|
||||
padding: ${theme.spacing(3)};
|
||||
transition: ${theme.transitions.create(['background-color', 'box-shadow', 'border-color', 'color'], {
|
||||
duration: theme.transitions.duration.short,
|
||||
})};
|
||||
|
||||
&:hover {
|
||||
background: ${theme.colors.emphasize(theme.colors.background.secondary, 0.03)};
|
||||
}
|
||||
`,
|
||||
list: css`
|
||||
row-gap: 0px;
|
||||
|
||||
> img {
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
> .plugin-content {
|
||||
min-height: 0px;
|
||||
grid-area: 2 / 2 / 4 / 3;
|
||||
|
||||
> p {
|
||||
margin: ${theme.spacing(0, 0, 0.5, 0)};
|
||||
}
|
||||
}
|
||||
|
||||
> .plugin-name {
|
||||
align-self: center;
|
||||
grid-area: 1 / 2 / 2 / 3;
|
||||
}
|
||||
`,
|
||||
pluginType: css`
|
||||
grid-area: 1 / 3 / 2 / 4;
|
||||
color: ${theme.colors.text.secondary};
|
||||
`,
|
||||
pluginLogo: css`
|
||||
grid-area: 1 / 1 / 3 / 2;
|
||||
max-width: 100%;
|
||||
align-self: center;
|
||||
object-fit: contain;
|
||||
`,
|
||||
content: css`
|
||||
grid-area: 3 / 1 / 4 / 3;
|
||||
color: ${theme.colors.text.secondary};
|
||||
`,
|
||||
name: css`
|
||||
color: ${theme.colors.text.primary};
|
||||
flex-grow: 1;
|
||||
grid-area: 1 / 2 / 3 / 3;
|
||||
align-self: center;
|
||||
font-size: ${theme.typography.h4.fontSize};
|
||||
margin-bottom: 0;
|
||||
`,
|
||||
image: css`
|
||||
object-fit: contain;
|
||||
max-width: 100%;
|
||||
`,
|
||||
icon: css`
|
||||
align-self: flex-start;
|
||||
color: ${theme.colors.text.secondary};
|
||||
`,
|
||||
orgName: css`
|
||||
color: ${theme.colors.text.secondary};
|
||||
${isRow &&
|
||||
css`
|
||||
margin: ${theme.spacing(0, 0, 0.5, 0)};
|
||||
`}
|
||||
${isCard &&
|
||||
css`
|
||||
margin-bottom: 0;
|
||||
`};
|
||||
`,
|
||||
hasUpdate: css`
|
||||
color: ${theme.colors.text.secondary};
|
||||
font-size: ${theme.typography.bodySmall.fontSize};
|
||||
margin-bottom: 0;
|
||||
color: ${theme.colors.text.primary};
|
||||
margin: 0;
|
||||
`,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { PluginErrorCode, PluginSignatureStatus, PluginType } from '@grafana/data';
|
||||
import { PluginListItemCard } from './PluginListItemCard';
|
||||
import { CatalogPlugin } from '../types';
|
||||
|
||||
describe('PluginListItemCard', () => {
|
||||
const plugin: CatalogPlugin = {
|
||||
description: 'The test plugin',
|
||||
downloads: 5,
|
||||
id: 'test-plugin',
|
||||
info: {
|
||||
logos: {
|
||||
small: 'https://grafana.com/api/plugins/test-plugin/versions/0.0.10/logos/small',
|
||||
large: 'https://grafana.com/api/plugins/test-plugin/versions/0.0.10/logos/large',
|
||||
},
|
||||
},
|
||||
name: 'Testing Plugin',
|
||||
orgName: 'Test',
|
||||
popularity: 0,
|
||||
signature: PluginSignatureStatus.valid,
|
||||
publishedAt: '2020-09-01',
|
||||
updatedAt: '2021-06-28',
|
||||
version: '1.0.0',
|
||||
hasUpdate: false,
|
||||
isInstalled: false,
|
||||
isCore: false,
|
||||
isDev: false,
|
||||
isEnterprise: false,
|
||||
isDisabled: false,
|
||||
};
|
||||
|
||||
it('renders a card with link, image, name, orgName and badges', () => {
|
||||
render(<PluginListItemCard plugin={plugin} pathName="/plugins" />);
|
||||
|
||||
expect(screen.getByRole('link')).toHaveAttribute('href', '/plugins/test-plugin?page=overview');
|
||||
|
||||
const logo = screen.getByRole('img');
|
||||
expect(logo).toHaveAttribute('src', plugin.info.logos.small);
|
||||
expect(logo).toHaveAttribute('alt', `${plugin.name} logo`);
|
||||
|
||||
expect(screen.getByRole('heading', { name: /testing plugin/i })).toBeVisible();
|
||||
expect(screen.getByText(`By ${plugin.orgName}`)).toBeVisible();
|
||||
expect(screen.getByText(/signed/i)).toBeVisible();
|
||||
expect(screen.queryByLabelText(/icon/i)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders a datasource plugin with correct icon', () => {
|
||||
const datasourcePlugin = { ...plugin, type: PluginType.datasource };
|
||||
render(<PluginListItemCard plugin={datasourcePlugin} pathName="" />);
|
||||
|
||||
expect(screen.getByTestId(/datasource plugin icon/i)).toBeVisible();
|
||||
});
|
||||
|
||||
it('renders a panel plugin with correct icon', () => {
|
||||
const panelPlugin = { ...plugin, type: PluginType.panel };
|
||||
render(<PluginListItemCard plugin={panelPlugin} pathName="" />);
|
||||
|
||||
expect(screen.getByTestId(/panel plugin icon/i)).toBeVisible();
|
||||
});
|
||||
|
||||
it('renders an app plugin with correct icon', () => {
|
||||
const appPlugin = { ...plugin, type: PluginType.app };
|
||||
render(<PluginListItemCard plugin={appPlugin} pathName="" />);
|
||||
|
||||
expect(screen.getByTestId(/app plugin icon/i)).toBeVisible();
|
||||
});
|
||||
|
||||
it('renders a disabled plugin with a badge to indicate its error', () => {
|
||||
const pluginWithError = { ...plugin, isDisabled: true, error: PluginErrorCode.modifiedSignature };
|
||||
render(<PluginListItemCard plugin={pluginWithError} pathName="" />);
|
||||
|
||||
expect(screen.getByText(/disabled/i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -1,47 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Icon, useStyles2, CardContainer, VerticalGroup } from '@grafana/ui';
|
||||
import { CatalogPlugin, PluginIconName, PluginListDisplayMode, PluginTabIds } from '../types';
|
||||
import { PluginLogo } from './PluginLogo';
|
||||
import { PluginListItemBadges } from './PluginListItemBadges';
|
||||
import { getStyles, LOGO_SIZE } from './PluginListItem';
|
||||
|
||||
type Props = {
|
||||
plugin: CatalogPlugin;
|
||||
pathName: string;
|
||||
};
|
||||
|
||||
export function PluginListItemCard({ plugin, pathName }: Props) {
|
||||
const styles = useStyles2((theme) => getStyles(theme, PluginListDisplayMode.Grid));
|
||||
|
||||
return (
|
||||
<CardContainer href={`${pathName}/${plugin.id}?page=${PluginTabIds.OVERVIEW}`} className={styles.cardContainer}>
|
||||
<VerticalGroup spacing="md">
|
||||
<div className={styles.headerWrap}>
|
||||
{/* Logo */}
|
||||
<PluginLogo
|
||||
src={plugin.info.logos.small}
|
||||
alt={`${plugin.name} logo`}
|
||||
className={styles.image}
|
||||
height={LOGO_SIZE}
|
||||
/>
|
||||
|
||||
{/* Name */}
|
||||
<h2 className={styles.name}>{plugin.name}</h2>
|
||||
|
||||
{/* Type Icon */}
|
||||
{plugin.type && (
|
||||
<div className={styles.icon} data-testid={`${plugin.type} plugin icon`}>
|
||||
<Icon name={PluginIconName[plugin.type]} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Org */}
|
||||
<p className={styles.orgName}>By {plugin.orgName}</p>
|
||||
|
||||
{/* Badges */}
|
||||
<PluginListItemBadges plugin={plugin} />
|
||||
</VerticalGroup>
|
||||
</CardContainer>
|
||||
);
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { PluginErrorCode, PluginSignatureStatus, PluginType } from '@grafana/data';
|
||||
import { PluginListItemRow } from './PluginListItemRow';
|
||||
import { CatalogPlugin } from '../types';
|
||||
|
||||
describe('PluginListItemRow', () => {
|
||||
const plugin: CatalogPlugin = {
|
||||
description: 'The test plugin',
|
||||
downloads: 5,
|
||||
id: 'test-plugin',
|
||||
info: {
|
||||
logos: {
|
||||
small: 'https://grafana.com/api/plugins/test-plugin/versions/0.0.10/logos/small',
|
||||
large: 'https://grafana.com/api/plugins/test-plugin/versions/0.0.10/logos/large',
|
||||
},
|
||||
},
|
||||
name: 'Testing Plugin',
|
||||
orgName: 'Test',
|
||||
popularity: 0,
|
||||
signature: PluginSignatureStatus.valid,
|
||||
publishedAt: '2020-09-01',
|
||||
updatedAt: '2021-06-28',
|
||||
version: '1.0.0',
|
||||
hasUpdate: false,
|
||||
isInstalled: false,
|
||||
isCore: false,
|
||||
isDev: false,
|
||||
isEnterprise: false,
|
||||
isDisabled: false,
|
||||
};
|
||||
|
||||
it('renders a row with link, image, name, orgName and badges', () => {
|
||||
render(<PluginListItemRow plugin={plugin} pathName="/plugins" />);
|
||||
|
||||
expect(screen.getByRole('link')).toHaveAttribute('href', '/plugins/test-plugin?page=overview');
|
||||
|
||||
const logo = screen.getByRole('img');
|
||||
expect(logo).toHaveAttribute('src', plugin.info.logos.small);
|
||||
expect(logo).toHaveAttribute('alt', `${plugin.name} logo`);
|
||||
|
||||
expect(screen.getByRole('heading', { name: /testing plugin/i })).toBeVisible();
|
||||
expect(screen.getByText(`By ${plugin.orgName}`)).toBeVisible();
|
||||
expect(screen.getByText(/signed/i)).toBeVisible();
|
||||
expect(screen.queryByLabelText(/icon/i)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders a datasource plugin with correct icon', () => {
|
||||
const datasourcePlugin = { ...plugin, type: PluginType.datasource };
|
||||
render(<PluginListItemRow plugin={datasourcePlugin} pathName="" />);
|
||||
|
||||
expect(screen.getByLabelText(/datasource plugin icon/i)).toBeVisible();
|
||||
});
|
||||
|
||||
it('renders a panel plugin with correct icon', () => {
|
||||
const panelPlugin = { ...plugin, type: PluginType.panel };
|
||||
render(<PluginListItemRow plugin={panelPlugin} pathName="" />);
|
||||
|
||||
expect(screen.getByLabelText(/panel plugin icon/i)).toBeVisible();
|
||||
});
|
||||
|
||||
it('renders an app plugin with correct icon', () => {
|
||||
const appPlugin = { ...plugin, type: PluginType.app };
|
||||
render(<PluginListItemRow plugin={appPlugin} pathName="" />);
|
||||
|
||||
expect(screen.getByLabelText(/app plugin icon/i)).toBeVisible();
|
||||
});
|
||||
|
||||
it('renders a disabled plugin with a badge to indicate its error', () => {
|
||||
const pluginWithError = { ...plugin, isDisabled: true, error: PluginErrorCode.modifiedSignature };
|
||||
render(<PluginListItemRow plugin={pluginWithError} pathName="" />);
|
||||
|
||||
expect(screen.getByText(/disabled/i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -1,49 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Icon, useStyles2, CardContainer, VerticalGroup } from '@grafana/ui';
|
||||
import { CatalogPlugin, PluginIconName, PluginListDisplayMode, PluginTabIds } from '../types';
|
||||
import { PluginLogo } from './PluginLogo';
|
||||
import { PluginListItemBadges } from './PluginListItemBadges';
|
||||
import { getStyles, LOGO_SIZE } from './PluginListItem';
|
||||
|
||||
type Props = {
|
||||
plugin: CatalogPlugin;
|
||||
pathName: string;
|
||||
};
|
||||
|
||||
export function PluginListItemRow({ plugin, pathName }: Props) {
|
||||
const styles = useStyles2((theme) => getStyles(theme, PluginListDisplayMode.List));
|
||||
|
||||
return (
|
||||
<CardContainer href={`${pathName}/${plugin.id}?page=${PluginTabIds.OVERVIEW}`} className={styles.cardContainer}>
|
||||
<VerticalGroup spacing="md">
|
||||
<div className={styles.headerWrap}>
|
||||
{/* Logo */}
|
||||
<PluginLogo
|
||||
src={plugin.info.logos.small}
|
||||
alt={`${plugin.name} logo`}
|
||||
className={styles.image}
|
||||
height={LOGO_SIZE}
|
||||
/>
|
||||
|
||||
<div>
|
||||
{/* Name */}
|
||||
<h3 className={styles.name}>{plugin.name}</h3>
|
||||
|
||||
{/* Org */}
|
||||
<p className={styles.orgName}>By {plugin.orgName}</p>
|
||||
|
||||
{/* Badges */}
|
||||
<PluginListItemBadges plugin={plugin} />
|
||||
</div>
|
||||
|
||||
{/* Type Icon */}
|
||||
{plugin.type && (
|
||||
<div className={styles.icon}>
|
||||
<Icon name={PluginIconName[plugin.type]} aria-label={`${plugin.type} plugin icon`} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</VerticalGroup>
|
||||
</CardContainer>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user