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

Refactored feature toggles table (#951)

* refactor: simplify table toolbar

* refactor: table links and padding

* fix: header icons colors

* fix: minor table style changes
This commit is contained in:
Tymoteusz Czech 2022-05-06 12:21:31 +02:00 committed by GitHub
parent 7785e2c717
commit 5ecc83f1b4
34 changed files with 600 additions and 220 deletions

View File

@ -4,6 +4,9 @@ export const useStyles = makeStyles()(theme => ({
tableCellHeaderSortable: { tableCellHeaderSortable: {
padding: 0, padding: 0,
position: 'relative', position: 'relative',
'&:hover, &:focus': {
backgroundColor: theme.palette.grey[400],
},
}, },
sortButton: { sortButton: {
all: 'unset', all: 'unset',
@ -15,9 +18,6 @@ export const useStyles = makeStyles()(theme => ({
}, },
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
'&:hover': {
backgroundColor: theme.palette.grey[400],
},
boxSizing: 'inherit', boxSizing: 'inherit',
cursor: 'pointer', cursor: 'pointer',
}, },

View File

@ -14,8 +14,4 @@ export const useStyles = makeStyles()(theme => ({
}, },
}, },
}, },
icon: {
marginLeft: theme.spacing(0.5),
fontSize: 18,
},
})); }));

View File

@ -0,0 +1,8 @@
import { FC } from 'react';
import { Box, Table as MUITable } from '@mui/material';
export const Table: FC = ({ children }) => (
<Box sx={{ p: 4, pb: 0 }}>
<MUITable>{children}</MUITable>
</Box>
);

View File

@ -14,6 +14,9 @@ interface ITableActionsProps {
isSeparated?: boolean; isSeparated?: boolean;
} }
/**
* @deprecated
*/
export const TableActions: FC<ITableActionsProps> = ({ export const TableActions: FC<ITableActionsProps> = ({
initialSearchValue: search, initialSearchValue: search,
onSearch = () => {}, onSearch = () => {},

View File

@ -0,0 +1,7 @@
import { makeStyles } from 'tss-react/mui';
export const useStyles = makeStyles()(theme => ({
tableCell: {
padding: 0,
},
}));

View File

@ -0,0 +1,9 @@
import { FC } from 'react';
import { TableCell as MUITableCell, TableCellProps } from '@mui/material';
import { useStyles } from './TableCell.styles';
export const TableCell: FC<TableCellProps> = ({ ...props }) => {
const { classes: styles } = useStyles();
return <MUITableCell className={styles.tableCell} {...props} />;
};

View File

@ -7,8 +7,4 @@ export const useStyles = makeStyles()(theme => ({
borderRadius: theme.spacing(1.5), borderRadius: theme.spacing(1.5),
paddingBottom: theme.spacing(4), paddingBottom: theme.spacing(4),
}, },
content: {
padding: theme.spacing(4),
paddingBottom: 0,
},
})); }));

View File

@ -0,0 +1,15 @@
import { forwardRef } from 'react';
import { Paper, PaperProps } from '@mui/material';
import { useStyles } from './TableContainer.styles';
export const TableContainer = forwardRef<HTMLDivElement, PaperProps>(
({ children, ...props }, ref) => {
const { classes } = useStyles();
return (
<Paper ref={ref} className={classes.panel} {...props}>
{children}
</Paper>
);
}
);

View File

@ -1,18 +0,0 @@
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,7 +8,6 @@ export const useStyles = makeStyles()(theme => ({
display: 'flex', display: 'flex',
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center', alignItems: 'center',
marginTop: theme.spacing(4), margin: theme.spacing(4),
marginBottom: theme.spacing(4),
}, },
})); }));

View File

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

View File

