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