Skip to content

Commit 3c4807a

Browse files
committed
Refactor Table to use flex layouts
1 parent 664debc commit 3c4807a

File tree

4 files changed

+152
-140
lines changed

4 files changed

+152
-140
lines changed

packages/gitbook/src/components/DocumentView/Table/RecordColumnValue.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { ContentRef, DocumentBlockTable } from '@gitbook/api';
22
import { Icon } from '@gitbook/icons';
33
import assertNever from 'assert-never';
44

5-
import { Checkbox, Emoji } from '@/components/primitives';
5+
import { Checkbox } from '@/components/primitives';
66
import { StyledLink } from '@/components/primitives';
77
import { Image } from '@/components/utils';
88
import { getNodeFragmentByName } from '@/lib/document';
Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,56 @@
11
import { DocumentTableViewGrid } from '@gitbook/api';
2+
import React from 'react';
23

34
import { tcls } from '@/lib/tailwind';
45

56
import { RecordColumnValue } from './RecordColumnValue';
67
import { TableRecordKV, TableViewProps } from './Table';
8+
import { getColumnWidth } from './ViewGrid';
79

8-
export async function RecordRow(
10+
export function RecordRow(
911
props: TableViewProps<DocumentTableViewGrid> & {
1012
record: TableRecordKV;
13+
autoSizedColumns: string[];
14+
fixedColumns: string[];
1115
},
1216
) {
13-
const { view } = props;
14-
const columnsLengthThreshold = view.columns.length >= 7;
15-
16-
const tableTR = columnsLengthThreshold
17-
? ['[&>*+*]:border-l', '[&>*]:px-4']
18-
: ['[&>*+*]:border-l', '[&>*+*]:pl-4'];
17+
const { view, autoSizedColumns, fixedColumns } = props;
1918

2019
return (
21-
<tr className={tcls(tableTR, 'border-dark/3', 'dark:border-light/2')}>
20+
<div
21+
className={tcls('flex', 'border-dark/3', 'dark:border-light/2', '[&>*+*]:border-l')}
22+
role="row"
23+
>
2224
{view.columns.map((column) => {
25+
const columnWidth = getColumnWidth({
26+
column,
27+
columnWidths: view.columnWidths,
28+
autoSizedColumns,
29+
fixedColumns,
30+
});
2331
return (
24-
<td
32+
<div
2533
key={column}
34+
role="cell"
2635
className={tcls(
36+
'flex-1',
2737
'align-middle',
28-
'min-w-[8rem]',
2938
'border-dark/2',
30-
'py-3',
39+
'py-2',
40+
'px-3',
3141
'text-sm',
3242
'lg:text-base',
3343
'dark:border-light/2',
3444
)}
45+
style={{
46+
width: columnWidth,
47+
minWidth: columnWidth || '100px',
48+
}}
3549
>
36-
<RecordColumnValue key={column} {...props} column={column} />
37-
</td>
50+
<RecordColumnValue {...props} column={column} />
51+
</div>
3852
);
3953
})}
40-
</tr>
54+
</div>
4155
);
4256
}
Lines changed: 93 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { DocumentTableViewGrid } from '@gitbook/api';
2+
import * as React from 'react';
23

34
import { tcls } from '@/lib/tailwind';
45

@@ -7,159 +8,126 @@ import { TableViewProps } from './Table';
78
import styles from './table.module.css';
89
import { getColumnAlignment } from './utils';
910

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+
*/
1016
export function ViewGrid(props: TableViewProps<DocumentTableViewGrid>) {
1117
const { block, view, records, style } = props;
12-
const columnsOverThreshold = view.columns.length >= 7;
1318

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]);
2623

27-
const tableTR = columnsOverThreshold
28-
? ['[&>*+*]:border-l', '[&>*]:px-4']
29-
: ['[&>*+*]:border-l', '[&>*+*]:px-4'];
24+
const tableWidth = autoSizedColumns.length > 0 ? 'w-full' : 'w-fit';
3025

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 */
3427
const withHeader =
3528
!view.hideHeader &&
3629
view.columns.some((columnId) => block.data.definition[columnId].title.trim().length > 0);
3730

