diff --git a/gradle/changelog/searchbar_in_search_page.yaml b/gradle/changelog/searchbar_in_search_page.yaml new file mode 100644 index 0000000000..e8a8994cbf --- /dev/null +++ b/gradle/changelog/searchbar_in_search_page.yaml @@ -0,0 +1,2 @@ +- type: added + description: The search page now contains another search bar within, that persists the current query diff --git a/scm-ui/ui-webapp/public/locales/de/commons.json b/scm-ui/ui-webapp/public/locales/de/commons.json index 5d271422f1..baceac31ff 100644 --- a/scm-ui/ui-webapp/public/locales/de/commons.json +++ b/scm-ui/ui-webapp/public/locales/de/commons.json @@ -260,8 +260,8 @@ "ariaLabel": "Globale Suche", "placeholder": "Suche...", "title": "Suche", - "subtitle": "{{type}} Ergebnisse für \"{{query}}\"", - "subtitleWithContext": "{{type}} Ergebnisse für \"{{query}}\" in \"{{context}}\"", + "subtitle": "{{type}} Ergebnisse", + "subtitleWithContext": "{{type}} Ergebnisse für in \"{{context}}\"", "types": "Ergebnisse", "noHits": "Keine Ergebnisse gefunden. Auf der rechten Seite finden Sie weitere Kategorien, in denen Ergebnisse vorhanden sein könnten", "syntaxHelp": "Finden Sie bessere Ergebnisse durch die Nutzung der vollen <0>Such-Syntax", diff --git a/scm-ui/ui-webapp/public/locales/en/commons.json b/scm-ui/ui-webapp/public/locales/en/commons.json index 80cd72ac7c..2a69fdb6cc 100644 --- a/scm-ui/ui-webapp/public/locales/en/commons.json +++ b/scm-ui/ui-webapp/public/locales/en/commons.json @@ -261,8 +261,8 @@ "ariaLabel": "Global search", "placeholder": "Search...", "title": "Search", - "subtitle": "{{type}} results for \"{{query}}\"", - "subtitleWithContext": "{{type}} results for \"{{query}}\" in \"{{context}}\"", + "subtitle": "{{type}} results", + "subtitleWithContext": "{{type}} results in \"{{context}}\"", "types": "Results", "noHits": "No results found. On the right side you will find other categories where results might be available", "syntaxHelp": "Find better results by using the full <0>search syntax", diff --git a/scm-ui/ui-webapp/src/containers/NavigationBar.tsx b/scm-ui/ui-webapp/src/containers/NavigationBar.tsx index 5c0be5718c..3e14398a86 100644 --- a/scm-ui/ui-webapp/src/containers/NavigationBar.tsx +++ b/scm-ui/ui-webapp/src/containers/NavigationBar.tsx @@ -139,7 +139,7 @@ const NavigationBar: FC = ({ links }) => {
- +
diff --git a/scm-ui/ui-webapp/src/containers/OmniSearch.tsx b/scm-ui/ui-webapp/src/containers/OmniSearch.tsx index a10f1acc62..fab07df619 100644 --- a/scm-ui/ui-webapp/src/containers/OmniSearch.tsx +++ b/scm-ui/ui-webapp/src/containers/OmniSearch.tsx @@ -53,6 +53,11 @@ const Input = styled.input` `; type Props = { + shouldClear: boolean; + ariaId: string; +}; + +type GuardProps = Props & { links: Links; }; @@ -67,6 +72,7 @@ type HitsProps = { entries: ReactElement>[]; hits: Hit[]; showHelp: () => void; + ariaId: string; }; const QuickSearchNotification: FC = ({ children }) =>
{children}
; @@ -97,21 +103,22 @@ const AvatarSection: FC<{ repository: Repository }> = ({ repository }) => { ); }; -const HitList: FC> = ({ entries }) => { +const HitList: FC> = ({ entries, ariaId }) => { return ( -
    +
      {entries}
    ); }; -const HitEntry: FC<{ selected: boolean; link: string; label: string; clear: () => void; repository?: Repository }> = ({ - selected, - link, - label, - clear, - repository, -}) => { +const HitEntry: FC<{ + selected: boolean; + link: string; + label: string; + clear: () => void; + repository?: Repository; + ariaId: string; +}> = ({ selected, link, label, clear, repository, ariaId }) => { return (
  • { }; }; -const OmniSearch: FC = () => { +const OmniSearch: FC = ({ shouldClear, ariaId }) => { const [t] = useTranslation("commons"); const { searchType, initialQuery } = useSearchParams(); const [query, setQuery] = useState(initialQuery); @@ -345,9 +352,17 @@ const OmniSearch: FC = () => { setIndex(-1); }, []); + useEffect(() => { + setQuery(shouldClear ? "" : initialQuery); + }, [shouldClear, initialQuery]); + const openHelp = () => setShowHelp(true); const closeHelp = () => setShowHelp(false); - const clearQuery = useCallback(() => setQuery(""), []); + const clearQuery = useCallback(() => { + if (shouldClear) { + setQuery(""); + } + }, [shouldClear]); const hits = data?._embedded?.hits || []; const searchTypes = useSearchTypes({ @@ -375,6 +390,7 @@ const OmniSearch: FC = () => { link={`/search/${searchTypes[0]}/?q=${encodeURIComponent(query)}&namespace=${context.namespace}&name=${ context.name }`} + ariaId={ariaId} /> ); } @@ -386,6 +402,7 @@ const OmniSearch: FC = () => { clear={clearQuery} label={t("search.quickSearch.searchNamespace")} link={`/search/repository/?q=${encodeURIComponent(query)}&namespace=${context.namespace}`} + ariaId={ariaId} /> ); } @@ -396,6 +413,7 @@ const OmniSearch: FC = () => { clear={clearQuery} label={t("search.quickSearch.searchEverywhere")} link={`/search/repository/?q=${encodeURIComponent(query)}`} + ariaId={ariaId} /> ); const length = newEntries.length; @@ -408,11 +426,12 @@ const OmniSearch: FC = () => { label={id(hit)} link={`/repo/${id(hit)}`} repository={hit._embedded?.repository} + ariaId={ariaId} /> ); }); return newEntries; - }, [clearQuery, context.name, context.namespace, hits, id, index, query, searchTypes, t]); + }, [ariaId, clearQuery, context.name, context.namespace, hits, id, index, query, searchTypes, t]); const defaultLink = `/search/${searchType}/?q=${encodeURIComponent(query)}`; const { onKeyDown } = useKeyBoardNavigation(entries, clearQuery, hideResults, index, setIndex, defaultLink); @@ -428,7 +447,7 @@ const OmniSearch: FC = () => {
    setQuery(e.target.value)} @@ -439,8 +458,8 @@ const OmniSearch: FC = () => { data-omnisearch="true" aria-expanded={query.length > 2} aria-label={t("search.ariaLabel")} - aria-owns="omni-search-results" - aria-activedescendant={index >= 0 ? "omni-search-selected-option" : undefined} + aria-owns={`omni-search-results-${ariaId}`} + aria-activedescendant={index >= 0 ? `omni-search-selected-option-${ariaId}` : undefined} ref={searchInputRef} {...handlers} /> @@ -456,7 +475,7 @@ const OmniSearch: FC = () => { ) : null} - {!error && data ? : null} + {!error && data ? : null}
    @@ -464,11 +483,11 @@ const OmniSearch: FC = () => { ); }; -const OmniSearchGuard: FC = ({ links }) => { +const OmniSearchGuard: FC = ({ links, shouldClear, ariaId }) => { if (!links.search) { return null; } - return ; + return ; }; export default OmniSearchGuard; diff --git a/scm-ui/ui-webapp/src/search/Search.tsx b/scm-ui/ui-webapp/src/search/Search.tsx index 90c65d57e7..e9a1ea4731 100644 --- a/scm-ui/ui-webapp/src/search/Search.tsx +++ b/scm-ui/ui-webapp/src/search/Search.tsx @@ -35,19 +35,53 @@ import { urls, } from "@scm-manager/ui-components"; import { Link, useLocation, useParams } from "react-router-dom"; -import { useNamespaceAndNameContext, useSearch, useSearchCounts, useSearchTypes } from "@scm-manager/ui-api"; +import { useIndex, useNamespaceAndNameContext, useSearch, useSearchCounts, useSearchTypes } from "@scm-manager/ui-api"; import Results from "./Results"; import { Trans, useTranslation } from "react-i18next"; import SearchErrorNotification from "./SearchErrorNotification"; import SyntaxModal from "./SyntaxModal"; import type { TFunction } from "i18next"; import styled from "styled-components"; +import OmniSearch from "../containers/OmniSearch"; +import { Links } from "@scm-manager/ui-types"; const DisabledNavLink = styled.div` opacity: 0.4; cursor: not-allowed; `; +const OmniSearchWrapper = styled.div` + .omni-search-bar { + width: 100% !important; + font-size: 1rem !important; + } + + .icon { + font-size: 1rem !important; + } + + .navbar-item { + padding: 0 !important; + } + + .dropdown { + width: 100% !important; + } + + .dropdown-trigger { + width: 100% !important; + } + + .control { + width: 100% !important; + } + + .dropdown-menu { + width: 100% !important; + max-width: none !important; + } +`; + type PathParams = { type: string; page: string; @@ -103,27 +137,29 @@ export const orderTypes = (t: TFunction) => (a: string, b: string) => { type Props = { selectedType: string; query: string; + links: Links; }; const SyntaxHelpLink: FC = ({ children }) => {children}; -const SearchSubTitle: FC = ({ selectedType, query }) => { +const SearchSubTitle: FC = ({ selectedType, query, links }) => { const [t] = useTranslation("commons"); const context = useNamespaceAndNameContext(); return ( <> {context.namespace ? t("search.subtitleWithContext", { - query, type: t(`plugins:search.types.${selectedType}.subtitle`, selectedType), context: `${context.namespace}${context.name ? `/${context.name}` : ""}`, }) : t("search.subtitle", { - query, type: t(`plugins:search.types.${selectedType}.subtitle`, selectedType), })}
    ]} /> + + + ); }; @@ -134,6 +170,7 @@ const InvalidSearch: FC = () => { }; const Search: FC = () => { + const { data: index } = useIndex(); const [t] = useTranslation(["commons", "plugins"]); const [showHelp, setShowHelp] = useState(false); const { query, selectedType, page, namespace, name } = usePageParams(); @@ -179,7 +216,7 @@ const Search: FC = () => { return ( } + subtitle={} loading={isLoading} > {showHelp ? setShowHelp(false)} /> : null}