@ -0,0 +1,59 @@
import { makeStyles } from 'tss-react/mui';
export const useStyles = makeStyles()(theme => ({
searchField: {
width: '45px',
'& .search-icon': {
marginRight: 0,
},
'& .input-container, .clear-container': {
width: 0,
},
'& input::placeholder': {
color: 'transparent',
transition: 'color 0.6s',
},
'& input:focus-within::placeholder': {
color: theme.palette.text.primary,
},
},
searchFieldEnter: {
width: '250px',
transition: 'width 0.6s',
'& .search-icon': {
marginRight: '8px',
},
'& .input-container': {
width: '100%',
transition: 'width 0.6s',
},
'& .clear-container': {
width: '30px',
transition: 'width 0.6s',
},
'& .search-container': {
borderColor: theme.palette.grey[300],
},
},
searchFieldLeave: {
width: '45px',
transition: 'width 0.6s',
'& .search-icon': {
marginRight: 0,
transition: 'margin-right 0.6s',
},
'& .input-container, .clear-container': {
width: 0,
transition: 'width 0.6s',
},
'& .search-container': {
borderColor: 'transparent',
},
},
searchButton: {
marginTop: '-4px',
marginBottom: '-4px',
marginRight: '-4px',
marginLeft: '-4px',
},
}));

View File

@ -0,0 +1,75 @@
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 './TableSearchField/TableSearchField';
import { useStyles } from './TableSearch.styles';
interface ITableSearchProps {
initialValue?: string;
onChange?: (value: string) => void;
placeholder?: string;
}
export const TableSearch: FC<ITableSearchProps> = ({
initialValue,
onChange = () => {},
placeholder = 'Search',
}) => {
const [searchInputState, setSearchInputState] = useState(initialValue);
const [isSearchExpanded, setIsSearchExpanded] = useState(
Boolean(initialValue)
);
const [isAnimating, setIsAnimating] = useState(false);
const debouncedOnSearch = useAsyncDebounce(onChange, 200);
const { classes: styles } = useStyles();
const onBlur = (clear = false) => {
if (!searchInputState || clear) {
setIsSearchExpanded(false);
}
};
const onSearchChange = (value: string) => {
debouncedOnSearch(value);
setSearchInputState(value);
};
return (
<>
<AnimateOnMount
mounted={isSearchExpanded}
start={styles.searchField}
enter={styles.searchFieldEnter}
leave={styles.searchFieldLeave}
onStart={() => setIsAnimating(true)}
onEnd={() => setIsAnimating(false)}
>
<TableSearchField
value={searchInputState!}
onChange={onSearchChange}
placeholder={`${placeholder}`}
onBlur={onBlur}
/>
</AnimateOnMount>
<ConditionallyRender
condition={!isSearchExpanded && !isAnimating}
show={
<Tooltip title={placeholder} arrow>
<IconButton
aria-label={placeholder}
onClick={() => setIsSearchExpanded(true)}
size="large"
className={styles.searchButton}
>
<Search />
</IconButton>
</Tooltip>
}
/>
</>
);
};

View File

@ -0,0 +1,43 @@
import { makeStyles } from 'tss-react/mui';
export const useStyles = makeStyles()(theme => ({
container: {
display: 'flex',
alignItems: 'center',
flexWrap: 'wrap',
gap: '1rem',
},
search: {
display: 'flex',
alignItems: 'center',
backgroundColor: theme.palette.background.paper,
border: `1px solid ${theme.palette.grey[300]}`,
borderRadius: '25px',
padding: '3px 5px 3px 12px',
maxWidth: '450px',
[theme.breakpoints.down('sm')]: {
width: '100%',
},
'&.search-container:focus-within': {
borderColor: theme.palette.primary.light,
boxShadow: theme.boxShadows.main,
},
},
searchIcon: {
marginRight: 8,
color: theme.palette.grey[600],
},
clearContainer: {
width: '30px',
'& > button': {
padding: '7px',
},
},
clearIcon: {
color: theme.palette.grey[600],
fontSize: '18px',
},
inputRoot: {
width: '100%',
},
}));

View File

