mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-15 01:16:22 +02:00
feat: filter by user when interacting with the avatar (#7347)
This PR lets you filter by flag creator by interacting with the user's avatar. Additionally, I've switched the custom popover for the standard tooltip that we use elsewhere in the table. This gives the table a more cohesive feel. As such, I have also deleted the component created in a previous PR, because it's no longer in use anywhere. It now looks like this (when tabbed to; notice the focus ring): 
This commit is contained in:
parent
a602768c57
commit
3643016a0e
@ -0,0 +1,14 @@
|
||||
import { styled } from '@mui/material';
|
||||
|
||||
// Visually hide content, but make it available to screen readers
|
||||
export const ScreenReaderOnly = styled('div')(() => ({
|
||||
border: 0,
|
||||
clip: 'rect(0 0 0 0)',
|
||||
height: 'auto',
|
||||
margin: 0,
|
||||
overflow: 'hidden',
|
||||
padding: 0,
|
||||
position: 'absolute',
|
||||
width: '1px',
|
||||
whiteSpace: 'nowrap',
|
||||
}));
|
@ -36,6 +36,7 @@ export interface IUserAvatarProps extends AvatarProps {
|
||||
className?: string;
|
||||
sx?: SxProps<Theme>;
|
||||
avatarWidth?: (theme: Theme) => string;
|
||||
hideTitle?: boolean;
|
||||
}
|
||||
|
||||
export const UserAvatar: FC<IUserAvatarProps> = ({
|
||||
@ -47,9 +48,10 @@ export const UserAvatar: FC<IUserAvatarProps> = ({
|
||||
className,
|
||||
sx,
|
||||
children,
|
||||
hideTitle,
|
||||
...props
|
||||
}) => {
|
||||
if (!title && !onMouseEnter && user) {
|
||||
if (!hideTitle && !title && !onMouseEnter && user) {
|
||||
title = `${user?.name || user?.email || user?.username} (id: ${
|
||||
user?.id
|
||||
})`;
|
||||
|
@ -1,92 +0,0 @@
|
||||
import { styled, Popover } from '@mui/material';
|
||||
import { useState, type FC } from 'react';
|
||||
import type React from 'react';
|
||||
import { type IUserAvatarProps, UserAvatar } from './UserAvatar';
|
||||
|
||||
type PopoverProps = {
|
||||
mainText: string;
|
||||
open: boolean;
|
||||
anchorEl: HTMLElement | null;
|
||||
onPopoverClose(event: React.MouseEvent<HTMLElement>): void;
|
||||
};
|
||||
|
||||
const StyledPopover = styled(Popover)(({ theme }) => ({
|
||||
pointerEvents: 'none',
|
||||
'.MuiPaper-root': {
|
||||
padding: theme.spacing(1),
|
||||
},
|
||||
}));
|
||||
|
||||
const AvatarPopover = ({
|
||||
mainText,
|
||||
open,
|
||||
anchorEl,
|
||||
onPopoverClose,
|
||||
}: PopoverProps) => {
|
||||
return (
|
||||
<StyledPopover
|
||||
open={open}
|
||||
anchorEl={anchorEl}
|
||||
onClose={onPopoverClose}
|
||||
disableScrollLock={true}
|
||||
disableRestoreFocus={true}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'center',
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'center',
|
||||
}}
|
||||
>
|
||||
<p>{mainText}</p>
|
||||
</StyledPopover>
|
||||
);
|
||||
};
|
||||
|
||||
type UserAvatarWithPopoverProps = Omit<
|
||||
IUserAvatarProps,
|
||||
'user' | 'onMouseEnter' | 'onMouseLeave'
|
||||
> & {
|
||||
user: {
|
||||
name: string;
|
||||
id?: number;
|
||||
imageUrl?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export const UserAvatarWithPopover: FC<UserAvatarWithPopoverProps> = ({
|
||||
user,
|
||||
...userAvatarProps
|
||||
}) => {
|
||||
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
|
||||
const onPopoverOpen = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const onPopoverClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const avatarOpen = Boolean(anchorEl);
|
||||
|
||||
return (
|
||||
<>
|
||||
<UserAvatar
|
||||
{...userAvatarProps}
|
||||
user={user}
|
||||
data-loading
|
||||
onMouseEnter={(event) => {
|
||||
onPopoverOpen(event);
|
||||
}}
|
||||
onMouseLeave={onPopoverClose}
|
||||
/>
|
||||
<AvatarPopover
|
||||
mainText={user.name}
|
||||
open={avatarOpen}
|
||||
anchorEl={anchorEl}
|
||||
onPopoverClose={onPopoverClose}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
@ -0,0 +1,73 @@
|
||||
import { type Theme, styled } from '@mui/material';
|
||||
import type { FC } from 'react';
|
||||
import { visuallyHiddenStyles } from '../CreateProject/NewCreateProjectForm/ConfigButtons/shared.styles';
|
||||
import { ScreenReaderOnly } from 'component/common/ScreenReaderOnly/ScreenReaderOnly';
|
||||
import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip';
|
||||
import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
|
||||
|
||||
type AvatarCellProps = {
|
||||
row: {
|
||||
original: {
|
||||
createdBy: {
|
||||
id: number;
|
||||
name: string;
|
||||
imageUrl?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
const StyledContainer = styled('div')({
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
const StyledAvatarButton = styled('button')({
|
||||
border: 'none',
|
||||
background: 'none',
|
||||
cursor: 'pointer',
|
||||
borderRadius: '100%',
|
||||
padding: 0,
|
||||
});
|
||||
|
||||
export const VisuallyHiddenButtonText = styled('span')(() => ({
|
||||
...visuallyHiddenStyles,
|
||||
position: 'absolute',
|
||||
}));
|
||||
|
||||
export const AvatarCell =
|
||||
(onAvatarClick: (userId: number) => void): FC<AvatarCellProps> =>
|
||||
({ row: { original } }) => {
|
||||
return (
|
||||
<StyledContainer>
|
||||
<HtmlTooltip
|
||||
arrow
|
||||
describeChild
|
||||
title={original.createdBy.name}
|
||||
>
|
||||
<StyledAvatarButton
|
||||
onClick={() => onAvatarClick(original.createdBy.id)}
|
||||
>
|
||||
<ScreenReaderOnly>
|
||||
<span>
|
||||
Show only flags created by{' '}
|
||||
{original.createdBy.name}
|
||||
</span>
|
||||
</ScreenReaderOnly>
|
||||
|
||||
<UserAvatar
|
||||
hideTitle
|
||||
user={{
|
||||
id: original.createdBy.id,
|
||||
name: original.createdBy.name,
|
||||
imageUrl: original.createdBy.imageUrl,
|
||||
}}
|
||||
avatarWidth={(theme: Theme) => theme.spacing(3)}
|
||||
/>
|
||||
</StyledAvatarButton>
|
||||
</HtmlTooltip>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
@ -39,8 +39,7 @@ import {
|
||||
useProjectFeatureSearch,
|
||||
useProjectFeatureSearchActions,
|
||||
} from './useProjectFeatureSearch';
|
||||
import { UserAvatarWithPopover } from '../../../common/UserAvatar/UserAvatarWithPopover';
|
||||
import type { Theme } from '@mui/material';
|
||||
import { AvatarCell } from './AvatarCell';
|
||||
|
||||
interface IPaginatedProjectFeatureTogglesProps {
|
||||
environments: string[];
|
||||
@ -69,10 +68,8 @@ export const ProjectFeatureToggles = ({
|
||||
setTableState,
|
||||
} = useProjectFeatureSearch(projectId);
|
||||
|
||||
const { onFlagTypeClick, onTagClick } = useProjectFeatureSearchActions(
|
||||
tableState,
|
||||
setTableState,
|
||||
);
|
||||
const { onFlagTypeClick, onTagClick, onAvatarClick } =
|
||||
useProjectFeatureSearchActions(tableState, setTableState);
|
||||
|
||||
const filterState = {
|
||||
tag: tableState.tag,
|
||||
@ -175,20 +172,7 @@ export const ProjectFeatureToggles = ({
|
||||
columnHelper.accessor('createdBy', {
|
||||
id: 'createdBy',
|
||||
header: 'By',
|
||||
cell: ({ row: { original } }) => {
|
||||
return (
|
||||
<UserAvatarWithPopover
|
||||
user={{
|
||||
id: original.createdBy.id,
|
||||
name: original.createdBy.name,
|
||||
imageUrl: original.createdBy.imageUrl,
|
||||
}}
|
||||
avatarWidth={(theme: Theme) =>
|
||||
theme.spacing(3)
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
cell: AvatarCell(onAvatarClick),
|
||||
enableSorting: false,
|
||||
meta: {
|
||||
width: '1%',
|
||||
|
@ -19,7 +19,8 @@ import { useUiFlag } from 'hooks/useUiFlag';
|
||||
|
||||
type Attribute =
|
||||
| { key: 'tag'; operator: 'INCLUDE' }
|
||||
| { key: 'type'; operator: 'IS' };
|
||||
| { key: 'type'; operator: 'IS' }
|
||||
| { key: 'createdBy'; operator: 'IS' };
|
||||
|
||||
export const useProjectFeatureSearch = (
|
||||
projectId: string,
|
||||
@ -102,9 +103,15 @@ export const useProjectFeatureSearchActions = (
|
||||
onAttributeClick({ key: 'tag', operator: 'INCLUDE' }, tag);
|
||||
const onFlagTypeClick = (type: string) =>
|
||||
onAttributeClick({ key: 'type', operator: 'IS' }, type);
|
||||
const onAvatarClick = (userId: number) =>
|
||||
onAttributeClick(
|
||||
{ key: 'createdBy', operator: 'IS' },
|
||||
userId.toString(),
|
||||
);
|
||||
|
||||
return {
|
||||
onFlagTypeClick,
|
||||
onTagClick,
|
||||
onAvatarClick,
|
||||
};
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user