Skip to content

Commit a2936ed

Browse files
committed
fix sticky header
1 parent a417260 commit a2936ed

File tree

6 files changed

+110
-40
lines changed

6 files changed

+110
-40
lines changed

frontend/src/components/ui/data-grid/data-grid.ts

+59-22
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,12 @@ export class DataGrid extends TailwindElement {
5757
items?: GridItem[];
5858

5959
/**
60-
* Stick header row to the top of the viewport.
60+
* Stick header row to the top of the table or the viewport.
61+
*
62+
* Horizontal scroll will be disabled if the header sticks to the viewport.
6163
*/
62-
@property({ type: Boolean })
63-
stickyHeader = false;
64+
@property({ type: String })
65+
stickyHeader?: "table" | "viewport";
6466

6567
/**
6668
* Item key to use as the row key, like an ID.
@@ -134,20 +136,43 @@ export class DataGrid extends TailwindElement {
134136
render() {
135137
if (!this.columns?.length) return;
136138

137-
const cssWidths = this.columns.map((col) => col.width ?? "1fr");
138-
139139
return html`
140140
<slot name="label">
141141
<label id=${this.formControlLabelId} class="form-label text-xs">
142142
${this.formControlLabel}
143143
</label>
144144
</slot>
145+
<div
146+
class=${clsx(
147+
this.stickyHeader === "table" && tw`overflow-x-auto overscroll-none`,
148+
this.stickyHeader && tw`rounded border`,
149+
)}
150+
>
151+
${this.renderTable()}
152+
</div>
145153
154+
${this.addRows && !this.addRowsInputValue
155+
? this.renderAddButton()
156+
: nothing}
157+
`;
158+
}
159+
160+
private renderTable() {
161+
if (!this.columns?.length) return;
162+
163+
const cssWidths = this.columns.map((col) => col.width ?? "1fr");
164+
const showFooter = this.addRows && this.addRowsInputValue;
165+
166+
return html`
146167
<btrix-table
147168
role="grid"
148169
class=${clsx(
149-
tw`relative size-full overflow-auto`,
150-
this.stickyHeader && tw`rounded border`,
170+
tw`relative size-full min-w-0`,
171+
// Add background color for overscroll:
172+
this.stickyHeader && tw`rounded bg-neutral-50`,
173+
// Height is required for sticky + horizontal scrolling to work:
174+
this.stickyHeader === "table" &&
175+
tw`max-h-[calc(100vh-4rem)] overflow-y-auto`,
151176
)}
152177
style="--btrix-table-grid-template-columns: ${cssWidths.join(" ")}${this
153178
.removeRows
@@ -168,14 +193,16 @@ export class DataGrid extends TailwindElement {
168193
>
169194
${this.columns.map(
170195
(col) => html`
171-
<btrix-table-header-cell>
196+
<btrix-table-header-cell
197+
class=${clsx(col.description && tw`flex-wrap`)}
198+
>
172199
${col.label}
173200
${col.description
174201
? html`
175202
<sl-tooltip content=${col.description}>
176203
<sl-icon
177204
name="info-circle"
178-
class="ml-1.5 align-[-.175em] text-sm text-slate-500"
205+
class="ml-1.5 flex-shrink-0 align-[-.175em] text-sm text-slate-500"
179206
></sl-icon>
180207
</sl-tooltip>
181208
`
@@ -192,8 +219,11 @@ export class DataGrid extends TailwindElement {
192219
<btrix-table-body
193220
class=${clsx(
194221
tw`[--btrix-table-cell-padding:var(--sl-spacing-x-small)]`,
195-
tw`leading-none`,
196-
!this.stickyHeader && tw`rounded border`,
222+
tw`bg-[var(--sl-panel-background-color)] leading-none`,
223+
!this.stickyHeader && [
224+
tw`border`,
225+
showFooter ? tw`rounded-t` : tw`rounded`,
226+
],
197227
)}
198228
@btrix-remove=${(e: CustomEvent<RowRemoveEventDetail>) => {
199229
const { key } = e.detail;
@@ -206,10 +236,21 @@ export class DataGrid extends TailwindElement {
206236
}}
207237
>
208238
${this.renderRows()}
209-
${this.addRows && this.addRowsInputValue
210-
? html`
211-
<btrix-table-row class="border-t">
212-
<btrix-table-cell class="col-span-full px-1">
239+
</btrix-table-body>
240+
241+
${showFooter
242+
? html`
243+
<btrix-table-footer
244+
class=${clsx(
245+
tw`[--btrix-table-cell-padding-x:var(--sl-spacing-2x-small)] [--btrix-table-cell-padding-y:var(--sl-spacing-x-small)]`,
246+
tw`bg-[var(--sl-panel-background-color)]`,
247+
this.stickyHeader
248+
? tw`border-t`
249+
: tw`rounded-b border-x border-b`,
250+
)}
251+
>
252+
<btrix-table-row>
253+
<btrix-table-cell class="col-span-full">
213254
<!-- TODO Replace navigation button -->
214255
<btrix-navigation-button
215256
size="small"
@@ -242,14 +283,10 @@ export class DataGrid extends TailwindElement {
242283
</span>
243284
</btrix-table-cell>
244285
</btrix-table-row>
245-
`
246-
: nothing}
247-
</btrix-table-body>
286+
</btrix-table-footer>
287+
`
288+
: nothing}
248289
</btrix-table>
249-
250-
${this.addRows && !this.addRowsInputValue
251-
? this.renderAddButton()
252-
: nothing}
253290
`;
254291
}
255292

Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import "./table";
22
import "./table-body";
33
import "./table-cell";
4+
import "./table-footer";
45
import "./table-head";
56
import "./table-header-cell";
67
import "./table-row";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { customElement } from "lit/decorators.js";
2+
3+
import { TableBody } from "./table-body";
4+
5+
@customElement("btrix-table-footer")
6+
export class TableFooter extends TableBody {}

frontend/src/features/org/usage-history-table.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ export class UsageHistoryTable extends BtrixElement {
161161
<btrix-data-grid
162162
.columns=${cols}
163163
.items=${items}
164-
stickyHeader
164+
stickyHeader="viewport"
165165
></btrix-data-grid>
166166
`;
167167
}

frontend/src/stories/components/DataGrid.stories.ts

+29-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import type { Meta, StoryObj } from "@storybook/web-components";
22
import { html } from "lit";
33

4-
import { defaultArgs, renderComponent, type RenderProps } from "./DataGrid";
4+
import {
5+
defaultArgs,
6+
makeItems,
7+
renderComponent,
8+
type RenderProps,
9+
} from "./DataGrid";
510
import {
611
dataGridDecorator,
712
formControlName,
@@ -37,11 +42,28 @@ export const Basic: Story = {
3742
};
3843

3944
/**
40-
* The table header can stick to the top of the containing element.
45+
* The table header can stick to the top of the table.
46+
*/
47+
export const StickyHeaderTable: Story = {
48+
name: "Sticky Header to Table",
49+
args: {
50+
stickyHeader: "table",
51+
items: makeItems(50),
52+
},
53+
};
54+
55+
/**
56+
* The table header can stick to the top of the viewport.
57+
*
58+
* A caveat is that this will disable horizontal scrolling the table,
59+
* so care should be taken that the table will render correctly in
60+
* smaller screen sizes.
4161
*/
42-
export const StickyHeader: Story = {
62+
export const StickyHeaderViewport: Story = {
63+
name: "Sticky Header to Viewport",
4364
args: {
44-
stickyHeader: true,
65+
stickyHeader: "viewport",
66+
items: makeItems(50),
4567
},
4668
};
4769

@@ -181,7 +203,7 @@ export const FormControl: Story = {
181203
return html`
182204
<btrix-syntax-input
183205
name="selector"
184-
class="flex-1 [--sl-input-border-radius-medium:0] [--sl-input-border-color:transparent]"
206+
class="flex-1 [--sl-input-border-color:transparent] [--sl-input-border-radius-medium:0]"
185207
value=${item.selector || ""}
186208
language="css"
187209
></btrix-syntax-input>
@@ -242,8 +264,9 @@ export const FormControl: Story = {
242264
context.rowsController
243265
}
244266
formControlLabel="Page QA Table"
245-
stickyHeader
267+
stickyHeader="table"
246268
addRows
269+
addRowsInputValue="10"
247270
removeRows
248271
editCells
249272
>

frontend/src/stories/components/DataGrid.ts

+14-11
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,24 @@ import "@/components/ui/data-grid";
88

99
export type RenderProps = Pick<DataGrid, keyof DataGrid>;
1010

11+
export const makeItems = (n: number) =>
12+
Array.from({ length: n }).map((_, i) => ({
13+
...columns.reduce(
14+
(obj, { field, label }) => ({
15+
...obj,
16+
[field]: `${label}${i + 1}`,
17+
}),
18+
{},
19+
),
20+
id: nanoid(),
21+
})) satisfies RenderProps["items"];
22+
1123
const columns = "abcde".split("").map((field, i) => ({
1224
field,
1325
label: field.toUpperCase(),
1426
editable: i > 0,
1527
})) satisfies RenderProps["columns"];
16-
const items = Array.from({ length: 5 }).map((_, i) => ({
17-
...columns.reduce(
18-
(obj, { field, label }) => ({
19-
...obj,
20-
[field]: `${label}${i + 1}`,
21-
}),
22-
{},
23-
),
24-
id: nanoid(),
25-
})) satisfies RenderProps["items"];
28+
const items = makeItems(10);
2629

2730
export const defaultArgs = { columns, items } satisfies Pick<
2831
RenderProps,
@@ -46,7 +49,7 @@ export const renderComponent = ({
4649
.items=${items || defaultArgs.items}
4750
.defaultItem=${defaultItem}
4851
formControlLabel=${ifDefined(formControlLabel)}
49-
?stickyHeader=${stickyHeader}
52+
stickyHeader=${ifDefined(stickyHeader)}
5053
?addRows=${addRows}
5154
addRowsInputValue=${ifDefined(addRowsInputValue)}
5255
?removeRows=${removeRows}

0 commit comments

Comments
 (0)