@ -0,0 +1,73 @@
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';
interface ITableSearchFieldProps {
value: string;
onChange: (value: string) => void;
className?: string;
placeholder: string;
onBlur?: (clear?: boolean) => void;
}
export const TableSearchField = ({
value = '',
onChange,
className,
placeholder,
onBlur,
}: ITableSearchFieldProps) => {
const { classes: styles } = useStyles();
return (
<div className={styles.container}>
<div
className={classnames(
styles.search,
className,
'search-container'
)}
>
<Search
className={classnames(styles.searchIcon, 'search-icon')}
/>
<InputBase
autoFocus
placeholder={placeholder}
classes={{
root: classnames(styles.inputRoot, 'input-container'),
}}
inputProps={{ 'aria-label': placeholder }}
value={value}
onChange={e => onChange(e.target.value)}
onBlur={() => onBlur?.()}
/>
<div
className={classnames(
styles.clearContainer,
'clear-container'
)}
>
<ConditionallyRender
condition={Boolean(value)}
show={
<Tooltip title="Clear search query" arrow>
<IconButton
size="small"
onClick={() => {
onChange('');
onBlur?.(true);
}}
>
<Close className={styles.clearIcon} />
</IconButton>
</Tooltip>
}
/>
</div>
</div>
</div>
);
};

View File

@ -1,12 +1,30 @@
import { makeStyles } from 'tss-react/mui'; import { makeStyles } from 'tss-react/mui';
export const useStyles = makeStyles()(theme => ({ export const useStyles = makeStyles()(theme => ({
root: { toolbar: {
paddingLeft: theme.spacing(4), paddingLeft: theme.spacing(4),
paddingRight: theme.spacing(4), paddingRight: theme.spacing(4),
paddingTop: theme.spacing(2.5), paddingTop: theme.spacing(2),
paddingBottom: theme.spacing(2.5), paddingBottom: theme.spacing(2),
borderBottom: `1px solid ${theme.palette.divider}`, borderBottom: `1px solid ${theme.palette.divider}`,
justifyContent: 'space-between', display: 'flex',
flexWrap: 'wrap',
alignItems: 'center',
},
actions: {
flex: 1,
display: 'flex',
justifyContent: 'flex-end',
alignItems: 'center',
},
verticalSeparator: {
height: '100%',
backgroundColor: theme.palette.grey[500],
width: '1px',
display: 'inline-block',
marginLeft: theme.spacing(2),
marginRight: theme.spacing(4),
padding: '10px 0',
verticalAlign: 'middle',
}, },
})); }));

View File

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

View File

@ -0,0 +1,8 @@
export { TableContainer } from './TableContainer/TableContainer';
export { TableToolbar } from './TableToolbar/TableToolbar';
export { TableSearch } from './TableSearch/TableSearch';
export { Table } from './Table';
export { SortableTableHeader } from './SortableTableHeader/SortableTableHeader';
export { TableBody, TableRow } from '@mui/material';
export { TableCell } from './TableCell/TableCell';
export { TablePlaceholder } from './TablePlaceholder/TablePlaceholder';

View File

