diff --git a/frontend/src/component/common/ScreenReaderOnly/ScreenReaderOnly.tsx b/frontend/src/component/common/ScreenReaderOnly/ScreenReaderOnly.tsx new file mode 100644 index 0000000000..17802db5bc --- /dev/null +++ b/frontend/src/component/common/ScreenReaderOnly/ScreenReaderOnly.tsx @@ -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', +})); diff --git a/frontend/src/component/common/UserAvatar/UserAvatar.tsx b/frontend/src/component/common/UserAvatar/UserAvatar.tsx index ae4732398c..d894ed7b62 100644 --- a/frontend/src/component/common/UserAvatar/UserAvatar.tsx +++ b/frontend/src/component/common/UserAvatar/UserAvatar.tsx @@ -36,6 +36,7 @@ export interface IUserAvatarProps extends AvatarProps { className?: string; sx?: SxProps; avatarWidth?: (theme: Theme) => string; + hideTitle?: boolean; } export const UserAvatar: FC = ({ @@ -47,9 +48,10 @@ export const UserAvatar: FC = ({ className, sx, children, + hideTitle, ...props }) => { - if (!title && !onMouseEnter && user) { + if (!hideTitle && !title && !onMouseEnter && user) { title = `${user?.name || user?.email || user?.username} (id: ${ user?.id })`; diff --git a/frontend/src/component/common/UserAvatar/UserAvatarWithPopover.tsx b/frontend/src/component/common/UserAvatar/UserAvatarWithPopover.tsx deleted file mode 100644 index 8796e7f93b..0000000000 --- a/frontend/src/component/common/UserAvatar/UserAvatarWithPopover.tsx +++ /dev/null @@ -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): void; -}; - -const StyledPopover = styled(Popover)(({ theme }) => ({ - pointerEvents: 'none', - '.MuiPaper-root': { - padding: theme.spacing(1), - }, -})); - -const AvatarPopover = ({ - mainText, - open, - anchorEl, - onPopoverClose, -}: PopoverProps) => { - return ( - -

{mainText}

-
- ); -}; - -type UserAvatarWithPopoverProps = Omit< - IUserAvatarProps, - 'user' | 'onMouseEnter' | 'onMouseLeave' -> & { - user: { - name: string; - id?: number; - imageUrl?: string; - }; -}; - -export const UserAvatarWithPopover: FC = ({ - user, - ...userAvatarProps -}) => { - const [anchorEl, setAnchorEl] = useState(null); - const onPopoverOpen = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); - }; - - const onPopoverClose = () => { - setAnchorEl(null); - }; - - const avatarOpen = Boolean(anchorEl); - - return ( - <> - { - onPopoverOpen(event); - }} - onMouseLeave={onPopoverClose} - /> - - - ); -}; diff --git a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/AvatarCell.tsx b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/AvatarCell.tsx new file mode 100644 index 0000000000..3552d830d9 --- /dev/null +++ b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/AvatarCell.tsx @@ -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 => + ({ row: { original } }) => { + return ( + + + onAvatarClick(original.createdBy.id)} + > + + + Show only flags created by{' '} + {original.createdBy.name} + + + + theme.spacing(3)} + /> + + + + ); + }; diff --git a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureToggles.tsx b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureToggles.tsx index ac8df912f6..a131997cad 100644 --- a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureToggles.tsx +++ b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureToggles.tsx @@ -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 ( - - theme.spacing(3) - } - /> - ); - }, + cell: AvatarCell(onAvatarClick), enableSorting: false, meta: { width: '1%', diff --git a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/useProjectFeatureSearch.ts b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/useProjectFeatureSearch.ts index f51651fd75..0b646081b0 100644 --- a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/useProjectFeatureSearch.ts +++ b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/useProjectFeatureSearch.ts @@ -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, }; };