mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
Project features list update (#991)
* refactor: column icon position * project overview horizontal scroll * updated table headers styles * fix: feature overview switch title * refactor: cleanup of sortable header styles * fix: z-index issue in test * fix: html semantics after review
This commit is contained in:
parent
98b6214c28
commit
06b0a29ea8
@ -2,6 +2,7 @@ import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
rolesListBody: {
|
||||
padding: theme.spacing(4),
|
||||
paddingBottom: '4rem',
|
||||
minHeight: '50vh',
|
||||
position: 'relative',
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(() => ({
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
userListBody: {
|
||||
padding: theme.spacing(4),
|
||||
paddingBottom: '4rem',
|
||||
minHeight: '50vh',
|
||||
position: 'relative',
|
||||
|
@ -15,9 +15,9 @@ export const useStyles = makeStyles()(theme => ({
|
||||
},
|
||||
},
|
||||
bodyContainer: {
|
||||
padding: '2rem',
|
||||
padding: theme.spacing(4),
|
||||
[theme.breakpoints.down('md')]: {
|
||||
padding: '1rem',
|
||||
padding: theme.spacing(2),
|
||||
},
|
||||
},
|
||||
paddingDisabled: {
|
||||
|
@ -38,11 +38,13 @@ export const PageContent: FC<IPageContentProps> = ({
|
||||
[styles.borderDisabled]: disableBorder,
|
||||
});
|
||||
|
||||
const bodyClasses = classnames(styles.bodyContainer, {
|
||||
[styles.paddingDisabled]: disablePadding,
|
||||
[styles.borderDisabled]: disableBorder,
|
||||
[bodyClass]: bodyClass,
|
||||
});
|
||||
const bodyClasses = classnames(
|
||||
bodyClass ? bodyClass : styles.bodyContainer,
|
||||
{
|
||||
[styles.paddingDisabled]: disablePadding,
|
||||
[styles.borderDisabled]: disableBorder,
|
||||
}
|
||||
);
|
||||
|
||||
const paperProps = disableBorder ? { elevation: 0 } : {};
|
||||
|
||||
|
@ -10,7 +10,12 @@ export const useStyles = makeStyles()(theme => ({
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
position: 'relative',
|
||||
flexWrap: 'wrap',
|
||||
},
|
||||
header: {
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
marginRight: theme.spacing(2),
|
||||
},
|
||||
headerTitle: {
|
||||
fontSize: theme.fontSizes.mainHeader,
|
||||
@ -21,6 +26,7 @@ export const useStyles = makeStyles()(theme => ({
|
||||
flexGrow: 1,
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
gap: theme.spacing(1),
|
||||
},
|
||||
verticalSeparator: {
|
||||
height: '100%',
|
||||
|
@ -35,7 +35,10 @@ const PageHeaderComponent: FC<IPageHeaderProps> & { Divider: VFC } = ({
|
||||
return (
|
||||
<div className={styles.headerContainer}>
|
||||
<div className={styles.topContainer}>
|
||||
<div className={headerClasses} data-loading>
|
||||
<div
|
||||
className={classnames(styles.header, headerClasses)}
|
||||
data-loading
|
||||
>
|
||||
<Typography
|
||||
variant={variant || 'h1'}
|
||||
className={classnames(styles.headerTitle, className)}
|
||||
|
@ -19,11 +19,17 @@ export const useStyles = makeStyles()(theme => ({
|
||||
padding: theme.spacing(2),
|
||||
whiteSpace: 'nowrap',
|
||||
width: '100%',
|
||||
'& .hover-only': {
|
||||
visibility: 'hidden',
|
||||
},
|
||||
':hover, :focus, &:focus-visible, &:active': {
|
||||
outline: 'revert',
|
||||
'& svg': {
|
||||
color: 'inherit',
|
||||
},
|
||||
'& .hover-only': {
|
||||
visibility: 'visible',
|
||||
},
|
||||
},
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
|
@ -41,7 +41,7 @@ export const SortArrow: VFC<ISortArrowProps> = ({
|
||||
}
|
||||
elseShow={
|
||||
<UnfoldMoreOutlined
|
||||
className={styles.icon}
|
||||
className={classnames(styles.icon, 'hover-only')}
|
||||
fontSize="inherit"
|
||||
/>
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ interface ITableActionsProps {
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @deprecated Use <PageHeader actions={} /> instead
|
||||
*/
|
||||
export const TableActions: FC<ITableActionsProps> = ({
|
||||
initialSearchValue: search,
|
||||
|
@ -1,9 +1,15 @@
|
||||
import { FC } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { TableCell as MUITableCell, TableCellProps } from '@mui/material';
|
||||
import { useStyles } from './TableCell.styles';
|
||||
|
||||
export const TableCell: FC<TableCellProps> = ({ ...props }) => {
|
||||
export const TableCell: FC<TableCellProps> = ({ className, ...props }) => {
|
||||
const { classes: styles } = useStyles();
|
||||
|
||||
return <MUITableCell className={styles.tableCell} {...props} />;
|
||||
return (
|
||||
<MUITableCell
|
||||
className={classnames(styles.tableCell, className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -37,7 +37,7 @@ const FeatureOverviewEnvSwitch = ({
|
||||
await toggleFeatureEnvironmentOn(projectId, featureId, env.name);
|
||||
setToastData({
|
||||
type: 'success',
|
||||
title: 'Available in production',
|
||||
title: `Available in ${env.name}`,
|
||||
text: `${featureId} is now available in ${env.name} based on its defined strategies.`,
|
||||
});
|
||||
refetchFeature();
|
||||
@ -61,7 +61,7 @@ const FeatureOverviewEnvSwitch = ({
|
||||
await toggleFeatureEnvironmentOff(projectId, featureId, env.name);
|
||||
setToastData({
|
||||
type: 'success',
|
||||
title: 'Unavailable in production',
|
||||
title: `Unavailable in ${env.name}`,
|
||||
text: `${featureId} is unavailable in ${env.name} and its strategies will no longer have any effect.`,
|
||||
});
|
||||
refetchFeature();
|
||||
|
@ -8,7 +8,10 @@ export const useStyles = makeStyles()(theme => ({
|
||||
flexDirection: 'column',
|
||||
},
|
||||
},
|
||||
projectToggles: { width: '100%', minHeight: '100%' },
|
||||
projectToggles: {
|
||||
width: '100%',
|
||||
minWidth: 0,
|
||||
},
|
||||
header: {
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
@ -36,13 +39,15 @@ export const useStyles = makeStyles()(theme => ({
|
||||
},
|
||||
},
|
||||
title: {
|
||||
margin: 0,
|
||||
width: '100%',
|
||||
fontSize: theme.fontSizes.mainHeader,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: '0.5rem',
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr auto',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
gridGap: '1rem',
|
||||
gap: '1rem',
|
||||
},
|
||||
titleText: {
|
||||
overflow: 'hidden',
|
||||
|
@ -90,7 +90,6 @@ const Project = () => {
|
||||
return tabData.map((tab, index) => {
|
||||
return (
|
||||
<Tab
|
||||
data-loading
|
||||
key={tab.title}
|
||||
id={`tab-${index}`}
|
||||
aria-controls={`tabpanel-${index}`}
|
||||
@ -116,12 +115,10 @@ const Project = () => {
|
||||
<div ref={ref}>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.innerContainer}>
|
||||
<h2
|
||||
data-loading
|
||||
className={styles.title}
|
||||
style={{ margin: 0, width: '100%' }}
|
||||
>
|
||||
<div className={styles.titleText}>{project?.name}</div>
|
||||
<h2 className={styles.title}>
|
||||
<div className={styles.titleText} data-loading>
|
||||
{project?.name || projectId}
|
||||
</div>
|
||||
<PermissionIconButton
|
||||
permission={UPDATE_PROJECT}
|
||||
projectId={project?.id}
|
||||
|
@ -1,9 +1,15 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
cell: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
paddingRight: theme.spacing(2),
|
||||
},
|
||||
menuContainer: {
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
padding: theme.spacing(1),
|
||||
paddingRight: theme.spacing(3),
|
||||
},
|
||||
item: {
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
|
@ -57,7 +57,7 @@ export const ActionsCell: VFC<IActionsCellProps> = ({ projectId, row }) => {
|
||||
const menuId = `${id}-menu`;
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<Box className={classes.cell}>
|
||||
<Tooltip
|
||||
title="Feature toggle actions"
|
||||
arrow
|
||||
|
@ -17,12 +17,11 @@ import {
|
||||
import ViewColumnIcon from '@mui/icons-material/ViewColumn';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { capitalize } from 'lodash';
|
||||
import { useStyles } from './ColumnsMenu.styles';
|
||||
|
||||
interface IColumnsMenuProps {
|
||||
allColumns: {
|
||||
Header: string | any;
|
||||
Header?: string | any;
|
||||
id: string;
|
||||
isVisible: boolean;
|
||||
toggleHidden: (state: boolean) => void;
|
||||
@ -104,6 +103,7 @@ export const ColumnsMenu: VFC<IColumnsMenuProps> = ({
|
||||
onClick={handleClick}
|
||||
type="button"
|
||||
className={classes.button}
|
||||
data-loading
|
||||
>
|
||||
<ViewColumnIcon />
|
||||
</IconButton>
|
||||
@ -165,14 +165,12 @@ export const ColumnsMenu: VFC<IColumnsMenuProps> = ({
|
||||
primary={
|
||||
<Typography variant="body2">
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
condition={Boolean(
|
||||
typeof column.Header ===
|
||||
'string'
|
||||
}
|
||||
'string' && column.Header
|
||||
)}
|
||||
show={() => <>{column.Header}</>}
|
||||
elseShow={() =>
|
||||
capitalize(column.id)
|
||||
}
|
||||
elseShow={() => column.id}
|
||||
/>
|
||||
</Typography>
|
||||
}
|
||||
|
@ -43,12 +43,13 @@ export const FeatureToggleSwitch: VFC<IFeatureToggleSwitchProps> = ({
|
||||
key={`${featureName}-${environmentName}`} // Prevent animation when archiving rows
|
||||
>
|
||||
<PermissionSwitch
|
||||
checked={isChecked}
|
||||
checked={value}
|
||||
environmentId={environmentName}
|
||||
projectId={projectId}
|
||||
permission={UPDATE_FEATURE_ENVIRONMENT}
|
||||
inputProps={{ 'aria-label': environmentName }}
|
||||
onClick={onClick}
|
||||
disabled={isChecked !== value}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
@ -7,7 +7,6 @@ export const useStyles = makeStyles()(theme => ({
|
||||
minHeight: '100%',
|
||||
width: 'calc(100% - 1rem)',
|
||||
position: 'relative',
|
||||
paddingBottom: '4rem',
|
||||
[theme.breakpoints.down('md')]: {
|
||||
marginLeft: '0',
|
||||
paddingBottom: '4rem',
|
||||
@ -25,7 +24,10 @@ export const useStyles = makeStyles()(theme => ({
|
||||
},
|
||||
},
|
||||
},
|
||||
bodyClass: { padding: '0.5rem 1rem' },
|
||||
bodyClass: {
|
||||
overflowX: 'auto',
|
||||
padding: theme.spacing(4),
|
||||
},
|
||||
header: {
|
||||
padding: '1rem',
|
||||
},
|
||||
|
@ -70,13 +70,15 @@ export const ProjectFeatureToggles = ({
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const navigate = useNavigate();
|
||||
const { uiConfig } = useUiConfig();
|
||||
const environments = useEnvironmentsRef(newEnvironments);
|
||||
const environments = useEnvironmentsRef(
|
||||
loading ? ['a', 'b', 'c'] : newEnvironments
|
||||
);
|
||||
const { refetch } = useProject(projectId);
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
|
||||
const data = useMemo<ListItemType[]>(() => {
|
||||
if (loading) {
|
||||
return Array(12).fill({
|
||||
return Array(6).fill({
|
||||
type: '-',
|
||||
name: 'Feature name',
|
||||
createdAt: new Date(),
|
||||
@ -180,7 +182,8 @@ export const ProjectFeatureToggles = ({
|
||||
/>
|
||||
),
|
||||
width: '99%',
|
||||
minWdith: 100,
|
||||
minWidth: 100,
|
||||
maxWidth: 200,
|
||||
sortType: 'alphanumeric',
|
||||
},
|
||||
{
|
||||
@ -191,9 +194,9 @@ export const ProjectFeatureToggles = ({
|
||||
align: 'center',
|
||||
},
|
||||
...environments.map(name => ({
|
||||
Header: name,
|
||||
maxWidth: 103,
|
||||
minWidth: 103,
|
||||
Header: loading ? () => '' : name,
|
||||
maxWidth: 90,
|
||||
minWidth: 90,
|
||||
accessor: `environments.${name}`,
|
||||
align: 'center',
|
||||
Cell: ({
|
||||
@ -218,25 +221,16 @@ export const ProjectFeatureToggles = ({
|
||||
},
|
||||
})),
|
||||
{
|
||||
Header: ({ allColumns, setHiddenColumns }: any) => (
|
||||
<ColumnsMenu
|
||||
allColumns={allColumns}
|
||||
staticColumns={['actions', 'name']}
|
||||
dividerAfter={['createdAt']}
|
||||
dividerBefore={['actions']}
|
||||
setHiddenColumns={setHiddenColumns}
|
||||
/>
|
||||
),
|
||||
maxWidth: 60,
|
||||
width: 60,
|
||||
id: 'actions',
|
||||
id: 'Actions',
|
||||
maxWidth: 56,
|
||||
width: 56,
|
||||
Cell: (props: { row: { original: ListItemType } }) => (
|
||||
<ActionsCell projectId={projectId} {...props} />
|
||||
),
|
||||
disableSortBy: true,
|
||||
},
|
||||
],
|
||||
[projectId, environments, onToggle]
|
||||
[projectId, environments, onToggle, loading]
|
||||
);
|
||||
|
||||
const initialState = useMemo(
|
||||
@ -250,13 +244,15 @@ export const ProjectFeatureToggles = ({
|
||||
);
|
||||
|
||||
const {
|
||||
state: { filters },
|
||||
getTableProps,
|
||||
getTableBodyProps,
|
||||
allColumns,
|
||||
headerGroups,
|
||||
rows,
|
||||
state: { filters },
|
||||
getTableBodyProps,
|
||||
getTableProps,
|
||||
prepareRow,
|
||||
setFilter,
|
||||
setHiddenColumns,
|
||||
} = useTable(
|
||||
{
|
||||
columns: columns as any[], // TODO: fix after `react-table` v8 update
|
||||
@ -275,7 +271,6 @@ export const ProjectFeatureToggles = ({
|
||||
() => filters?.find(filterRow => filterRow?.id === 'name')?.value || '',
|
||||
[filters]
|
||||
);
|
||||
|
||||
return (
|
||||
<PageContent
|
||||
isLoading={loading}
|
||||
@ -291,6 +286,13 @@ export const ProjectFeatureToggles = ({
|
||||
initialValue={filter}
|
||||
onChange={value => setFilter('name', value)}
|
||||
/>
|
||||
<ColumnsMenu
|
||||
allColumns={allColumns}
|
||||
staticColumns={['Actions', 'name']}
|
||||
dividerAfter={['createdAt']}
|
||||
dividerBefore={['Actions']}
|
||||
setHiddenColumns={setHiddenColumns}
|
||||
/>
|
||||
<PageHeader.Divider />
|
||||
<ResponsiveButton
|
||||
onClick={() =>
|
||||
|
@ -41,7 +41,7 @@ export const useStyles = makeStyles()(theme => ({
|
||||
},
|
||||
},
|
||||
subtitle: {
|
||||
marginBottom: '1.25rem',
|
||||
marginBottom: '1rem',
|
||||
},
|
||||
emphazisedText: {
|
||||
fontSize: '1.5rem',
|
||||
|
@ -121,7 +121,7 @@ const ProjectInfo = ({
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.idContainer}>
|
||||
<p data-loading>projectId: {id}</p>
|
||||
<p>projectId: {id}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -13,10 +13,10 @@ exports[`renders correctly 1`] = `
|
||||
className="tss-1ylehva-headerContainer"
|
||||
>
|
||||
<div
|
||||
className="tss-1dw2af8-topContainer"
|
||||
className="tss-1uxyh7x-topContainer"
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
className="tss-sd6bs4-header"
|
||||
data-loading={true}
|
||||
>
|
||||
<h1
|
||||
@ -26,7 +26,7 @@ exports[`renders correctly 1`] = `
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
className="tss-1u1bjy8-headerActions"
|
||||
className="tss-u5t8ea-headerActions"
|
||||
>
|
||||
<span
|
||||
id="useId-0"
|
||||
@ -66,7 +66,7 @@ exports[`renders correctly 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="tss-f35ha2-bodyContainer"
|
||||
className="tss-54jt3w-bodyContainer"
|
||||
>
|
||||
<ul
|
||||
className="MuiList-root MuiList-padding mui-h4y409-MuiList-root"
|
||||
|
@ -13,10 +13,10 @@ exports[`renders an empty list correctly 1`] = `
|
||||
className="tss-1ylehva-headerContainer"
|
||||
>
|
||||
<div
|
||||
className="tss-1dw2af8-topContainer"
|
||||
className="tss-1uxyh7x-topContainer"
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
className="tss-sd6bs4-header"
|
||||
data-loading={true}
|
||||
>
|
||||
<h1
|
||||
@ -26,7 +26,7 @@ exports[`renders an empty list correctly 1`] = `
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
className="tss-1u1bjy8-headerActions"
|
||||
className="tss-u5t8ea-headerActions"
|
||||
>
|
||||
<button
|
||||
className="MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonBase-root mui-1aw3qf3-MuiButtonBase-root-MuiButton-root"
|
||||
@ -54,7 +54,7 @@ exports[`renders an empty list correctly 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="tss-f35ha2-bodyContainer"
|
||||
className="tss-54jt3w-bodyContainer"
|
||||
>
|
||||
<ul
|
||||
className="MuiList-root MuiList-padding mui-h4y409-MuiList-root"
|
||||
|
@ -7,9 +7,13 @@ type UsePersistentGlobalState<T> = () => [
|
||||
setValue: React.Dispatch<React.SetStateAction<T>>
|
||||
];
|
||||
|
||||
// Create a hook that stores global state (shared across all hook instances).
|
||||
// The state is also persisted to localStorage and restored on page load.
|
||||
// The localStorage state is not synced between tabs.
|
||||
/**
|
||||
* Create a hook that stores global state (shared across all hook instances).
|
||||
* The state is also persisted to localStorage and restored on page load.
|
||||
* The localStorage state is not synced between tabs.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
export const createPersistentGlobalStateHook = <T extends object>(
|
||||
key: string,
|
||||
initialValue: T
|
||||
|
@ -4,6 +4,8 @@
|
||||
* @see https://www.figma.com/file/qdwpPfuitJUNinm6mvmCmG/Unleash-application?node-id=7175%3A44590
|
||||
*/
|
||||
export const colors = {
|
||||
white: '#FFFFFF',
|
||||
black: '#000000',
|
||||
grey: {
|
||||
900: '#202021',
|
||||
800: '#6E6E70',
|
||||
|
@ -234,5 +234,15 @@ export default createTheme({
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiSwitch: {
|
||||
styleOverrides: {
|
||||
switchBase: {
|
||||
zIndex: 1,
|
||||
'&:not(.Mui-checked) .MuiTouchRipple-child': {
|
||||
color: colors.grey['500'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -77,5 +77,10 @@ declare module '@mui/system/createTheme/shape' {
|
||||
borderRadiusExtraLarge: string;
|
||||
}
|
||||
}
|
||||
declare module '@mui/material/styles/zIndex' {
|
||||
interface ZIndex {
|
||||
sticky: number;
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
|
Loading…
Reference in New Issue
Block a user