From 1bc32a3099bb374d91316b89b2efaabef4609100 Mon Sep 17 00:00:00 2001
From: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
Date: Fri, 28 Nov 2025 13:52:30 +1100
Subject: [PATCH] Add sticky selection toolbar (#6320)
---
.../components/List/FilteredListToolbar.tsx | 101 ++++++++++++++----
ui/v2.5/src/components/List/styles.scss | 52 +++++++--
2 files changed, 121 insertions(+), 32 deletions(-)
diff --git a/ui/v2.5/src/components/List/FilteredListToolbar.tsx b/ui/v2.5/src/components/List/FilteredListToolbar.tsx
index 24ef7b539..b3c0883a6 100644
--- a/ui/v2.5/src/components/List/FilteredListToolbar.tsx
+++ b/ui/v2.5/src/components/List/FilteredListToolbar.tsx
@@ -8,11 +8,47 @@ import {
IListFilterOperation,
ListOperationButtons,
} from "./ListOperationButtons";
-import { ButtonGroup, ButtonToolbar } from "react-bootstrap";
+import { Button, ButtonGroup, ButtonToolbar } from "react-bootstrap";
import { View } from "./views";
import { IListSelect, useFilterOperations } from "./util";
import { SavedFilterDropdown } from "./SavedFilterList";
import { FilterButton } from "./Filters/FilterButton";
+import { Icon } from "../Shared/Icon";
+import { faTimes } from "@fortawesome/free-solid-svg-icons";
+import { faSquareCheck } from "@fortawesome/free-regular-svg-icons";
+import { useIntl } from "react-intl";
+import cx from "classnames";
+
+const SelectionSection: React.FC<{
+ filter: ListFilterModel;
+ selected: number;
+ onSelectAll: () => void;
+ onSelectNone: () => void;
+}> = ({ selected, onSelectAll, onSelectNone }) => {
+ const intl = useIntl();
+
+ return (
+
+
+ {selected}
+
+
+ );
+};
export interface IItemListOperation {
text: string;
@@ -62,33 +98,54 @@ export const FilteredListToolbar: React.FC = ({
setFilter,
});
const { selectedIds, onSelectAll, onSelectNone } = listSelect;
+ const hasSelection = selectedIds.size > 0;
return (
-
-
-
-
-
+ {hasSelection ? (
+
- showEditFilter()} count={filter.count()} />
-
+ ) : (
+ <>
+
- setFilter(filter.setSortBy(e ?? undefined))}
- onChangeSortDirection={() => setFilter(filter.toggleSortDirection())}
- onReshuffleRandomSort={() => setFilter(filter.reshuffleRandomSort())}
- />
+
+
+ showEditFilter()}
+ count={filter.count()}
+ />
+
- setFilter(filter.setPageSize(size))}
- />
+ setFilter(filter.setSortBy(e ?? undefined))}
+ onChangeSortDirection={() =>
+ setFilter(filter.toggleSortDirection())
+ }
+ onReshuffleRandomSort={() =>
+ setFilter(filter.reshuffleRandomSort())
+ }
+ />
+
+ setFilter(filter.setPageSize(size))}
+ />
+ >
+ )}
.btn-group {
flex-wrap: wrap;
@@ -1091,10 +1096,6 @@ input[type="range"].zoom-slider {
&.filtered-list-toolbar {
flex-wrap: nowrap;
gap: 1rem;
- // offset the main padding
- margin-top: -0.5rem;
- padding-bottom: 0.5rem;
- padding-top: 0.5rem;
position: sticky;
top: $navbar-height;
z-index: 10;
@@ -1141,11 +1142,6 @@ input[type="range"].zoom-slider {
margin-right: 0.5rem;
}
- .selected-items-info {
- align-items: center;
- display: flex;
- }
-
> div:first-child,
> div:last-child {
flex: 1;
@@ -1433,3 +1429,39 @@ input[type="range"].zoom-slider {
.duration-preset {
cursor: pointer;
}
+
+.selected-items-info {
+ align-items: center;
+ border: 1px solid $secondary;
+ display: flex;
+ gap: 0.25rem;
+ justify-content: flex-end;
+}
+
+.scene-list-toolbar .selected-items-info {
+ justify-content: flex-start;
+}
+
+.item-list-container > .filtered-list-toolbar.has-selection {
+ border-radius: 0.5rem;
+ margin-left: auto;
+ margin-right: auto;
+ padding-left: 0.5rem;
+ padding-right: 0.5rem;
+ position: sticky;
+ top: $navbar-height;
+ width: fit-content;
+ z-index: 10;
+
+ @include media-breakpoint-down(xs) {
+ top: 0;
+ }
+}
+
+.detail-body .filtered-list-toolbar.has-selection {
+ top: calc($sticky-detail-header-height + $navbar-height);
+
+ @include media-breakpoint-down(xs) {
+ top: 0;
+ }
+}