OperationInfoButton.tsx 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import { css } from '@emotion/css';
  2. import React, { useState } from 'react';
  3. import { usePopperTooltip } from 'react-popper-tooltip';
  4. import { GrafanaTheme2, renderMarkdown } from '@grafana/data';
  5. import { FlexItem } from '@grafana/experimental';
  6. import { Button, Portal, useStyles2 } from '@grafana/ui';
  7. import { QueryBuilderOperation, QueryBuilderOperationDef } from './types';
  8. export interface Props {
  9. operation: QueryBuilderOperation;
  10. def: QueryBuilderOperationDef;
  11. }
  12. export const OperationInfoButton = React.memo<Props>(({ def, operation }) => {
  13. const styles = useStyles2(getStyles);
  14. const [show, setShow] = useState(false);
  15. const { getTooltipProps, setTooltipRef, setTriggerRef, visible } = usePopperTooltip({
  16. placement: 'top',
  17. visible: show,
  18. offset: [0, 16],
  19. onVisibleChange: setShow,
  20. interactive: true,
  21. trigger: ['click'],
  22. });
  23. return (
  24. <>
  25. <Button
  26. title="Click to show description"
  27. ref={setTriggerRef}
  28. icon="info-circle"
  29. size="sm"
  30. variant="secondary"
  31. fill="text"
  32. />
  33. {visible && (
  34. <Portal>
  35. <div ref={setTooltipRef} {...getTooltipProps()} className={styles.docBox}>
  36. <div className={styles.docBoxHeader}>
  37. <span>{def.renderer(operation, def, '<expr>')}</span>
  38. <FlexItem grow={1} />
  39. <Button
  40. icon="times"
  41. onClick={() => setShow(false)}
  42. fill="text"
  43. variant="secondary"
  44. title="Remove operation"
  45. />
  46. </div>
  47. <div
  48. className={styles.docBoxBody}
  49. dangerouslySetInnerHTML={{ __html: getOperationDocs(def, operation) }}
  50. ></div>
  51. </div>
  52. </Portal>
  53. )}
  54. </>
  55. );
  56. });
  57. OperationInfoButton.displayName = 'OperationDocs';
  58. const getStyles = (theme: GrafanaTheme2) => {
  59. return {
  60. docBox: css({
  61. overflow: 'hidden',
  62. background: theme.colors.background.primary,
  63. border: `1px solid ${theme.colors.border.strong}`,
  64. boxShadow: theme.shadows.z3,
  65. maxWidth: '600px',
  66. padding: theme.spacing(1),
  67. borderRadius: theme.shape.borderRadius(),
  68. zIndex: theme.zIndex.tooltip,
  69. }),
  70. docBoxHeader: css({
  71. fontSize: theme.typography.h5.fontSize,
  72. fontFamily: theme.typography.fontFamilyMonospace,
  73. paddingBottom: theme.spacing(1),
  74. display: 'flex',
  75. alignItems: 'center',
  76. }),
  77. docBoxBody: css({
  78. // The markdown paragraph has a marginBottom this removes it
  79. marginBottom: theme.spacing(-1),
  80. color: theme.colors.text.secondary,
  81. }),
  82. signature: css({
  83. fontSize: theme.typography.bodySmall.fontSize,
  84. fontFamily: theme.typography.fontFamilyMonospace,
  85. }),
  86. dropdown: css({
  87. opacity: 0,
  88. color: theme.colors.text.secondary,
  89. }),
  90. };
  91. };
  92. function getOperationDocs(def: QueryBuilderOperationDef, op: QueryBuilderOperation): string {
  93. return renderMarkdown(def.explainHandler ? def.explainHandler(op, def) : def.documentation ?? 'no docs');
  94. }