From 97f0eeed72c254f752bc0a4a34af8d15b7ed651f Mon Sep 17 00:00:00 2001 From: Jonathan Payne Date: Thu, 22 Oct 2026 13:58:11 -0400 Subject: [PATCH 1/2] closes OpenConceptLab/ocl_issues#2946 | labelled Filters button; filters start open on wide screens on repo pages and repository lists - The results toolbar's filter toggle is now a "Filters" button in the same style as Sort By and Display, with the applied-filter count, e.g. "Filters (2)". It reuses the existing search.filters key in every locale. - Repo pages and org and user repository lists start with the filter panel open at MUI's lg breakpoint and up (1200px), closed below it. - Opening or closing the panel is remembered while the page stays mounted, so a tab or version change or a window resize doesn't undo it. - Sort By and Display no longer wrap when the toolbar is narrow. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TjM9dGow2KV1Gwr2dWQdph --- src/components/orgs/OrgHome.jsx | 5 ++++- src/components/repos/RepoHome.jsx | 5 ++++- src/components/search/Search.jsx | 5 ++++- src/components/search/SearchControls.jsx | 4 ++-- src/components/search/SearchResults.jsx | 12 +++++------- src/components/users/UserHome.jsx | 5 ++++- src/hooks/useDefaultFiltersOpen.js | 16 ++++++++++++++++ 7 files changed, 39 insertions(+), 13 deletions(-) create mode 100644 src/hooks/useDefaultFiltersOpen.js diff --git a/src/components/orgs/OrgHome.jsx b/src/components/orgs/OrgHome.jsx index 2b18615b2..d17d43bd6 100644 --- a/src/components/orgs/OrgHome.jsx +++ b/src/components/orgs/OrgHome.jsx @@ -10,6 +10,7 @@ import { OperationsContext } from '../app/LayoutContext'; import CommonTabs from '../common/CommonTabs'; import DeleteEntityDialog from '../common/DeleteEntityDialog' import Search from '../search/Search'; +import useDefaultFiltersOpen from '../../hooks/useDefaultFiltersOpen'; import Error40X from '../errors/Error40X'; import OrgOverview from './OrgOverview'; import OrgSummary from './OrgSummary'; @@ -25,6 +26,7 @@ const OrgHome = () => { const [members, setMembers] = React.useState([]) const [deleteOrg, setDeleteOrg] = React.useState(false) const [tab, setTab] = React.useState(findTab) + const [defaultFiltersOpen, onFiltersOpenChange] = useDefaultFiltersOpen() const { setAlert } = React.useContext(OperationsContext); const { pins: bookmarks, canPin, togglePin, deletePin } = usePins(params?.org ? {type: 'org', id: params.org} : false) @@ -108,7 +110,8 @@ const OrgHome = () => { pins={bookmarks} canPin={canPin} onPinToggle={togglePin} - defaultFiltersOpen={false} + defaultFiltersOpen={defaultFiltersOpen} + onFiltersOpenChange={onFiltersOpenChange} nested noTabs filtersHeightToSubtract={280} diff --git a/src/components/repos/RepoHome.jsx b/src/components/repos/RepoHome.jsx index 17f305bd2..667c49392 100644 --- a/src/components/repos/RepoHome.jsx +++ b/src/components/repos/RepoHome.jsx @@ -21,6 +21,7 @@ import { WHITE } from '../../common/colors'; import { OperationsContext } from '../app/LayoutContext'; import CommonTabs from '../common/CommonTabs'; import Search from '../search/Search'; +import useDefaultFiltersOpen from '../../hooks/useDefaultFiltersOpen'; import DeleteEntityDialog from '../common/DeleteEntityDialog' import ConceptHome from '../concepts/ConceptHome'; import MappingHome from '../mappings/MappingHome'; @@ -86,6 +87,7 @@ const RepoHome = () => { const [showSummary, setShowSummary] = React.useState(true) const [addReferencesOpen, setAddReferencesOpen] = React.useState(false) const [searchReloadKey, setSearchReloadKey] = React.useState(0) + const [defaultFiltersOpen, onFiltersOpenChange] = useDefaultFiltersOpen() const [expansions, setExpansions] = React.useState([]) const [expansionsLoading, setExpansionsLoading] = React.useState(false) const [selectedExpansion, setSelectedExpansion] = React.useState(false) @@ -591,7 +593,8 @@ const RepoHome = () => { summary={repoSummary || repo?.summary} resource={tab} url={getSearchURL()} - defaultFiltersOpen={false} + defaultFiltersOpen={defaultFiltersOpen} + onFiltersOpenChange={onFiltersOpenChange} nested repo={repo} noTabs diff --git a/src/components/search/Search.jsx b/src/components/search/Search.jsx index 0f83bcad3..96a461e12 100644 --- a/src/components/search/Search.jsx +++ b/src/components/search/Search.jsx @@ -804,7 +804,10 @@ const Search = props => { onSelect={newSelected => onSelectItem(newSelected)} selectedToShow={showItem} onShowItemSelect={onShowItemSelect} - onFiltersToggle={() => setOpenFilters(!openFilters)} + onFiltersToggle={() => { + setOpenFilters(!openFilters) + props.onFiltersOpenChange?.(!openFilters) + }} onDisplayChange={onDisplayChange} resultContainerStyle={props.resultContainerStyle} resultSize={props.resultSize} diff --git a/src/components/search/SearchControls.jsx b/src/components/search/SearchControls.jsx index 48556e1d2..d511ff45a 100644 --- a/src/components/search/SearchControls.jsx +++ b/src/components/search/SearchControls.jsx @@ -25,13 +25,13 @@ const SearchControls = ({ disabled, onDisplayChange, display, order, orderBy, on
{ hasSortOptions(sortConfig) && - } { showDisplayControl && - } diff --git a/src/components/search/SearchResults.jsx b/src/components/search/SearchResults.jsx index 1810d35e4..8004a314a 100644 --- a/src/components/search/SearchResults.jsx +++ b/src/components/search/SearchResults.jsx @@ -2,10 +2,8 @@ import React from 'react'; import { useTranslation } from 'react-i18next' import { useHistory } from 'react-router-dom'; import Box from '@mui/material/Box'; -import Badge from '@mui/material/Badge'; import Toolbar from '@mui/material/Toolbar'; import Typography from '@mui/material/Typography'; -import IconButton from '@mui/material/IconButton'; import Button from '@mui/material/Button'; import FilterListIcon from '@mui/icons-material/FilterList'; import AddIcon from '@mui/icons-material/PlaylistAddOutlined'; @@ -30,6 +28,7 @@ import { createSimilarRepoHref } from '../repos/utils'; import { MAX_SEARCH_RESULT_WINDOW } from '../../common/constants'; const ResultsToolbar = props => { + const { t } = useTranslation() const { numSelected, title, onFiltersToggle, disabled, isFilterable, onDisplayChange, display, order, orderBy, onOrderByChange, sortConfig, noCardDisplay, toolbarControl, appliedFilters, availableFilterFields, openFilters, bulkActions, leftControls, displayOptions, resource } = props; const countableFilters = availableFilterFields ? pick(appliedFilters, availableFilterFields) : appliedFilters const filtersCount = resource === 'references' ? flatten(values(countableFilters))?.length : flatten(values(countableFilters).map(v => values(v))).length @@ -49,11 +48,9 @@ const ResultsToolbar = props => { > { isFilterable && - 0 ? {backgroundColor: 'rgba(73, 69, 79, 0.12)'} : {})}} onClick={onFiltersToggle} disabled={Boolean(disabled)}> - - - - + } { leftControls?.length > 0 && @@ -405,6 +402,7 @@ const SearchResults = props => { numSelected={selected.length} title={getTitle()} onFiltersToggle={props.onFiltersToggle} + openFilters={props.openFilters} disabled={props.noResults} isFilterable={props.isFilterable} onDisplayChange={onDisplayChange} diff --git a/src/components/users/UserHome.jsx b/src/components/users/UserHome.jsx index 0459d9095..78a9572f1 100644 --- a/src/components/users/UserHome.jsx +++ b/src/components/users/UserHome.jsx @@ -8,6 +8,7 @@ import { COLORS } from '../../common/colors' import APIService from '../../services/APIService' import CommonTabs from '../common/CommonTabs'; import Search from '../search/Search'; +import useDefaultFiltersOpen from '../../hooks/useDefaultFiltersOpen'; import Error40X from '../errors/Error40X'; import UserOverview from './UserOverview'; import UserSummary from './UserSummary' @@ -19,6 +20,7 @@ const UserHome = () => { const params = useParams() const history = useHistory() const [tab, setTab] = React.useState(findTab || 'overview') + const [defaultFiltersOpen, onFiltersOpenChange] = useDefaultFiltersOpen() const [user, setUser] = React.useState({}) const [status, setStatus] = React.useState(false) const { pins: bookmarks, canPin, togglePin, deletePin } = usePins(params?.user ? {type: 'user', id: params.user} : false) @@ -122,7 +124,8 @@ const UserHome = () => { filtersHeightToSubtract={baseHeightToDeduct} resultContainerStyle={{height: `calc(var(--app-height) - ${baseHeightToDeduct}px - 88px)`, overflow: 'auto'}} containerStyle={{padding: 0}} - defaultFiltersOpen={false} + defaultFiltersOpen={defaultFiltersOpen} + onFiltersOpenChange={onFiltersOpenChange} resultSize='medium' excludedColumns={['owner']} /> diff --git a/src/hooks/useDefaultFiltersOpen.js b/src/hooks/useDefaultFiltersOpen.js new file mode 100644 index 000000000..0148f3907 --- /dev/null +++ b/src/hooks/useDefaultFiltersOpen.js @@ -0,0 +1,16 @@ +import React from 'react'; +import useMediaQuery from '@mui/material/useMediaQuery'; + +// For pages that embed Search (repo pages, org and user repository lists): the filter +// panel starts open on wide screens and closed on narrow ones. Once the user opens or +// closes it, that choice is kept while the page stays mounted, including when Search +// remounts on a tab or version change. A window resize never opens or closes it. +// Returns [defaultFiltersOpen, onFiltersOpenChange] to pass to Search. +const useDefaultFiltersOpen = () => { + // Search reads defaultFiltersOpen once, when it mounts, so the first render needs the real width. + const isWide = useMediaQuery(theme => theme.breakpoints.up('lg'), {noSsr: true}) // eslint-disable-line spellcheck/spell-checker + const [choice, setChoice] = React.useState(null) + return [choice === null ? isWide : choice, setChoice] +} + +export default useDefaultFiltersOpen; From a5929ecbc20b1a6bd2bd8f891316e2578df79581 Mon Sep 17 00:00:00 2001 From: Jonathan Payne Date: Thu, 22 Oct 2026 14:05:17 -0400 Subject: [PATCH 2/2] OpenConceptLab/ocl_issues#2946 | Filters count ignores the exclude flag and counts reference filters The button now shows the applied-filter count, so the count it inherited has to be right: an excluded facet counted its __exclude__ marker as a second filter, and reference searches (which fetch no facets) always counted zero. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TjM9dGow2KV1Gwr2dWQdph --- src/components/search/SearchResults.jsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/search/SearchResults.jsx b/src/components/search/SearchResults.jsx index 8004a314a..af47fcbd1 100644 --- a/src/components/search/SearchResults.jsx +++ b/src/components/search/SearchResults.jsx @@ -11,7 +11,7 @@ import RepeatIcon from '@mui/icons-material/Repeat'; import CloneIcon from '@mui/icons-material/ControlPointDuplicate'; import TablePagination from '@mui/material/TablePagination'; import Skeleton from '@mui/material/Skeleton'; -import { isNumber, isNaN, flatten, values, uniqBy, compact, pick, keys } from 'lodash' +import { isNumber, isNaN, flatten, values, uniqBy, compact, pick, keys, omit } from 'lodash' import SearchControls from './SearchControls'; import NoResults from './NoResults'; import TableResults from './TableResults'; @@ -22,6 +22,7 @@ import CloneToSourceDialog from '../repos/CloneToSourceDialog'; import MappingIcon from '../mappings/MappingIcon'; import GAService from '../../services/GAService'; import { getSortConfig } from './sortConfig' +import { EXCLUDE_FILTER_KEY } from './ResultConstants' import { resolveColumns } from './columns' import { isLoggedIn, currentUserHasAccess } from '../../common/utils'; import { createSimilarRepoHref } from '../repos/utils'; @@ -30,8 +31,9 @@ import { MAX_SEARCH_RESULT_WINDOW } from '../../common/constants'; const ResultsToolbar = props => { const { t } = useTranslation() const { numSelected, title, onFiltersToggle, disabled, isFilterable, onDisplayChange, display, order, orderBy, onOrderByChange, sortConfig, noCardDisplay, toolbarControl, appliedFilters, availableFilterFields, openFilters, bulkActions, leftControls, displayOptions, resource } = props; - const countableFilters = availableFilterFields ? pick(appliedFilters, availableFilterFields) : appliedFilters - const filtersCount = resource === 'references' ? flatten(values(countableFilters))?.length : flatten(values(countableFilters).map(v => values(v))).length + // Reference searches fetch no facets, so their filters can't be narrowed to facet fields. + const countableFilters = (availableFilterFields && resource !== 'references') ? pick(appliedFilters, availableFilterFields) : appliedFilters + const filtersCount = resource === 'references' ? flatten(values(countableFilters))?.length : flatten(values(countableFilters).map(v => values(omit(v, EXCLUDE_FILTER_KEY)))).length return (