diff --git a/frontend/src/components/orgs-list.ts b/frontend/src/components/orgs-list.ts
index 9c87bbcdcc..f9df083b5c 100644
--- a/frontend/src/components/orgs-list.ts
+++ b/frontend/src/components/orgs-list.ts
@@ -115,7 +115,9 @@ export class OrgsList extends BtrixElement {
library="default"
>
-
+
@@ -144,7 +146,7 @@ export class OrgsList extends BtrixElement {
${orgs?.map(this.renderOrg)}
-
+
${this.renderOrgQuotas()} ${this.renderOrgProxies()}
${this.renderOrgReadOnly()} ${this.renderOrgDelete()}
diff --git a/frontend/src/components/ui/index.ts b/frontend/src/components/ui/index.ts
index c7c9f96f61..485b542f6c 100644
--- a/frontend/src/components/ui/index.ts
+++ b/frontend/src/components/ui/index.ts
@@ -27,6 +27,7 @@ import("./menu-item-link");
import("./meter");
import("./numbered-list");
import("./overflow-dropdown");
+import("./overflow-scroll");
import("./pagination");
import("./pw-strength-alert");
import("./relative-duration");
diff --git a/frontend/src/components/ui/overflow-scroll.ts b/frontend/src/components/ui/overflow-scroll.ts
new file mode 100644
index 0000000000..aabba5266b
--- /dev/null
+++ b/frontend/src/components/ui/overflow-scroll.ts
@@ -0,0 +1,105 @@
+import { css, html, LitElement } from "lit";
+import { customElement, property } from "lit/decorators.js";
+
+/**
+ * Overflow scroller. Optionally displays a scrim/shadow (a small gradient
+ * indicating there's more available content) on supported browsers,
+ * depending on scroll position.
+ * @slot
+ * @cssPart content
+ * @cssproperty --btrix-overflow-scrim-width The width of the scrim. 3rem by default.
+ * @cssproperty --btrix-overflow-scroll-scrim-color The color of the scrim. White by default.
+ */
+@customElement("btrix-overflow-scroll")
+export class OverflowScroll extends LitElement {
+ /**
+ * The direction of the overflow scroll. Currently just horizontal.
+ */
+ // TODO: Implement vertical overflow scroller
+ @property({ type: String })
+ // eslint-disable-next-line @typescript-eslint/prefer-as-const
+ direction: "horizontal" = "horizontal";
+
+ /**
+ * Whether to show a scrim when the overflow scroll is active. Only appears when the inner content is wider than this element.
+ *
+ * Progressive enhancement: only works on Chromium-based browsers currently.
+ * See https://caniuse.com/mdn-css_properties_scroll-timeline for support.
+ */
+ @property({ type: Boolean })
+ scrim = true;
+
+ static styles = css`
+ :host {
+ display: block;
+ position: relative;
+ }
+
+ [direction="horizontal"] {
+ overflow-x: auto;
+ }
+
+ @supports (scroll-timeline-name: --btrix-overflow-scroll-timeline) {
+ [scrim][direction="horizontal"] {
+ scroll-timeline-name: --btrix-overflow-scroll-timeline;
+ scroll-timeline-axis: inline;
+ }
+
+ [scrim][direction="horizontal"]:before,
+ [scrim][direction="horizontal"]:after {
+ content: "";
+ width: var(--btrix-overflow-scrim-width, 3rem);
+ position: absolute;
+ z-index: 1;
+ top: 0;
+ height: 100%;
+ pointer-events: none;
+ animation-name: btrix-scroll-scrim;
+ animation-timeline: --btrix-overflow-scroll-timeline;
+ opacity: 0;
+ }
+
+ [scrim][direction="horizontal"]:before {
+ left: 0;
+ background: linear-gradient(
+ to right,
+ var(--btrix-overflow-scroll-scrim-color, white),
+ transparent
+ );
+ /* background-color: blue; */
+ }
+ [scrim][direction="horizontal"]:after {
+ right: 0;
+ background: linear-gradient(
+ to right,
+ transparent,
+ var(--btrix-overflow-scroll-scrim-color, white)
+ );
+ /* background-color: blue; */
+ animation-direction: reverse;
+ }
+ @keyframes btrix-scroll-scrim {
+ 0% {
+ opacity: 0;
+ }
+ 20% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 1;
+ }
+ }
+ }
+ `;
+
+ render() {
+ return html`
+
+
`;
+ }
+}
diff --git a/frontend/src/features/archived-items/archived-item-list.ts b/frontend/src/features/archived-items/archived-item-list.ts
index e90ee08144..1dea96a05a 100644
--- a/frontend/src/features/archived-items/archived-item-list.ts
+++ b/frontend/src/features/archived-items/archived-item-list.ts
@@ -451,7 +451,7 @@ export class ArchivedItemList extends TailwindElement {
.join(" ")};
}
-
+
-
+
`;
}
}
diff --git a/frontend/src/features/archived-items/crawl-list.ts b/frontend/src/features/archived-items/crawl-list.ts
index 0772f2e235..0f8080239f 100644
--- a/frontend/src/features/archived-items/crawl-list.ts
+++ b/frontend/src/features/archived-items/crawl-list.ts
@@ -283,7 +283,7 @@ export class CrawlList extends TailwindElement {
[clickable-end] min-content;
}
-
+
@@ -320,7 +320,7 @@ export class CrawlList extends TailwindElement {
-
`;
+ `;
}
private handleSlotchange() {
diff --git a/frontend/src/pages/org/browser-profiles-list.ts b/frontend/src/pages/org/browser-profiles-list.ts
index 689ad8af15..6ffbea89b1 100644
--- a/frontend/src/pages/org/browser-profiles-list.ts
+++ b/frontend/src/pages/org/browser-profiles-list.ts
@@ -163,52 +163,54 @@ export class BrowserProfilesList extends BtrixElement {
};
return html`
-
-
- ${headerCells.map(({ sortBy, sortDirection, label, className }) => {
- const isSorting = sortBy === this.sort.sortBy;
- const sortValue =
- (isSorting && SortDirection.get(this.sort.sortDirection)) ||
- "none";
- // TODO implement sort render logic in table-header-cell
- return html`
- {
- if (isSorting) {
- this.sort = {
- ...this.sort,
- sortDirection: this.sort.sortDirection * -1,
- };
- } else {
- this.sort = {
- sortBy,
- sortDirection,
- };
- }
- }}
- >
- ${label} ${getSortIcon(sortValue)}
-
- `;
- })}
-
- ${msg("Row Actions")}
-
-
-
- ${when(this.browserProfiles, ({ total, items }) =>
- total ? html` ${items.map(this.renderItem)} ` : nothing,
- )}
- ${when(this.isLoading, this.renderLoading)}
-
-
+
+
+
+ ${headerCells.map(({ sortBy, sortDirection, label, className }) => {
+ const isSorting = sortBy === this.sort.sortBy;
+ const sortValue =
+ (isSorting && SortDirection.get(this.sort.sortDirection)) ||
+ "none";
+ // TODO implement sort render logic in table-header-cell
+ return html`
+ {
+ if (isSorting) {
+ this.sort = {
+ ...this.sort,
+ sortDirection: this.sort.sortDirection * -1,
+ };
+ } else {
+ this.sort = {
+ sortBy,
+ sortDirection,
+ };
+ }
+ }}
+ >
+ ${label} ${getSortIcon(sortValue)}
+
+ `;
+ })}
+
+ ${msg("Row Actions")}
+
+
+
+ ${when(this.browserProfiles, ({ total, items }) =>
+ total ? html` ${items.map(this.renderItem)} ` : nothing,
+ )}
+ ${when(this.isLoading, this.renderLoading)}
+
+
+
${when(this.browserProfiles, ({ total, page, pageSize }) =>
total
? html`
diff --git a/frontend/src/pages/org/collections-list.ts b/frontend/src/pages/org/collections-list.ts
index 4897f56d3f..d7d49ff0ed 100644
--- a/frontend/src/pages/org/collections-list.ts
+++ b/frontend/src/pages/org/collections-list.ts
@@ -195,14 +195,39 @@ export class CollectionsList extends BtrixElement {
>
${this.renderControls()}
-
+
${guard(
[this.collections, this.listView, this.collectionRefreshing],
this.listView === ListView.List
? this.renderList
: this.renderGrid,
)}
-
+
+ ${when(this.listView === ListView.List, () =>
+ when(
+ (this.collections &&
+ this.collections.total > this.collections.pageSize) ||
+ (this.collections && this.collections.page > 1),
+ () => html`
+
+ `,
+ ),
+ )}
`
: this.renderLoading(),
)}
@@ -507,29 +532,6 @@ export class CollectionsList extends BtrixElement {
${this.collections.items.map(this.renderItem)}
-
- ${when(
- this.collections.total > this.collections.pageSize ||
- this.collections.page > 1,
- () => html`
-
- `,
- )}
`;
}
diff --git a/frontend/src/pages/org/index.ts b/frontend/src/pages/org/index.ts
index 3b0191a2a7..2f9fe150fd 100644
--- a/frontend/src/pages/org/index.ts
+++ b/frontend/src/pages/org/index.ts
@@ -355,36 +355,38 @@ export class Org extends BtrixElement {
-
+
+
+
diff --git a/frontend/src/stories/components/OverflowScroll.stories.ts b/frontend/src/stories/components/OverflowScroll.stories.ts
new file mode 100644
index 0000000000..837fa5defd
--- /dev/null
+++ b/frontend/src/stories/components/OverflowScroll.stories.ts
@@ -0,0 +1,30 @@
+import type { Meta, StoryObj } from "@storybook/web-components";
+
+import { renderOverflowScroll, type RenderProps } from "./OverflowScroll";
+
+const meta = {
+ title: "Components/Overflow Scroll",
+ component: "btrix-overflow-scroll",
+ tags: ["autodocs"],
+ render: renderOverflowScroll,
+ argTypes: {
+ direction: {
+ control: { type: "select" },
+ options: ["horizontal"] satisfies RenderProps["direction"][],
+ },
+ scrim: {
+ control: { type: "boolean" },
+ },
+ },
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ direction: "horizontal",
+ scrim: true,
+ },
+};
diff --git a/frontend/src/stories/components/OverflowScroll.ts b/frontend/src/stories/components/OverflowScroll.ts
new file mode 100644
index 0000000000..22b90fdfe6
--- /dev/null
+++ b/frontend/src/stories/components/OverflowScroll.ts
@@ -0,0 +1,37 @@
+import clsx from "clsx";
+import { html } from "lit";
+import { ifDefined } from "lit/directives/if-defined.js";
+
+import { defaultArgs, renderTable } from "./Table";
+
+import "@/components/ui/overflow-scroll";
+
+import type { OverflowScroll } from "@/components/ui/overflow-scroll";
+import { tw } from "@/utils/tailwind";
+
+export type RenderProps = OverflowScroll;
+
+export const renderOverflowScroll = ({
+ direction,
+ scrim,
+}: Partial) => {
+ return html`
+
+
+
+ ${renderTable({
+ ...defaultArgs,
+ classes: clsx(
+ ...defaultArgs.classes,
+ tw`w-[800px] rounded border bg-neutral-50 p-2 [--btrix-table-cell-padding:var(--sl-spacing-2x-small)]`,
+ ),
+ })}
+
+
+ `;
+};