toolbar.tsx 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. import type {useSortable} from '@dnd-kit/sortable';
  2. import styled from '@emotion/styled';
  3. import {Button} from 'sentry/components/button';
  4. import {IconCopy, IconDelete, IconEdit, IconGrabbable} from 'sentry/icons';
  5. import {t} from 'sentry/locale';
  6. import {space} from 'sentry/styles/space';
  7. import {DRAG_HANDLE_CLASS} from '../dashboard';
  8. type DraggableProps = Pick<ReturnType<typeof useSortable>, 'attributes' | 'listeners'>;
  9. type ToolbarProps = {
  10. draggableProps?: DraggableProps;
  11. hideToolbar?: boolean;
  12. isMobile?: boolean;
  13. onDelete?: () => void;
  14. onDuplicate?: () => void;
  15. onEdit?: () => void;
  16. };
  17. export function Toolbar({
  18. hideToolbar,
  19. isMobile,
  20. onEdit,
  21. onDelete,
  22. onDuplicate,
  23. draggableProps,
  24. }: ToolbarProps) {
  25. return (
  26. <ToolbarPanel>
  27. <IconContainer style={{visibility: hideToolbar ? 'hidden' : 'visible'}}>
  28. {!isMobile && (
  29. <GrabbableButton
  30. size="xs"
  31. aria-label={t('Drag Widget')}
  32. icon={<IconGrabbable />}
  33. borderless
  34. className={DRAG_HANDLE_CLASS}
  35. {...draggableProps?.listeners}
  36. {...draggableProps?.attributes}
  37. />
  38. )}
  39. {onEdit && (
  40. <Button
  41. data-test-id="widget-edit"
  42. aria-label={t('Edit Widget')}
  43. size="xs"
  44. borderless
  45. onClick={onEdit}
  46. icon={<IconEdit />}
  47. />
  48. )}
  49. {onDuplicate && (
  50. <Button
  51. aria-label={t('Duplicate Widget')}
  52. size="xs"
  53. borderless
  54. onClick={onDuplicate}
  55. icon={<IconCopy />}
  56. />
  57. )}
  58. {onDelete && (
  59. <Button
  60. data-test-id="widget-delete"
  61. aria-label={t('Delete Widget')}
  62. borderless
  63. size="xs"
  64. onClick={onDelete}
  65. icon={<IconDelete />}
  66. />
  67. )}
  68. </IconContainer>
  69. </ToolbarPanel>
  70. );
  71. }
  72. const ToolbarPanel = styled('div')`
  73. position: absolute;
  74. top: 0;
  75. left: 0;
  76. z-index: 2;
  77. width: 100%;
  78. height: 100%;
  79. display: flex;
  80. justify-content: flex-end;
  81. align-items: flex-start;
  82. background-color: ${p => p.theme.overlayBackgroundAlpha};
  83. border-radius: calc(${p => p.theme.panelBorderRadius} - 1px);
  84. `;
  85. const IconContainer = styled('div')`
  86. display: flex;
  87. margin: ${space(1)};
  88. touch-action: none;
  89. `;
  90. const GrabbableButton = styled(Button)`
  91. cursor: grab;
  92. `;
  93. export const WidgetTitleRow = styled('span')`
  94. display: flex;
  95. align-items: center;
  96. gap: ${space(0.75)};
  97. `;
  98. export const WidgetDescription = styled('small')`
  99. ${p => p.theme.overflowEllipsis}
  100. color: ${p => p.theme.gray300};
  101. `;