table.tsx 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import React, {useCallback, useEffect, useMemo, useRef} from 'react';
  2. import styled from '@emotion/styled';
  3. import {COL_WIDTH_MINIMUM} from 'sentry/components/gridEditable';
  4. import type {Alignments} from 'sentry/components/gridEditable/sortLink';
  5. import {
  6. Body as _TableWrapper,
  7. Grid as _Table,
  8. GridBody,
  9. GridBodyCell,
  10. GridBodyCellStatus,
  11. GridHead,
  12. GridHeadCell,
  13. GridRow,
  14. Header,
  15. HeaderButtonContainer,
  16. HeaderTitle,
  17. } from 'sentry/components/gridEditable/styles';
  18. import {space} from 'sentry/styles/space';
  19. import {Actions} from 'sentry/views/discover/table/cellAction';
  20. interface TableProps extends React.ComponentProps<typeof _TableWrapper> {}
  21. export const Table = React.forwardRef<HTMLTableElement, TableProps>(
  22. ({children, styles, ...props}, ref) => (
  23. <_TableWrapper {...props}>
  24. <_Table ref={ref} style={styles}>
  25. {children}
  26. </_Table>
  27. </_TableWrapper>
  28. )
  29. );
  30. interface TableStatusProps {
  31. children: React.ReactNode;
  32. }
  33. export function TableStatus({children}: TableStatusProps) {
  34. return (
  35. <GridRow>
  36. <GridBodyCellStatus>{children}</GridBodyCellStatus>
  37. </GridRow>
  38. );
  39. }
  40. export const ALLOWED_CELL_ACTIONS: Actions[] = [
  41. Actions.ADD,
  42. Actions.EXCLUDE,
  43. Actions.SHOW_GREATER_THAN,
  44. Actions.SHOW_LESS_THAN,
  45. ];
  46. const MINIMUM_COLUMN_WIDTH = COL_WIDTH_MINIMUM;
  47. export function useTableStyles(
  48. fields: string[],
  49. tableRef: React.RefObject<HTMLDivElement>,
  50. minimumColumnWidth = MINIMUM_COLUMN_WIDTH
  51. ) {
  52. const resizingColumnIndex = useRef<number | null>(null);
  53. const columnWidthsRef = useRef<(number | null)[]>(fields.map(() => null));
  54. useEffect(() => {
  55. columnWidthsRef.current = fields.map(
  56. (_, index) => columnWidthsRef.current[index] ?? null
  57. );
  58. }, [fields]);
  59. const initialTableStyles = useMemo(
  60. () => ({
  61. gridTemplateColumns: fields
  62. .map(() => `minmax(${minimumColumnWidth}px, auto)`)
  63. .join(' '),
  64. }),
  65. [fields, minimumColumnWidth]
  66. );
  67. const onResizeMouseDown = useCallback(
  68. (event: React.MouseEvent<HTMLDivElement>, index: number) => {
  69. event.preventDefault();
  70. // <GridResizer> is expected to be nested 1 level down from <GridHeadCell>
  71. const cell = event.currentTarget!.parentElement;
  72. if (!cell) {
  73. return;
  74. }
  75. resizingColumnIndex.current = index;
  76. const startX = event.clientX;
  77. const initialWidth = cell.offsetWidth;
  78. const gridElement = tableRef.current;
  79. function onMouseMove(e: MouseEvent) {
  80. if (resizingColumnIndex.current === null || !gridElement) {
  81. return;
  82. }
  83. const newWidth = Math.max(
  84. MINIMUM_COLUMN_WIDTH,
  85. initialWidth + (e.clientX - startX)
  86. );
  87. columnWidthsRef.current[index] = newWidth;
  88. // Updating the grid's `gridTemplateColumns` directly
  89. gridElement.style.gridTemplateColumns = columnWidthsRef.current
  90. .map(width => {
  91. return typeof width === 'number'
  92. ? `${width}px`
  93. : `minmax(${minimumColumnWidth}px, auto)`;
  94. })
  95. .join(' ');
  96. }
  97. function onMouseUp() {
  98. resizingColumnIndex.current = null;
  99. // Cleaning up event listeners
  100. window.removeEventListener('mousemove', onMouseMove);
  101. window.removeEventListener('mouseup', onMouseUp);
  102. }
  103. window.addEventListener('mousemove', onMouseMove);
  104. window.addEventListener('mouseup', onMouseUp);
  105. },
  106. [tableRef, minimumColumnWidth]
  107. );
  108. return {initialTableStyles, onResizeMouseDown};
  109. }
  110. export const TableBody = GridBody;
  111. export const TableRow = GridRow;
  112. export const TableBodyCell = GridBodyCell;
  113. export const TableHead = GridHead;
  114. export const TableHeader = Header;
  115. export const TableHeaderActions = HeaderButtonContainer;
  116. export const TableHeaderTitle = HeaderTitle;
  117. export const TableHeadCell = styled(GridHeadCell)<{align?: Alignments}>`
  118. ${p => p.align && `justify-content: ${p.align};`}
  119. `;
  120. export const TableHeadCellContent = styled('div')`
  121. display: flex;
  122. align-items: center;
  123. gap: ${space(0.5)};
  124. cursor: pointer;
  125. `;