1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-10 01:16:39 +02:00

Feature list table (#908)

* experiment with generic table

* feat: example implementation of sortable table interfaces

* add enhanced table header

Co-authored-by: Nuno Góis <github@nunogois.com>

* table cleanup

Co-authored-by: Nuno Góis
Co-authored-by: Fredrik Strand Oseberg

* useSort hook interface surface

Co-authored-by: Nuno Góis <github@nunogois.com>

* sort handler initial implementation

Co-authored-by: Tymoteusz Czech <Tymek@users.noreply.github.com>

* new table unified components

* feature flags table components

Co-authored-by: Nuno Góis <github@nunogois.com>

* feat: new table sort hook

* feat: table sort

* useSearch hook implementation

* update new sort hook tests

* sortable headers hook

* feat: add sort to other table features

* move experimental table hooks to a directory

* update new table header styles

* fix: header, tableActions

* add some details like pagination and highlighter so we keep them in mind

* feature table cells

* update new table sort logic

* new pagination

* fix formatting and remove unused component

* fix: adapt useSearch default search to text instead of regex (PR #924)

* fix: update table title based on visible rows

* fix: remove test route

* refactor: move table experiment files

* features table experimentation

* feat: enhanced feature flags table

* fix: features default sort

* feat: enhanced table loading

* fix: table theme after mui5 update

* features list placeholder

* add react-table

* update snapshots after theme change

* remove unused files

* fix: improve features table after review

* refactor: rename feature type cell variables

Co-authored-by: Fredrik Oseberg <fredrik.no@gmail.com>
Co-authored-by: Nuno Góis <github@nunogois.com>
Co-authored-by: Tymoteusz Czech <Tymek@users.noreply.github.com>
This commit is contained in:
Tymoteusz Czech 2022-05-05 15:34:46 +02:00 committed by GitHub
parent d8143c6ff4
commit 1772997d28
54 changed files with 1307 additions and 210 deletions

View File

@ -55,6 +55,8 @@
"@types/node": "17.0.18",
"@types/react": "17.0.44",
"@types/react-dom": "17.0.16",
"@types/react-router-dom": "5.3.3",
"@types/react-table": "^7.7.11",
"@types/react-test-renderer": "17.0.2",
"@types/react-timeago": "4.1.3",
"@types/semver": "^7.3.9",
@ -82,6 +84,7 @@
"react-hooks-global-state": "1.0.2",
"react-router-dom": "6.3.0",
"react-scripts": "5.0.1",
"react-table": "^7.7.0",
"react-test-renderer": "17.0.2",
"react-timeago": "6.2.1",
"sass": "1.51.0",

View File

@ -32,7 +32,7 @@ const UsersAdmin = () => {
show={
<div className={styles.tableActions}>
<TableActions
search={search}
initialSearchValue={search}
onSearch={search =>
setSearch(search)
}

View File

@ -1,19 +1,18 @@
import { makeStyles } from 'tss-react/mui';
import { unleashGrey } from 'themes/themeColors';
export const useStyles = makeStyles()(theme => ({
tableRow: {
'& > td': {
padding: '4px 16px',
borderColor: unleashGrey[300],
borderColor: theme.palette.grey[300],
},
'&:hover': {
backgroundColor: unleashGrey[100],
backgroundColor: theme.palette.grey[100],
},
},
tableCellHeader: {
'& > th': {
backgroundColor: unleashGrey[200],
backgroundColor: theme.palette.grey[200],
fontWeight: 'normal',
border: 0,
'&:first-of-type': {

View File

@ -1,5 +1,5 @@
import { useFeaturesArchive } from 'hooks/api/getters/useFeaturesArchive/useFeaturesArchive';
import { FeatureToggleList } from '../feature/FeatureToggleList/FeatureToggleList';
import { FeatureToggleList } from '../feature/FeatureToggleList/FeatureToggleArchiveList';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { useFeaturesFilter } from 'hooks/useFeaturesFilter';
import { useFeatureArchiveApi } from 'hooks/api/actions/useFeatureArchiveApi/useReviveFeatureApi';

View File

@ -1,6 +1,6 @@
import { FC } from 'react';
import { useProjectFeaturesArchive } from 'hooks/api/getters/useProjectFeaturesArchive/useProjectFeaturesArchive';
import { FeatureToggleList } from '../feature/FeatureToggleList/FeatureToggleList';
import { FeatureToggleList } from '../feature/FeatureToggleList/FeatureToggleArchiveList';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { useFeaturesFilter } from 'hooks/useFeaturesFilter';
import { useFeatureArchiveApi } from 'hooks/api/actions/useFeatureArchiveApi/useReviveFeatureApi';

View File

@ -1,16 +1,26 @@
import { VFC } from 'react';
interface IHighlighterProps {
search: string;
children: string;
search?: string;
children?: string;
caseSensitive?: boolean;
}
const escapeRegex = (str: string) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
export const Highlighter = ({
export const Highlighter: VFC<IHighlighterProps> = ({
search,
children,
caseSensitive,
}: IHighlighterProps) => {
}) => {
if (!children) {
return null;
}
if (!search) {
return <>{children}</>;
}
const regex = new RegExp(escapeRegex(search), caseSensitive ? 'g' : 'gi');
return (

View File

@ -1,27 +0,0 @@
import { ListItem } from '@mui/material';
import { Link } from 'react-router-dom';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { useStyles } from 'component/common/ListPlaceholder/ListPlaceholder.styles';
interface IListPlaceholderProps {
text: string;
link?: string;
linkText?: string;
}
const ListPlaceholder = ({ text, link, linkText }: IListPlaceholderProps) => {
const { classes: styles } = useStyles();
return (
<ListItem className={styles.emptyStateListItem}>
{text}
<ConditionallyRender
condition={Boolean(link && linkText)}
// @ts-expect-error
show={<Link to={link}>Add your first toggle</Link>}
/>
</ListItem>
);
};
export default ListPlaceholder;

View File

@ -18,6 +18,9 @@ interface IPaginateUIProps {
style?: React.CSSProperties;
}
/**
* @deprecated
*/
const PaginateUI = ({
pages,
pageIndex,

View File

@ -0,0 +1,8 @@
import { createContext, useContext } from 'react';
const SearchHighlightContext = createContext('');
export const SearchHighlightProvider = SearchHighlightContext.Provider;
export const useSearchHighlightContext = () =>
useContext(SearchHighlightContext);

View File

@ -0,0 +1,27 @@
import { makeStyles } from 'tss-react/mui';
export const useStyles = makeStyles()(theme => ({
tableCellHeaderSortable: {
padding: 0,
position: 'relative',
},
sortButton: {
all: 'unset',
padding: theme.spacing(2),
fontWeight: theme.fontWeight.medium,
width: '100%',
'&:focus-visible, &:active': {
outline: 'revert',
},
display: 'flex',
alignItems: 'center',
'&:hover': {
backgroundColor: theme.palette.grey[400],
},
boxSizing: 'inherit',
cursor: 'pointer',
},
sorted: {
fontWeight: theme.fontWeight.bold,
},
}));

View File

@ -0,0 +1,67 @@
import React, { FC, MouseEventHandler, useContext } from 'react';
import { TableCell } from '@mui/material';
import classnames from 'classnames';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { useStyles } from './CellSortable.styles';
import { AnnouncerContext } from 'component/common/Announcer/AnnouncerContext/AnnouncerContext';
import { SortArrow } from './SortArrow/SortArrow';
interface ICellSortableProps {
isSortable?: boolean;
isSorted?: boolean;
isDescending?: boolean;
ariaTitle?: string;
onClick?: MouseEventHandler<HTMLButtonElement>;
}
export const CellSortable: FC<ICellSortableProps> = ({
children,
isSortable = true,
isSorted = false,
isDescending,
ariaTitle,
onClick = () => {},
}) => {
const { setAnnouncement } = useContext(AnnouncerContext);
const { classes: styles } = useStyles();
const ariaSort = isSorted
? isDescending
? 'descending'
: 'ascending'
: undefined;
const onSortClick: MouseEventHandler<HTMLButtonElement> = event => {
onClick(event);
setAnnouncement(
`Sorted${ariaTitle ? ` by ${ariaTitle} ` : ''}, ${
isDescending ? 'ascending' : 'descending'
}`
);
};
return (
<TableCell
component="th"
aria-sort={ariaSort}
className={classnames(styles.tableCellHeaderSortable)}
>
<ConditionallyRender
condition={isSortable}
show={
<button
className={classnames(
styles.sortButton,
isSorted && styles.sorted
)}
onClick={onSortClick}
>
{children}
<SortArrow isSorted={isSorted} isDesc={isDescending} />
</button>
}
elseShow={children}
/>
</TableCell>
);
};

View File

@ -0,0 +1,13 @@
import { makeStyles } from 'tss-react/mui';
export const useStyles = makeStyles()(theme => ({
icon: {
marginLeft: theme.spacing(0.5),
color: theme.palette.grey[700],
fontSize: theme.fontSizes.mainHeader,
verticalAlign: 'middle',
},
sorted: {
color: theme.palette.grey[900],
},
}));

View File

@ -0,0 +1,50 @@
import { VFC } from 'react';
import {
KeyboardArrowDown,
KeyboardArrowUp,
UnfoldMoreOutlined,
} from '@mui/icons-material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { useStyles } from './SortArrow.styles';
import classnames from 'classnames';
interface ISortArrowProps {
isSorted?: boolean;
isDesc?: boolean;
}
export const SortArrow: VFC<ISortArrowProps> = ({
isSorted: sorted,
isDesc: desc = false,
}) => {
const { classes: styles } = useStyles();
return (
<ConditionallyRender
condition={Boolean(sorted)}
show={
<ConditionallyRender
condition={Boolean(desc)}
show={
<KeyboardArrowDown
className={classnames(styles.icon, styles.sorted)}
fontSize="inherit"
/>
}
elseShow={
<KeyboardArrowUp
className={classnames(styles.icon, styles.sorted)}
fontSize="inherit"
/>
}
/>
}
elseShow={
<UnfoldMoreOutlined
className={styles.icon}
fontSize="inherit"
/>
}
/>
);
};

View File

@ -0,0 +1,21 @@
import { makeStyles } from 'tss-react/mui';
export const useStyles = makeStyles()(theme => ({
tableHeader: {
'& > th': {
border: 0,
'&:first-of-type': {
borderTopLeftRadius: '8px',
borderBottomLeftRadius: '8px',
},
'&:last-of-type': {
borderTopRightRadius: '8px',
borderBottomRightRadius: '8px',
},
},
},
icon: {
marginLeft: theme.spacing(0.5),
fontSize: 18,
},
}));

View File

@ -0,0 +1,47 @@
import { VFC } from 'react';
import { TableHead, TableRow } from '@mui/material';
import { HeaderGroup } from 'react-table';
import { useStyles } from './SortableTableHeader.styles';
import { CellSortable } from './CellSortable/CellSortable';
interface ISortableTableHeaderProps {
headerGroups: HeaderGroup<object>[];
}
export const SortableTableHeader: VFC<ISortableTableHeaderProps> = ({
headerGroups,
}) => {
const { classes: styles } = useStyles();
return (
<TableHead>
{headerGroups.map(headerGroup => (
<TableRow
{...headerGroup.getHeaderGroupProps()}
className={styles.tableHeader}
>
{headerGroup.headers.map(column => {
const content = column.render('Header');
return (
<CellSortable
{...column.getHeaderProps(
column.getSortByToggleProps()
)}
ariaTitle={
typeof content === 'string'
? content
: undefined
}
isSortable={column.canSort}
isSorted={column.isSorted}
isDescending={column.isSortedDesc}
>
{content}
</CellSortable>
);
})}
</TableRow>
))}
</TableHead>
);
};

View File

@ -1,9 +1,18 @@
import { makeStyles } from 'tss-react/mui';
import { unleashGrey } from 'themes/themeColors';
export const useStyles = makeStyles()(theme => ({
tableActions: {
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
'&>button': {
padding: theme.spacing(1),
flexShrink: 0,
},
paddingRight: theme.spacing(1),
},
fieldWidth: {
width: '49px',
width: '45px',
'& .search-icon': {
marginRight: 0,
},
@ -19,7 +28,7 @@ export const useStyles = makeStyles()(theme => ({
},
},
fieldWidthEnter: {
width: '100%',
width: '250px',
transition: 'width 0.6s',
'& .search-icon': {
marginRight: '8px',
@ -37,7 +46,7 @@ export const useStyles = makeStyles()(theme => ({
},
},
fieldWidthLeave: {
width: '49px',
width: '45px',
transition: 'width 0.6s',
'& .search-icon': {
marginRight: 0,
@ -53,11 +62,11 @@ export const useStyles = makeStyles()(theme => ({
},
verticalSeparator: {
height: '100%',
backgroundColor: unleashGrey[500],
backgroundColor: theme.palette.grey[500],
width: '1px',
display: 'inline-block',
marginLeft: '16px',
marginRight: '32px',
marginLeft: theme.spacing(2),
marginRight: theme.spacing(4),
padding: '10px 0',
verticalAlign: 'middle',
},

View File

@ -1,60 +1,87 @@
import { useState } from 'react';
import { FC, useState } from 'react';
import { IconButton, Tooltip } from '@mui/material';
import { Search } from '@mui/icons-material';
import { useAsyncDebounce } from 'react-table';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import AnimateOnMount from 'component/common/AnimateOnMount/AnimateOnMount';
import { TableSearchField } from 'component/common/Table/TableActions/TableSearchField/TableSearchField';
import { useStyles } from 'component/common/Table/TableActions/TableActions.styles';
import { TableSearchField } from './TableSearchField/TableSearchField';
import { useStyles } from './TableActions.styles';
interface ITableActionsProps {
search: string;
onSearch: (value: string) => void;
initialSearchValue?: string;
onSearch?: (value: string) => void;
searchTip?: string;
isSeparated?: boolean;
}
export const TableActions = ({ search, onSearch }: ITableActionsProps) => {
const [searchExpanded, setSearchExpanded] = useState(false);
export const TableActions: FC<ITableActionsProps> = ({
initialSearchValue: search,
onSearch = () => {},
searchTip = 'Search',
children,
isSeparated,
}) => {
const [searchExpanded, setSearchExpanded] = useState(Boolean(search));
const [searchInputState, setSearchInputState] = useState(search);
const [animating, setAnimating] = useState(false);
const debouncedOnSearch = useAsyncDebounce(onSearch, 200);
const { classes: styles } = useStyles();
const onBlur = (clear = false) => {
if (!search || clear) {
if (!searchInputState || clear) {
setSearchExpanded(false);
}
};
const onSearchChange = (value: string) => {
debouncedOnSearch(value);
setSearchInputState(value);
};
return (
<>
<AnimateOnMount
mounted={searchExpanded}
start={styles.fieldWidth}
enter={styles.fieldWidthEnter}
leave={styles.fieldWidthLeave}
onStart={() => setAnimating(true)}
onEnd={() => setAnimating(false)}
>
<TableSearchField
value={search}
onChange={onSearch}
placeholder="Search users..."
onBlur={onBlur}
/>
</AnimateOnMount>
<div className={styles.tableActions}>
<ConditionallyRender
condition={!searchExpanded && !animating}
condition={Boolean(onSearch)}
show={
<Tooltip title="Search users" arrow>
<IconButton
aria-label="Search users"
onClick={() => setSearchExpanded(true)}
size="large"
<>
<AnimateOnMount
mounted={searchExpanded}
start={styles.fieldWidth}
enter={styles.fieldWidthEnter}
leave={styles.fieldWidthLeave}
onStart={() => setAnimating(true)}
onEnd={() => setAnimating(false)}
>
<Search />
</IconButton>
</Tooltip>
<TableSearchField
value={searchInputState!}
onChange={onSearchChange}
placeholder={`${searchTip}...`}
onBlur={onBlur}
/>
</AnimateOnMount>
<ConditionallyRender
condition={!searchExpanded && !animating}
show={
<Tooltip title={searchTip} arrow>
<IconButton
aria-label={searchTip}
onClick={() => setSearchExpanded(true)}
size="large"
>
<Search />
</IconButton>
</Tooltip>
}
/>
</>
}
/>
<div className={styles.verticalSeparator} />
</>
<ConditionallyRender
condition={Boolean(isSeparated)}
show={<div className={styles.verticalSeparator} />}
/>
{children}
</div>
);
};

View File

@ -1,11 +1,11 @@
import { IconButton, InputBase, Tooltip } from '@mui/material';
import { Search, Close } from '@mui/icons-material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { useStyles } from 'component/common/Table/TableActions/TableSearchField/TableSearchField.styles';
import classnames from 'classnames';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { useStyles } from './TableSearchField.styles';
interface ITableSearchFieldProps {
value: string;
value?: string;
onChange: (value: string) => void;
className?: string;
placeholder?: string;
@ -13,7 +13,7 @@ interface ITableSearchFieldProps {
}
export const TableSearchField = ({
value,
value = '',
onChange,
className,
placeholder,

View File

@ -1,5 +1,4 @@
import { makeStyles } from 'tss-react/mui';
import { unleashGrey } from 'themes/themeColors';
export const useStyles = makeStyles()(theme => ({
tableCellHeaderSortable: {
@ -9,13 +8,13 @@ export const useStyles = makeStyles()(theme => ({
'& > svg': {
fontSize: 18,
verticalAlign: 'middle',
color: unleashGrey[700],
color: theme.palette.grey[700],
marginLeft: '4px',
},
'&.sorted': {
fontWeight: 'bold',
'& > svg': {
color: unleashGrey[900],
color: theme.palette.grey[900],
},
},
},
@ -29,9 +28,9 @@ export const useStyles = makeStyles()(theme => ({
display: 'flex',
alignItems: 'center',
'&:hover': {
backgroundColor: unleashGrey[400],
backgroundColor: theme.palette.grey[400],
'& > svg': {
color: unleashGrey[900],
color: theme.palette.grey[900],
},
},
},

View File

@ -23,6 +23,9 @@ interface ITableCellSortableProps {
children: ReactNode;
}
/**
* @deprecated No longer in use. See `SortableTableHeader`. Remove when Users table is refactored.
*/
export const TableCellSortable = ({
className,
name,
@ -40,12 +43,6 @@ export const TableCellSortable = ({
: 'ascending'
: undefined;
const cellClassName = classnames(
className,
styles.tableCellHeaderSortable,
sort.type === name && 'sorted'
);
const onSortClick = () => {
setSort(prev => ({
desc: !Boolean(prev.desc),
@ -57,7 +54,14 @@ export const TableCellSortable = ({
};
return (
<TableCell aria-sort={ariaSort} className={cellClassName}>
<TableCell
aria-sort={ariaSort}
className={classnames(
className,
styles.tableCellHeaderSortable,
sort.type === name && 'sorted'
)}
>
<button className={styles.sortButton} onClick={onSortClick}>
{children}
<ConditionallyRender

View File

@ -0,0 +1,14 @@
import { makeStyles } from 'tss-react/mui';
export const useStyles = makeStyles()(theme => ({
panel: {
width: '100%',
marginBottom: theme.spacing(2),
borderRadius: theme.spacing(1.5),
paddingBottom: theme.spacing(4),
},
content: {
padding: theme.spacing(4),
paddingBottom: 0,
},
}));

View File

@ -0,0 +1,18 @@
import { forwardRef, ReactNode } from 'react';
import { Paper } from '@mui/material';
import { Box } from '@mui/material';
import { useStyles } from './TablePanel.styles';
export const TablePanel = forwardRef<
HTMLDivElement,
{ children: ReactNode; header?: ReactNode }
>(({ header, children, ...props }, ref) => {
const { classes: styles } = useStyles();
return (
<Paper ref={ref} className={styles.panel} {...props}>
{header}
<Box className={styles.content}>{children}</Box>
</Paper>
);
});

View File

@ -8,5 +8,7 @@ export const useStyles = makeStyles()(theme => ({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginTop: theme.spacing(4),
marginBottom: theme.spacing(4),
},
}));

View File

@ -0,0 +1,11 @@
import { FC } from 'react';
import { Box } from '@mui/material';
import { useStyles } from 'component/common/Table/TablePlaceholder/TablePlaceholder.styles';
const TablePlaceholder: FC = ({ children }) => {
const { classes: styles } = useStyles();
return <Box className={styles.emptyStateListItem}>{children}</Box>;
};
export default TablePlaceholder;

View File

@ -0,0 +1,12 @@
import { makeStyles } from 'tss-react/mui';
export const useStyles = makeStyles()(theme => ({
root: {
paddingLeft: theme.spacing(4),
paddingRight: theme.spacing(4),
paddingTop: theme.spacing(2.5),
paddingBottom: theme.spacing(2.5),
borderBottom: `1px solid ${theme.palette.divider}`,
justifyContent: 'space-between',
},
}));

View File

@ -0,0 +1,20 @@
import { FC } from 'react';
import { Toolbar, Typography } from '@mui/material';
import { useStyles } from './TableToolbar.styles';
interface ITableToolbarProps {
title: string;
}
export const TableToolbar: FC<ITableToolbarProps> = ({ title, children }) => {
const { classes: styles } = useStyles();
return (
<Toolbar className={styles.root}>
<Typography variant="h1" component="h1" data-loading>
{title}
</Typography>
{children}
</Toolbar>
);
};

View File

@ -9,12 +9,10 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
import PageContent from 'component/common/PageContent/PageContent';
import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle';
import AccessContext from 'contexts/AccessContext';
import ListPlaceholder from 'component/common/ListPlaceholder/ListPlaceholder';
import { IFeaturesFilter } from 'hooks/useFeaturesFilter';
import { FeatureToggleListItem } from './FeatureToggleListItem/FeatureToggleListItem';
import { FeatureToggleListActions } from './FeatureToggleListActions/FeatureToggleListActions';
import { CreateFeatureButton } from '../CreateFeatureButton/CreateFeatureButton';
import { useCreateFeaturePath } from '../CreateFeatureButton/useCreateFeaturePath';
import { IFeaturesSort } from 'hooks/useFeaturesSort';
import { FeatureSchema } from 'openapi';
import { useStyles } from './styles';
@ -63,7 +61,6 @@ export const FeatureToggleList: VFC<IFeatureToggleListProps> = ({
setSort,
}) => {
const { hasAccess } = useContext(AccessContext);
const createFeature = useCreateFeaturePath(filter);
const { classes: styles } = useStyles();
const smallScreen = useMediaQuery('(max-width:800px)');
const mobileView = useMediaQuery('(max-width:600px)');
@ -101,26 +98,9 @@ export const FeatureToggleList: VFC<IFeatureToggleListProps> = ({
/>
))}
elseShow={
<ConditionallyRender
condition={Boolean(isArchive)}
show={
<ListItem className={styles.emptyStateListItem}>
No archived features.
</ListItem>
}
elseShow={
<ConditionallyRender
condition={Boolean(createFeature?.access)}
show={() => (
<ListPlaceholder
text="No features available. Get started by adding a new feature toggle."
link={createFeature?.path}
linkText="Add your first toggle"
/>
)}
/>
}
/>
<ListItem className={styles.emptyStateListItem}>
No archived features.
</ListItem>
}
/>
);

View File

@ -1,24 +1,22 @@
import { useFeatures } from 'hooks/api/getters/useFeatures/useFeatures';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { useFeaturesFilter } from 'hooks/useFeaturesFilter';
import { FeatureToggleList } from './FeatureToggleList';
import { useFeaturesSort } from 'hooks/useFeaturesSort';
import { FeatureSchema } from 'openapi';
import { FeatureToggleListTable } from './FeatureToggleListTable/FeatureToggleListTable';
const featuresPlaceholder: FeatureSchema[] = Array(7).fill({
name: 'Name of the feature',
description: 'Short description of the feature',
type: '-',
createdAt: new Date(2022, 1, 1),
project: 'projectID',
});
export const FeatureToggleListContainer = () => {
const { uiConfig } = useUiConfig();
const { features = [], loading } = useFeatures();
const { filtered, filter, setFilter } = useFeaturesFilter(features);
const { sorted, sort, setSort } = useFeaturesSort(filtered);
return (
<FeatureToggleList
features={sorted}
loading={loading}
flags={uiConfig.flags}
filter={filter}
setFilter={setFilter}
sort={sort}
setSort={setSort}
<FeatureToggleListTable
data={loading ? featuresPlaceholder : features}
isLoading={loading}
/>
);
};

View File

@ -27,6 +27,9 @@ interface IFeatureToggleListItemProps {
className?: string;
}
/**
* @deprecated
*/
export const FeatureToggleListItem = memo<IFeatureToggleListItemProps>(
({
feature,

View File

@ -0,0 +1,32 @@
import { VFC } from 'react';
import { useLocationSettings } from 'hooks/useLocationSettings';
import { formatDateYMD, formatDateYMDHMS } from 'utils/formatDate';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { Tooltip } from '@mui/material';
interface IDateCellProps {
value?: Date | null;
}
export const DateCell: VFC<IDateCellProps> = ({ value }) => {
const { locationSettings } = useLocationSettings();
return (
<ConditionallyRender
condition={Boolean(value)}
show={
<Tooltip
title={formatDateYMDHMS(
value as Date,
locationSettings.locale
)}
arrow
>
<span data-loading>
{formatDateYMD(value as Date, locationSettings.locale)}
</span>
</Tooltip>
}
/>
);
};

View File

@ -0,0 +1,13 @@
import { makeStyles } from 'tss-react/mui';
export const useStyles = makeStyles()(theme => ({
description: {
color: theme.palette.grey[800],
fontSize: 'inherit',
display: 'inline-block',
maxWidth: '250px',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
},
}));

View File

@ -0,0 +1,53 @@
import { FC } from 'react';
import { Link, Typography } from '@mui/material';
import { Link as RouterLink } from 'react-router-dom';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { useStyles } from './FeatureNameCell.styles';
import { Highlighter } from 'component/common/Highlighter/Highlighter';
import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
interface IFeatureNameCellProps {
name?: string;
project?: string;
description?: string;
}
export const FeatureNameCell: FC<IFeatureNameCellProps> = ({
name,
project,
description,
}) => {
const { classes: styles } = useStyles();
const search = useSearchHighlightContext();
return (
<>
<Link
component={RouterLink}
to={`/projects/${project}/features/${name}`}
underline="hover"
data-loading
>
<Highlighter search={search}>{name}</Highlighter>
</Link>
<ConditionallyRender
condition={Boolean(description)}
show={
<>
<br />
<Typography
className={styles.description}
component="span"
data-loading
>
<Highlighter search={search}>
{description}
</Highlighter>
</Typography>
</>
}
/>
</>
);
};

View File

@ -0,0 +1,16 @@
import { makeStyles } from 'tss-react/mui';
export const useStyles = makeStyles()(theme => ({
container: {
width: '38px',
height: '38px',
background: 'gray',
borderRadius: '4px',
textAlign: 'center',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: theme.fontSizes.smallerBody,
margin: '0 auto',
},
}));

View File

@ -0,0 +1,115 @@
import React, { FC, VFC } from 'react';
import TimeAgo from 'react-timeago';
import { Tooltip, useTheme } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { useStyles } from './FeatureSeenCell.styles';
function shortenUnitName(unit?: string): string {
switch (unit) {
case 'second':
return 's';
case 'minute':
return 'm';
case 'hour':
return 'h';
case 'day':
return 'D';
case 'week':
return 'W';
case 'month':
return 'M';
case 'year':
return 'Y';
default:
return '';
}
}
const useFeatureColor = () => {
const theme = useTheme();
return (unit?: string): string => {
switch (unit) {
case 'second':
return theme.palette.success.light;
case 'minute':
return theme.palette.success.light;
case 'hour':
return theme.palette.success.light;
case 'day':
return theme.palette.success.light;
case 'week':
return theme.palette.warning.light;
case 'month':
return theme.palette.error.light;
case 'year':
return theme.palette.error.light;
default:
return theme.palette.grey[100];
}
};
};
interface IFeatureSeenCellProps {
value?: string | Date | null;
}
const Wrapper: FC<{ unit?: string; tooltip: string }> = ({
unit,
tooltip,
children,
}) => {
const { classes: styles } = useStyles();
const getColor = useFeatureColor();
return (
<Tooltip title={tooltip} arrow>
<div
className={styles.container}
style={{ background: getColor(unit) }}
data-loading
>
{children}
</div>
</Tooltip>
);
};
export const FeatureSeenCell: VFC<IFeatureSeenCellProps> = ({
value: lastSeenAt,
}) => {
return (
<ConditionallyRender
condition={Boolean(lastSeenAt)}
show={
<TimeAgo
date={lastSeenAt!}
title=""
live={false}
formatter={(
value: number,
unit: string,
suffix: string
) => {
return (
<Wrapper
tooltip={`Last usage reported ${value} ${unit}${
value !== 1 ? 's' : ''
} ${suffix}`}
unit={unit}
>
{value}
{shortenUnitName(unit)}
</Wrapper>
);
}}
/>
}
elseShow={
<Wrapper tooltip="No usage reported from connected applications">
&ndash;
</Wrapper>
}
/>
);
};

View File

@ -0,0 +1,11 @@
import { makeStyles } from 'tss-react/mui';
export const useStyles = makeStyles()(theme => ({
status: {
color: theme.palette.success.dark,
fontSize: 'inherit',
},
stale: {
color: theme.palette.error.dark,
},
}));

View File

@ -0,0 +1,21 @@
import { VFC } from 'react';
import { Typography } from '@mui/material';
import { useStyles } from './FeatureStaleCell.styles';
import classnames from 'classnames';
interface IFeatureStaleCellProps {
value?: boolean;
}
export const FeatureStaleCell: VFC<IFeatureStaleCellProps> = ({ value }) => {
const { classes: styles } = useStyles();
return (
<Typography
component="span"
className={classnames(styles.status, value && styles.stale)}
data-loading
>
{value ? 'Stale' : 'Active'}
</Typography>
);
};

View File

@ -0,0 +1,193 @@
import { useEffect, useMemo, VFC } from 'react';
import {
Link,
Table,
TableBody,
TableCell,
TableRow,
useMediaQuery,
useTheme,
} from '@mui/material';
import { Link as RouterLink } from 'react-router-dom';
import { useGlobalFilter, useSortBy, useTable } from 'react-table';
import useLoading from 'hooks/useLoading';
import { SortableTableHeader } from 'component/common/Table/SortableTableHeader/SortableTableHeader';
import { TableActions } from 'component/common/Table/TableActions/TableActions';
import { TablePanel } from 'component/common/Table/TablePanel/TablePanel';
import { TableToolbar } from 'component/common/Table/TableToolbar/TableToolbar';
import TablePlaceholder from 'component/common/Table/TablePlaceholder/TablePlaceholder';
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
import { DateCell } from './DateCell/DateCell';
import { FeatureNameCell } from './FeatureNameCell/FeatureNameCell';
import { FeatureSeenCell } from './FeatureSeenCell/FeatureSeenCell';
import { FeatureStaleCell } from './FeatureStaleCell/FeatureStaleCell';
import { FeatureTypeCell } from './FeatureTypeCell/FeatureTypeCell';
import { LinkCell } from './LinkCell/LinkCell';
import { CreateFeatureButton } from '../../CreateFeatureButton/CreateFeatureButton';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
interface IExperimentProps {
data: Record<string, any>[];
isLoading?: boolean;
}
const sortTypes = {
date: (a: any, b: any, id: string) =>
b?.values?.[id]?.getTime() - a?.values?.[id]?.getTime(),
boolean: (v1: any, v2: any, id: string) => {
const a = v1?.values?.[id];
const b = v2?.values?.[id];
return a === b ? 0 : a ? 1 : -1;
},
alphanumeric: (a: any, b: any, id: string) =>
a?.values?.[id]
?.toLowerCase()
.localeCompare(b?.values?.[id]?.toLowerCase()),
};
const columns = [
{
Header: 'Seen',
accessor: 'lastSeenAt',
Cell: FeatureSeenCell,
sortType: 'date',
},
{
Header: 'Type',
accessor: 'type',
Cell: FeatureTypeCell,
},
{
Header: 'Feature toggle name',
accessor: 'name',
// @ts-expect-error // TODO: props type
Cell: ({ row: { original } }) => <FeatureNameCell {...original} />,
sortType: 'alphanumeric',
},
{
Header: 'Created on',
accessor: 'createdAt',
Cell: DateCell,
sortType: 'date',
},
{
Header: 'Project ID',
accessor: 'project',
Cell: ({ value }: { value: string }) => (
<LinkCell to={`/projects/${value}`}>{value}</LinkCell>
),
sortType: 'alphanumeric',
},
{
Header: 'State',
accessor: 'stale',
Cell: FeatureStaleCell,
sortType: 'boolean',
},
// Always hidden -- for search
{
accessor: 'description',
},
];
export const FeatureToggleListTable: VFC<IExperimentProps> = ({
data,
isLoading = false,
}) => {
const theme = useTheme();
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
const isMediumScreen = useMediaQuery(theme.breakpoints.down('lg'));
const ref = useLoading(isLoading);
const initialState = useMemo(
() => ({
sortBy: [{ id: 'createdAt', desc: false }],
hiddenColumns: ['description'],
}),
[]
);
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
state: { globalFilter },
setGlobalFilter,
setHiddenColumns,
} = useTable(
{
columns,
data,
initialState,
sortTypes,
autoResetGlobalFilter: false,
},
useGlobalFilter,
useSortBy
);
useEffect(() => {
if (isSmallScreen) {
setHiddenColumns(['lastSeenAt', 'type', 'stale', 'description']);
} else if (isMediumScreen) {
setHiddenColumns(['lastSeenAt', 'stale', 'description']);
} else {
setHiddenColumns(['description']);
}
}, [setHiddenColumns, isSmallScreen, isMediumScreen]);
return (
<TablePanel
ref={ref}
header={
<TableToolbar title={`Feature Flags (${data.length})`}>
<TableActions isSeparated onSearch={setGlobalFilter}>
<Link
component={RouterLink}
to="/archive"
underline="always"
sx={{ marginRight: 3 }}
>
View archive
</Link>
<CreateFeatureButton
loading={false}
filter={{ query: '', project: 'default' }}
/>
</TableActions>
</TableToolbar>
}
>
<SearchHighlightProvider value={globalFilter}>
<Table {...getTableProps()}>
<SortableTableHeader headerGroups={headerGroups} />
<TableBody {...getTableBodyProps()}>
{rows.map(row => {
prepareRow(row);
return (
<TableRow {...row.getRowProps()}>
{row.cells.map(cell => (
<TableCell {...cell.getCellProps()}>
{cell.render('Cell')}
</TableCell>
))}
</TableRow>
);
})}
</TableBody>
</Table>
</SearchHighlightProvider>
<ConditionallyRender
condition={rows.length === 0}
show={
<TablePlaceholder>
No features available. Get started by adding a new
feature toggle.
</TablePlaceholder>
}
/>
</TablePanel>
);
};

View File

@ -0,0 +1,8 @@
import { makeStyles } from 'tss-react/mui';
export const useStyles = makeStyles()(theme => ({
icon: {
marginTop: theme.spacing(0.5),
color: theme.palette.grey[600],
},
}));

View File

@ -0,0 +1,27 @@
import { VFC } from 'react';
import { Tooltip } from '@mui/material';
import { getFeatureTypeIcons } from 'utils/getFeatureTypeIcons';
import useFeatureTypes from 'hooks/api/getters/useFeatureTypes/useFeatureTypes';
import { useStyles } from './FeatureTypeCell.styles';
interface IFeatureTypeProps {
value?: string;
}
export const FeatureTypeCell: VFC<IFeatureTypeProps> = ({ value }) => {
const { classes: styles } = useStyles();
const { featureTypes } = useFeatureTypes();
const IconComponent = getFeatureTypeIcons(value);
const typeName = featureTypes
.filter(type => type.id === value)
.map(type => type.name);
const title = `This is a "${typeName || value}" toggle`;
return (
<Tooltip arrow placement="right" title={title}>
<IconComponent data-loading className={styles.icon} />
</Tooltip>
);
};

View File

@ -0,0 +1,20 @@
import { VFC } from 'react';
import { Link as RouterLink } from 'react-router-dom';
import { Link } from '@mui/material';
import { Highlighter } from 'component/common/Highlighter/Highlighter';
import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
interface ILinkCellProps {
to: string;
children?: string;
}
export const LinkCell: VFC<ILinkCellProps> = ({ children, to }) => {
const search = useSearchHighlightContext();
return (
<Link component={RouterLink} to={to} data-loading underline="hover">
<Highlighter search={search}>{children}</Highlighter>
</Link>
);
};

View File

@ -76,6 +76,9 @@ const useFeatureToggLeProjectSort = createGlobalStateHook<ISortedState>(
{ field: 'name', type: 'string', direction: 0 }
);
/**
* @deprecated
*/
const FeatureToggleListNew = ({
features,
loading,

View File

@ -7,7 +7,7 @@ exports[`FeedbackCESForm 1`] = `
class="tss-fdcp7c-container"
>
<h1
class="tss-16bfuiy-title"
class="tss-1a5bydb-title"
>
Please help us improve
</h1>
@ -24,12 +24,12 @@ exports[`FeedbackCESForm 1`] = `
class="tss-io6e1g-scoreInput"
>
<span
class="tss-lys30y-scoreHelp"
class="tss-b4a690-scoreHelp"
>
Very difficult
</span>
<label
class="tss-mulaxt-scoreValue"
class="tss-pq8zjr-scoreValue"
>
<input
name="score"
@ -41,7 +41,7 @@ exports[`FeedbackCESForm 1`] = `
</span>
</label>
<label
class="tss-mulaxt-scoreValue"
class="tss-pq8zjr-scoreValue"
>
<input
name="score"
@ -53,7 +53,7 @@ exports[`FeedbackCESForm 1`] = `
</span>
</label>
<label
class="tss-mulaxt-scoreValue"
class="tss-pq8zjr-scoreValue"
>
<input
name="score"
@ -65,7 +65,7 @@ exports[`FeedbackCESForm 1`] = `
</span>
</label>
<label
class="tss-mulaxt-scoreValue"
class="tss-pq8zjr-scoreValue"
>
<input
name="score"
@ -77,7 +77,7 @@ exports[`FeedbackCESForm 1`] = `
</span>
</label>
<label
class="tss-mulaxt-scoreValue"
class="tss-pq8zjr-scoreValue"
>
<input
name="score"
@ -89,7 +89,7 @@ exports[`FeedbackCESForm 1`] = `
</span>
</label>
<label
class="tss-mulaxt-scoreValue"
class="tss-pq8zjr-scoreValue"
>
<input
name="score"
@ -101,7 +101,7 @@ exports[`FeedbackCESForm 1`] = `
</span>
</label>
<label
class="tss-mulaxt-scoreValue"
class="tss-pq8zjr-scoreValue"
>
<input
name="score"
@ -113,7 +113,7 @@ exports[`FeedbackCESForm 1`] = `
</span>
</label>
<span
class="tss-lys30y-scoreHelp"
class="tss-b4a690-scoreHelp"
>
Very easy
</span>
@ -131,7 +131,7 @@ exports[`FeedbackCESForm 1`] = `
class="MuiFormControl-root MuiFormControl-fullWidth MuiTextField-root mui-wb57ya-MuiFormControl-root-MuiTextField-root"
>
<div
class="MuiOutlinedInput-root MuiInputBase-root MuiInputBase-colorPrimary MuiInputBase-fullWidth MuiInputBase-formControl MuiInputBase-multiline mui-opjsp0-MuiInputBase-root-MuiOutlinedInput-root"
class="MuiOutlinedInput-root MuiInputBase-root MuiInputBase-colorPrimary MuiInputBase-fullWidth MuiInputBase-formControl MuiInputBase-multiline mui-1rae96v-MuiInputBase-root-MuiOutlinedInput-root"
>
<textarea
aria-invalid="false"
@ -169,7 +169,7 @@ exports[`FeedbackCESForm 1`] = `
hidden=""
>
<button
class="MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonBase-root Mui-disabled tss-1t8daf-button mui-16l97z4-MuiButtonBase-root-MuiButton-root"
class="MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonBase-root Mui-disabled tss-1t8daf-button mui-1aw3qf3-MuiButtonBase-root-MuiButton-root"
disabled=""
tabindex="-1"
type="submit"

View File

@ -52,7 +52,7 @@ exports[`should render DrawerMenu 1`] = `
className="MuiList-root MuiList-padding MuiList-dense tss-11htu0j-list mui-h4y409-MuiList-root"
>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -71,7 +71,7 @@ exports[`should render DrawerMenu 1`] = `
</div>
</li>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -90,7 +90,7 @@ exports[`should render DrawerMenu 1`] = `
</div>
</li>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -110,7 +110,7 @@ exports[`should render DrawerMenu 1`] = `
</li>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -130,7 +130,7 @@ exports[`should render DrawerMenu 1`] = `
</li>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -149,7 +149,7 @@ exports[`should render DrawerMenu 1`] = `
</div>
</li>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -168,7 +168,7 @@ exports[`should render DrawerMenu 1`] = `
</div>
</li>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -187,7 +187,7 @@ exports[`should render DrawerMenu 1`] = `
</div>
</li>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -223,7 +223,7 @@ exports[`should render DrawerMenu 1`] = `
className="MuiList-root MuiList-padding MuiList-dense tss-11htu0j-list mui-h4y409-MuiList-root"
>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -242,7 +242,7 @@ exports[`should render DrawerMenu 1`] = `
</div>
</li>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -261,7 +261,7 @@ exports[`should render DrawerMenu 1`] = `
</div>
</li>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -280,7 +280,7 @@ exports[`should render DrawerMenu 1`] = `
</div>
</li>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -299,7 +299,7 @@ exports[`should render DrawerMenu 1`] = `
</div>
</li>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -333,7 +333,7 @@ exports[`should render DrawerMenu 1`] = `
className="MuiList-root MuiList-padding MuiList-dense tss-11htu0j-list mui-h4y409-MuiList-root"
>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -352,7 +352,7 @@ exports[`should render DrawerMenu 1`] = `
</div>
</li>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -371,7 +371,7 @@ exports[`should render DrawerMenu 1`] = `
</div>
</li>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -390,7 +390,7 @@ exports[`should render DrawerMenu 1`] = `
</div>
</li>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -409,7 +409,7 @@ exports[`should render DrawerMenu 1`] = `
</div>
</li>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -488,7 +488,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = `
className="MuiList-root MuiList-padding MuiList-dense tss-11htu0j-list mui-h4y409-MuiList-root"
>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -507,7 +507,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = `
</div>
</li>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -526,7 +526,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = `
</div>
</li>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -546,7 +546,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = `
</li>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -566,7 +566,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = `
</li>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -585,7 +585,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = `
</div>
</li>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -604,7 +604,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = `
</div>
</li>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -623,7 +623,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = `
</div>
</li>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -659,7 +659,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = `
className="MuiList-root MuiList-padding MuiList-dense tss-11htu0j-list mui-h4y409-MuiList-root"
>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -678,7 +678,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = `
</div>
</li>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -697,7 +697,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = `
</div>
</li>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -716,7 +716,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = `
</div>
</li>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -735,7 +735,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = `
</div>
</li>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -769,7 +769,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = `
className="MuiList-root MuiList-padding MuiList-dense tss-11htu0j-list mui-h4y409-MuiList-root"
>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -788,7 +788,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = `
</div>
</li>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -807,7 +807,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = `
</div>
</li>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -826,7 +826,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = `
</div>
</li>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div
@ -845,7 +845,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = `
</div>
</li>
<li
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-1vgy6yu-MuiListItem-root"
className="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding tss-1xj02bu-listItem mui-w8p5u6-MuiListItem-root"
disabled={false}
>
<div

View File

@ -12,7 +12,7 @@ Array [
}
>
<div
className="tss-16iyfoa-headerContainer"
className="tss-8bhpw1-headerContainer"
>
<div
className="tss-14s7qul-headerTitleContainer"
@ -22,7 +22,7 @@ Array [
data-loading={true}
>
<h1
className="MuiTypography-root MuiTypography-h1 tss-whbfmi-headerTitle mui-1inkmva-MuiTypography-root"
className="MuiTypography-root MuiTypography-h1 tss-whbfmi-headerTitle mui-ylrecv-MuiTypography-root"
>
Strategies
</h1>
@ -35,7 +35,7 @@ Array [
>
<button
aria-describedby="useId-0"
className="MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonBase-root mui-16l97z4-MuiButtonBase-root-MuiButton-root"
className="MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonBase-root mui-1aw3qf3-MuiButtonBase-root-MuiButton-root"
data-testid="ADD_NEW_STRATEGY_ID"
disabled={false}
onBlur={[Function]}
@ -73,7 +73,7 @@ Array [
className="MuiList-root MuiList-padding mui-h4y409-MuiList-root"
>
<li
className="MuiListItem-root MuiListItem-gutters MuiListItem-padding tss-18csf3q-listItem mui-uz8enf-MuiListItem-root"
className="MuiListItem-root MuiListItem-gutters MuiListItem-padding tss-ynyzms-listItem mui-vlytkl-MuiListItem-root"
disabled={false}
>
<div

View File

@ -11,7 +11,7 @@ exports[`renders an empty list correctly 1`] = `
}
>
<div
className="tss-16iyfoa-headerContainer"
className="tss-8bhpw1-headerContainer"
>
<div
className="tss-14s7qul-headerTitleContainer"
@ -21,7 +21,7 @@ exports[`renders an empty list correctly 1`] = `
data-loading={true}
>
<h1
className="MuiTypography-root MuiTypography-h1 tss-whbfmi-headerTitle mui-1inkmva-MuiTypography-root"
className="MuiTypography-root MuiTypography-h1 tss-whbfmi-headerTitle mui-ylrecv-MuiTypography-root"
>
Tag Types
</h1>
@ -30,7 +30,7 @@ exports[`renders an empty list correctly 1`] = `
className="tss-ap2nhp-headerActions"
>
<button
className="MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonBase-root mui-16l97z4-MuiButtonBase-root-MuiButton-root"
className="MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonBase-root mui-1aw3qf3-MuiButtonBase-root-MuiButton-root"
disabled={false}
onBlur={[Function]}
onClick={[Function]}
@ -60,7 +60,7 @@ exports[`renders an empty list correctly 1`] = `
className="MuiList-root MuiList-padding mui-h4y409-MuiList-root"
>
<li
className="MuiListItem-root MuiListItem-gutters MuiListItem-padding mui-uz8enf-MuiListItem-root"
className="MuiListItem-root MuiListItem-gutters MuiListItem-padding mui-vlytkl-MuiListItem-root"
disabled={false}
>
No entries

View File

@ -1,6 +1,9 @@
import { useEffect, useState } from 'react';
import { paginate } from 'utils/paginate';
/**
* @deprecated
*/
const usePagination = <T>(
data: T[],
limit: number,

View File

@ -15,6 +15,9 @@ import {
import { ReportingSortType } from 'component/Reporting/constants';
import { IFeatureToggleListItem } from 'interfaces/featureToggle';
/**
* @deprecated
*/
const useSort = () => {
const [sortData, setSortData] = useState<{
sortKey: ReportingSortType;

View File

@ -38,6 +38,7 @@ button {
z-index: 9999;
box-shadow: none;
fill: none;
pointer-events: none;
}
.skeleton::before {

View File

@ -0,0 +1,77 @@
/**
* IMPORTANT: Use full color palette ONLY in theme. Don't import colors directly from this file.
*
* @see https://www.figma.com/file/qdwpPfuitJUNinm6mvmCmG/Unleash-application?node-id=7175%3A44590
*/
export const colors = {
grey: {
900: '#202021',
800: '#6E6E70',
700: '#78787A',
600: '#9F9FA1',
500: '#BDBDBF',
400: '#E1E1E3',
300: '#EAEAED',
200: '#F2F2F5',
100: '#F7F7FA',
50: '#FFFFFF',
},
purple: {
900: '#615BC2',
800: '#6C65E5',
700: '#817AFE',
600: '#8C87EB',
500: '#9D98EE',
400: '#ADA9F1',
300: '#BEBBF3',
200: '#CECCF6',
100: '#E4E3F9',
50: '#F1F0FC',
},
red: {
900: '#A6000E',
800: '#D11525',
700: '#D93644',
500: '#E04C59',
400: '#F0616D',
300: '#FEB0B7',
200: '#FFD4D8',
100: '#FFE5E7',
50: '#FFF2F3',
},
orange: {
900: '#B35300',
800: '#D76500',
700: '#F28D00',
600: '#FFAA33',
500: '#FFC46F',
400: '#FFCF8B',
300: '#FFD9A3',
200: '#FFEACC',
100: '#FFF4E5',
50: '#FFFCF5',
},
green: {
900: '#305200',
800: '#3B6600',
700: '#4D8400',
500: '#68A611',
400: '#7FB435',
300: '#99C35D',
200: '#CFE5AE',
100: '#E4F0D3',
50: '#F4FAEB',
},
blue: {
900: '#163E59',
800: '#0060A1',
700: '#0071BD',
600: '#007ACA',
500: '#0087E0',
400: '#1C98EB',
300: '#5BB4F0',
200: '#96D2FA',
100: '#DCEEFA',
50: '#EBF7FF',
},
} as const;

View File

@ -1,4 +1,5 @@
import { createTheme } from '@mui/material/styles';
import { colors } from './colors';
export default createTheme({
borderRadius: {
@ -13,6 +14,10 @@ export default createTheme({
fontWeightMedium: '700',
allVariants: { lineHeight: 1.4 },
button: { lineHeight: 1.75 },
h1: {
fontSize: '1.5rem',
lineHeight: 1.875,
},
},
fontSizes: {
mainHeader: '1.2rem',
@ -37,15 +42,16 @@ export default createTheme({
},
palette: {
primary: {
main: '#635DC5',
light: '#817AFE',
dark: '#635DC5',
main: colors.purple[800],
light: colors.purple[700],
dark: colors.purple[900],
},
secondary: {
main: '#635DC5',
light: '#817AFE',
dark: '#635DC5',
main: colors.purple[800],
light: colors.purple[700],
dark: colors.purple[900],
},
grey: colors.grey,
neutral: {
main: '#18243e',
},
@ -61,11 +67,20 @@ export default createTheme({
tabs: {
main: '#efefef',
},
error: {
main: '#d95e5e',
},
success: {
main: '#3bd86e',
light: colors.green[100],
main: colors.green[700],
dark: colors.green[800],
},
warning: {
light: colors.orange[200],
main: colors.orange[700],
dark: colors.orange[800],
},
error: {
light: colors.red[200],
main: colors.red[700],
dark: colors.red[800],
},
division: {
main: '#f1f1f1',
@ -74,4 +89,13 @@ export default createTheme({
main: '#000',
},
},
components: {
MuiTableHead: {
styleOverrides: {
root: {
background: colors.grey[200],
},
},
},
},
});

View File

@ -1,9 +0,0 @@
export const unleashGrey = {
'100': '#f7f7fa',
'200': '#f2f2f5',
'300': '#eaeaed',
'400': '#e1e1e3',
'500': '#bdbdbf',
'700': '#78787a',
'900': '#202021',
};

View File

@ -0,0 +1,134 @@
/**
* remove after `react-table` v8
* https://github.com/TanStack/react-table/issues/2970#issuecomment-756364081
*/
import {
UseColumnOrderInstanceProps,
UseColumnOrderState,
UseExpandedHooks,
UseExpandedInstanceProps,
UseExpandedOptions,
UseExpandedRowProps,
UseExpandedState,
UseFiltersColumnOptions,
UseFiltersColumnProps,
UseFiltersInstanceProps,
UseFiltersOptions,
UseFiltersState,
UseGlobalFiltersColumnOptions,
UseGlobalFiltersInstanceProps,
UseGlobalFiltersOptions,
UseGlobalFiltersState,
UseGroupByCellProps,
UseGroupByColumnOptions,
UseGroupByColumnProps,
UseGroupByHooks,
UseGroupByInstanceProps,
UseGroupByOptions,
UseGroupByRowProps,
UseGroupByState,
UsePaginationInstanceProps,
UsePaginationOptions,
UsePaginationState,
UseResizeColumnsColumnOptions,
UseResizeColumnsColumnProps,
UseResizeColumnsOptions,
UseResizeColumnsState,
UseRowSelectHooks,
UseRowSelectInstanceProps,
UseRowSelectOptions,
UseRowSelectRowProps,
UseRowSelectState,
UseRowStateCellProps,
UseRowStateInstanceProps,
UseRowStateOptions,
UseRowStateRowProps,
UseRowStateState,
UseSortByColumnOptions,
UseSortByColumnProps,
UseSortByHooks,
UseSortByInstanceProps,
UseSortByOptions,
UseSortByState,
} from 'react-table';
declare module 'react-table' {
// take this file as-is, or comment out the sections that don't apply to your plugin configuration
export interface TableOptions<
D extends Record<string, unknown>
> extends UseExpandedOptions<D>,
UseFiltersOptions<D>,
UseGlobalFiltersOptions<D>,
UseGroupByOptions<D>,
UsePaginationOptions<D>,
UseResizeColumnsOptions<D>,
UseRowSelectOptions<D>,
UseRowStateOptions<D>,
UseSortByOptions<D>,
// note that having Record here allows you to add anything to the options, this matches the spirit of the
// underlying js library, but might be cleaner if it's replaced by a more specific type that matches your
// feature set, this is a safe default.
Record<string, any> {}
export interface Hooks<
D extends Record<string, unknown> = Record<string, unknown>
> extends UseExpandedHooks<D>,
UseGroupByHooks<D>,
UseRowSelectHooks<D>,
UseSortByHooks<D> {}
export interface TableInstance<
D extends Record<string, unknown> = Record<string, unknown>
> extends UseColumnOrderInstanceProps<D>,
UseExpandedInstanceProps<D>,
UseFiltersInstanceProps<D>,
UseGlobalFiltersInstanceProps<D>,
UseGroupByInstanceProps<D>,
UsePaginationInstanceProps<D>,
UseRowSelectInstanceProps<D>,
UseRowStateInstanceProps<D>,
UseSortByInstanceProps<D> {}
export interface TableState<
D extends Record<string, unknown> = Record<string, unknown>
> extends UseColumnOrderState<D>,
UseExpandedState<D>,
UseFiltersState<D>,
UseGlobalFiltersState<D>,
UseGroupByState<D>,
UsePaginationState<D>,
UseResizeColumnsState<D>,
UseRowSelectState<D>,
UseRowStateState<D>,
UseSortByState<D> {}
export interface ColumnInterface<
D extends Record<string, unknown> = Record<string, unknown>
> extends UseFiltersColumnOptions<D>,
UseGlobalFiltersColumnOptions<D>,
UseGroupByColumnOptions<D>,
UseResizeColumnsColumnOptions<D>,
UseSortByColumnOptions<D> {}
export interface ColumnInstance<
D extends Record<string, unknown> = Record<string, unknown>
> extends UseFiltersColumnProps<D>,
UseGroupByColumnProps<D>,
UseResizeColumnsColumnProps<D>,
UseSortByColumnProps<D> {}
export interface Cell<
D extends Record<string, unknown> = Record<string, unknown>,
V = any
> extends UseGroupByCellProps<D>,
UseRowStateCellProps<D> {}
export interface Row<
D extends Record<string, unknown> = Record<string, unknown>
> extends UseExpandedRowProps<D>,
UseGroupByRowProps<D>,
UseRowSelectRowProps<D>,
UseRowStateRowProps<D> {}
}

View File

@ -12,7 +12,7 @@ import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
import PanToolIcon from '@mui/icons-material/PanTool';
import BuildIcon from '@mui/icons-material/Build';
export const getFeatureTypeIcons = (type: string) => {
export const getFeatureTypeIcons = (type?: string) => {
switch (type) {
case RELEASE:
return LoopIcon;

View File

@ -2222,6 +2222,11 @@
dependencies:
"@types/node" "*"
"@types/history@^4.7.11":
version "4.7.11"
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64"
integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==
"@types/html-minifier-terser@^6.0.0":
version "6.1.0"
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35"
@ -2367,6 +2372,30 @@
dependencies:
"@types/react" "*"
"@types/react-router-dom@5.3.3":
version "5.3.3"
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83"
integrity sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==
dependencies:
"@types/history" "^4.7.11"
"@types/react" "*"
"@types/react-router" "*"
"@types/react-router@*":
version "5.1.18"
resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.18.tgz#c8851884b60bc23733500d86c1266e1cfbbd9ef3"
integrity sha512-YYknwy0D0iOwKQgz9v8nOzt2J6l4gouBmDnWqUUznltOTaon+r8US8ky8HvN0tXvc38U9m6z/t2RsVsnd1zM0g==
dependencies:
"@types/history" "^4.7.11"
"@types/react" "*"
"@types/react-table@^7.7.11":
version "7.7.11"
resolved "https://registry.yarnpkg.com/@types/react-table/-/react-table-7.7.11.tgz#0efbb69aabf5b4b9c26c4c027b1e1ceb0f342303"
integrity sha512-Ntfr4EMWgqf/m/CxfmiHww5HvE1nOfK3yEm3NJ3ZWv9IkdteqTOklG3rJtFCtICKAkr3q5pqajkm0y1+WnmdbA==
dependencies:
"@types/react" "*"
"@types/react-test-renderer@17.0.2":
version "17.0.2"
resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-17.0.2.tgz#5f800a39b12ac8d2a2149e7e1885215bcf4edbbf"
@ -8613,6 +8642,11 @@ react-shallow-renderer@^16.13.1:
object-assign "^4.1.1"
react-is "^16.12.0 || ^17.0.0 || ^18.0.0"
react-table@^7.7.0:
version "7.7.0"
resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.7.0.tgz#e2ce14d7fe3a559f7444e9ecfe8231ea8373f912"
integrity sha512-jBlj70iBwOTvvImsU9t01LjFjy4sXEtclBovl3mTiqjz23Reu0DKnRza4zlLtOPACx6j2/7MrQIthIK1Wi+LIA==
react-test-renderer@17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-17.0.2.tgz#4cd4ae5ef1ad5670fc0ef776e8cc7e1231d9866c"