diff --git a/ui/v2.5/src/components/List/ListOperationButtons.tsx b/ui/v2.5/src/components/List/ListOperationButtons.tsx index 6bb31339a..bdb87fa3f 100644 --- a/ui/v2.5/src/components/List/ListOperationButtons.tsx +++ b/ui/v2.5/src/components/List/ListOperationButtons.tsx @@ -16,22 +16,28 @@ import { faTrash, } from "@fortawesome/free-solid-svg-icons"; import cx from "classnames"; +import { createPortal } from "react-dom"; export const OperationDropdown: React.FC< PropsWithChildren<{ className?: string; + menuPortalTarget?: HTMLElement; }> -> = ({ className, children }) => { +> = ({ className, menuPortalTarget, children }) => { if (!children) return null; + const menu = ( + + {children} + + ); + return ( - - {children} - + {menuPortalTarget ? createPortal(menu, menuPortalTarget) : menu} ); }; diff --git a/ui/v2.5/src/components/List/ListResultsHeader.tsx b/ui/v2.5/src/components/List/ListResultsHeader.tsx index 091317ec8..a2583c2e1 100644 --- a/ui/v2.5/src/components/List/ListResultsHeader.tsx +++ b/ui/v2.5/src/components/List/ListResultsHeader.tsx @@ -23,15 +23,6 @@ export const ListResultsHeader: React.FC<{ }) => { return ( -
- -
onChangeFilter(filter.setZoom(zoom))} />
+
+ +
+
); }; diff --git a/ui/v2.5/src/components/List/ListToolbar.tsx b/ui/v2.5/src/components/List/ListToolbar.tsx index 31ef7f7ee..25ee281c0 100644 --- a/ui/v2.5/src/components/List/ListToolbar.tsx +++ b/ui/v2.5/src/components/List/ListToolbar.tsx @@ -4,13 +4,15 @@ 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, ButtonToolbar } from "react-bootstrap"; +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; @@ -21,6 +23,7 @@ export const ToolbarFilterSection: React.FC<{ onRemoveAllCriterion: () => void; onEditSearchTerm: () => void; onRemoveSearchTerm: () => void; + view?: View; }> = PatchComponent( "ToolbarFilterSection", ({ @@ -32,6 +35,7 @@ export const ToolbarFilterSection: React.FC<{ onRemoveAllCriterion, onEditSearchTerm, onRemoveSearchTerm, + view, }) => { const { criteria, searchTerm } = filter; @@ -41,10 +45,19 @@ export const ToolbarFilterSection: React.FC<{
- onEditCriterion()} - count={criteria.length} - /> + + + + onEditCriterion()} + count={criteria.length} + /> + -
); @@ -65,28 +77,33 @@ export const ToolbarFilterSection: React.FC<{ export const ToolbarSelectionSection: React.FC<{ selected: number; onToggleSidebar: () => void; + operations?: React.ReactNode; onSelectAll: () => void; onSelectNone: () => void; }> = PatchComponent( "ToolbarSelectionSection", - ({ selected, onToggleSidebar, onSelectAll, onSelectNone }) => { + ({ selected, onToggleSidebar, operations, onSelectAll, onSelectNone }) => { const intl = useIntl(); return ( -
- - {selected} selected - - +
+
+ + + {selected} selected + +
+ {operations} +
); } @@ -114,7 +131,11 @@ export const FilteredListToolbar2: React.FC<{ })} > {!hasSelection ? filterSection : selectionSection} -
{operationSection}
+ {!hasSelection ? ( +
+ {operationSection} +
+ ) : null} ); }; diff --git a/ui/v2.5/src/components/List/SavedFilterList.tsx b/ui/v2.5/src/components/List/SavedFilterList.tsx index 7e03404d2..83c6d8a65 100644 --- a/ui/v2.5/src/components/List/SavedFilterList.tsx +++ b/ui/v2.5/src/components/List/SavedFilterList.tsx @@ -31,6 +31,7 @@ import { AlertModal } from "../Shared/Alert"; import cx from "classnames"; import { TruncatedInlineText } from "../Shared/TruncatedText"; import { OperationButton } from "../Shared/OperationButton"; +import { createPortal } from "react-dom"; const ExistingSavedFilterList: React.FC<{ name: string; @@ -243,6 +244,7 @@ interface ISavedFilterListProps { filter: ListFilterModel; onSetFilter: (f: ListFilterModel) => void; view?: View; + menuPortalTarget?: Element | DocumentFragment; } export const SavedFilterList: React.FC = ({ @@ -841,8 +843,15 @@ export const SavedFilterDropdown: React.FC = (props) => { )); SavedFilterDropdownRef.displayName = "SavedFilterDropdown"; + const menu = ( + + ); + return ( - + = (props) => { - + {props.menuPortalTarget + ? createPortal(menu, props.menuPortalTarget) + : menu} ); }; diff --git a/ui/v2.5/src/components/List/styles.scss b/ui/v2.5/src/components/List/styles.scss index c42c43a56..aba1f39df 100644 --- a/ui/v2.5/src/components/List/styles.scss +++ b/ui/v2.5/src/components/List/styles.scss @@ -1055,7 +1055,7 @@ input[type="range"].zoom-slider { } // hide sidebar Edit Filter button on larger screens -@include media-breakpoint-up(lg) { +@include media-breakpoint-up(md) { .sidebar .edit-filter-button { display: none; } @@ -1071,6 +1071,7 @@ input[type="range"].zoom-slider { display: flex; flex-wrap: wrap; justify-content: space-between; + margin-bottom: 0; row-gap: 1rem; > div { @@ -1101,10 +1102,6 @@ input[type="range"].zoom-slider { top: 0; } - .selected-items-info .btn { - margin-right: 0.5rem; - } - // hide drop down menu items for play and create new // when the buttons are visible @include media-breakpoint-up(sm) { @@ -1125,7 +1122,7 @@ input[type="range"].zoom-slider { } } - .selected-items-info, + .toolbar-selection-section, div.filter-section { border: 1px solid $secondary; border-radius: 0.25rem; @@ -1133,13 +1130,69 @@ input[type="range"].zoom-slider { overflow-x: hidden; } - .sidebar-toggle-button { - margin-left: auto; + div.toolbar-selection-section { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: center; + + .sidebar-toggle-button { + margin-right: 0.5rem; + } + + .selected-items-info { + align-items: center; + display: flex; + } + + > 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: block; + display: flex; margin-right: -0.5rem; min-width: calc($sidebar-width - 15px); padding-right: 10px; @@ -1175,21 +1228,27 @@ input[type="range"].zoom-slider { } } -@include media-breakpoint-up(xl) { +// 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 icon button when sidebar is shown on smaller screens -@include media-breakpoint-down(md) { - .sidebar-pane:not(.hide-sidebar) .filtered-list-toolbar .filter-button { - 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 @@ -1198,8 +1257,8 @@ input[type="range"].zoom-slider { } } -// move the sidebar toggle to the left on xl viewports -@include media-breakpoint-up(xl) { +// 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; @@ -1249,14 +1308,18 @@ input[type="range"].zoom-slider { align-items: center; background-color: $body-bg; display: flex; - justify-content: space-between; > 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; @@ -1265,18 +1328,55 @@ input[type="range"].zoom-slider { } .list-results-header { - flex-wrap: wrap-reverse; - gap: 0.5rem; + 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 { + justify-content: flex-end; + order: 3; + } + } + // center the header on smaller screens @include media-breakpoint-down(sm) { & > div, - & > div:last-child { + & > div.pagination-index-container { + flex-basis: 100%; + justify-content: center; + margin-left: auto; + margin-right: auto; + } + } +} + +// 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; diff --git a/ui/v2.5/src/components/Scenes/SceneList.tsx b/ui/v2.5/src/components/Scenes/SceneList.tsx index 982a11fed..f9257c9ad 100644 --- a/ui/v2.5/src/components/Scenes/SceneList.tsx +++ b/ui/v2.5/src/components/Scenes/SceneList.tsx @@ -37,7 +37,12 @@ import { OperationDropdownItem, } from "../List/ListOperationButtons"; import { useFilteredItemList } from "../List/ItemList"; -import { Sidebar, SidebarPane, useSidebarState } from "../Shared/Sidebar"; +import { + Sidebar, + SidebarPane, + SidebarPaneContent, + useSidebarState, +} from "../Shared/Sidebar"; import { SidebarPerformersFilter } from "../List/Filters/PerformersFilter"; import { SidebarStudiosFilter } from "../List/Filters/StudiosFilter"; import { PerformersCriterionOption } from "src/models/list-filter/criteria/performers"; @@ -355,7 +360,7 @@ const SceneListOperations: React.FC<{ const intl = useIntl(); return ( -
+
{!!items && (
diff --git a/ui/v2.5/src/components/Shared/Sidebar.tsx b/ui/v2.5/src/components/Shared/Sidebar.tsx index 52f9328f0..2fe0c48af 100644 --- a/ui/v2.5/src/components/Shared/Sidebar.tsx +++ b/ui/v2.5/src/components/Shared/Sidebar.tsx @@ -16,7 +16,8 @@ import { useIntl } from "react-intl"; import { Icon } from "./Icon"; import { faSliders } from "@fortawesome/free-solid-svg-icons"; -const fixedSidebarMediaQuery = "only screen and (max-width: 1199px)"; +// this needs to correspond to the CSS media query that overlaps the sidebar over content +const fixedSidebarMediaQuery = "only screen and (max-width: 767px)"; export const Sidebar: React.FC< PropsWithChildren<{ @@ -56,6 +57,10 @@ export const SidebarPane: React.FC< ); }; +export const SidebarPaneContent: React.FC = ({ children }) => { + return
{children}
; +}; + export const SidebarSection: React.FC< PropsWithChildren<{ text: React.ReactNode; @@ -87,7 +92,7 @@ export const SidebarToggleButton: React.FC<{ const intl = useIntl(); return (