diff --git a/src/renderer/api/navidrome/navidrome-controller.ts b/src/renderer/api/navidrome/navidrome-controller.ts index 2bbcbce09..d0bc9a0c0 100644 --- a/src/renderer/api/navidrome/navidrome-controller.ts +++ b/src/renderer/api/navidrome/navidrome-controller.ts @@ -299,6 +299,7 @@ export const NavidromeController: InternalControllerEndpoint = { genre_id: genres, library_id: getLibraryId(query.musicFolderId), name: query.searchTerm, + year: query.maxYear || query.minYear, ...query._custom, starred: query.favorite, ...excludeMissing(apiClientProps.server), diff --git a/src/renderer/features/albums/components/album-list-content.tsx b/src/renderer/features/albums/components/album-list-content.tsx index 79fdb3548..d79f5ab2d 100644 --- a/src/renderer/features/albums/components/album-list-content.tsx +++ b/src/renderer/features/albums/components/album-list-content.tsx @@ -2,9 +2,12 @@ import { lazy, Suspense, useMemo } from 'react'; import { useListContext } from '/@/renderer/context/list-context'; import { useAlbumListFilters } from '/@/renderer/features/albums/hooks/use-album-list-filters'; +import { ListFilters } from '/@/renderer/features/shared/components/list-filters'; +import { ListWithSidebarContainer } from '/@/renderer/features/shared/components/list-with-sidebar-container'; import { ItemListSettings, useCurrentServer, useListSettings } from '/@/renderer/store'; +import { ScrollArea } from '/@/shared/components/scroll-area/scroll-area'; import { Spinner } from '/@/shared/components/spinner/spinner'; -import { AlbumListQuery } from '/@/shared/types/domain-types'; +import { AlbumListQuery, LibraryItem } from '/@/shared/types/domain-types'; import { ItemListKey, ListDisplayType, ListPaginationType } from '/@/shared/types/types'; const AlbumListInfiniteGrid = lazy(() => @@ -37,16 +40,23 @@ export const AlbumListContent = () => { const { customFilters } = useListContext(); return ( - }> - - + <> + + + + + + }> + + + ); }; @@ -77,6 +87,10 @@ export const AlbumListView = ({ }; }, [query, overrideQuery]); + console.log('query', query); + console.log('overrideQuery', overrideQuery); + console.log('mergedQuery', mergedQuery); + switch (display) { case ListDisplayType.GRID: { switch (pagination) { diff --git a/src/renderer/features/albums/components/album-list-header-filters.tsx b/src/renderer/features/albums/components/album-list-header-filters.tsx index f6aa8c053..6db35b049 100644 --- a/src/renderer/features/albums/components/album-list-header-filters.tsx +++ b/src/renderer/features/albums/components/album-list-header-filters.tsx @@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next'; import { ALBUM_TABLE_COLUMNS } from '/@/renderer/components/item-list/item-table-list/default-columns'; import { useAlbumListFilters } from '/@/renderer/features/albums/hooks/use-album-list-filters'; import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu'; -import { ListFilters } from '/@/renderer/features/shared/components/list-filters'; +import { ListFiltersModal } from '/@/renderer/features/shared/components/list-filters'; import { ListRefreshButton } from '/@/renderer/features/shared/components/list-refresh-button'; import { ListSortByDropdown } from '/@/renderer/features/shared/components/list-sort-by-dropdown'; import { ListSortOrderToggleButton } from '/@/renderer/features/shared/components/list-sort-order-toggle-button'; @@ -65,7 +65,7 @@ export const AlbumListHeaderFilters = ({ toggleGenreTarget }: { toggleGenreTarge defaultSortOrder={SortOrder.ASC} listKey={ItemListKey.ALBUM} /> - + diff --git a/src/renderer/features/albums/components/navidrome-album-filters.tsx b/src/renderer/features/albums/components/navidrome-album-filters.tsx index 9e7c59e5c..9bb58cb60 100644 --- a/src/renderer/features/albums/components/navidrome-album-filters.tsx +++ b/src/renderer/features/albums/components/navidrome-album-filters.tsx @@ -1,6 +1,5 @@ import { useQuery } from '@tanstack/react-query'; -import debounce from 'lodash/debounce'; -import { ChangeEvent, useMemo } from 'react'; +import { ChangeEvent, memo, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -11,19 +10,17 @@ import { useAlbumListFilters } from '/@/renderer/features/albums/hooks/use-album import { artistsQueries } from '/@/renderer/features/artists/api/artists-api'; import { useGenreList } from '/@/renderer/features/genres/api/genres-api'; import { sharedQueries } from '/@/renderer/features/shared/api/shared-api'; -import { useCurrentServer } from '/@/renderer/store'; -import { NDSongQueryFields } from '/@/shared/api/navidrome/navidrome-types'; -import { hasFeature } from '/@/shared/api/utils'; +import { useCurrentServer, useCurrentServerId } from '/@/renderer/store'; +import { titleCase } from '/@/renderer/utils'; import { Divider } from '/@/shared/components/divider/divider'; import { Group } from '/@/shared/components/group/group'; import { NumberInput } from '/@/shared/components/number-input/number-input'; -import { SpinnerIcon } from '/@/shared/components/spinner/spinner'; +import { Spinner, SpinnerIcon } from '/@/shared/components/spinner/spinner'; import { Stack } from '/@/shared/components/stack/stack'; import { Switch } from '/@/shared/components/switch/switch'; import { Text } from '/@/shared/components/text/text'; import { YesNoSelect } from '/@/shared/components/yes-no-select/yes-no-select'; import { AlbumArtistListSort, LibraryItem, SortOrder } from '/@/shared/types/domain-types'; -import { ServerFeature } from '/@/shared/types/features-types'; interface NavidromeAlbumFiltersProps { disableArtistFilter?: boolean; @@ -38,7 +35,6 @@ export const NavidromeAlbumFilters = ({ disableArtistFilter }: NavidromeAlbumFil query, setAlbumArtist, setCompilation, - setCustom, setFavorite, setGenreId, setHasRating, @@ -57,60 +53,73 @@ export const NavidromeAlbumFilters = ({ disableArtistFilter }: NavidromeAlbumFil })); }, [genreListQuery.data]); - const tagsQuery = useQuery( - sharedQueries.tags({ - options: { - gcTime: 1000 * 60 * 2, - staleTime: 1000 * 60 * 1, + const yesNoUndefinedFilters = useMemo( + () => [ + { + label: t('filter.isFavorited', { postProcess: 'sentenceCase' }), + onChange: (favorite?: boolean) => { + setFavorite(favorite ?? null); + }, + value: query.favorite, }, - query: { - type: LibraryItem.ALBUM, + { + label: t('filter.isCompilation', { postProcess: 'sentenceCase' }), + onChange: (compilation?: boolean) => { + setCompilation(compilation ?? null); + }, + value: query.compilation, }, - serverId, - }), + ], + [t, query.favorite, query.compilation, setFavorite, setCompilation], ); - const yesNoUndefinedFilters = [ - { - label: t('filter.isFavorited', { postProcess: 'sentenceCase' }), - onChange: (favorite?: boolean) => { - setFavorite(favorite ?? null); + const toggleFilters = useMemo( + () => [ + { + label: t('filter.isRated', { postProcess: 'sentenceCase' }), + onChange: (e: ChangeEvent) => { + const hasRating = e.currentTarget.checked ? true : undefined; + setHasRating(hasRating ?? null); + }, + value: query.hasRating, }, - value: query.favorite, - }, - { - label: t('filter.isCompilation', { postProcess: 'sentenceCase' }), - onChange: (compilation?: boolean) => { - setCompilation(compilation ?? null); + { + label: t('filter.isRecentlyPlayed', { postProcess: 'sentenceCase' }), + onChange: (e: ChangeEvent) => { + const recentlyPlayed = e.currentTarget.checked ? true : undefined; + setRecentlyPlayed(recentlyPlayed ?? null); + }, + value: query.recentlyPlayed, }, - value: query.compilation, - }, - ]; + ], + [t, query.hasRating, query.recentlyPlayed, setHasRating, setRecentlyPlayed], + ); - const toggleFilters = [ - { - label: t('filter.isRated', { postProcess: 'sentenceCase' }), - onChange: (e: ChangeEvent) => { - const hasRating = e.currentTarget.checked ? true : undefined; - setHasRating(hasRating ?? null); - }, - value: query.hasRating, - }, - { - label: t('filter.isRecentlyPlayed', { postProcess: 'sentenceCase' }), - onChange: (e: ChangeEvent) => { - const recentlyPlayed = e.currentTarget.checked ? true : undefined; - setRecentlyPlayed(recentlyPlayed ?? null); - }, - value: query.recentlyPlayed, - }, - ]; + const handleYearFilter = useMemo( + () => (e: number | string) => { + // Handle empty string, null, undefined, or invalid numbers as clearing - const handleYearFilter = debounce((e: number | string) => { - const year = e === '' ? undefined : (e as number); - setMinYear(year ?? null); - setMaxYear(year ?? null); - }, 500); + if (e === '' || e === null || e === undefined) { + console.log('clearing year filters'); + setMinYear(null); + setMaxYear(null); + return; + } + + const year = typeof e === 'number' ? e : Number(e); + // If it's a valid number, set it; otherwise clear + if (!isNaN(year) && isFinite(year) && year > 0) { + console.log('setting year filters', year); + setMinYear(year); + setMaxYear(year); + } else { + console.log('clearing year filters', year); + setMinYear(null); + setMaxYear(null); + } + }, + [setMinYear, setMaxYear], + ); const albumArtistListQuery = useQuery( artistsQueries.albumArtistList({ @@ -136,26 +145,15 @@ export const NavidromeAlbumFilters = ({ disableArtistFilter }: NavidromeAlbumFil })); }, [albumArtistListQuery.data?.items]); - const handleTagFilter = debounce((tag: string, e: null | string) => { - setCustom((prev) => ({ - ...prev, - [tag]: e || undefined, - })); - }, 250); - - const hasBFR = hasFeature(server, ServerFeature.BFR); - return ( {yesNoUndefinedFilters.map((filter) => ( - - {filter.label} - - + ))} {toggleFilters.map((filter) => ( @@ -164,67 +162,153 @@ export const NavidromeAlbumFilters = ({ disableArtistFilter }: NavidromeAlbumFil ))} - - handleYearFilter(e)} - /> - (e ? setGenreId([e]) : undefined)} - searchable - /> - - {hasBFR && ( - - (e ? setGenreId(e) : undefined)} - searchable - /> - - )} - - setAlbumArtist(e ? [e] : null)} - rightSection={albumArtistListQuery.isFetching ? : undefined} - searchable - /> - - {tagsQuery.data?.enumTags?.length && - tagsQuery.data.enumTags.length > 0 && - tagsQuery.data.enumTags.map((tag) => ( - - i.value === tag.name)?.label || - tag.name - } - onChange={(value) => handleTagFilter(tag.name, value)} - searchable - width={150} - /> - - ))} + handleYearFilter(e.currentTarget.value)} + /> + (e && e.length > 0 ? setGenreId(e) : setGenreId(null))} + searchable + /> + setAlbumArtist(e ? [e] : null)} + rightSection={albumArtistListQuery.isFetching ? : undefined} + searchable + /> + ); }; + +interface TagFilterItemProps { + label: string; + onChange: (value: null | string) => void; + options: string[]; + tagValue: string; + value: string | undefined; +} + +const TagFilterItem = memo( + ({ label, onChange, options, tagValue, value }: TagFilterItemProps) => { + return ( + + ); + }, + (prevProps, nextProps) => { + // Only re-render if the specific tag's value or options change + // We don't compare onChange since it's a stable wrapper around handleTagFilter + // and handleTagFilter itself is memoized and stable + return ( + prevProps.tagValue === nextProps.tagValue && + prevProps.label === nextProps.label && + prevProps.value === nextProps.value && + prevProps.options === nextProps.options + ); + }, +); + +TagFilterItem.displayName = 'TagFilterItem'; + +const TagFilters = () => { + const { query, setCustom } = useAlbumListFilters(); + + const serverId = useCurrentServerId(); + + const tagsQuery = useQuery( + sharedQueries.tags({ + options: { + gcTime: 1000 * 60 * 60, + staleTime: 1000 * 60 * 60, + }, + query: { + type: LibraryItem.ALBUM, + }, + serverId, + }), + ); + + const handleTagFilter = useMemo( + () => (tag: string, e: null | string) => { + setCustom((prev) => { + if (!prev) { + return e ? { [tag]: e } : null; + } + + if (e === null) { + const rest = Object.fromEntries( + Object.entries(prev).filter(([key]) => key !== tag), + ); + + return Object.keys(rest).length === 0 ? null : rest; + } + + return { + ...prev, + [tag]: e, + }; + }); + }, + [setCustom], + ); + + const tags = useMemo(() => { + return ( + tagsQuery.data?.enumTags?.map((tag) => ({ + label: titleCase(tag.name), + options: tag.options, + value: tag.name, + })) || [] + ); + }, [tagsQuery.data?.enumTags]); + + // Create stable onChange handlers for each tag using useMemo + const tagHandlers = useMemo(() => { + const handlers = new Map void>(); + tags.forEach((tag) => { + handlers.set(tag.value, (value: null | string) => handleTagFilter(tag.value, value)); + }); + return handlers; + }, [tags, handleTagFilter]); + + if (tagsQuery.isLoading) { + return ; + } + + return ( + <> + {tags.map((tag) => ( + + ))} + + ); +}; diff --git a/src/renderer/features/albums/hooks/use-album-list-filters.ts b/src/renderer/features/albums/hooks/use-album-list-filters.ts index 24bcd244b..1694247b2 100644 --- a/src/renderer/features/albums/hooks/use-album-list-filters.ts +++ b/src/renderer/features/albums/hooks/use-album-list-filters.ts @@ -6,7 +6,7 @@ import { parseAsString, useQueryState, } from 'nuqs'; -import { useCallback } from 'react'; +import { useCallback, useMemo } from 'react'; import { useSearchTermFilter } from '/@/renderer/features/shared/hooks/use-search-term-filter'; import { useSortByFilter } from '/@/renderer/features/shared/hooks/use-sort-by-filter'; @@ -86,20 +86,36 @@ export const useAlbumListFilters = () => { setSortOrder, ]); - const query = { - [FILTER_KEYS.ALBUM._CUSTOM]: custom ?? undefined, - [FILTER_KEYS.ALBUM.ARTIST_IDS]: albumArtist ?? undefined, - [FILTER_KEYS.ALBUM.COMPILATION]: compilation ?? undefined, - [FILTER_KEYS.ALBUM.FAVORITE]: favorite ?? undefined, - [FILTER_KEYS.ALBUM.GENRE_ID]: genreId ?? undefined, - [FILTER_KEYS.ALBUM.HAS_RATING]: hasRating ?? undefined, - [FILTER_KEYS.ALBUM.MAX_YEAR]: maxYear ?? undefined, - [FILTER_KEYS.ALBUM.MIN_YEAR]: minYear ?? undefined, - [FILTER_KEYS.ALBUM.RECENTLY_PLAYED]: recentlyPlayed ?? undefined, - [FILTER_KEYS.SHARED.SEARCH_TERM]: searchTerm ?? undefined, - [FILTER_KEYS.SHARED.SORT_BY]: sortBy ?? undefined, - [FILTER_KEYS.SHARED.SORT_ORDER]: sortOrder ?? undefined, - }; + const query = useMemo( + () => ({ + [FILTER_KEYS.ALBUM._CUSTOM]: custom ?? undefined, + [FILTER_KEYS.ALBUM.ARTIST_IDS]: albumArtist ?? undefined, + [FILTER_KEYS.ALBUM.COMPILATION]: compilation ?? undefined, + [FILTER_KEYS.ALBUM.FAVORITE]: favorite ?? undefined, + [FILTER_KEYS.ALBUM.GENRE_ID]: genreId ?? undefined, + [FILTER_KEYS.ALBUM.HAS_RATING]: hasRating ?? undefined, + [FILTER_KEYS.ALBUM.MAX_YEAR]: maxYear ?? undefined, + [FILTER_KEYS.ALBUM.MIN_YEAR]: minYear ?? undefined, + [FILTER_KEYS.ALBUM.RECENTLY_PLAYED]: recentlyPlayed ?? undefined, + [FILTER_KEYS.SHARED.SEARCH_TERM]: searchTerm ?? undefined, + [FILTER_KEYS.SHARED.SORT_BY]: sortBy ?? undefined, + [FILTER_KEYS.SHARED.SORT_ORDER]: sortOrder ?? undefined, + }), + [ + custom, + albumArtist, + compilation, + favorite, + genreId, + hasRating, + maxYear, + minYear, + recentlyPlayed, + searchTerm, + sortBy, + sortOrder, + ], + ); return { clear, diff --git a/src/renderer/features/albums/routes/album-list-route.tsx b/src/renderer/features/albums/routes/album-list-route.tsx index 8b6a9d0b1..101a6b8b8 100644 --- a/src/renderer/features/albums/routes/album-list-route.tsx +++ b/src/renderer/features/albums/routes/album-list-route.tsx @@ -5,6 +5,7 @@ import { ListContext } from '/@/renderer/context/list-context'; import { AlbumListContent } from '/@/renderer/features/albums/components/album-list-content'; import { AlbumListHeader } from '/@/renderer/features/albums/components/album-list-header'; import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page'; +import { ListWithSidebarContainer } from '/@/renderer/features/shared/components/list-with-sidebar-container'; import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary'; import { AlbumListQuery } from '/@/shared/types/domain-types'; import { ItemListKey } from '/@/shared/types/types'; @@ -28,10 +29,19 @@ const AlbumListRoute = () => { const [itemCount, setItemCount] = useState(undefined); const customFilters: Partial = useMemo(() => { - return { - artistIds: albumArtistId ? [albumArtistId] : undefined, - genreIds: genreId ? [genreId] : undefined, - }; + if (albumArtistId) { + return { + artistIds: [albumArtistId], + }; + } + + if (genreId) { + return { + genreIds: [genreId], + }; + } + + return {}; }, [albumArtistId, genreId]); const providerValue = useMemo(() => { @@ -48,7 +58,9 @@ const AlbumListRoute = () => { - + + + ); diff --git a/src/renderer/features/genres/components/genre-list-header-filters.tsx b/src/renderer/features/genres/components/genre-list-header-filters.tsx index 28e7592c2..dd0b36198 100644 --- a/src/renderer/features/genres/components/genre-list-header-filters.tsx +++ b/src/renderer/features/genres/components/genre-list-header-filters.tsx @@ -1,6 +1,6 @@ import { GENRE_TABLE_COLUMNS } from '/@/renderer/components/item-list/item-table-list/default-columns'; import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu'; -import { ListFilters } from '/@/renderer/features/shared/components/list-filters'; +import { ListFiltersModal } from '/@/renderer/features/shared/components/list-filters'; import { ListRefreshButton } from '/@/renderer/features/shared/components/list-refresh-button'; import { ListSortByDropdown } from '/@/renderer/features/shared/components/list-sort-by-dropdown'; import { ListSortOrderToggleButton } from '/@/renderer/features/shared/components/list-sort-order-toggle-button'; @@ -24,7 +24,7 @@ export const GenreListHeaderFilters = () => { defaultSortOrder={SortOrder.ASC} listKey={ItemListKey.GENRE} /> - + diff --git a/src/renderer/features/playlists/components/playlist-list-header-filters.tsx b/src/renderer/features/playlists/components/playlist-list-header-filters.tsx index d6a12d9b8..f907ef630 100644 --- a/src/renderer/features/playlists/components/playlist-list-header-filters.tsx +++ b/src/renderer/features/playlists/components/playlist-list-header-filters.tsx @@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next'; import { PLAYLIST_TABLE_COLUMNS } from '/@/renderer/components/item-list/item-table-list/default-columns'; import { CreatePlaylistForm } from '/@/renderer/features/playlists/components/create-playlist-form'; import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu'; -import { ListFilters } from '/@/renderer/features/shared/components/list-filters'; +import { ListFiltersModal } from '/@/renderer/features/shared/components/list-filters'; import { ListRefreshButton } from '/@/renderer/features/shared/components/list-refresh-button'; import { ListSortByDropdown } from '/@/renderer/features/shared/components/list-sort-by-dropdown'; import { ListSortOrderToggleButton } from '/@/renderer/features/shared/components/list-sort-order-toggle-button'; @@ -39,7 +39,7 @@ export const PlaylistListHeaderFilters = () => { defaultSortOrder={SortOrder.ASC} listKey={ItemListKey.PLAYLIST} /> - + diff --git a/src/renderer/features/shared/components/list-filters.tsx b/src/renderer/features/shared/components/list-filters.tsx index 534ef7694..4821c1d8d 100644 --- a/src/renderer/features/shared/components/list-filters.tsx +++ b/src/renderer/features/shared/components/list-filters.tsx @@ -17,7 +17,7 @@ interface ListFiltersProps { itemType: LibraryItem; } -export const ListFilters = ({ isActive, itemType }: ListFiltersProps) => { +export const ListFiltersModal = ({ isActive, itemType }: ListFiltersProps) => { const { t } = useTranslation(); const server = useCurrentServer(); @@ -41,6 +41,14 @@ export const ListFilters = ({ isActive, itemType }: ListFiltersProps) => { ); }; +export const ListFilters = ({ itemType }: ListFiltersProps) => { + const server = useCurrentServer(); + const serverType = server.type; + const FilterComponent = FILTERS[serverType][itemType]; + + return ; +}; + const FILTERS = { [ServerType.JELLYFIN]: { [LibraryItem.ALBUM]: JellyfinAlbumFilters, diff --git a/src/renderer/features/shared/components/list-with-sidebar-container.module.css b/src/renderer/features/shared/components/list-with-sidebar-container.module.css new file mode 100644 index 000000000..d4d88bb16 --- /dev/null +++ b/src/renderer/features/shared/components/list-with-sidebar-container.module.css @@ -0,0 +1,31 @@ +.container { + position: relative; + display: flex; + flex-direction: row; + width: 100%; + height: 100%; + container-type: inline-size; + overflow: hidden; +} + +.sidebar-container { + position: relative; + flex-shrink: 0; + width: 300px; + min-width: 300px; + max-width: 300px; + height: 100%; + overflow: hidden; + border-right: 1px solid var(--theme-colors-border); +} + +.content-container { + position: relative; + display: flex; + flex: 1; + flex-direction: column; + width: 100%; + min-width: 0; + height: 100%; + overflow: hidden; +} diff --git a/src/renderer/features/shared/components/list-with-sidebar-container.tsx b/src/renderer/features/shared/components/list-with-sidebar-container.tsx new file mode 100644 index 000000000..a5f42dded --- /dev/null +++ b/src/renderer/features/shared/components/list-with-sidebar-container.tsx @@ -0,0 +1,100 @@ +import { motion } from 'motion/react'; +import { createContext, ReactNode, useContext, useMemo, useRef } from 'react'; + +import styles from './list-with-sidebar-container.module.css'; + +import { useContainerQuery } from '/@/renderer/hooks'; +import { animationProps } from '/@/shared/components/animations/animation-props'; +import { Portal } from '/@/shared/components/portal/portal'; + +interface ListWithSidebarContainerContextValue { + showSidebar: boolean; + sidebarRef: React.RefObject; +} + +const ListWithSidebarContainerContext = createContext( + null, +); + +interface ListWithSidebarContainerProps { + children: ReactNode; + sidebarBreakpoint?: number; +} + +interface SidebarPortalProps { + children: ReactNode; +} + +interface SidebarProps { + children: ReactNode; +} + +function Sidebar({ children }: SidebarProps) { + const context = useContext(ListWithSidebarContainerContext); + + if (!context) { + throw new Error('Sidebar must be used within ResponsiveAnimatedPage'); + } + + if (!context.showSidebar || !context.sidebarRef?.current) { + return null; + } + + return ( + + + {children} + + + ); +} + +function SidebarPortal({ children }: SidebarPortalProps) { + const context = useContext(ListWithSidebarContainerContext); + + if (!context) { + throw new Error('SidebarPortal must be used within ResponsiveAnimatedPage'); + } + + if (!context.showSidebar || !context.sidebarRef?.current) { + return null; + } + + return {children}; +} + +export const ListWithSidebarContainer = ({ + children, + sidebarBreakpoint, +}: ListWithSidebarContainerProps) => { + const sidebarRef = useRef(null); + const { isLg, ref: containerQueryRef } = useContainerQuery({ + lg: sidebarBreakpoint, + }); + + const showSidebar = isLg; + + const contextValue = useMemo( + () => ({ + showSidebar, + sidebarRef, + }), + [showSidebar], + ); + + return ( + +
+
+
{children}
+
+ + ); +}; + +ListWithSidebarContainer.Sidebar = Sidebar; +ListWithSidebarContainer.SidebarPortal = SidebarPortal; diff --git a/src/renderer/features/shared/utils.ts b/src/renderer/features/shared/utils.ts index 15eeccbd1..3056ef62d 100644 --- a/src/renderer/features/shared/utils.ts +++ b/src/renderer/features/shared/utils.ts @@ -40,7 +40,7 @@ enum AlbumFilterKeys { ARTIST_IDS = 'artistIds', COMPILATION = 'compilation', FAVORITE = 'favorite', - GENRE_ID = 'genreId', + GENRE_ID = 'genreIds', HAS_RATING = 'hasRating', MAX_YEAR = 'maxYear', MIN_YEAR = 'minYear', diff --git a/src/renderer/features/songs/components/song-list-header-filters.tsx b/src/renderer/features/songs/components/song-list-header-filters.tsx index d5214ee57..6b7e707cf 100644 --- a/src/renderer/features/songs/components/song-list-header-filters.tsx +++ b/src/renderer/features/songs/components/song-list-header-filters.tsx @@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next'; import { SONG_TABLE_COLUMNS } from '/@/renderer/components/item-list/item-table-list/default-columns'; import { useAlbumListFilters } from '/@/renderer/features/albums/hooks/use-album-list-filters'; import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu'; -import { ListFilters } from '/@/renderer/features/shared/components/list-filters'; +import { ListFiltersModal } from '/@/renderer/features/shared/components/list-filters'; import { ListRefreshButton } from '/@/renderer/features/shared/components/list-refresh-button'; import { ListSortByDropdown } from '/@/renderer/features/shared/components/list-sort-by-dropdown'; import { ListSortOrderToggleButton } from '/@/renderer/features/shared/components/list-sort-order-toggle-button'; @@ -65,7 +65,7 @@ export const SongListHeaderFilters = ({ toggleGenreTarget }: { toggleGenreTarget defaultSortOrder={SortOrder.ASC} listKey={ItemListKey.SONG} /> - + diff --git a/src/shared/components/select/select.tsx b/src/shared/components/select/select.tsx index 06973fcc2..de012025d 100644 --- a/src/shared/components/select/select.tsx +++ b/src/shared/components/select/select.tsx @@ -31,7 +31,8 @@ export const Select = ({ section: styles.section, ...classNames, }} - clearable={false} + clearable={clearable} + spellCheck={false} style={{ maxWidth, width }} variant={variant} withCheckIcon={false}