1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-09-01 13:47:27 +02:00

chore: make project list table take less horizontal space (#10480)

https://linear.app/unleash/issue/2-3761/address-ux-feedback-make-table-take-less-horizontal-space-to-prevent

Addresses UX feedback by making the project list table take less
horizontal space. This should prevent us from having to scroll
horizontally in most cases.

<img width="1103" height="647" alt="image"
src="https://github.com/user-attachments/assets/e5cc22a2-5eda-4cb5-a226-c54993c019ce"
/>
This commit is contained in:
Nuno Góis 2025-08-08 08:03:02 +01:00 committed by GitHub
parent 60a2fa675b
commit 937cba4c1a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 82 additions and 37 deletions

View File

@ -10,15 +10,21 @@ import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip';
type ProjectLastSeenProps = { type ProjectLastSeenProps = {
date?: Date | number | string | null; date?: Date | number | string | null;
hideLabel?: boolean;
}; };
const StyledContainer = styled(Box)(({ theme }) => ({ const StyledContainer = styled(Box, {
shouldForwardProp: (prop) => prop !== 'secondary',
})<{ secondary?: boolean }>(({ theme, secondary }) => ({
...flexRow, ...flexRow,
justifyContent: 'flex-start', justifyContent: 'flex-start',
textWrap: 'nowrap', textWrap: 'nowrap',
width: '50%', width: '50%',
gap: theme.spacing(1), gap: theme.spacing(1),
cursor: 'default', cursor: 'default',
color: secondary
? theme.palette.text.secondary
: theme.palette.text.primary,
})); }));
const StyledIcon = styled(StyledIconWrapper)<{ background: string }>( const StyledIcon = styled(StyledIconWrapper)<{ background: string }>(
@ -28,13 +34,18 @@ const StyledIcon = styled(StyledIconWrapper)<{ background: string }>(
}), }),
); );
const Title = () => ( const Title = ({ date }: Pick<ProjectLastSeenProps, 'date'>) => (
<> <>
<Typography <Typography
component='span' component='span'
sx={(theme) => ({ fontSize: theme.fontSizes.smallBody })} sx={(theme) => ({ fontSize: theme.fontSizes.smallBody })}
> >
Last usage reported Last usage reported:{' '}
{date ? (
<TimeAgo date={date} refresh={false} />
) : (
<span>No activity</span>
)}
</Typography> </Typography>
<Typography <Typography
sx={(theme) => ({ sx={(theme) => ({
@ -48,32 +59,29 @@ const Title = () => (
</> </>
); );
export const ProjectLastSeen: FC<ProjectLastSeenProps> = ({ date }) => { export const ProjectLastSeen: FC<ProjectLastSeenProps> = ({
date,
hideLabel,
}) => {
const getColor = useLastSeenColors(); const getColor = useLastSeenColors();
const { text, background } = getColor(date); const { text, background } = getColor(date);
if (!date) {
return ( return (
<HtmlTooltip title={<Title />} arrow> <HtmlTooltip title={<Title date={date} />} arrow>
<StyledContainer <StyledContainer secondary={!date}>
sx={(theme) => ({ color: theme.palette.text.secondary })}
>
<StyledIcon background={background}>
<UsageLine stroke={text} />
</StyledIcon>{' '}
<div>No activity</div>
</StyledContainer>
</HtmlTooltip>
);
}
return (
<HtmlTooltip title={<Title />} arrow>
<StyledContainer>
<StyledIcon background={background}> <StyledIcon background={background}>
{date ? (
<UsageRate stroke={text} /> <UsageRate stroke={text} />
) : (
<UsageLine stroke={text} />
)}
</StyledIcon>{' '} </StyledIcon>{' '}
{!hideLabel &&
(date ? (
<TimeAgo date={date} refresh={false} /> <TimeAgo date={date} refresh={false} />
) : (
<div>No activity</div>
))}
</StyledContainer> </StyledContainer>
</HtmlTooltip> </HtmlTooltip>
); );

View File

@ -0,0 +1,25 @@
import { styled } from '@mui/material';
import { ProjectLastSeen } from 'component/project/ProjectCard/ProjectLastSeen/ProjectLastSeen';
const StyledContainer = styled('div', {
shouldForwardProp: (prop) => prop !== 'hideLabel',
})<{ hideLabel?: boolean }>(({ theme, hideLabel }) => ({
display: 'flex',
alignItems: 'center',
justifyContent: hideLabel ? 'center' : 'start',
padding: theme.spacing(1, 2),
}));
type ProjectListTableLastSeenCellProps = {
value?: Date | number | string | null;
hideLabel?: boolean;
};
export const ProjectListTableLastSeenCell = ({
value,
hideLabel,
}: ProjectListTableLastSeenCellProps) => (
<StyledContainer hideLabel={hideLabel}>
<ProjectLastSeen date={value} hideLabel={hideLabel} />
</StyledContainer>
);

View File

@ -4,14 +4,16 @@ import { HighlightCell } from 'component/common/Table/cells/HighlightCell/Highli
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell'; import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
import { TimeAgoCell } from 'component/common/Table/cells/TimeAgoCell/TimeAgoCell'; import { TimeAgoCell } from 'component/common/Table/cells/TimeAgoCell/TimeAgoCell';
import { ProjectOwners } from 'component/project/ProjectCard/ProjectCardFooter/ProjectOwners/ProjectOwners'; import { ProjectOwners } from 'component/project/ProjectCard/ProjectCardFooter/ProjectOwners/ProjectOwners';
import { ProjectLastSeen } from 'component/project/ProjectCard/ProjectLastSeen/ProjectLastSeen';
import { useFavoriteProjectsApi } from 'hooks/api/actions/useFavoriteProjectsApi/useFavoriteProjectsApi'; import { useFavoriteProjectsApi } from 'hooks/api/actions/useFavoriteProjectsApi/useFavoriteProjectsApi';
import useProjects from 'hooks/api/getters/useProjects/useProjects'; import useProjects from 'hooks/api/getters/useProjects/useProjects';
import type { ProjectSchema, ProjectSchemaOwners } from 'openapi'; import type { ProjectSchema, ProjectSchemaOwners } from 'openapi';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useFlexLayout, useTable } from 'react-table'; import { useFlexLayout, useTable } from 'react-table';
import { formatDateYMDHMS } from 'utils/formatDate'; import { formatDateYMDHMS } from 'utils/formatDate';
import { ProjectsListTableProjectName } from './ProjectsListTableProjectName.tsx'; import { ProjectsListTableNameCell } from './ProjectsListTableNameCell.tsx';
import { useMediaQuery } from '@mui/material';
import theme from 'themes/theme.ts';
import { ProjectListTableLastSeenCell } from './ProjectListTableLastSeenCell.tsx';
type ProjectsListTableProps = { type ProjectsListTableProps = {
projects: ProjectSchema[]; projects: ProjectSchema[];
@ -20,6 +22,7 @@ type ProjectsListTableProps = {
export const ProjectsListTable = ({ projects }: ProjectsListTableProps) => { export const ProjectsListTable = ({ projects }: ProjectsListTableProps) => {
const { refetch } = useProjects(); const { refetch } = useProjects();
const { favorite, unfavorite } = useFavoriteProjectsApi(); const { favorite, unfavorite } = useFavoriteProjectsApi();
const isMediumScreen = useMediaQuery(theme.breakpoints.down('lg'));
const onFavorite = useCallback( const onFavorite = useCallback(
async (project: ProjectSchema) => { async (project: ProjectSchema) => {
@ -51,7 +54,7 @@ export const ProjectsListTable = ({ projects }: ProjectsListTableProps) => {
accessor: 'name', accessor: 'name',
minWidth: 200, minWidth: 200,
searchable: true, searchable: true,
Cell: ProjectsListTableProjectName, Cell: ProjectsListTableNameCell,
}, },
{ {
Header: 'Last updated', Header: 'Last updated',
@ -64,15 +67,17 @@ export const ProjectsListTable = ({ projects }: ProjectsListTableProps) => {
dateFormat={formatDateYMDHMS} dateFormat={formatDateYMDHMS}
/> />
), ),
width: 150,
}, },
{ {
Header: 'Number of flags', Header: 'Flags',
accessor: 'featureCount', accessor: 'featureCount',
Cell: ({ value }: { value: number }) => ( Cell: ({ value }: { value: number }) => (
<TextCell> <TextCell>
{value} flag{value === 1 ? '' : 's'} {value} flag{value === 1 ? '' : 's'}
</TextCell> </TextCell>
), ),
width: 90,
}, },
{ {
Header: 'Health', Header: 'Health',
@ -80,24 +85,30 @@ export const ProjectsListTable = ({ projects }: ProjectsListTableProps) => {
Cell: ({ value }: { value: number }) => ( Cell: ({ value }: { value: number }) => (
<TextCell>{value}%</TextCell> <TextCell>{value}%</TextCell>
), ),
width: 100, width: 70,
}, },
{ {
Header: 'Last seen', Header: 'Last seen',
accessor: 'lastReportedFlagUsage', accessor: 'lastReportedFlagUsage',
Cell: ({ value }: { value: Date }) => ( Cell: ({ value }: { value: Date }) => (
<ProjectLastSeen date={value} /> <ProjectListTableLastSeenCell
value={value}
hideLabel={isMediumScreen}
/>
), ),
width: isMediumScreen ? 100 : 140,
}, },
{ {
Header: 'Owner', Header: 'Owner',
accessor: 'owners', accessor: 'owners',
Cell: ({ value }: { value: ProjectSchemaOwners }) => ( Cell: ({ value }: { value: ProjectSchemaOwners }) => (
<TextCell>
<ProjectOwners <ProjectOwners
owners={value?.filter( owners={value?.filter(
(owner) => owner.ownerType !== 'system', (owner) => owner.ownerType !== 'system',
)} )}
/> />
</TextCell>
), ),
}, },
{ {
@ -106,6 +117,7 @@ export const ProjectsListTable = ({ projects }: ProjectsListTableProps) => {
Cell: ({ value }: { value: number }) => ( Cell: ({ value }: { value: number }) => (
<TextCell>{value} members</TextCell> <TextCell>{value} members</TextCell>
), ),
width: 120,
}, },
], ],
[onFavorite], [onFavorite],

View File

@ -20,15 +20,15 @@ const StyledFeatureLink = styled(Link)({
}, },
}); });
type ProjectsListTableProjectNameProps = { type ProjectsListTableNameCellProps = {
row: { row: {
original: ProjectSchema; original: ProjectSchema;
}; };
}; };
export const ProjectsListTableProjectName = ({ export const ProjectsListTableNameCell = ({
row, row,
}: ProjectsListTableProjectNameProps) => { }: ProjectsListTableNameCellProps) => {
const { searchQuery } = useSearchHighlightContext(); const { searchQuery } = useSearchHighlightContext();
return ( return (