VariableLink.tsx 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import { css } from '@emotion/css';
  2. import React, { FC, MouseEvent, useCallback } from 'react';
  3. import { GrafanaTheme } from '@grafana/data';
  4. import { selectors } from '@grafana/e2e-selectors';
  5. import { Icon, Tooltip, useStyles } from '@grafana/ui';
  6. interface Props {
  7. onClick: () => void;
  8. text: string;
  9. loading: boolean;
  10. onCancel: () => void;
  11. /**
  12. * htmlFor, needed for the label
  13. */
  14. id: string;
  15. }
  16. export const VariableLink: FC<Props> = ({ loading, onClick: propsOnClick, text, onCancel, id }) => {
  17. const styles = useStyles(getStyles);
  18. const onClick = useCallback(
  19. (event: MouseEvent<HTMLButtonElement>) => {
  20. event.stopPropagation();
  21. event.preventDefault();
  22. propsOnClick();
  23. },
  24. [propsOnClick]
  25. );
  26. if (loading) {
  27. return (
  28. <div
  29. className={styles.container}
  30. data-testid={selectors.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts(`${text}`)}
  31. title={text}
  32. id={id}
  33. >
  34. <VariableLinkText text={text} />
  35. <LoadingIndicator onCancel={onCancel} />
  36. </div>
  37. );
  38. }
  39. return (
  40. <button
  41. onClick={onClick}
  42. className={styles.container}
  43. data-testid={selectors.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts(`${text}`)}
  44. aria-expanded={false}
  45. aria-controls={`options-${id}`}
  46. id={id}
  47. title={text}
  48. >
  49. <VariableLinkText text={text} />
  50. <Icon aria-hidden name="angle-down" size="sm" />
  51. </button>
  52. );
  53. };
  54. interface VariableLinkTextProps {
  55. text: string;
  56. }
  57. const VariableLinkText: FC<VariableLinkTextProps> = ({ text }) => {
  58. const styles = useStyles(getStyles);
  59. return <span className={styles.textAndTags}>{text}</span>;
  60. };
  61. const LoadingIndicator: FC<Pick<Props, 'onCancel'>> = ({ onCancel }) => {
  62. const onClick = useCallback(
  63. (event: MouseEvent) => {
  64. event.preventDefault();
  65. onCancel();
  66. },
  67. [onCancel]
  68. );
  69. return (
  70. <Tooltip content="Cancel query">
  71. <Icon
  72. className="spin-clockwise"
  73. name="sync"
  74. size="xs"
  75. onClick={onClick}
  76. aria-label={selectors.components.LoadingIndicator.icon}
  77. />
  78. </Tooltip>
  79. );
  80. };
  81. const getStyles = (theme: GrafanaTheme) => ({
  82. container: css`
  83. max-width: 500px;
  84. padding-right: 10px;
  85. padding: 0 ${theme.spacing.sm};
  86. background-color: ${theme.colors.formInputBg};
  87. border: 1px solid ${theme.colors.formInputBorder};
  88. border-radius: ${theme.border.radius.sm};
  89. display: flex;
  90. align-items: center;
  91. color: ${theme.colors.text};
  92. height: ${theme.height.md}px;
  93. .label-tag {
  94. margin: 0 5px;
  95. }
  96. `,
  97. textAndTags: css`
  98. overflow: hidden;
  99. text-overflow: ellipsis;
  100. white-space: nowrap;
  101. margin-right: ${theme.spacing.xxs};
  102. user-select: none;
  103. `,
  104. });