mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 00:13:46 +01:00
Add sticky selection toolbar (#6320)
This commit is contained in:
parent
d1ee64d36f
commit
1bc32a3099
2 changed files with 121 additions and 32 deletions
|
|
@ -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 (
|
||||
<div className="selected-items-info">
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="minimal"
|
||||
onClick={() => onSelectNone()}
|
||||
title={intl.formatMessage({ id: "actions.select_none" })}
|
||||
>
|
||||
<Icon icon={faTimes} />
|
||||
</Button>
|
||||
<span className="selected-count">{selected}</span>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="minimal"
|
||||
onClick={() => onSelectAll()}
|
||||
title={intl.formatMessage({ id: "actions.select_all" })}
|
||||
>
|
||||
<Icon icon={faSquareCheck} />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export interface IItemListOperation<T extends QueryResult> {
|
||||
text: string;
|
||||
|
|
@ -62,9 +98,21 @@ export const FilteredListToolbar: React.FC<IFilteredListToolbar> = ({
|
|||
setFilter,
|
||||
});
|
||||
const { selectedIds, onSelectAll, onSelectNone } = listSelect;
|
||||
const hasSelection = selectedIds.size > 0;
|
||||
|
||||
return (
|
||||
<ButtonToolbar className="filtered-list-toolbar">
|
||||
<ButtonToolbar
|
||||
className={cx("filtered-list-toolbar", { "has-selection": hasSelection })}
|
||||
>
|
||||
{hasSelection ? (
|
||||
<SelectionSection
|
||||
filter={filter}
|
||||
selected={selectedIds.size}
|
||||
onSelectAll={onSelectAll}
|
||||
onSelectNone={onSelectNone}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<SearchTermInput filter={filter} onFilterUpdate={setFilter} />
|
||||
|
||||
<ButtonGroup>
|
||||
|
|
@ -73,7 +121,10 @@ export const FilteredListToolbar: React.FC<IFilteredListToolbar> = ({
|
|||
onSetFilter={setFilter}
|
||||
view={view}
|
||||
/>
|
||||
<FilterButton onClick={() => showEditFilter()} count={filter.count()} />
|
||||
<FilterButton
|
||||
onClick={() => showEditFilter()}
|
||||
count={filter.count()}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
|
||||
<SortBySelect
|
||||
|
|
@ -81,14 +132,20 @@ export const FilteredListToolbar: React.FC<IFilteredListToolbar> = ({
|
|||
sortDirection={filter.sortDirection}
|
||||
options={filterOptions.sortByOptions}
|
||||
onChangeSortBy={(e) => setFilter(filter.setSortBy(e ?? undefined))}
|
||||
onChangeSortDirection={() => setFilter(filter.toggleSortDirection())}
|
||||
onReshuffleRandomSort={() => setFilter(filter.reshuffleRandomSort())}
|
||||
onChangeSortDirection={() =>
|
||||
setFilter(filter.toggleSortDirection())
|
||||
}
|
||||
onReshuffleRandomSort={() =>
|
||||
setFilter(filter.reshuffleRandomSort())
|
||||
}
|
||||
/>
|
||||
|
||||
<PageSizeSelector
|
||||
pageSize={filter.itemsPerPage}
|
||||
setPageSize={(size) => setFilter(filter.setPageSize(size))}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<ListOperationButtons
|
||||
onSelectAll={onSelectAll}
|
||||
|
|
|
|||
|
|
@ -918,9 +918,14 @@ input[type="range"].zoom-slider {
|
|||
|
||||
.filtered-list-toolbar {
|
||||
align-items: center;
|
||||
background-color: $body-bg;
|
||||
gap: 0.5rem;
|
||||
justify-content: center;
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
// offset the main padding
|
||||
margin-top: -0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
padding-top: 0.5rem;
|
||||
|
||||
& > .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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue