1
- import { useMemo } from 'react' ;
1
+ import React , { useCallback , useEffect , useMemo , useRef } from 'react' ;
2
2
import styled from '@emotion/styled' ;
3
3
4
4
import { COL_WIDTH_MINIMUM } from 'sentry/components/gridEditable' ;
@@ -16,17 +16,20 @@ import {
16
16
HeaderButtonContainer ,
17
17
HeaderTitle ,
18
18
} from 'sentry/components/gridEditable/styles' ;
19
+ import { space } from 'sentry/styles/space' ;
19
20
import { Actions } from 'sentry/views/discover/table/cellAction' ;
20
21
21
22
interface TableProps extends React . ComponentProps < typeof _TableWrapper > { }
22
23
23
- export function Table ( { children , style , ... props } : TableProps ) {
24
- return (
24
+ export const Table = React . forwardRef < HTMLTableElement , TableProps > (
25
+ ( { children , styles , ... props } , ref ) => (
25
26
< _TableWrapper { ...props } >
26
- < _Table style = { style } > { children } </ _Table >
27
+ < _Table ref = { ref } style = { styles } >
28
+ { children }
29
+ </ _Table >
27
30
</ _TableWrapper >
28
- ) ;
29
- }
31
+ )
32
+ ) ;
30
33
31
34
interface TableStatusProps {
32
35
children : React . ReactNode ;
@@ -49,38 +52,83 @@ export const ALLOWED_CELL_ACTIONS: Actions[] = [
49
52
50
53
const MINIMUM_COLUMN_WIDTH = COL_WIDTH_MINIMUM ;
51
54
52
- type Item = {
53
- label : React . ReactNode ;
54
- value : string ;
55
- width ?: number | 'min-content' ;
56
- } ;
55
+ export function useTableStyles (
56
+ fields : string [ ] ,
57
+ tableRef : React . RefObject < HTMLDivElement > ,
58
+ minimumColumnWidth = MINIMUM_COLUMN_WIDTH
59
+ ) {
60
+ const resizingColumnIndex = useRef < number | null > ( null ) ;
61
+ const columnWidthsRef = useRef < ( number | null ) [ ] > ( fields . map ( ( ) => null ) ) ;
57
62
58
- interface UseTableStylesOptions {
59
- items : Item [ ] ;
60
- minimumColumnWidth ?: number ;
61
- }
63
+ useEffect ( ( ) => {
64
+ columnWidthsRef . current = fields . map (
65
+ ( _ , index ) => columnWidthsRef . current [ index ] ?? null
66
+ ) ;
67
+ } , [ fields ] ) ;
68
+
69
+ const initialTableStyles = useMemo (
70
+ ( ) => ( {
71
+ gridTemplateColumns : fields
72
+ . map ( ( ) => `minmax(${ minimumColumnWidth } px, auto)` )
73
+ . join ( ' ' ) ,
74
+ } ) ,
75
+ [ fields , minimumColumnWidth ]
76
+ ) ;
77
+
78
+ const onResizeMouseDown = useCallback (
79
+ ( event : React . MouseEvent < HTMLDivElement > , index : number ) => {
80
+ event . preventDefault ( ) ;
81
+
82
+ // <GridResizer> is expected to be nested 1 level down from <GridHeadCell>
83
+ const cell = event . currentTarget ! . parentElement ;
84
+ if ( ! cell ) {
85
+ return ;
86
+ }
87
+
88
+ resizingColumnIndex . current = index ;
62
89
63
- export function useTableStyles ( {
64
- items,
65
- minimumColumnWidth = MINIMUM_COLUMN_WIDTH ,
66
- } : UseTableStylesOptions ) {
67
- const tableStyles = useMemo ( ( ) => {
68
- const columns = new Array ( items . length ) ;
69
-
70
- for ( let i = 0 ; i < items . length ; i ++ ) {
71
- if ( typeof items [ i ] . width === 'number' ) {
72
- columns [ i ] = `${ items [ i ] . width } px` ;
73
- } else {
74
- columns [ i ] = items [ i ] . width ?? `minmax(${ minimumColumnWidth } px, auto)` ;
90
+ const startX = event . clientX ;
91
+ const initialWidth = cell . offsetWidth ;
92
+
93
+ const gridElement = tableRef . current ;
94
+
95
+ function onMouseMove ( e : MouseEvent ) {
96
+ if ( resizingColumnIndex . current === null || ! gridElement ) {
97
+ return ;
98
+ }
99
+
100
+ const newWidth = Math . max (
101
+ MINIMUM_COLUMN_WIDTH ,
102
+ initialWidth + ( e . clientX - startX )
103
+ ) ;
104
+
105
+ columnWidthsRef . current [ index ] = newWidth ;
106
+
107
+ // Updating the grid's `gridTemplateColumns` directly
108
+ gridElement . style . gridTemplateColumns = columnWidthsRef . current
109
+ . map ( width => {
110
+ return typeof width === 'number'
111
+ ? `${ width } px`
112
+ : `minmax(${ minimumColumnWidth } px, auto)` ;
113
+ } )
114
+ . join ( ' ' ) ;
75
115
}
76
- }
77
116
78
- return {
79
- gridTemplateColumns : columns . join ( ' ' ) ,
80
- } ;
81
- } , [ items , minimumColumnWidth ] ) ;
117
+ function onMouseUp ( ) {
118
+ resizingColumnIndex . current = null ;
82
119
83
- return { tableStyles} ;
120
+ // Cleaning up event listeners
121
+ window . removeEventListener ( 'mousemove' , onMouseMove ) ;
122
+ window . removeEventListener ( 'mouseup' , onMouseUp ) ;
123
+ }
124
+
125
+ window . addEventListener ( 'mousemove' , onMouseMove ) ;
126
+ window . addEventListener ( 'mouseup' , onMouseUp ) ;
127
+ } ,
128
+ [ tableRef , minimumColumnWidth ]
129
+ ) ;
130
+
131
+ return { initialTableStyles, onResizeMouseDown} ;
84
132
}
85
133
86
134
export const TableBody = GridBody ;
@@ -94,3 +142,9 @@ export const TableHeaderTitle = HeaderTitle;
94
142
export const TableHeadCell = styled ( GridHeadCell ) < { align ?: Alignments } > `
95
143
${ p => p . align && `justify-content: ${ p . align } ;` }
96
144
` ;
145
+ export const TableHeadCellContent = styled ( 'div' ) `
146
+ display: flex;
147
+ align-items: center;
148
+ gap: ${ space ( 0.5 ) } ;
149
+ cursor: pointer;
150
+ ` ;
0 commit comments