3831
return (
3932
<div
40-
className={`${tcls(style, 'relative', 'grid', tableWrapper, styles.progressContainer)}`}
33+
className={tcls(
34+
style,
35+
styles.progressContainer,
36+
styles.tableWrapper,
37+
'relative',
38+
'grid',
39+
'w-full',
40+
'overflow-x-auto',
41+
'overflow-y-hidden',
42+
'mx-auto',
43+
)}
4144
>
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)}>
45+
{/* Table */}
46+
<div role="table" className={tcls('flex', 'flex-col')}>
47+
{/* Header */}
48+
{withHeader && (
49+
<div role="rowgroup" className={tcls('flex flex-col', tableWidth)}>
50+
<div role="row" className={tcls('flex', 'w-full', '[&>*+*]:border-l')}>
12751
{view.columns.map((column) => {
128-
const columnWidth = view.columnWidths?.[column];
12952
const alignment = getColumnAlignment(block.data.definition[column]);
130-
13153
return (
132-
<th
54+
<div
13355
key={column}
56+
role="columnheader"
13457
className={tcls(
135-
tableTH,
13658
'align-middle',
137-
'text-balance',
138-
'border-b',
139-
'border-b-dark/5',
14059
'text-left',
141-
'text-xs',
142-
'lg:text-base',
60+
'text-base',
61+
'font-medium',
62+
'border-b',
14363
'dark:border-l-light/2',
14464
'dark:border-b-light/4',
65+
'py-2',
66+
'px-3',
14567
alignment === 'right' ? 'text-right' : null,
14668
alignment === 'center' ? 'text-center' : null,
14769
)}
148-
style={columnWidth ? { width: columnWidth } : undefined}
70+
style={{
71+
width: getColumnWidth({
72+
column,
73+
columnWidths,
74+
autoSizedColumns,
75+
fixedColumns,
76+
}),
77+
minWidth: columnWidths?.[column] || '100px',
78+
}}
79+
title={block.data.definition[column].title}
14980
>
15081
{block.data.definition[column].title}
151-
</th>
82+
</div>
15283
);
15384
})}
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>
85+
</div>
86+
</div>
87+
)}
88+
<div
89+
role="rowgroup"
90+
className={tcls('flex', 'flex-col', tableWidth, '[&>*+*]:border-t')}
91+
>
92+
{records.map((record) => (
93+
<RecordRow
94+
key={record[0]}
95+
record={record}
96+
autoSizedColumns={autoSizedColumns}
97+
fixedColumns={fixedColumns}
98+
{...props}
99+
/>
100+
))}
101+
</div>
102+
</div>
163103
</div>
164104
);
165105
}
106+
107+
export const getColumnWidth = ({
108+
column,
109+
columnWidths,
110+
autoSizedColumns,
111+
fixedColumns,
112+
}: {
113+
column: string;
114+
columnWidths: Record<string, number> | undefined;
115+
autoSizedColumns: string[];
116+
fixedColumns: string[];
117+
}) => {
118+
const columnWidth = columnWidths?.[column];
119+
120+
/* Column was explicitly set by user or user turned off auto-sizing (in that case, columnWidth should've also been set to 100px) */
121+
if (columnWidth) return `${columnWidth}px`;
122+
123+
/* Fallback minimum width for columns, so the columns don't become unreadable from being too narrow and instead table will become scrollable. */
124+
const minAutoColumnWidth = '100px';
125+
126+
const totalFixedWidth = fixedColumns.reduce((sum, col) => {
127+
return sum + (columnWidths?.[col] || 0);
128+
}, 0);
129+
130+
/* Column should use auto-sizing, which means it grows to fill available space */
131+
const availableWidth = `calc((100% - ${totalFixedWidth}px) / ${autoSizedColumns.length})`;
132+
return `clamp(${minAutoColumnWidth}, ${availableWidth}, 100%)`;
133+
};

packages/gitbook/src/components/DocumentView/Table/table.module.css

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,33 @@
1+
/* Detect whether a scrollbar exists on the table */
2+
@keyframes detect-scroll {
3+
from,
4+
to {
5+
--can-scroll: ;
6+
}
7+
}
8+
9+
/* Apply styles to the Table if scrollbar exists */
10+
.tableWrapper {
11+
animation: detect-scroll linear;
12+
animation-timeline: scroll(self x);
13+
14+
--border-radius-if-can-scroll: var(--can-scroll) 0.375rem;
15+
--border-radius-if-cant-scroll: 0;
16+
border-radius: var(--border-radius-if-can-scroll, var(--border-radius-if-cant-scroll));
17+
18+
--border-width-if-can-scroll: var(--can-scroll) 1px;
19+
--border-width-if-cant-scroll: 0;
20+
border-width: var(--border-width-if-can-scroll, var(--border-width-if-cant-scroll));
21+
22+
@apply border-dark/3;
23+
}
24+
25+
:global(.dark) .tableWrapper {
26+
@apply border-light/2;
27+
}
28+
29+
/* Styles for circular scroll progress animation on scrollable tables */
30+
131
/* Hide it on browsers (firefox / safari) that do not support it */
232
.progressOpacitySharp {
333
display: none;

0 commit comments

Comments
 (0)