SectionHeader.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import { css, cx } from '@emotion/css';
  2. import React, { FC, useCallback } from 'react';
  3. import { useLocalStorage } from 'react-use';
  4. import { GrafanaTheme } from '@grafana/data';
  5. import { CollapsableSection, Icon, stylesFactory, useTheme } from '@grafana/ui';
  6. import { useUniqueId } from 'app/plugins/datasource/influxdb/components/useUniqueId';
  7. import { DashboardSection, OnToggleChecked } from '../types';
  8. import { getSectionIcon, getSectionStorageKey } from '../utils';
  9. import { SearchCheckbox } from './SearchCheckbox';
  10. interface SectionHeaderProps {
  11. editable?: boolean;
  12. onSectionClick: (section: DashboardSection) => void;
  13. onToggleChecked?: OnToggleChecked;
  14. section: DashboardSection;
  15. children: React.ReactNode;
  16. }
  17. export const SectionHeader: FC<SectionHeaderProps> = ({
  18. section,
  19. onSectionClick,
  20. children,
  21. onToggleChecked,
  22. editable = false,
  23. }) => {
  24. const theme = useTheme();
  25. const styles = getSectionHeaderStyles(theme, section.selected, editable);
  26. const setSectionExpanded = useLocalStorage(getSectionStorageKey(section.title), true)[1];
  27. const onSectionExpand = () => {
  28. setSectionExpanded(!section.expanded);
  29. onSectionClick(section);
  30. };
  31. const handleCheckboxClick = useCallback(
  32. (ev: React.MouseEvent) => {
  33. ev.stopPropagation();
  34. ev.preventDefault();
  35. onToggleChecked?.(section);
  36. },
  37. [onToggleChecked, section]
  38. );
  39. const id = useUniqueId();
  40. const labelId = `section-header-label-${id}`;
  41. return (
  42. <CollapsableSection
  43. isOpen={section.expanded ?? false}
  44. onToggle={onSectionExpand}
  45. className={styles.wrapper}
  46. contentClassName={styles.content}
  47. loading={section.itemsFetching}
  48. labelId={labelId}
  49. label={
  50. <>
  51. <SearchCheckbox
  52. className={styles.checkbox}
  53. editable={editable}
  54. checked={section.checked}
  55. onClick={handleCheckboxClick}
  56. aria-label="Select folder"
  57. />
  58. <div className={styles.icon}>
  59. <Icon name={getSectionIcon(section)} />
  60. </div>
  61. <div className={styles.text}>
  62. <span id={labelId}>{section.title}</span>
  63. {section.url && (
  64. <a href={section.url} className={styles.link}>
  65. <span className={styles.separator}>|</span> <Icon name="folder-upload" /> Go to folder
  66. </a>
  67. )}
  68. </div>
  69. </>
  70. }
  71. >
  72. {children}
  73. </CollapsableSection>
  74. );
  75. };
  76. const getSectionHeaderStyles = stylesFactory((theme: GrafanaTheme, selected = false, editable: boolean) => {
  77. const { sm } = theme.spacing;
  78. return {
  79. wrapper: cx(
  80. css`
  81. align-items: center;
  82. font-size: ${theme.typography.size.base};
  83. padding: 12px;
  84. border-bottom: none;
  85. color: ${theme.colors.textWeak};
  86. z-index: 1;
  87. &:hover,
  88. &.selected {
  89. color: ${theme.colors.text};
  90. }
  91. &:hover,
  92. &:focus-visible,
  93. &:focus-within {
  94. a {
  95. opacity: 1;
  96. }
  97. }
  98. `,
  99. 'pointer',
  100. { selected }
  101. ),
  102. checkbox: css`
  103. padding: 0 ${sm} 0 0;
  104. `,
  105. icon: css`
  106. padding: 0 ${sm} 0 ${editable ? 0 : sm};
  107. `,
  108. text: css`
  109. flex-grow: 1;
  110. line-height: 24px;
  111. `,
  112. link: css`
  113. padding: 2px 10px 0;
  114. color: ${theme.colors.textWeak};
  115. opacity: 0;
  116. transition: opacity 150ms ease-in-out;
  117. `,
  118. separator: css`
  119. margin-right: 6px;
  120. `,
  121. content: css`
  122. padding-top: 0px;
  123. padding-bottom: 0px;
  124. `,
  125. };
  126. });