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:
parent
7785e2c717
commit
5ecc83f1b4
@ -4,6 +4,9 @@ export const useStyles = makeStyles()(theme => ({
|
||||
tableCellHeaderSortable: {
|
||||
padding: 0,
|
||||
position: 'relative',
|
||||
'&:hover, &:focus': {
|
||||
backgroundColor: theme.palette.grey[400],
|
||||
},
|
||||
},
|
||||
sortButton: {
|
||||
all: 'unset',
|
||||
@ -15,9 +18,6 @@ export const useStyles = makeStyles()(theme => ({
|
||||
},
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.grey[400],
|
||||
},
|
||||
boxSizing: 'inherit',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
|
@ -14,8 +14,4 @@ export const useStyles = makeStyles()(theme => ({
|
||||
},
|
||||
},
|
||||
},
|
||||
icon: {
|
||||
marginLeft: theme.spacing(0.5),
|
||||
fontSize: 18,
|
||||
},
|
||||
}));
|
||||
|
8
frontend/src/component/common/Table/Table.tsx
Normal file
8
frontend/src/component/common/Table/Table.tsx
Normal 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>
|
||||
);
|
@ -14,6 +14,9 @@ interface ITableActionsProps {
|
||||
isSeparated?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export const TableActions: FC<ITableActionsProps> = ({
|
||||
initialSearchValue: search,
|
||||
onSearch = () => {},
|
||||
|
@ -0,0 +1,7 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
tableCell: {
|
||||
padding: 0,
|
||||
},
|
||||
}));
|
@ -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} />;
|
||||
};
|
@ -7,8 +7,4 @@ export const useStyles = makeStyles()(theme => ({
|
||||
borderRadius: theme.spacing(1.5),
|
||||
paddingBottom: theme.spacing(4),
|
||||
},
|
||||
content: {
|
||||
padding: theme.spacing(4),
|
||||
paddingBottom: 0,
|
||||
},
|
||||
}));
|
@ -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>
|
||||
);
|
||||
}
|
||||
);
|
@ -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>
|
||||
);
|
||||
});
|
@ -8,7 +8,6 @@ export const useStyles = makeStyles()(theme => ({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginTop: theme.spacing(4),
|
||||
marginBottom: theme.spacing(4),
|
||||
margin: theme.spacing(4),
|
||||
},
|
||||
}));
|
||||
|
@ -2,10 +2,8 @@ import { FC } from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { useStyles } from 'component/common/Table/TablePlaceholder/TablePlaceholder.styles';
|
||||
|
||||
const TablePlaceholder: FC = ({ children }) => {
|
||||
export const TablePlaceholder: FC = ({ children }) => {
|
||||
const { classes: styles } = useStyles();
|
||||
|
||||
return <Box className={styles.emptyStateListItem}>{children}</Box>;
|
||||
};
|
||||
|
||||
export default TablePlaceholder;
|
||||
|
@ -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',
|
||||
},
|
||||
}));
|
@ -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>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
@ -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%',
|
||||
},
|
||||
}));
|
@ -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>
|
||||
);
|
||||
};
|
@ -1,12 +1,30 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
root: {
|
||||
toolbar: {
|
||||
paddingLeft: theme.spacing(4),
|
||||
paddingRight: theme.spacing(4),
|
||||
paddingTop: theme.spacing(2.5),
|
||||
paddingBottom: theme.spacing(2.5),
|
||||
paddingTop: theme.spacing(2),
|
||||
paddingBottom: theme.spacing(2),
|
||||
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',
|
||||
},
|
||||
}));
|
||||
|
@ -1,20 +1,32 @@
|
||||
import { FC } from 'react';
|
||||
import { Toolbar, Typography } from '@mui/material';
|
||||
import { FC, VFC } from 'react';
|
||||
import { Box, Toolbar, Typography } from '@mui/material';
|
||||
import { useStyles } from './TableToolbar.styles';
|
||||
|
||||
interface ITableToolbarProps {
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const TableToolbar: FC<ITableToolbarProps> = ({ title, children }) => {
|
||||
export const TableToolbarComponent: FC<ITableToolbarProps> & {
|
||||
Divider: VFC;
|
||||
} = ({ title, children }) => {
|
||||
const { classes: styles } = useStyles();
|
||||
|
||||
return (
|
||||
<Toolbar className={styles.root}>
|
||||
<Typography variant="h1" component="h1" data-loading>
|
||||
<Toolbar className={styles.toolbar}>
|
||||
<Typography variant="h1" data-loading>
|
||||
{title}
|
||||
</Typography>
|
||||
{children}
|
||||
<Box className={styles.actions}>{children}</Box>
|
||||
</Toolbar>
|
||||
);
|
||||
};
|
||||
|
||||
const Divider: VFC = () => {
|
||||
const { classes: styles } = useStyles();
|
||||
|
||||
return <Box className={styles.verticalSeparator} />;
|
||||
};
|
||||
|
||||
TableToolbarComponent.Divider = Divider;
|
||||
|
||||
export const TableToolbar = TableToolbarComponent;
|
||||
|
8
frontend/src/component/common/Table/index.ts
Normal file
8
frontend/src/component/common/Table/index.ts
Normal 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';
|
@ -2,7 +2,7 @@ import { useFeatures } from 'hooks/api/getters/useFeatures/useFeatures';
|
||||
import { FeatureSchema } from 'openapi';
|
||||
import { FeatureToggleListTable } from './FeatureToggleListTable/FeatureToggleListTable';
|
||||
|
||||
const featuresPlaceholder: FeatureSchema[] = Array(7).fill({
|
||||
const featuresPlaceholder: FeatureSchema[] = Array(15).fill({
|
||||
name: 'Name of the feature',
|
||||
description: 'Short description of the feature',
|
||||
type: '-',
|
||||
|
@ -2,7 +2,7 @@ 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';
|
||||
import { Box, Tooltip } from '@mui/material';
|
||||
|
||||
interface IDateCellProps {
|
||||
value?: Date | null;
|
||||
@ -12,21 +12,26 @@ 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>
|
||||
}
|
||||
/>
|
||||
<Box sx={{ py: 1.5, px: 2 }}>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(value)}
|
||||
show={
|
||||
<Tooltip
|
||||
title={formatDateYMDHMS(
|
||||
value as Date,
|
||||
locationSettings.locale
|
||||
)}
|
||||
arrow
|
||||
>
|
||||
<span data-loading role="tooltip">
|
||||
{formatDateYMD(
|
||||
value as Date,
|
||||
locationSettings.locale
|
||||
)}
|
||||
</span>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,15 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
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: {
|
||||
color: theme.palette.grey[800],
|
||||
fontSize: 'inherit',
|
@ -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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
@ -2,6 +2,10 @@ import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
container: {
|
||||
display: 'flex',
|
||||
padding: theme.spacing(1.5),
|
||||
},
|
||||
box: {
|
||||
width: '38px',
|
||||
height: '38px',
|
||||
background: 'gray',
|
||||
|
@ -63,15 +63,18 @@ const Wrapper: FC<{ unit?: string; tooltip: string }> = ({
|
||||
const getColor = useFeatureColor();
|
||||
|
||||
return (
|
||||
<Tooltip title={tooltip} arrow>
|
||||
<div
|
||||
className={styles.container}
|
||||
style={{ background: getColor(unit) }}
|
||||
data-loading
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div className={styles.container}>
|
||||
<Tooltip title={tooltip} arrow describeChild>
|
||||
<div
|
||||
role="tooltip"
|
||||
className={styles.box}
|
||||
style={{ background: getColor(unit) }}
|
||||
data-loading
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { VFC } from 'react';
|
||||
import { Typography } from '@mui/material';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import { useStyles } from './FeatureStaleCell.styles';
|
||||
import classnames from 'classnames';
|
||||
|
||||
@ -10,12 +10,14 @@ interface IFeatureStaleCellProps {
|
||||
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>
|
||||
<Box sx={{ py: 1.5, px: 2 }}>
|
||||
<Typography
|
||||
component="span"
|
||||
className={classnames(styles.status, value && styles.stale)}
|
||||
data-loading
|
||||
>
|
||||
{value ? 'Stale' : 'Active'}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@ -1,28 +1,25 @@
|
||||
import { useEffect, useMemo, VFC } from 'react';
|
||||
import {
|
||||
Link,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableRow,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
import { Link, 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 {
|
||||
TableContainer,
|
||||
TableToolbar,
|
||||
Table,
|
||||
SortableTableHeader,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableRow,
|
||||
TablePlaceholder,
|
||||
TableSearch,
|
||||
} from 'component/common/Table';
|
||||
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
||||
import { DateCell } from './DateCell/DateCell';
|
||||
import { FeatureNameCell } from './FeatureNameCell/FeatureNameCell';
|
||||
import { FeatureLinkCell } from './FeatureLinkCell/FeatureLinkCell';
|
||||
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';
|
||||
|
||||
@ -51,17 +48,29 @@ const columns = [
|
||||
accessor: 'lastSeenAt',
|
||||
Cell: FeatureSeenCell,
|
||||
sortType: 'date',
|
||||
totalWidth: 120,
|
||||
},
|
||||
{
|
||||
Header: 'Type',
|
||||
accessor: 'type',
|
||||
Cell: FeatureTypeCell,
|
||||
totalWidth: 120,
|
||||
},
|
||||
{
|
||||
Header: 'Feature toggle name',
|
||||
accessor: 'name',
|
||||
// @ts-expect-error // TODO: props type
|
||||
Cell: ({ row: { original } }) => <FeatureNameCell {...original} />,
|
||||
Cell: ({
|
||||
row: {
|
||||
// @ts-expect-error -- props type
|
||||
original: { name, description, project },
|
||||
},
|
||||
}) => (
|
||||
<FeatureLinkCell
|
||||
title={name}
|
||||
subtitle={description}
|
||||
to={`/projects/${project}/features/${name}`}
|
||||
/>
|
||||
),
|
||||
sortType: 'alphanumeric',
|
||||
},
|
||||
{
|
||||
@ -74,7 +83,7 @@ const columns = [
|
||||
Header: 'Project ID',
|
||||
accessor: 'project',
|
||||
Cell: ({ value }: { value: string }) => (
|
||||
<LinkCell to={`/projects/${value}`}>{value}</LinkCell>
|
||||
<FeatureLinkCell title={value} to={`/projects/${value}`} />
|
||||
),
|
||||
sortType: 'alphanumeric',
|
||||
},
|
||||
@ -123,6 +132,7 @@ export const FeatureToggleListTable: VFC<IExperimentProps> = ({
|
||||
initialState,
|
||||
sortTypes,
|
||||
autoResetGlobalFilter: false,
|
||||
disableSortRemove: true,
|
||||
},
|
||||
useGlobalFilter,
|
||||
useSortBy
|
||||
@ -130,7 +140,13 @@ export const FeatureToggleListTable: VFC<IExperimentProps> = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (isSmallScreen) {
|
||||
setHiddenColumns(['lastSeenAt', 'type', 'stale', 'description']);
|
||||
setHiddenColumns([
|
||||
'lastSeenAt',
|
||||
'type',
|
||||
'stale',
|
||||
'description',
|
||||
'createdAt',
|
||||
]);
|
||||
} else if (isMediumScreen) {
|
||||
setHiddenColumns(['lastSeenAt', 'stale', 'description']);
|
||||
} else {
|
||||
@ -139,27 +155,26 @@ export const FeatureToggleListTable: VFC<IExperimentProps> = ({
|
||||
}, [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>
|
||||
}
|
||||
>
|
||||
<TableContainer ref={ref}>
|
||||
<TableToolbar title={`Feature toggles (${data.length})`}>
|
||||
<TableSearch
|
||||
initialValue={globalFilter}
|
||||
onChange={setGlobalFilter}
|
||||
/>
|
||||
<TableToolbar.Divider />
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to="/archive"
|
||||
underline="always"
|
||||
sx={{ marginRight: 3 }}
|
||||
>
|
||||
View archive
|
||||
</Link>
|
||||
<CreateFeatureButton
|
||||
loading={false}
|
||||
filter={{ query: '', project: 'default' }}
|
||||
/>
|
||||
</TableToolbar>
|
||||
<SearchHighlightProvider value={globalFilter}>
|
||||
<Table {...getTableProps()}>
|
||||
<SortableTableHeader headerGroups={headerGroups} />
|
||||
@ -182,12 +197,24 @@ export const FeatureToggleListTable: VFC<IExperimentProps> = ({
|
||||
<ConditionallyRender
|
||||
condition={rows.length === 0}
|
||||
show={
|
||||
<TablePlaceholder>
|
||||
No features available. Get started by adding a new
|
||||
feature toggle.
|
||||
</TablePlaceholder>
|
||||
<ConditionallyRender
|
||||
condition={globalFilter?.length > 0}
|
||||
show={
|
||||
<TablePlaceholder>
|
||||
No features or projects found matching “
|
||||
{globalFilter}
|
||||
”
|
||||
</TablePlaceholder>
|
||||
}
|
||||
elseShow={
|
||||
<TablePlaceholder>
|
||||
No features available. Get started by adding a
|
||||
new feature toggle.
|
||||
</TablePlaceholder>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</TablePanel>
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,12 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
container: {
|
||||
padding: theme.spacing(1.5),
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
icon: {
|
||||
marginTop: theme.spacing(0.5),
|
||||
color: theme.palette.grey[600],
|
||||
|
@ -20,8 +20,10 @@ export const FeatureTypeCell: VFC<IFeatureTypeProps> = ({ value }) => {
|
||||
const title = `This is a "${typeName || value}" toggle`;
|
||||
|
||||
return (
|
||||
<Tooltip arrow placement="right" title={title}>
|
||||
<IconComponent data-loading className={styles.icon} />
|
||||
</Tooltip>
|
||||
<div className={styles.container}>
|
||||
<Tooltip arrow placement="right" title={title} describeChild>
|
||||
<IconComponent data-loading className={styles.icon} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
@ -83,7 +83,7 @@ export const useStyles = makeStyles()(theme => ({
|
||||
color: '#000',
|
||||
},
|
||||
icon: {
|
||||
color: theme.palette.grey[600],
|
||||
color: theme.palette.grey[700],
|
||||
},
|
||||
wideButton: {
|
||||
borderRadius: 100,
|
||||
|
@ -15,6 +15,6 @@ export const useStyles = makeStyles()(theme => ({
|
||||
},
|
||||
},
|
||||
icon: {
|
||||
color: theme.palette.grey[600],
|
||||
color: theme.palette.grey[700],
|
||||
},
|
||||
}));
|
||||
|
@ -51,22 +51,6 @@ export default createTheme({
|
||||
light: colors.purple[700],
|
||||
dark: colors.purple[900],
|
||||
},
|
||||
grey: colors.grey,
|
||||
neutral: {
|
||||
main: '#18243e',
|
||||
},
|
||||
chips: {
|
||||
main: '#b0bec5',
|
||||
},
|
||||
searchField: {
|
||||
main: '#fff',
|
||||
},
|
||||
iconButtons: {
|
||||
main: '#fff',
|
||||
},
|
||||
tabs: {
|
||||
main: '#efefef',
|
||||
},
|
||||
success: {
|
||||
light: colors.green[100],
|
||||
main: colors.green[700],
|
||||
@ -82,6 +66,22 @@ export default createTheme({
|
||||
main: colors.red[700],
|
||||
dark: colors.red[800],
|
||||
},
|
||||
grey: colors.grey,
|
||||
// neutral: {
|
||||
// main: '#18243e',
|
||||
// },
|
||||
chips: {
|
||||
main: '#b0bec5',
|
||||
},
|
||||
searchField: {
|
||||
main: '#fff',
|
||||
},
|
||||
iconButtons: {
|
||||
main: '#fff',
|
||||
},
|
||||
tabs: {
|
||||
main: '#efefef',
|
||||
},
|
||||
division: {
|
||||
main: '#f1f1f1',
|
||||
},
|
||||
@ -97,5 +97,12 @@ export default createTheme({
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiLink: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
color: colors.purple[900],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -2,6 +2,9 @@ import { SimplePaletteColorOptions } from '@mui/material';
|
||||
|
||||
declare module '@mui/material/styles' {
|
||||
interface CustomTheme {
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
fontSizes: {
|
||||
mainHeader: string;
|
||||
subHeader: string;
|
||||
@ -9,12 +12,18 @@ declare module '@mui/material/styles' {
|
||||
smallBody: string;
|
||||
smallerBody: string;
|
||||
};
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
fontWeight: {
|
||||
thin: number;
|
||||
medium: number;
|
||||
semi: number;
|
||||
bold: number;
|
||||
};
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
code: {
|
||||
main: string;
|
||||
diffAdd: string;
|
||||
@ -23,21 +32,44 @@ declare module '@mui/material/styles' {
|
||||
edited: string;
|
||||
background: string;
|
||||
};
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
borderRadius: {
|
||||
main: string;
|
||||
};
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
boxShadows: {
|
||||
main: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface CustomPalette {
|
||||
neutral: SimplePaletteColorOptions;
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
chips: SimplePaletteColorOptions;
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
searchField: SimplePaletteColorOptions;
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
iconButtons: SimplePaletteColorOptions;
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
tabs: SimplePaletteColorOptions;
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
division: SimplePaletteColorOptions;
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
footer: SimplePaletteColorOptions;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user