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; + } +}