ResourceCards.tsx 3.5 KB

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