[RFC] Refactor scene list toolbar (#6322)

* Revert scene list toolbar to use common filtered list toolbar
* Add unobtrusive sidebar toggle button
* Revert small device sidebar changes
* Minor styling fixes
This commit is contained in:
WithoutPants 2025-12-03 14:59:15 +11:00 committed by GitHub
parent e213fde0cc
commit 730e877e73
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 197 additions and 667 deletions

View file

@ -78,6 +78,7 @@ export interface IFilteredListToolbar {
onEdit?: () => void; onEdit?: () => void;
onDelete?: () => void; onDelete?: () => void;
operations?: IListFilterOperation[]; operations?: IListFilterOperation[];
operationComponent?: React.ReactNode;
zoomable?: boolean; zoomable?: boolean;
} }
@ -90,6 +91,7 @@ export const FilteredListToolbar: React.FC<IFilteredListToolbar> = ({
onEdit, onEdit,
onDelete, onDelete,
operations, operations,
operationComponent,
zoomable = false, zoomable = false,
}) => { }) => {
const filterOptions = filter.options; const filterOptions = filter.options;
@ -100,6 +102,17 @@ export const FilteredListToolbar: React.FC<IFilteredListToolbar> = ({
const { selectedIds, onSelectAll, onSelectNone } = listSelect; const { selectedIds, onSelectAll, onSelectNone } = listSelect;
const hasSelection = selectedIds.size > 0; const hasSelection = selectedIds.size > 0;
const renderOperations = operationComponent ?? (
<ListOperationButtons
onSelectAll={onSelectAll}
onSelectNone={onSelectNone}
otherOperations={operations}
itemsSelected={selectedIds.size > 0}
onEdit={onEdit}
onDelete={onDelete}
/>
);
return ( return (
<ButtonToolbar <ButtonToolbar
className={cx("filtered-list-toolbar", { "has-selection": hasSelection })} className={cx("filtered-list-toolbar", { "has-selection": hasSelection })}
@ -147,14 +160,7 @@ export const FilteredListToolbar: React.FC<IFilteredListToolbar> = ({
</> </>
)} )}
<ListOperationButtons {renderOperations}
onSelectAll={onSelectAll}
onSelectNone={onSelectNone}
otherOperations={operations}
itemsSelected={selectedIds.size > 0}
onEdit={onEdit}
onDelete={onDelete}
/>
<ListViewButtonGroup <ListViewButtonGroup
displayMode={filter.displayMode} displayMode={filter.displayMode}

View file

@ -16,12 +16,13 @@ export const OperationDropdown: React.FC<
PropsWithChildren<{ PropsWithChildren<{
className?: string; className?: string;
menuPortalTarget?: HTMLElement; menuPortalTarget?: HTMLElement;
menuClassName?: string;
}> }>
> = ({ className, menuPortalTarget, children }) => { > = ({ className, menuPortalTarget, menuClassName, children }) => {
if (!children) return null; if (!children) return null;
const menu = ( const menu = (
<Dropdown.Menu className="bg-secondary text-white"> <Dropdown.Menu className={cx("bg-secondary text-white", menuClassName)}>
{children} {children}
</Dropdown.Menu> </Dropdown.Menu>
); );

View file

@ -1,73 +0,0 @@
import React from "react";
import { ListFilterModel } from "src/models/list-filter/filter";
import { Pagination, PaginationIndex } from "../List/Pagination";
import { ButtonToolbar } from "react-bootstrap";
import { ListViewOptions } from "../List/ListViewOptions";
import { PageSizeSelector, SortBySelect } from "../List/ListFilter";
import cx from "classnames";
export const ListResultsHeader: React.FC<{
className?: string;
loading: boolean;
filter: ListFilterModel;
totalCount: number;
metadataByline?: React.ReactNode;
onChangeFilter: (filter: ListFilterModel) => void;
}> = ({
className,
loading,
filter,
totalCount,
metadataByline,
onChangeFilter,
}) => {
return (
<ButtonToolbar className={cx(className, "list-results-header")}>
<div>
<SortBySelect
options={filter.options.sortByOptions}
sortBy={filter.sortBy}
sortDirection={filter.sortDirection}
onChangeSortBy={(s) =>
onChangeFilter(filter.setSortBy(s ?? undefined))
}
onChangeSortDirection={() =>
onChangeFilter(filter.toggleSortDirection())
}
onReshuffleRandomSort={() =>
onChangeFilter(filter.reshuffleRandomSort())
}
/>
<PageSizeSelector
pageSize={filter.itemsPerPage}
setPageSize={(s) => onChangeFilter(filter.setPageSize(s))}
/>
<ListViewOptions
displayMode={filter.displayMode}
zoomIndex={filter.zoomIndex}
displayModeOptions={filter.options.displayModeOptions}
onSetDisplayMode={(mode) =>
onChangeFilter(filter.setDisplayMode(mode))
}
onSetZoom={(zoom) => onChangeFilter(filter.setZoom(zoom))}
/>
</div>
<div className="pagination-index-container">
<Pagination
currentPage={filter.currentPage}
itemsPerPage={filter.itemsPerPage}
totalItems={totalCount}
onChangePage={(page) => onChangeFilter(filter.changePage(page))}
/>
<PaginationIndex
loading={loading}
itemsPerPage={filter.itemsPerPage}
currentPage={filter.currentPage}
totalItems={totalCount}
metadataByline={metadataByline}
/>
</div>
<div className="empty-space"></div>
</ButtonToolbar>
);
};

View file

@ -1,141 +0,0 @@
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { ListFilterModel } from "src/models/list-filter/filter";
import { faTimes } from "@fortawesome/free-solid-svg-icons";
import { FilterTags } from "../List/FilterTags";
import cx from "classnames";
import { Button, ButtonGroup, ButtonToolbar } from "react-bootstrap";
import { FilterButton } from "../List/Filters/FilterButton";
import { Icon } from "../Shared/Icon";
import { SearchTermInput } from "../List/ListFilter";
import { Criterion } from "src/models/list-filter/criteria/criterion";
import { SidebarToggleButton } from "../Shared/Sidebar";
import { PatchComponent } from "src/patch";
import { SavedFilterDropdown } from "./SavedFilterList";
import { View } from "./views";
export const ToolbarFilterSection: React.FC<{
filter: ListFilterModel;
onToggleSidebar: () => void;
onSetFilter: (filter: ListFilterModel) => void;
onEditCriterion: (c?: Criterion) => void;
onRemoveCriterion: (criterion: Criterion, valueIndex?: number) => void;
onRemoveAllCriterion: () => void;
onEditSearchTerm: () => void;
onRemoveSearchTerm: () => void;
view?: View;
}> = PatchComponent(
"ToolbarFilterSection",
({
filter,
onToggleSidebar,
onSetFilter,
onEditCriterion,
onRemoveCriterion,
onRemoveAllCriterion,
onEditSearchTerm,
onRemoveSearchTerm,
view,
}) => {
const { criteria, searchTerm } = filter;
return (
<>
<div className="search-container">
<SearchTermInput filter={filter} onFilterUpdate={onSetFilter} />
</div>
<div className="filter-section">
<ButtonGroup>
<SidebarToggleButton onClick={onToggleSidebar} />
<SavedFilterDropdown
filter={filter}
onSetFilter={onSetFilter}
view={view}
menuPortalTarget={document.body}
/>
<FilterButton
onClick={() => onEditCriterion()}
count={criteria.length}
/>
</ButtonGroup>
<FilterTags
searchTerm={searchTerm}
criteria={criteria}
onEditCriterion={onEditCriterion}
onRemoveCriterion={onRemoveCriterion}
onRemoveAll={onRemoveAllCriterion}
onEditSearchTerm={onEditSearchTerm}
onRemoveSearchTerm={onRemoveSearchTerm}
truncateOnOverflow
/>
</div>
</>
);
}
);
export const ToolbarSelectionSection: React.FC<{
selected: number;
onToggleSidebar: () => void;
operations?: React.ReactNode;
onSelectAll: () => void;
onSelectNone: () => void;
}> = PatchComponent(
"ToolbarSelectionSection",
({ selected, onToggleSidebar, operations, onSelectAll, onSelectNone }) => {
const intl = useIntl();
return (
<div className="toolbar-selection-section">
<div className="selected-items-info">
<SidebarToggleButton onClick={onToggleSidebar} />
<Button
variant="secondary"
className="minimal"
onClick={() => onSelectNone()}
title={intl.formatMessage({ id: "actions.select_none" })}
>
<Icon icon={faTimes} />
</Button>
<span>{selected} selected</span>
<Button variant="link" onClick={() => onSelectAll()}>
<FormattedMessage id="actions.select_all" />
</Button>
</div>
{operations}
<div className="empty-space" />
</div>
);
}
);
// TODO - rename to FilteredListToolbar once all list components have been updated
// TODO - and expose to plugins
export const FilteredListToolbar2: React.FC<{
className?: string;
hasSelection: boolean;
filterSection: React.ReactNode;
selectionSection: React.ReactNode;
operationSection: React.ReactNode;
}> = ({
className,
hasSelection,
filterSection,
selectionSection,
operationSection,
}) => {
return (
<ButtonToolbar
className={cx(className, "filtered-list-toolbar", {
"has-selection": hasSelection,
})}
>
{!hasSelection ? filterSection : selectionSection}
{!hasSelection ? (
<div className="filtered-list-toolbar-operations">
{operationSection}
</div>
) : null}
</ButtonToolbar>
);
};

View file

@ -948,14 +948,6 @@ input[type="range"].zoom-slider {
} }
} }
.sidebar-pane .filtered-list-toolbar {
flex-wrap: nowrap;
& > .btn-group {
align-items: baseline;
}
}
.custom-field-filter { .custom-field-filter {
align-items: center; align-items: center;
display: flex; display: flex;
@ -987,14 +979,6 @@ input[type="range"].zoom-slider {
} }
.sidebar { .sidebar {
// make controls slightly larger on mobile
@include media-breakpoint-down(xs) {
.btn,
.form-control {
font-size: 1.25rem;
}
}
.sidebar-search-container { .sidebar-search-container {
display: flex; display: flex;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
@ -1033,18 +1017,22 @@ input[type="range"].zoom-slider {
} }
} }
.pagination-footer { .pagination-footer-container {
background-color: transparent; background-color: transparent;
bottom: $navbar-height; bottom: $navbar-height;
margin: auto;
padding: 0.5rem 1rem 0.75rem;
position: sticky; position: sticky;
width: fit-content;
z-index: 10; z-index: 10;
@include media-breakpoint-up(sm) { @include media-breakpoint-up(sm) {
bottom: 0; bottom: 0;
} }
}
.pagination-footer {
margin: auto;
padding: 0.5rem 1rem 0.75rem;
width: fit-content;
.pagination.btn-group { .pagination.btn-group {
box-shadow: 0 8px 10px 2px rgb(0 0 0 / 30%); box-shadow: 0 8px 10px 2px rgb(0 0 0 / 30%);
@ -1060,6 +1048,18 @@ input[type="range"].zoom-slider {
} }
} }
// on very large screens, offset the margins to center the pagination controls
@media (min-width: 1800px) {
.sidebar-pane:not(.hide-sidebar) {
.filter-tags,
.pagination-index-container,
.pagination-footer-container {
margin-left: -$sidebar-width;
margin-right: 0;
}
}
}
// hide sidebar Edit Filter button on larger screens // hide sidebar Edit Filter button on larger screens
@include media-breakpoint-up(md) { @include media-breakpoint-up(md) {
.sidebar .edit-filter-button { .sidebar .edit-filter-button {
@ -1067,232 +1067,11 @@ input[type="range"].zoom-slider {
} }
} }
// the following refers to the new FilteredListToolbar2 component // hide the search input field if the sidebar is open on smaller screens
// ensure the rules here don't conflict with the original filtered-list-toolbar above @media (min-width: 576px) and (max-width: 1400px) {
// TODO - replace with only .filtered-list-toolbar once all lists use the new toolbar .sidebar-pane:not(.hide-sidebar) .filtered-list-toolbar .search-term-input {
.scene-list-toolbar {
&.filtered-list-toolbar {
align-items: center;
background-color: $body-bg;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
margin-bottom: 0;
row-gap: 1rem;
> div {
align-items: center;
display: flex;
gap: 0.5rem;
justify-content: flex-start;
&:last-child {
flex-shrink: 0;
justify-content: flex-end;
}
}
}
&.filtered-list-toolbar {
flex-wrap: nowrap;
gap: 1rem;
position: sticky;
top: $navbar-height;
z-index: 10;
@include media-breakpoint-down(xs) {
top: 0;
}
// hide drop down menu items for play and create new
// when the buttons are visible
@include media-breakpoint-up(sm) {
.scene-list-operations {
.play-item,
.create-new-item {
display: none; display: none;
} }
}
}
// hide play and create new buttons on xs screens
// show these in the drop down menu instead
@include media-breakpoint-down(xs) {
.play-button,
.create-new-button {
display: none;
}
}
.toolbar-selection-section,
div.filter-section {
border: 1px solid $secondary;
border-radius: 0.25rem;
flex-grow: 1;
overflow-x: hidden;
}
div.toolbar-selection-section {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: center;
.sidebar-toggle-button {
margin-right: 0.5rem;
}
> div:first-child,
> div:last-child {
flex: 1;
}
> div:last-child {
display: flex;
justify-content: flex-end;
}
.scene-list-operations {
display: flex;
}
// on smaller viewports move the operation buttons to the right
@include media-breakpoint-down(md) {
div.scene-list-operations {
flex: 1;
justify-content: flex-end;
order: 3;
}
> div:last-child {
flex: 0;
order: 2;
}
}
}
// on larger viewports, move the operation buttons to the center
@include media-breakpoint-up(lg) {
div.toolbar-selection-section div.scene-list-operations {
justify-content: center;
> .btn-group {
gap: 0.5rem;
}
}
div.toolbar-selection-section .empty-space {
flex: 1;
order: 3;
}
}
.search-container {
border-right: 1px solid $secondary;
display: flex;
margin-right: -0.5rem;
min-width: calc($sidebar-width - 15px);
padding-right: 10px;
.search-term-input {
margin-right: 0;
width: 100%;
.clearable-text-field {
height: 100%;
}
}
}
.filter-tags {
flex-grow: 1;
flex-wrap: nowrap;
justify-content: flex-start;
margin-bottom: 0;
// account for filter button, and toggle sidebar buttons with gaps
width: calc(100% - 70px - 1rem);
@include media-breakpoint-down(xs) {
overflow-x: auto;
scrollbar-width: thin;
}
.tag-item {
white-space: nowrap;
}
}
}
}
// hide the search box in the toolbar when sidebar is shown on larger screens
// larger screens don't overlap the sidebar
@include media-breakpoint-up(md) {
.sidebar-pane:not(.hide-sidebar) .filtered-list-toolbar .search-container {
display: none;
}
}
// hide the search box when sidebar is hidden on smaller screens
@include media-breakpoint-down(md) {
.sidebar-pane.hide-sidebar .filtered-list-toolbar .search-container {
display: none;
}
}
// hide the filter and saved filters icon buttons when sidebar is shown on smaller screens
@include media-breakpoint-down(sm) {
.sidebar-pane:not(.hide-sidebar) .filtered-list-toolbar {
.filter-button,
.saved-filter-dropdown {
display: none;
}
}
// adjust the width of the filter-tags as well
.sidebar-pane:not(.hide-sidebar) .filtered-list-toolbar .filter-tags {
width: calc(100% - 35px - 0.5rem);
}
}
// move the sidebar toggle to the left on larger viewports
@include media-breakpoint-up(md) {
.filtered-list-toolbar .filter-section {
.sidebar-toggle-button {
margin-left: 0;
}
.filter-tags {
order: 2;
}
}
}
// hide the search term tag item when the search box is visible
@include media-breakpoint-up(lg) {
// TODO - remove scene-list-toolbar when all lists use the new toolbar
.scene-list-toolbar.filtered-list-toolbar
.filter-tags
.search-term-filter-tag {
display: none;
}
}
@include media-breakpoint-down(md) {
// TODO - remove scene-list-toolbar when all lists use the new toolbar
.sidebar-pane:not(.hide-sidebar)
.scene-list-toolbar.filtered-list-toolbar
.filter-tags
.search-term-filter-tag {
display: none;
}
}
// TODO - remove scene-list-toolbar when all lists use the new toolbar
.detail-body .scene-list-toolbar.filtered-list-toolbar {
top: calc($sticky-detail-header-height + $navbar-height);
@include media-breakpoint-down(xs) {
top: 0;
}
} }
#more-criteria-popover { #more-criteria-popover {
@ -1301,103 +1080,6 @@ input[type="range"].zoom-slider {
padding: 0.25rem; padding: 0.25rem;
} }
.list-results-header {
align-items: flex-start;
background-color: $body-bg;
display: flex;
> div {
align-items: center;
display: flex;
flex: 1;
gap: 0.5rem;
justify-content: flex-start;
&.pagination-index-container {
justify-content: center;
}
&:last-child {
flex-shrink: 0;
justify-content: flex-end;
}
}
}
.list-results-header .pagination-index-container {
display: flex;
flex-direction: column;
gap: 0.5rem;
.pagination {
// hidden by default. Can be shown via css override if needed
display: none;
margin: 0;
}
}
.list-results-header {
gap: 0.25rem;
margin-bottom: 0.5rem;
.paginationIndex {
margin: 0;
}
// move pagination info to right on medium screens
@include media-breakpoint-down(md) {
& > .empty-space {
flex: 0;
}
& > div.pagination-index-container {
align-items: flex-end;
order: 3;
}
}
// center the header on smaller screens
@include media-breakpoint-down(sm) {
& > div,
& > div.pagination-index-container {
flex-basis: 100%;
justify-content: center;
margin-left: auto;
margin-right: auto;
}
& > div.pagination-index-container {
align-items: center;
}
}
}
// sidebar visible styling
.sidebar-pane:not(.hide-sidebar) .list-results-header {
// move pagination info to right on medium screens when sidebar
@include media-breakpoint-down(lg) {
& > .empty-space {
flex: 0;
}
& > div.pagination-index-container {
justify-content: flex-end;
order: 3;
}
}
// center the header on smaller screens when sidebar is visible
@include media-breakpoint-down(md) {
& > div,
& > div.pagination-index-container {
flex-basis: 100%;
justify-content: center;
margin-left: auto;
margin-right: auto;
}
}
}
// Duration slider styles // Duration slider styles
.duration-slider, .duration-slider,
.age-slider-container { .age-slider-container {
@ -1442,7 +1124,23 @@ input[type="range"].zoom-slider {
justify-content: flex-start; justify-content: flex-start;
} }
.item-list-container > .filtered-list-toolbar.has-selection { // modify margins for toolbar within sidebar pane to accommodate toggle button
.sidebar-pane .filtered-list-toolbar {
margin-left: 40px;
margin-right: 40px;
}
// on very large screens, offset the margins to center the toolbar
@media (min-width: 1800px) {
.sidebar-pane:not(.hide-sidebar) {
.filtered-list-toolbar {
margin-left: -$sidebar-width;
margin-right: 0;
}
}
}
.item-list-container .filtered-list-toolbar.has-selection {
border-radius: 0.5rem; border-radius: 0.5rem;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;

View file

@ -67,17 +67,13 @@ import {
useFilteredSidebarKeybinds, useFilteredSidebarKeybinds,
} from "../List/Filters/FilterSidebar"; } from "../List/Filters/FilterSidebar";
import { PatchContainerComponent } from "src/patch"; import { PatchContainerComponent } from "src/patch";
import { Pagination } from "../List/Pagination"; import { Pagination, PaginationIndex } from "../List/Pagination";
import { Button, ButtonGroup } from "react-bootstrap"; import { Button, ButtonGroup } from "react-bootstrap";
import { Icon } from "../Shared/Icon"; import { Icon } from "../Shared/Icon";
import useFocus from "src/utils/focus"; import useFocus from "src/utils/focus";
import {
FilteredListToolbar2,
ToolbarFilterSection,
ToolbarSelectionSection,
} from "../List/ListToolbar";
import { ListResultsHeader } from "../List/ListResultsHeader";
import { useZoomKeybinds } from "../List/ZoomSlider"; import { useZoomKeybinds } from "../List/ZoomSlider";
import { FilteredListToolbar } from "../List/FilteredListToolbar";
import { FilterTags } from "../List/FilterTags";
function renderMetadataByline(result: GQL.FindScenesQueryResult) { function renderMetadataByline(result: GQL.FindScenesQueryResult) {
const duration = result?.data?.findScenes?.duration; const duration = result?.data?.findScenes?.duration;
@ -439,6 +435,7 @@ const SceneListOperations: React.FC<{
<OperationDropdown <OperationDropdown
className="scene-list-operations" className="scene-list-operations"
menuClassName="scene-list-operations-dropdown"
menuPortalTarget={document.body} menuPortalTarget={document.body}
> >
{operations.map((o) => { {operations.map((o) => {
@ -474,7 +471,6 @@ export const FilteredSceneList = (props: IFilteredScenes) => {
const history = useHistory(); const history = useHistory();
const searchFocus = useFocus(); const searchFocus = useFocus();
const [, setSearchFocus] = searchFocus;
const { filterHook, defaultSort, view, alterQuery, fromGroupId } = props; const { filterHook, defaultSort, view, alterQuery, fromGroupId } = props;
@ -752,50 +748,44 @@ export const FilteredSceneList = (props: IFilteredScenes) => {
focus={searchFocus} focus={searchFocus}
/> />
</Sidebar> </Sidebar>
<SidebarPaneContent> <SidebarPaneContent
<FilteredListToolbar2 onSidebarToggle={() => setShowSidebar(!showSidebar)}
className="scene-list-toolbar" >
hasSelection={hasSelection} <FilteredListToolbar
filterSection={
<ToolbarFilterSection
filter={filter} filter={filter}
onSetFilter={setFilter} listSelect={listSelect}
onToggleSidebar={() => setShowSidebar(!showSidebar)} setFilter={setFilter}
onEditCriterion={(c) => showEditFilter={showEditFilter}
showEditFilter(c?.criterionOption.type) onDelete={onDelete}
} onEdit={onEdit}
onRemoveCriterion={removeCriterion} operationComponent={operations}
onRemoveAllCriterion={() => clearAllCriteria(true)}
onEditSearchTerm={() => {
setShowSidebar(true);
setSearchFocus(true);
}}
onRemoveSearchTerm={() =>
setFilter(filter.clearSearchTerm())
}
view={view} view={view}
/> zoomable
}
selectionSection={
<ToolbarSelectionSection
selected={selectedIds.size}
onToggleSidebar={() => setShowSidebar(!showSidebar)}
onSelectAll={() => onSelectAll()}
onSelectNone={() => onSelectNone()}
operations={operations}
/>
}
operationSection={operations}
/> />
<ListResultsHeader <FilterTags
loading={cachedResult.loading} criteria={filter.criteria}
filter={filter} onEditCriterion={(c) => showEditFilter(c.criterionOption.type)}
totalCount={totalCount} onRemoveCriterion={removeCriterion}
metadataByline={metadataByline} onRemoveAll={clearAllCriteria}
onChangeFilter={(newFilter) => setFilter(newFilter)}
/> />
<div className="pagination-index-container">
<Pagination
currentPage={filter.currentPage}
itemsPerPage={filter.itemsPerPage}
totalItems={totalCount}
onChangePage={(page) => setFilter(filter.changePage(page))}
/>
<PaginationIndex
loading={cachedResult.loading}
itemsPerPage={filter.itemsPerPage}
currentPage={filter.currentPage}
totalItems={totalCount}
metadataByline={metadataByline}
/>
</div>
<LoadedContent loading={result.loading} error={result.error}> <LoadedContent loading={result.loading} error={result.error}>
<SceneList <SceneList
filter={effectiveFilter} filter={effectiveFilter}
@ -807,6 +797,7 @@ export const FilteredSceneList = (props: IFilteredScenes) => {
</LoadedContent> </LoadedContent>
{totalCount > filter.itemsPerPage && ( {totalCount > filter.itemsPerPage && (
<div className="pagination-footer-container">
<div className="pagination-footer"> <div className="pagination-footer">
<Pagination <Pagination
itemsPerPage={filter.itemsPerPage} itemsPerPage={filter.itemsPerPage}
@ -817,6 +808,7 @@ export const FilteredSceneList = (props: IFilteredScenes) => {
pagePopupPlacement="top" pagePopupPlacement="top"
/> />
</div> </div>
</div>
)} )}
</SidebarPaneContent> </SidebarPaneContent>
</SidebarPane> </SidebarPane>

View file

@ -964,3 +964,25 @@ input[type="range"].blue-slider {
// TODO - this will need to be rolled out to other tables // TODO - this will need to be rolled out to other tables
max-height: calc(100dvh - 210px); max-height: calc(100dvh - 210px);
} }
.scene-list .filtered-list-toolbar {
// hide play and create new buttons on xs screens
// show these in the drop down menu instead
@include media-breakpoint-down(xs) {
.play-button,
.create-new-button {
display: none;
}
}
}
// hide drop down menu items for play and create new
// when the buttons are visible
@include media-breakpoint-up(sm) {
.scene-list-operations-dropdown {
.dropdown-item.play-item,
.dropdown-item.create-new-item {
display: none;
}
}
}

View file

@ -60,8 +60,34 @@ export const SidebarPane: React.FC<
); );
}; };
export const SidebarPaneContent: React.FC = ({ children }) => { export const SidebarToggleButton: React.FC<{
return <div className="sidebar-pane-content">{children}</div>; onClick: () => void;
}> = ({ onClick }) => {
const intl = useIntl();
return (
<div className="sidebar-toggle-button-container">
<Button
className="sidebar-toggle-button ignore-sidebar-outside-click minimal"
variant="secondary"
onClick={onClick}
title={intl.formatMessage({ id: "actions.sidebar.toggle" })}
>
<Icon icon={faSliders} />
</Button>
</div>
);
};
export const SidebarPaneContent: React.FC<{ onSidebarToggle: () => void }> = ({
onSidebarToggle,
children,
}) => {
return (
<div className="sidebar-pane-content">
<SidebarToggleButton onClick={onSidebarToggle} />
{children}
</div>
);
}; };
interface IContext { interface IContext {
@ -125,22 +151,6 @@ export const SidebarSection: React.FC<
); );
}; };
export const SidebarToggleButton: React.FC<{
onClick: () => void;
}> = ({ onClick }) => {
const intl = useIntl();
return (
<Button
className="sidebar-toggle-button ignore-sidebar-outside-click"
variant="secondary"
onClick={onClick}
title={intl.formatMessage({ id: "actions.sidebar.toggle" })}
>
<Icon icon={faSliders} />
</Button>
);
};
// show sidebar by default if not on mobile // show sidebar by default if not on mobile
export function defaultShowSidebar() { export function defaultShowSidebar() {
return !ScreenUtils.matchesMediaQuery(fixedSidebarMediaQuery); return !ScreenUtils.matchesMediaQuery(fixedSidebarMediaQuery);

View file

@ -815,21 +815,6 @@ button.btn.favorite-button {
} }
} }
@include media-breakpoint-down(xs) { @include media-breakpoint-down(xs) {
.sidebar {
width: 100%;
}
&.hide-sidebar .sidebar {
margin-left: -100%;
}
&.hide-sidebar > :nth-child(2) {
width: 100%;
}
}
@include media-breakpoint-down(xs) {
display: block;
.sidebar { .sidebar {
margin-bottom: $navbar-height; margin-bottom: $navbar-height;
margin-top: 0; margin-top: 0;
@ -837,6 +822,36 @@ button.btn.favorite-button {
} }
} }
.sidebar-toggle-button-container {
height: 100%;
position: absolute;
.sidebar-toggle-button {
border-bottom: 1px solid $secondary;
border-bottom-left-radius: 0;
border-bottom-right-radius: 10px;
border-right: 1px solid $secondary;
border-top: 1px solid $secondary;
border-top-left-radius: 0;
border-top-right-radius: 10px;
margin-left: -15px;
opacity: 0.5;
position: sticky;
top: calc($navbar-height + 0.5rem);
z-index: 10;
@include media-breakpoint-down(sm) {
top: 0.5rem;
}
}
}
.sidebar-pane:not(.hide-sidebar) .sidebar-toggle-button-container {
.sidebar-toggle-button {
margin-left: -0.5rem;
}
}
.sidebar-toolbar { .sidebar-toolbar {
// TODO - use different colours for sidebar and toolbar // TODO - use different colours for sidebar and toolbar
background-color: $body-bg; background-color: $body-bg;
@ -884,6 +899,14 @@ button.btn.favorite-button {
$sticky-header-height: calc(50px + 3.3rem); $sticky-header-height: calc(50px + 3.3rem);
// special case for sidebar in details view // special case for sidebar in details view
.detail-body .sidebar-toggle-button-container .sidebar-toggle-button {
top: calc($sticky-header-height + 0.5rem);
@include media-breakpoint-down(sm) {
top: 0.5rem;
}
}
.detail-body { .detail-body {
.sidebar { .sidebar {
// required for sticky to work // required for sticky to work
@ -921,19 +944,11 @@ $sticky-header-height: calc(50px + 3.3rem);
} }
@include media-breakpoint-down(xs) { @include media-breakpoint-down(xs) {
.sidebar { .sidebar {
flex: 100% 0 0; // flex: 100% 0 0;
height: calc(100vh - 4rem); height: calc(100vh - $navbar-height);
max-height: calc(100vh - 4rem); max-height: calc(100vh - $navbar-height);
top: 0; top: 0;
} }
.sidebar-pane:not(.hide-sidebar) .sidebar {
margin-right: -100%;
}
.sidebar-pane.hide-sidebar .sidebar {
display: none;
}
} }
@include media-breakpoint-up(md) { @include media-breakpoint-up(md) {
.sidebar-pane:not(.hide-sidebar) { .sidebar-pane:not(.hide-sidebar) {