Skip to content

Commit

Permalink
feat: Project owners UI (#6949)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: Thomas Heartman <thomas@getunleash.io>
  • Loading branch information
Tymek and thomasheartman committed Apr 29, 2024
1 parent 3978c69 commit b6865a5
Show file tree
Hide file tree
Showing 24 changed files with 428 additions and 265 deletions.
Binary file added frontend/public/logo-unleash.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions frontend/src/assets/icons/projectIconSmall.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ interface IGroupCardAvatarsProps {
users: IGroupUser[];
}

/**
* @deprecated Remove after with `projectsListNewCards` flag
*/
export const GroupCardAvatars = ({ users }: IGroupCardAvatarsProps) => {
const shownUsers = useMemo(
() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const StyledName = styled('div')(({ theme }) => ({
}));

interface IGroupPopoverProps {
user: IGroupUser | undefined;
user: Partial<IGroupUser & { description?: string }> | undefined;

open: boolean;
anchorEl: HTMLElement | null;
Expand Down Expand Up @@ -44,7 +44,7 @@ export const GroupPopover = ({
}}
>
<StyledName>{user?.name || user?.username}</StyledName>
<div>{user?.email}</div>
<div>{user?.description || user?.email}</div>
</StyledPopover>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { styled } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import type { IGroupUser } from 'interfaces/group';
import type React from 'react';
import { type ReactNode, useMemo, useState } from 'react';
import { GroupPopover } from './GroupPopover/GroupPopover';
import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
import { objectId } from 'utils/objectId';

const StyledContainer = styled('div')(() => ({
display: 'flex',
flexDirection: 'column',
}));

const StyledAvatars = styled('div')(({ theme }) => ({
display: 'inline-flex',
alignItems: 'center',
flexWrap: 'wrap',
marginLeft: theme.spacing(1),
}));

const StyledAvatar = styled(UserAvatar)(({ theme }) => ({
outline: `${theme.spacing(0.25)} solid ${theme.palette.background.paper}`,
marginLeft: theme.spacing(-1),
'&:hover': {
outlineColor: theme.palette.primary.main,
},
}));

const StyledUsername = styled('div')(({ theme }) => ({
fontSize: theme.typography.body2.fontSize,
color: theme.palette.text.primary,
marginLeft: theme.spacing(1),
}));

const StyledHeader = styled('h3')(({ theme }) => ({
margin: theme.spacing(0, 0, 1),
fontSize: theme.typography.caption.fontSize,
color: theme.palette.text.primary,
fontWeight: theme.typography.fontWeightRegular,
}));

interface IGroupCardAvatarsProps {
users: {
name: string;
description?: string;
imageUrl?: string;
}[];
header?: ReactNode;
}

export const GroupCardAvatars = ({
users = [],
header = null,
}: IGroupCardAvatarsProps) => {
const shownUsers = useMemo(
() =>
users
.sort((a, b) => {
if (
Object.hasOwn(a, 'joinedAt') &&
Object.hasOwn(b, 'joinedAt')
) {
return (
(b as IGroupUser)?.joinedAt!.getTime() -
(a as IGroupUser)?.joinedAt!.getTime()
);
}
return 0;
})
.slice(0, 9),
[users],
);

const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
const [popupUser, setPopupUser] = useState<{
name: string;
description?: string;
imageUrl?: string;
}>();

const onPopoverOpen = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};

const onPopoverClose = () => {
setAnchorEl(null);
};

const avatarOpen = Boolean(anchorEl);

return (
<StyledContainer>
<ConditionallyRender
condition={typeof header === 'string'}
show={<StyledHeader>{header}</StyledHeader>}
elseShow={header}
/>
<StyledAvatars>
{shownUsers.map((user) => (
<StyledAvatar
key={objectId(user)}
user={{ ...user, id: objectId(user) }}
onMouseEnter={(event) => {
onPopoverOpen(event);
setPopupUser(user);
}}
onMouseLeave={onPopoverClose}
/>
))}
<ConditionallyRender
condition={users.length > 9}
show={
<StyledAvatar>
+{users.length - shownUsers.length}
</StyledAvatar>
}
/>
<GroupPopover
open={avatarOpen}
user={popupUser}
anchorEl={anchorEl}
onPopoverClose={onPopoverClose}
/>
</StyledAvatars>
</StyledContainer>
);
};
41 changes: 25 additions & 16 deletions frontend/src/component/common/Badge/Badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,21 +59,30 @@ const StyledBadge = styled('span')<IBadgeProps>(
}),
);

const StyledBadgeIcon = styled('span')<IBadgeIconProps>(
({ theme, color = 'neutral', iconRight = false }) => ({
display: 'flex',
color:
color === 'disabled'
? theme.palette.action.disabled
: theme.palette[color].main,
margin: iconRight
? theme.spacing(0, 0, 0, 0.5)
: theme.spacing(0, 0.5, 0, 0),
}),
);
const StyledBadgeIcon = styled('span')<
IBadgeIconProps & { hasChildren?: boolean }
>(({ theme, color = 'neutral', iconRight = false, hasChildren }) => ({
display: 'flex',
color:
color === 'disabled'
? theme.palette.action.disabled
: theme.palette[color].main,
margin: iconRight
? theme.spacing(0, 0, 0, hasChildren ? 0.5 : 0)
: theme.spacing(0, hasChildren ? 0.5 : 0, 0, 0),
}));

const BadgeIcon = (color: Color, icon: ReactElement, iconRight = false) => (
<StyledBadgeIcon color={color} iconRight={iconRight}>
const BadgeIcon = (
color: Color,
icon: ReactElement,
iconRight = false,
hasChildren = false,
) => (
<StyledBadgeIcon
color={color}
iconRight={iconRight}
hasChildren={hasChildren}
>
<ConditionallyRender
condition={Boolean(icon?.props.sx)}
show={icon}
Expand Down Expand Up @@ -112,12 +121,12 @@ export const Badge: FC<IBadgeProps> = forwardRef(
>
<ConditionallyRender
condition={Boolean(icon) && !iconRight}
show={BadgeIcon(color, icon!)}
show={BadgeIcon(color, icon!, false, Boolean(children))}
/>
{children}
<ConditionallyRender
condition={Boolean(icon) && Boolean(iconRight)}
show={BadgeIcon(color, icon!, true)}
show={BadgeIcon(color, icon!, true, Boolean(children))}
/>
</StyledBadge>
),
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/component/common/UserAvatar/UserAvatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ const StyledAvatar = styled(Avatar)(({ theme }) => ({
}));

interface IUserAvatarProps extends AvatarProps {
user?: IUser;
user?: Partial<
Pick<IUser, 'id' | 'name' | 'email' | 'username' | 'imageUrl'>
>;
src?: string;
title?: string;
onMouseEnter?: (event: any) => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const StyledAccordionDetails = styled(AccordionDetails, {
background: theme.palette.envAccordion.expanded,
borderBottomLeftRadius: theme.shape.borderRadiusLarge,
borderBottomRightRadius: theme.shape.borderRadiusLarge,
boxShadow: 'inset 0px 2px 4px rgba(32, 32, 33, 0.05)', // replace this with variable
boxShadow: theme.boxShadows.accordionFooter,

[theme.breakpoints.down('md')]: {
padding: theme.spacing(2, 1),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Card, Box } from '@mui/material';
import Delete from '@mui/icons-material/Delete';
import Edit from '@mui/icons-material/Edit';
import { flexRow } from 'themes/themeStyles';
import { ReactComponent as ProjectIcon } from 'assets/icons/projectIconSmall.svg';

export const StyledProjectCard = styled(Card)(({ theme }) => ({
display: 'flex',
Expand Down Expand Up @@ -31,9 +32,9 @@ export const StyledDivHeader = styled('div')(({ theme }) => ({
marginBottom: theme.spacing(2),
}));

export const StyledH2Title = styled('h2')(({ theme }) => ({
fontWeight: 'normal',
fontSize: theme.fontSizes.bodySize,
export const StyledCardTitle = styled('h3')(({ theme }) => ({
fontWeight: theme.typography.fontWeightRegular,
fontSize: theme.typography.body1.fontSize,
lineClamp: '2',
WebkitLineClamp: 2,
lineHeight: '1.2',
Expand Down Expand Up @@ -65,13 +66,22 @@ export const StyledDivInfo = styled('div')(({ theme }) => ({
display: 'flex',
justifyContent: 'space-between',
fontSize: theme.fontSizes.smallerBody,
}));

export const StyledDivInfoContainer = styled('div')(() => ({
textAlign: 'center',
padding: theme.spacing(0, 1),
}));

export const StyledParagraphInfo = styled('p')(({ theme }) => ({
color: theme.palette.primary.dark,
fontWeight: 'bold',
fontSize: theme.typography.body1.fontSize,
}));

export const StyledProjectIcon = styled(ProjectIcon)(({ theme }) => ({
color: theme.palette.primary.main,
}));

export const StyledIconBox = styled(Box)(({ theme }) => ({
display: 'flex',
justifyContent: 'center',
padding: theme.spacing(0.5, 0.5, 0.5, 0),
marginRight: theme.spacing(2),
}));

0 comments on commit b6865a5

Please sign in to comment.