1import React, { memo, CSSProperties } from 'react';
2import { areEqual, FixedSizeGrid as Grid } from 'react-window';
3import AutoSizer from 'react-virtualized-auto-sizer';
4import { GrafanaTheme2 } from '@grafana/data';
5import { useTheme2, stylesFactory } from '@grafana/ui';
6import SVG from 'react-inlinesvg';
7import { css, cx } from '@emotion/css';
8import { ResourceItem } from './ResourcePicker';
9
10interface CellProps {
11  columnIndex: number;
12  rowIndex: number;
13  style: CSSProperties;
14  data: {
15    cards: ResourceItem[];
16    columnCount: number;
17    onChange: (value: string) => void;
18    selected?: string;
19  };
20}
21
22function Cell(props: CellProps) {
23  const { columnIndex, rowIndex, style, data } = props;
24  const { cards, columnCount, onChange, selected } = data;
25  const singleColumnIndex = columnIndex + rowIndex * columnCount;
26  const card = cards[singleColumnIndex];
27  const theme = useTheme2();
28  const styles = getStyles(theme);
29
30  return (
31    <div style={style}>
32      {card && (
33        <div
34          key={card.value}
35          className={selected === card.value ? cx(styles.card, styles.selected) : styles.card}
36          onClick={() => onChange(card.value)}
37        >
38          {card.imgUrl.endsWith('.svg') ? (
39            <SVG src={card.imgUrl} className={styles.img} />
40          ) : (
41            <img src={card.imgUrl} className={styles.img} />
42          )}
43          <h6 className={styles.text}>{card.label.substr(0, card.label.length - 4)}</h6>
44        </div>
45      )}
46    </div>
47  );
48}
49
50const getStyles = stylesFactory((theme: GrafanaTheme2) => {
51  return {
52    card: css`
53      display: inline-block;
54      width: 90px;
55      height: 90px;
56      margin: 0.75rem;
57      margin-left: 15px;
58      text-align: center;
59      cursor: pointer;
60      position: relative;
61      background-color: transparent;
62      border: 1px solid transparent;
63      border-radius: 8px;
64      padding-top: 6px;
65      :hover {
66        border-color: ${theme.colors.action.hover};
67        box-shadow: ${theme.shadows.z2};
68      }
69    `,
70    selected: css`
71      border: 2px solid ${theme.colors.primary.main};
72      :hover {
73        border-color: ${theme.colors.primary.main};
74      }
75    `,
76    img: css`
77      width: 40px;
78      height: 40px;
79      object-fit: cover;
80      vertical-align: middle;
81      fill: ${theme.colors.text.primary};
82    `,
83    text: css`
84      color: ${theme.colors.text.primary};
85      white-space: nowrap;
86      font-size: 12px;
87      text-overflow: ellipsis;
88      display: block;
89      overflow: hidden;
90    `,
91    grid: css`
92      border: 1px solid ${theme.colors.border.medium};
93    `,
94  };
95});
96
97interface CardProps {
98  onChange: (value: string) => void;
99  cards: ResourceItem[];
100  value?: string;
101}
102
103export const ResourceCards = (props: CardProps) => {
104  const { onChange, cards, value } = props;
105  const theme = useTheme2();
106  const styles = getStyles(theme);
107
108  return (
109    <AutoSizer defaultWidth={680}>
110      {({ width, height }) => {
111        const cardWidth = 90;
112        const cardHeight = 90;
113        const columnCount = Math.floor(width / cardWidth);
114        const rowCount = Math.ceil(cards.length / columnCount);
115        return (
116          <Grid
117            width={width}
118            height={height}
119            columnCount={columnCount}
120            columnWidth={cardWidth}
121            rowCount={rowCount}
122            rowHeight={cardHeight}
123            itemData={{ cards, columnCount, onChange, selected: value }}
124            className={styles.grid}
125          >
126            {memo(Cell, areEqual)}
127          </Grid>
128        );
129      }}
130    </AutoSizer>
131  );
132};
133