DashboardSearch.tsx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. import { css } from '@emotion/css';
  2. import React, { FC, memo, useState } from 'react';
  3. import { useDebounce, useLocalStorage } from 'react-use';
  4. import { GrafanaTheme2 } from '@grafana/data';
  5. import { config } from '@grafana/runtime';
  6. import { CustomScrollbar, IconButton, stylesFactory, useStyles2, useTheme2 } from '@grafana/ui';
  7. import { SEARCH_PANELS_LOCAL_STORAGE_KEY } from '../constants';
  8. import { useDashboardSearch } from '../hooks/useDashboardSearch';
  9. import { useKeyNavigationListener } from '../hooks/useSearchKeyboardSelection';
  10. import { useSearchQuery } from '../hooks/useSearchQuery';
  11. import { SearchView } from '../page/components/SearchView';
  12. import { ActionRow } from './ActionRow';
  13. import { PreviewsSystemRequirements } from './PreviewsSystemRequirements';
  14. import { SearchField } from './SearchField';
  15. import { SearchResults } from './SearchResults';
  16. export interface Props {
  17. onCloseSearch: () => void;
  18. }
  19. export default function DashboardSearch({ onCloseSearch }: Props) {
  20. if (config.featureToggles.panelTitleSearch) {
  21. // TODO: "folder:current" ????
  22. return <DashboardSearchNew onCloseSearch={onCloseSearch} />;
  23. }
  24. return <DashboardSearchOLD onCloseSearch={onCloseSearch} />;
  25. }
  26. function DashboardSearchNew({ onCloseSearch }: Props) {
  27. const styles = useStyles2(getStyles);
  28. const { query, onQueryChange } = useSearchQuery({});
  29. let [includePanels, setIncludePanels] = useLocalStorage<boolean>(SEARCH_PANELS_LOCAL_STORAGE_KEY, true);
  30. if (!config.featureToggles.panelTitleSearch) {
  31. includePanels = false;
  32. }
  33. const [inputValue, setInputValue] = useState(query.query ?? '');
  34. const onSearchQueryChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  35. e.preventDefault();
  36. setInputValue(e.currentTarget.value);
  37. };
  38. useDebounce(() => onQueryChange(inputValue), 200, [inputValue]);
  39. const { onKeyDown, keyboardEvents } = useKeyNavigationListener();
  40. return (
  41. <div tabIndex={0} className={styles.overlay}>
  42. <div className={styles.container}>
  43. <div className={styles.searchField}>
  44. <div>
  45. <input
  46. type="text"
  47. placeholder={includePanels ? 'Search dashboards and panels by name' : 'Search dashboards by name'}
  48. value={inputValue}
  49. onChange={onSearchQueryChange}
  50. onKeyDown={onKeyDown}
  51. tabIndex={0}
  52. spellCheck={false}
  53. className={styles.input}
  54. autoFocus
  55. />
  56. </div>
  57. <div className={styles.closeBtn}>
  58. <IconButton name="times" onClick={onCloseSearch} size="xxl" tooltip="Close search" />
  59. </div>
  60. </div>
  61. <div className={styles.search}>
  62. <SearchView
  63. showManage={false}
  64. queryText={query.query}
  65. onQueryTextChange={(newQueryText) => {
  66. setInputValue(newQueryText);
  67. }}
  68. includePanels={includePanels!}
  69. setIncludePanels={setIncludePanels}
  70. keyboardEvents={keyboardEvents}
  71. />
  72. </div>
  73. </div>
  74. </div>
  75. );
  76. }
  77. export const DashboardSearchOLD: FC<Props> = memo(({ onCloseSearch }) => {
  78. const { query, onQueryChange, onTagFilterChange, onTagAdd, onSortChange, onLayoutChange } = useSearchQuery({});
  79. const { results, loading, onToggleSection, onKeyDown, showPreviews, setShowPreviews } = useDashboardSearch(
  80. query,
  81. onCloseSearch
  82. );
  83. const theme = useTheme2();
  84. const styles = getStyles(theme);
  85. return (
  86. <div tabIndex={0} className={styles.overlay}>
  87. <div className={styles.container}>
  88. <div className={styles.searchField}>
  89. <SearchField query={query} onChange={onQueryChange} onKeyDown={onKeyDown} autoFocus clearable />
  90. <div className={styles.closeBtn}>
  91. <IconButton name="times" onClick={onCloseSearch} size="xxl" tooltip="Close search" />
  92. </div>
  93. </div>
  94. <div className={styles.search}>
  95. <ActionRow
  96. {...{
  97. onLayoutChange,
  98. setShowPreviews,
  99. onSortChange,
  100. onTagFilterChange,
  101. query,
  102. showPreviews,
  103. }}
  104. />
  105. <PreviewsSystemRequirements
  106. bottomSpacing={3}
  107. showPreviews={showPreviews}
  108. onRemove={() => setShowPreviews(false)}
  109. />
  110. <CustomScrollbar>
  111. <SearchResults
  112. results={results}
  113. loading={loading}
  114. onTagSelected={onTagAdd}
  115. editable={false}
  116. onToggleSection={onToggleSection}
  117. layout={query.layout}
  118. showPreviews={showPreviews}
  119. />
  120. </CustomScrollbar>
  121. </div>
  122. </div>
  123. </div>
  124. );
  125. });
  126. DashboardSearchOLD.displayName = 'DashboardSearchOLD';
  127. const getStyles = stylesFactory((theme: GrafanaTheme2) => {
  128. return {
  129. overlay: css`
  130. left: 0;
  131. top: 0;
  132. right: 0;
  133. bottom: 0;
  134. z-index: ${theme.zIndex.sidemenu};
  135. position: fixed;
  136. background: ${theme.colors.background.canvas};
  137. padding: ${theme.spacing(1)};
  138. ${theme.breakpoints.up('md')} {
  139. left: ${theme.components.sidemenu.width}px;
  140. z-index: ${theme.zIndex.navbarFixed + 1};
  141. padding: ${theme.spacing(2)};
  142. }
  143. `,
  144. container: css`
  145. display: flex;
  146. flex-direction: column;
  147. max-width: 1400px;
  148. margin: 0 auto;
  149. padding: ${theme.spacing(1)};
  150. background: ${theme.colors.background.primary};
  151. border: 1px solid ${theme.components.panel.borderColor};
  152. height: 100%;
  153. ${theme.breakpoints.up('md')} {
  154. padding: ${theme.spacing(3)};
  155. }
  156. `,
  157. closeBtn: css`
  158. right: -5px;
  159. top: 2px;
  160. z-index: 1;
  161. position: absolute;
  162. `,
  163. searchField: css`
  164. position: relative;
  165. `,
  166. search: css`
  167. display: flex;
  168. flex-direction: column;
  169. overflow: hidden;
  170. height: 100%;
  171. padding: ${theme.spacing(2, 0, 3, 0)};
  172. `,
  173. input: css`
  174. box-sizing: border-box;
  175. outline: none;
  176. background-color: transparent;
  177. background: transparent;
  178. border-bottom: 2px solid ${theme.v1.colors.border1};
  179. font-size: 20px;
  180. line-height: 38px;
  181. width: 100%;
  182. &::placeholder {
  183. color: ${theme.v1.colors.textWeak};
  184. }
  185. `,
  186. };
  187. });