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

Merge branch 'main' into archive_table

This commit is contained in:
Tymoteusz Czech 2022-06-14 11:43:06 +02:00 committed by GitHub
commit b1d9437d99
8 changed files with 1 additions and 483 deletions

View File

@ -1,7 +1,7 @@
{ {
"name": "unleash-frontend", "name": "unleash-frontend",
"description": "unleash your features", "description": "unleash your features",
"version": "4.11.0-beta.2", "version": "4.13.0-beta.0",
"keywords": [ "keywords": [
"unleash", "unleash",
"feature toggle", "feature toggle",

View File

@ -1,41 +0,0 @@
import { IGetSearchContextOutput } from 'hooks/useSearch';
import { FC, useState } from 'react';
import { useAsyncDebounce } from 'react-table';
import { TableSearchField } from './TableSearchField/TableSearchField';
interface ITableSearchProps {
initialValue?: string;
onChange?: (value: string) => void;
placeholder?: string;
hasFilters?: boolean;
getSearchContext?: () => IGetSearchContextOutput;
}
/**
* @deprecated use `Search` instead.
*/
export const TableSearch: FC<ITableSearchProps> = ({
initialValue,
onChange = () => {},
placeholder,
hasFilters,
getSearchContext,
}) => {
const [searchInputState, setSearchInputState] = useState(initialValue);
const debouncedOnSearch = useAsyncDebounce(onChange, 200);
const onSearchChange = (value: string) => {
debouncedOnSearch(value);
setSearchInputState(value);
};
return (
<TableSearchField
value={searchInputState!}
onChange={onSearchChange}
placeholder={placeholder}
hasFilters={hasFilters}
getSearchContext={getSearchContext}
/>
);
};

View File

@ -1,46 +0,0 @@
import { makeStyles } from 'tss-react/mui';
export const useStyles = makeStyles()(theme => ({
container: {
display: 'flex',
flexGrow: 1,
alignItems: 'center',
position: 'relative',
maxWidth: '400px',
[theme.breakpoints.down('md')]: {
marginTop: theme.spacing(1),
maxWidth: '100%',
},
},
search: {
display: 'flex',
alignItems: 'center',
backgroundColor: theme.palette.background.paper,
border: `1px solid ${theme.palette.grey[300]}`,
borderRadius: theme.shape.borderRadiusExtraLarge,
padding: '3px 5px 3px 12px',
width: '100%',
zIndex: 3,
'&.search-container:focus-within': {
borderColor: theme.palette.primary.light,
boxShadow: theme.boxShadows.main,
},
},
searchIcon: {
marginRight: 8,
color: theme.palette.inactiveIcon,
},
clearContainer: {
width: '30px',
'& > button': {
padding: '7px',
},
},
clearIcon: {
color: theme.palette.grey[700],
fontSize: '18px',
},
inputRoot: {
width: '100%',
},
}));

View File

@ -1,110 +0,0 @@
import { useRef, useState } from 'react';
import { IconButton, InputBase, Tooltip } from '@mui/material';
import { Search, Close } from '@mui/icons-material';
import classnames from 'classnames';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { useStyles } from './TableSearchField.styles';
import { TableSearchFieldSuggestions } from './TableSearchFieldSuggestions/TableSearchFieldSuggestions';
import { IGetSearchContextOutput } from 'hooks/useSearch';
import { useKeyboardShortcut } from 'hooks/useKeyboardShortcut';
interface ITableSearchFieldProps {
value: string;
onChange: (value: string) => void;
className?: string;
placeholder?: string;
hasFilters?: boolean;
getSearchContext?: () => IGetSearchContextOutput;
}
/**
* @deprecated use `Search` instead.
*/
export const TableSearchField = ({
value = '',
onChange,
className,
placeholder: customPlaceholder,
hasFilters,
getSearchContext,
}: ITableSearchFieldProps) => {
const ref = useRef<HTMLInputElement>();
const { classes: styles } = useStyles();
const [showSuggestions, setShowSuggestions] = useState(false);
const hotkey = useKeyboardShortcut(
{ modifiers: ['ctrl'], key: 'k', preventDefault: true },
() => {
if (document.activeElement === ref.current) {
ref.current?.blur();
} else {
ref.current?.focus();
}
}
);
useKeyboardShortcut({ key: 'Escape' }, () => {
if (document.activeElement === ref.current) {
ref.current?.blur();
}
});
const placeholder = `${customPlaceholder ?? 'Search'} (${hotkey})`;
return (
<div className={styles.container}>
<div
className={classnames(
styles.search,
className,
'search-container'
)}
>
<Search
className={classnames(styles.searchIcon, 'search-icon')}
/>
<InputBase
inputRef={ref}
placeholder={placeholder}
classes={{
root: classnames(styles.inputRoot, 'input-container'),
}}
inputProps={{ 'aria-label': placeholder }}
value={value}
onChange={e => onChange(e.target.value)}
onFocus={() => setShowSuggestions(true)}
onBlur={() => setShowSuggestions(false)}
/>
<div
className={classnames(
styles.clearContainer,
'clear-container'
)}
>
<ConditionallyRender
condition={Boolean(value)}
show={
<Tooltip title="Clear search query" arrow>
<IconButton
size="small"
onClick={() => {
onChange('');
ref.current?.focus();
}}
>
<Close className={styles.clearIcon} />
</IconButton>
</Tooltip>
}
/>
</div>
</div>
<ConditionallyRender
condition={Boolean(hasFilters) && showSuggestions}
show={
<TableSearchFieldSuggestions
getSearchContext={getSearchContext!}
/>
}
/>
</div>
);
};

View File

@ -1,72 +0,0 @@
import { styled } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import {
getSearchTextGenerator,
IGetSearchContextOutput,
} from 'hooks/useSearch';
import { VFC } from 'react';
const StyledHeader = styled('span')(({ theme }) => ({
fontSize: theme.fontSizes.smallBody,
color: theme.palette.text.primary,
}));
const StyledCode = styled('span')(({ theme }) => ({
backgroundColor: theme.palette.secondaryContainer,
color: theme.palette.text.primary,
padding: theme.spacing(0, 0.5),
borderRadius: theme.spacing(0.5),
}));
interface ISearchDescriptionProps {
filters: any[];
getSearchContext: () => IGetSearchContextOutput;
searchableColumnsString: string;
}
export const SearchDescription: VFC<ISearchDescriptionProps> = ({
filters,
getSearchContext,
searchableColumnsString,
}) => {
const searchContext = getSearchContext();
const getSearchText = getSearchTextGenerator(searchContext.columns);
const searchText = getSearchText(searchContext.searchValue);
const searchFilters = filters.filter(filter => filter.values.length > 0);
return (
<>
<ConditionallyRender
condition={Boolean(searchText)}
show={
<>
<StyledHeader>Searching for:</StyledHeader>
<p>
<StyledCode>{searchText}</StyledCode>{' '}
{searchableColumnsString
? ` in ${searchableColumnsString}`
: ''}
</p>
</>
}
/>
<ConditionallyRender
condition={searchFilters.length > 0}
show={
<>
<StyledHeader>Filtering by:</StyledHeader>
{searchFilters.map(filter => (
<p key={filter.name}>
<StyledCode>
{filter.values.join(',')}
</StyledCode>{' '}
in {filter.header}. Options:{' '}
{filter.options.join(', ')}
</p>
))}
</>
}
/>
</>
);
};

View File

@ -1,62 +0,0 @@
import { styled } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { IGetSearchContextOutput } from 'hooks/useSearch';
import { VFC } from 'react';
const StyledHeader = styled('span')(({ theme }) => ({
fontSize: theme.fontSizes.smallBody,
color: theme.palette.text.primary,
}));
const StyledCode = styled('span')(({ theme }) => ({
backgroundColor: theme.palette.secondaryContainer,
color: theme.palette.text.primary,
padding: theme.spacing(0, 0.5),
borderRadius: theme.spacing(0.5),
}));
interface ISearchInstructionsProps {
filters: any[];
getSearchContext: () => IGetSearchContextOutput;
searchableColumnsString: string;
}
export const SearchInstructions: VFC<ISearchInstructionsProps> = ({
filters,
getSearchContext,
searchableColumnsString,
}) => {
return (
<>
<StyledHeader>
{filters.length > 0
? 'Filter your search with operators like:'
: `Start typing to search${
searchableColumnsString
? ` in ${searchableColumnsString}`
: '...'
}`}
</StyledHeader>
{filters.map(filter => (
<p key={filter.name}>
Filter by {filter.header}:{' '}
<StyledCode>
{filter.name}:{filter.options[0]}
</StyledCode>
<ConditionallyRender
condition={filter.options.length > 1}
show={
<>
{' or '}
<StyledCode>
{filter.name}:
{filter.options.slice(0, 2).join(',')}
</StyledCode>
</>
}
/>
</p>
))}
</>
);
};

View File

@ -1,150 +0,0 @@
import { FilterList } from '@mui/icons-material';
import { Box, Divider, Paper, styled } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import {
getColumnValues,
getFilterableColumns,
getFilterValues,
IGetSearchContextOutput,
} from 'hooks/useSearch';
import { useMemo, VFC } from 'react';
import { SearchDescription } from './SearchDescription/SearchDescription';
import { SearchInstructions } from './SearchInstructions/SearchInstructions';
const randomIndex = (arr: any[]) => Math.floor(Math.random() * arr.length);
const StyledPaper = 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 StyledBox = styled(Box)(({ theme }) => ({
display: 'flex',
gap: theme.spacing(2),
}));
const StyledFilterList = styled(FilterList)(({ theme }) => ({
color: theme.palette.text.secondary,
}));
const StyledDivider = styled(Divider)(({ theme }) => ({
border: `1px dashed ${theme.palette.dividerAlternative}`,
margin: theme.spacing(1.5, 0),
}));
const StyledCode = styled('span')(({ theme }) => ({
backgroundColor: theme.palette.secondaryContainer,
color: theme.palette.text.primary,
padding: theme.spacing(0, 0.5),
borderRadius: theme.spacing(0.5),
}));
interface TableSearchFieldSuggestionsProps {
getSearchContext: () => IGetSearchContextOutput;
}
export const TableSearchFieldSuggestions: VFC<
TableSearchFieldSuggestionsProps
> = ({ getSearchContext }) => {
const searchContext = getSearchContext();
const randomRow = useMemo(
() => randomIndex(searchContext.data),
[searchContext.data]
);
const filters = getFilterableColumns(searchContext.columns)
.map(column => {
const filterOptions = searchContext.data.map(row =>
getColumnValues(column, row)
);
return {
name: column.filterName,
header: column.Header ?? column.filterName,
options: [...new Set(filterOptions)].sort((a, b) =>
a.localeCompare(b)
),
suggestedOption:
filterOptions[randomRow] ?? `example-${column.filterName}`,
values: getFilterValues(
column.filterName,
searchContext.searchValue
),
};
})
.sort((a, b) => a.name.localeCompare(b.name));
const searchableColumns = searchContext.columns.filter(
column => column.searchable && column.accessor
);
const searchableColumnsString = searchableColumns
.map(column => column.Header ?? column.accessor)
.join(', ');
const suggestedTextSearch =
searchContext.data.length && searchableColumns.length
? getColumnValues(
searchableColumns[0],
searchContext.data[randomRow]
)
: 'example-search-text';
return (
<StyledPaper>
<StyledBox>
<StyledFilterList />
<Box>
<ConditionallyRender
condition={Boolean(searchContext.searchValue)}
show={
<SearchDescription
filters={filters}
getSearchContext={getSearchContext}
searchableColumnsString={
searchableColumnsString
}
/>
}
elseShow={
<SearchInstructions
filters={filters}
getSearchContext={getSearchContext}
searchableColumnsString={
searchableColumnsString
}
/>
}
/>
</Box>
</StyledBox>
<StyledDivider />
<ConditionallyRender
condition={filters.length > 0}
show="Combine filters and search."
/>
<p>
Example:{' '}
<StyledCode>
{filters.map(filter => (
<span key={filter.name}>
{filter.name}:{filter.suggestedOption}{' '}
</span>
))}
<span>{suggestedTextSearch}</span>
</StyledCode>
</p>
</StyledPaper>
);
};

View File

@ -1,4 +1,3 @@
export { TableSearch } from './TableSearch/TableSearch';
export { SortableTableHeader } from './SortableTableHeader/SortableTableHeader'; export { SortableTableHeader } from './SortableTableHeader/SortableTableHeader';
export { TableBody, TableRow } from '@mui/material'; export { TableBody, TableRow } from '@mui/material';
export { Table } from './Table/Table'; export { Table } from './Table/Table';