mirror of
https://github.com/datahub-project/datahub.git
synced 2025-12-25 08:58:26 +00:00
feat(homePageRedesign): add header (#13904)
This commit is contained in:
parent
73cb3621e0
commit
31ee414008
@ -95,6 +95,10 @@ const meta = {
|
||||
'Determine whether the dropdown menu and the select input are the same width.' +
|
||||
'Default set min-width same as input. Will ignore when value less than select width.',
|
||||
},
|
||||
clickOutsideWidth: {
|
||||
description:
|
||||
'Customize width of the wrapper to handle outside clicks. This wrapper is fit-content by default',
|
||||
},
|
||||
},
|
||||
|
||||
// Define defaults
|
||||
|
||||
@ -19,6 +19,7 @@ export default function AutoComplete({
|
||||
onChange,
|
||||
onClear,
|
||||
value,
|
||||
clickOutsideWidth,
|
||||
...props
|
||||
}: React.PropsWithChildren<AutoCompleteProps>) {
|
||||
const { open } = props;
|
||||
@ -60,10 +61,18 @@ export default function AutoComplete({
|
||||
}
|
||||
};
|
||||
|
||||
// Automatically close the dropdown on resize to avoid the dropdown's misalignment
|
||||
useEffect(() => {
|
||||
const onResize = () => setInternalOpen(false);
|
||||
window.addEventListener('resize', onResize, true);
|
||||
return () => window.removeEventListener('resize', onResize);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ClickOutside
|
||||
ignoreSelector={AUTOCOMPLETE_WRAPPER_CLASS_CSS_SELECTOR}
|
||||
onClickOutside={() => setInternalOpen(false)}
|
||||
width={clickOutsideWidth}
|
||||
>
|
||||
<AntdAutoComplete
|
||||
open={internalOpen}
|
||||
|
||||
@ -35,4 +35,6 @@ export interface AutoCompleteProps {
|
||||
style?: React.CSSProperties;
|
||||
dropdownStyle?: React.CSSProperties;
|
||||
dropdownMatchSelectWidth?: boolean | number;
|
||||
|
||||
clickOutsideWidth?: string;
|
||||
}
|
||||
|
||||
@ -27,11 +27,19 @@ const meta = {
|
||||
description: 'Optional CSS-selector to ignore handling of clicks as outside clicks',
|
||||
},
|
||||
outsideSelector: {
|
||||
description: 'Optional CSS-selector to cosider clicked element as outside click',
|
||||
description: 'Optional CSS-selector to consider clicked element as outside click',
|
||||
},
|
||||
ignoreWrapper: {
|
||||
description: 'Enable to ignore clicking outside of wrapper',
|
||||
},
|
||||
width: {
|
||||
description: 'Customize the width of the wrapper',
|
||||
table: {
|
||||
defaultValue: {
|
||||
summary: 'fit-content',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Define defaults
|
||||
|
||||
@ -7,11 +7,16 @@ import useClickOutside from '@components/components/Utils/ClickOutside/useClickO
|
||||
export default function ClickOutside({
|
||||
children,
|
||||
onClickOutside,
|
||||
width,
|
||||
...options
|
||||
}: React.PropsWithChildren<ClickOutsideProps>) {
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useClickOutside(onClickOutside, { ...options, wrappers: [wrapperRef] });
|
||||
|
||||
return <Wrapper ref={wrapperRef}>{children}</Wrapper>;
|
||||
return (
|
||||
<Wrapper ref={wrapperRef} $width={width}>
|
||||
{children}
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Wrapper = styled.div({
|
||||
width: 'fit-content',
|
||||
});
|
||||
export const Wrapper = styled.div<{ $width?: string }>(({ $width }) => ({
|
||||
width: $width || 'fit-content',
|
||||
}));
|
||||
|
||||
@ -9,4 +9,5 @@ export interface ClickOutsideOptions {
|
||||
|
||||
export interface ClickOutsideProps extends Omit<ClickOutsideOptions, 'wrappers'> {
|
||||
onClickOutside: ClickOutsideCallback;
|
||||
width?: string;
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import React from 'react';
|
||||
|
||||
import PersonalizationLoadingModal from '@app/homeV2/persona/PersonalizationLoadingModal';
|
||||
import HomePageContent from '@app/homepageV2/HomePageContent';
|
||||
import HomePageHeader from '@app/homepageV2/HomePageHeader';
|
||||
import Header from '@app/homepageV2/header/Header';
|
||||
import { PageWrapper } from '@app/homepageV2/styledComponents';
|
||||
import { SearchablePage } from '@app/searchV2/SearchablePage';
|
||||
|
||||
@ -11,7 +11,7 @@ export const HomePage = () => {
|
||||
<>
|
||||
<SearchablePage hideSearchBar>
|
||||
<PageWrapper>
|
||||
<HomePageHeader />
|
||||
<Header />
|
||||
<HomePageContent />
|
||||
</PageWrapper>
|
||||
</SearchablePage>
|
||||
|
||||
35
datahub-web-react/src/app/homepageV2/header/Header.tsx
Normal file
35
datahub-web-react/src/app/homepageV2/header/Header.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import { colors } from '@components';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import GreetingText from '@app/homepageV2/header/components/GreetingText';
|
||||
import SearchBar from '@app/homepageV2/header/components/SearchBar';
|
||||
|
||||
export const HeaderWrapper = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 27px 0 24px 0;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(180deg, #f8fcff 0%, #fafafb 100%);
|
||||
border: 1px solid ${colors.gray[100]};
|
||||
border-radius: 12px 12px 0 0;
|
||||
`;
|
||||
|
||||
const CenteredContainer = styled.div`
|
||||
max-width: 1016px;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const Header = () => {
|
||||
return (
|
||||
<HeaderWrapper>
|
||||
<CenteredContainer>
|
||||
<GreetingText />
|
||||
<SearchBar />
|
||||
</CenteredContainer>
|
||||
</HeaderWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
@ -0,0 +1,32 @@
|
||||
import { PageTitle } from '@components';
|
||||
import React, { useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { useUserContext } from '@app/context/useUserContext';
|
||||
import { getGreetingText } from '@app/homeV2/reference/header/getGreetingText';
|
||||
import { useEntityRegistryV2 } from '@app/useEntityRegistry';
|
||||
|
||||
import { EntityType } from '@types';
|
||||
|
||||
const Container = styled.div`
|
||||
// FYI: horizontal 8px padding to align with the search bar's input as it has a wrapper on focus.
|
||||
// bottom 8px to add gap between the greeting text and the search bar. Flex gap breaks the views popover
|
||||
padding: 0 8px 8px 8px;
|
||||
`;
|
||||
|
||||
export default function GreetingText() {
|
||||
const greetingText = getGreetingText();
|
||||
const { user } = useUserContext();
|
||||
const entityRegistry = useEntityRegistryV2();
|
||||
|
||||
const finalText = useMemo(() => {
|
||||
if (!user) return `${greetingText}!`;
|
||||
return `${greetingText}, ${entityRegistry.getDisplayName(EntityType.CorpUser, user)}!`;
|
||||
}, [greetingText, user, entityRegistry]);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<PageTitle title={finalText} />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
import { Button, Icon } from '@components';
|
||||
import React from 'react';
|
||||
import styled, { useTheme } from 'styled-components';
|
||||
|
||||
import { SearchBarV2 } from '@app/searchV2/searchBarV2/SearchBarV2';
|
||||
import useGoToSearchPage from '@app/searchV2/useGoToSearchPage';
|
||||
import useSearchViewAll from '@app/searchV2/useSearchViewAll';
|
||||
import { useEntityRegistryV2 } from '@app/useEntityRegistry';
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const ViewAllContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
`;
|
||||
|
||||
const StyledButton = styled(Button)`
|
||||
padding: 0 8px;
|
||||
`;
|
||||
|
||||
export default function SearchBar() {
|
||||
const entityRegistry = useEntityRegistryV2();
|
||||
const searchViewAll = useSearchViewAll();
|
||||
const search = useGoToSearchPage(null);
|
||||
const themeConfig = useTheme();
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<SearchBarV2
|
||||
placeholderText={themeConfig.content.search.searchbarMessage}
|
||||
onSearch={search}
|
||||
entityRegistry={entityRegistry}
|
||||
width="100%"
|
||||
fixAutoComplete
|
||||
viewsEnabled
|
||||
isShowNavBarRedesign
|
||||
showViewAllResults
|
||||
combineSiblings
|
||||
showCommandK
|
||||
/>
|
||||
<ViewAllContainer>
|
||||
<StyledButton variant="text" color="gray" size="sm" onClick={searchViewAll}>
|
||||
Discover <Icon icon="ArrowRight" source="phosphor" size="sm" />
|
||||
</StyledButton>
|
||||
</ViewAllContainer>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@ -152,7 +152,7 @@ export interface SearchBarProps {
|
||||
isLoading?: boolean;
|
||||
initialQuery?: string;
|
||||
placeholderText: string;
|
||||
suggestions: Array<AutoCompleteResultForEntity>;
|
||||
suggestions?: Array<AutoCompleteResultForEntity>;
|
||||
onSearch: (query: string, filters?: FacetFilterInput[]) => void;
|
||||
onQueryChange?: (query: string) => void;
|
||||
style?: React.CSSProperties;
|
||||
@ -284,7 +284,7 @@ export const SearchBar = ({
|
||||
}, [effectiveQuery, showViewAllResults]);
|
||||
|
||||
const autoCompleteEntityOptions = useMemo(() => {
|
||||
return suggestions.map((suggestion: AutoCompleteResultForEntity) => {
|
||||
return (suggestions ?? []).map((suggestion: AutoCompleteResultForEntity) => {
|
||||
const combinedSuggestion = combineSiblingsInAutoComplete(suggestion, {
|
||||
combineSiblings: finalCombineSiblings,
|
||||
});
|
||||
|
||||
@ -129,6 +129,7 @@ type Props = {
|
||||
onSearch: (query: string) => void;
|
||||
onQueryChange: (query: string) => void;
|
||||
entityRegistry: EntityRegistry;
|
||||
hideSearchBar?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -141,6 +142,7 @@ export const SearchHeader = ({
|
||||
onSearch,
|
||||
onQueryChange,
|
||||
entityRegistry,
|
||||
hideSearchBar,
|
||||
}: Props) => {
|
||||
const [, setIsSearchBarFocused] = useState(false);
|
||||
const appConfig = useAppConfig();
|
||||
@ -164,34 +166,36 @@ export const SearchHeader = ({
|
||||
<NavBarToggler />
|
||||
</NavBarTogglerWrapper>
|
||||
)}
|
||||
<SearchBarContainer $isShowNavBarRedesign={isShowNavBarRedesign}>
|
||||
<FinalSearchBar
|
||||
isLoading={isUserInitializing || !appConfig.loaded}
|
||||
id={V2_SEARCH_BAR_ID}
|
||||
style={styles.searchBoxContainer}
|
||||
autoCompleteStyle={styles.searchBox}
|
||||
inputStyle={styles.input}
|
||||
initialQuery={initialQuery}
|
||||
placeholderText={placeholderText}
|
||||
suggestions={suggestions}
|
||||
onSearch={onSearch}
|
||||
onQueryChange={onQueryChange}
|
||||
entityRegistry={entityRegistry}
|
||||
setIsSearchBarFocused={setIsSearchBarFocused}
|
||||
viewsEnabled={viewsEnabled}
|
||||
isShowNavBarRedesign={isShowNavBarRedesign}
|
||||
combineSiblings
|
||||
fixAutoComplete
|
||||
showQuickFilters
|
||||
showViewAllResults
|
||||
showCommandK
|
||||
/>
|
||||
{isShowNavBarRedesign && (
|
||||
<StyledButton type="link" onClick={searchViewAll}>
|
||||
View all <ArrowRight />
|
||||
</StyledButton>
|
||||
)}
|
||||
</SearchBarContainer>
|
||||
{!hideSearchBar && (
|
||||
<SearchBarContainer $isShowNavBarRedesign={isShowNavBarRedesign}>
|
||||
<FinalSearchBar
|
||||
isLoading={isUserInitializing || !appConfig.loaded}
|
||||
id={V2_SEARCH_BAR_ID}
|
||||
style={styles.searchBoxContainer}
|
||||
autoCompleteStyle={styles.searchBox}
|
||||
inputStyle={styles.input}
|
||||
initialQuery={initialQuery}
|
||||
placeholderText={placeholderText}
|
||||
suggestions={suggestions}
|
||||
onSearch={onSearch}
|
||||
onQueryChange={onQueryChange}
|
||||
entityRegistry={entityRegistry}
|
||||
setIsSearchBarFocused={setIsSearchBarFocused}
|
||||
viewsEnabled={viewsEnabled}
|
||||
isShowNavBarRedesign={isShowNavBarRedesign}
|
||||
combineSiblings
|
||||
fixAutoComplete
|
||||
showQuickFilters
|
||||
showViewAllResults
|
||||
showCommandK
|
||||
/>
|
||||
{isShowNavBarRedesign && (
|
||||
<StyledButton type="link" onClick={searchViewAll}>
|
||||
View all <ArrowRight />
|
||||
</StyledButton>
|
||||
)}
|
||||
</SearchBarContainer>
|
||||
)}
|
||||
</Header>
|
||||
</Wrapper>
|
||||
</>
|
||||
|
||||
@ -1,18 +1,15 @@
|
||||
import { debounce } from 'lodash';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useHistory } from 'react-router';
|
||||
import styled, { useTheme } from 'styled-components';
|
||||
|
||||
import analytics, { EventType } from '@app/analytics';
|
||||
import { useUserContext } from '@app/context/useUserContext';
|
||||
import { REDESIGN_COLORS } from '@app/entityV2/shared/constants';
|
||||
import { NavSidebar } from '@app/homeV2/layout/NavSidebar';
|
||||
import { NavSidebar as NavSidebarRedesign } from '@app/homeV2/layout/navBarRedesign/NavSidebar';
|
||||
import { useSelectedSortOption } from '@app/search/context/SearchContext';
|
||||
import { SearchHeader } from '@app/searchV2/SearchHeader';
|
||||
import useGoToSearchPage from '@app/searchV2/useGoToSearchPage';
|
||||
import useQueryAndFiltersFromLocation from '@app/searchV2/useQueryAndFiltersFromLocation';
|
||||
import { getAutoCompleteInputFromQuickFilter } from '@app/searchV2/utils/filterUtils';
|
||||
import { navigateToSearchUrl } from '@app/searchV2/utils/navigateToSearchUrl';
|
||||
import { useAppConfig } from '@app/useAppConfig';
|
||||
import { useEntityRegistry } from '@app/useEntityRegistry';
|
||||
import { useShowNavBarRedesign } from '@app/useShowNavBarRedesign';
|
||||
@ -23,7 +20,6 @@ import {
|
||||
GetAutoCompleteMultipleResultsQuery,
|
||||
useGetAutoCompleteMultipleResultsLazyQuery,
|
||||
} from '@graphql/search.generated';
|
||||
import { FacetFilterInput } from '@types';
|
||||
|
||||
const Body = styled.div`
|
||||
display: flex;
|
||||
@ -72,11 +68,9 @@ type Props = React.PropsWithChildren<{
|
||||
export const SearchablePage = ({ children, hideSearchBar }: Props) => {
|
||||
const appConfig = useAppConfig();
|
||||
const showSearchBarAutocompleteRedesign = appConfig.config.featureFlags?.showSearchBarAutocompleteRedesign;
|
||||
const { filters, query: currentQuery } = useQueryAndFiltersFromLocation();
|
||||
const selectedSortOption = useSelectedSortOption();
|
||||
const { query: currentQuery } = useQueryAndFiltersFromLocation();
|
||||
const isShowNavBarRedesign = useShowNavBarRedesign();
|
||||
|
||||
const history = useHistory();
|
||||
const entityRegistry = useEntityRegistry();
|
||||
const themeConfig = useTheme();
|
||||
const { selectedQuickFilter } = useQuickFiltersContext();
|
||||
@ -92,29 +86,7 @@ export const SearchablePage = ({ children, hideSearchBar }: Props) => {
|
||||
}
|
||||
}, [suggestionsData]);
|
||||
|
||||
const search = (query: string, newFilters?: FacetFilterInput[]) => {
|
||||
analytics.event({
|
||||
type: EventType.SearchEvent,
|
||||
query,
|
||||
pageNumber: 1,
|
||||
originPath: window.location.pathname,
|
||||
selectedQuickFilterTypes: selectedQuickFilter ? [selectedQuickFilter.field] : undefined,
|
||||
selectedQuickFilterValues: selectedQuickFilter ? [selectedQuickFilter.value] : undefined,
|
||||
});
|
||||
|
||||
let newAppliedFilters: FacetFilterInput[] | undefined = filters;
|
||||
|
||||
if (newFilters && newFilters?.length > 0) {
|
||||
newAppliedFilters = newFilters;
|
||||
}
|
||||
|
||||
navigateToSearchUrl({
|
||||
query,
|
||||
filters: newAppliedFilters,
|
||||
history,
|
||||
selectedSortOption,
|
||||
});
|
||||
};
|
||||
const search = useGoToSearchPage(selectedQuickFilter);
|
||||
|
||||
const autoComplete = debounce((query: string) => {
|
||||
if (query && query.trim() !== '') {
|
||||
@ -148,21 +120,20 @@ export const SearchablePage = ({ children, hideSearchBar }: Props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{!hideSearchBar && (
|
||||
<SearchHeader
|
||||
initialQuery={currentQuery as string}
|
||||
placeholderText={themeConfig.content.search.searchbarMessage}
|
||||
suggestions={
|
||||
(newSuggestionData &&
|
||||
newSuggestionData?.autoCompleteForMultiple &&
|
||||
newSuggestionData.autoCompleteForMultiple.suggestions) ||
|
||||
[]
|
||||
}
|
||||
onSearch={search}
|
||||
onQueryChange={autoComplete}
|
||||
entityRegistry={entityRegistry}
|
||||
/>
|
||||
)}
|
||||
<SearchHeader
|
||||
initialQuery={currentQuery as string}
|
||||
placeholderText={themeConfig.content.search.searchbarMessage}
|
||||
suggestions={
|
||||
(newSuggestionData &&
|
||||
newSuggestionData?.autoCompleteForMultiple &&
|
||||
newSuggestionData.autoCompleteForMultiple.suggestions) ||
|
||||
[]
|
||||
}
|
||||
onSearch={search}
|
||||
onQueryChange={autoComplete}
|
||||
entityRegistry={entityRegistry}
|
||||
hideSearchBar={hideSearchBar}
|
||||
/>
|
||||
<BodyBackground $isShowNavBarRedesign={isShowNavBarRedesign} />
|
||||
<Body>
|
||||
<Navigation $isShowNavBarRedesign={isShowNavBarRedesign}>
|
||||
|
||||
@ -0,0 +1,107 @@
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
|
||||
import { useSelectedSortOption } from '@app/search/context/SearchContext';
|
||||
import useGoToSearchPage from '@app/searchV2/useGoToSearchPage';
|
||||
import useQueryAndFiltersFromLocation from '@app/searchV2/useQueryAndFiltersFromLocation';
|
||||
import { navigateToSearchUrl } from '@app/searchV2/utils/navigateToSearchUrl';
|
||||
|
||||
vi.mock('@app/search/context/SearchContext', () => ({
|
||||
useSelectedSortOption: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@app/searchV2/useQueryAndFiltersFromLocation', () => ({
|
||||
default: vi.fn(() => ({ filters: [] })),
|
||||
}));
|
||||
|
||||
vi.mock('@app/searchV2/utils/navigateToSearchUrl', () => ({
|
||||
navigateToSearchUrl: vi.fn(),
|
||||
}));
|
||||
|
||||
describe('useGoToSearchPage Hook', () => {
|
||||
const mockQuickFilter = {
|
||||
field: 'type',
|
||||
value: 'dataset',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return a function that navigates to search url', () => {
|
||||
const mockSortOption = 'relevance';
|
||||
vi.mocked(useSelectedSortOption).mockReturnValue(mockSortOption);
|
||||
|
||||
const { result } = renderHook(() => useGoToSearchPage(mockQuickFilter));
|
||||
|
||||
const query = 'testQuery';
|
||||
const filters = [{ field: 'origin', values: ['urn:li:dataPlatform:bigquery'] }];
|
||||
|
||||
result.current(query, filters);
|
||||
|
||||
expect(vi.mocked(navigateToSearchUrl)).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
query,
|
||||
filters,
|
||||
selectedSortOption: mockSortOption,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should use existing filters if no newFilters provided', () => {
|
||||
const existingFilters = [{ field: 'platform', values: ['urn:li:dataPlatform:mysql'] }];
|
||||
vi.mocked(useQueryAndFiltersFromLocation).mockReturnValue({ filters: existingFilters, query: '' });
|
||||
|
||||
vi.mocked(useSelectedSortOption).mockReturnValue(undefined);
|
||||
|
||||
const { result } = renderHook(() => useGoToSearchPage(null));
|
||||
|
||||
const query = 'hello';
|
||||
|
||||
result.current(query);
|
||||
|
||||
expect(vi.mocked(navigateToSearchUrl)).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
query,
|
||||
filters: existingFilters,
|
||||
selectedSortOption: undefined,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should always pass newFilters if newFilters is provided', () => {
|
||||
vi.mocked(useQueryAndFiltersFromLocation).mockReturnValue({ filters: [], query: '' });
|
||||
vi.mocked(useSelectedSortOption).mockReturnValue(undefined);
|
||||
const newFilters = [{ field: 'platform', values: ['urn:li:dataPlatform:hive'] }];
|
||||
|
||||
const { result } = renderHook(() => useGoToSearchPage(null));
|
||||
|
||||
const query = 'featureFlagTest';
|
||||
|
||||
result.current(query, newFilters);
|
||||
|
||||
expect(vi.mocked(navigateToSearchUrl)).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
query,
|
||||
filters: newFilters,
|
||||
selectedSortOption: undefined,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not override filters if newFilters is not provided', () => {
|
||||
const existingFilters = [{ field: 'platform', values: ['urn:li:dataPlatform:hive'] }];
|
||||
vi.mocked(useQueryAndFiltersFromLocation).mockReturnValue({ filters: existingFilters, query: '' });
|
||||
|
||||
const { result } = renderHook(() => useGoToSearchPage(null));
|
||||
|
||||
const query = 'noOverride';
|
||||
|
||||
result.current(query);
|
||||
|
||||
expect(vi.mocked(navigateToSearchUrl)).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
filters: existingFilters,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -27,6 +27,14 @@ import { SearchBarApi } from '@src/types.generated';
|
||||
|
||||
const Wrapper = styled.div``;
|
||||
|
||||
const StyledAutocomplete = styled(AutoComplete)`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
interface SearchBarV2Props extends SearchBarProps {
|
||||
width?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the search bar appearing in the default header view.
|
||||
*/
|
||||
@ -44,7 +52,8 @@ export const SearchBarV2 = ({
|
||||
onBlur,
|
||||
showViewAllResults = false,
|
||||
isShowNavBarRedesign,
|
||||
}: SearchBarProps) => {
|
||||
width,
|
||||
}: SearchBarV2Props) => {
|
||||
const appConfig = useAppConfig();
|
||||
const showAutoCompleteResults = appConfig?.config?.featureFlags?.showAutoCompleteResults;
|
||||
const isShowSeparateSiblingsEnabled = useIsShowSeparateSiblingsEnabled();
|
||||
@ -139,7 +148,7 @@ export const SearchBarV2 = ({
|
||||
|
||||
return (
|
||||
<Wrapper id={id} className={SEARCH_BAR_CLASS_NAME}>
|
||||
<AutoComplete
|
||||
<StyledAutocomplete
|
||||
dataTestId="search-bar"
|
||||
defaultActiveFirstOption={false}
|
||||
options={options}
|
||||
@ -162,6 +171,7 @@ export const SearchBarV2 = ({
|
||||
onClearFilters={onClearFiltersAndSelectedViewHandler}
|
||||
/>
|
||||
}
|
||||
dropdownMatchSelectWidth
|
||||
onSelect={onSelectHandler}
|
||||
defaultValue={initialQuery || undefined}
|
||||
value={searchQuery}
|
||||
@ -186,6 +196,7 @@ export const SearchBarV2 = ({
|
||||
onDropdownVisibleChange={onDropdownVisibilityChangeHandler}
|
||||
open={isDropdownVisible}
|
||||
dropdownContentHeight={480}
|
||||
clickOutsideWidth={width === '100%' ? '100%' : undefined}
|
||||
>
|
||||
<SearchBarInput
|
||||
placeholder={placeholderText}
|
||||
@ -199,8 +210,9 @@ export const SearchBarV2 = ({
|
||||
showCommandK={showCommandK}
|
||||
isDropdownOpened={isDropdownVisible}
|
||||
viewsEnabled={viewsEnabled}
|
||||
width={width}
|
||||
/>
|
||||
</AutoComplete>
|
||||
</StyledAutocomplete>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@ -38,6 +38,8 @@ const ViewSelectContainer = styled.div``;
|
||||
|
||||
export const Wrapper = styled.div<{ $open?: boolean; $isShowNavBarRedesign?: boolean }>`
|
||||
background: transparent;
|
||||
width: 100%;
|
||||
min-width: 500px;
|
||||
|
||||
${(props) =>
|
||||
props.$isShowNavBarRedesign &&
|
||||
@ -77,6 +79,7 @@ interface Props {
|
||||
placeholder?: string;
|
||||
showCommandK?: boolean;
|
||||
viewsEnabled?: boolean;
|
||||
width?: string;
|
||||
}
|
||||
|
||||
const SearchBarInput = forwardRef<InputRef, Props>(
|
||||
@ -93,6 +96,7 @@ const SearchBarInput = forwardRef<InputRef, Props>(
|
||||
placeholder,
|
||||
showCommandK,
|
||||
viewsEnabled,
|
||||
width,
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
@ -158,7 +162,7 @@ const SearchBarInput = forwardRef<InputRef, Props>(
|
||||
)}
|
||||
</SuffixWrapper>
|
||||
}
|
||||
width={isShowNavBarRedesign ? '664px' : '620px'}
|
||||
width={width ?? (isShowNavBarRedesign ? '664px' : '620px')}
|
||||
height="44px"
|
||||
$isShowNavBarRedesign={isShowNavBarRedesign}
|
||||
/>
|
||||
|
||||
@ -2,10 +2,14 @@ import { AutocompleteDropdownAlign } from '@src/alchemy-components/components/Au
|
||||
|
||||
// Adjusted aligning to show dropdown in the correct place
|
||||
export const AUTOCOMPLETE_DROPDOWN_ALIGN_WITH_NEW_NAV_BAR: AutocompleteDropdownAlign = {
|
||||
// bottom-center of input (search) and top-center of dropdown
|
||||
points: ['bl', 'tl'],
|
||||
// additional offset
|
||||
offset: [0, 6],
|
||||
// top-left of dropdown and bottom-left of input (search)
|
||||
points: ['tl', 'bl'],
|
||||
overflow: {
|
||||
adjustX: 1,
|
||||
adjustY: 1,
|
||||
},
|
||||
offset: [0, -6],
|
||||
targetOffset: [0, 0],
|
||||
};
|
||||
export const AUTOCOMPLETE_DROPDOWN_ALIGN: AutocompleteDropdownAlign = {
|
||||
// bottom-center of input (search) and top-center of dropdown
|
||||
|
||||
43
datahub-web-react/src/app/searchV2/useGoToSearchPage.ts
Normal file
43
datahub-web-react/src/app/searchV2/useGoToSearchPage.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useHistory } from 'react-router';
|
||||
|
||||
import analytics, { EventType } from '@app/analytics';
|
||||
import { useSelectedSortOption } from '@app/search/context/SearchContext';
|
||||
import useQueryAndFiltersFromLocation from '@app/searchV2/useQueryAndFiltersFromLocation';
|
||||
import { navigateToSearchUrl } from '@app/searchV2/utils/navigateToSearchUrl';
|
||||
|
||||
import { FacetFilterInput, QuickFilter } from '@types';
|
||||
|
||||
export default function useGoToSearchPage(quickFilter: QuickFilter | null) {
|
||||
const history = useHistory();
|
||||
const selectedSortOption = useSelectedSortOption();
|
||||
|
||||
const { filters } = useQueryAndFiltersFromLocation();
|
||||
|
||||
return useCallback(
|
||||
(query: string, newFilters?: FacetFilterInput[]) => {
|
||||
analytics.event({
|
||||
type: EventType.SearchEvent,
|
||||
query,
|
||||
pageNumber: 1,
|
||||
originPath: window.location.pathname,
|
||||
selectedQuickFilterTypes: quickFilter ? [quickFilter.field] : undefined,
|
||||
selectedQuickFilterValues: quickFilter ? [quickFilter.value] : undefined,
|
||||
});
|
||||
|
||||
let newAppliedFilters: FacetFilterInput[] | undefined = filters;
|
||||
|
||||
if (newFilters && newFilters?.length > 0) {
|
||||
newAppliedFilters = newFilters;
|
||||
}
|
||||
|
||||
navigateToSearchUrl({
|
||||
query,
|
||||
filters: newAppliedFilters,
|
||||
history,
|
||||
selectedSortOption,
|
||||
});
|
||||
},
|
||||
[filters, history, quickFilter, selectedSortOption],
|
||||
);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user