1
1
import { DocumentTableViewGrid } from '@gitbook/api' ;
2
+ import * as React from 'react' ;
2
3
3
4
import { tcls } from '@/lib/tailwind' ;
4
5
@@ -7,159 +8,106 @@ import { TableViewProps } from './Table';
7
8
import styles from './table.module.css' ;
8
9
import { getColumnAlignment } from './utils' ;
9
10
11
+ /* Columns are sized in 3 ways:
12
+ 1. Set to auto-size by default, these columns share the available width
13
+ 2. Explicitly set by the user by dragging column separator (we then turn off auto-size)
14
+ 3. Auto-size is turned off without setting a width, we then default to a fixed width of 100px
15
+ */
10
16
export function ViewGrid ( props : TableViewProps < DocumentTableViewGrid > ) {
11
17
const { block, view, records, style } = props ;
12
- const columnsOverThreshold = view . columns . length >= 7 ;
13
18
14
- const tableWrapper = columnsOverThreshold
15
- ? [
16
- // has over X columns
17
- 'overflow-x-auto' ,
18
- 'overflow-y-hidden' ,
19
- 'mx-auto' ,
20
- 'rounded-md' ,
21
- 'border' ,
22
- 'border-dark/3' ,
23
- 'dark:border-light/2' ,
24
- ]
25
- : [ 'overflow-x-auto' , 'overflow-y-hidden' , 'mx-auto' ] ;
19
+ /* Calculate how many columns are auto-sized vs fixed width */
20
+ const columnWidths = view . columnWidths ;
21
+ const autoSizedColumns = view . columns . filter ( ( column ) => ! columnWidths ?. [ column ] ) ;
22
+ const fixedColumns = view . columns . filter ( ( column ) => columnWidths ?. [ column ] ) ;
26
23
27
- const tableTR = columnsOverThreshold
28
- ? [ '[&>*+*]:border-l' , '[&>*]:px-4' ]
29
- : [ '[&>*+*]:border-l' , '[&>*+*]:px-4' ] ;
24
+ const tableWidth = autoSizedColumns . length > 0 ? 'w-full' : 'w-fit' ;
30
25
31
- const tableTH = columnsOverThreshold ? [ 'py-3' ] : [ 'py-1' , 'pt-0' ] ;
32
-
33
- // Only show the header when configured and not empty
26
+ /* Only show the header when configured and not empty */
34
27
const withHeader =
35
28
! view . hideHeader &&
36
29
view . columns . some ( ( columnId ) => block . data . definition [ columnId ] . title . trim ( ) . length > 0 ) ;
37
30
38
31
return (
39
- < div
40
- className = { `${ tcls ( style , 'relative' , 'grid' , tableWrapper , styles . progressContainer ) } ` }
41
- >
42
- { /* ProgressScroller: */ }
43
- < div
44
- className = { `${ styles . progressOpacitySharp } ${ tcls (
45
- 'grid' ,
46
- 'items-center' ,
47
- 'grid-area-1-1' ,
48
- 'w-[5rem]' ,
49
- 'h-full' ,
50
- 'top-0' ,
51
- 'z-[1]' ,
52
- 'sticky' ,
53
- 'left-[calc(100%-5rem)]' ,
54
- 'pointer-events-none' ,
55
- ) } `}
56
- >
57
- < svg
58
- className = { `${ styles . progressSvg } ${ tcls (
59
- 'grid-area-1-1' ,
60
- 'relative' ,
61
- '[strokeDasharray:_0_100]' ,
62
- 'z-[1]' ,
63
- 'w-7' ,
64
- 'mt-3' ,
65
- 'mr-3' ,
66
- 'self-start' ,
67
- 'justify-self-end' ,
68
- 'stroke-primary-600' ,
69
- 'shadow-1xs' ,
70
- 'bg-light' ,
71
- 'ring-1' ,
72
- 'ring-inset' ,
73
- 'rounded-full' ,
74
- 'ring-dark/2' ,
75
- 'dark:ring-light/2' ,
76
- 'dark:bg-dark' ,
77
- 'dark:stroke-primary-400' ,
78
- ) } `}
79
- preserveAspectRatio = "xMaxYMid meet"
80
- width = "100%"
81
- viewBox = "0 0 26 26"
82
- fill = "none"
83
- xmlns = "http://www.w3.org/2000/svg"
84
- >
85
- < circle
86
- cx = "13"
87
- className = { `${ styles . strokeOpacityProgressInverted } ` }
88
- cy = "13"
89
- r = "12.5"
90
- fill = "none"
91
- stroke = "inherit"
92
- strokeWidth = "1.5"
93
- pathLength = "100"
94
- strokeLinecap = "round"
95
- strokeOpacity = { 0 }
96
- />
97
-
98
- < path
99
- strokeDasharray = "none"
100
- d = "M12 10L15 13L12 16"
101
- stroke = "inherit"
102
- fill = "none"
103
- strokeOpacity = { 0.64 }
104
- />
105
- </ svg >
106
-
107
- < div
108
- className = { `${ styles . progressOpacity } ${ tcls (
109
- 'bg-gradient-to-r' ,
110
- 'from-transparent' ,
111
- 'to-light' ,
112
- 'to-40%' ,
113
- 'grid-area-1-1' ,
114
- 'w-full' ,
115
- 'h-full' ,
116
- 'dark:from-transparent' ,
117
- 'dark:to-dark/10' ,
118
- ) } `}
119
- > </ div >
120
- </ div >
121
-
122
- { /* Table: */ }
123
- < table className = { tcls ( 'w-full' , 'grid-area-1-1' , 'table-auto' ) } >
124
- { withHeader ? (
125
- < thead >
126
- < tr className = { tcls ( tableTR ) } >
32
+ < div className = { tcls ( style , styles . tableWrapper ) } >
33
+ { /* Table */ }
34
+ < div role = "table" className = { tcls ( 'flex' , 'flex-col' ) } >
35
+ { /* Header */ }
36
+ { withHeader && (
37
+ < div role = "rowgroup" className = { tcls ( 'flex flex-col' , tableWidth ) } >
38
+ < div role = "row" className = { tcls ( 'flex' , 'w-full' , '[&>*+*]:border-l' ) } >
127
39
{ view . columns . map ( ( column ) => {
128
- const columnWidth = view . columnWidths ?. [ column ] ;
129
40
const alignment = getColumnAlignment ( block . data . definition [ column ] ) ;
130
-
131
41
return (
132
- < th
42
+ < div
133
43
key = { column }
44
+ role = "columnheader"
134
45
className = { tcls (
135
- tableTH ,
136
- 'align-middle' ,
137
- 'text-balance' ,
138
- 'border-b' ,
139
- 'border-b-dark/5' ,
140
- 'text-left' ,
141
- 'text-xs' ,
142
- 'lg:text-base' ,
143
- 'dark:border-l-light/2' ,
144
- 'dark:border-b-light/4' ,
46
+ styles . columnHeader ,
145
47
alignment === 'right' ? 'text-right' : null ,
146
48
alignment === 'center' ? 'text-center' : null ,
147
49
) }
148
- style = { columnWidth ? { width : columnWidth } : undefined }
50
+ style = { {
51
+ width : getColumnWidth ( {
52
+ column,
53
+ columnWidths,
54
+ autoSizedColumns,
55
+ fixedColumns,
56
+ } ) ,
57
+ minWidth : columnWidths ?. [ column ] || '100px' ,
58
+ } }
59
+ title = { block . data . definition [ column ] . title }
149
60
>
150
61
{ block . data . definition [ column ] . title }
151
- </ th >
62
+ </ div >
152
63
) ;
153
64
} ) }
154
- </ tr >
155
- </ thead >
156
- ) : null }
157
- < tbody className = { tcls ( '[&>*+*]:border-t' ) } >
158
- { records . map ( ( record ) => {
159
- return < RecordRow key = { record [ 0 ] } { ...props } record = { record } /> ;
160
- } ) }
161
- </ tbody >
162
- </ table >
65
+ </ div >
66
+ </ div >
67
+ ) }
68
+ < div
69
+ role = "rowgroup"
70
+ className = { tcls ( 'flex' , 'flex-col' , tableWidth , '[&>*+*]:border-t' ) }
71
+ >
72
+ { records . map ( ( record ) => (
73
+ < RecordRow
74
+ key = { record [ 0 ] }
75
+ record = { record }
76
+ autoSizedColumns = { autoSizedColumns }
77
+ fixedColumns = { fixedColumns }
78
+ { ...props }
79
+ />
80
+ ) ) }
81
+ </ div >
82
+ </ div >
163
83
</ div >
164
84
) ;
165
85
}
86
+
87
+ export const getColumnWidth = ( {
88
+ column,
89
+ columnWidths,
90
+ autoSizedColumns,
91
+ fixedColumns,
92
+ } : {
93
+ column : string ;
94
+ columnWidths : Record < string , number > | undefined ;
95
+ autoSizedColumns : string [ ] ;
96
+ fixedColumns : string [ ] ;
97
+ } ) => {
98
+ const columnWidth = columnWidths ?. [ column ] ;
99
+
100
+ /* Column was explicitly set by user or user turned off auto-sizing (in that case, columnWidth should've also been set to 100px) */
101
+ if ( columnWidth ) return `${ columnWidth } px` ;
102
+
103
+ /* Fallback minimum width for columns, so the columns don't become unreadable from being too narrow and instead table will become scrollable. */
104
+ const minAutoColumnWidth = '100px' ;
105
+
106
+ const totalFixedWidth = fixedColumns . reduce ( ( sum , col ) => {
107
+ return sum + ( columnWidths ?. [ col ] || 0 ) ;
108
+ } , 0 ) ;
109
+
110
+ /* Column should use auto-sizing, which means it grows to fill available space */
111
+ const availableWidth = `calc((100% - ${ totalFixedWidth } px) / ${ autoSizedColumns . length } )` ;
112
+ return `clamp(${ minAutoColumnWidth } , ${ availableWidth } , 100%)` ;
113
+ } ;
0 commit comments