@ -2,7 +2,7 @@ import { useFeatures } from 'hooks/api/getters/useFeatures/useFeatures';
import { FeatureSchema } from 'openapi'; import { FeatureSchema } from 'openapi';
import { FeatureToggleListTable } from './FeatureToggleListTable/FeatureToggleListTable'; import { FeatureToggleListTable } from './FeatureToggleListTable/FeatureToggleListTable';
const featuresPlaceholder: FeatureSchema[] = Array(7).fill({ const featuresPlaceholder: FeatureSchema[] = Array(15).fill({
name: 'Name of the feature', name: 'Name of the feature',
description: 'Short description of the feature', description: 'Short description of the feature',
type: '-', type: '-',

View File

@ -2,7 +2,7 @@ import { VFC } from 'react';
import { useLocationSettings } from 'hooks/useLocationSettings'; import { useLocationSettings } from 'hooks/useLocationSettings';
import { formatDateYMD, formatDateYMDHMS } from 'utils/formatDate'; import { formatDateYMD, formatDateYMDHMS } from 'utils/formatDate';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { Tooltip } from '@mui/material'; import { Box, Tooltip } from '@mui/material';
interface IDateCellProps { interface IDateCellProps {
value?: Date | null; value?: Date | null;
@ -12,21 +12,26 @@ export const DateCell: VFC<IDateCellProps> = ({ value }) => {
const { locationSettings } = useLocationSettings(); const { locationSettings } = useLocationSettings();
return ( return (
<ConditionallyRender <Box sx={{ py: 1.5, px: 2 }}>
condition={Boolean(value)} <ConditionallyRender
show={ condition={Boolean(value)}
<Tooltip show={
title={formatDateYMDHMS( <Tooltip
value as Date, title={formatDateYMDHMS(
locationSettings.locale value as Date,
)} locationSettings.locale
arrow )}
> arrow
<span data-loading> >
{formatDateYMD(value as Date, locationSettings.locale)} <span data-loading role="tooltip">
</span> {formatDateYMD(
</Tooltip> value as Date,
} locationSettings.locale
/> )}
</span>
</Tooltip>
}
/>
</Box>
); );
}; };

View File

@ -1,6 +1,15 @@
import { makeStyles } from 'tss-react/mui'; import { makeStyles } from 'tss-react/mui';
export const useStyles = makeStyles()(theme => ({ export const useStyles = makeStyles()(theme => ({
container: {
paddingTop: theme.spacing(1.5),
paddingBottom: theme.spacing(1.5),
paddingLeft: theme.spacing(2),
paddingRight: theme.spacing(2),
display: 'flex',
alignItems: 'center',
minHeight: '62px',
},
description: { description: {
color: theme.palette.grey[800], color: theme.palette.grey[800],
fontSize: 'inherit', fontSize: 'inherit',

View File

@ -0,0 +1,55 @@
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 './FeatureLinkCell.styles';
import { Highlighter } from 'component/common/Highlighter/Highlighter';
import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
interface IFeatureLinkCellProps {
title?: string;
to: string;
subtitle?: string;
}
export const FeatureLinkCell: FC<IFeatureLinkCellProps> = ({
title,
to,
subtitle,
}) => {
const { classes: styles } = useStyles();
const search = useSearchHighlightContext();
return (
<Link
component={RouterLink}
to={to}
underline="hover"
className={styles.container}
>
<div>
<span data-loading>
<Highlighter search={search}>{title}</Highlighter>
</span>
<ConditionallyRender
condition={Boolean(subtitle)}
show={
<>
<br />
<Typography
className={styles.description}
component="span"
data-loading
>
<Highlighter search={search}>
{subtitle}
</Highlighter>
</Typography>
</>
}
/>
</div>
</Link>
);
};

View File

@ -1,53 +0,0 @@
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

@ -2,6 +2,10 @@ import { makeStyles } from 'tss-react/mui';
export const useStyles = makeStyles()(theme => ({ export const useStyles = makeStyles()(theme => ({
container: { container: {
display: 'flex',
padding: theme.spacing(1.5),
},
box: {
width: '38px', width: '38px',
height: '38px', height: '38px',
background: 'gray', background: 'gray',

View File

@ -63,15 +63,18 @@ const Wrapper: FC<{ unit?: string; tooltip: string }> = ({
const getColor = useFeatureColor(); const getColor = useFeatureColor();
return ( return (
<Tooltip title={tooltip} arrow> <div className={styles.container}>
<div <Tooltip title={tooltip} arrow describeChild>
className={styles.container} <div
style={{ background: getColor(unit) }} role="tooltip"
data-loading className={styles.box}
> style={{ background: getColor(unit) }}
{children} data-loading
</div> >
</Tooltip> {children}
</div>
</Tooltip>
</div>
); );
}; };

View File

@ -1,5 +1,5 @@
import { VFC } from 'react'; import { VFC } from 'react';
import { Typography } from '@mui/material'; import { Box, Typography } from '@mui/material';
import { useStyles } from './FeatureStaleCell.styles'; import { useStyles } from './FeatureStaleCell.styles';
import classnames from 'classnames'; import classnames from 'classnames';
@ -10,12 +10,14 @@ interface IFeatureStaleCellProps {
export const FeatureStaleCell: VFC<IFeatureStaleCellProps> = ({ value }) => { export const FeatureStaleCell: VFC<IFeatureStaleCellProps> = ({ value }) => {
const { classes: styles } = useStyles(); const { classes: styles } = useStyles();
return ( return (
<Typography <Box sx={{ py: 1.5, px: 2 }}>
component="span" <Typography
className={classnames(styles.status, value && styles.stale)} component="span"
data-loading className={classnames(styles.status, value && styles.stale)}
> data-loading
{value ? 'Stale' : 'Active'} >
</Typography> {value ? 'Stale' : 'Active'}
</Typography>
</Box>
); );
}; };

View File

@ -1,28 +1,25 @@
import { useEffect, useMemo, VFC } from 'react'; import { useEffect, useMemo, VFC } from 'react';
import { import { Link, useMediaQuery, useTheme } from '@mui/material';
Link,
Table,
TableBody,
TableCell,
TableRow,
useMediaQuery,
useTheme,
} from '@mui/material';
import { Link as RouterLink } from 'react-router-dom'; import { Link as RouterLink } from 'react-router-dom';
import { useGlobalFilter, useSortBy, useTable } from 'react-table'; import { useGlobalFilter, useSortBy, useTable } from 'react-table';
import useLoading from 'hooks/useLoading'; import useLoading from 'hooks/useLoading';
import { SortableTableHeader } from 'component/common/Table/SortableTableHeader/SortableTableHeader'; import {
import { TableActions } from 'component/common/Table/TableActions/TableActions'; TableContainer,
import { TablePanel } from 'component/common/Table/TablePanel/TablePanel'; TableToolbar,
import { TableToolbar } from 'component/common/Table/TableToolbar/TableToolbar'; Table,
import TablePlaceholder from 'component/common/Table/TablePlaceholder/TablePlaceholder'; SortableTableHeader,
TableBody,
TableCell,
TableRow,
TablePlaceholder,
TableSearch,
} from 'component/common/Table';
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
import { DateCell } from './DateCell/DateCell'; import { DateCell } from './DateCell/DateCell';
import { FeatureNameCell } from './FeatureNameCell/FeatureNameCell'; import { FeatureLinkCell } from './FeatureLinkCell/FeatureLinkCell';
import { FeatureSeenCell } from './FeatureSeenCell/FeatureSeenCell'; import { FeatureSeenCell } from './FeatureSeenCell/FeatureSeenCell';
import { FeatureStaleCell } from './FeatureStaleCell/FeatureStaleCell'; import { FeatureStaleCell } from './FeatureStaleCell/FeatureStaleCell';
import { FeatureTypeCell } from './FeatureTypeCell/FeatureTypeCell'; import { FeatureTypeCell } from './FeatureTypeCell/FeatureTypeCell';
import { LinkCell } from './LinkCell/LinkCell';
import { CreateFeatureButton } from '../../CreateFeatureButton/CreateFeatureButton'; import { CreateFeatureButton } from '../../CreateFeatureButton/CreateFeatureButton';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
@ -51,17 +48,29 @@ const columns = [
accessor: 'lastSeenAt', accessor: 'lastSeenAt',
Cell: FeatureSeenCell, Cell: FeatureSeenCell,
sortType: 'date', sortType: 'date',
totalWidth: 120,
}, },
{ {
Header: 'Type', Header: 'Type',
accessor: 'type', accessor: 'type',
Cell: FeatureTypeCell, Cell: FeatureTypeCell,
totalWidth: 120,
}, },
{ {
Header: 'Feature toggle name', Header: 'Feature toggle name',
accessor: 'name', accessor: 'name',
// @ts-expect-error // TODO: props type Cell: ({
Cell: ({ row: { original } }) => <FeatureNameCell {...original} />, row: {
// @ts-expect-error -- props type
original: { name, description, project },
},
}) => (
<FeatureLinkCell
title={name}
subtitle={description}
to={`/projects/${project}/features/${name}`}
/>
),
sortType: 'alphanumeric', sortType: 'alphanumeric',
}, },
{ {
@ -74,7 +83,7 @@ const columns = [
Header: 'Project ID', Header: 'Project ID',
accessor: 'project', accessor: 'project',
Cell: ({ value }: { value: string }) => ( Cell: ({ value }: { value: string }) => (
<LinkCell to={`/projects/${value}`}>{value}</LinkCell> <FeatureLinkCell title={value} to={`/projects/${value}`} />
), ),
sortType: 'alphanumeric', sortType: 'alphanumeric',
}, },
@ -123,6 +132,7 @@ export const FeatureToggleListTable: VFC<IExperimentProps> = ({
initialState, initialState,
sortTypes, sortTypes,
autoResetGlobalFilter: false, autoResetGlobalFilter: false,
disableSortRemove: true,
}, },
useGlobalFilter, useGlobalFilter,
useSortBy useSortBy
@ -130,7 +140,13 @@ export const FeatureToggleListTable: VFC<IExperimentProps> = ({
useEffect(() => { useEffect(() => {
if (isSmallScreen) { if (isSmallScreen) {
setHiddenColumns(['lastSeenAt', 'type', 'stale', 'description']); setHiddenColumns([
'lastSeenAt',
'type',
'stale',
'description',
'createdAt',
]);
} else if (isMediumScreen) { } else if (isMediumScreen) {
setHiddenColumns(['lastSeenAt', 'stale', 'description']); setHiddenColumns(['lastSeenAt', 'stale', 'description']);
} else { } else {
@ -139,27 +155,26 @@ export const FeatureToggleListTable: VFC<IExperimentProps> = ({
}, [setHiddenColumns, isSmallScreen, isMediumScreen]); }, [setHiddenColumns, isSmallScreen, isMediumScreen]);
return ( return (
<TablePanel <TableContainer ref={ref}>
ref={ref} <TableToolbar title={`Feature toggles (${data.length})`}>
header={ <TableSearch
<TableToolbar title={`Feature Flags (${data.length})`}> initialValue={globalFilter}
<TableActions isSeparated onSearch={setGlobalFilter}> onChange={setGlobalFilter}
<Link />
component={RouterLink} <TableToolbar.Divider />
to="/archive" <Link
underline="always" component={RouterLink}
sx={{ marginRight: 3 }} to="/archive"
> underline="always"
View archive sx={{ marginRight: 3 }}
</Link> >
<CreateFeatureButton View archive
loading={false} </Link>
filter={{ query: '', project: 'default' }} <CreateFeatureButton
/> loading={false}
</TableActions> filter={{ query: '', project: 'default' }}
</TableToolbar> />
} </TableToolbar>
>
<SearchHighlightProvider value={globalFilter}> <SearchHighlightProvider value={globalFilter}>
<Table {...getTableProps()}> <Table {...getTableProps()}>
<SortableTableHeader headerGroups={headerGroups} /> <SortableTableHeader headerGroups={headerGroups} />
@ -182,12 +197,24 @@ export const FeatureToggleListTable: VFC<IExperimentProps> = ({
<ConditionallyRender <ConditionallyRender
condition={rows.length === 0} condition={rows.length === 0}
show={ show={
<TablePlaceholder> <ConditionallyRender
No features available. Get started by adding a new condition={globalFilter?.length > 0}
feature toggle. show={
</TablePlaceholder> <TablePlaceholder>
No features or projects found matching &ldquo;
{globalFilter}
&rdquo;
</TablePlaceholder>
}
elseShow={
<TablePlaceholder>
No features available. Get started by adding a
new feature toggle.
</TablePlaceholder>
}
/>
} }
/> />
</TablePanel> </TableContainer>
); );
}; };

View File

@ -1,6 +1,12 @@
import { makeStyles } from 'tss-react/mui'; import { makeStyles } from 'tss-react/mui';
export const useStyles = makeStyles()(theme => ({ export const useStyles = makeStyles()(theme => ({
container: {
padding: theme.spacing(1.5),
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
},
icon: { icon: {
marginTop: theme.spacing(0.5), marginTop: theme.spacing(0.5),
color: theme.palette.grey[600], color: theme.palette.grey[600],

View File

@ -20,8 +20,10 @@ export const FeatureTypeCell: VFC<IFeatureTypeProps> = ({ value }) => {
const title = `This is a "${typeName || value}" toggle`; const title = `This is a "${typeName || value}" toggle`;
return ( return (
<Tooltip arrow placement="right" title={title}> <div className={styles.container}>
<IconComponent data-loading className={styles.icon} /> <Tooltip arrow placement="right" title={title} describeChild>
</Tooltip> <IconComponent data-loading className={styles.icon} />
</Tooltip>
</div>
); );
}; };

View File

@ -1,20 +0,0 @@
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

@ -83,7 +83,7 @@ export const useStyles = makeStyles()(theme => ({
color: '#000', color: '#000',
}, },
icon: { icon: {
color: theme.palette.grey[600], color: theme.palette.grey[700],
}, },
wideButton: { wideButton: {
borderRadius: 100, borderRadius: 100,

View File

@ -15,6 +15,6 @@ export const useStyles = makeStyles()(theme => ({
}, },
}, },
icon: { icon: {
color: theme.palette.grey[600], color: theme.palette.grey[700],
}, },
})); }));

View File

@ -51,22 +51,6 @@ export default createTheme({
light: colors.purple[700], light: colors.purple[700],
dark: colors.purple[900], dark: colors.purple[900],
}, },
grey: colors.grey,
neutral: {
main: '#18243e',
},
chips: {
main: '#b0bec5',
},
searchField: {
main: '#fff',
},
iconButtons: {
main: '#fff',
},
tabs: {
main: '#efefef',
},
success: { success: {
light: colors.green[100], light: colors.green[100],
main: colors.green[700], main: colors.green[700],
@ -82,6 +66,22 @@ export default createTheme({
main: colors.red[700], main: colors.red[700],
dark: colors.red[800], dark: colors.red[800],
}, },
grey: colors.grey,
// neutral: {
// main: '#18243e',
// },
chips: {
main: '#b0bec5',
},
searchField: {
main: '#fff',
},
iconButtons: {
main: '#fff',
},
tabs: {
main: '#efefef',
},
division: { division: {
main: '#f1f1f1', main: '#f1f1f1',
}, },
@ -97,5 +97,12 @@ export default createTheme({
}, },
}, },
}, },
MuiLink: {
styleOverrides: {
root: {
color: colors.purple[900],
},
},
},
}, },
}); });

View File

@ -2,6 +2,9 @@ import { SimplePaletteColorOptions } from '@mui/material';
declare module '@mui/material/styles' { declare module '@mui/material/styles' {
interface CustomTheme { interface CustomTheme {
/**
* @deprecated
*/
fontSizes: { fontSizes: {
mainHeader: string; mainHeader: string;
subHeader: string; subHeader: string;
@ -9,12 +12,18 @@ declare module '@mui/material/styles' {
smallBody: string; smallBody: string;
smallerBody: string; smallerBody: string;
}; };
/**
* @deprecated
*/
fontWeight: { fontWeight: {
thin: number; thin: number;
medium: number; medium: number;
semi: number; semi: number;
bold: number; bold: number;
}; };
/**
* @deprecated
*/
code: { code: {
main: string; main: string;
diffAdd: string; diffAdd: string;
@ -23,21 +32,44 @@ declare module '@mui/material/styles' {
edited: string; edited: string;
background: string; background: string;
}; };
/**
* @deprecated
*/
borderRadius: { borderRadius: {
main: string; main: string;
}; };
/**
* @deprecated
*/
boxShadows: { boxShadows: {
main: string; main: string;
}; };
} }
interface CustomPalette { interface CustomPalette {
neutral: SimplePaletteColorOptions; /**
* @deprecated
*/
chips: SimplePaletteColorOptions; chips: SimplePaletteColorOptions;
/**
* @deprecated
*/
searchField: SimplePaletteColorOptions; searchField: SimplePaletteColorOptions;
/**
* @deprecated
*/
iconButtons: SimplePaletteColorOptions; iconButtons: SimplePaletteColorOptions;
/**
* @deprecated
*/
tabs: SimplePaletteColorOptions; tabs: SimplePaletteColorOptions;
/**
* @deprecated
*/
division: SimplePaletteColorOptions; division: SimplePaletteColorOptions;
/**
* @deprecated
*/
footer: SimplePaletteColorOptions; footer: SimplePaletteColorOptions;
} }