Skip to content

Commit

Permalink
feat: new project card (#7992)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tymek authored Aug 28, 2024
1 parent 3188f99 commit 8923e28
Show file tree
Hide file tree
Showing 13 changed files with 415 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,10 @@ import type { FC } from 'react';
import useToast from 'hooks/useToast';
import { useFavoriteProjectsApi } from 'hooks/api/actions/useFavoriteProjectsApi/useFavoriteProjectsApi';
import useProjects from 'hooks/api/getters/useProjects/useProjects';
import { styled } from '@mui/material';
import { FavoriteIconButton } from 'component/common/FavoriteIconButton/FavoriteIconButton';

type FavoriteActionProps = { id: string; isFavorite?: boolean };

const StyledFavoriteIconButton = styled(FavoriteIconButton)(({ theme }) => ({
margin: theme.spacing(1, 2, 0, 0),
}));

export const FavoriteAction: FC<FavoriteActionProps> = ({ id, isFavorite }) => {
const { setToastApiError } = useToast();
const { favorite, unfavorite } = useFavoriteProjectsApi();
Expand All @@ -31,7 +26,7 @@ export const FavoriteAction: FC<FavoriteActionProps> = ({ id, isFavorite }) => {
};

return (
<StyledFavoriteIconButton
<FavoriteIconButton
onClick={onFavorite}
isFavorite={Boolean(isFavorite)}
size='medium'
Expand Down
23 changes: 7 additions & 16 deletions frontend/src/component/project/ProjectCard/LegacyProjectCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,10 @@ import {
} from './ProjectCard.styles';
import { ProjectCardFooter } from './ProjectCardFooter/ProjectCardFooter';
import { ProjectModeBadge } from './ProjectModeBadge/ProjectModeBadge';
import type { ProjectSchemaOwners } from 'openapi';
import { ProjectIcon } from 'component/common/ProjectIcon/ProjectIcon';
import { FavoriteAction } from './ProjectCardFooter/FavoriteAction/FavoriteAction';

interface IProjectCardProps {
name: string;
featureCount: number;
health: number;
memberCount?: number;
id: string;
onHover: () => void;
favorite?: boolean;
mode: string;
owners?: ProjectSchemaOwners;
}
import { FavoriteAction } from './FavoriteAction/FavoriteAction';
import type { IProjectCard } from 'interfaces/project';
import { Box } from '@mui/material';

export const ProjectCard = ({
name,
Expand All @@ -38,7 +27,7 @@ export const ProjectCard = ({
mode,
favorite = false,
owners,
}: IProjectCardProps) => (
}: IProjectCard) => (
<StyledProjectCard onMouseEnter={onHover}>
<StyledProjectCardBody>
<StyledDivHeader>
Expand Down Expand Up @@ -79,7 +68,9 @@ export const ProjectCard = ({
</StyledDivInfo>
</StyledProjectCardBody>
<ProjectCardFooter id={id} owners={owners}>
<FavoriteAction id={id} isFavorite={favorite} />
<Box sx={(theme) => ({ margin: theme.spacing(1, 2, 0, 0) })}>
<FavoriteAction id={id} isFavorite={favorite} />
</Box>
</ProjectCardFooter>
</StyledProjectCard>
);
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ export type ProjectArchiveCardProps = {
id: string;
name: string;
archivedAt?: string;
archivedFeaturesCount?: number;
onRevive: () => void;
onDelete: () => void;
mode?: string;
Expand All @@ -45,7 +44,6 @@ export const ProjectArchiveCard: FC<ProjectArchiveCardProps> = ({
id,
name,
archivedAt,
archivedFeaturesCount,
onRevive,
onDelete,
mode,
Expand Down
30 changes: 16 additions & 14 deletions frontend/src/component/project/ProjectCard/ProjectCard.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,22 @@ export const StyledDivHeader = styled('div')(({ theme }) => ({
gap: theme.spacing(1),
}));

export const StyledCardTitle = styled('h3')(({ theme }) => ({
fontWeight: theme.typography.fontWeightRegular,
fontSize: theme.typography.body1.fontSize,
lineClamp: '2',
WebkitLineClamp: 2,
lineHeight: '1.2',
display: '-webkit-box',
boxOrient: 'vertical',
textOverflow: 'ellipsis',
overflow: 'hidden',
alignItems: 'flex-start',
WebkitBoxOrient: 'vertical',
wordBreak: 'break-word',
}));
export const StyledCardTitle = styled('h3')<{ lines?: number }>(
({ theme, lines = 2 }) => ({
fontWeight: theme.typography.fontWeightRegular,
fontSize: theme.typography.body1.fontSize,
lineClamp: `${lines}`,
WebkitLineClamp: lines,
lineHeight: '1.2',
display: '-webkit-box',
boxOrient: 'vertical',
textOverflow: 'ellipsis',
overflow: 'hidden',
alignItems: 'flex-start',
WebkitBoxOrient: 'vertical',
wordBreak: 'break-word',
}),
);

export const StyledBox = styled(Box)(() => ({
...flexRow,
Expand Down
105 changes: 56 additions & 49 deletions frontend/src/component/project/ProjectCard/ProjectCard.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,37 @@
import { DEFAULT_PROJECT_ID } from 'hooks/api/getters/useDefaultProject/useDefaultProjectId';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import {
StyledProjectCard,
StyledDivHeader,
StyledBox,
StyledCardTitle,
StyledDivInfo,
StyledParagraphInfo,
StyledProjectCardBody,
StyledIconBox,
} from './ProjectCard.styles';
import { ProjectCardFooter } from './ProjectCardFooter/ProjectCardFooter';
import { ProjectModeBadge } from './ProjectModeBadge/ProjectModeBadge';
import type { ProjectSchemaOwners } from 'openapi';
import { ProjectIcon } from 'component/common/ProjectIcon/ProjectIcon';
import { FavoriteAction } from './ProjectCardFooter/FavoriteAction/FavoriteAction';
import { FavoriteAction } from './FavoriteAction/FavoriteAction';
import { Box, styled } from '@mui/material';
import { flexColumn } from 'themes/themeStyles';
import { TimeAgo } from 'component/common/TimeAgo/TimeAgo';
import { ProjectLastSeen } from './ProjectLastSeen/ProjectLastSeen';
import type { IProjectCard } from 'interfaces/project';

interface IProjectCardProps {
name: string;
featureCount: number;
health: number;
memberCount?: number;
id: string;
onHover: () => void;
favorite?: boolean;
mode: string;
owners?: ProjectSchemaOwners;
}
const StyledUpdated = styled('span')(({ theme }) => ({
color: theme.palette.text.secondary,
fontSize: theme.fontSizes.smallerBody,
}));

const StyledCount = styled('strong')(({ theme }) => ({
fontWeight: theme.typography.fontWeightMedium,
}));

const StyledInfo = styled('div')(({ theme }) => ({
display: 'flex',
justifyContent: 'space-between',
marginTop: theme.spacing(1),
fontSize: theme.fontSizes.smallerBody,
alignItems: 'flex-end',
}));

export const ProjectCard = ({
name,
Expand All @@ -38,48 +43,50 @@ export const ProjectCard = ({
mode,
favorite = false,
owners,
}: IProjectCardProps) => (
lastUpdatedAt,
lastReportedFlagUsage,
}: IProjectCard) => (
<StyledProjectCard onMouseEnter={onHover}>
<StyledProjectCardBody>
<StyledDivHeader>
<StyledIconBox>
<ProjectIcon />
</StyledIconBox>
<StyledBox data-loading>
<StyledCardTitle>{name}</StyledCardTitle>
</StyledBox>
<Box
data-loading
sx={(theme) => ({
...flexColumn,
margin: theme.spacing(1, 'auto', 1, 0),
})}
>
<StyledCardTitle lines={1} sx={{ margin: 0 }}>
{name}
</StyledCardTitle>
<ConditionallyRender
condition={Boolean(lastUpdatedAt)}
show={
<StyledUpdated>
Updated <TimeAgo date={lastUpdatedAt} />
</StyledUpdated>
}
/>
</Box>
<ProjectModeBadge mode={mode} />
<FavoriteAction id={id} isFavorite={favorite} />
</StyledDivHeader>
<StyledDivInfo>
<div>
<StyledParagraphInfo data-loading>
{featureCount}
</StyledParagraphInfo>
<p data-loading>{featureCount === 1 ? 'flag' : 'flags'}</p>
</div>
<ConditionallyRender
condition={id !== DEFAULT_PROJECT_ID}
show={
<div>
<StyledParagraphInfo data-loading>
{memberCount}
</StyledParagraphInfo>
<p data-loading>
{memberCount === 1 ? 'member' : 'members'}
</p>
</div>
}
/>
<StyledInfo>
<div>
<StyledParagraphInfo data-loading>
{health}%
</StyledParagraphInfo>
<p data-loading>healthy</p>
<div>
<StyledCount>{featureCount}</StyledCount> flag
{featureCount === 1 ? '' : 's'}
</div>
<div>
<StyledCount>{health}%</StyledCount> health
</div>
</div>
</StyledDivInfo>
<ProjectLastSeen date={lastReportedFlagUsage} />
</StyledInfo>
</StyledProjectCardBody>
<ProjectCardFooter id={id} owners={owners}>
<FavoriteAction id={id} isFavorite={favorite} />
</ProjectCardFooter>
<ProjectCardFooter id={id} owners={owners} memberCount={memberCount} />
</StyledProjectCard>
);
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,21 @@ import type { FC } from 'react';
import { Box, styled } from '@mui/material';
import {
type IProjectOwnersProps,
ProjectOwners,
} from '../ProjectOwners/ProjectOwners';
ProjectOwners as LegacyProjectOwners,
} from '../LegacyProjectOwners/LegacyProjectOwners';
import { ProjectOwners } from './ProjectOwners/ProjectOwners';
import { useUiFlag } from 'hooks/useUiFlag';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { ProjectMembers } from './ProjectMembers/ProjectMembers';
import { DEFAULT_PROJECT_ID } from 'hooks/api/getters/useDefaultProject/useDefaultProjectId';

interface IProjectCardFooterProps {
id: string;
id?: string;
isFavorite?: boolean;
children?: React.ReactNode;
disabled?: boolean;
owners: IProjectOwnersProps['owners'];
memberCount?: number;
}

const StyledFooter = styled(Box)<{ disabled: boolean }>(
Expand All @@ -28,14 +34,29 @@ const StyledFooter = styled(Box)<{ disabled: boolean }>(
);

export const ProjectCardFooter: FC<IProjectCardFooterProps> = ({
id,
children,
owners,
disabled = false,
memberCount,
}) => {
const projectListImprovementsEnabled = useUiFlag('projectListImprovements');

return (
<StyledFooter disabled={disabled}>
<ProjectOwners owners={owners} />
{children}
<ConditionallyRender
condition={Boolean(projectListImprovementsEnabled)}
show={<ProjectOwners owners={owners} />}
elseShow={<LegacyProjectOwners owners={owners} />}
/>
<ConditionallyRender
condition={
Boolean(projectListImprovementsEnabled) &&
id !== DEFAULT_PROJECT_ID
}
show={<ProjectMembers count={memberCount} members={[]} />}
elseShow={children}
/>
</StyledFooter>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { FC } from 'react';
import { styled } from '@mui/material';
import { AvatarGroup } from 'component/common/AvatarGroup/AvatarGroup';
import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
import { flexColumn } from 'themes/themeStyles';

type ProjectMembersProps = {
count?: number;
members: Array<{
imageUrl?: string;
email?: string;
name: string;
}>;
};

const StyledContainer = styled('div')(({ theme }) => ({
...flexColumn,
alignItems: 'flex-end',
padding: theme.spacing(0, 2, 0, 1),
minWidth: 95,
}));

const StyledDescription = styled('span')(({ theme }) => ({
fontSize: theme.fontSizes.smallerBody,
color: theme.palette.text.secondary,
textWrap: 'nowrap',
}));

const StyledAvatar = styled(UserAvatar)(({ theme }) => ({
width: theme.spacing(2),
height: theme.spacing(2),
}));

const AvatarComponent = ({ ...props }) => (
<StyledAvatar {...props} disableTooltip />
);

export const ProjectMembers: FC<ProjectMembersProps> = ({
count = 0,
members,
}) => {
return (
<StyledContainer>
<StyledDescription data-loading>
{count} member
{count === 1 ? '' : 's'}
</StyledDescription>
<AvatarGroup
users={members}
avatarLimit={4}
AvatarComponent={AvatarComponent}
/>
</StyledContainer>
);
};
Loading

0 comments on commit 8923e28

Please sign in to comment.