1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

feat: add history for search (#5651)

This is simple refactor. Just moving history part out of suggestions
component.
This commit is contained in:
Jaanus Sellin 2023-12-15 13:22:19 +02:00 committed by GitHub
parent 203d6ac848
commit 848415c5ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 131 additions and 42 deletions

View File

@ -51,3 +51,18 @@ test('should update saved query without local storage', async () => {
expect(screen.getByText('newquery')).toBeInTheDocument(); // new saved query updated expect(screen.getByText('newquery')).toBeInTheDocument(); // new saved query updated
}); });
test('should still render history if no search context', async () => {
const { setValue } = createLocalStorage('Search:localStorageId:v1', {});
setValue({
query: 'oldquery',
});
render(<Search onChange={() => {}} id='localStorageId' />);
const input = screen.getByTestId(SEARCH_INPUT);
input.focus();
await screen.findByText('oldquery');
});

View File

@ -1,6 +1,13 @@
import React, { useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import { useAsyncDebounce } from 'react-table'; import { useAsyncDebounce } from 'react-table';
import { Box, IconButton, InputBase, styled, Tooltip } from '@mui/material'; import {
Box,
IconButton,
InputBase,
Paper,
styled,
Tooltip,
} from '@mui/material';
import { Close, Search as SearchIcon } from '@mui/icons-material'; import { Close, Search as SearchIcon } from '@mui/icons-material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { SearchSuggestions } from './SearchSuggestions/SearchSuggestions'; import { SearchSuggestions } from './SearchSuggestions/SearchSuggestions';
@ -10,6 +17,7 @@ import { SEARCH_INPUT } from 'utils/testIds';
import { useOnClickOutside } from 'hooks/useOnClickOutside'; import { useOnClickOutside } from 'hooks/useOnClickOutside';
import { useSavedQuery } from './useSavedQuery'; import { useSavedQuery } from './useSavedQuery';
import { useOnBlur } from 'hooks/useOnBlur'; import { useOnBlur } from 'hooks/useOnBlur';
import { SearchHistory } from './SearchSuggestions/SearchHistory';
interface ISearchProps { interface ISearchProps {
id?: string; id?: string;
@ -27,9 +35,26 @@ interface ISearchProps {
expandable?: boolean; expandable?: boolean;
} }
export const SearchPaper = styled(Paper)(({ theme }) => ({
position: 'absolute',
width: '100%',
left: 0,
top: '20px',
zIndex: 2,
padding: theme.spacing(4, 1.5, 1.5),
borderBottomLeftRadius: theme.spacing(1),
borderBottomRightRadius: theme.spacing(1),
boxShadow: '0px 8px 20px rgba(33, 33, 33, 0.15)',
fontSize: theme.fontSizes.smallBody,
color: theme.palette.text.secondary,
wordBreak: 'break-word',
}));
const StyledContainer = styled('div', { const StyledContainer = styled('div', {
shouldForwardProp: (prop) => prop !== 'active', shouldForwardProp: (prop) => prop !== 'active',
})<{ active: boolean | undefined }>(({ theme, active }) => ({ })<{
active: boolean | undefined;
}>(({ theme, active }) => ({
display: 'flex', display: 'flex',
flexGrow: 1, flexGrow: 1,
alignItems: 'center', alignItems: 'center',
@ -93,7 +118,7 @@ export const Search = ({
const { savedQuery, setSavedQuery } = useSavedQuery(id); const { savedQuery, setSavedQuery } = useSavedQuery(id);
const [value, setValue] = useState(initialValue); const [value, setValue] = useState<string>(initialValue);
const debouncedOnChange = useAsyncDebounce(onChange, debounceTime); const debouncedOnChange = useAsyncDebounce(onChange, debounceTime);
const onSearchChange = (value: string) => { const onSearchChange = (value: string) => {
@ -103,7 +128,11 @@ export const Search = ({
}; };
const hotkey = useKeyboardShortcut( const hotkey = useKeyboardShortcut(
{ modifiers: ['ctrl'], key: 'k', preventDefault: true }, {
modifiers: ['ctrl'],
key: 'k',
preventDefault: true,
},
() => { () => {
if (document.activeElement === searchInputRef.current) { if (document.activeElement === searchInputRef.current) {
searchInputRef.current?.blur(); searchInputRef.current?.blur();
@ -122,6 +151,10 @@ export const Search = ({
useOnClickOutside([searchContainerRef], hideSuggestions); useOnClickOutside([searchContainerRef], hideSuggestions);
useOnBlur(searchContainerRef, hideSuggestions); useOnBlur(searchContainerRef, hideSuggestions);
useEffect(() => {
setValue(initialValue);
}, [initialValue]);
return ( return (
<StyledContainer <StyledContainer
ref={searchContainerRef} ref={searchContainerRef}
@ -189,6 +222,17 @@ export const Search = ({
getSearchContext={getSearchContext!} getSearchContext={getSearchContext!}
/> />
} }
elseShow={
showSuggestions &&
savedQuery && (
<SearchPaper className='dropdown-outline'>
<SearchHistory
onSuggestion={onSearchChange}
savedQuery={savedQuery}
/>
</SearchPaper>
)
}
/> />
</StyledContainer> </StyledContainer>
); );

View File

@ -0,0 +1,56 @@
import { FilterList, History } from '@mui/icons-material';
import { Box, Divider, Paper, styled } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { IGetSearchContextOutput } from 'hooks/useSearch';
import { VFC } from 'react';
import { StyledCode } from './SearchInstructions/SearchInstructions';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { onEnter } from './onEnter';
const StyledBox = styled(Box)(({ theme }) => ({
display: 'flex',
gap: theme.spacing(2),
}));
const StyledHistory = styled(History)(({ theme }) => ({
color: theme.palette.text.secondary,
}));
interface ISearchHistoryProps {
onSuggestion: (suggestion: string) => void;
savedQuery?: string;
}
export const SearchHistory: VFC<ISearchHistoryProps> = ({
onSuggestion,
savedQuery,
}) => {
const { trackEvent } = usePlausibleTracker();
const onSavedQuery = () => {
onSuggestion(savedQuery || '');
trackEvent('search-filter-suggestions', {
props: {
eventType: 'saved query',
},
});
};
return (
<ConditionallyRender
condition={Boolean(savedQuery)}
show={
<>
<StyledBox>
<StyledHistory />
<StyledCode
tabIndex={0}
onClick={onSavedQuery}
onKeyDown={onEnter(onSavedQuery)}
>
<span>{savedQuery}</span>
</StyledCode>
</StyledBox>
</>
}
/>
);
};

View File

@ -7,7 +7,7 @@ import {
getFilterValues, getFilterValues,
IGetSearchContextOutput, IGetSearchContextOutput,
} from 'hooks/useSearch'; } from 'hooks/useSearch';
import { VFC } from 'react'; import React, { VFC } from 'react';
import { SearchDescription } from './SearchDescription/SearchDescription'; import { SearchDescription } from './SearchDescription/SearchDescription';
import { import {
SearchInstructions, SearchInstructions,
@ -15,21 +15,8 @@ import {
} from './SearchInstructions/SearchInstructions'; } from './SearchInstructions/SearchInstructions';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { onEnter } from './onEnter'; import { onEnter } from './onEnter';
import { SearchHistory } from './SearchHistory';
const StyledPaper = styled(Paper)(({ theme }) => ({ import { SearchPaper } from '../Search';
position: 'absolute',
width: '100%',
left: 0,
top: '20px',
zIndex: 2,
padding: theme.spacing(4, 1.5, 1.5),
borderBottomLeftRadius: theme.spacing(1),
borderBottomRightRadius: theme.spacing(1),
boxShadow: '0px 8px 20px rgba(33, 33, 33, 0.15)',
fontSize: theme.fontSizes.smallBody,
color: theme.palette.text.secondary,
wordBreak: 'break-word',
}));
const StyledBox = styled(Box)(({ theme }) => ({ const StyledBox = styled(Box)(({ theme }) => ({
display: 'flex', display: 'flex',
@ -127,36 +114,21 @@ export const SearchSuggestions: VFC<SearchSuggestionsProps> = ({
}, },
}); });
}; };
const onSavedQuery = () => {
onSuggestion(savedQuery || '');
trackEvent('search-filter-suggestions', {
props: {
eventType: 'saved query',
},
});
};
return ( return (
<StyledPaper className='dropdown-outline'> <SearchPaper className='dropdown-outline'>
<ConditionallyRender <ConditionallyRender
condition={Boolean(savedQuery)} condition={Boolean(savedQuery)}
show={ show={
<> <>
<StyledBox> <SearchHistory
<StyledHistory /> onSuggestion={onSuggestion}
<StyledCode savedQuery={savedQuery}
tabIndex={0} />
onClick={onSavedQuery}
onKeyDown={onEnter(onSavedQuery)}
>
<span>{savedQuery}</span>
</StyledCode>
</StyledBox>
<StyledDivider /> <StyledDivider />
</> </>
} }
/> />
<StyledBox> <StyledBox>
<StyledFilterList /> <StyledFilterList />
<Box> <Box>
@ -198,6 +170,6 @@ export const SearchSuggestions: VFC<SearchSuggestionsProps> = ({
<span>{suggestedTextSearch}</span> <span>{suggestedTextSearch}</span>
</StyledCode> </StyledCode>
</Box> </Box>
</StyledPaper> </SearchPaper>
); );
}; };

View File

@ -282,6 +282,7 @@ const FeatureToggleListTableComponent: VFC = () => {
tableState.query || '' tableState.query || ''
} }
onChange={setSearchValue} onChange={setSearchValue}
id='globalFeatureToggles'
/> />
<PageHeader.Divider /> <PageHeader.Divider />
</> </>
@ -331,6 +332,7 @@ const FeatureToggleListTableComponent: VFC = () => {
<Search <Search
initialValue={tableState.query || ''} initialValue={tableState.query || ''}
onChange={setSearchValue} onChange={setSearchValue}
id='globalFeatureToggles'
/> />
} }
/> />