From 9ed178342f13087d48d58ef32e4153728b1834cf Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Tue, 21 Jul 2026 00:51:14 +0200 Subject: [PATCH] Collapse library filters --- CHANGELOG.md | 3 +- src/base/components/Collapsable.tsx | 75 +++++++++++++ .../components/LibraryOptionsPanel.tsx | 100 ++++++++++++------ 3 files changed, 145 insertions(+), 33 deletions(-) create mode 100644 src/base/components/Collapsable.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 637648b6..31010d3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Added -- (**Library**) Add source filter to library filtering options - (**Migration**) Add a search option to ignore outdated matches - (**Migration**) Add a search option to ignore matches with missing chapters - (**Migration**) Add "local source" as a possible destination source @@ -19,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - (**Reader**) Add setting to keep the screen on while reading - (**Extension**) Support installing external JARs - (**Reader**) Add auto background color setting +- (**Library**) Add source filter - (**Library**) Add random sort option to library ### Changed @@ -49,6 +49,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - (**History**) Show only the last read chapter per manga - (**Updates**) Show only the first unread chapter per manga per day - (**Manga**) Hide details page title copy button in unsupported environments (secure context (https, localhost) required) +- (**Library**) Collapse `Status`, `Tracked` and `Source` filters ### Fixed diff --git a/src/base/components/Collapsable.tsx b/src/base/components/Collapsable.tsx new file mode 100644 index 00000000..0b3b658c --- /dev/null +++ b/src/base/components/Collapsable.tsx @@ -0,0 +1,75 @@ +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +import type { CollapseProps } from '@mui/material/Collapse'; +import Collapse from '@mui/material/Collapse'; +import type { StackProps } from '@mui/material/Stack'; +import Stack from '@mui/material/Stack'; +import type { ReactNode } from 'react'; +import { useState } from 'react'; +import ExpandLess from '@mui/icons-material/ExpandLess'; +import ExpandMore from '@mui/icons-material/ExpandMore'; + +export const Collapsable = ({ + header, + collapse, + initialState, + slots, +}: { + header: ReactNode; + collapse: ReactNode; + initialState?: boolean; + slots?: { + headerWrapper?: StackProps; + collapse?: CollapseProps; + }; +}) => { + const [isOpen, setIsOpen] = useState(initialState); + + return ( + <> + setIsOpen(!isOpen)} + > + {header} + {isOpen ? : } + + { + const wrapperInner = slots?.collapse?.slotProps?.wrapperInner; + const wrapperInnerProps = + typeof wrapperInner === 'function' ? wrapperInner(ownerState) : wrapperInner; + + return { + ...wrapperInnerProps, + sx: { + display: 'flex', + flexDirection: 'column', + ...wrapperInnerProps?.sx, + }, + }; + }, + }} + in={isOpen} + > + {collapse} + + + ); +}; diff --git a/src/features/library/components/LibraryOptionsPanel.tsx b/src/features/library/components/LibraryOptionsPanel.tsx index 95c3f803..f071784b 100644 --- a/src/features/library/components/LibraryOptionsPanel.tsx +++ b/src/features/library/components/LibraryOptionsPanel.tsx @@ -11,6 +11,7 @@ import FormLabel from '@mui/material/FormLabel'; import RadioGroup from '@mui/material/RadioGroup'; import { useLingui } from '@lingui/react/macro'; import { msg } from '@lingui/core/macro'; +import type { ReactNode } from 'react'; import { useMemo } from 'react'; import uniqBy from 'lodash/fp/uniqBy'; import { CheckboxInput } from '@/base/components/inputs/CheckboxInput.tsx'; @@ -36,6 +37,7 @@ import type { CategoryMetadataInfo } from '@/features/category/Category.types.ts import { MANGA_STATUS_TO_TRANSLATION } from '@/features/manga/Manga.constants.ts'; import { GridLayout } from '@/base/Base.types'; import { getErrorMessage } from '@/lib/HelperFunctions.ts'; +import { Collapsable } from '@/base/components/Collapsable.tsx'; const TITLES: { [key in 'filter' | 'sort' | 'display']: MessageDescriptor } = { filter: msg`Filter`, @@ -54,6 +56,23 @@ const SORT_OPTIONS: [LibrarySortMode, MessageDescriptor][] = [ ['random', msg`Random`], ]; +const CollapsableFilter = ({ title, items, isActive }: { title: string; items: ReactNode[]; isActive: boolean }) => ( + +); + export const LibraryOptionsPanel = ({ category, open, @@ -90,6 +109,14 @@ export const LibraryOptionsPanel = ({ makeToast(t`Could not save the default search settings to the server`, 'error', getErrorMessage(e)), ); + const isStatusFilterActive = Object.values(MangaStatus).some( + (status) => categoryLibraryOptions.hasStatus[status] != null, + ); + const isTrackerFilterActive = loggedInTrackers.some( + (tracker) => categoryLibraryOptions.hasTrackerBinding[tracker.id] != null, + ); + const isSourceFilterActive = librarySources.some((source) => categoryLibraryOptions.hasSource[source.id] != null); + return ( open={open} @@ -125,38 +152,46 @@ export const LibraryOptionsPanel = ({ checked={categoryLibraryOptions.hasDuplicateChapters} onChange={(c) => updateCategoryLibraryOptions('hasDuplicateChapters', c)} /> - {t`Status`} - {Object.values(MangaStatus).map((status) => ( - - updateCategoryLibraryOptions('hasStatus', { - ...categoryLibraryOptions.hasStatus, - [status]: checked, - }) - } + ( + + updateCategoryLibraryOptions('hasStatus', { + ...categoryLibraryOptions.hasStatus, + [status]: checked, + }) + } + /> + ))} + isActive={isStatusFilterActive} + /> + {!!loggedInTrackers.length && ( + ( + + updateCategoryLibraryOptions('hasTrackerBinding', { + ...categoryLibraryOptions.hasTrackerBinding, + [tracker.id]: checked, + }) + } + /> + ))} + isActive={isTrackerFilterActive} /> - ))} - {t`Tracked`} - {loggedInTrackers.map((tracker) => ( - - updateCategoryLibraryOptions('hasTrackerBinding', { - ...categoryLibraryOptions.hasTrackerBinding, - [tracker.id]: checked, - }) - } - /> - ))} - {librarySources.length > 0 && ( - <> - {t`Source`} - {librarySources.map((source) => ( + )} + {!!librarySources.length && ( + ( ))} - + isActive={isSourceFilterActive} + /> )} );